From 3bc3f15f9aa511b8ad83032691bc8bc5ebb5c32e Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 19 Sep 2026 22:15:09 -0400 Subject: [PATCH 01/33] Add decode_fields, remove_field and encoders to the protobuf codec --- sharklocal/protobuf.py | 136 ++++++++++++++++++++++++++++++++++++++++- tests/test_protobuf.py | 114 +++++++++++++++++++++++++++++++++- 2 files changed, 246 insertions(+), 4 deletions(-) diff --git a/sharklocal/protobuf.py b/sharklocal/protobuf.py index f89436f..6c8ca54 100644 --- a/sharklocal/protobuf.py +++ b/sharklocal/protobuf.py @@ -1,13 +1,20 @@ -"""Pure-Python protobuf decoder for SharkIQ MQTT messages. +"""Pure-Python protobuf codec for SharkIQ MQTT messages. -Decodes raw protobuf wire format without a compiled schema. +Decodes raw protobuf wire format without a compiled schema, and encodes the +few field shapes needed to build command payloads. Adapted for the SharkIQ local MQTT protocol. """ from __future__ import annotations import struct -from typing import Any, Dict, Tuple +from typing import Any, Dict, List, Tuple + +# Wire types, per the protobuf encoding specification. +WIRE_VARINT = 0 +WIRE_FIXED64 = 1 +WIRE_LENGTH_DELIMITED = 2 +WIRE_FIXED32 = 5 def _decode_varint(data: bytes, pos: int) -> Tuple[int, int]: @@ -76,3 +83,126 @@ def decode_raw(data: bytes) -> Dict[int, Any]: break # pragma: no cover return result + + +def decode_fields(data: bytes) -> Dict[int, List[Any]]: + """Decode one protobuf message level, keeping every occurrence of a field. + + Unlike :func:`decode_raw`, this does **not** recurse into length-delimited + fields and does **not** collapse repeated fields — each field number maps + to the list of values in wire order. Length-delimited values are returned + as ``bytes`` for the caller to interpret (nested message, string, or a + packed binary blob such as a map raster), and 32-bit fixed values are + returned as the raw unsigned integer (see :func:`float32`). + + Use this for messages whose layout is known, where ``decode_raw``'s + heuristics would mis-parse binary blobs or drop repeated entries. + """ + result: Dict[int, List[Any]] = {} + pos = 0 + + while pos < len(data): + tag, pos = _decode_varint(data, pos) + field_num = tag >> 3 + wire_type = tag & 0x7 + + if wire_type == WIRE_VARINT: + value, pos = _decode_varint(data, pos) + elif wire_type == WIRE_FIXED64: + value = struct.unpack(" float: + """Reinterpret a decoded 32-bit fixed field as an IEEE-754 ``float``.""" + return struct.unpack(" bytes: + """Return *data* with every top-level occurrence of *field_num* removed. + + Other fields are copied byte-for-byte, so the result decodes identically + apart from the dropped field. Parsing stops at an unknown wire type, and + the remainder is kept as-is. + """ + out = bytearray() + pos = 0 + + while pos < len(data): + start = pos + tag, pos = _decode_varint(data, pos) + wire_type = tag & 0x7 + + if wire_type == WIRE_VARINT: + _, pos = _decode_varint(data, pos) + elif wire_type == WIRE_FIXED64: + pos += 8 + elif wire_type == WIRE_LENGTH_DELIMITED: + length, pos = _decode_varint(data, pos) + pos += length + elif wire_type == WIRE_FIXED32: + pos += 4 + else: + out += data[start:] + break + + if (tag >> 3) != field_num: + out += data[start:pos] + + return bytes(out) + + +# --------------------------------------------------------------------------- +# Encoding — enough to build command payloads +# --------------------------------------------------------------------------- + + +def encode_varint(value: int) -> bytes: + """Encode a non-negative integer as a protobuf varint.""" + out = bytearray() + while True: + byte = value & 0x7F + value >>= 7 + if value: + out.append(byte | 0x80) + else: + out.append(byte) + return bytes(out) + + +def encode_varint_field(field_num: int, value: int) -> bytes: + """Encode ``field_num`` as a varint field.""" + return encode_varint((field_num << 3) | WIRE_VARINT) + encode_varint(value) + + +def encode_float_field(field_num: int, value: float) -> bytes: + """Encode ``field_num`` as a 32-bit ``float`` field.""" + return encode_varint((field_num << 3) | WIRE_FIXED32) + struct.pack(" bytes: + """Encode ``field_num`` as a length-delimited field (nested message or bytes).""" + return ( + encode_varint((field_num << 3) | WIRE_LENGTH_DELIMITED) + + encode_varint(len(content)) + + content + ) + + +def encode_string_field(field_num: int, text: str) -> bytes: + """Encode ``field_num`` as a UTF-8 string field.""" + return encode_bytes_field(field_num, text.encode("utf-8")) diff --git a/tests/test_protobuf.py b/tests/test_protobuf.py index 15b4567..71db1fc 100644 --- a/tests/test_protobuf.py +++ b/tests/test_protobuf.py @@ -4,7 +4,18 @@ import pytest -from sharklocal.protobuf import _decode_varint, decode_raw +from sharklocal.protobuf import ( + _decode_varint, + decode_fields, + decode_raw, + encode_bytes_field, + encode_float_field, + encode_string_field, + encode_varint, + encode_varint_field, + float32, + remove_field, +) # --------------------------------------------------------------------------- @@ -207,3 +218,104 @@ def test_decode_raw_wire_type_2_nested_exception_falls_back_to_bytes(): result = decode_raw(data) assert isinstance(result[5], bytes) assert result[5] == inner + + +# --------------------------------------------------------------------------- +# decode_fields — one level, every occurrence, no recursion +# --------------------------------------------------------------------------- + + +def test_decode_fields_keeps_repeated_values_in_order(): + data = _make_field(1, 0, _varint_encode(10)) + _make_field(1, 0, _varint_encode(99)) + assert decode_fields(data) == {1: [10, 99]} + + +def test_decode_fields_returns_length_delimited_as_bytes(): + inner = _make_field(8, 0, _varint_encode(75)) + data = _make_field(9, 2, _length_delimited(inner)) + result = decode_fields(data) + assert result[9] == [inner] + + +def test_decode_fields_fixed_widths(): + data = _make_field(2, 1, struct.pack(" Date: Sat, 19 Sep 2026 22:15:09 -0400 Subject: [PATCH 02/33] Decode MQTT map frames into VacuumMap Live frames carry the occupancy grid, cleaned path and pose log; the persisted frame published on docking adds rooms, wall/door vectors, dock pose, job stats and the robot's event log. VacuumStatus gains job_active, deep_clean, recharge_resume and evac_resume. Captured RV2610BFCA frames are included as fixtures. --- sharklocal/__init__.py | 25 +- sharklocal/models.py | 147 ++++++- sharklocal/mqtt_client.py | 60 ++- sharklocal/vacuum_map.py | 340 ++++++++++++++++ tests/fixtures/sharkiq_cmd_room_clean.b64 | 1 + .../sharkiq_cmd_room_clean_matrix.b64 | 1 + tests/fixtures/sharkiq_cmd_spot_clean.b64 | 1 + tests/fixtures/sharkiq_live_map_frame.b64 | 1 + .../fixtures/sharkiq_persisted_map_frame.b64 | 1 + tests/fixtures/sharkiq_status_frame.b64 | 1 + tests/test_models.py | 123 ++++++ tests/test_mqtt_client.py | 116 ++++++ tests/test_vacuum_map.py | 382 ++++++++++++++++++ 13 files changed, 1196 insertions(+), 3 deletions(-) create mode 100644 sharklocal/vacuum_map.py create mode 100644 tests/fixtures/sharkiq_cmd_room_clean.b64 create mode 100644 tests/fixtures/sharkiq_cmd_room_clean_matrix.b64 create mode 100644 tests/fixtures/sharkiq_cmd_spot_clean.b64 create mode 100644 tests/fixtures/sharkiq_live_map_frame.b64 create mode 100644 tests/fixtures/sharkiq_persisted_map_frame.b64 create mode 100644 tests/fixtures/sharkiq_status_frame.b64 create mode 100644 tests/test_vacuum_map.py diff --git a/sharklocal/__init__.py b/sharklocal/__init__.py index e8e6a52..ca2963b 100644 --- a/sharklocal/__init__.py +++ b/sharklocal/__init__.py @@ -41,7 +41,21 @@ load_mqtt_mapping, load_rest_mapping, ) -from .models import DeviceInfo, ProbeResult, VacuumEvent, VacuumMode, VacuumStatus +from .models import ( + DeviceInfo, + MapFeature, + MapGrid, + MapPoint, + MapPose, + MapRoom, + ProbeResult, + SuctionLevel, + VacuumEvent, + VacuumLogEntry, + VacuumMap, + VacuumMode, + VacuumStatus, +) from .mqtt_client import MQTTVacuumClient, register_decoder from .rest_client import RESTVacuumClient @@ -69,6 +83,15 @@ "VacuumEvent", "DeviceInfo", "ProbeResult", + "SuctionLevel", + # Map models + "VacuumMap", + "MapGrid", + "MapPoint", + "MapPose", + "MapRoom", + "MapFeature", + "VacuumLogEntry", # Exceptions "SharklocalError", "ConnectError", diff --git a/sharklocal/models.py b/sharklocal/models.py index 2d897c5..4cf67f4 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -19,15 +19,32 @@ class VacuumMode(str, Enum): EXPLORING = "exploring" # Mapping/exploration run in progress +class SuctionLevel(str, Enum): + """Suction power levels, named as in the SharkClean app.""" + + ECO = "eco" + NORMAL = "normal" + MAX = "max" + @dataclass class VacuumStatus: - """Normalized vacuum status, independent of transport protocol.""" + """Normalized vacuum status, independent of transport protocol. + + The optional fields after ``raw`` are only populated by transports that + carry them (currently the SharkIQ MQTT protocol) and stay ``None`` + otherwise. ``map`` is set only on frames that include map data. + """ mode: VacuumMode battery_level: Optional[int] = None charging: Optional[bool] = None raw: Dict[str, Any] = field(default_factory=dict) + job_active: Optional[bool] = None + deep_clean: Optional[bool] = None # Matrix / spot clean in progress + recharge_resume: Optional[bool] = None + evac_resume: Optional[bool] = None + map: Optional["VacuumMap"] = None @property def is_cleaning(self) -> bool: @@ -38,6 +55,134 @@ def is_docked(self) -> bool: return self.mode in (VacuumMode.DOCKED, VacuumMode.DOCKING) +# --------------------------------------------------------------------------- +# Map models (SharkIQ MQTT map frames) +# --------------------------------------------------------------------------- + + +@dataclass +class MapPoint: + """A point in map world coordinates, in metres.""" + + x: float + y: float + + +@dataclass +class MapPose: + """A position with heading, in metres and radians (0 = +x, anticlockwise).""" + + x: float + y: float + heading: float + + +@dataclass +class MapGrid: + """An occupancy grid, row-major from the map origin (bottom-left). + + ``cells`` holds one byte per cell; ``room_ids`` (persisted maps only) holds + one room segment id per cell, ``0`` meaning unassigned. World-to-cell: + ``col = (x - origin.x) / resolution``, ``row = (y - origin.y) / resolution``. + """ + + resolution: float # metres per cell + width: int + height: int + origin: MapPoint + cells: bytes + room_ids: Optional[bytes] = None + + # Cell values observed in SharkIQ grids. + UNKNOWN = 0x4B + VOID = 0x00 + + @staticmethod + def is_wall(value: int) -> bool: + """``True`` for wall/obstacle cells (``0x50`` and above).""" + return value >= 0x50 + + @staticmethod + def is_floor(value: int) -> bool: + """``True`` for free-floor cells (explored, not wall, not void).""" + return 0 < value < 0x4B + + def cell(self, col: int, row: int) -> int: + """Return the occupancy value at ``(col, row)``.""" + return self.cells[row * self.width + col] + + def room_id(self, col: int, row: int) -> Optional[int]: + """Return the room segment id at ``(col, row)``, or ``None`` if unavailable.""" + if self.room_ids is None: + return None + return self.room_ids[row * self.width + col] + + +@dataclass +class MapRoom: + """A named room as drawn in the app, with its bounding polygon.""" + + name: str + polygon: List[MapPoint] + selected: bool = False # Included in the most recent job + coverage: Optional[float] = None # Fraction cleaned in the most recent job + + +@dataclass +class MapFeature: + """A vector feature of the persisted map — a wall outline or a door.""" + + kind: str # "edge" or "door" + points: List[MapPoint] + + +@dataclass +class VacuumLogEntry: + """One entry of the robot's own event log, carried in the persisted map.""" + + key: str # e.g. "DT_STATE_CHANGE", "DT_WARNING_CODE" + time: int # Unix epoch seconds as reported by the robot + code: str + + +@dataclass +class VacuumMap: + """Map data decoded from a SharkIQ MQTT map frame. + + Live frames (every few seconds during a job) carry the grid, the path + cleaned so far and the robot's pose history. The larger *persisted* frame, + published once when the robot reaches the dock, adds rooms, wall and door + vectors, the dock pose, job statistics and the robot's event log. + """ + + grid: MapGrid + path: List[MapPoint] = field(default_factory=list) + poses: List[MapPose] = field(default_factory=list) + persisted: bool = False + map_id: Optional[str] = None + name: Optional[str] = None + dock: Optional[MapPose] = None + rooms: List[MapRoom] = field(default_factory=list) + features: List[MapFeature] = field(default_factory=list) + log: List[VacuumLogEntry] = field(default_factory=list) + job_started: Optional[int] = None # Unix epoch seconds + job_duration: Optional[int] = None # seconds + cleaned_cells: Optional[int] = None + raw: Dict[str, Any] = field(default_factory=dict) + + @property + def robot(self) -> Optional[MapPose]: + """Current robot pose: the last recorded pose, if any.""" + return self.poses[-1] if self.poses else None + + @property + def cleaned_area(self) -> Optional[float]: + """Cleaned area of the job in square metres, if reported.""" + if self.cleaned_cells is None: + return None + return self.cleaned_cells * self.grid.resolution**2 + + @dataclass class VacuumEvent: """A single event from the vacuum event log.""" diff --git a/sharklocal/mqtt_client.py b/sharklocal/mqtt_client.py index 9b77d77..264edf2 100644 --- a/sharklocal/mqtt_client.py +++ b/sharklocal/mqtt_client.py @@ -9,7 +9,7 @@ from .exceptions import ActionNotSupportedError, CommandError, ConnectError, DecoderError from .mappings.base import MQTTMappingConfig from .models import VacuumMode, VacuumStatus -from . import protobuf +from . import protobuf, vacuum_map # Registry mapping decoder name -> callable(payload_bytes, modes) -> VacuumStatus. @@ -48,7 +48,23 @@ def _decode_sharkiq_protobuf_v1( * Field 1 — ``ChargingState`` (3 = ``CHARGING_ON_DOCK``) * Field 8 — ``battery_percent`` (0–100) + + * Field 35 — present (``2``) while Recharge & Resume is enabled + * Field 36 — present (``2``) while Evac & Resume is enabled + * Field 40 — ``2`` normal clean / ``1`` Matrix or Spot clean (during a job) + * Field 45 — ``2`` while a job is active, ``0`` otherwise + * Field 7 — map data on map-bearing frames (see :mod:`sharklocal.vacuum_map`) """ + fields = protobuf.decode_fields(payload) + + # Map frames are large binary blobs that decode_raw would mis-parse as + # nested messages; decode them separately and keep them out of ``raw``. + map_blob = fields.pop(vacuum_map.FIELD_MAP, None) + decoded_map = None + if map_blob is not None: + decoded_map = vacuum_map.decode_map({vacuum_map.FIELD_MAP: map_blob, **fields}) + payload = protobuf.remove_field(payload, vacuum_map.FIELD_MAP) + raw = protobuf.decode_raw(payload) mode_int = raw.get(4, 0) @@ -66,11 +82,24 @@ def _decode_sharkiq_protobuf_v1( charging_state = battery_info.get(1, 0) charging = charging_state == 3 # ChargingState.CHARGING_ON_DOCK + # Settings are reflected by presence: the field disappears when switched off. + recharge_resume = 35 in raw + evac_resume = 36 in raw + job_active = raw.get(45) == 2 + deep_clean: Optional[bool] = None + if 40 in raw: + deep_clean = raw[40] == 1 + return VacuumStatus( mode=mode, battery_level=battery_percent, charging=charging, raw={"protobuf_fields": raw}, + job_active=job_active, + deep_clean=deep_clean, + recharge_resume=recharge_resume, + evac_resume=evac_resume, + map=decoded_map, ) @@ -153,6 +182,35 @@ async def call(self, action: str) -> Any: raise CommandError(f"Unrecognised MQTT action type '{spec.type}'") + async def send(self, payload: bytes) -> bool: + """Publish a raw command payload that is built at runtime. + + Used for commands whose content depends on state — room selection + carries the map's room definition — and so cannot be a fixed mapping + action. *payload* is the protobuf message bytes; it is encoded per the + mapping's ``encoding`` before publishing. + + Raises: + ConnectError: If the MQTT broker cannot be reached. + """ + try: + import aiomqtt + except ImportError as exc: + raise ConnectError("aiomqtt is required for MQTT support") from exc + + encoded: Any = payload + if self.mapping.encoding == "base64": + encoded = base64.b64encode(payload).decode("ascii") + + try: + async with aiomqtt.Client(self.host, port=self.mapping.port) as client: + await client.publish(self.mapping.command_topic, payload=encoded) + except Exception as exc: + raise ConnectError( + f"MQTT error connecting to {self.host}:{self.mapping.port}: {exc}" + ) from exc + return True + async def _request_status(self, command_payload: str, timeout: float) -> VacuumStatus: """Publish a status-request command and return the decoded first response.""" try: diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py new file mode 100644 index 0000000..43f3f68 --- /dev/null +++ b/sharklocal/vacuum_map.py @@ -0,0 +1,340 @@ +"""Map decoding and room-command encoding for the SharkIQ MQTT protocol. + +The robot publishes two kinds of map-bearing frames on the status topic, both +inside top-level field ``7``: + +* **Live frames** (~27 KB, every few seconds while a job runs) — the occupancy + grid, the path cleaned so far and the robot's pose history. +* **Persisted frame** (~92 KB, once, as the robot reaches the dock; flagged by + top-level field ``3 = 1``) — the saved map with rooms, wall and door vectors, + the dock pose, job statistics and the robot's own event log. + +Coordinates are metres in the map frame; ``cell = (m - origin) / resolution``. +Everything here was reverse-engineered from live captures, so field meanings +are documented at the point of use rather than by a ``.proto`` name. +""" + +from __future__ import annotations + +import json +import struct +from typing import Any, Dict, List, Optional + +from . import protobuf +from .models import ( + MapFeature, + MapGrid, + MapPoint, + MapPose, + MapRoom, + VacuumLogEntry, + VacuumMap, +) + +# Top-level fields of a status frame that carry map data. +FIELD_MAP = 7 +FIELD_PERSISTED_FLAG = 3 + +# Room-record fields the app adds to a room selected for Matrix (deep) Clean. +_DEEP_CLEAN_FIELDS = ((7, 2), (9, 0), (10, 2)) +# Spot Clean is a synthetic room of this name with a square polygon around the pin. +SPOT_ROOM_NAME = "PinDrop" +SPOT_HALF_SIZE = 0.762 # metres — the app draws a ~1.5 m square + + +def _fields(data: bytes) -> Dict[int, List[Any]]: + return protobuf.decode_fields(data) + + +def _first(fields: Dict[int, List[Any]], num: int, default: Any = None) -> Any: + values = fields.get(num) + return values[0] if values else default + + +def _text(value: Optional[bytes]) -> Optional[str]: + return value.decode("utf-8", errors="replace") if value is not None else None + + +# --------------------------------------------------------------------------- +# Decoding +# --------------------------------------------------------------------------- + + +def has_map(fields: Dict[int, List[Any]]) -> bool: + """``True`` if a decoded status frame carries an occupancy grid.""" + blob = _first(fields, FIELD_MAP) + return isinstance(blob, bytes) and 5 in _fields(blob) + + +def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: + """Decode the map carried in a status frame. + + Args: + fields: The frame as returned by :func:`sharklocal.protobuf.decode_fields`. + + Returns: + A :class:`~sharklocal.models.VacuumMap`, or ``None`` if the frame has + no grid (ordinary status frames). + """ + blob = _first(fields, FIELD_MAP) + if not isinstance(blob, bytes): + return None + m = _fields(blob) + if 5 not in m: + return None + + grid = _decode_grid(m[5][0]) + persisted = _first(fields, FIELD_PERSISTED_FLAG) == 1 + + vacuum_map = VacuumMap( + grid=grid, + path=_decode_points(_first(m, 8)), + poses=_decode_poses(_first(m, 10)), + persisted=persisted, + map_id=_text(_first(m, 3)), + name=_text(_first(m, 2)), + dock=_decode_pose_f32(_first(m, 7)), + rooms=[_decode_room(r) for r in m.get(15, [])], + features=[f for f in (_decode_feature(r) for r in m.get(28, [])) if f], + ) + + # Field 21 is the persisted grid; its sub-field 19 is the room-id raster. + persisted_grid = _first(m, 21) + if isinstance(persisted_grid, bytes): + g21 = _fields(persisted_grid) + room_ids = _first(g21, 19) + if isinstance(room_ids, bytes) and len(room_ids) == len(grid.cells): + grid.room_ids = room_ids + + # Field 11 is the job summary: .1 start epoch, .3 duration s, .6 cleaned cells. + stats = _first(m, 11) + if isinstance(stats, bytes): + s = _fields(stats) + vacuum_map.job_started = _first(s, 1) + vacuum_map.job_duration = _first(s, 3) + vacuum_map.cleaned_cells = _first(s, 6) + + # Field 20.3.6 is the robot's event log as a JSON array of {key, time, code}. + vacuum_map.log = _decode_log(_first(m, 20)) + + return vacuum_map + + +def _decode_grid(data: bytes) -> MapGrid: + """Field 7.5: ``.1`` resolution f32, ``.2`` origin {x,y} f32, ``.3``/``.4`` + width/height, ``.6`` one byte per cell.""" + g = _fields(data) + origin = _fields(_first(g, 2, b"")) + return MapGrid( + resolution=protobuf.float32(_first(g, 1, 0)), + width=_first(g, 3, 0), + height=_first(g, 4, 0), + origin=MapPoint( + protobuf.float32(_first(origin, 1, 0)), + protobuf.float32(_first(origin, 2, 0)), + ), + cells=_first(g, 6, b""), + ) + + +def _decode_int16_list(data: Optional[bytes], stride: int) -> List[tuple]: + """Fields 7.8 / 7.10: ``.1`` scale f32, ``.3`` packed little-endian int16 tuples. + + Every component (metres, and radians for the heading) is ``int16 * scale``. + """ + if not isinstance(data, bytes): + return [] + p = _fields(data) + scale = protobuf.float32(_first(p, 1, 0)) + raw = _first(p, 3, b"") + count = len(raw) // (2 * stride) + values = struct.unpack(f"<{count * stride}h", raw[: count * stride * 2]) + return [ + tuple(v * scale for v in values[i : i + stride]) + for i in range(0, count * stride, stride) + ] + + +def _decode_points(data: Optional[bytes]) -> List[MapPoint]: + return [MapPoint(x, y) for x, y in _decode_int16_list(data, 2)] + + +def _decode_poses(data: Optional[bytes]) -> List[MapPose]: + return [MapPose(x, y, h) for x, y, h in _decode_int16_list(data, 3)] + + +def _decode_pose_f32(data: Optional[bytes]) -> Optional[MapPose]: + """Field 7.7: ``.1`` x, ``.2`` y, ``.3`` heading — all f32.""" + if not isinstance(data, bytes): + return None + p = _fields(data) + return MapPose( + protobuf.float32(_first(p, 1, 0)), + protobuf.float32(_first(p, 2, 0)), + protobuf.float32(_first(p, 3, 0)), + ) + + +def _decode_point_f32(data: bytes) -> MapPoint: + """``.1`` x f32, ``.2`` y f32 — used by room polygons and features.""" + p = _fields(data) + return MapPoint( + protobuf.float32(_first(p, 1, 0)), + protobuf.float32(_first(p, 2, 0)), + ) + + +def _decode_room(data: bytes) -> MapRoom: + """Field 7.15: ``.2`` name, ``.4`` polygon of ``.1`` points, ``.5`` selected, + ``.6`` coverage fraction (f32).""" + r = _fields(data) + polygon = _fields(_first(r, 4, b"")) + coverage = _first(r, 6) + return MapRoom( + name=_text(_first(r, 2, b"")) or "", + polygon=[_decode_point_f32(pt) for pt in polygon.get(1, [])], + selected=_first(r, 5) == 1, + coverage=protobuf.float32(coverage) if coverage is not None else None, + ) + + +def _decode_feature(data: bytes) -> Optional[MapFeature]: + """Field 7.28: ``.2`` kind ("edge" / "door"), ``.4`` polyline of ``.1`` points.""" + f = _fields(data) + kind = _text(_first(f, 2)) + polyline = _first(f, 4) + if kind is None or not isinstance(polyline, bytes): + return None + points = [_decode_point_f32(pt) for pt in _fields(polyline).get(1, [])] + return MapFeature(kind=kind, points=points) + + +def _decode_log(data: Optional[bytes]) -> List[VacuumLogEntry]: + """Field 7.20.3.6: JSON ``[{"key": ..., "time": "s", "code": ...}]``.""" + if not isinstance(data, bytes): + return [] + inner = _first(_fields(data), 3) + if not isinstance(inner, bytes): + return [] + text = _first(_fields(inner), 6) + if not isinstance(text, bytes): + return [] + try: + entries = json.loads(text.decode("utf-8", errors="replace")) + except ValueError: + return [] + log: List[VacuumLogEntry] = [] + for entry in entries: + if not isinstance(entry, dict): + continue + time_text = str(entry.get("time", "0")).rstrip("s") + log.append( + VacuumLogEntry( + key=str(entry.get("key", "")), + time=int(time_text) if time_text.isdigit() else 0, + code=str(entry.get("code", "")), + ) + ) + return log + + +# --------------------------------------------------------------------------- +# Encoding — room and spot selection +# --------------------------------------------------------------------------- + + +def _encode_point(point: MapPoint) -> bytes: + return protobuf.encode_bytes_field( + 1, + protobuf.encode_float_field(1, point.x) + protobuf.encode_float_field(2, point.y), + ) + + +def _encode_room(room: MapRoom, *, deep: bool, spot: bool = False) -> bytes: + """One field-15 room record, in the shape the app sends it.""" + polygon = b"".join(_encode_point(pt) for pt in room.polygon) + record = ( + protobuf.encode_string_field(2, room.name) + + protobuf.encode_string_field(3, room.name) + + protobuf.encode_bytes_field(4, polygon) + ) + if deep or spot: + for num, value in _DEEP_CLEAN_FIELDS: + record += protobuf.encode_varint_field(num, value) + if spot and num == 7: + record += protobuf.encode_varint_field(8, 1) + return record + + +def encode_room_selection( + vacuum_map: VacuumMap, + room_names: List[str], + *, + deep: bool = False, +) -> bytes: + """Build the "set cleaning area" message for a room clean. + + Mirrors what the SharkClean app publishes before ``start_cleaning``: the + map's full room definition (field ``6``), the command ``16 = 11``, and the + selected room names in field ``41``. Rooms named in *room_names* are marked + for Matrix Clean when *deep* is set. + + Args: + vacuum_map: A persisted map carrying the room definition. + room_names: Names of the rooms to clean, as shown in the app. + deep: Request a Matrix (two-pass) clean of the selected rooms. + + Raises: + ValueError: If a name is not a room of *vacuum_map*, or the map has no id. + """ + if vacuum_map.map_id is None: + raise ValueError("Map has no id; room cleaning needs a persisted map") + known = {room.name: room for room in vacuum_map.rooms} + missing = [name for name in room_names if name not in known] + if missing: + raise ValueError(f"Unknown room(s) {missing}; map has {sorted(known)}") + + rooms = b"".join( + protobuf.encode_bytes_field( + 15, _encode_room(room, deep=deep and room.name in room_names) + ) + for room in vacuum_map.rooms + ) + return _encode_selection(vacuum_map.map_id, rooms, room_names) + + +def encode_spot_selection(vacuum_map: VacuumMap, x: float, y: float) -> bytes: + """Build the "set cleaning area" message for a Spot Clean centred on ``(x, y)``. + + The app models a spot clean as an extra room named ``"PinDrop"`` with a + square polygon around the pin, appended to the map's room definition. + """ + if vacuum_map.map_id is None: + raise ValueError("Map has no id; spot cleaning needs a persisted map") + d = SPOT_HALF_SIZE + spot = MapRoom( + name=SPOT_ROOM_NAME, + polygon=[ + MapPoint(x - d, y + d), + MapPoint(x + d, y + d), + MapPoint(x + d, y - d), + MapPoint(x - d, y - d), + ], + ) + rooms = b"".join( + protobuf.encode_bytes_field(15, _encode_room(room, deep=False)) + for room in vacuum_map.rooms + ) + protobuf.encode_bytes_field(15, _encode_room(spot, deep=True, spot=True)) + return _encode_selection(vacuum_map.map_id, rooms, [SPOT_ROOM_NAME]) + + +def _encode_selection(map_id: str, rooms: bytes, names: List[str]) -> bytes: + definition = protobuf.encode_string_field(3, map_id) + rooms + selection = b"".join(protobuf.encode_string_field(1, name) for name in names) + selection += protobuf.encode_string_field(3, map_id) + return ( + protobuf.encode_bytes_field(6, definition) + + protobuf.encode_varint_field(16, 11) + + protobuf.encode_varint_field(39, 2) + + protobuf.encode_bytes_field(41, selection) + ) diff --git a/tests/fixtures/sharkiq_cmd_room_clean.b64 b/tests/fixtures/sharkiq_cmd_room_clean.b64 new file mode 100644 index 0000000..6fb27e0 --- /dev/null +++ b/tests/fixtures/sharkiq_cmd_room_clean.b64 @@ -0,0 +1 @@ +MqwCGggzQ0RCQkM1OXpKEgpCYXRocm9vbS4gGgpCYXRocm9vbS4gIjAKCg0PqB7AFau6dUAKCg1actq+Fau6dUAKCg1actq+FfL8tj8KCg0PqB7AFQH9tj96PhIEUm9vbRoEUm9vbSIwCgoNJIjfwBUfOJJACgoNX78nwBUfOJJACgoNX78nwBX9DZS/CgoNJIjfwBX9DZS/ek4SDExhdW5kcnkgUm9vbRoMTGF1bmRyeSBSb29tIjAKCg2WqHo/FZzB5D8KCg3xL5BAFZzB5D8KCg3xL5BAFQuq3r8KCg2WqHo/FQuq3r96RBIHSGFsbHdheRoHSGFsbHdheSIwCgoNqNIrwBUj3rg/CgoNjmt+PxUj3rg/CgoNjmt+PxVqdALACgoNqNIrwBVqdALAgAELuAICygIWCgpCYXRocm9vbS4gGggzQ0RCQkM1OQ== \ No newline at end of file diff --git a/tests/fixtures/sharkiq_cmd_room_clean_matrix.b64 b/tests/fixtures/sharkiq_cmd_room_clean_matrix.b64 new file mode 100644 index 0000000..a9b1bfa --- /dev/null +++ b/tests/fixtures/sharkiq_cmd_room_clean_matrix.b64 @@ -0,0 +1 @@ +MrICGggzQ0RCQkM1OXpQEgpCYXRocm9vbS4gGgpCYXRocm9vbS4gIjAKCg0PqB7AFau6dUAKCg1actq+Fau6dUAKCg1actq+FfL8tj8KCg0PqB7AFQH9tj84AkgAUAJ6PhIEUm9vbRoEUm9vbSIwCgoNJIjfwBUfOJJACgoNX78nwBUfOJJACgoNX78nwBX9DZS/CgoNJIjfwBX9DZS/ek4SDExhdW5kcnkgUm9vbRoMTGF1bmRyeSBSb29tIjAKCg2WqHo/FZzB5D8KCg3xL5BAFZzB5D8KCg3xL5BAFQuq3r8KCg2WqHo/FQuq3r96RBIHSGFsbHdheRoHSGFsbHdheSIwCgoNqNIrwBUj3rg/CgoNjmt+PxUj3rg/CgoNjmt+PxVqdALACgoNqNIrwBVqdALAgAELuAICygIWCgpCYXRocm9vbS4gGggzQ0RCQkM1OQ== \ No newline at end of file diff --git a/tests/fixtures/sharkiq_cmd_spot_clean.b64 b/tests/fixtures/sharkiq_cmd_spot_clean.b64 new file mode 100644 index 0000000..f07a75b --- /dev/null +++ b/tests/fixtures/sharkiq_cmd_spot_clean.b64 @@ -0,0 +1 @@ +MvoCGggzQ0RCQkM1OXpKEgpCYXRocm9vbS4gGgpCYXRocm9vbS4gIjAKCg0PqB7AFau6dUAKCg1actq+Fau6dUAKCg1actq+FfL8tj8KCg0PqB7AFQH9tj96PhIEUm9vbRoEUm9vbSIwCgoNJIjfwBUfOJJACgoNX78nwBUfOJJACgoNX78nwBX9DZS/CgoNJIjfwBX9DZS/ek4SDExhdW5kcnkgUm9vbRoMTGF1bmRyeSBSb29tIjAKCg2WqHo/FZzB5D8KCg3xL5BAFZzB5D8KCg3xL5BAFQuq3r8KCg2WqHo/FQuq3r96RBIHSGFsbHdheRoHSGFsbHdheSIwCgoNqNIrwBUj3rg/CgoNjmt+PxUj3rg/CgoNjmt+PxVqdALACgoNqNIrwBVqdALAekwSB1BpbkRyb3AaB1BpbkRyb3AiMAoKDeFIkb4VlMOnPwoKDTPAnj8VlMOnPwoKDTPAnj8V8nZavgoKDeFIkb4V8nZavjgCQAFIAFACgAELuAICygITCgdQaW5Ecm9wGggzQ0RCQkM1OQ== \ No newline at end of file diff --git a/tests/fixtures/sharkiq_live_map_frame.b64 b/tests/fixtures/sharkiq_live_map_frame.b64 new file mode 100644 index 0000000..f122a9f --- /dev/null +++ b/tests/fixtures/sharkiq_live_map_frame.b64 @@ -0,0 +1 @@ +CKmu0QEQSiAGMgIEBDr1uQEgASq/ngENj8J1PRIKDUfhBsEV4Xq0vxi/ASBqMpaeAUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkS2RLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkZGRLD0sPDw8PDw9kZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLZGRkZGRkZGRkZGRkZGRLS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPS0tLS2RkZGRkD2QPDw8PDw9kZEtLSw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGQPS0tLDw8PDw8PD2RkD2RkDw9kS0tLD0sPZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPS0sPDw9kZA8PDw9kZGQPD2RLS0sPSw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PD2QPDw8PDw8PDw8PZGRkZGRkD2RkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD2QPDw8PD2RkZA9kZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PZGRkZA8PDw8PZGRkDw9kZGQPDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PD2RkGQ9kDxkZGRkPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9LD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLZA8PDw8PDw8PS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8KChkZZGQPDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD0tLS0tLS0tLS0tLZGQPDw8ZCg8PDw8PDw8PDw8PDw8PDw9kD2RkD2RkS0tLS0tLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PDw8PDw9kD0tLS2QPZBkPDw8PDw8PDw8PDw8PDw8PDw9kZA8PZGRLS0tLS0tLD2QPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPZA8PZA9kDw8PZA8PDw8PDw8PDw8PDw8PDw8PD2QPD2RcXFxcXFxcS0sPZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9WZBkPXGQAZFxcAABcXA9kDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw9kZGQPDw8PDw8PZA8PDw8PDw8PVlZkXFxcAAAAAAAAAAAAD2QPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QPCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9LS2RkZA8PDw8PD0tkZA8PDw9WVgAAAAAAXAAAAAAAAAAAAAAAZA9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw9LS0tLS0tLS0tLS0tLS0tkXFxcXFxcXFxcXFxcXFwPZGRkZGRkZEtkZAoKCgoKCgoKCgoZGQ8PDw8PDw8PDw8PDw8PD2RkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw9kDw8PXFYAAAAAAAAAAAAAAAAAAAAAAABkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PD0tLS0tLS0tkZGRkZGRkZGRkAAAAAAAAAAAAAAAAAGRkZGRkZGRkZGRkCgoKCgoKCgoKCgoZDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDxkZZA8PZGRkXAAAAAAAAAAAAAAAAAAAAAAAAGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PS0tLS0tkS2RkZGRkZGRkDwAAAAAAAAAAAAAAAAAAAA8PD2QPDw8PZGQKCgoKCgoKCgoKChkPDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS2RkZA8PDw8PGWRkDw8PZAAAAAAAAAAAAAAAAAAAAAAAAAAAZA9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD0tLS0tLS2RkZA8PDw9kDw8PAAAAAAAAAAAAAAAAAAAAVg9kZA8PDw8ZZAoKCgoKCgoKCgoKGQ8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9LZGQPDw8PDw8PD2RkDw8AAAAAAAAAAAAAAAAAAAAAAAAAAFxkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PS0tLS2RkDw8PZGQPZGQPDw8AAAAAAAAAAAAAAAAAAABcDw9kDw8PDw9kDwoKCgoKCgoPGRkPDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kD2QPDw8PD1xkZGRkDwAAAAAAAAAAAAAAAAAAAABcXFwAXGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9LS0tLS0tLSw9kZGRkZA8PDwAAAAAAAAAAAAAAAAAAZFxcZGQPD1xcWWQPCgoKCgoKChkZGQ8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9kZA8PDw9cXGQPXGRkAAAAAAAADw8PAAAAAAAAAFxcXGQPZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LDw8PD0tLZA8PD2RkD2RkZGRkDw8PDwAAAAAAAAAAAAAAAAAAZGRWXFxcXFoAAAAPCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PD1xkAAAAXGQAAAAAAA8PDwAAAAAAAAAAXA9kZA9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tLS0tLD0sPDw8PS0tLZA8PZA8PXFwPZGQPDw8PAAAAAAAAAAAAAAAAAAAAAGRcXFxcXAAAAAAKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPZA8PD2RkZGRcAAAAAAAAXFYAAAAADw8PAAAAAAAAAA9cD2RkDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPSw8PDw9LS0tkS0sPSw8PSw8PDw9LS0tLS1xcXGRkAAAAAAAADw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoKCgoKCgoKDw8KCgoKCgoKDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEsPDw8PZGRkZAAAAAAAAAAAAAAAAAAPDw8AAAAAAABcDw8PZGQPD2RkD0tLS0tkD2RkZGRkZGQPZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRcXFxcXFxcXFxcXFxcZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw9LS2QAAAAAAAAAAAAAAAAAAA8PDwAAAAAAWmRkDw8PZA8PZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD2QPDw8PDw8PZAAAAAAAAAAAAAAAAAAADw8AAAAAVlxkZGRkD2RkD2RkZGRkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw9kAAAAAAAAAAAAAAAAAAAPDwAAAABWXGQPDw8PZGRkZGQPD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkDw8PDw8PD1wAAAAAAAAAAAAAAAAADw8AAAAAAABkZA8PDw9kZGRkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PXAAAAAAAAAAAAAAPDw8PDwAAAAAAAGRkZGRkZGRkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw8PDw9cAAAAAAAADw8PDw8PDw8PAAAAAABcZGRkZGRkZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgoKCgoPDw8KCgoKCgoKCgoKCgoKCgoKD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9kDw8PDw8PD1xkAAAAAAAPDw8PDw8PDw8AAAAAAFxkZA9kZGRkXGQAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkKCgoKDw8PDw8PDw8PDw8PCgoKCgoKCgoPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPDw8PDw8PXAAAAAAAAA8PDw8PDw8PDwAAAAAAXGQPDw9kZGRcZAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxkPDw9kDw8PDw8PDw8PDw8PDwoKCgoKCg9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw9cAAAAAAAADw8PDw8PDw8PAAAAAABcZA8PDw9kZGRkAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PGWRkZGQPDw8PDw8PDw8ZGQoKCgoKD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2RkDw8PDw8PD1wAAAAAAAAPDw8PDw8PDw8AAAAAAFxkXGRkDw9kZGQAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PZA8PD2RkZGRkZGRkZGRkCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZGQPDw8PDw8PXAAAAAAADw8PDw8PDw8PDwAAAAAAAABkXFxcXA9kAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PZGQPDw8PZGRkZGQPDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QPZA8PDw8PD1ZcAAAAAAAPDw8PDw8PDw8PAAAAAAAAAAAAZFxcZGQAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PZA8PDw9kZA8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8PDw8PDw8PVlwAAAAAAA8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZA8PZA8PD2QPDw8PD2RkDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PDw8PDw9WXAAAAAAAAA8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw9kDw9kZGQPDw8PZGQPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tLS0sPD0tcAAAAAAAADw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWg8PDw8PD2QPDw9kDw8PDw8PDw8PZA8PDw8PD2QPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPD1wAAAAAAAAPDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFxcXFxcXFxcXFxcXFxcXFxcZGRkZGRkZGRkZGRkZGRkZGRkZGRkZA9kZGRkZGRkD2RkDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PXAAAAAAAAA8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAWlpcXFxcXFxcXFxcXFxcXGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkSw9LZGRkZGRkD2RkDw9kZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9cAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAABcXAAAAAAAAAAAAAAAAFxcXFxcXFxcXFwPDw8PD1xkD2RkZGRkZGRkZGRkS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PZGRLS1wAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZAAAAAAAAAAAAAAAXFxkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PS0tWXAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZBlkZGRkAAAAAAAAAAAAAABcZGRkZGRkZGRkZGRkDw9kD0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPXFxWVlZcAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kD2RkZGQAAAAAAAAAAAAAAABkZABkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZA8AAABaWlwAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPZGRkXAAAAAAAAAAAAAAAAAAAAABkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9kZGRcZAAAAAAAAAAAAAAAAAAAAFxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZA8AAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZFxkAAAAAAAAAAAAAAAAAAAAXGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDwAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGQPD2RcAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZA8PZFwAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkDw9kZAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGQPD2RkAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA9kZGRaAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkD2RcZFwAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPZGRkXAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA9kZGRkAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PD2RkZGRkDw9kZGQAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PD2RkZGQAAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9kZA8PDw8PDw8PDw8PDw8PDw8PZGRkAAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkAAAAAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQAAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkAAAAAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQAAAAAAAAAAAAAAAAAAAAAAFpkZFxcXEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZAAAAAAAAAAAAAAAAAAAAAAAAGRkXFxcXEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkXGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRcZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPXA8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZFxkWQAAAAAAAAAAAAAAAAAAAABcXFxcDw8PZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkXGQAAAAAAAAAAAAAAAAAAFZWXFxcXFwPDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RcZAAAAAAAAAAAAAAAAAAAAFpcZGRkZA8PD2RkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZFxkAAAAAAAAAAAAAAAAAAAAAGRkS0sPZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kXGQAAAAAAAAAAAAAAAAAAAAAZFxcXA8PZGRkZGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RcZAAAAAAAAAAAAAAAAAAAAABkAFkPDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZFxkAAAAAAAAAAAAAAAAAAAAAAAAAF0PDw9kZEsPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kXAAAAAAAAAAAAAAAAAAAAAAAAAAAXV0PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RcAAAAAAAAAAAAAAAAAAAAAAAAAABdDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZFwAAAAAAAAAAAAAAAAAAAAAAAAAXA8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kXGQAAAAAAAAAAABaWloAAABaWloPDw8PDw9kZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9kD2RkZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZA9kXFxcZGRkZGRkZGRkZGRkZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PD2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PZA8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PD2QPD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PDw8PDw9kDw8PDw8PDw8PZA8PDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA9kZGRkZGRkZGQPD2QPD2QPD2QPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLSw8PZGRkZA8PZGRkZGRkZGRkZGRkZGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQPZGQPZA9kZGRLZGRLS0tLS0tLS0sPD0tkZGQPDw9LD0tLDw8PDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD2RkZGRkZGRkZGRkZA8PDw9LD0tLSw8PDw8PS0tLSw8PSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS0tLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw9LDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSgoNTCoLvxXsS7O9iAEBQqIPDW8SAzsQAhqYD8L/0P+i/8//jf/m/5r/YACb/34AmP+8AJn/2gAC/7UAxf6rAKb+qACL/rcAc/7KAFX+zgA5/toA/P3eAN792gC+/dwAYv3gAEP93gCq/N4AbPzfAPH73wCV+98AdvvbADj73QC8+t4AYfrhAEL64ABX+vYAX/oVAYb6RAGu+nIB0PqkAeD6vgHr+toB8vr5Afz6FgL9+jQCHfs4Alr7OQKY+0ACsvtQAsT7aQLN+4UC1vujAtz7wALo+9wC/fvzAgL8EQP++1ADGPznAyr8QgQs/GEEKfx/BBP8lQT5+6UE3fuyBL77tgSg+7AElvvOBJP77gSW+0kFmPtpBZz7iAWW+6UFnPsABpr7HgaX+3sGl/vXBqj77wbH++8G5vvqBkL87Aa+/O4GOf30Blb98AY4/fIGG/3nBgT90wb2/LcG6vybBtf8YAbU/EIG2vwlBuv8CwYx/dAFT/3IBan9wQXH/cEFw/2iBYb9nAVo/aEFS/2ZBTL9iAU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 \ No newline at end of file diff --git a/tests/fixtures/sharkiq_persisted_map_frame.b64 b/tests/fixtures/sharkiq_persisted_map_frame.b64 new file mode 100644 index 0000000..8ff74a2 --- /dev/null +++ b/tests/fixtures/sharkiq_persisted_map_frame.b64 @@ -0,0 +1 @@ +CKHJ1wEQShgBIA0yASs6tdQFCOJ1Egd1bm5hbWVkGggzQ0RCQkM1OSACKpadAQ2PwnU9EgoNR+EGwRXherS/GL8BIGky15wBS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRLZEtLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tkZGRkZGRkZGRkZGRkZEtLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPZGRkZGRkZGRkZGRkZEtkZGRkS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA9LS0tLZGRkZGQPZA8PDw8PD2RkS0tLS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA9LS0tLDw8PDw8PZGQPZGQPD2RLS0tLSw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA9LS0tLS2RkDw8PD2RkZA8PZEtLS0tLDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw9LS0tLZA8PDw8PDw8PDw9kZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw9LS0sPZA8PDw8PZGRkD2RkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw9kZGRkDw8PDw9kZGQPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZGQZD2QPGRkZGQ8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tkDw8PDw8PDw9LS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDwoKGRlkZA8PCgoKCgoKDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0sPDw8PDw8PDw8PDw8PS0tLS0tLS0tLS0tkZA8PDxkKCgoKChkZGQoKCgoKCgoKDw8PZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD2QPS0tLZA9kGQoKCgoKCgoKCgoKCgoKCgoKDw9kDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA9kDw9kD2QPDw9kCgoKCgoKCgoKCgoKCgoKCgoKGQ8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoZGQ8PZEtLS0tLS0tLS0tLD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkZA8KCgoKCgoKCgoKChkKCgoKCgoKGQ9kZGRkZGRkZGQPDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8KCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLZGRkDw8KCgoKCgoKCgoKGQoKCgoKCgoKD2RkDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZGRkZGRkZGRkDw9kZGRkZGRkZGRkCgoKCgoKCgoKChkZDw8PDw8PDw8PDw8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8KCgoKCgoKChlkCgoKCgoKCgoKDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQKCgoKCgoKCgoKChkPDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PGRlkDw8ZZGRkCgoKCgoKCgoKDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS2RkZGRkZGQPAAAAAAAAAGRkZA8PDw8PDw8PZA8PDw9kZAoKCgoKCgoKCgoKGQ8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLZGRkDw8PDw8ZZGQPDw9kZBkPCgoKCgoKCgoKCgoKDw8PZGQPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PD2QPDwAAAAAAAAAAAAoKCg8PDw8PD2RkDw8PDxlkCgoKCgoKCgoKCgoZDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD0tkZA8PDw8PDw8PZGQPDw9kDw8KCgoKCgoKCgoKCgoKZGRkZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA9kZA8AAAAAAAAAAAAACgoKCg8PDw8PD2QPDw8PD2QPCgoKCgoKCg8ZGQ8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPD2QPZA8PDw8PDw9kZGQPZGQPDw8KCgoKCgoKCgoKCgpkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkAAAAAAAAAAAAAAAKCgoKCg8PDw9kZA8PD2QPZA8KCgoKCgoKGRkZDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD2RkDw8PDw8PDw8PZGRkZGQPGQ8KCgoKCgoKCgoKCmRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PZGQPZGRkZAAAAAAAAAAAAAAAAAoKCgoKCg8PDw9kZGQPZA8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw9kZGRkZGRkZA8KCgoKCgoKCgoPD2RkS0tLS0tLD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLD0tLS0tLS0tLS0tkDw9kDw8PZFwAAAAAAAAAAAAAAAAKCgoKCgoKCg8PD2RkDxkZDw8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kS0sPZGRkZA8PDw8PZGRkZGQPDw8PCgoKCgoKCgoKDw8PZGRLS0tLDw8PDw8PS0tLS0tLS0sPS0tLS0tLS0tLSw8PDw8PZGRkZGRkZGRkDw9LS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAADw8KCgoKCgoKCg8PDw8PDwoKCgoKCgoKCgoKCgoPDwoKCgoKCgoPDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PD0tkZGRkZGQPD2RkDw9kZA8PDwoKCgoKCgoKCgoPDw9kZEtLZGQPD2RkZGRkZGRkZGRkZA9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQPSw9kD0sPS0tLD2RkZGQAAAAAAAAAAAAAAA8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPS0sPDw8PDw9kDw8PDw8PD2RkDw8KCgoKCgoKCgoKCg8PDw9kDw9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkAAAAAAAAAAAAAAAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD0sPDw9LD2QPDw8PDw8PD2QPCgoKCgoKCgoKCgoPDw8PZGQPD2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkCgoKCgAAAAAAAAAAAAAACgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PZGQPDw8PDw8PDwoKCgoKCgoKCgoKDw8PDw9kZGRkZA8PZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoAAAAAAAAAAAAAAAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZA9kZA8PDw8PDw8PCgoKCgoKChkZGRkPDw8PD2RkZGRkZA9kZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgAAAAAAAAAAAAAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw9kDw8PDw8PDwoKCgoKCgoZGWRkZGRkZGRkZGRkD2RkDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoAAAAAAAAAAAAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PDw8PZGQPDw8PDw8PCgoKCgoKChlkZGRkZGRkZGRkZGQPD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKAAAAAAAAAAAACgoKCgoKCg8PDw8PDw8PDw8KCgoKCgoKChkPDwoKCgoKCg8PDwoKCgoKCgoKCgoKCgoKCgoPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPDw8PDw8PDw8PZGQPDw8PDw8KCgoKCgoKGWRkD2RkD2RkZGRkDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKAAAAAAAAAAAAAAoKDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKGRkZGQoKCgoPDw8PDw8PDw8PDw8KCgoKCgoKCg9kZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA8PDw8PDw8PDw9kZA8PDw8PCgoKCgoKCg8PD2QPDw8PD2RkZGRkDw8PD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgAAAAAAAAAAAAAKCg8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoZDw8PGQ8PD2QPDw8PDw8PDw8PDw8PCgoKCgoKD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PD2QKCgoPDwoKCgoKCgoKDw8PZGQPDw8PD2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgAAAAAAAAAAAAAKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKChkPDw8PDw8ZZGRkZA8PDw8PDw8PDxkZCgoKCgoPZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8ZCgoKCgoKCgoKCgoKCgoPDw9kZA9kZGQPD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoAAAAAAAAAAAAACgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKGQ8PDw8PDw9kDw8PZGRkZGRkZGRkZGQKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDxkKCgoKCgoKCgoKCgoKCg8PD2RkZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoAAAAAAAAAAAAAAAAAAAoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCg8PDw8PDw8PDw8PDw9kZA8PDw9kZGRkZA8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PZAoKCgoKCgoKCgoKCgoKDw8PD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoAAAAAAAAAAAAAAAAAAAAKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PD2RkDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw9kCgoKCgoKCgoKCgoKCgoKDw8PZGRkZGRkZGQPDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKAAAAAAAAAAAAAAAAAAAACgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkDw9kDw8PZA8PDw8PZGQPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPD2RkCgoKCgoKCg8KCgoKCgoKCg8PZGRkZGRkZA8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgAAAAAAAAAAAAAAAAAAAAoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PDw8PD2QPD2RkZA8PDw9kZA8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tkZGQKCgoKCg8PDwoKCgoKCgoKCg9kZGQPD2QPDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoAAAAAAAAAAAAAAAAAAAAKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PD2QPDw8PDw8PDw9kDw8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PS2RkZAoKCgoKDw8PDwoKCgoKCgoKCg8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkD2RkZGRkZGQPZGQPDw8PDw9kZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9LZGRkCgoKCgoPDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKGQ8PD2RkCgoAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tkZGRkZGQPZGQPS2RkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZGQKCgoKCg8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PCgoKCgoKCgoKCgoZZGRkZGQPAAAAAAAAAAAAXFxkXA8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRLS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kZA8PD2RkZAoKCgoKDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDwoKCgoKCgoKCgoKChlkZGRkZAAAAAAAAAAAAABcCmRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoPGWRkZGQAAAAAAAAAAAAAAFxcZGRkZGRkZEtLS0tLS2RLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PZGRkXAAAAAAAAAAAAAAAAFxkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw9kZGRcXAAAAAAAAAAAAAAAAAAAD1xkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PD2RkZFxcAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PZGRkD1wAAAAAAAAAAAAAAAAAAABkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PD2RkZA8PZAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw8PZGRkDw9kXAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PDw9kZGQPD2RcAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw8PDw8PD2RkZA8PZFwAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw8PZGRkD2RkXAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKDw8PDw8PDw8PDw9kZGQPZGRcAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PD2RkZA9kZFwAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PZGRkD2RkXFwAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkCgoKCgoKDw8PDw8PDw8PDw9kZGQAAAAAXAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPCgoKCgoPDw8PDw8PDw8PD2RkZAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA8KCgoKCg8PDw8PDw8PDw8PZGQAAAAAAAAAAAAAAAoKCgAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8ZZGQPCgoKCgoKDw8PDw8PDw8PDw9kZGQAAAAAAAAAAAAKCgoKAAAAAABcZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoPDw8PDw8PDw8PD2RkZAAAAAAAAAAACgoKCgoAAAAAAFxkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PZGRkAAAAAAAAAAoKCgoKCgAAAAAAXGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw9kZAAAAAAAAA8KCgoKCgoKAAAAAABcZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8KCgoKCg8PCgoKDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PD2RkAAAAAAAADwoKCgoKCgoAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKDwoKCgoKDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PZGQAAAAAAAAPCgoKCgoKAAAAAAAAZGRcS0tLZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw9kZAAAAAAAAA8KCgoKCgoAAAAAAABkAABkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PD2RkAAAAAAAACgoKCgoKCgAAAAAAAAAAAABcXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQAAAAAAAAKCgoKCgoKAAAAAAAAAAAAAABcZGQPD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZAAAAAAAAAoKCgoKCgoAAAAAAAAAAAAAAFxkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkAAAAAAAACgoKCgoKAAAAAAAAAAAAAAAAD1wPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQAAAAAAAAKCgoKCgAAAAAAAAAAAAAAAAAPDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZAAAAAAAAA8KCgoKAAAAAAAAAAAAAAAAD1xkDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkAAAAAAAADw8KCgoAAAAAAABcZGRkZGQPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QAAAAAAAAPDw8KCgAAAAAAWlxkZGRkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PGQoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZAAAAAAADw8PDwoKAAAAAAAAZGRkSw9kZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8ZCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kAAAAAAAPDw8PCgoAAAAAAABkZGRLDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDxkKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QAAAAAAAAPAAAAAAAAAAAAAFxcZGRkDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDxkKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZAAAAAAAAAAAAAAAAAAAAAAAAABcDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGQPDw8PDw8PGRkKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZAAAAAAAAAAAAAAAAABaAAAADw8PDw8PD2RkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZFpaWlpaWlpaXGRkZGRkZGRkZGRkZGRkZEtkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PDw9kZGRcDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw8PDw8PDw8PDw8PD2QPDw8PDw8PDw9kDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLDw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw9kSw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEsPD2QPDw8PDw8PDw8PDw8PDw8PDw8PD2RLSw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkD2RkZGRkZGRkZA8PZA8PZA8PZA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLZGRLS2RkZGRkZGRkZGRkZGRkZGRkZGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLZGRkS0tkZGRkZGRkZGRkZGRkZGQPD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZEtkZGRkS0tLS0tLS2RkZGRkZEtLZEtkZA9kS2RkZEtkZEtLS0tLS0tLS0tLS2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t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V4Xq0vxi/ASBpMtecAUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkS2RLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLZGRkZGRkZGRkZGRkZGRLS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkD2RkZGRkZGRkZGRkZGRLZGRkZEtLZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPS0tLS2RkZGRkD2QPDw8PDw9kZEtLS0tLZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGQPS0tLSw8PDw8PD2RkD2RkDw9kS0tLS0sPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPS0tLS0tkZA8PDw9kZGQPD2RLS0tLSw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PS0tLS2QPDw8PDw8PDw8PZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PS0tLD2QPDw8PD2RkZA9kZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PZGRkZA8PDw8PZGRkDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PD2RkXA9kD1ZWWloPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLZA8PDw8PDw8PS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8AAFpcZGQPDwAAAAAAAA8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD0tLS0tLS0tLS0tLZGQPDw9cAAAAAABaWloAAAAAAAAAAA8PD2RkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PDw8PDw9kD0tLS2QPZFoAAAAAAAAAAAAAAAAAAAAAAA8PZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPZA8PZA9kDw8PZAAAAAAAAAAAAAAAAAAAAAAAAFYPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAVlYPD2RLS0tLS0tLS0tLSw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw9kZGQPAAAAAAAAAAAAAABWAAAAAAAAAFYPZGRkZGRkZGRkDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QPAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9LS2RkZA8PAAAAAAAAAAAAAFwAAAAAAAAAAA9kZA8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tkS0tLZGRkS0tLZGRkZGRkZEtkZAAAAAAAAAAAAABcXA8PDw8PDw8PDw8PDw8PD2RkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PAAAAAAAAAABcZAAAAAAAAAAAAA8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkAAAAAAAAAAAAAABcDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PD1xcZA8PXGRkZAAAAAAAAAAAAA8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tkZGRkZGRkD2QPZGQPDw9kZGQPDw8PDw8PD2QPDw8PZGQAAAAAAAAAAAAAAFwPDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS2RkZA8PDw8PXGRkDw8PZGRWDwAAAAAAAAAAAAAAAA8PD2RkDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw9kDw8PDw8PDwAAAAAAAAAPDw8PDw9kZA8PDw9cZAAAAAAAAAAAAAAAXA8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9LZGQPDw8PDw8PD2RkDw8PZA8PAAAAAAAAAAAAAAAAAGRkZGQPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGQPZGQPDw8PDw8AAAAAAAAAAAAPDw8PDw9kDw8PDw9kDwAAAAAAAAAPXFwPDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kD2QPDw8PDw8PZGRkD2RkDw8PAAAAAAAAAAAAAAAAZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkZA8PDwAAAAAAAAAAAAAAAAAPDw8PZGQPDw9kD2QPAAAAAAAAAFxcXA8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9kZA8PDw8PDw8PD2RkZGRkD1wPAAAAAAAAAAAAAABkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkD2RkZGRkDw8AAAAAAAAAAAAAAAAAAAAPDw8PZGRkD2QPDw8PAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGQPAAAAAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLZA8PZA8PD2QPZGQPDwAAAAAAAAAAAAAAAAAAAAAPDw9kZA9cXA8PDw8AAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPZEtLD2RkZGQPDw8PD2RkZGRkDw8PDwAAAAAAAAAAAA8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkZA8AAAAAAAAADw8PAAAAAAAAAAAPDw8PDw8AAAAAAAAAAAAAAAAADw8AAAAAAAAADw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEsPDw9LZGRkZGRkDw9kZA8PZGQPDw8AAAAAAAAAAAAADw8PZGRLS2RkD0tLS0tkS2RkZGRkZGQPZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tLZEtLS0tLS0tcXGRkZGQPAAAAAAAAAA8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLDw9LSw8PZA8PDw8PDw9kZA8PAAAAAAAAAAAAAAAPDw8PZEtLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQPDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw9LDw8PSw9kDw8PDw8PDw9kDwAAAAAAAAAAAAAADw8PD2RkS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD2RkDw8PDw8PDw8AAAAAAAAAAAAAAA8PDw8PZGRkS0tLD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PD2QPZGQPDw8PDw8PDwAAAAAAAABcXFxcDw8PDw9kZGRkZGQPZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PZA8PDw8PDw8AAAAAAAAAXFxkZGRkZGRkZGRkZA9kZA8PZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PDw8PD2RkDw8PDw8PDwAAAAAAAABcZGRkZGRkZGRkZGRkDw9kZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PAAAAAAAAAABWDw8AAAAAAAAPDw8AAAAAAAAAAAAAAAAAAAAAD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PD2RkDw8PDw8PAAAAAAAAAFxkZA9kZA9kZGRkZA8PDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAFlWVloAAAAADw8PDw8PDw8PDw8PAAAAAAAAAAAPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PZGQPDw8PDwAAAAAAAAAPDw9kDw8PDw9kZGRkZA8PDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAVg8PD1oPDw9kDw8PDw8PDw8PDw8PDwAAAAAAAA9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw9kAAAADw8AAAAAAAAAAA8PD2RkDw8PDw9kZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAABaDw8PDw8PXGRkZGQPDw8PDw8PDw9cXAAAAAAAD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PXAAAAAAAAAAAAAAAAAAADw8PZGQPZGRkDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAFoPDw8PDw8PZA8PD2RkZGRkZGRkZGRkAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9cAAAAAAAAAAAAAAAAAAAPDw9kZGQPZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAPDw8PDw8PDw8PDw8PZGQPDw8PZGRkZGQPDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PD2QAAAAAAAAAAAAAAAAAAA8PDw9kZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PDw9kZEtLSw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PZAAAAAAAAAAAAAAAAAAAAA8PD2RkZGRkZGRkDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZA8PZA8PD2QPDw8PD2RkS0sPDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9LS0tkZAAAAAAAAAAPAAAAAAAAAAAPD2RkZGRkZGQPDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPDw8PDw9kDw9kZGQPDw8PZGRLDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLZGRkAAAAAAAPDw8AAAAAAAAAAAAPZGRkDw9kDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPDw9kDw8PDw8PD0sPZA8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPD0tkZGQAAAAAAA8PDw8AAAAAAAAAAAAPDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZA9kZGRkZGRkD2RkDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PS2RkZAAAAAAADw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFwPDw9cXAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS0tLZGRkZGRkD2RkD0tkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGRkAAAAAAAPDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDwAAAAAAAAAAAAAAXGRkZGRkDwAAAAAAAAAAAAAAXA8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PZGQPDw9kZGQAAAAAAA8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8AAAAAAAAAAAAAAABcZGRkZGRkDwAAAAAAAAAAAABkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAD1xkZGRkZGQPDwAAAAAAAAAAXGRkZGRkZGRLS0tLS0tkS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8AAAAAAAAADw8PDw8PD2RkZA9kZGQPDwAAAAAAAA9cZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PZGRkDw9kZA8PDwAAAAAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PDw9kZGQPD2RkDw8PAAAAAAAAAAAAAFxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAADw8PDw8PDw8PD2RkZA8PZA8PDw8AAA8AAAAAAAAAXGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGQPDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw9kZGQPD2QPDw8PDw8AAAAAAAAAAFxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PD2RkZA8PZA8PDw8PDwAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAADw8PDw8PDw8PDw8PZGRkDw9kDw8PZGQPDwAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PDw8PDw9kZGQPD2QPDw8PZGQPAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw8PD2RkZA9kZA8PDw8PZA8AAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAA8PDw8PDw8PDw8PZGRkD2RkDw8PDw9kZAAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAADw8PDw8PDw8PDw9kZGQPZGQPDw8PD1wAAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PDw8PD2RkZA9kZA8PDwAAAAAAAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZAAAAAAAAA8PDw8PDw8PDw8PZGRkDw9kDw8AAAAAAAAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkDwAAAAAADw8PDw8PDw8PDw9kZGQPDw8PDwAAAAAAAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPAAAAAAAPDw8PDw8PDw8PD2RkZA8PDw8PAAAAAAAAAAAAAA8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PXGRkDwAAAAAAAA8PDw8PDw8PDw8PZGRkDw8PDw8AAAAAAAAAAAAPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8AAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAADw8PDw8PDw8PDw9kZGRkDw8PDwAAAAAAAAAAAA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDwAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PD2RkZA8PDw8PDwAAAAAAAAAADw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PZGRkDw8PDw8PAAAAAAAAAAAPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PAAAAAAAPDwAAAA8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw9kZGQPDw8PDw8AAAAAAAAADw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8AAAAAAA8AAAAAAA8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PD2RkZA8PDw8PDwAAAAAAAAAPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PAAAAAAAAAA8PDw8PZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDwAAAAAAAAAADw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8AAAAAAAAAAAAPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDwAAAAAAAAAAAA8PDw8PDw8PDw8PZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PAAAAAAAAAAAADw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8AAAAAAAAAAA8PDwAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PAAAAAAAAAA8PDw8PDw8PDw8PZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkDw8PDw8PAAAAAAAADw8PD2RkZGRkDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PAAAAAAAADw8PZGRkZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD1YAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8AAAAAAAAAD2RkZEtLZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PVgAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw8PDw8PDwAAAAAAAAAAXGRkSw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw9ZAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PAAAAAAAAAAAAVmRkZA8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw9aAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PD1laAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8AAAAAAAAPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkDw8PDw8PDw8PDw8PDw9kZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZEtkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZEsPDw8PDw8PDw8PZGRkDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PD2RkZA8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEsPDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkZGRkZGRkZGQPD2QPD2QPD2QPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS2RkS0tkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tLS2RkZEtLZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkZGRLZGRkZEtLS0tLS0tkZGRkZGRLS2RLZGQPZEtkZGRLZGRLS0tLS0tLS0tLS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0ABSg8NfHTsvhX108G8HYadGT1VAACAP1jjEhr9Nxj///////////8BIP///////////wEoATLiN1t7ImtleSI6IkRUX1ZFUlNJT05fTUNVIiwidGltZSI6IjBzIiwiY29kZSI6IkxpZGFyMl8wTTEuMC45MSJ9LHsia2V5IjoiRFRfVkVSU0lPTl9MMDEiLCJ0aW1lIjoiMHMiLCJjb2RlIjoiVjYuNi4xMC1QNy5OMzMwOC4xNy4wLVNlcCAyMSAyMDIzIn0seyJrZXkiOiJEVF9XQVRFUl9UQU5LIiwidGltZSI6IjE3ODk4NjUzODNzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX1dBVEVSX0xFVkVMIiwidGltZSI6IjE3ODk4NjUzODNzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX1JFTE9DX1NUQVRFIiwidGltZSI6IjE3ODk4NjUzODNzIiwiY29kZSI6IlJTX05PTkUifSx7ImtleSI6IkRUX0dPX1pPTkVfSU5GTyIsInRpbWUiOiIxNzg5ODY3MDA2cyIsImNvZGUiOiJJbmZvOltab25lXUdldCBSb29tIERlZmluZTogPDNDREJCQzU5PjxHb1pvbmU6QmF0aHJvb20uICxSb29tLExhdW5kcnkgUm9vbSxIYWxsd2F5LD48Tm9Hb1pvbmU6PntIYTFhaGt3eTJRSWFDRE5EUkVKQ1F6VTVlbGNTQ2tKaGRHaHliMjl0TGlBYUNrSmhkR2h5YjI5dExpQWlNQW9LRFErb0hzQVZxN3AxUUFvS0RWcHkycjRWcTdwMVFBb0tEVnB5MnI0Vjh2eTJQd29LRFErb0hzQVZBZjIyUDRJQkNrSmhkR2h5YjI5dExpQjZSUklFVW05dmJSb0VVbTl2YlNJd0Nnb05KSWpmd0JVZk9KSkFDZ29OWDc4bndCVWZPSkpBQ2dvTlg3OG53Qlg5RFpTL0Nnb05KSWpmd0JYOURaUy9nZ0VFVW05dmJYcGRFZ3hNWVhWdVpISjVJRkp2YjIwYURFeGhkVzVrY25rZ1VtOXZiU0l3Q2dvTmxxaDZQeFdjd2VRL0Nnb044UytRUUJXY3dlUS9DZ29OOFMrUVFCVUxxdDYvQ2dvTmxxaDZQeFVMcXQ2L2dnRU1UR0YxYm1SeWVTQlNiMjl0ZWs0U0IwaGhiR3gzWVhrYUIwaGhiR3gzWVhraU1Bb0tEYWpTSzhBVkk5NjRQd29LRFk1cmZqOFZJOTY0UHdvS0RZNXJmajhWYW5RQ3dBb0tEYWpTSzhBVmFuUUN3SUlCQjBoaGJHeDNZWGxDQWxnVVNBR1NBZ1FvQWpBQ3FnSWRDR1FRQUJnQUtBQXdBRGdEUVAvLy8vLy8vLy8vL3dGUUFtQUVhQUs0QWdJPX0ifSx7ImtleSI6IkRUX0dPX1pPTkVfSU5GTyIsInRpbWUiOiIxNzg5ODY3MDA3cyIsImNvZGUiOiJJbmZvOltab25lXUdldCBSb29tIERlZmluZTogPDNDREJCQzU5PjxHb1pvbmU6QmF0aHJvb20uICxSb29tLExhdW5kcnkgUm9vbSxIYWxsd2F5LD48Tm9Hb1pvbmU6PntIY1phaGt3eXJBSWFDRE5EUkVKQ1F6VTVla29TQ2tKaGRHaHliMjl0TGlBYUNrSmhkR2h5YjI5dExpQWlNQW9LRFErb0hzQVZxN3AxUUFvS0RWcHkycjRWcTdwMVFBb0tEVnB5MnI0Vjh2eTJQd29LRFErb0hzQVZBZjIyUDNvK0VnUlNiMjl0R2dSU2IyOXRJakFLQ2cwa2lOL0FGUjg0a2tBS0NnMWZ2eWZBRlI4NGtrQUtDZzFmdnlmQUZmME5sTDhLQ2cwa2lOL0FGZjBObEw5NlRoSU1UR0YxYm1SeWVTQlNiMjl0R2d4TVlYVnVaSEo1SUZKdmIyMGlNQW9LRFphb2VqOFZuTUhrUHdvS0RmRXZrRUFWbk1Ia1B3b0tEZkV2a0VBVkM2cmV2d29LRFphb2VqOFZDNnJldjNwRUVnZElZV3hzZDJGNUdnZElZV3hzZDJGNUlqQUtDZzJvMGl2QUZTUGV1RDhLQ2cyT2EzNC9GU1BldUQ4S0NnMk9hMzQvRldwMEFzQUtDZzJvMGl2QUZXcDBBc0JDQWxnVVNBR0FBUXVTQWdRb0FqQUNxZ0lkQ0dRUUFCZ0FLQUF3QURnRFFQLy8vLy8vLy8vLy93RlFBbUFFYUFLNEFnTEtBaFlLQ2tKaGRHaHliMjl0TGlBYUNETkRSRUpDUXpVNX0ifSx7ImtleSI6IkRUX0dPX1pPTkVfSU5GTyIsInRpbWUiOiIxNzg5ODY3MDA3cyIsImNvZGUiOiJJbmZvOltHb1pvbmVdIGdldCBVU1JfQ1RSX0FVVE8sPDNDREJCQzU5PjxHb3pvbmVzOkJhdGhyb29tLiAsPntIY1phaGt3eXJBSWFDRE5EUkVKQ1F6VTVla29TQ2tKaGRHaHliMjl0TGlBYUNrSmhkR2h5YjI5dExpQWlNQW9LRFErb0hzQVZxN3AxUUFvS0RWcHkycjRWcTdwMVFBb0tEVnB5MnI0Vjh2eTJQd29LRFErb0hzQVZBZjIyUDNvK0VnUlNiMjl0R2dSU2IyOXRJakFLQ2cwa2lOL0FGUjg0a2tBS0NnMWZ2eWZBRlI4NGtrQUtDZzFmdnlmQUZmME5sTDhLQ2cwa2lOL0FGZjBObEw5NlRoSU1UR0YxYm1SeWVTQlNiMjl0R2d4TVlYVnVaSEo1SUZKdmIyMGlNQW9LRFphb2VqOFZuTUhrUHdvS0RmRXZrRUFWbk1Ia1B3b0tEZkV2a0VBVkM2cmV2d29LRFphb2VqOFZDNnJldjNwRUVnZElZV3hzZDJGNUdnZElZV3hzZDJGNUlqQUtDZzJvMGl2QUZTUGV1RDhLQ2cyT2EzNC9GU1BldUQ4S0NnMk9hMzQvRldwMEFzQUtDZzJvMGl2QUZXcDBBc0JDQWxnVVNBR0FBUXVTQWdRb0FqQUNxZ0lkQ0dRUUFCZ0FLQUF3QURnRFFQLy8vLy8vLy8vLy93RlFBbUFFYUFLNEFnTEtBaFlLQ2tKaGRHaHliMjl0TGlBYUNETkRSRUpDUXpVNX0ifSx7ImtleSI6IkRUX1dFVF9DTEVBTl9NT0RFIiwidGltZSI6IjE3ODk4NjcwMDdzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX1NUQVRFX0NIQU5HRSIsInRpbWUiOiIxNzg5ODY3MDA3cyIsImNvZGUiOiJTWVNfU1RfQ0xFQU5JTkcifSx7ImtleSI6IkRUX0JBVFRFUllfVkFMVUVfQVRfU1RBVEVfVFJBTlMiLCJ0aW1lIjoiMTc4OTg2NzAwN3MiLCJjb2RlIjoiMTAwIn0seyJrZXkiOiJEVF9SRUxPQ19TVEFURSIsInRpbWUiOiIxNzg5ODY3MDA3cyIsImNvZGUiOiJSU19VTlJFTE9DIn0seyJrZXkiOiJEVF9MSURBUl9TQ0FORlJFUVVFTkNZX1ZBTFVFIiwidGltZSI6IjE3ODk4NjcwMDdzIiwiY29kZSI6IlJlc2V0IExpZGFyIFNjYW5GcmVxdWVuY3k6IDYuMCJ9LHsia2V5IjoiRFRfQ0hBUkdJTkdfVFlQRSIsInRpbWUiOiIxNzg5ODY3MDA3cyIsImNvZGUiOiJDSEFSR0VfTk9ORSJ9LHsia2V5IjoiRFRfTU1fU1RBVFVTIiwidGltZSI6IjE3ODk4NjcwMDhzIiwiY29kZSI6Ik1NX1NUQVRVU19MT1dMSUdIVCJ9LHsia2V5IjoiRFRfV0FSTklOR19DT0RFIiwidGltZSI6IjE3ODk4NjcwMDhzIiwiY29kZSI6IldBUk5fTU1fTE9XTElHSFQifSx7ImtleSI6IkRUX0dPX1pPTkVfSU5GTyIsInRpbWUiOiIxNzg5ODY3MDExcyIsImNvZGUiOiJJTkZPOiBbWm9uZUluZm9dIEdvIHpvbmUgQmF0aHJvb20uICBpcyBlbmFibGVkISJ9LHsia2V5IjoiRFRfR09fWk9ORV9JTkZPIiwidGltZSI6IjE3ODk4NjcwMTFzIiwiY29kZSI6IklORk86IFtab25lSW5mb10gR28gem9uZSBSb29tIGlzIG5vdCBlbmFibGVkISJ9LHsia2V5IjoiRFRfR09fWk9ORV9JTkZPIiwidGltZSI6IjE3ODk4NjcwMTFzIiwiY29kZSI6IklORk86IFtab25lSW5mb10gR28gem9uZSBMYXVuZHJ5IFJvb20gaXMgbm90IGVuYWJsZWQhIn0seyJrZXkiOiJEVF9HT19aT05FX0lORk8iLCJ0aW1lIjoiMTc4OTg2NzAxMXMiLCJjb2RlIjoiSU5GTzogW1pvbmVJbmZvXSBHbyB6b25lIEhhbGx3YXkgaXMgbm90IGVuYWJsZWQhIn0seyJrZXkiOiJEVF9HT19aT05FX05VTSIsInRpbWUiOiIxNzg5ODY3MDExcyIsImNvZGUiOiI0In0seyJrZXkiOiJEVF9OT0dPX1pPTkVfTlVNIiwidGltZSI6IjE3ODk4NjcwMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0dPX1pPTkVfRU5BQkxFRF9OVU0iLCJ0aW1lIjoiMTc4OTg2NzAxMXMiLCJjb2RlIjoiMSJ9LHsia2V5IjoiRFRfUkVMT0NfU1RBVEUiLCJ0aW1lIjoiMTc4OTg2NzAxMnMiLCJjb2RlIjoiUlNfUkVMT0MifSx7ImtleSI6IkRUX1NQRUNJQUxfTU9ERSIsInRpbWUiOiIxNzg5ODY3MDEycyIsImNvZGUiOiJDdXJyZW50IHNwZWNpYWwgbW9kZTogVU5ERUZJTkVEX01PREUifSx7ImtleSI6IkRUX1JFTE9DX1NUQVRFIiwidGltZSI6IjE3ODk4NjcwMTNzIiwiY29kZSI6IlJTX1VOUkVMT0MifSx7ImtleSI6IkRUX1JFTE9DQUxfU1VDQ0VTUyIsInRpbWUiOiIxNzg5ODY3MDEzcyIsImNvZGUiOiIxIn0seyJrZXkiOiJEVF9SRUxPQ19TVEFURSIsInRpbWUiOiIxNzg5ODY3MDEzcyIsImNvZGUiOiJSU19TVUNDRVNTIn0seyJrZXkiOiJEVF9JTVVfU09GVF9DQUxJX1NVQ0NFU1MiLCJ0aW1lIjoiMXMiLCJjb2RlIjoiMSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTBzIiwiY29kZSI6IjE1In0seyJrZXkiOiJEVF9QUF9SRUxPQ19TVEFUVVMiLCJ0aW1lIjoiMTRzIiwiY29kZSI6IlJlbG9jIHN1Y2Nlc3MifSx7ImtleSI6IkRUX0NMRUFOSU5HX01PREUiLCJ0aW1lIjoiMTVzIiwiY29kZSI6IkNsZWFuaW5nIG1vZGU6IFJPT01fU0VMRUNUSU9OIn0seyJrZXkiOiJEVF9SRUxPQ19NT0RFIiwidGltZSI6IjE1cyIsImNvZGUiOiJSZWxvY2F0aW9uIG1vZGU6IFNQSU4ifSx7ImtleSI6IkRUX1NQRUNJQUxfTU9ERSIsInRpbWUiOiIxNXMiLCJjb2RlIjoiQ3VycmVudCBzcGVjaWFsIG1vZGU6IFVOREVGSU5FRF9NT0RFIn0seyJrZXkiOiJEVF9aX1NJR05BTCIsInRpbWUiOiIxOXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfV0ZGX1RFUk1fQ09ERSIsInRpbWUiOiIxMDFzIiwiY29kZSI6IlpvbmU6IDEsIE5PUk1BTCJ9LHsia2V5IjoiRFRfRE9DS19DT0RFIiwidGltZSI6IjEyOXMiLCJjb2RlIjoiREVfVVNSX0NUUl9ET0NLIn0seyJrZXkiOiJEVF9TVEFURV9DSEFOR0UiLCJ0aW1lIjoiMTI5cyIsImNvZGUiOiJTWVNfU1RfRE9DS0lORyJ9LHsia2V5IjoiRFRfQkFUVEVSWV9WQUxVRV9BVF9TVEFURV9UUkFOUyIsInRpbWUiOiIxMjlzIiwiY29kZSI6Ijk0In0seyJrZXkiOiJEVF9MSURBUl9TQ0FORlJFUVVFTkNZX1ZBTFVFIiwidGltZSI6IjEyOXMiLCJjb2RlIjoiUmVzZXQgTGlkYXIgU2NhbkZyZXF1ZW5jeTogNC41In0seyJrZXkiOiJEVF9aX1NJR05BTCIsInRpbWUiOiIxNTRzIiwiY29kZSI6IjE1In0seyJrZXkiOiJEVF9aX1NJR05BTCIsInRpbWUiOiIxNTVzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX1pfU0lHTkFMIiwidGltZSI6IjE1NXMiLCJjb2RlIjoiMTUifSx7ImtleSI6IkRUX1pfU0lHTkFMIiwidGltZSI6IjE1NnMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTU2cyIsImNvZGUiOiIxNSJ9LHsia2V5IjoiRFRfVElNRV9QUF9ET0NLIiwidGltZSI6IjE1N3MiLCJjb2RlIjoiMjcifSx7ImtleSI6IkRUX0ZBU1RfRE9DS0lORyIsInRpbWUiOiIxNThzIiwiY29kZSI6IjEifSx7ImtleSI6IkRUX0JBUkNPREVfU0laRSIsInRpbWUiOiIxNThzIiwiY29kZSI6IjI3In0seyJrZXkiOiJEVF9aX1NJR05BTCIsInRpbWUiOiIxNjJzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX1pfU0lHTkFMIiwidGltZSI6IjE2MnMiLCJjb2RlIjoiMTUifSx7ImtleSI6IkRUX1pfU0lHTkFMIiwidGltZSI6IjE2NHMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfVElNRV9CQVNFX0RPQ0siLCJ0aW1lIjoiMTcycyIsImNvZGUiOiIxNCJ9LHsia2V5IjoiRFRfQUVEX0hBVENIRE9PUl9PUEVOX1RJTUUiLCJ0aW1lIjoiMjEwcyIsImNvZGUiOiIxNjA1MCJ9LHsia2V5IjoiRFRfQUVEX0hBVENIRE9PUl9DTE9TRV9USU1FIiwidGltZSI6IjIxMHMiLCJjb2RlIjoiMTAxMTkifSx7ImtleSI6IkRUX0NIQVJHSU5HX1RZUEUiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiJDSEFSR0VfQkFDSyJ9LHsia2V5IjoiRFRfU1RBVEVfQ0hBTkdFIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiU1lTX1NUX1dBSVRJTkcifSx7ImtleSI6IkRUX0JBVFRFUllfVkFMVUVfQVRfU1RBVEVfVFJBTlMiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiI5NCJ9LHsia2V5IjoiRFRfTk9STUFMX0NMRUFOX1RJTUUiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIyOCJ9LHsia2V5IjoiRFRfRk9MTE9XX0NMRUFOX1RJTUUiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiI3MyJ9LHsia2V5IjoiRFRfRVNDQVBFX0NMRUFOX1RJTUUiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIwIn0seyJrZXkiOiJEVF9OT1JNQUxfRE9DS19USU1FIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMjcifSx7ImtleSI6IkRUX0ZPTExPV19ET0NLX1RJTUUiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIwIn0seyJrZXkiOiJEVF9FU0NBUEVfRE9DS19USU1FIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfU1RBUlRfRlJPTV9ET0NLIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMSJ9LHsia2V5IjoiRFRfQ0xJRkZfUklHSFRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0NMSUZGX0NFTlRFUl9SSUdIVF9DT1VOVEVSIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfQ0xJRkZfQ0VOVEVSX0xFRlRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0NMSUZGX0xFRlRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0JVTVBFUl9UT1BfUklHSFRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0JVTVBFUl9UT1BfQ0VOVEVSX0NPVU5URVIiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIwIn0seyJrZXkiOiJEVF9CVU1QRVJfVE9QX0xFRlRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0JVTVBFUl9MSURBUl9SSUdIVF9DT1VOVEVSIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfQlVNUEVSX0xJREFSX0NFTlRFUl9DT1VOVEVSIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfQlVNUEVSX0xJREFSX0xFRlRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0JVTVBFUl9SSUdIVF9DT1VOVEVSIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMiJ9LHsia2V5IjoiRFRfQlVNUEVSX0xFRlRfQ09VTlRFUiIsInRpbWUiOiIyMTFzIiwiY29kZSI6IjUifSx7ImtleSI6IkRUX0JVTVBFUl9CT1RIX0NPVU5URVIiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIxIn0seyJrZXkiOiJEVF9JUl9BVk9JRF9SSUdIVF9DT1VOVEVSIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMCJ9LHsia2V5IjoiRFRfSVJfQVZPSURfQ0VOVEVSX0NPVU5URVIiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIwIn0seyJrZXkiOiJEVF9JUl9BVk9JRF9MRUZUX0NPVU5URVIiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiIwIn0seyJrZXkiOiJEVF9NTV9TVEFUVVMiLCJ0aW1lIjoiMjExcyIsImNvZGUiOiJNTV9TVEFUVVNfTE9XTElHSFQifSx7ImtleSI6IkRUX1BFUlNJU1RfVkFMIiwidGltZSI6IjIxMXMiLCJjb2RlIjoiMTAwIn0seyJrZXkiOiJEVF9DVVJSRU5UX1VUQ19USU1FIiwidGltZSI6IjBzIiwiY29kZSI6IlN1biBTZXAgMjAgMDE6MjA6MjQgVVRDIDIwMjYifV2qAe25Ag2PwnU9EgoNR+EGwRXherS/GL8BIGky15wBS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRLZEtLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tkZGRkZGRkZGRkZGRkZEtLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPZGRkZGRkZGRkZGRkZEtkZGRkS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA9LS0tLZGRkZGQPZA8PDw8PD2RkS0tLS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA9LS0tLDw8PDw8PZGQPZGQPD2RLS0tLSw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA9LS0tLS2RkDw8PD2RkZA8PZEtLS0tLDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw9LS0tLZA8PDw8PDw8PDw9kZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw9LS0sPZA8PDw8PZGRkD2RkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw9kZGRkDw8PDw9kZGQPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZGRcD2QPVlZaWg8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tkDw8PDw8PDw9LS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDwoKWlxkZA8PCgoKCgoKDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0sPDw8PDw8PDw8PDw8PS0tLS0tLS0tLS0tkZA8PD1wKCgoKClpaWgoKCgoKCgoKDw8PZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD2QPS0tLZA9kWgoKCgoKCgoKCgoKCgoKCgoKDw9kDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA9kDw9kD2QPDw9kCgoKCgoKCgoKCgoKCgoKCgoKVg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgpWVg8PZEtLS0tLS0tLS0tLD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkZA8KCgoKCgoKCgoKClYKCgoKCgoKVg9kZGRkZGRkZGQPDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8KCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLZGRkDw8KCgoKCgoKCgoKXAoKCgoKCgoKD2RkDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS2RLS0tkZGRLS0tkZGRkZGRkS2RkCgoKCgoKCgoKClxcDw8PDw8PDw8PDw8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8KCgoKCgoKClxkCgoKCgoKCgoKDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQKCgoKCgoKCgoKClwPDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PXFxkDw9cZGRkCgoKCgoKCgoKDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS2RkZGRkZGQPZA9kZA8PD2RkZA8PDw8PDw8PZA8PDw9kZAoKCgoKCgoKCgoKXA8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLZGRkDw8PDw9cZGQPDw9kZFYPCgoKCgoKCgoKCgoKDw8PZGQPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PD2QPDw8PDw8PCgoKCgoKCg8PDw8PD2RkDw8PD1xkCgoKCgoKCgoKCgpcDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD0tkZA8PDw8PDw8PZGQPDw9kDw8KCgoKCgoKCgoKCgoKZGRkZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA9kZA8PDw8PDwoKCgoKCgoKCg8PDw8PD2QPDw8PD2QPCgoKCgoKCg9cXA8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPD2QPZA8PDw8PDw9kZGQPZGQPDw8KCgoKCgoKCgoKCgpkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDw8PCgoKCgoKCgoKCgoKCg8PDw9kZA8PD2QPZA8KCgoKCgoKXFxcDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD2RkDw8PDw8PDw8PZGRkZGQPXA8KCgoKCgoKCgoKCmRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PZGQPZGRkZGQPDwoKCgoKCgoKCgoKCgoKCg8PDw9kZGQPZA8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw9kZGRkZGRkZA8KCgoKCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tkDw9kDw8PZA9kZA8PCgoKCgoKCgoKCgoKCgoKCg8PD2RkD1xcDw8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kS0sPZGRkZA8PDw8PZGRkZGQPDw8PCgoKCgoKCgoKDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDwoKCgoKCgoPDw8KCgoKCgoKCg8PDw8PDwoKCgoKCgoKCgoKCgoPDwoKCgoKCgoPDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkSw8PD0tkZGRkZGQPD2RkDw9kZA8PDwoKCgoKCgoKCgoPDw9kZEtLZGQPS0tLS2RLZGRkZGRkZA9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tkS0tLS0tLS1xcZGRkZA8KCgoKCgoKDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPS0sPD0tLDw9kDw8PDw8PD2RkDw8KCgoKCgoKCgoKCg8PDw9kS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZA8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD0sPDw9LD2QPDw8PDw8PD2QPCgoKCgoKCgoKCgoPDw8PZGRLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PZGQPDw8PDw8PDwoKCgoKCgoKCgoKDw8PDw9kZGRLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZA9kZA8PDw8PDw8PCgoKCgoKClxcXFwPDw8PD2RkZGRkZA9kZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw9kDw8PDw8PDwoKCgoKCgpcXGRkZGRkZGRkZGRkD2RkDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PZGQPDw8PDw8PCgoKCgoKClxkZGRkZGRkZGRkZGQPD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8KCgoKCgoKClYPDwoKCgoKCg8PDwoKCgoKCgoKCgoKCgoKCgoPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PZGQPDw8PDw8KCgoKCgoKXGRkD2RkD2RkZGRkDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKWVZWWgoKCgoPDw8PDw8PDw8PDw8KCgoKCgoKCg9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw9kZA8PDw8PCgoKCgoKCg8PD2QPDw8PD2RkZGRkDw8PD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgpWDw8PWg8PD2QPDw8PDw8PDw8PDw8PCgoKCgoKD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PD2QKCgoPDwoKCgoKCgoKDw8PZGQPDw8PD2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCloPDw8PDw9cZGRkZA8PDw8PDw8PD1xcCgoKCgoPZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9cCgoKCgoKCgoKCgoKCgoPDw9kZA9kZGQPD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKWg8PDw8PDw9kDw8PZGRkZGRkZGRkZGQKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD1wKCgoKCgoKCgoKCgoKCg8PD2RkZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCg8PDw8PDw8PDw8PDw9kZA8PDw9kZGRkZA8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PZAoKCgoKCgoKCgoKCgoKDw8PD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PD2RkS0tLDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw9kCgoKCgoKCgoKCgoKCgoKDw8PZGRkZGRkZGQPDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkDw9kDw8PZA8PDw8PZGRLSw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS2RkCgoKCgoKCg8KCgoKCgoKCg8PZGRkZGRkZA8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PDw8PD2QPD2RkZA8PDw9kZEsPDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tkZGQKCgoKCg8PDwoKCgoKCgoKCg9kZGQPD2QPDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PD2QPDw8PDw8PSw9kDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PS2RkZAoKCgoKDw8PDwoKCgoKCgoKCg8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkD2RkZGRkZGQPZGQPDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9LZGRkCgoKCgoPDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKXA8PD1xcCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tkZGRkZGQPZGQPS2RkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZGQKCgoKCg8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PCgoKCgoKCgoKCgpcZGRkZGQPCgoKCgoKCgoKCgpcDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRLS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kZA8PD2RkZAoKCgoKDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDwoKCgoKCgoKCgoKClxkZGRkZGQPCgoKCgoKCgoKCmRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoPXGRkZGRkZA8PCgoKCgoKCgpcZGRkZGRkZEtLS0tLS2RLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PZGRkD2RkZA8PCgoKCgoKD1xkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw9kZGQPD2RkDw8PCgoKCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PD2RkZA8PZGQPDw8KCgoKCgoKCgoKXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PZGRkDw9kDw8PDwoKDwoKCgoKCgpcZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PD2RkZA8PZA8PDw8PDwoKCgoKCgoKXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw8PZGRkDw9kDw8PDw8PCgoKCgoKCgpcZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PDw9kZGQPD2QPDw9kZA8PCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw8PDw8PD2RkZA8PZA8PDw9kZA8KCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw8PZGRkD2RkDw8PDw9kDwoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKDw8PDw8PDw8PDw9kZGQPZGQPDw8PD2RkCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PD2RkZA9kZA8PDw8PXAoKCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PZGRkD2RkDw8PCgoKCgoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkCgoKCgoKDw8PDw8PDw8PDw9kZGQPD2QPDwoKCgoKCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPCgoKCgoPDw8PDw8PDw8PD2RkZA8PDw8PCgoKCgoKCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA8KCgoKCg8PDw8PDw8PDw8PZGRkDw8PDw8KCgoKCgoKCgoKDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9cZGQPCgoKCgoKDw8PDw8PDw8PDw9kZGQPDw8PDwoKCgoKCgoKCg8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoPDw8PDw8PDw8PD2RkZGQPDw8PCgoKCgoKCgoKDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PZGRkDw8PDw8PCgoKCgoKCgoPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw9kZGQPDw8PDw8KCgoKCgoKCg8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8KCgoKCg8PCgoKDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PD2RkZA8PDw8PDwoKCgoKCgoPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKDwoKCgoKDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PZGRkDw8PDw8PCgoKCgoKCg8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8KCgoKCgoKDw8PDw9kZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDwoKCgoKCgoKCg8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PCgoKCgoKCgoKDw8PDw8PDw8PDw9kZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8KCgoKCgoKCgoPDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8KCgoKCgoKDw8PDw8PDw8PDw9kDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPDw8PDw8KCgoKCgoPDw8PZGRkZGQPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8KCgoKCgoPDw9kZGRkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PVgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw8PDw8PDwoKCgoKCgoPZGRkS0tkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9WCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PCgoKCgoKCgpcZGRLDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PD1kKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8KCgoKCgoKCgpWZGRkDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PD1oKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGQPDw8PDw8PWVoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDwoKCgoKCg8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGQPDw8PDw8PDw8PDw8PD2RkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZEtkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkSw8PDw8PDw8PDw9kZGQPDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0sPDw8PDw8PDw8PZGRkDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLDw8PDw8PDw8PDw8PD2QPDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkSw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEsPD2QPDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2RkZGRkZGRkZA8PZA8PZA8PZA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLZGRLS2RkZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLZGRkS0tkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZEtkZGRkS0tLS0tLS2RkZGRkZEtLZEtkZA9kS2RkZEtkZEtLS0tLS0tLS0tLS2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLPQAAyEJAAUoPDXx07L4V9dPBvB2GnRk9VQAAgD9Y4xKaAdecAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAABAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQABAQEBAQEBAQEAAAEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEAAAEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMAAAADAwMDAwMAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEAAAEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEAAAAAAQEBAQAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAABAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAACAgIAAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAACAgICAgICAgIAAAICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQADAwMAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAwMDAwMDAwMDAwMDAAMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAADAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAADAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAADAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAADAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAAADAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAMDAwMDAAMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAwMDAwMAAAMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAADAwMDAwADAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAADAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAADAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAAAAAAMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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 \ No newline at end of file diff --git a/tests/fixtures/sharkiq_status_frame.b64 b/tests/fixtures/sharkiq_status_frame.b64 new file mode 100644 index 0000000..46ec5f0 --- /dev/null +++ b/tests/fixtures/sharkiq_status_frame.b64 @@ -0,0 +1 @@ +COimzQEQSiAOSggIA0BkSABYFFABggEXCgAYACAAKAAwAFgAYACAAQCIAQKaAQCYAgKgAgLoAgA= \ No newline at end of file diff --git a/tests/test_models.py b/tests/test_models.py index d69b6f8..b99388f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,8 +4,16 @@ from sharklocal.models import ( DeviceInfo, + MapFeature, + MapGrid, + MapPoint, + MapPose, + MapRoom, ProbeResult, + SuctionLevel, VacuumEvent, + VacuumLogEntry, + VacuumMap, VacuumMode, VacuumStatus, ) @@ -175,3 +183,118 @@ def test_probe_result_defaults_none(): result = ProbeResult() assert result.rest_mapping is None assert result.mqtt_mapping is None + + +# --------------------------------------------------------------------------- +# SuctionLevel +# --------------------------------------------------------------------------- + + +def test_suction_level_values(): + assert {level.value for level in SuctionLevel} == {"eco", "normal", "max"} + + +def test_suction_level_is_str_enum(): + assert SuctionLevel.MAX == "max" + + +# --------------------------------------------------------------------------- +# VacuumStatus — MQTT-only optional fields +# --------------------------------------------------------------------------- + + +def test_vacuum_status_mqtt_fields_default_none(): + status = VacuumStatus(mode=VacuumMode.IDLE) + assert status.job_active is None + assert status.deep_clean is None + assert status.recharge_resume is None + assert status.evac_resume is None + assert status.map is None + + +# --------------------------------------------------------------------------- +# MapGrid +# --------------------------------------------------------------------------- + + +def _grid(room_ids=None) -> MapGrid: + return MapGrid( + resolution=0.06, + width=2, + height=2, + origin=MapPoint(-1.0, 0.5), + cells=b"\x0f\x64\x4b\x00", + room_ids=room_ids, + ) + + +@pytest.mark.parametrize( + "value, wall, floor", + [ + (0x00, False, False), # void + (0x0A, False, True), # floor + (0x0F, False, True), # floor + (0x19, False, True), # floor + (0x4B, False, False), # unknown + (0x5C, True, False), # wall + (0x64, True, False), # wall + ], +) +def test_map_grid_cell_classification(value, wall, floor): + assert MapGrid.is_wall(value) is wall + assert MapGrid.is_floor(value) is floor + + +def test_map_grid_cell_lookup_is_row_major(): + grid = _grid() + assert grid.cell(0, 0) == 0x0F + assert grid.cell(1, 0) == 0x64 + assert grid.cell(0, 1) == 0x4B + assert grid.cell(1, 1) == 0x00 + + +def test_map_grid_room_id_lookup(): + assert _grid().room_id(1, 1) is None + assert _grid(room_ids=b"\x01\x01\x02\x00").room_id(0, 1) == 2 + + +def test_map_grid_constants(): + assert MapGrid.UNKNOWN == 0x4B + assert MapGrid.VOID == 0x00 + + +# --------------------------------------------------------------------------- +# VacuumMap +# --------------------------------------------------------------------------- + + +def test_vacuum_map_defaults(): + vacuum_map = VacuumMap(grid=_grid()) + assert vacuum_map.path == [] + assert vacuum_map.poses == [] + assert vacuum_map.persisted is False + assert vacuum_map.rooms == [] + assert vacuum_map.features == [] + assert vacuum_map.log == [] + assert vacuum_map.robot is None + assert vacuum_map.cleaned_area is None + + +def test_vacuum_map_robot_is_last_pose(): + poses = [MapPose(0.0, 0.0, 0.0), MapPose(1.0, 2.0, 3.1)] + assert VacuumMap(grid=_grid(), poses=poses).robot == poses[-1] + + +def test_vacuum_map_cleaned_area_uses_grid_resolution(): + vacuum_map = VacuumMap(grid=_grid(), cleaned_cells=100) + assert vacuum_map.cleaned_area == pytest.approx(100 * 0.06 * 0.06) + + +def test_map_room_and_feature_and_log_entry_fields(): + room = MapRoom(name="Kitchen", polygon=[MapPoint(0, 0)]) + assert room.selected is False + assert room.coverage is None + feature = MapFeature(kind="door", points=[MapPoint(0, 0), MapPoint(1, 0)]) + assert feature.kind == "door" + entry = VacuumLogEntry(key="DT_WARNING_CODE", time=1789867008, code="WARN_MM_LOWLIGHT") + assert entry.code == "WARN_MM_LOWLIGHT" diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index 20ec77a..39584ca 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -703,3 +703,119 @@ async def test_monitor_reraises_connect_error_from_subscribe(mqtt_mapping): with patch("aiomqtt.Client", return_value=mock_ctx): with pytest.raises(ConnectError, match="broker dropped connection"): await client.monitor(lambda s: None) + + +# --------------------------------------------------------------------------- +# _decode_sharkiq_protobuf_v1 — settings, job flags and map frames +# --------------------------------------------------------------------------- + + +def test_decode_sharkiq_settings_default_when_fields_absent(): + result = _decode_sharkiq_protobuf_v1(_build_status_payload(14, 3, 100), _MODES) + assert result.recharge_resume is False + assert result.evac_resume is False + assert result.job_active is False + assert result.deep_clean is None + assert result.map is None + + +def test_decode_sharkiq_settings_reflected_by_presence(): + payload = ( + _build_status_payload(6, 3, 90) + + _field(35, 0, _varint(2)) + + _field(36, 0, _varint(2)) + + _field(45, 0, _varint(2)) + ) + result = _decode_sharkiq_protobuf_v1(payload, _MODES) + assert result.recharge_resume is True + assert result.evac_resume is True + assert result.job_active is True + + +@pytest.mark.parametrize("value, expected", [(2, False), (1, True)]) +def test_decode_sharkiq_deep_clean_from_field_40(value, expected): + payload = _build_status_payload(6, 3, 90) + _field(40, 0, _varint(value)) + assert _decode_sharkiq_protobuf_v1(payload, _MODES).deep_clean is expected + + +def _map_frame_payload(mode: int = 6) -> bytes: + """A status payload with a minimal map in field 7 (2x2 grid, one path point).""" + origin = _field(1, 5, struct.pack(" bytes: + """Return the decoded bytes of a base64 fixture captured from a real robot.""" + return base64.b64decode((FIXTURES / name).read_text().strip()) + + +def _fields(data: bytes): + return protobuf.decode_fields(data) + + +def _point_f32(x: float, y: float) -> bytes: + return protobuf.encode_bytes_field( + 1, protobuf.encode_float_field(1, x) + protobuf.encode_float_field(2, y) + ) + + +def _grid_message(width: int = 2, height: int = 2, cells: bytes = b"\x0f\x64\x4b\x00") -> bytes: + origin = protobuf.encode_float_field(1, -1.0) + protobuf.encode_float_field(2, 0.5) + return ( + protobuf.encode_float_field(1, 0.06) + + protobuf.encode_bytes_field(2, origin) + + protobuf.encode_varint_field(3, width) + + protobuf.encode_varint_field(4, height) + + protobuf.encode_bytes_field(6, cells) + ) + + +def _int16_list(values: list, scale: float = 0.002) -> bytes: + raw = struct.pack(f"<{len(values)}h", *values) + return ( + protobuf.encode_float_field(1, scale) + + protobuf.encode_varint_field(2, 2) + + protobuf.encode_bytes_field(3, raw) + ) + + +def _frame(map_fields: bytes, *, persisted: bool = False) -> dict: + frame = protobuf.encode_varint_field(4, 6) + protobuf.encode_bytes_field(FIELD_MAP, map_fields) + if persisted: + frame = protobuf.encode_varint_field(FIELD_PERSISTED_FLAG, 1) + frame + return _fields(frame) + + +# --------------------------------------------------------------------------- +# has_map / decode_map — frames without a grid +# --------------------------------------------------------------------------- + + +def test_has_map_false_without_field_7(): + assert has_map(_fields(protobuf.encode_varint_field(4, 14))) is False + + +def test_has_map_false_when_field_7_has_no_grid(): + frame = _fields(protobuf.encode_bytes_field(FIELD_MAP, protobuf.encode_varint_field(1, 1))) + assert has_map(frame) is False + + +def test_has_map_false_when_field_7_is_not_bytes(): + assert has_map({FIELD_MAP: [5]}) is False + + +def test_decode_map_returns_none_without_field_7(): + assert decode_map(_fields(protobuf.encode_varint_field(4, 14))) is None + + +def test_decode_map_returns_none_when_field_7_is_not_bytes(): + assert decode_map({FIELD_MAP: [5]}) is None + + +def test_decode_map_returns_none_without_grid(): + frame = _fields(protobuf.encode_bytes_field(FIELD_MAP, protobuf.encode_varint_field(1, 1))) + assert decode_map(frame) is None + + +# --------------------------------------------------------------------------- +# decode_map — minimal hand-built frames +# --------------------------------------------------------------------------- + + +def test_decode_grid_geometry_and_cells(): + vacuum_map = decode_map(_frame(protobuf.encode_bytes_field(5, _grid_message()))) + grid = vacuum_map.grid + assert (grid.width, grid.height) == (2, 2) + assert grid.resolution == pytest.approx(0.06) + assert (grid.origin.x, grid.origin.y) == (-1.0, 0.5) + assert grid.cells == b"\x0f\x64\x4b\x00" + assert grid.room_ids is None + assert grid.cell(1, 0) == 0x64 + assert grid.room_id(0, 0) is None + + +def test_decode_live_frame_defaults(): + vacuum_map = decode_map(_frame(protobuf.encode_bytes_field(5, _grid_message()))) + assert vacuum_map.persisted is False + assert vacuum_map.path == [] + assert vacuum_map.poses == [] + assert vacuum_map.robot is None + assert vacuum_map.dock is None + assert vacuum_map.rooms == [] + assert vacuum_map.features == [] + assert vacuum_map.log == [] + assert vacuum_map.cleaned_area is None + assert vacuum_map.map_id is None + assert vacuum_map.name is None + + +def test_decode_path_and_poses_scale_int16(): + # path: (100, -50) -> (0.2, -0.1); poses: (10, 20, 1570) -> heading ~ pi + fields = ( + protobuf.encode_bytes_field(5, _grid_message()) + + protobuf.encode_bytes_field(8, _int16_list([100, -50])) + + protobuf.encode_bytes_field(10, _int16_list([10, 20, 1570])) + ) + vacuum_map = decode_map(_frame(fields)) + assert vacuum_map.path == [MapPoint(pytest.approx(0.2), pytest.approx(-0.1))] + assert vacuum_map.robot.x == pytest.approx(0.02) + assert vacuum_map.robot.y == pytest.approx(0.04) + assert vacuum_map.robot.heading == pytest.approx(3.14) + + +def test_decode_ignores_trailing_partial_tuple(): + # Five int16s cannot make whole (x, y) pairs — the odd one is dropped. + fields = protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field( + 8, _int16_list([1, 2, 3, 4, 5]) + ) + assert len(decode_map(_frame(fields)).path) == 2 + + +def test_decode_persisted_flag_id_name_and_dock(): + dock = ( + protobuf.encode_float_field(1, -0.5) + + protobuf.encode_float_field(2, 0.25) + + protobuf.encode_float_field(3, -0.05) + ) + fields = ( + protobuf.encode_string_field(2, "unnamed") + + protobuf.encode_string_field(3, "3CDBBC59") + + protobuf.encode_bytes_field(5, _grid_message()) + + protobuf.encode_bytes_field(7, dock) + ) + vacuum_map = decode_map(_frame(fields, persisted=True)) + assert vacuum_map.persisted is True + assert vacuum_map.map_id == "3CDBBC59" + assert vacuum_map.name == "unnamed" + assert vacuum_map.dock == MapPose(-0.5, 0.25, pytest.approx(-0.05)) + + +def test_decode_room_record(): + polygon = _point_f32(0.0, 0.0) + _point_f32(1.0, 0.0) + _point_f32(1.0, 1.0) + _point_f32(0.0, 1.0) + room = ( + protobuf.encode_string_field(2, "Kitchen") + + protobuf.encode_string_field(3, "Kitchen") + + protobuf.encode_bytes_field(4, polygon) + + protobuf.encode_varint_field(5, 1) + + protobuf.encode_float_field(6, 0.5) + ) + bare = protobuf.encode_bytes_field(4, b"") + fields = ( + protobuf.encode_bytes_field(5, _grid_message()) + + protobuf.encode_bytes_field(15, room) + + protobuf.encode_bytes_field(15, bare) + ) + rooms = decode_map(_frame(fields)).rooms + assert rooms[0].name == "Kitchen" + assert rooms[0].selected is True + assert rooms[0].coverage == pytest.approx(0.5) + assert rooms[0].polygon == [MapPoint(0.0, 0.0), MapPoint(1.0, 0.0), MapPoint(1.0, 1.0), MapPoint(0.0, 1.0)] + # A record with no name, no selection and no coverage decodes to safe defaults. + assert rooms[1] == MapRoom(name="", polygon=[], selected=False, coverage=None) + + +def test_decode_features_edge_and_door_and_skips_malformed(): + polyline = _point_f32(0.0, 0.0) + _point_f32(2.0, 0.0) + edge = protobuf.encode_string_field(2, "edge") + protobuf.encode_bytes_field(4, polyline) + door = protobuf.encode_string_field(2, "door") + protobuf.encode_bytes_field(4, polyline) + no_kind = protobuf.encode_bytes_field(4, polyline) + no_points = protobuf.encode_string_field(2, "edge") + fields = protobuf.encode_bytes_field(5, _grid_message()) + for record in (edge, door, no_kind, no_points): + fields += protobuf.encode_bytes_field(28, record) + features = decode_map(_frame(fields)).features + assert [(f.kind, len(f.points)) for f in features] == [("edge", 2), ("door", 2)] + + +def test_decode_room_ids_only_when_sizes_match(): + grid = _grid_message() + matching = protobuf.encode_bytes_field(21, protobuf.encode_bytes_field(19, b"\x01\x01\x02\x00")) + wrong_size = protobuf.encode_bytes_field(21, protobuf.encode_bytes_field(19, b"\x01")) + no_raster = protobuf.encode_bytes_field(21, protobuf.encode_varint_field(1, 1)) + + with_ids = decode_map(_frame(protobuf.encode_bytes_field(5, grid) + matching)) + assert with_ids.grid.room_ids == b"\x01\x01\x02\x00" + assert with_ids.grid.room_id(0, 1) == 2 + + assert decode_map(_frame(protobuf.encode_bytes_field(5, grid) + wrong_size)).grid.room_ids is None + assert decode_map(_frame(protobuf.encode_bytes_field(5, grid) + no_raster)).grid.room_ids is None + + +def test_decode_job_stats_and_cleaned_area(): + stats = ( + protobuf.encode_varint_field(1, 1789867008) + + protobuf.encode_varint_field(3, 335) + + protobuf.encode_varint_field(6, 100) + ) + fields = protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field(11, stats) + vacuum_map = decode_map(_frame(fields)) + assert vacuum_map.job_started == 1789867008 + assert vacuum_map.job_duration == 335 + assert vacuum_map.cleaned_cells == 100 + assert vacuum_map.cleaned_area == pytest.approx(100 * 0.06 * 0.06) + + +# --------------------------------------------------------------------------- +# decode_map — event log (field 20.3.6) +# --------------------------------------------------------------------------- + + +def _log_frame(log_field_20: bytes) -> dict: + return _frame(protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field(20, log_field_20)) + + +def _log_message(text: bytes) -> bytes: + return protobuf.encode_bytes_field(3, protobuf.encode_bytes_field(6, text)) + + +def test_decode_log_entries(): + entries = [ + {"key": "DT_STATE_CHANGE", "time": "1789867007s", "code": "SYS_ST_CLEANING"}, + {"key": "DT_WARNING_CODE", "time": "1789867008s", "code": "WARN_MM_LOWLIGHT"}, + {"key": "DT_VERSION_MCU", "time": "0s", "code": "Lidar2_0M1.0.91"}, + "not-a-dict", + {"key": "DT_ODD_TIME", "time": "soon", "code": "x"}, + ] + log = decode_map(_log_frame(_log_message(json.dumps(entries).encode()))).log + assert [(e.key, e.time, e.code) for e in log] == [ + ("DT_STATE_CHANGE", 1789867007, "SYS_ST_CLEANING"), + ("DT_WARNING_CODE", 1789867008, "WARN_MM_LOWLIGHT"), + ("DT_VERSION_MCU", 0, "Lidar2_0M1.0.91"), + ("DT_ODD_TIME", 0, "x"), + ] + + +def test_decode_log_tolerates_missing_or_invalid_parts(): + assert decode_map(_log_frame(protobuf.encode_varint_field(1, 1))).log == [] + assert decode_map(_log_frame(protobuf.encode_bytes_field(3, protobuf.encode_varint_field(1, 1)))).log == [] + assert decode_map(_log_frame(_log_message(b"{not json"))).log == [] + + +# --------------------------------------------------------------------------- +# decode_map — real captured frames +# --------------------------------------------------------------------------- + + +def test_decode_real_live_frame(): + vacuum_map = decode_map(_fields(_fixture("sharkiq_live_map_frame.b64"))) + assert vacuum_map.persisted is False + assert (vacuum_map.grid.width, vacuum_map.grid.height) == (191, 106) + assert len(vacuum_map.grid.cells) == 191 * 106 + assert vacuum_map.grid.resolution == pytest.approx(0.06, abs=1e-6) + assert len(vacuum_map.path) == 486 + assert len(vacuum_map.poses) == 254 + # Path and pose logs describe the same trajectory; both end at the robot. + assert vacuum_map.path[-1].x == pytest.approx(vacuum_map.robot.x, abs=0.2) + assert vacuum_map.path[-1].y == pytest.approx(vacuum_map.robot.y, abs=0.2) + assert vacuum_map.rooms == [] + + +def test_decode_real_persisted_frame(): + vacuum_map = decode_map(_fields(_fixture("sharkiq_persisted_map_frame.b64"))) + assert vacuum_map.persisted is True + assert vacuum_map.map_id == "3CDBBC59" + assert vacuum_map.name == "unnamed" + assert [room.name for room in vacuum_map.rooms] == ["Bathroom. ", "Room", "Laundry Room", "Hallway"] + assert vacuum_map.rooms[0].selected is True + assert vacuum_map.rooms[0].coverage == pytest.approx(0.848, abs=1e-3) + assert all(len(room.polygon) == 4 for room in vacuum_map.rooms) + assert [(f.kind, len(f.points)) for f in vacuum_map.features] == [ + ("edge", 119), ("edge", 31), ("edge", 105), ("door", 2), ("door", 2), + ] + assert vacuum_map.dock.x == pytest.approx(-0.52, abs=0.01) + assert len(vacuum_map.grid.room_ids) == len(vacuum_map.grid.cells) + assert set(vacuum_map.grid.room_ids) == {0, 1, 2, 3} + assert vacuum_map.job_duration == 335 + assert vacuum_map.cleaned_area == pytest.approx(14.2, abs=0.1) + assert len(vacuum_map.log) == 83 + assert [e.code for e in vacuum_map.log if e.key == "DT_WARNING_CODE"] == ["WARN_MM_LOWLIGHT"] + + +# --------------------------------------------------------------------------- +# encode_room_selection / encode_spot_selection +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def persisted_map() -> VacuumMap: + return decode_map(_fields(_fixture("sharkiq_persisted_map_frame.b64"))) + + +def test_encode_room_selection_matches_app_capture(persisted_map): + assert encode_room_selection(persisted_map, ["Bathroom. "]) == _fixture("sharkiq_cmd_room_clean.b64") + + +def test_encode_room_selection_deep_matches_app_capture(persisted_map): + expected = _fixture("sharkiq_cmd_room_clean_matrix.b64") + assert encode_room_selection(persisted_map, ["Bathroom. "], deep=True) == expected + + +def test_encode_room_selection_lists_every_chosen_room(persisted_map): + payload = encode_room_selection(persisted_map, ["Bathroom. ", "Hallway"]) + selection = _fields(_fields(payload)[41][0]) + assert selection[1] == [b"Bathroom. ", b"Hallway"] + assert selection[3] == [b"3CDBBC59"] + assert _fields(payload)[16] == [11] + + +def test_encode_room_selection_rejects_unknown_room(persisted_map): + with pytest.raises(ValueError, match="Unknown room"): + encode_room_selection(persisted_map, ["Attic"]) + + +def test_encode_room_selection_requires_map_id(persisted_map): + persisted_map.map_id = None + with pytest.raises(ValueError, match="no id"): + encode_room_selection(persisted_map, ["Bathroom. "]) + + +def test_encode_spot_selection_appends_pindrop_room(persisted_map): + app = _fixture("sharkiq_cmd_spot_clean.b64") + app_rooms = _fields(_fields(app)[6][0])[15] + app_spot = _fields(app_rooms[-1]) + app_polygon = [_fields(p) for p in _fields(app_spot[4][0])[1]] + centre_x = sum(protobuf.float32(p[1][0]) for p in app_polygon) / 4 + centre_y = sum(protobuf.float32(p[2][0]) for p in app_polygon) / 4 + + ours = encode_spot_selection(persisted_map, centre_x, centre_y) + our_rooms = _fields(_fields(ours)[6][0])[15] + assert our_rooms[:-1] == app_rooms[:-1] + + spot = _fields(our_rooms[-1]) + assert spot[2] == [SPOT_ROOM_NAME.encode()] + assert (spot[7], spot[8], spot[9], spot[10]) == ([2], [1], [0], [2]) + corners = [_fields(p) for p in _fields(spot[4][0])[1]] + assert protobuf.float32(corners[0][1][0]) == pytest.approx(centre_x - SPOT_HALF_SIZE, abs=1e-5) + assert protobuf.float32(corners[2][2][0]) == pytest.approx(centre_y - SPOT_HALF_SIZE, abs=1e-5) + assert _fields(_fields(ours)[41][0])[1] == [SPOT_ROOM_NAME.encode()] + + +def test_encode_spot_selection_requires_map_id(persisted_map): + persisted_map.map_id = None + with pytest.raises(ValueError, match="no id"): + encode_spot_selection(persisted_map, 0.0, 0.0) From b46b5d796f368f701223d957487f7289b0de82bb Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 19 Sep 2026 22:15:09 -0400 Subject: [PATCH 03/33] Add suction, find robot and resume settings to the sharkiq_v1 MQTT mapping start_cleaning and stop/go_home now send the bare job command; the previous payloads also set suction (normal on start, eco on dock). Mode 4 maps to idle. --- sharklocal/__main__.py | 30 ++++++++++++++- sharklocal/mappings/mqtt/sharkiq_v1.yaml | 48 ++++++++++++++++++++++-- tests/test_main.py | 41 +++++++++++++++++++- 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/sharklocal/__main__.py b/sharklocal/__main__.py index 9f7dc3b..71ec1c7 100644 --- a/sharklocal/__main__.py +++ b/sharklocal/__main__.py @@ -19,7 +19,12 @@ ) # Actions metadata for help and validation -VALID_COMMANDS = ["start", "stop", "dock", "status", "events", "info"] +VALID_COMMANDS = [ + "start", "stop", "dock", "status", "events", "info", "find", + "suction-eco", "suction-normal", "suction-max", + "recharge-resume-on", "recharge-resume-off", + "evac-resume-on", "evac-resume-off", +] ACTION_MAP = { "status": "get_status", "start": "start_cleaning", @@ -27,6 +32,14 @@ "dock": "go_home", "events": "get_events", "info": "get_robot_id", + "find": "find_robot", + "suction-eco": "set_suction_eco", + "suction-normal": "set_suction_normal", + "suction-max": "set_suction_max", + "recharge-resume-on": "recharge_resume_on", + "recharge-resume-off": "recharge_resume_off", + "evac-resume-on": "evac_resume_on", + "evac-resume-off": "evac_resume_off", } def print_status(status: VacuumStatus, prefix: str = "[STATUS]"): @@ -35,6 +48,21 @@ def print_status(status: VacuumStatus, prefix: str = "[STATUS]"): chg = f" (Charging)" if status.charging else "" mode_str = status.mode.value if hasattr(status.mode, "value") else str(status.mode) print(f"\r{prefix} Mode: {mode_str:20} | Battery: {bat}{chg}", end="", flush=True) + if status.map is not None: + print_map(status.map) + +def print_map(vacuum_map) -> None: + """Helper to summarise a map frame on its own line.""" + grid = vacuum_map.grid + robot = vacuum_map.robot + pose = f"({robot.x:.2f}, {robot.y:.2f}) @ {robot.heading:.2f} rad" if robot else "unknown" + kind = "persisted map" if vacuum_map.persisted else "live map" + rooms = ", ".join(r.name for r in vacuum_map.rooms) or "none" + print( + f"\n[MAP] {kind}: {grid.width}x{grid.height} @ {grid.resolution:.2f} m | " + f"path pts: {len(vacuum_map.path)} | robot: {pose} | rooms: {rooms}", + flush=True, + ) async def run_command(host: str, cmd: str, transport: str): """Execute a single specific command.""" diff --git a/sharklocal/mappings/mqtt/sharkiq_v1.yaml b/sharklocal/mappings/mqtt/sharkiq_v1.yaml index aa292fe..c157ec8 100644 --- a/sharklocal/mappings/mqtt/sharkiq_v1.yaml +++ b/sharklocal/mappings/mqtt/sharkiq_v1.yaml @@ -18,24 +18,66 @@ status_decoder: sharkiq_protobuf_v1 # Maps protobuf OperatingMode integer values to normalized VacuumMode strings. # Reference: sharkiq.proto OperatingMode enum modes: + 4: idle 6: cleaning 7: returning_to_dock 13: docking 14: docked +# Payloads are protobuf. Field 16 is the job command (9 start, 17 return to +# dock); field 7 carries settings, which the robot echoes back in field 8 of +# the next status message. actions: start_cleaning: type: command - payload: "OgQKAhBLgAEJ" + payload: "gAEJ" + # The app bundles a suction change into its start/dock messages; these + # send the bare job command so the setting is left alone. stop: type: command - payload: "OgQKAhAygAER" + payload: "gAER" # go_home issues the same stop-and-return command as stop in this protocol. go_home: type: command - payload: "OgQKAhAygAER" + payload: "gAER" + + # Field 25 = 104 + find_robot: + type: command + payload: "yAFo" + + # Field 7.1.2 = 50 / 75 / 100 + set_suction_eco: + type: command + payload: "OgQKAhAy" + + set_suction_normal: + type: command + payload: "OgQKAhBL" + + set_suction_max: + type: command + payload: "OgQKAhBk" + + # Field 7.8 = 1 / 2. Reflected by field 35 present / absent in status. + recharge_resume_on: + type: command + payload: "OgJAAQ==" + + recharge_resume_off: + type: command + payload: "OgJAAg==" + + # Field 7.7 = 1 / 2. Reflected by field 36 present / absent in status. + evac_resume_on: + type: command + payload: "OgI4AQ==" + + evac_resume_off: + type: command + payload: "OgI4Ag==" get_status: type: status_request diff --git a/tests/test_main.py b/tests/test_main.py index f62cbfe..9fd3964 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -11,6 +11,7 @@ from sharklocal.__main__ import ( generate_markdown_report, main, + print_map, print_status, run_command, run_probe, @@ -19,7 +20,17 @@ setup_argparse, monitor_vacuum, ) -from sharklocal.models import DeviceInfo, ProbeResult, VacuumMode, VacuumStatus +from sharklocal.models import ( + DeviceInfo, + MapGrid, + MapPoint, + MapPose, + MapRoom, + ProbeResult, + VacuumMap, + VacuumMode, + VacuumStatus, +) # --------------------------------------------------------------------------- @@ -663,3 +674,31 @@ async def test_run_test_logic_load_mqtt_mapping_raises(capsys): # bad_mapping should not appear in results (exception was swallowed) assert "bad_mapping" not in results["mqtt"] + + +def test_print_status_with_map_adds_map_line(capsys): + """A map-bearing status prints a second, map summary line.""" + grid = MapGrid(resolution=0.06, width=191, height=105, origin=MapPoint(-8.43, -1.41), cells=b"") + vacuum_map = VacuumMap( + grid=grid, + path=[MapPoint(0, 0), MapPoint(0.1, 0)], + poses=[MapPose(-6.27, 1.72, -2.89)], + rooms=[MapRoom(name="Bathroom", polygon=[]), MapRoom(name="Hallway", polygon=[])], + ) + status = VacuumStatus(mode=VacuumMode.CLEANING, battery_level=84, map=vacuum_map) + print_status(status) + captured = capsys.readouterr() + assert "[STATUS] Mode: cleaning" in captured.out + assert "[MAP] live map: 191x105 @ 0.06 m" in captured.out + assert "path pts: 2" in captured.out + assert "robot: (-6.27, 1.72) @ -2.89 rad" in captured.out + assert "rooms: Bathroom, Hallway" in captured.out + + +def test_print_map_persisted_without_robot_or_rooms(capsys): + grid = MapGrid(resolution=0.06, width=2, height=2, origin=MapPoint(0, 0), cells=b"") + print_map(VacuumMap(grid=grid, persisted=True)) + captured = capsys.readouterr() + assert "[MAP] persisted map: 2x2" in captured.out + assert "robot: unknown" in captured.out + assert "rooms: none" in captured.out From 65a35c96f91e11410b2ec7891d34bae4817d2ff7 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 19 Sep 2026 22:15:09 -0400 Subject: [PATCH 04/33] Add settings, room and spot cleaning to VacuumClient --- sharklocal/client.py | 134 +++++++++++++++++++++++++++++- tests/test_client.py | 194 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 325 insertions(+), 3 deletions(-) diff --git a/sharklocal/client.py b/sharklocal/client.py index 20c3aa9..1095abf 100644 --- a/sharklocal/client.py +++ b/sharklocal/client.py @@ -8,9 +8,17 @@ from .exceptions import ActionNotSupportedError, ConnectError, SharklocalError from .mappings import load_mqtt_mapping, load_rest_mapping -from .models import DeviceInfo, ProbeResult, VacuumEvent, VacuumStatus +from .models import ( + DeviceInfo, + ProbeResult, + SuctionLevel, + VacuumEvent, + VacuumMap, + VacuumStatus, +) from .mqtt_client import MQTTVacuumClient from .rest_client import RESTVacuumClient +from .vacuum_map import encode_room_selection, encode_spot_selection class VacuumClient: @@ -73,6 +81,13 @@ def __init__( self._monitor_stop: Optional[asyncio.Event] = None self._monitor_task: Optional[asyncio.Task] = None + # Most recent status and map seen while monitoring. ``last_map`` keeps + # the latest *persisted* map (rooms, dock, vectors) — room and spot + # cleaning need its room definition — and is not replaced by the + # room-less live frames a job produces. + self.last_status: Optional[VacuumStatus] = None + self.last_map: Optional[VacuumMap] = None + # Primary transport in use. "REST" when REST is pinned and reachable, # "MQTT" when only MQTT is available, "NONE" until probe() confirms a # working transport (or a single mapping is auto-pinned). @@ -153,6 +168,88 @@ async def get_wifi_status(self) -> DeviceInfo: """Return Wi-Fi connection details including MAC address.""" return await self._execute("get_wifi_status") + async def find_robot(self) -> bool: + """Make the vacuum play its locate sound.""" + return await self._execute("find_robot") + + async def set_suction(self, level: SuctionLevel) -> bool: + """Set the suction power level (``eco``, ``normal`` or ``max``). + + The setting has no field in the status message — the robot only echoes + it once on change — so callers wanting to display it should remember + the last level they set. + """ + return await self._execute(f"set_suction_{SuctionLevel(level).value}") + + async def set_recharge_resume(self, enabled: bool) -> bool: + """Enable or disable Recharge & Resume (finish the job after charging).""" + return await self._execute(f"recharge_resume_{'on' if enabled else 'off'}") + + async def set_evac_resume(self, enabled: bool) -> bool: + """Enable or disable Evac & Resume (empty the bin mid-job, then continue).""" + return await self._execute(f"evac_resume_{'on' if enabled else 'off'}") + + async def clean_rooms( + self, + room_names: List[str], + *, + deep: bool = False, + vacuum_map: Optional[VacuumMap] = None, + ) -> bool: + """Clean the named rooms, then start. + + Room cleaning is an MQTT-only, two-message sequence: the map's room + definition with the selection, then ``start_cleaning``. It needs a + persisted map for the room definition — pass one, or let the client use + the latest seen while monitoring (:attr:`last_map`). + + Args: + room_names: Room names exactly as shown in the app. + deep: Request a Matrix (two-pass) clean of those rooms. + vacuum_map: The persisted map to take the room definition from. + + Raises: + SharklocalError: If no persisted map is available. + ValueError: If a room name is not on the map. + ActionNotSupportedError: If no MQTT mapping is configured. + """ + payload = self._room_definition_payload( + vacuum_map, lambda m: encode_room_selection(m, room_names, deep=deep) + ) + await self._send(payload) + return await self.start_cleaning() + + async def clean_spot( + self, + x: float, + y: float, + *, + vacuum_map: Optional[VacuumMap] = None, + ) -> bool: + """Spot-clean a ~1.5 m square centred on ``(x, y)`` metres, then start. + + Same requirements as :meth:`clean_rooms`. The coordinates are in the + map frame — the one :class:`~sharklocal.models.VacuumMap` reports. + """ + payload = self._room_definition_payload( + vacuum_map, lambda m: encode_spot_selection(m, x, y) + ) + await self._send(payload) + return await self.start_cleaning() + + def _room_definition_payload( + self, + vacuum_map: Optional[VacuumMap], + build: Callable[[VacuumMap], bytes], + ) -> bytes: + chosen = vacuum_map or self.last_map + if chosen is None or not chosen.persisted: + raise SharklocalError( + "A persisted map is required for room or spot cleaning; the robot " + "publishes one each time it docks — monitor until last_map is set" + ) + return build(chosen) + # ------------------------------------------------------------------ # Mapping probe # ------------------------------------------------------------------ @@ -253,9 +350,20 @@ async def start_monitoring(self) -> None: self._monitor_stop = asyncio.Event() self._monitor_task = asyncio.ensure_future( - self._mqtt.monitor(self._status_callback, stop_event=self._monitor_stop) + self._mqtt.monitor(self._on_monitor_status, stop_event=self._monitor_stop) ) + async def _on_monitor_status(self, status: VacuumStatus) -> None: + """Cache the latest status and persisted map, then forward to the callback.""" + self.last_status = status + if status.map is not None and status.map.persisted: + self.last_map = status.map + callback = self._status_callback + if asyncio.iscoroutinefunction(callback): + await callback(status) + else: + callback(status) # type: ignore[misc] + async def stop_monitoring(self) -> None: """Stop background status monitoring if it is running.""" if self._monitor_stop: @@ -313,6 +421,28 @@ def active_mqtt_mapping(self) -> Optional[str]: # Internal transport evaluation # ------------------------------------------------------------------ + async def _send(self, payload: bytes) -> bool: + """Publish a runtime-built payload over the active MQTT transport. + + Only MQTT can carry these, so REST is never consulted. With several + MQTT candidates and no pinned client, each is tried in order. + """ + mqtt_options: List[MQTTVacuumClient] = ( + [self._mqtt] if self._mqtt is not None else self._mqtt_candidates + ) + if not mqtt_options: + raise ActionNotSupportedError( + "Room and spot cleaning need an MQTT mapping; none is configured" + ) + + last_connect_error: Optional[ConnectError] = None + for client in mqtt_options: + try: + return await client.send(payload) + except ConnectError as exc: + last_connect_error = exc + raise last_connect_error # type: ignore[misc] + async def _execute(self, action: str) -> Any: """Execute *action* using the best available transport. diff --git a/tests/test_client.py b/tests/test_client.py index 9ddee06..7cfed29 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -15,7 +15,18 @@ ConnectError, SharklocalError, ) -from sharklocal.models import DeviceInfo, ProbeResult, VacuumEvent, VacuumMode, VacuumStatus +from sharklocal.models import ( + DeviceInfo, + MapGrid, + MapPoint, + MapRoom, + ProbeResult, + SuctionLevel, + VacuumEvent, + VacuumMap, + VacuumMode, + VacuumStatus, +) # --------------------------------------------------------------------------- @@ -576,3 +587,184 @@ async def test_stop_monitoring_cancels_task(): await client.stop_monitoring() assert client._monitor_task is None assert client._monitor_stop is None + + +# --------------------------------------------------------------------------- +# Settings and locate actions → _execute() call with correct action name +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + "method_name, args, expected_action", + [ + ("find_robot", (), "find_robot"), + ("set_suction", (SuctionLevel.ECO,), "set_suction_eco"), + ("set_suction", ("normal",), "set_suction_normal"), + ("set_suction", (SuctionLevel.MAX,), "set_suction_max"), + ("set_recharge_resume", (True,), "recharge_resume_on"), + ("set_recharge_resume", (False,), "recharge_resume_off"), + ("set_evac_resume", (True,), "evac_resume_on"), + ("set_evac_resume", (False,), "evac_resume_off"), + ], +) +async def test_setting_actions_call_execute(method_name, args, expected_action): + client = _make_vacuum_client_with_mocks() + with patch.object(client, "_execute", new_callable=AsyncMock) as mock_exec: + mock_exec.return_value = True + await getattr(client, method_name)(*args) + mock_exec.assert_awaited_once_with(expected_action) + + +async def test_set_suction_rejects_unknown_level(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(ValueError): + await client.set_suction("turbo") + + +# --------------------------------------------------------------------------- +# clean_rooms() / clean_spot() — room definition, then start +# --------------------------------------------------------------------------- + + +def _persisted_map(persisted: bool = True) -> VacuumMap: + grid = MapGrid(resolution=0.06, width=1, height=1, origin=MapPoint(0.0, 0.0), cells=b"\x0f") + room = MapRoom(name="Kitchen", polygon=[MapPoint(0, 0), MapPoint(1, 0), MapPoint(1, 1), MapPoint(0, 1)]) + return VacuumMap(grid=grid, persisted=persisted, map_id="ABCD1234", rooms=[room]) + + +async def test_clean_rooms_sends_selection_then_starts(): + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + with patch.object(client, "_execute", new_callable=AsyncMock) as mock_exec: + mock_exec.return_value = True + result = await client.clean_rooms(["Kitchen"], vacuum_map=_persisted_map()) + assert result is True + client._mqtt.send.assert_awaited_once() + payload = client._mqtt.send.await_args.args[0] + assert b"Kitchen" in payload and b"ABCD1234" in payload + mock_exec.assert_awaited_once_with("start_cleaning") + + +async def test_clean_rooms_uses_last_map_from_monitoring(): + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + client.last_map = _persisted_map() + with patch.object(client, "_execute", new_callable=AsyncMock) as mock_exec: + await client.clean_rooms(["Kitchen"], deep=True) + client._mqtt.send.assert_awaited_once() + mock_exec.assert_awaited_once_with("start_cleaning") + + +async def test_clean_rooms_without_persisted_map_raises(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(SharklocalError, match="persisted map"): + await client.clean_rooms(["Kitchen"]) + + +async def test_clean_rooms_rejects_live_map(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(SharklocalError, match="persisted map"): + await client.clean_rooms(["Kitchen"], vacuum_map=_persisted_map(persisted=False)) + + +async def test_clean_rooms_unknown_room_propagates_value_error(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(ValueError, match="Unknown room"): + await client.clean_rooms(["Attic"], vacuum_map=_persisted_map()) + + +async def test_clean_spot_sends_pindrop_then_starts(): + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + with patch.object(client, "_execute", new_callable=AsyncMock) as mock_exec: + mock_exec.return_value = True + result = await client.clean_spot(0.5, 0.5, vacuum_map=_persisted_map()) + assert result is True + assert b"PinDrop" in client._mqtt.send.await_args.args[0] + mock_exec.assert_awaited_once_with("start_cleaning") + + +async def test_clean_spot_without_map_raises(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(SharklocalError, match="persisted map"): + await client.clean_spot(0.0, 0.0) + + +# --------------------------------------------------------------------------- +# _send() — MQTT-only transport for runtime-built payloads +# --------------------------------------------------------------------------- + + +async def test_send_without_mqtt_raises_action_not_supported(): + with patch("sharklocal.client.load_rest_mapping"), patch("sharklocal.client.RESTVacuumClient"): + client = VacuumClient("host", rest_mappings="test_rest_v1") + with pytest.raises(ActionNotSupportedError, match="MQTT"): + await client._send(b"\x00") + + +async def test_send_uses_pinned_mqtt_client(): + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + assert await client._send(b"\x01") is True + client._mqtt.send.assert_awaited_once_with(b"\x01") + + +async def test_send_cascades_through_mqtt_candidates(): + mqtt_a = _make_mqtt_client_mock() + mqtt_a.send = AsyncMock(side_effect=ConnectError("a down")) + mqtt_b = _make_mqtt_client_mock() + mqtt_b.send = AsyncMock(return_value=True) + with patch("sharklocal.client.load_mqtt_mapping"), \ + patch("sharklocal.client.MQTTVacuumClient", side_effect=[mqtt_a, mqtt_b]): + client = VacuumClient("host", mqtt_mappings=["test_a", "test_b"]) + assert await client._send(b"\x02") is True + mqtt_a.send.assert_awaited_once() + mqtt_b.send.assert_awaited_once() + + +async def test_send_raises_last_connect_error_when_all_fail(): + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(side_effect=ConnectError("down")) + with pytest.raises(ConnectError, match="down"): + await client._send(b"\x03") + + +# --------------------------------------------------------------------------- +# _on_monitor_status() — caches status and persisted map, forwards to callback +# --------------------------------------------------------------------------- + + +async def test_on_monitor_status_forwards_to_sync_callback_and_caches(): + client = _make_vacuum_client_with_mocks() + received: List[VacuumStatus] = [] + client.on_status_update(received.append) + status = VacuumStatus(mode=VacuumMode.CLEANING, map=_persisted_map(persisted=False)) + await client._on_monitor_status(status) + assert received == [status] + assert client.last_status is status + assert client.last_map is None # live maps are not cached as the room definition + + +async def test_on_monitor_status_forwards_to_async_callback_and_caches_persisted_map(): + client = _make_vacuum_client_with_mocks() + received: List[VacuumStatus] = [] + + async def _cb(status: VacuumStatus) -> None: + received.append(status) + + client.on_status_update(_cb) + persisted = _persisted_map() + await client._on_monitor_status(VacuumStatus(mode=VacuumMode.DOCKED, map=persisted)) + await client._on_monitor_status(VacuumStatus(mode=VacuumMode.DOCKED)) + assert len(received) == 2 + assert client.last_map is persisted # a later map-less status keeps it + + +async def test_start_monitoring_wraps_callback(): + client = _make_vacuum_client_with_mocks() + client._status_callback = lambda s: None + client._mqtt.monitor = AsyncMock() + await client.start_monitoring() + await asyncio.sleep(0) + assert client._mqtt.monitor.await_args.args[0] == client._on_monitor_status + await client.stop_monitoring() From 3db5dc812e01dc404072e52eb0b3aa7e8ddfbe3f Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 19 Sep 2026 22:15:09 -0400 Subject: [PATCH 05/33] Document map data, new actions and update the RV2610BFCA report --- README.md | 26 ++++++- docs/cli.md | 4 +- docs/compatibility/rv2610bfca.md | 27 ++++++- docs/data-models.md | 121 +++++++++++++++++++++++++++++++ docs/mapping-configuration.md | 9 ++- docs/mappings.md | 19 ++++- docs/vacuum-client.md | 51 +++++++++++++ 7 files changed, 245 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bed89ec..7392fce 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,9 @@ sharklocal/ ├── client.py # VacuumClient — unified entry point with transport selection ├── rest_client.py # RESTVacuumClient — async HTTPS/HTTP client (aiohttp) ├── mqtt_client.py # MQTTVacuumClient — async MQTT client (aiomqtt) -├── protobuf.py # Pure-Python schema-free protobuf decoder -├── models.py # VacuumStatus, VacuumEvent, DeviceInfo, VacuumMode +├── protobuf.py # Pure-Python schema-free protobuf codec +├── vacuum_map.py # Map / room / trajectory decoding and room-command encoding (MQTT) +├── models.py # VacuumStatus, VacuumMap, VacuumEvent, DeviceInfo, VacuumMode ├── exceptions.py # Typed exception hierarchy └── mappings/ ├── __init__.py # load_* / list_* utilities @@ -155,6 +156,23 @@ All transport clients return normalized model objects. See [docs/data-models.md] --- +## Maps, Rooms and Settings (MQTT) + +On models that publish map frames, monitoring yields the live map (occupancy grid, cleaned path, robot pose with heading) every few seconds during a job, and a persisted map (rooms, wall and door vectors, dock pose, job statistics, the robot's event log) each time the robot docks. The `sharkiq_v1` MQTT mapping also exposes suction level, find-robot and the Recharge & Resume / Evac & Resume settings, and `VacuumClient` can start room, Matrix and spot cleans. + +```python +async with VacuumClient("192.168.1.100", mqtt_mappings="sharkiq_v1") as vacuum: + vacuum.on_status_update(lambda s: s.map and print(s.map.robot, len(s.map.path))) + await vacuum.start_monitoring() + + await vacuum.set_suction(SuctionLevel.MAX) + await vacuum.clean_rooms(["Kitchen"], deep=True) # needs vacuum.last_map (persisted) +``` + +See [docs/vacuum-client.md](docs/vacuum-client.md#maps-rooms-and-settings) and the `VacuumMap` section of [docs/data-models.md](docs/data-models.md). + +--- + ## Mapping Configuration See [docs/mapping-configuration.md](docs/mapping-configuration.md) for annotated YAML examples for both transports, the full field reference, instructions for adding support for a new model, and how to register a custom MQTT decoder. @@ -209,6 +227,10 @@ The minimum required coverage is **95%**. All PRs must pass before merging. See - The `status_water_tank_removed` event type is fired for dustbin removal on vacuums, not only water tank removal on mops. Handle accordingly in Home Assistant event translation. - The `/get/robot_id` endpoint does not expose a serial number. Use the `mac_address` from `/get/wifi_status` as the device `unique_id`. - MQTT `go_home` and `stop` send identical payloads in the `sharkiq_v1` mapping — both issue the protobuf stop-and-return command. +- The SharkClean app bundles a suction change into its start and dock messages. The `sharkiq_v1` mapping sends the bare job commands, so `start_cleaning` and `go_home` leave the suction setting alone. +- The suction level is not reported in MQTT status; the robot echoes a change once (field 8) and is then silent about it. Remember the last level you set. +- Room and spot cleans re-upload the map's room definition, names included. Build them from the latest persisted map (`VacuumClient.last_map`) or a stale one renames rooms. +- On the RV2610BFCA, `BatteryInfo.ChargingState` is `3` in every frame, so `charging` is `True` mid-clean. Use `mode` to decide whether the robot is docked. - The REST API uses a self-signed TLS certificate. SSL verification is disabled in the `sharkiq_v1` mapping (`verify_ssl: false`). - The REST `charging` field returns `"connected"` or `"unconnected"` as strings, not a boolean. The library normalises this to `True`/`False` on `VacuumStatus.charging`. - The REST `mode` field alone is insufficient to determine if a vacuum is docked. `mode: "ready"` with `charging: "connected"` means docked (`VacuumMode.DOCKED`); `mode: "ready"` with `charging: "unconnected"` means the vacuum is stopped but off the dock (`VacuumMode.IDLE`). This combined evaluation is handled automatically by the library. diff --git a/docs/cli.md b/docs/cli.md index b550edf..5b95c9a 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -52,4 +52,6 @@ Send a specific command via a chosen transport (defaults to `mqtt`): python -m sharklocal --cmd dock --transport mqtt ``` -Available commands: `start`, `stop`, `dock`, `status`, `events`, `info`. +Available commands: `start`, `stop`, `dock`, `status`, `events`, `info`, `find`, `suction-eco`, `suction-normal`, `suction-max`, `recharge-resume-on`, `recharge-resume-off`, `evac-resume-on`, `evac-resume-off`. + +The settings and `find` commands exist only in the MQTT mapping. While `--monitor` is running, any status update that carries map data prints an extra `[MAP]` line with the grid size, the number of cleaned-path points, the robot's pose and the room names (persisted map only). diff --git a/docs/compatibility/rv2610bfca.md b/docs/compatibility/rv2610bfca.md index e1eb009..a1282f5 100644 --- a/docs/compatibility/rv2610bfca.md +++ b/docs/compatibility/rv2610bfca.md @@ -11,9 +11,17 @@ | Return to dock | ❌ | ✅ | MQTT: `sharkiq_v1` | | Explore / Map | ❌ | ❌ | | | Get status | ❌ | ✅ | MQTT: `sharkiq_v1` | -| Get event log | ❌ | ❌ | | +| Get event log | ❌ | ✅ ¹ | MQTT: `sharkiq_v1` | | Get robot ID | ❌ | ❌ | | | Get Wi-Fi status | ❌ | ❌ | | +| Find robot | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Set suction (eco / normal / max) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Recharge & Resume on / off | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Evac & Resume on / off | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Clean rooms (incl. Matrix Clean) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Spot clean | ❌ | ✅ | MQTT: `sharkiq_v1` | + +> ¹ Via the event log embedded in the persisted map frame the robot publishes when it docks (`VacuumMap.log`), not via a request. --- @@ -23,7 +31,13 @@ |-------|:----:|:----:|--------------------| | Operating mode | ❌ | ✅ | MQTT: `sharkiq_v1` | | Battery level | ❌ | ✅ | MQTT: `sharkiq_v1` | -| Charging status | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Charging status | ❌ | ✅ ² | MQTT: `sharkiq_v1` | +| Job active / deep clean | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Recharge & Resume / Evac & Resume | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Live map (grid, cleaned path, robot pose with heading) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Persisted map (rooms, room raster, walls, doors, dock, job stats, event log) | ❌ | ✅ | MQTT: `sharkiq_v1` | + +> ² `charging` reads `true` in every status frame on this model, including mid-clean — `BatteryInfo.ChargingState` is a constant `3` here, so do not infer docked from it. --- @@ -35,7 +49,7 @@ | `returning_to_dock` | ❌ | ✅ | MQTT: `sharkiq_v1` | | `docking` | ❌ | ✅ | MQTT: `sharkiq_v1` | | `docked` | ❌ | ✅ | MQTT: `sharkiq_v1` | -| `idle` | ❌ | ❌ | | +| `idle` | ❌ | ✅ | MQTT: `sharkiq_v1` | | `exploring` | ❌ | ❌ | | --- @@ -46,4 +60,9 @@ - **MQTT:** Uses standard `sharkiq_v1` protobuf format. Status requests, passive monitoring, and basic commands work. - **Observed MQTT status:** `mode=docked`, `battery_level=100`, `charging=true`. - **Command test:** `start_cleaning` returned `True` and status changed to `cleaning` within about 2 seconds. `stop` returned `True` and status changed to `returning_to_dock` within about 2 seconds. `go_home` returned `True`; the robot reported `docked` about 28 seconds after the first return-to-dock command. -- **Command caveat:** In the current `sharkiq_v1` MQTT mapping, `stop` and `go_home` use the same payload, so both appear to initiate return-to-dock behavior on this model. +- **Lifecycle** (firmware `V6.6.10-P7.N3308.17.0`): `docked (14)` → `cleaning (6)` → `returning_to_dock (7)` → `docking (13)` → `docked (14)`; the persisted map frame is published on the `13 → 14` edge. Mode `4` (`idle`) is reported when stopped off the dock or lifted. +- **Suction:** accepted while docked (the app only greys the control out). Echoed once on change; no status field. +- **Battery:** counts down through a job and snaps back to 100 within seconds of docking. +- **Maps:** a ~27 KB live frame every ~4 s during a job (0.06 m grid, cleaned path, pose log with heading); a ~92 KB persisted frame on docking with rooms, room-id raster, wall/door polylines, dock pose, job stats and the event log. +- **Room commands re-upload the room definition**, names included. Build them from the latest persisted map. +- **Not observed:** pause, an in-job error field (no fault occurred during testing), `exploring`. diff --git a/docs/data-models.md b/docs/data-models.md index a38eff8..8af7420 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -75,3 +75,124 @@ class DeviceInfo: ``` > **Note:** The MAC address returned by `get_wifi_status()` is the recommended value to use as `unique_id` when configuring a Home Assistant device. The robot ID endpoint does not expose a serial number. + +--- + +## SuctionLevel + +```python +class SuctionLevel(str, Enum): + ECO = "eco" + NORMAL = "normal" + MAX = "max" +``` + +Named as in the SharkClean app. Used by `VacuumClient.set_suction()`. + +--- + +## VacuumStatus — MQTT-only fields + +The SharkIQ MQTT status decoder fills in a few more fields; other transports leave them `None`: + +```python +job_active: bool | None # A job is in progress +deep_clean: bool | None # Matrix / spot clean in progress (only set during a job) +recharge_resume: bool | None # Recharge & Resume setting +evac_resume: bool | None # Evac & Resume setting +map: VacuumMap | None # Only on map-bearing frames +``` + +The suction level is deliberately absent: the robot does not report it in status, only echoes it once when it changes. + +--- + +## VacuumMap + +Decoded from the map frames the robot publishes over MQTT. Live frames arrive every few seconds during a job; the *persisted* frame arrives once, as the robot reaches the dock, and carries everything the live frame does plus rooms, vectors, the dock pose, job statistics and the robot's event log. + +```python +@dataclass +class VacuumMap: + grid: MapGrid + path: list[MapPoint] # Cleaned path so far, ~1 point per 0.12 m + poses: list[MapPose] # Pose log with heading, ~1 pose per 0.22 m + persisted: bool # True for the end-of-job frame + map_id: str | None # e.g. "3CDBBC59" — persisted only + name: str | None # persisted only + dock: MapPose | None # persisted only + rooms: list[MapRoom] # persisted only + features: list[MapFeature] # persisted only: wall "edge" and "door" polylines + log: list[VacuumLogEntry] # persisted only: the robot's event log + job_started: int | None # Unix epoch seconds — persisted only + job_duration: int | None # seconds — persisted only + cleaned_cells: int | None # persisted only + raw: dict + + @property + def robot(self) -> MapPose | None: ... # last pose, i.e. where the robot is + @property + def cleaned_area(self) -> float | None: ... # m², from cleaned_cells × resolution² +``` + +All coordinates are metres in the map frame; headings are radians, `0` along +x, anticlockwise positive. Convert to grid cells with `col = (x - grid.origin.x) / grid.resolution` and `row = (y - grid.origin.y) / grid.resolution`; row 0 is the bottom of the map. + +### MapGrid + +```python +@dataclass +class MapGrid: + resolution: float # metres per cell (0.06 on the RV2610BFCA) + width: int + height: int + origin: MapPoint # world position of cell (0, 0) + cells: bytes # width × height occupancy values, row-major + room_ids: bytes | None # persisted only: room segment id per cell, 0 = unassigned + + UNKNOWN = 0x4B # unexplored + VOID = 0x00 + + @staticmethod + def is_wall(value: int) -> bool: ... # 0x50 and above + @staticmethod + def is_floor(value: int) -> bool: ... # explored free floor + def cell(self, col: int, row: int) -> int: ... + def room_id(self, col: int, row: int) -> int | None: ... +``` + +### MapPoint, MapPose, MapRoom, MapFeature, VacuumLogEntry + +```python +@dataclass +class MapPoint: + x: float + y: float + +@dataclass +class MapPose: + x: float + y: float + heading: float # radians + +@dataclass +class MapRoom: + name: str # as shown in the app + polygon: list[MapPoint] # bounding polygon drawn in the app + selected: bool # included in the most recent job + coverage: float | None # fraction cleaned in the most recent job + +@dataclass +class MapFeature: + kind: str # "edge" (wall outline) or "door" + points: list[MapPoint] + +@dataclass +class VacuumLogEntry: + key: str # e.g. "DT_STATE_CHANGE", "DT_WARNING_CODE" + time: int # Unix epoch seconds, as reported by the robot + code: str # e.g. "SYS_ST_CLEANING", "WARN_MM_LOWLIGHT" +``` + +Room segment ids in `room_ids` do not necessarily map one-to-one onto `rooms`: the robot segments the floor itself, and the app's named rooms are polygons drawn over those segments (two named rooms can share one segment). Use the polygons to name a location and the raster to colour it. + +The event log's `time` values can jump when the robot re-syncs its clock mid-job; rely on list order, not `time`, for sequence. diff --git a/docs/mapping-configuration.md b/docs/mapping-configuration.md index d33e982..a543d83 100644 --- a/docs/mapping-configuration.md +++ b/docs/mapping-configuration.md @@ -69,6 +69,7 @@ status_decoder: sharkiq_protobuf_v1 # Maps protobuf OperatingMode integers to normalized VacuumMode strings modes: + 4: idle 6: cleaning 7: returning_to_dock 13: docking @@ -77,7 +78,11 @@ modes: actions: start_cleaning: type: command # Fire-and-forget MQTT publish - payload: "OgQKAhBLgAEJ" + payload: "gAEJ" + + set_suction_max: # Settings are commands too; the robot echoes them in field 8 + type: command + payload: "OgQKAhBk" get_status: type: status_request # Publish then wait for a response message @@ -90,6 +95,8 @@ actions: - `command` — publishes the payload and returns `True` - `status_request` — publishes the payload, then subscribes and waits up to `timeout` seconds for the first response; returns the decoded `VacuumStatus` +Commands whose payload depends on state — room and spot selection carry the map's room definition — are not mapping actions. They are built at runtime by `sharklocal.vacuum_map` and published with `MQTTVacuumClient.send()`; `VacuumClient.clean_rooms()` / `clean_spot()` wrap that. + --- ## Listing and Loading Mappings diff --git a/docs/mappings.md b/docs/mappings.md index 097a1c8..64e2e72 100644 --- a/docs/mappings.md +++ b/docs/mappings.md @@ -31,10 +31,17 @@ The table below shows which features are available per transport for the built-i | Stop (pause) | ✅ | ✅ | | Return to dock | ✅ | ✅ | | Explore / map room | ✅ | ❌ | +| Find robot | ❌ | ✅ | +| Suction level (eco / normal / max) | ❌ | ✅ | +| Recharge & Resume, Evac & Resume | ❌ | ✅ | +| Clean rooms / Matrix Clean / spot clean | ❌ | ✅ ² | | **Status** | | | | Polling status (mode + battery) | ✅ | ✅ | | Real-time status (mode) | ❌ | ✅ | -| Event log | ✅ | ❌ | +| Job active, deep clean, settings | ❌ | ✅ | +| Live map (grid, cleaned path, robot pose) | ❌ | ✅ | +| Persisted map (rooms, walls, doors, dock, job stats) | ❌ | ✅ ³ | +| Event log | ✅ | ✅ ³ | | **Device info** | | | | Firmware version | ✅ | ❌ | | MAC address / unique ID | ✅ | ❌ | @@ -45,7 +52,7 @@ The table below shows which features are available per transport for the built-i | Returning to dock | ✅ | ✅ | | Docking | ❌ | ✅ | | Docked (calculated) | ✅ ¹ | ✅ | -| Idle / stopped off dock (calculated) | ✅ ¹ | ❌ | +| Idle / stopped off dock | ✅ ¹ | ✅ | | Exploring / mapping | ✅ | ❌ | | **Connection** | | | | Protocol | HTTPS | MQTT | @@ -53,10 +60,14 @@ The table below shows which features are available per transport for the built-i | SSL | Self-signed (verify disabled) | None | > ¹ `DOCKED` and `IDLE` are derived from the combination of `mode` and `charging` fields in the REST response — neither is reported directly by the API. Charging reports connected or not connected, not active charging of the battery. +> +> ² Built at runtime from the persisted map's room definition (`VacuumClient.clean_rooms()` / `clean_spot()`), not a fixed mapping action. +> +> ³ Carried by the persisted map frame the robot publishes when it docks, available through monitoring (`VacuumStatus.map`), not by request. **Recommendations:** -- Configure **both transports** (`rest_mappings` + `mqtt_mappings`) to get full feature coverage: REST for device info, events, and explore; MQTT for real-time monitoring and docking state. -- If only one transport is available, **REST** provides broader feature coverage. **MQTT** is the better choice when real-time status updates without polling are required. +- Configure **both transports** (`rest_mappings` + `mqtt_mappings`) to get full feature coverage: REST for device info and explore; MQTT for real-time monitoring, maps, rooms and settings. +- If only one transport is available, **MQTT** now provides the broader feature coverage on models that publish map frames, and is the only source of real-time status. **REST** remains the only source of device info (firmware, MAC address) where it is reachable. - Use `probe()` when the correct mapping is not known ahead of time. --- diff --git a/docs/vacuum-client.md b/docs/vacuum-client.md index f5181ca..5e907a2 100644 --- a/docs/vacuum-client.md +++ b/docs/vacuum-client.md @@ -98,6 +98,12 @@ print(vacuum.via) # "REST", "MQTT", or "NONE" depending on what responded | `get_events()` | `GET /get/event_log` | *(not in MQTT mapping)* | | `get_device_info()` | `GET /get/robot_id` | *(not in MQTT mapping)* | | `get_wifi_status()` | `GET /get/wifi_status` | *(not in MQTT mapping)* | +| `find_robot()` | *(not in REST mapping)* | `find_robot` (command) | +| `set_suction(level)` | *(not in REST mapping)* | `set_suction_eco` / `_normal` / `_max` (command) | +| `set_recharge_resume(enabled)` | *(not in REST mapping)* | `recharge_resume_on` / `_off` (command) | +| `set_evac_resume(enabled)` | *(not in REST mapping)* | `evac_resume_on` / `_off` (command) | +| `clean_rooms(names, deep=False, vacuum_map=None)` | *(not in REST mapping)* | runtime-built payload, then `start_cleaning` | +| `clean_spot(x, y, vacuum_map=None)` | *(not in REST mapping)* | runtime-built payload, then `start_cleaning` | ### Return Types @@ -125,6 +131,51 @@ async with VacuumClient("192.168.1.100", mqtt_mappings="sharkiq_v1") as vacuum: await vacuum.stop_monitoring() ``` +While monitoring, the client keeps the most recent status in `vacuum.last_status` and the most recent **persisted** map in `vacuum.last_map` (see below). Live map frames update `last_status.map` but never replace `last_map`, so the room definition stays available through a job. + +--- + +## Maps, Rooms and Settings + +These are MQTT-only and, today, specific to the `sharkiq_v1` mapping. Models that publish map frames deliver them through monitoring: every `VacuumStatus` that carries map data has `status.map` set to a `VacuumMap`. + +```python +def on_status(status: VacuumStatus) -> None: + if status.map is None: + return + robot = status.map.robot # MapPose(x, y, heading) or None + print(len(status.map.path), "path points, robot at", robot) + if status.map.persisted: # the end-of-job frame + print([room.name for room in status.map.rooms]) + print([e.code for e in status.map.log if e.key == "DT_WARNING_CODE"]) + +vacuum.on_status_update(on_status) +await vacuum.start_monitoring() +``` + +### Settings and locate + +```python +await vacuum.find_robot() # play the locate sound +await vacuum.set_suction(SuctionLevel.MAX) # "eco" | "normal" | "max" +await vacuum.set_recharge_resume(True) +await vacuum.set_evac_resume(False) +``` + +Recharge & Resume and Evac & Resume are read back from `VacuumStatus.recharge_resume` / `evac_resume`. The suction level is **not** reported in status — the robot echoes a change once and is then silent about it — so keep the last value you set if you need to display it. + +### Room, Matrix and spot cleaning + +```python +# Needs a persisted map for the room definition: either the one the client +# cached while monitoring (vacuum.last_map) or one you pass in. +await vacuum.clean_rooms(["Kitchen", "Hallway"]) +await vacuum.clean_rooms(["Kitchen"], deep=True) # Matrix Clean (two passes) +await vacuum.clean_spot(1.2, -0.4) # ~1.5 m square around (x, y) metres +``` + +Each of these publishes the map's room definition with the selection, then `start_cleaning`. Room names must match `VacuumMap.rooms` exactly (`ValueError` otherwise); with no persisted map available a `SharklocalError` is raised. Because the room definition is re-uploaded in full, **always use the latest persisted map** — a stale one renames rooms back to the names it was captured with. + --- ## Transport Introspection From e93e8c21cb67b0e6a5e51fff97e21bb06f062ece Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:19:40 -0400 Subject: [PATCH 06/33] Add to_dict and from_dict to VacuumMap so a persisted map can be stored The persisted map only arrives when the robot docks, but room and spot cleaning need its room definition at any time; storing it lets a caller clean rooms after a restart. --- docs/data-models.md | 2 ++ sharklocal/models.py | 59 +++++++++++++++++++++++++++++++++++++++- tests/test_vacuum_map.py | 14 ++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/data-models.md b/docs/data-models.md index 8af7420..c03f7d7 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -137,6 +137,8 @@ class VacuumMap: All coordinates are metres in the map frame; headings are radians, `0` along +x, anticlockwise positive. Convert to grid cells with `col = (x - grid.origin.x) / grid.resolution` and `row = (y - grid.origin.y) / grid.resolution`; row 0 is the bottom of the map. +The persisted map is only published when the robot docks, yet room and spot cleaning need it at any time. `map.to_dict()` returns a JSON-serialisable form (rasters base64-encoded, `raw` dropped) and `VacuumMap.from_dict(data)` rebuilds it, so a caller can store the latest persisted map and still clean rooms after a restart. + ### MapGrid ```python diff --git a/sharklocal/models.py b/sharklocal/models.py index 4cf67f4..6ab6368 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -2,7 +2,8 @@ from __future__ import annotations -from dataclasses import dataclass, field +import base64 +from dataclasses import asdict, dataclass, field from enum import Enum from typing import Any, Dict, List, Optional @@ -182,6 +183,62 @@ def cleaned_area(self) -> Optional[float]: return None return self.cleaned_cells * self.grid.resolution**2 + def to_dict(self) -> Dict[str, Any]: + """Return a JSON-serialisable form of the map, for storage. + + The persisted map arrives only when the robot docks, but room and spot + cleaning need its room definition at any time — so a caller that + restarts wants to keep it. Byte rasters are base64-encoded and ``raw`` + is dropped. :meth:`from_dict` reverses this. + """ + data = asdict(self) + data.pop("raw", None) + grid = data["grid"] + grid["cells"] = base64.b64encode(self.grid.cells).decode("ascii") + if self.grid.room_ids is not None: + grid["room_ids"] = base64.b64encode(self.grid.room_ids).decode("ascii") + return data + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "VacuumMap": + """Rebuild a map from :meth:`to_dict` output.""" + g = data["grid"] + grid = MapGrid( + resolution=g["resolution"], + width=g["width"], + height=g["height"], + origin=MapPoint(**g["origin"]), + cells=base64.b64decode(g["cells"]), + room_ids=base64.b64decode(g["room_ids"]) if g.get("room_ids") else None, + ) + dock = data.get("dock") + return cls( + grid=grid, + path=[MapPoint(**p) for p in data.get("path", [])], + poses=[MapPose(**p) for p in data.get("poses", [])], + persisted=data.get("persisted", False), + map_id=data.get("map_id"), + name=data.get("name"), + dock=MapPose(**dock) if dock else None, + rooms=[ + MapRoom( + name=r["name"], + polygon=[MapPoint(**p) for p in r["polygon"]], + selected=r.get("selected", False), + coverage=r.get("coverage"), + ) + for r in data.get("rooms", []) + ], + features=[ + MapFeature(kind=f["kind"], points=[MapPoint(**p) for p in f["points"]]) + for f in data.get("features", []) + ], + log=[VacuumLogEntry(**e) for e in data.get("log", [])], + job_started=data.get("job_started"), + job_duration=data.get("job_duration"), + cleaned_cells=data.get("cleaned_cells"), + ) + @dataclass class VacuumEvent: diff --git a/tests/test_vacuum_map.py b/tests/test_vacuum_map.py index fb7ccec..8e8c575 100644 --- a/tests/test_vacuum_map.py +++ b/tests/test_vacuum_map.py @@ -336,6 +336,20 @@ def test_encode_room_selection_deep_matches_app_capture(persisted_map): assert encode_room_selection(persisted_map, ["Bathroom. "], deep=True) == expected +def test_map_survives_json_round_trip(persisted_map): + restored = VacuumMap.from_dict(json.loads(json.dumps(persisted_map.to_dict()))) + persisted_map.raw = {} + assert restored == persisted_map + + +def test_stored_map_still_builds_the_app_room_command(persisted_map): + # The reason the map is stored at all: after a restart, room cleaning must + # send exactly what the app sends, from the stored copy. + restored = VacuumMap.from_dict(json.loads(json.dumps(persisted_map.to_dict()))) + expected = _fixture("sharkiq_cmd_room_clean_matrix.b64") + assert encode_room_selection(restored, ["Bathroom. "], deep=True) == expected + + def test_encode_room_selection_lists_every_chosen_room(persisted_map): payload = encode_room_selection(persisted_map, ["Bathroom. ", "Hallway"]) selection = _fields(_fields(payload)[41][0]) From c439c19ccb434a076a20e5493ee01b9c97a095da Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:25:29 -0400 Subject: [PATCH 07/33] Version 0.3.0.dev1 for the map, rooms and settings branch A distinct version lets Home Assistant tell this build from the PyPI 0.2.0 it may already have installed. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c8d8ccf..5e7ce7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.2.0" +version = "0.3.0.dev1" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From ee5cb805be554b79f6cf6b672ab192e603e10710 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:37:41 -0400 Subject: [PATCH 08/33] Build the REST SSL context when the client is constructed Creating an SSL context reads the certificate store, which Home Assistant reports as a blocking call when it happens on the first request inside the event loop. Built in the constructor, it runs wherever the caller constructs the client, which Home Assistant does in an executor. --- sharklocal/rest_client.py | 18 +++++++++++------- tests/test_rest_client.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/sharklocal/rest_client.py b/sharklocal/rest_client.py index ee91773..7561200 100644 --- a/sharklocal/rest_client.py +++ b/sharklocal/rest_client.py @@ -24,6 +24,15 @@ def __init__(self, host: str, mapping: RESTMappingConfig) -> None: self.host = host self.mapping = mapping self._session: Optional[aiohttp.ClientSession] = None + # Building an SSL context reads the system certificate store, which is + # blocking I/O; doing it here keeps it out of the event loop for callers + # (Home Assistant) that construct clients in an executor. + self._ssl_context: Optional[ssl.SSLContext] = None + if mapping.transport != "http": + self._ssl_context = ssl.create_default_context() + if not mapping.verify_ssl: + self._ssl_context.check_hostname = False + self._ssl_context.verify_mode = ssl.CERT_NONE @property def base_url(self) -> str: @@ -35,14 +44,9 @@ def supports(self, action: str) -> bool: def _make_connector(self) -> aiohttp.TCPConnector: """Build a ``TCPConnector`` with SSL settings from the mapping.""" - if self.mapping.transport == "http": + if self._ssl_context is None: return aiohttp.TCPConnector() - if not self.mapping.verify_ssl: - ctx = ssl.create_default_context() - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - return aiohttp.TCPConnector(ssl=ctx) - return aiohttp.TCPConnector() + return aiohttp.TCPConnector(ssl=self._ssl_context) async def _get_session(self) -> aiohttp.ClientSession: if self._session is None or self._session.closed: diff --git a/tests/test_rest_client.py b/tests/test_rest_client.py index cc68b1e..18be7f1 100644 --- a/tests/test_rest_client.py +++ b/tests/test_rest_client.py @@ -105,6 +105,18 @@ async def test_make_connector_https_with_ssl_verify(rest_mapping): await connector.close() +async def test_ssl_context_is_built_at_construction_not_in_the_event_loop(rest_mapping): + rest_mapping.transport = "https" + rest_mapping.verify_ssl = False + with patch("sharklocal.rest_client.ssl.create_default_context", wraps=ssl.create_default_context) as create: + client = RESTVacuumClient("host", rest_mapping) + assert create.call_count == 1 + connector = client._make_connector() + assert create.call_count == 1 + assert client._ssl_context.verify_mode == ssl.CERT_NONE + await connector.close() + + # --------------------------------------------------------------------------- # _get_session() # --------------------------------------------------------------------------- From 0618bc56e81de50685bf89dee48fd22991681541 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:37:49 -0400 Subject: [PATCH 09/33] Version 0.3.0.dev2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5e7ce7f..b0acefc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev1" +version = "0.3.0.dev2" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 5a85de17a2e54c915bb5377580c69d4f739a53e8 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:46:55 -0400 Subject: [PATCH 10/33] Read cleaned area from the grid and clean time from the event log Cell value 0x00 is floor covered by the job, not void: every point of the cleaned path lands on one in every captured frame. The persisted frame's field 11 counters (.3, .6) were read as duration and cleaned cells from a single capture, but a second job showed them almost unchanged (335 -> 306, 3947 -> 3978) while the robot's own log reported 101 s and 70 s of cleaning. VacuumMap.cleaned_area now counts cleaned cells and the new clean_time sums the log's DT_*_CLEAN_TIME entries; job_duration and cleaned_cells are removed. --- docs/compatibility/rv2610bfca.md | 2 +- docs/data-models.md | 16 +++++++---- sharklocal/models.py | 47 +++++++++++++++++++++++--------- sharklocal/vacuum_map.py | 11 ++++---- tests/test_models.py | 29 ++++++++++++++++---- tests/test_vacuum_map.py | 27 ++++++++++++------ 6 files changed, 93 insertions(+), 39 deletions(-) diff --git a/docs/compatibility/rv2610bfca.md b/docs/compatibility/rv2610bfca.md index a1282f5..27e7204 100644 --- a/docs/compatibility/rv2610bfca.md +++ b/docs/compatibility/rv2610bfca.md @@ -63,6 +63,6 @@ - **Lifecycle** (firmware `V6.6.10-P7.N3308.17.0`): `docked (14)` → `cleaning (6)` → `returning_to_dock (7)` → `docking (13)` → `docked (14)`; the persisted map frame is published on the `13 → 14` edge. Mode `4` (`idle`) is reported when stopped off the dock or lifted. - **Suction:** accepted while docked (the app only greys the control out). Echoed once on change; no status field. - **Battery:** counts down through a job and snaps back to 100 within seconds of docking. -- **Maps:** a ~27 KB live frame every ~4 s during a job (0.06 m grid, cleaned path, pose log with heading); a ~92 KB persisted frame on docking with rooms, room-id raster, wall/door polylines, dock pose, job stats and the event log. +- **Maps:** a ~27 KB live frame every ~4 s during a job (0.06 m grid, cleaned path, pose log with heading); a ~92 KB persisted frame on docking with rooms, room-id raster, wall/door polylines, dock pose, job start time and the event log. Cleaned floor is marked `0x00` in the grid (every point of the cleaned path lands on one), and the log's `DT_*_CLEAN_TIME` entries give the time spent cleaning. The persisted frame's field 11 also carries two counters that look like a duration and a cell count, but they barely move between different jobs, so they are not decoded. - **Room commands re-upload the room definition**, names included. Build them from the latest persisted map. - **Not observed:** pause, an in-job error field (no fault occurred during testing), `exploring`. diff --git a/docs/data-models.md b/docs/data-models.md index c03f7d7..ede6d4c 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -125,14 +125,14 @@ class VacuumMap: features: list[MapFeature] # persisted only: wall "edge" and "door" polylines log: list[VacuumLogEntry] # persisted only: the robot's event log job_started: int | None # Unix epoch seconds — persisted only - job_duration: int | None # seconds — persisted only - cleaned_cells: int | None # persisted only raw: dict @property def robot(self) -> MapPose | None: ... # last pose, i.e. where the robot is @property - def cleaned_area(self) -> float | None: ... # m², from cleaned_cells × resolution² + def cleaned_area(self) -> float: ... # m² covered by the job: grid.cleaned_cells × resolution² + @property + def clean_time(self) -> int | None: ... # s spent cleaning, summed from the log's DT_*_CLEAN_TIME entries ``` All coordinates are metres in the map frame; headings are radians, `0` along +x, anticlockwise positive. Convert to grid cells with `col = (x - grid.origin.x) / grid.resolution` and `row = (y - grid.origin.y) / grid.resolution`; row 0 is the bottom of the map. @@ -152,12 +152,16 @@ class MapGrid: room_ids: bytes | None # persisted only: room segment id per cell, 0 = unassigned UNKNOWN = 0x4B # unexplored - VOID = 0x00 + CLEANED = 0x00 # floor covered by the job @staticmethod - def is_wall(value: int) -> bool: ... # 0x50 and above + def is_wall(value: int) -> bool: ... # 0x50 and above + @staticmethod + def is_floor(value: int) -> bool: ... # explored free floor, cleaned or not @staticmethod - def is_floor(value: int) -> bool: ... # explored free floor + def is_cleaned(value: int) -> bool: ... # CLEANED + @property + def cleaned_cells(self) -> int: ... def cell(self, col: int, row: int) -> int: ... def room_id(self, col: int, row: int) -> int | None: ... ``` diff --git a/sharklocal/models.py b/sharklocal/models.py index 6ab6368..28ed431 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -94,9 +94,11 @@ class MapGrid: cells: bytes room_ids: Optional[bytes] = None - # Cell values observed in SharkIQ grids. + # Cell values observed in SharkIQ grids. ``CLEANED`` marks floor covered + # by the current (or, on a persisted map, the last) job: every point of + # the cleaned path lands on one, in every captured frame. UNKNOWN = 0x4B - VOID = 0x00 + CLEANED = 0x00 @staticmethod def is_wall(value: int) -> bool: @@ -105,8 +107,18 @@ def is_wall(value: int) -> bool: @staticmethod def is_floor(value: int) -> bool: - """``True`` for free-floor cells (explored, not wall, not void).""" - return 0 < value < 0x4B + """``True`` for free-floor cells (explored, not wall), cleaned or not.""" + return value < 0x4B + + @staticmethod + def is_cleaned(value: int) -> bool: + """``True`` for floor covered by the job.""" + return value == MapGrid.CLEANED + + @property + def cleaned_cells(self) -> int: + """Number of cells covered by the job.""" + return self.cells.count(MapGrid.CLEANED) def cell(self, col: int, row: int) -> int: """Return the occupancy value at ``(col, row)``.""" @@ -167,8 +179,6 @@ class VacuumMap: features: List[MapFeature] = field(default_factory=list) log: List[VacuumLogEntry] = field(default_factory=list) job_started: Optional[int] = None # Unix epoch seconds - job_duration: Optional[int] = None # seconds - cleaned_cells: Optional[int] = None raw: Dict[str, Any] = field(default_factory=dict) @property @@ -177,11 +187,24 @@ def robot(self) -> Optional[MapPose]: return self.poses[-1] if self.poses else None @property - def cleaned_area(self) -> Optional[float]: - """Cleaned area of the job in square metres, if reported.""" - if self.cleaned_cells is None: - return None - return self.cleaned_cells * self.grid.resolution**2 + def cleaned_area(self) -> float: + """Area covered by the job in square metres, counted from the grid.""" + return self.grid.cleaned_cells * self.grid.resolution**2 + + @property + def clean_time(self) -> Optional[int]: + """Seconds spent cleaning in the last job, from the robot's event log. + + The sum of the log's ``DT_*_CLEAN_TIME`` entries (normal, wall-follow + and escape cleaning; docking is logged separately). ``None`` when the + log carries none — live frames have no log. + """ + times = [ + int(entry.code) + for entry in self.log + if entry.key.endswith("_CLEAN_TIME") and entry.code.isdigit() + ] + return sum(times) if times else None def to_dict(self) -> Dict[str, Any]: """Return a JSON-serialisable form of the map, for storage. @@ -235,8 +258,6 @@ def from_dict(cls, data: Dict[str, Any]) -> "VacuumMap": ], log=[VacuumLogEntry(**e) for e in data.get("log", [])], job_started=data.get("job_started"), - job_duration=data.get("job_duration"), - cleaned_cells=data.get("cleaned_cells"), ) diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py index 43f3f68..edf5078 100644 --- a/sharklocal/vacuum_map.py +++ b/sharklocal/vacuum_map.py @@ -106,13 +106,14 @@ def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: if isinstance(room_ids, bytes) and len(room_ids) == len(grid.cells): grid.room_ids = room_ids - # Field 11 is the job summary: .1 start epoch, .3 duration s, .6 cleaned cells. + # Field 11 is a job summary; .1 is the job's start epoch. Its .3 and .6 + # looked like duration and cleaned cells on one capture, but they barely + # moved between two different jobs (335 -> 306, 3947 -> 3978) while the + # robot's own log said 101 s and 70 s of cleaning, so they are not decoded: + # cleaned area comes from the grid and clean time from the log. stats = _first(m, 11) if isinstance(stats, bytes): - s = _fields(stats) - vacuum_map.job_started = _first(s, 1) - vacuum_map.job_duration = _first(s, 3) - vacuum_map.cleaned_cells = _first(s, 6) + vacuum_map.job_started = _first(_fields(stats), 1) # Field 20.3.6 is the robot's event log as a JSON array of {key, time, code}. vacuum_map.log = _decode_log(_first(m, 20)) diff --git a/tests/test_models.py b/tests/test_models.py index b99388f..aff744d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -231,7 +231,7 @@ def _grid(room_ids=None) -> MapGrid: @pytest.mark.parametrize( "value, wall, floor", [ - (0x00, False, False), # void + (0x00, False, True), # cleaned floor (0x0A, False, True), # floor (0x0F, False, True), # floor (0x19, False, True), # floor @@ -260,7 +260,13 @@ def test_map_grid_room_id_lookup(): def test_map_grid_constants(): assert MapGrid.UNKNOWN == 0x4B - assert MapGrid.VOID == 0x00 + assert MapGrid.CLEANED == 0x00 + + +def test_map_grid_counts_cleaned_cells(): + assert MapGrid.is_cleaned(0x00) is True + assert MapGrid.is_cleaned(0x0F) is False + assert _grid().cleaned_cells == 1 # --------------------------------------------------------------------------- @@ -277,7 +283,7 @@ def test_vacuum_map_defaults(): assert vacuum_map.features == [] assert vacuum_map.log == [] assert vacuum_map.robot is None - assert vacuum_map.cleaned_area is None + assert vacuum_map.clean_time is None def test_vacuum_map_robot_is_last_pose(): @@ -285,9 +291,20 @@ def test_vacuum_map_robot_is_last_pose(): assert VacuumMap(grid=_grid(), poses=poses).robot == poses[-1] -def test_vacuum_map_cleaned_area_uses_grid_resolution(): - vacuum_map = VacuumMap(grid=_grid(), cleaned_cells=100) - assert vacuum_map.cleaned_area == pytest.approx(100 * 0.06 * 0.06) +def test_vacuum_map_cleaned_area_counts_cleaned_cells(): + # _grid() has one cleaned cell. + assert VacuumMap(grid=_grid()).cleaned_area == pytest.approx(0.06 * 0.06) + + +def test_vacuum_map_clean_time_sums_the_logged_clean_times(): + log = [ + VacuumLogEntry("DT_NORMAL_CLEAN_TIME", 0, "28"), + VacuumLogEntry("DT_FOLLOW_CLEAN_TIME", 0, "73"), + VacuumLogEntry("DT_ESCAPE_CLEAN_TIME", 0, "0"), + VacuumLogEntry("DT_NORMAL_DOCK_TIME", 0, "42"), + VacuumLogEntry("DT_WARNING_CODE", 0, "WARN_MM_LOWLIGHT"), + ] + assert VacuumMap(grid=_grid(), log=log).clean_time == 101 def test_map_room_and_feature_and_log_entry_fields(): diff --git a/tests/test_vacuum_map.py b/tests/test_vacuum_map.py index 8e8c575..0e18d6d 100644 --- a/tests/test_vacuum_map.py +++ b/tests/test_vacuum_map.py @@ -10,7 +10,7 @@ import pytest from sharklocal import protobuf -from sharklocal.models import MapPoint, MapPose, MapRoom, VacuumMap +from sharklocal.models import MapGrid, MapPoint, MapPose, MapRoom, VacuumMap from sharklocal.vacuum_map import ( FIELD_MAP, FIELD_PERSISTED_FLAG, @@ -130,7 +130,7 @@ def test_decode_live_frame_defaults(): assert vacuum_map.rooms == [] assert vacuum_map.features == [] assert vacuum_map.log == [] - assert vacuum_map.cleaned_area is None + assert vacuum_map.clean_time is None assert vacuum_map.map_id is None assert vacuum_map.name is None @@ -227,7 +227,7 @@ def test_decode_room_ids_only_when_sizes_match(): assert decode_map(_frame(protobuf.encode_bytes_field(5, grid) + no_raster)).grid.room_ids is None -def test_decode_job_stats_and_cleaned_area(): +def test_decode_job_start_only(): stats = ( protobuf.encode_varint_field(1, 1789867008) + protobuf.encode_varint_field(3, 335) @@ -236,9 +236,7 @@ def test_decode_job_stats_and_cleaned_area(): fields = protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field(11, stats) vacuum_map = decode_map(_frame(fields)) assert vacuum_map.job_started == 1789867008 - assert vacuum_map.job_duration == 335 - assert vacuum_map.cleaned_cells == 100 - assert vacuum_map.cleaned_area == pytest.approx(100 * 0.06 * 0.06) + assert not hasattr(vacuum_map, "job_duration") # --------------------------------------------------------------------------- @@ -311,12 +309,25 @@ def test_decode_real_persisted_frame(): assert vacuum_map.dock.x == pytest.approx(-0.52, abs=0.01) assert len(vacuum_map.grid.room_ids) == len(vacuum_map.grid.cells) assert set(vacuum_map.grid.room_ids) == {0, 1, 2, 3} - assert vacuum_map.job_duration == 335 - assert vacuum_map.cleaned_area == pytest.approx(14.2, abs=0.1) + # The robot's log says 28 s normal + 73 s wall-follow cleaning. + assert vacuum_map.clean_time == 101 + assert vacuum_map.cleaned_area == pytest.approx(3.17, abs=0.01) assert len(vacuum_map.log) == 83 assert [e.code for e in vacuum_map.log if e.key == "DT_WARNING_CODE"] == ["WARN_MM_LOWLIGHT"] +@pytest.mark.parametrize("name", ["sharkiq_live_map_frame.b64", "sharkiq_persisted_map_frame.b64"]) +def test_cleaned_cells_are_where_the_robot_went(name): + # The evidence for CLEANED = 0x00: every point of the cleaned path, in + # every captured frame, lands on a cell of that value. + vacuum_map = decode_map(_fields(_fixture(name))) + grid = vacuum_map.grid + for point in vacuum_map.path: + col = int((point.x - grid.origin.x) / grid.resolution) + row = int((point.y - grid.origin.y) / grid.resolution) + assert grid.cell(col, row) == MapGrid.CLEANED + + # --------------------------------------------------------------------------- # encode_room_selection / encode_spot_selection # --------------------------------------------------------------------------- From 8a1ae04873cee2ecb3370bb69de73110bda2b2d4 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 21:46:55 -0400 Subject: [PATCH 11/33] Version 0.3.0.dev3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0acefc..cbaadf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev2" +version = "0.3.0.dev3" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 15c59c6a5d9e62935395db1d463fade9ae8e427e Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:05:53 -0400 Subject: [PATCH 12/33] Treat the saved spot zone as a zone, not a room After a Spot Clean the robot keeps the PinDrop square in its saved map, marked as the room the job cleaned (measured 2026-09-26). VacuumMap.spot returns it and named_rooms leaves it out; room and spot cleaning re-upload only the named rooms, so a stale zone is neither selectable nor sent back. spot_polygon(x, y) exposes the square a spot clean sends. --- docs/data-models.md | 6 ++++++ sharklocal/models.py | 16 ++++++++++++++++ sharklocal/vacuum_map.py | 33 +++++++++++++++++++-------------- tests/test_vacuum_map.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/docs/data-models.md b/docs/data-models.md index ede6d4c..d460885 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -133,10 +133,16 @@ class VacuumMap: def cleaned_area(self) -> float: ... # m² covered by the job: grid.cleaned_cells × resolution² @property def clean_time(self) -> int | None: ... # s spent cleaning, summed from the log's DT_*_CLEAN_TIME entries + @property + def named_rooms(self) -> list[MapRoom]: ... # rooms without the spot zone + @property + def spot(self) -> MapRoom | None: ... # the saved spot zone ("PinDrop"), if any ``` All coordinates are metres in the map frame; headings are radians, `0` along +x, anticlockwise positive. Convert to grid cells with `col = (x - grid.origin.x) / grid.resolution` and `row = (y - grid.origin.y) / grid.resolution`; row 0 is the bottom of the map. +A Spot Clean is sent as an extra room named `"PinDrop"` (a ~1.5 m square, `vacuum_map.spot_polygon(x, y)`), and the robot keeps it in its saved map afterwards, marked `selected` — so `rooms` can contain it. `named_rooms` leaves it out, and room and spot cleaning never re-upload it. + The persisted map is only published when the robot docks, yet room and spot cleaning need it at any time. `map.to_dict()` returns a JSON-serialisable form (rasters base64-encoded, `raw` dropped) and `VacuumMap.from_dict(data)` rebuilds it, so a caller can store the latest persisted map and still clean rooms after a restart. ### MapGrid diff --git a/sharklocal/models.py b/sharklocal/models.py index 28ed431..7af9e22 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -8,6 +8,12 @@ from typing import Any, Dict, List, Optional +# The app models a Spot Clean as an extra room of this name: a ~1.5 m square +# around the pin. The robot keeps it in its saved map afterwards, marked as the +# room the last job cleaned, until another room definition replaces it. +SPOT_ROOM_NAME = "PinDrop" + + class VacuumMode(str, Enum): """Normalized operating modes across all transports.""" @@ -186,6 +192,16 @@ def robot(self) -> Optional[MapPose]: """Current robot pose: the last recorded pose, if any.""" return self.poses[-1] if self.poses else None + @property + def named_rooms(self) -> List[MapRoom]: + """The rooms as the app names them — every room except a spot zone.""" + return [room for room in self.rooms if room.name != SPOT_ROOM_NAME] + + @property + def spot(self) -> Optional[MapRoom]: + """The spot-clean zone the robot saved, if the map carries one.""" + return next((room for room in self.rooms if room.name == SPOT_ROOM_NAME), None) + @property def cleaned_area(self) -> float: """Area covered by the job in square metres, counted from the grid.""" diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py index edf5078..0e00392 100644 --- a/sharklocal/vacuum_map.py +++ b/sharklocal/vacuum_map.py @@ -27,6 +27,7 @@ MapPoint, MapPose, MapRoom, + SPOT_ROOM_NAME, VacuumLogEntry, VacuumMap, ) @@ -38,7 +39,6 @@ # Room-record fields the app adds to a room selected for Matrix (deep) Clean. _DEEP_CLEAN_FIELDS = ((7, 2), (9, 0), (10, 2)) # Spot Clean is a synthetic room of this name with a square polygon around the pin. -SPOT_ROOM_NAME = "PinDrop" SPOT_HALF_SIZE = 0.762 # metres — the app draws a ~1.5 m square @@ -290,7 +290,9 @@ def encode_room_selection( """ if vacuum_map.map_id is None: raise ValueError("Map has no id; room cleaning needs a persisted map") - known = {room.name: room for room in vacuum_map.rooms} + # A spot zone left in the saved map is not a room anyone can choose, and + # is not re-uploaded: the definition sent is the app's rooms alone. + known = {room.name: room for room in vacuum_map.named_rooms} missing = [name for name in room_names if name not in known] if missing: raise ValueError(f"Unknown room(s) {missing}; map has {sorted(known)}") @@ -299,11 +301,22 @@ def encode_room_selection( protobuf.encode_bytes_field( 15, _encode_room(room, deep=deep and room.name in room_names) ) - for room in vacuum_map.rooms + for room in vacuum_map.named_rooms ) return _encode_selection(vacuum_map.map_id, rooms, room_names) +def spot_polygon(x: float, y: float) -> List[MapPoint]: + """The square the app sends for a Spot Clean centred on ``(x, y)``.""" + d = SPOT_HALF_SIZE + return [ + MapPoint(x - d, y + d), + MapPoint(x + d, y + d), + MapPoint(x + d, y - d), + MapPoint(x - d, y - d), + ] + + def encode_spot_selection(vacuum_map: VacuumMap, x: float, y: float) -> bytes: """Build the "set cleaning area" message for a Spot Clean centred on ``(x, y)``. @@ -312,19 +325,11 @@ def encode_spot_selection(vacuum_map: VacuumMap, x: float, y: float) -> bytes: """ if vacuum_map.map_id is None: raise ValueError("Map has no id; spot cleaning needs a persisted map") - d = SPOT_HALF_SIZE - spot = MapRoom( - name=SPOT_ROOM_NAME, - polygon=[ - MapPoint(x - d, y + d), - MapPoint(x + d, y + d), - MapPoint(x + d, y - d), - MapPoint(x - d, y - d), - ], - ) + spot = MapRoom(name=SPOT_ROOM_NAME, polygon=spot_polygon(x, y)) + # The previous spot zone, if the saved map still has one, is replaced. rooms = b"".join( protobuf.encode_bytes_field(15, _encode_room(room, deep=False)) - for room in vacuum_map.rooms + for room in vacuum_map.named_rooms ) + protobuf.encode_bytes_field(15, _encode_room(spot, deep=True, spot=True)) return _encode_selection(vacuum_map.map_id, rooms, [SPOT_ROOM_NAME]) diff --git a/tests/test_vacuum_map.py b/tests/test_vacuum_map.py index 0e18d6d..832fdfd 100644 --- a/tests/test_vacuum_map.py +++ b/tests/test_vacuum_map.py @@ -3,6 +3,7 @@ from __future__ import annotations import base64 +import dataclasses import json import struct from pathlib import Path @@ -16,6 +17,7 @@ FIELD_PERSISTED_FLAG, SPOT_HALF_SIZE, SPOT_ROOM_NAME, + spot_polygon, decode_map, encode_room_selection, encode_spot_selection, @@ -328,6 +330,32 @@ def test_cleaned_cells_are_where_the_robot_went(name): assert grid.cell(col, row) == MapGrid.CLEANED +def _with_saved_spot(vacuum_map: VacuumMap) -> VacuumMap: + """The map as the robot saves it after a Spot Clean (measured 2026-09-26).""" + spot = MapRoom(SPOT_ROOM_NAME, spot_polygon(2.74, 0.02), selected=True, coverage=1.0) + return dataclasses.replace(vacuum_map, rooms=[*vacuum_map.rooms, spot]) + + +def test_saved_spot_zone_is_not_a_room(persisted_map): + saved = _with_saved_spot(persisted_map) + assert saved.spot.name == SPOT_ROOM_NAME + assert saved.spot.selected is True + assert [r.name for r in saved.named_rooms] == [r.name for r in persisted_map.rooms] + assert persisted_map.spot is None + + +def test_saved_spot_zone_is_not_uploaded_with_a_room_clean(persisted_map): + saved = _with_saved_spot(persisted_map) + assert encode_room_selection(saved, ["Bathroom. "]) == _fixture("sharkiq_cmd_room_clean.b64") + with pytest.raises(ValueError, match="PinDrop"): + encode_room_selection(saved, [SPOT_ROOM_NAME]) + + +def test_new_spot_replaces_a_saved_one(persisted_map): + saved = _with_saved_spot(persisted_map) + assert encode_spot_selection(saved, 1.0, 2.0) == encode_spot_selection(persisted_map, 1.0, 2.0) + + # --------------------------------------------------------------------------- # encode_room_selection / encode_spot_selection # --------------------------------------------------------------------------- From f408f63481354c7359da2f7dbf837fbd1e8328a8 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:05:53 -0400 Subject: [PATCH 13/33] Version 0.3.0.dev4 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cbaadf7..b657f56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev3" +version = "0.3.0.dev4" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 3615f3f62e2d7f0be47050fbde4d0757ec2fb1fd Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:26:36 -0400 Subject: [PATCH 14/33] Name the robot's codes, decode live errors and warnings, correct the state table - sharklocal.codes: the SysStateT, ErrorCodeT, WarningCodeT and dock-event names from the SharkClean app's protobuf schema - the same names the event log writes as strings. - VacuumStatus.errors / warnings: status fields 5 and 6, the codes active now (repeated, packed or not). - The sharkiq_v1 mode table now follows SysStateT: 13 is charging on the dock (docked, was docking - a robot back on its dock read as still docking until the battery was full), 4 is paused (was idle), and the error, waiting, spot, zone, redock and explore states are mapped. VacuumMode gains PAUSED and ERROR. --- docs/data-models.md | 5 + sharklocal/codes.py | 361 +++++++++++++++++++++++ sharklocal/mappings/mqtt/sharkiq_v1.yaml | 28 +- sharklocal/models.py | 5 + sharklocal/mqtt_client.py | 24 +- tests/test_codes.py | 72 +++++ tests/test_models.py | 4 +- 7 files changed, 488 insertions(+), 11 deletions(-) create mode 100644 sharklocal/codes.py create mode 100644 tests/test_codes.py diff --git a/docs/data-models.md b/docs/data-models.md index d460885..1f97ae5 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -33,6 +33,8 @@ class VacuumMode(str, Enum): DOCKED = "docked" IDLE = "idle" # Stopped and off the dock (mode=ready, charging=unconnected) EXPLORING = "exploring" # Mapping/exploration run in progress + PAUSED = "paused" # A job paused mid-way (SharkIQ MQTT) + ERROR = "error" # Stopped by a fault (SharkIQ MQTT) ``` The REST API does not expose `docked` directly. `DOCKED` is derived automatically from two fields: @@ -107,6 +109,9 @@ The suction level is deliberately absent: the robot does not report it in status --- + +`errors` and `warnings` are the codes active right now (SharkIQ status fields 5 and 6, repeated). They are numbers; `sharklocal.codes` names them (`codes.names(codes.ERROR_CODES, status.errors)`), with the robot's own enum names — the same names its event log writes as strings (`WARN_MM_LOWLIGHT`, `DE_USR_CTR_DOCK`). `codes.SYSTEM_STATES` names the state behind `mode` (field 4) and `codes.DOCK_EVENTS` the dock reasons. + ## VacuumMap Decoded from the map frames the robot publishes over MQTT. Live frames arrive every few seconds during a job; the *persisted* frame arrives once, as the robot reaches the dock, and carries everything the live frame does plus rooms, vectors, the dock pose, job statistics and the robot's event log. diff --git a/sharklocal/codes.py b/sharklocal/codes.py new file mode 100644 index 0000000..1a2f9cf --- /dev/null +++ b/sharklocal/codes.py @@ -0,0 +1,361 @@ +"""Names for the numeric codes in SharkIQ protobuf messages. + +These are the robot's own enum names (``SysStateT``, ``ErrorCodeT``, +``WarningCodeT`` and the dock-event codes), as the SharkClean app's protobuf +schema defines them. The robot's event log writes the same names as strings +(``WARN_MM_LOWLIGHT``, ``DE_USR_CTR_DOCK``); status frames carry the numbers. +Names only: the robot and the app define no descriptions for them. +""" + +from __future__ import annotations + +from typing import Dict, Iterable, List + +# Status field 4. +SYSTEM_STATES: Dict[int, str] = { + 0: "SYS_ST_NONE", + 1: "SYS_ST_UNINIT", + 2: "SYS_ST_SLEEPING", + 3: "SYS_ST_WAITING", + 4: "SYS_ST_PAUSE", + 5: "SYS_ST_ERROR", + 6: "SYS_ST_CLEANING", + 7: "SYS_ST_DOCKING", + 8: "SYS_ST_SPOTING", + 9: "SYS_ST_ALONG_WALL", + 10: "SYS_ST_REMOTE", + 11: "SYS_ST_UPDATING", + 12: "SYS_ST_WIFI_CONFIG", + 13: "SYS_ST_CHARGING", + 14: "SYS_ST_BATTERY_FULL", + 15: "SYS_ST_GO_TARGET", + 16: "SYS_ST_CLEAN_ZONE", + 17: "SYS_ST_REBOOT", + 18: "SYS_ST_POWEROFF", + 19: "SYS_ST_WIFI_CONNECTED", + 20: "SYS_ST_TEST", + 21: "SYS_ST_FACTORY_TEST", + 22: "SYS_ST_WAITING_2R_PENDING", + 23: "SYS_ST_WAITING_2R", + 24: "SYS_ST_AUTO_EXPLORE", + 25: "SYS_ST_REDOCKING", + 26: "SYS_ST_EXPLORE_DOCKING", + 27: "SYS_ST_PRIMING", + 28: "SYS_ST_RELOCAL", + 29: "SYS_ST_BASE_DOCK", + 30: "SYS_ST_MOP_CLEAN", + 31: "SYS_ST_CARPET_EXPLORE", + 32: "SYS_ST_MOP_WASH", + 33: "SYS_ST_FACTORY_RESETTING", + 34: "MAX_SYS_ST_NUM", +} + + +# Status field 5 (repeated): errors active now. +ERROR_CODES: Dict[int, str] = { + 0: "ERROR_NONE", + 1: "ERROR_INIT_WIFI", + 2: "ERROR_INIT_IMU", + 3: "ERROR_INIT_CAM", + 4: "ERROR_INIT_RVC", + 5: "ERROR_INIT_OPTIC", + 6: "ERROR_ENDATA", + 7: "ERROR_DATA_HALL", + 8: "ERROR_DATA_IR", + 9: "ERROR_DATA_CLIFF", + 10: "ERROR_DATA_BUMPER", + 11: "ERROR_DATA_IMU", + 12: "ERROR_DATA_CAM", + 13: "ERROR_DATA_TOF", + 14: "ERROR_DATA_RVC", + 16: "ERROR_WHEEL_DROP_L", + 17: "ERROR_WHEEL_DROP_R", + 18: "ERROR_WHEEL_DROP_BOTH", + 19: "ERROR_WHEEL_STUCK_L", + 20: "ERROR_WHEEL_STUCK_R", + 21: "ERROR_WHEEL_STUCK_BOTH", + 22: "ERROR_FAN_STUCK", + 23: "ERROR_FAN_BROCKEN", + 24: "ERROR_SFRONT_WHEEL_STUCK_L", + 25: "ERROR_SFRONT_WHEEL_STUCK_R", + 26: "ERROR_SFRONT_WHEEL_STUCK_BOTH", + 27: "ERROR_RFRONT_WHEEL_STUCK", + 28: "ERROR_VIBRATOR_MOTOR_STUCK", + 29: "ERROR_PUMP_STUCK", + 30: "ERROR_SFRONT_WHEEL_BROCKEN", + 31: "ERROR_RFRONT_WHEEL_BROCKEN", + 32: "ERROR_BUTTON_BROCKEN", + 33: "ERROR_PUMP_BROCKEN", + 40: "ERROR_BATTERY_ABNORMAL", + 41: "ERROR_BATTERY_CRITICAL_LOW", + 42: "ERROR_BATTERY_LOW_TO_START", + 43: "ERROR_CHARGE_VOL_ABNORMAL", + 44: "ERROR_TEMPERATURE_HIGH", + 45: "ERROR_TEMPERATURE_LOW", + 46: "ERROR_DUST_UNINSTALLED", + 47: "ERROR_TANK_UNINSTALLED", + 48: "ERROR_BASE_DOCK_FAILED", + 49: "ERROR_NO_DOCK_SIGNAL", + 50: "ERROR_ATTITUDE", + 51: "ERROR_CANNOT_FINISH_CLEAN", + 52: "ERROR_WIFI_CONFIG_TIMEOUT", + 53: "ERROR_WIFI_AUTH_FAILED", + 54: "ERROR_OTA_FAILED", + 55: "ERROR_OTA_DOWNLOAD_FAILED", + 56: "ERROR_OTA_NO_SPACE_LEFT", + 57: "ERROR_OTA_BATTERY_LOW", + 58: "ERROR_WIFI_SSID_NOT_FOUND", + 59: "ERROR_WIFI_NO_INTERNET", + 60: "ERROR_WIFI_NO_SERVICE", + 61: "ERROR_CLIFF_START_FAIL", + 62: "ERROR_WHEEL_DROP_START_FAIL", + 63: "ERROR_BUMPER_START_FAIL", + 64: "ERROR_PP_FATAL", + 65: "ERROR_PP_ESCAPE_FAILED", + 66: "ERROR_PP_CANNOT_REACH_DEST", + 67: "ERROR_SPOT_ON_DOCKER", + 68: "ERROR_AED_NOT_FOUND", + 69: "ERROR_AED_PROC_STUCK", + 70: "ERROR_ROBOT_STUCK", + 71: "ERROR_BOUNDARY", + 80: "ERROR_VT_FATAL", + 81: "ERROR_VT_SLIP", + 82: "ERROR_MAP_INCONSISTENT", + 83: "ERROR_MAP_BUSY", + 84: "ERROR_MAP_IO_ERROR", + 85: "ERROR_MAP_NO_SPACE_LEFT", + 86: "ERROR_MAP_RELOC_FAILED", + 87: "ERROR_VIBRATOR_MOTOR_BROCKEN", + 88: "ERROR_TEMPERATURE_ERR", + 89: "ERROR_GNG_ZONE_CLEAN_FAILED", + 90: "ERROR_PICK_UP", + 91: "ERROR_LOW_LIGHT", + 92: "ERROR_SOFTCALIFAILED", + 93: "ERROR_MCU_PARAM", + 94: "ERROR_DUST_UNINSTALLED_IN_REDOCK", + 95: "ERROR_BATTERY_CRITICAL_LOW_IN_PAUSE", + 96: "ERROR_LIDAR_BROCKEN", + 97: "ERROR_NOGO_ESCAPE_TIMEOUT", + 98: "ERROR_LIDAR_OVER_CURRENT", + 99: "ERROR_LIDAR_MODEL_UNKNOWN", + 100: "ERROR_LIDAR_LOST_SPEED", + 101: "ERROR_LIDAR_CRC_FAILED", + 102: "ERROR_LIDAR_DATA_TIMEOUT", + 103: "ERROR_LIDAR_COVERED", + 104: "ERROR_LIDAR_INIT_FAILED", + 105: "ERROR_WATER_TANK_EMPTY", + 106: "ERROR_EXPLORE_MODE_INSTALL_TANK", + 107: "ERROR_CARPET_DETECT", + 108: "ERROR_FANJET_STUCK", + 109: "ERROR_FANJET_BROKEN", + 110: "ERROR_CT_UNINSTALL", + 111: "ERROR_CT_OVERFLOW", + 112: "ERROR_ST_FULL", + 113: "ERROR_WT_UNINSTALL", + 114: "ERROR_WT_EMPTY", + 115: "ERROR_SP_WORK_WITHOUT_CUR", + 116: "ERROR_WSP_WORK_CUR_OVERLOAD", + 117: "ERROR_WFAN_WORK_WITHOUT_CUR", + 118: "ERROR_WFAN_WORK_CUR_OVERLOAD", + 119: "ERROR_WWP_WORK_WITHOUT_CUR", + 120: "ERROR_WWP_WORK_CUR_OVERLOAD", + 121: "ERROR_WFAN_IDLE_WITH_CUR", + 122: "ERROR_WWP_IDLE_WITH_CUR", + 123: "ERROR_WSP_IDLE_WITH_CUR", + 124: "ERROR_WCHARGING_CUR", + 125: "ERROR_WCHARGE_INTRRUPT", + 126: "ERROR_WCHARGE_OVER_VOLTAGE", + 127: "ERROR_WCHARGE_LOW_VOLTAGE", + 128: "ERROR_WS_DUST_UNINSTALL", + 129: "ERROR_WS_ROBOT_DISCONNECT", + 130: "ERROR_WS_ROBOT_DOCK_DISCONNECT", + 131: "ERROR_SPLIT_ZONE_FAIL", + 132: "ERROR_MERGE_ZONE_FAIL", + 133: "ERROR_RESET_ZONE_FAIL", + 134: "ERROR_SPLIT_ZONE_MULTIPLE_REGIONS", + 135: "ERROR_SPLIT_ZONE_AREA_SMALL", + 136: "ERROR_BATTERY_OVER_CURRENT", + 137: "ERROR_BATTERY_TIMEOUT", + 138: "ERROR_BATTERY_OVER_TEMP", + 139: "ERROR_BATTERY_LOW_TEMP", + 140: "ERROR_WASH_MOP_TIMEOUT", + 141: "ERROR_DRY_TIMEOUT", + 142: "ERROR_DRY_INTERRUPT", + 143: "ERROR_DRY_DOCK_MODE_UNMATCH", + 144: "ERROR_CARPET_START_FAILED", + 145: "ERROR_CARPET_ESCAPE_TIMEOUT", + 146: "ERROR_RESTORE_FACTORY_FAILED", + 147: "ERROR_CLEANSENSE_DIRT", + 148: "ERROR_SERVO_OVER_CURRENT", + 149: "ERROR_WET_CLEAN_DETACH_PAD", + 150: "ERROR_DRY_CLEAN_ATTACH_PAD", + 151: "ERROR_MFD_CLEAN_TANK_UNINSTALL", + 152: "ERROR_MFD_CLEAN_TANK_EMPTY", + 153: "ERROR_MFD_GREY_TANK_UNINSTALL", + 154: "ERROR_MFD_GREY_TANK_FULL", + 155: "ERROR_MFD_SHUTTLE_OVER_CURRENT", + 156: "ERROR_MFD_PUMP_OVER_CURRENT", + 157: "ERROR_MFD_PRESSURE_OVER_CURRENT", + 158: "ERROR_MFD_GREY_OVER_CURRENT", + 159: "ERROR_MFD_DRY_FAN_OVER_CURRENT", + 160: "ERROR_MFD_WATER_OVER_FLOW", + 161: "ERROR_MFD_CHARGE_OVER_CURRENT", + 162: "ERROR_UFD_MODEL_UNKNOWN", + 163: "ERROR_EXPLORE_PAD_ATTACHED", + 164: "ERROR_MFD_SHUTTLE_BROKEN", + 165: "ERROR_MFD_PUMP_BROKEN", + 166: "ERROR_MFD_DETACH_MOTOR_BROKEN", + 167: "ERROR_MFD_GREY_BROKEN", + 168: "ERROR_MFD_DRY_FAN_BROKEN", + 169: "ERROR_MFD_DETACH_MOTOR_OVER_CURRENT", + 170: "ERROR_DEEP_CLEAN_OFF_DOCK", + 171: "ERROR_UFD_INIT_FAILED", + 172: "ERROR_UFD_DATA_TIMEOUT", + 173: "ERROR_SERVO_BROKEN", + 174: "ERROR_WATER_TANK_REFILL_EMPTY", + 175: "ERROR_AGITATOR_RESET_FAILED", + 176: "ERROR_LINE_LASER_INIT_FAILED", + 177: "ERROR_LINE_LASER_DATA_TIMEOUT", + 178: "ERROR_LINE_LASER_MODEL_UNKNOWN", + 179: "ERROR_OBS_AVOID_INIT_FAILED", + 180: "ERROR_OBS_AVOID_DATA_TIMEOUT", + 181: "ERROR_WALL_FOLLOW_INIT_FAILED", + 182: "ERROR_WALL_FOLLOW_DATA_TIMEOUT", + 183: "ERROR_MFD_BOILER_NTC_ABNORMAL", + 184: "ERROR_MFD_HEATER_NTC_ABNORMAL", + 185: "ERROR_MFD_BOILER_ABNORMAL", + 186: "ERROR_MFD_HEATER_ABNORMAL", + 187: "ERROR_MFD_LIMIT_SWITCH_BROKEN", + 188: "ERROR_MFD_VALVE_OVER_CURRENT", + 189: "ERROR_MFD_VALVE_BROKEN", + 190: "ERROR_MFD_SCALING_NTC_ABNORMAL", + 191: "MAX_ERROR_NUM", +} + + +# Status field 6 (repeated): warnings active now; also DT_WARNING_CODE in the log. +WARNING_CODES: Dict[int, str] = { + 0: "WARN_NONE", + 1: "WARN_BATTERY_LOW", + 2: "WARN_MCU_PARAM", + 3: "WARN_TANK_EMPTY", + 4: "WARN_LOW_LIGHT", + 5: "WARN_NO_LIGHT", + 6: "WARN_UNLOOPCLOSE", + 7: "WARN_WHEEL_FLY", + 8: "WARN_WHEEL_SLIP", + 9: "WARN_INCLINE", + 10: "WARN_DANGEROUS_ZONE", + 11: "WARN_VT_LOST_LOCALIZATION", + 12: "WARN_VT_RELOC_FAILED", + 13: "WARN_LENS_DIRTY", + 14: "WARN_PP_NO_DOCK", + 15: "WARN_TANK_LOW", + 16: "WARN_WIFI_CONFIG_TIMEOUT", + 17: "WARN_WIFI_AUTH_FAILED", + 18: "WARN_WIFI_SSID_NOT_FOUND", + 19: "WARN_WIFI_NO_INTERNET", + 20: "WARN_FIND_ME", + 21: "WARN_CAMERA_RUN", + 22: "WARN_RECHARGE_RESUME_OPEN", + 23: "WARN_RECHARGE_RESUME_CLOSE", + 24: "WARN_WHEEL_OVER_CURRENT", + 25: "WARN_EVAUCATION_RESUME_OPEN", + 26: "WARN_EVAUCATION_RESUME_CLOSE", + 27: "WARN_SAVE_MAP_BUSY", + 28: "WARN_ESCAPE_FAILED", + 29: "WARN_PICK_UP", + 30: "WARN_OVER_CURRENT", + 31: "WARN_MM_ALLRIGHT", + 32: "WARN_MM_FIRSTCLEAN", + 33: "WARN_MM_TRAINING", + 34: "WARN_MM_NOTTRAINING", + 35: "WARN_MM_AREASMALL", + 36: "WARN_MM_RESETFLOOR1", + 37: "WARN_MM_RESETFLOOR2", + 38: "WARN_MM_SIMILARCHECK1", + 39: "WARN_MM_SIMILARCHECK2", + 40: "WARN_MM_SIMILARCHECK3", + 41: "WARN_MM_REPLACESUCC", + 42: "WARN_MM_NOTFROMSTATION", + 43: "WARN_MM_LOWLIGHT", + 44: "WARN_MM_NOTFINISH", + 45: "WARN_MM_NOTEFFIENCY", + 46: "WARN_MCU_CALI_RESETTED", + 47: "WARN_MCU_CALI_STOP_OK", + 48: "WARN_MCU_CALI_STOP_EARLY", + 49: "WARN_NEED_ENABLE_RR", + 50: "WARN_DUST_FULL", + 51: "WARN_RBR_NOT_COVER_ALL", + 52: "WARN_RELOC_FAILED_WITH_NOGO", + 53: "WARN_BAD_RUN", + 54: "WARN_MAP_SYNC_FAILED", + 55: "WARN_ALL_FLOOR_LOCKED", + 56: "WARN_LIDAR_MAP_INCONSISTENT", + 57: "WARNING_CHARGING_INTERRUPTED", + 58: "WARN_LIDAR_BROKEN", + 59: "WARN_CT_UNINSTALL", + 60: "WARN_CT_OVERFLOW", + 61: "WARN_ST_FULL", + 62: "WARN_WT_UNINSTALL", + 63: "WARN_WT_EMPTY", + 64: "WARN_LACK_OF_WATER", + 65: "WARN_CANT_EDIT_MAP", + 66: "WARN_CANT_SAVE_MAP", + 67: "WARN_CLEANSENSE_DIRT", + 68: "WARN_PARTIAL_CLEANED", + 69: "WARN_SERVO_MOTOR_BROKEN", + 70: "WARN_SERVO_MOTOR_OVER_CURRENT", + 71: "WARN_SERVO_VOLTAGE_L", + 72: "WARN_SERVO_VOLTAGE_R", + 73: "WARN_SERVO_ANGLE_L", + 74: "WARN_SERVO_ANGLE_R", + 75: "WARN_SERVO_OVERTEMP_L", + 76: "WARN_SERVO_OVERTEMP_R", + 77: "WARN_SERVO_STALL_L", + 78: "WARN_SERVO_STALL_R", + 79: "WARN_SERVO_CHECKSUM_L", + 80: "WARN_SERVO_CHECKSUM_R", + 81: "WARN_SERVO_OC_L", + 82: "WARN_SERVO_OC_R", + 83: "WARN_SERVO_PARSING_L", + 84: "WARN_SERVO_PARSING_R", + 85: "WARN_SERVO_BROKEN_L", + 86: "WARN_SERVO_BROKEN_R", + 87: "WARN_SERVO_CLEAN_OC_L", + 88: "WARN_SERVO_CLEAN_OC_R", + 89: "WARN_LEFT_TOF_DIRT", + 90: "WARN_RIGHT_TOF_DIRT", + 91: "WARN_PAD_SHIFT_TOF_DIRT", + 92: "WARN_AED_PROC_STUCK", + 93: "WARN_SERVO_NO_DATA_L", + 94: "WARN_SERVO_NO_DATA_R", + 95: "WARN_SERVO_CHECKSUM", + 96: "WARN_MM_NEW_FLOOR", + 97: "WARN_MM_UPDATE_FLOOR", + 98: "WARN_DOCK_OTA_FAILED", + 99: "WARN_MM_UPDATE_SLAM", + 100: "MAX_WARN_NUM", +} + + +# Why the robot went to its dock: DT_DOCK_CODE in the log. +DOCK_EVENTS: Dict[int, str] = { + 0: "DE_NONE", + 1: "DE_ER", + 2: "DE_2R", + 3: "DE_BAT_LOW", + 4: "DE_ESC_FAILED", + 5: "DE_RELOC_FAILED", + 6: "DE_REDOCK", + 7: "DE_USR_CTR_DOCK", + 8: "DE_CLEAN_FINISH", + 9: "DE_FR", + 10: "DE_BAT_LOW_DC", + 11: "DE_CLEAN_FINISH_DC", +} + + +def names(table: Dict[int, str], codes: Iterable[int]) -> List[str]: + """Names for *codes*, keeping unknown numbers visible as ``UNKNOWN_``.""" + return [table.get(code, f"UNKNOWN_{code}") for code in codes] diff --git a/sharklocal/mappings/mqtt/sharkiq_v1.yaml b/sharklocal/mappings/mqtt/sharkiq_v1.yaml index c157ec8..8545e1b 100644 --- a/sharklocal/mappings/mqtt/sharkiq_v1.yaml +++ b/sharklocal/mappings/mqtt/sharkiq_v1.yaml @@ -15,14 +15,28 @@ encoding: base64 # Name of the registered decoder function used to parse status payloads. status_decoder: sharkiq_protobuf_v1 -# Maps protobuf OperatingMode integer values to normalized VacuumMode strings. -# Reference: sharkiq.proto OperatingMode enum +# Maps the robot's system state (SysStateT, field 4) to normalized VacuumMode +# strings. Names per sharklocal.codes.SYSTEM_STATES; 6, 7, 13 and 14 have been +# observed on an RV2610BFCA (13 is charging on the dock, 14 battery full). modes: - 4: idle - 6: cleaning - 7: returning_to_dock - 13: docking - 14: docked + 2: idle # SYS_ST_SLEEPING + 3: idle # SYS_ST_WAITING + 4: paused # SYS_ST_PAUSE + 5: error # SYS_ST_ERROR + 6: cleaning # SYS_ST_CLEANING + 7: returning_to_dock # SYS_ST_DOCKING + 8: cleaning # SYS_ST_SPOTING + 9: cleaning # SYS_ST_ALONG_WALL + 13: docked # SYS_ST_CHARGING + 14: docked # SYS_ST_BATTERY_FULL + 15: cleaning # SYS_ST_GO_TARGET + 16: cleaning # SYS_ST_CLEAN_ZONE + 24: exploring # SYS_ST_AUTO_EXPLORE + 25: returning_to_dock # SYS_ST_REDOCKING + 26: returning_to_dock # SYS_ST_EXPLORE_DOCKING + 28: cleaning # SYS_ST_RELOCAL + 29: docking # SYS_ST_BASE_DOCK + 31: exploring # SYS_ST_CARPET_EXPLORE # Payloads are protobuf. Field 16 is the job command (9 start, 17 return to # dock); field 7 carries settings, which the robot echoes back in field 8 of diff --git a/sharklocal/models.py b/sharklocal/models.py index 7af9e22..d4e7ca6 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -24,6 +24,8 @@ class VacuumMode(str, Enum): DOCKED = "docked" IDLE = "idle" # Powered on but not cleaning and not on the charging dock EXPLORING = "exploring" # Mapping/exploration run in progress + PAUSED = "paused" # A job paused mid-way + ERROR = "error" # Stopped by a fault class SuctionLevel(str, Enum): @@ -52,6 +54,9 @@ class VacuumStatus: recharge_resume: Optional[bool] = None evac_resume: Optional[bool] = None map: Optional["VacuumMap"] = None + # Codes active now (SharkIQ fields 5 and 6); names in sharklocal.codes. + errors: Optional[List[int]] = None + warnings: Optional[List[int]] = None @property def is_cleaning(self) -> bool: diff --git a/sharklocal/mqtt_client.py b/sharklocal/mqtt_client.py index 264edf2..fa875ce 100644 --- a/sharklocal/mqtt_client.py +++ b/sharklocal/mqtt_client.py @@ -4,7 +4,7 @@ import asyncio import base64 -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Dict, List, Optional from .exceptions import ActionNotSupportedError, CommandError, ConnectError, DecoderError from .mappings.base import MQTTMappingConfig @@ -43,7 +43,9 @@ def _decode_sharkiq_protobuf_v1( Field reference: - * Field 4 — ``OperatingMode`` integer + * Field 4 — system state (``SysStateT``, see :mod:`sharklocal.codes`) + * Field 5 — error codes active now (repeated ``ErrorCodeT``) + * Field 6 — warning codes active now (repeated ``WarningCodeT``) * Field 9 — ``BatteryInfo`` nested message * Field 1 — ``ChargingState`` (3 = ``CHARGING_ON_DOCK``) @@ -66,6 +68,8 @@ def _decode_sharkiq_protobuf_v1( payload = protobuf.remove_field(payload, vacuum_map.FIELD_MAP) raw = protobuf.decode_raw(payload) + errors = _repeated_varints(fields.get(5, [])) + warnings = _repeated_varints(fields.get(6, [])) mode_int = raw.get(4, 0) mode_str = modes.get(mode_int, "unknown") @@ -100,9 +104,25 @@ def _decode_sharkiq_protobuf_v1( recharge_resume=recharge_resume, evac_resume=evac_resume, map=decoded_map, + errors=errors, + warnings=warnings, ) +def _repeated_varints(values: list) -> List[int]: + """Values of a repeated varint field, packed (one bytes blob) or not.""" + out: List[int] = [] + for value in values: + if isinstance(value, int): + out.append(value) + continue + pos = 0 + while pos < len(value): + number, pos = protobuf._decode_varint(value, pos) + out.append(number) + return out + + class MQTTVacuumClient: """Async MQTT client for local vacuum control. diff --git a/tests/test_codes.py b/tests/test_codes.py new file mode 100644 index 0000000..3665192 --- /dev/null +++ b/tests/test_codes.py @@ -0,0 +1,72 @@ +"""Tests for sharklocal.codes and the live error/warning fields.""" + +import base64 +from pathlib import Path + +import pytest + +from sharklocal import codes, protobuf +from sharklocal.mappings import load_mqtt_mapping +from sharklocal.models import VacuumMode +from sharklocal.mqtt_client import _decode_sharkiq_protobuf_v1 + +FIXTURES = Path(__file__).parent / "fixtures" + + +def _status_frame() -> bytes: + return base64.b64decode((FIXTURES / "sharkiq_status_frame.b64").read_text().strip()) + + +def test_names_agree_with_the_event_log(): + # The log writes these as strings; the schema numbers them. + assert codes.WARNING_CODES[43] == "WARN_MM_LOWLIGHT" + assert codes.DOCK_EVENTS[7] == "DE_USR_CTR_DOCK" + assert codes.DOCK_EVENTS[8] == "DE_CLEAN_FINISH" + assert codes.SYSTEM_STATES[13] == "SYS_ST_CHARGING" + assert codes.ERROR_CODES[0] == "ERROR_NONE" + + +def test_unknown_numbers_stay_visible(): + assert codes.names(codes.WARNING_CODES, [43, 9999]) == ["WARN_MM_LOWLIGHT", "UNKNOWN_9999"] + + +def _decode(extra: bytes): + mapping = load_mqtt_mapping("sharkiq_v1") + return _decode_sharkiq_protobuf_v1(_status_frame() + extra, mapping.modes) + + +def test_a_real_status_frame_has_no_active_errors(): + status = _decode(b"") + assert status.errors == [] + assert status.warnings == [] + + +def test_packed_errors_and_warnings_are_decoded(): + packed = protobuf.encode_bytes_field(5, bytes([21, 64])) + protobuf.encode_bytes_field(6, bytes([43])) + status = _decode(packed) + assert status.errors == [21, 64] + assert codes.names(codes.ERROR_CODES, status.errors)[0] == codes.ERROR_CODES[21] + assert status.warnings == [43] + + +def test_unpacked_errors_are_decoded(): + status = _decode(protobuf.encode_varint_field(5, 64) + protobuf.encode_varint_field(5, 150)) + assert status.errors == [64, 150] + + +@pytest.mark.parametrize( + "state, mode", + [ + (3, VacuumMode.IDLE), + (4, VacuumMode.PAUSED), + (5, VacuumMode.ERROR), + (6, VacuumMode.CLEANING), + (7, VacuumMode.RETURNING_TO_DOCK), + (8, VacuumMode.CLEANING), + (13, VacuumMode.DOCKED), # charging on the dock: observed after every job + (14, VacuumMode.DOCKED), + (16, VacuumMode.CLEANING), + ], +) +def test_system_states_map_to_modes(state, mode): + assert load_mqtt_mapping("sharkiq_v1").modes[state] == mode.value diff --git a/tests/test_models.py b/tests/test_models.py index aff744d..005511a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -25,13 +25,13 @@ def test_vacuum_mode_all_values(): - expected = {"unknown", "cleaning", "returning_to_dock", "docking", "docked", "idle", "exploring"} + expected = {"unknown", "cleaning", "returning_to_dock", "docking", "docked", "idle", "exploring", "paused", "error"} actual = {m.value for m in VacuumMode} assert actual == expected def test_vacuum_mode_count(): - assert len(VacuumMode) == 7 + assert len(VacuumMode) == 9 def test_vacuum_mode_is_str_enum(): From 49118fc42288b7abd3524ea192b7bf7dfa41ddc5 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:26:36 -0400 Subject: [PATCH 15/33] Version 0.3.0.dev5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b657f56..8b30338 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev4" +version = "0.3.0.dev5" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From c594750060266e257ddd4e741b3cbc0675a100e0 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:31:50 -0400 Subject: [PATCH 16/33] Decode device info and motor speeds; add pause, edge clean, explore and settings - Status: charging now follows the system state (13 charging); field 9.1 is the Wi-Fi state, which reads 3 while the robot cleans and made charging read true mid-job. Device info (temperature, Wi-Fi state/link/signal, water and dust level, IP), live suction/brushroll/side-brush speeds, CleanEdge and carpet-detect status and the relocation state are decoded. Checked against the captured frames: the end-of-job frame's live warning (WARN_MM_LOWLIGHT) matches its own event log. - Commands from the app's schema: pause (USR_CTR_PAUSE; start resumes), edge clean (USR_CTR_ALONG_WALL) and explore over MQTT. - set_config writes one field of the settings block; set_toggle names the on/off settings (DnD, child lock, silent mode, CleanEdge, carpet boost, button sounds, underglow lights, continuous crosshatch, room by room), and reproduces the app's captured Recharge & Resume / Evac & Resume commands byte for byte. set_carpet_detect and set_volume write theirs. - codes gains the Wi-Fi, carpet-detect and relocation state names. --- docs/vacuum-client.md | 10 ++++ sharklocal/client.py | 55 +++++++++++++++++++++- sharklocal/codes.py | 36 +++++++++++++++ sharklocal/mappings/mqtt/sharkiq_v1.yaml | 15 ++++++ sharklocal/models.py | 16 +++++++ sharklocal/mqtt_client.py | 59 +++++++++++++++++++++--- tests/test_client.py | 55 ++++++++++++++++++++++ tests/test_codes.py | 44 ++++++++++++++++++ tests/test_mqtt_client.py | 16 ++++--- 9 files changed, 292 insertions(+), 14 deletions(-) diff --git a/docs/vacuum-client.md b/docs/vacuum-client.md index 5e907a2..f86aecf 100644 --- a/docs/vacuum-client.md +++ b/docs/vacuum-client.md @@ -104,6 +104,10 @@ print(vacuum.via) # "REST", "MQTT", or "NONE" depending on what responded | `set_evac_resume(enabled)` | *(not in REST mapping)* | `evac_resume_on` / `_off` (command) | | `clean_rooms(names, deep=False, vacuum_map=None)` | *(not in REST mapping)* | runtime-built payload, then `start_cleaning` | | `clean_spot(x, y, vacuum_map=None)` | *(not in REST mapping)* | runtime-built payload, then `start_cleaning` | +| `pause()` | *(not in REST mapping)* | `pause` (command; `start_cleaning` resumes) | +| `edge_clean()` | *(not in REST mapping)* | `edge_clean` (command) | +| `set_toggle(name, enabled)` | *(not in REST mapping)* | runtime-built settings payload (`CONFIG_TOGGLES`) | +| `set_carpet_detect(mode)`, `set_volume(n)` | *(not in REST mapping)* | runtime-built settings payload | ### Return Types @@ -164,6 +168,12 @@ await vacuum.set_evac_resume(False) Recharge & Resume and Evac & Resume are read back from `VacuumStatus.recharge_resume` / `evac_resume`. The suction level is **not** reported in status — the robot echoes a change once and is then silent about it — so keep the last value you set if you need to display it. +### More settings, pause and live faults + +The SharkIQ settings block (`PbConfig` in the app's schema) is written one field at a time with `set_config(field, value)`; `CONFIG_TOGGLES` names the on/off settings (`do_not_disturb`, `child_lock`, `silent_mode`, `clean_edge`, `carpet_boost`, `button_sounds`, `underglow_lights`, `continuous_cross_hatch`, `room_by_room`, plus the two resume settings, whose writes match the app's captured commands byte for byte). Which settings a given model honours is not known in advance; the robot only echoes a setting once, so keep what you set. + +`pause()` sends `USR_CTR_PAUSE`; `start_cleaning()` (`USR_CTR_RESUME`) resumes. Status frames report the faults and warnings active now in `VacuumStatus.errors` / `warnings`, named by `sharklocal.codes`. + ### Room, Matrix and spot cleaning ```python diff --git a/sharklocal/client.py b/sharklocal/client.py index 1095abf..c288384 100644 --- a/sharklocal/client.py +++ b/sharklocal/client.py @@ -4,8 +4,9 @@ import asyncio from pathlib import Path -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional, Union +from . import protobuf from .exceptions import ActionNotSupportedError, ConnectError, SharklocalError from .mappings import load_mqtt_mapping, load_rest_mapping from .models import ( @@ -21,6 +22,26 @@ from .vacuum_map import encode_room_selection, encode_spot_selection +# On/off settings in the SharkIQ settings block (PbConfig), by field number, +# from the app's protobuf schema. Written as PbToggleT: 1 on, 2 off. Field 8 +# (Recharge & Resume) and 7 (Evac & Resume) match the app captures exactly. +CONFIG_TOGGLES: Dict[str, int] = { + "evac_resume": 7, + "recharge_resume": 8, + "room_by_room": 12, + "continuous_cross_hatch": 13, + "clean_edge": 14, + "do_not_disturb": 18, + "carpet_boost": 19, + "child_lock": 24, + "silent_mode": 25, + "underglow_lights": 26, + "button_sounds": 27, +} +CONFIG_VOLUME = 2 +CONFIG_CARPET_DETECT = 15 + + class VacuumClient: """Unified vacuum client with automatic transport selection. @@ -168,6 +189,38 @@ async def get_wifi_status(self) -> DeviceInfo: """Return Wi-Fi connection details including MAC address.""" return await self._execute("get_wifi_status") + async def pause(self) -> bool: + """Pause the current job (resume it with :meth:`start_cleaning`).""" + return await self._execute("pause") + + async def edge_clean(self) -> bool: + """Clean along the walls.""" + return await self._execute("edge_clean") + + async def set_config(self, field_num: int, value: int) -> bool: + """Write one field of the robot's settings block (``PbConfig``). + + MQTT-only and SharkIQ-specific: the command is field 7 carrying the one + setting. :data:`CONFIG_TOGGLES` names the on/off settings; suction and + the resume settings have their own methods. + """ + payload = protobuf.encode_bytes_field(7, protobuf.encode_varint_field(field_num, value)) + return await self._send(payload) + + async def set_toggle(self, name: str, enabled: bool) -> bool: + """Switch a named on/off setting (see :data:`CONFIG_TOGGLES`).""" + if name not in CONFIG_TOGGLES: + raise ValueError(f"Unknown setting {name!r}; known: {sorted(CONFIG_TOGGLES)}") + return await self.set_config(CONFIG_TOGGLES[name], 1 if enabled else 2) + + async def set_carpet_detect(self, mode: int) -> bool: + """Set carpet detection (1 disabled, 2 automatic; codes.CARPET_DETECT_MODES).""" + return await self.set_config(CONFIG_CARPET_DETECT, mode) + + async def set_volume(self, volume: int) -> bool: + """Set the robot's voice volume.""" + return await self.set_config(CONFIG_VOLUME, volume) + async def find_robot(self) -> bool: """Make the vacuum play its locate sound.""" return await self._execute("find_robot") diff --git a/sharklocal/codes.py b/sharklocal/codes.py index 1a2f9cf..6c01233 100644 --- a/sharklocal/codes.py +++ b/sharklocal/codes.py @@ -356,6 +356,42 @@ } +# Device info field 1. +WIFI_STATES: Dict[int, str] = { + 0: "WIFI_NONE", + 1: "WIFI_CONFIGURING", + 2: "WIFI_CONNECTING", + 3: "WIFI_CONNECTED", + 4: "WIFI_CONNECTED_INTERNET", + 5: "WIFI_DRIVER_ERR", + 6: "WIFI_UNKNOW_ERR", + 7: "WIFI_UNCONNECTED", + 8: "WIFI_CONFIG_TIMEOUT", + 9: "WIFI_SSID_NOT_FOUND", + 10: "WIFI_AUTH_FAILED", + 11: "WIFI_NO_SERVICE", + 12: "WIFI_NO_INTERNET", +} + + +# Status field 44 and config field 15. +CARPET_DETECT_MODES: Dict[int, str] = { + 0: "CD_NONE", + 1: "CD_DISABLE_ALL", + 2: "CD_AUTO", +} + + +# Status field 41. +RELOCATION_STATES: Dict[int, str] = { + 0: "RS_NONE", + 1: "RS_RELOC", + 2: "RS_FAIL", + 3: "RS_SUCCESS", + 4: "RS_UNRELOC", +} + + def names(table: Dict[int, str], codes: Iterable[int]) -> List[str]: """Names for *codes*, keeping unknown numbers visible as ``UNKNOWN_``.""" return [table.get(code, f"UNKNOWN_{code}") for code in codes] diff --git a/sharklocal/mappings/mqtt/sharkiq_v1.yaml b/sharklocal/mappings/mqtt/sharkiq_v1.yaml index 8545e1b..012921e 100644 --- a/sharklocal/mappings/mqtt/sharkiq_v1.yaml +++ b/sharklocal/mappings/mqtt/sharkiq_v1.yaml @@ -57,6 +57,21 @@ actions: type: command payload: "gAER" + # Field 16 is UserCtrlSignalT (sharklocal.codes-style names from the app's + # schema): 8 USR_CTR_PAUSE; start_cleaning above is 9 USR_CTR_RESUME, which + # also resumes a paused job; 16 USR_CTR_ALONG_WALL; 38 USR_CTR_AUTO_EXPLORE. + pause: + type: command + payload: "gAEI" + + edge_clean: + type: command + payload: "gAEQ" + + explore: + type: command + payload: "gAEm" + # Field 25 = 104 find_robot: type: command diff --git a/sharklocal/models.py b/sharklocal/models.py index d4e7ca6..38524e0 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -57,6 +57,22 @@ class VacuumStatus: # Codes active now (SharkIQ fields 5 and 6); names in sharklocal.codes. errors: Optional[List[int]] = None warnings: Optional[List[int]] = None + # SharkIQ device info (field 9), live motor speeds (field 16) and feature + # status, each None when the frame does not carry it. + state: Optional[int] = None # system state (codes.SYSTEM_STATES) + temperature: Optional[int] = None # degrees C + wifi_state: Optional[int] = None # codes.WIFI_STATES + wifi_link_quality: Optional[int] = None + wifi_signal: Optional[int] = None + water_level: Optional[int] = None + dust_level: Optional[int] = None + ip_address: Optional[str] = None + fan_speed: Optional[int] = None # suction motor, live + brushroll_speed: Optional[int] = None + side_brush_speed: Optional[int] = None + clean_edge: Optional[bool] = None # CleanEdge (fan jet) enabled + carpet_detect: Optional[int] = None # codes.CARPET_DETECT_MODES + relocation: Optional[int] = None # codes.RELOCATION_STATES @property def is_cleaning(self) -> bool: diff --git a/sharklocal/mqtt_client.py b/sharklocal/mqtt_client.py index fa875ce..d0a29ed 100644 --- a/sharklocal/mqtt_client.py +++ b/sharklocal/mqtt_client.py @@ -78,13 +78,13 @@ def _decode_sharkiq_protobuf_v1( except ValueError: mode = VacuumMode.UNKNOWN - battery_info = raw.get(9, {}) - battery_percent: Optional[int] = None - charging: Optional[bool] = None - if isinstance(battery_info, dict): - battery_percent = battery_info.get(8) - charging_state = battery_info.get(1, 0) - charging = charging_state == 3 # ChargingState.CHARGING_ON_DOCK + # Field 9 is device info. Its field 1 is the Wi-Fi state, not a charging + # state (it reads 3, WIFI_CONNECTED, while the robot cleans); charging is + # the system state SYS_ST_CHARGING. + device = _submessage(fields, 9) + battery_percent: Optional[int] = _first_int(device, 8) + charging: Optional[bool] = (mode_int == 13) if 4 in raw else None + base = _submessage(fields, 16) # Settings are reflected by presence: the field disappears when switched off. recharge_resume = 35 in raw @@ -106,9 +106,54 @@ def _decode_sharkiq_protobuf_v1( map=decoded_map, errors=errors, warnings=warnings, + state=raw.get(4), + temperature=_first_int(device, 11), + wifi_state=_first_int(device, 1), + wifi_link_quality=_first_int(device, 2), + wifi_signal=_first_int(device, 3), + water_level=_first_int(device, 9), + dust_level=_first_int(device, 10), + ip_address=_first_text(device, 17), + fan_speed=_first_int(base, 3), + brushroll_speed=_first_int(base, 5), + side_brush_speed=_first_int(base, 6), + clean_edge=_toggle(raw.get(43)), + carpet_detect=raw.get(44), + relocation=raw.get(41), ) +def _submessage(fields: Dict[int, list], num: int) -> Dict[int, list]: + """The fields of a nested message, or an empty dict.""" + value = fields.get(num, [None])[0] + if not isinstance(value, bytes): + return {} + try: + return protobuf.decode_fields(value) + except Exception: # noqa: BLE001 - a malformed sub-message is just absent + return {} + + +def _first_int(fields: Dict[int, list], num: int) -> Optional[int]: + value = fields.get(num, [None])[0] + return value if isinstance(value, int) else None + + +def _first_text(fields: Dict[int, list], num: int) -> Optional[str]: + value = fields.get(num, [None])[0] + if not isinstance(value, bytes): + return None + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return None + + +def _toggle(value: Any) -> Optional[bool]: + """A PbToggleT: 1 on, 2 off, anything else unknown.""" + return {1: True, 2: False}.get(value) + + def _repeated_varints(values: list) -> List[int]: """Values of a repeated varint field, packed (one bytes blob) or not.""" out: List[int] = [] diff --git a/tests/test_client.py b/tests/test_client.py index 7cfed29..1c5973d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -605,6 +605,8 @@ async def test_stop_monitoring_cancels_task(): ("set_recharge_resume", (False,), "recharge_resume_off"), ("set_evac_resume", (True,), "evac_resume_on"), ("set_evac_resume", (False,), "evac_resume_off"), + ("pause", (), "pause"), + ("edge_clean", (), "edge_clean"), ], ) async def test_setting_actions_call_execute(method_name, args, expected_action): @@ -615,6 +617,59 @@ async def test_setting_actions_call_execute(method_name, args, expected_action): mock_exec.assert_awaited_once_with(expected_action) +@pytest.mark.parametrize( + "name, enabled, captured", + [ + # The app's own Recharge & Resume / Evac & Resume commands. + ("recharge_resume", True, "OgJAAQ=="), + ("recharge_resume", False, "OgJAAg=="), + ("evac_resume", True, "OgI4AQ=="), + ("evac_resume", False, "OgI4Ag=="), + ], +) +async def test_toggle_writer_reproduces_the_app_captures(name, enabled, captured): + import base64 + + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + assert await client.set_toggle(name, enabled) is True + client._mqtt.send.assert_awaited_once_with(base64.b64decode(captured)) + + +async def test_settings_write_their_config_fields(): + from sharklocal import protobuf + + client = _make_vacuum_client_with_mocks() + client._mqtt.send = AsyncMock(return_value=True) + await client.set_toggle("do_not_disturb", True) + await client.set_carpet_detect(2) + await client.set_volume(7) + sent = [call.args[0] for call in client._mqtt.send.await_args_list] + assert sent == [ + protobuf.encode_bytes_field(7, protobuf.encode_varint_field(18, 1)), + protobuf.encode_bytes_field(7, protobuf.encode_varint_field(15, 2)), + protobuf.encode_bytes_field(7, protobuf.encode_varint_field(2, 7)), + ] + + +async def test_unknown_toggle_is_refused(): + client = _make_vacuum_client_with_mocks() + with pytest.raises(ValueError, match="turbo"): + await client.set_toggle("turbo", True) + + +def test_mapping_carries_the_new_commands(): + import base64 + + from sharklocal.mappings import load_mqtt_mapping + + actions = load_mqtt_mapping("sharkiq_v1").actions + assert base64.b64decode(actions["pause"].payload) == bytes([0x80, 0x01, 8]) # 16 = USR_CTR_PAUSE + assert base64.b64decode(actions["start_cleaning"].payload) == bytes([0x80, 0x01, 9]) # USR_CTR_RESUME + assert base64.b64decode(actions["edge_clean"].payload) == bytes([0x80, 0x01, 16]) # USR_CTR_ALONG_WALL + assert base64.b64decode(actions["explore"].payload) == bytes([0x80, 0x01, 38]) # USR_CTR_AUTO_EXPLORE + + async def test_set_suction_rejects_unknown_level(): client = _make_vacuum_client_with_mocks() with pytest.raises(ValueError): diff --git a/tests/test_codes.py b/tests/test_codes.py index 3665192..bdf11b9 100644 --- a/tests/test_codes.py +++ b/tests/test_codes.py @@ -70,3 +70,47 @@ def test_unpacked_errors_are_decoded(): ) def test_system_states_map_to_modes(state, mode): assert load_mqtt_mapping("sharkiq_v1").modes[state] == mode.value + + +def _fixture_status(name: str): + mapping = load_mqtt_mapping("sharkiq_v1") + payload = base64.b64decode((FIXTURES / name).read_text().strip()) + return _decode_sharkiq_protobuf_v1(payload, mapping.modes) + + +def test_live_frame_mid_clean(): + status = _fixture_status("sharkiq_live_map_frame.b64") + assert status.state == 6 + assert status.mode == VacuumMode.CLEANING + assert status.charging is False + assert (status.fan_speed, status.brushroll_speed, status.side_brush_speed) == (82, 54, 44) + assert codes.RELOCATION_STATES[status.relocation] == "RS_SUCCESS" + # A dark basement: the live warning field reports low light. + assert codes.names(codes.WARNING_CODES, status.warnings) == ["WARN_LOW_LIGHT", "WARN_LOW_LIGHT"] + + +def test_end_of_job_frame_agrees_with_its_own_log(): + status = _fixture_status("sharkiq_persisted_map_frame.b64") + assert codes.SYSTEM_STATES[status.state] == "SYS_ST_CHARGING" + assert status.mode == VacuumMode.DOCKED + assert status.charging is True + # The live warning code and the event log's DT_WARNING_CODE name agree. + assert codes.names(codes.WARNING_CODES, status.warnings) == ["WARN_MM_LOWLIGHT"] + assert [e.code for e in status.map.log if e.key == "DT_WARNING_CODE"] == ["WARN_MM_LOWLIGHT"] + + +def test_docked_and_full(): + status = _fixture_status("sharkiq_status_frame.b64") + assert codes.SYSTEM_STATES[status.state] == "SYS_ST_BATTERY_FULL" + assert status.charging is False + assert status.battery_level == 100 + assert status.temperature == 20 + assert codes.WIFI_STATES[status.wifi_state] == "WIFI_CONNECTED" + assert status.fan_speed == 0 + + +def test_feature_toggles(): + status = _decode(protobuf.encode_varint_field(43, 1) + protobuf.encode_varint_field(44, 2)) + assert status.clean_edge is True + assert codes.CARPET_DETECT_MODES[status.carpet_detect] == "CD_AUTO" + assert _decode(protobuf.encode_varint_field(43, 2)).clean_edge is False diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index 39584ca..f6348cb 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -125,10 +125,14 @@ def test_decode_sharkiq_mode_int(mode_int, expected_mode): assert result.mode == expected_mode -def test_decode_sharkiq_charging_state_3_means_charging(): - payload = _build_status_payload(6, 3, 80) - result = _decode_sharkiq_protobuf_v1(payload, _MODES) - assert result.charging is True +def test_decode_sharkiq_charging_comes_from_the_system_state(): + # Field 9.1 is the Wi-Fi state (3 = connected), which the robot reports + # while cleaning; it was once misread as "charging on dock". + cleaning = _decode_sharkiq_protobuf_v1(_build_status_payload(6, 3, 80), _MODES) + assert cleaning.charging is False + assert cleaning.wifi_state == 3 + on_dock = _decode_sharkiq_protobuf_v1(_build_status_payload(13, 3, 80), _MODES) + assert on_dock.charging is True def test_decode_sharkiq_charging_state_0_not_charging(): @@ -157,7 +161,7 @@ def test_decode_sharkiq_battery_info_not_dict(): with patch.object(protobuf, "decode_raw", return_value={4: 6, 9: b"\xff\xff"}): result = _decode_sharkiq_protobuf_v1(b"\x00", _MODES) assert result.battery_level is None - assert result.charging is None + assert result.charging is False # cleaning, from the state def test_decode_sharkiq_raw_contains_protobuf_fields(): @@ -343,7 +347,7 @@ async def test_request_status_returns_decoded_status(mqtt_mapping): assert isinstance(result, VacuumStatus) assert result.mode == VacuumMode.CLEANING assert result.battery_level == 80 - assert result.charging is True + assert result.charging is False # --------------------------------------------------------------------------- From 3e716b1b97583ae4ff3043df8247f5ad2d4f672c Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:31:50 -0400 Subject: [PATCH 17/33] Version 0.3.0.dev6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8b30338..6dece4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev5" +version = "0.3.0.dev6" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From b532b43ef7a583fe745b6f3a4837326263ca13e4 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:50:51 -0400 Subject: [PATCH 18/33] Add compat: which features a model has, as the SharkClean app decides A port of the app's capability logic and its 611-model table (SharkClean 6.27.0): model string -> family -> classification, the prefix-letter and hex-bitmask hardware rules, and the exact RV3000 / OPP / spot-lidar / 360EZ / ODM / MPP lists. Features are three-valued, None when the model is missing from the table, because the robot does not report its cloud model locally and retail SKUs are not cloud model strings. --- docs/vacuum-client.md | 4 + sharklocal/compat.py | 908 ++++++++++++++++++++++++++++++++++++++++++ tests/test_compat.py | 67 ++++ 3 files changed, 979 insertions(+) create mode 100644 sharklocal/compat.py create mode 100644 tests/test_compat.py diff --git a/docs/vacuum-client.md b/docs/vacuum-client.md index f86aecf..9328d6b 100644 --- a/docs/vacuum-client.md +++ b/docs/vacuum-client.md @@ -174,6 +174,10 @@ The SharkIQ settings block (`PbConfig` in the app's schema) is written one field `pause()` sends `USR_CTR_PAUSE`; `start_cleaning()` (`USR_CTR_RESUME`) resumes. Status frames report the faults and warnings active now in `VacuumStatus.errors` / `warnings`, named by `sharklocal.codes`. +### Which features a model has + +`sharklocal.compat.RobotProfile(model)` answers what the SharkClean app would show for a robot, from the cloud model string (`oem_model`, e.g. `RV3000-001D5F7F`): its family and classification, and features such as `has_auto_empty`, `do_not_disturb`, `has_volume`, `has_pin_drop`, `has_ultra_clean`, `has_underglow_lights` or `has_carpet_boost`. It is a port of the app's own rules and model table (SharkClean 6.27.0). The model is not reported over local MQTT, so it has to be supplied; a string the table lacks (retail SKUs such as `RV2610BFCA` are not cloud model strings) gives `None` for family-based features rather than a guess. + ### Room, Matrix and spot cleaning ```python diff --git a/sharklocal/compat.py b/sharklocal/compat.py new file mode 100644 index 0000000..938cbc7 --- /dev/null +++ b/sharklocal/compat.py @@ -0,0 +1,908 @@ +"""Which features a Shark robot has, by its model string, as the SharkClean app decides. + +A port of the app's own capability logic (its embedded "launch" module, SharkClean +6.27.0). Almost everything follows from one string, the robot's cloud model +(``oem_model``, e.g. ``RV2500AWSX``): a table gives its family, the family its +classification, letters in the model prefix and a hex bitmask add hardware, and a +few exact model lists pick out product lines. + +The model is not reported over local MQTT, so callers supply it. Features are +three-valued: ``True`` / ``False`` as the app would decide, or ``None`` when the +model is missing from the table (a retail SKU such as ``RV2610BFCA`` is not the +cloud model string) and a family-based answer cannot be given. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict, FrozenSet, Optional + +# Fs.robots: cloud model string -> family. +MODEL_FAMILIES: Dict[str, str] = { + "AV752": "RandomBounceAV75", + "AV753": "RandomBounceAV75", + "AV911": "Mesa3", + "AV911S": "Mesa3", + "AV970": "Valley1", + "AV990": "Valley2", + "AV992": "Mesa3", + "AV993": "Mesa3", + "RV1000": "Valley1", + "RV1000A": "Mesa1", + "RV1000A-CN": "Mesa1", + "RV1000ADev-CN": "RandomBounceOther", + "RV1000A-EU": "Mesa1", + "RV1000A-JP": "Mesa1", + "RV1000A-UK": "Mesa1", + "RV1000-CN": "Valley1", + "RV1000Dev-CN": "RandomBounceOther", + "RV1000-EU": "Valley1", + "RV1000-JP": "Valley1", + "RV1000-UK": "Valley1", + "RV100V2": "Valley2", + "RV100V3": "Valley2", + "RV1100": "Valley2", + "RV1100A": "Mesa2", + "RV1100A2": "Mesa2", + "RV1100A3": "Mesa2", + "RV1100AA": "MesaAir", + "RV1100AA-CN": "MesaAir", + "RV1100AA-EU": "MesaAir", + "RV1100AA-JP": "MesaAir", + "RV1100AA-UK": "MesaAir", + "RV1100AAX": "MesaAir", + "RV1100AAXDev": "MesaAir", + "RV1100AB": "Mesa2", + "RV1100AC": "Mesa2", + "RV1100A-CN": "Mesa2", + "RV1100A-EU": "Mesa2", + "RV1100A-JP": "Mesa2", + "RV1100A-UK": "Mesa2", + "RV1100AX": "Mesa2", + "RV1100AXDev": "Mesa2", + "RV1100-CN": "Valley2", + "RV1100-EU": "Valley2", + "RV1100-JP": "Valley2", + "RV1100-UK": "Valley2", + "RV1100V2": "Valley2", + "RV1100V3": "Valley2", + "RV1100VA": "ValleyAir", + "RV1100VA-CN": "ValleyAir", + "RV1100VA-EU": "ValleyAir", + "RV1100VA-JP": "ValleyAir", + "RV1100VA-UK": "ValleyAir", + "RV1100VAX": "ValleyAir", + "RV1100VAXDev": "ValleyAir", + "RV1100VB": "ValleyAir", + "RV1100VC": "ValleyAir", + "RV1100X": "Valley2", + "RV1100XDev": "Valley2", + "RV1107": "Valley2", + "RV1107A": "Mesa2", + "RV1107VA": "ValleyAir", + "RV1107AA": "MesaAir", + "RV1107X": "Valley2", + "RV1107AX": "Mesa2", + "RV1107VAX": "ValleyAir", + "RV1107AAX": "MesaAir", + "RV19GyroDev": "RandomBounceOther", + "RV19GyroPDev": "RandomBounceOther", + "RV19OPPDev": "RandomBounceOther", + "RV19PosDev": "RandomBounceOther", + "RV2000D": "Gaia", + "RV2000D-EU": "Gaia", + "RV2000D-UK": "Gaia", + "RV2000DX": "Gaia", + "RV2000DXDev": "Gaia", + "RV2000WD": "Poseidon", + "RV2000WD-EU": "Poseidon", + "RV2000WD-UK": "Poseidon", + "RV2000WX": "Poseidon", + "RV2000WXDev": "Poseidon", + "RV2018Hill": "RandomBounceOther", + "RV2018HillPlus": "RandomBounceOther", + "RV2019QF": "Valley1", + "RV2019QFA": "Mesa1", + "RV2019QFA-CN": "RandomBounceOther", + "RV2019QF-CN": "RandomBounceOther", + "RV20GaiDev": "Gaia", + "RV20PosDev": "Poseidon", + "RV20QFADev": "Mesa3", + "RV20QFDev": "Valley2", + "RV2100DF": "Gaia", + "RV2100DFDev": "Gaia", + "RV2100WF": "Poseidon", + "RV2100WFDev": "Poseidon", + "RV21QFAADev": "MesaAir", + "RV21QFADev": "Mesa3", + "RV21QFDev": "Valley3", + "RV21QFVADev": "ValleyAir", + "RV2500A": "Three60", + "RV2500A-EU": "Three60", + "RV2500A-UK": "Three60", + "RV2500AX": "Three60", + "RV2500AXDev": "Three60", + "RV360LDev": "Three60", + "RV750": "RandomBounceRV75", + "RV750G": "RandomBounceRV75", + "RV750G-EU": "RandomBounceRV75", + "RV750G-UK": "RandomBounceRV75", + "RV750L": "RandomBounceRV75", + "RV750L-CN": "RandomBounceRV75", + "RV750LDev": "RandomBounceRV75", + "RV750LDev-CN": "RandomBounceRV75", + "RV750L-EU": "RandomBounceRV75", + "RV750L-JP": "RandomBounceRV75", + "RV750L-UK": "RandomBounceRV75", + "RV750N": "RandomBounceRV75", + "RV750NDev": "RandomBounceRV75", + "RV750O": "RandomBounceRV75", + "RV750P": "RandomBounceRV75", + "RV750P-EU": "RandomBounceRV75", + "RV750P-UK": "RandomBounceRV75", + "RV750R": "RandomBounceRV75", + "RV750RDev": "RandomBounceRV75", + "RV750X": "RandomBounceRV75", + "RV750XDev": "RandomBounceRV75", + "RV754G-UK": "RandomBounceRV75", + "RV754P-UK": "RandomBounceRV75", + "RV850": "RandomBounceRV85", + "RV850Dev": "RandomBounceRV85", + "RV871": "RandomBounceRV87", + "RV871-CN": "RandomBounceRV87", + "RV871Dev": "RandomBounceRV87", + "RV871Dev-CN": "RandomBounceRV87", + "RV871-EU": "RandomBounceRV87", + "RV871-JP": "RandomBounceRV87", + "RV871P": "RandomBounceRV87", + "RV871P-CN": "RandomBounceRV87", + "RV871P-EU": "RandomBounceRV87", + "RV871P-JP": "RandomBounceRV87", + "RV871P-UK": "RandomBounceRV87", + "RV871-UK": "RandomBounceRV87", + "RV912": "MesaAir", + "RV912S": "MesaAir", + "RV912SCA": "MesaAir", + "RV913S": "MesaAir", + "RV914SCA": "MesaAir", + "RV990CA": "ValleyAir", + "RoboVac01": "RandomBounceOther", + "RV360ZDev": "Three60", + "RV2500AZ": "Three60", + "RV1300S3EU": "MesaAir", + "RV2500WD": "Three60WetDry", + "RV2500WDDev": "Three60WetDry", + "RV360LAFDev": "Three60", + "RV360LWFDev": "Three60WetDry", + "RV360LBFDev": "Three60WetDry", + "RV2500AF": "Three60", + "RV2500WF": "Three60WetDry", + "RV2500BF": "Three60WetDry", + "RV2500AF-EU": "Three60", + "RV2500AF-UK": "Three60", + "RV360LAWFXDev": "Three60WetDry", + "RV360LAWFDev": "Three60WetDry", + "RV360LAWXDev": "Three60WetDry", + "RV360LAWDev": "Three60WetDry", + "RV360LAFXDev": "Three60", + "RV360LAXDev": "Three60", + "RV360LADev": "Three60", + "RV360LWFXDev": "Three60WetDry", + "RV360LWXDev": "Three60WetDry", + "RV360LWDev": "Three60WetDry", + "RV360LFXDev": "Three60", + "RV360LFDev": "Three60", + "RV360LXDev": "Three60", + "RV2300D": "Three60", + "RV2300AE": "Three60", + "RV2300S": "Three60", + "RV2300D0-US": "Three60", + "RV2300D0-JP": "Three60", + "RV2300C0-US": "Three60", + "RV2300E0-US": "Three60", + "RV2300E0-EU": "Three60", + "RV2300E0-JP": "Three60", + "RV2500AWFX": "Three60WetDry", + "RV2500AWF": "Three60WetDry", + "RV2500AWX": "Three60WetDry", + "RV2500AW": "Three60WetDry", + "RV2500AFX": "Three60", + "RV2500WFX": "Three60WetDry", + "RV2500WX": "Three60WetDry", + "RV2500W": "Three60WetDry", + "RV2500FX": "Three60", + "RV2500F": "Three60", + "RV2500X": "Three60", + "RV2500": "Three60", + "RV2500AWFX-EU": "Three60WetDry", + "RV2500AWF-EU": "Three60WetDry", + "RV2500AWX-EU": "Three60WetDry", + "RV2500AW-EU": "Three60WetDry", + "RV2500AFX-EU": "Three60", + "RV2500AX-EU": "Three60", + "RV2500WFX-EU": "Three60WetDry", + "RV2500WF-EU": "Three60WetDry", + "RV2500WX-EU": "Three60WetDry", + "RV2500W-EU": "Three60WetDry", + "RV2500FX-EU": "Three60", + "RV2500F-EU": "Three60", + "RV2500X-EU": "Three60", + "RV2500-EU": "Three60", + "RV2500AWFX-UK": "Three60WetDry", + "RV2500AWF-UK": "Three60WetDry", + "RV2500AWX-UK": "Three60WetDry", + "RV2500AW-UK": "Three60WetDry", + "RV2500AFX-UK": "Three60", + "RV2500AX-UK": "Three60", + "RV2500WFX-UK": "Three60WetDry", + "RV2500WF-UK": "Three60WetDry", + "RV2500WX-UK": "Three60WetDry", + "RV2500W-UK": "Three60WetDry", + "RV2500FX-UK": "Three60", + "RV2500F-UK": "Three60", + "RV2500X-UK": "Three60", + "RV2500-UK": "Three60", + "RV360LVADev": "Three60", + "RV360LVAXDev": "Three60", + "RV360LAADev": "Three60", + "RV360LAAXDev": "Three60", + "RV2500VA": "Three60", + "RV2500VAX": "Three60", + "RV2500AA": "Three60", + "RV2500AAX": "Three60", + "RV2500VA-JP": "Three60", + "RV365LAWFXDev": "Three60WetDry", + "RV365LAWFDev": "Three60WetDry", + "RV365LAWXDev": "Three60WetDry", + "RV365LAWDev": "Three60WetDry", + "RV365LAFXDev": "Three60", + "RV365LAFDev": "Three60", + "RV365LAXDev": "Three60", + "RV365LADev": "Three60", + "RV365LWFXDev": "Three60WetDry", + "RV365LWFDev": "Three60WetDry", + "RV365LWXDev": "Three60WetDry", + "RV365LWDev": "Three60WetDry", + "RV365LFXDev": "Three60", + "RV365LFDev": "Three60", + "RV365LXDev": "Three60", + "RV365LDev": "Three60", + "RV2500AA-EU": "Three60", + "RV2500VA-EU": "Three60", + "RV2500VA-UK": "Three60", + "RV2500AA-UK": "Three60", + "RV2500AA-JP": "Three60", + "RV380LAWFDev": "Three60WetDry", + "RV380LAWDev": "Three60WetDry", + "RV380LWFDev": "Three60WetDry", + "RV380LWDev": "Three60WetDry", + "RV380LAWFXDev": "Three60WetDry", + "RV380LAWXDev": "Three60WetDry", + "RV380LWFXDev": "Three60WetDry", + "RV380LWXDev": "Three60WetDry", + "RV2500-JP": "Three60", + "RV2500A-JP": "Three60", + "RV2500AF-JP": "Three60", + "RV2500AW-JP": "Three60WetDry", + "RV2500AWF-JP": "Three60WetDry", + "RV2500F-JP": "Three60", + "RV2500W-JP": "Three60WetDry", + "RV2500WF-JP": "Three60WetDry", + "RV2505": "Three60", + "RV2505-JP": "Three60", + "RV2505A": "Three60", + "RV2505A-JP": "Three60", + "RV2505AF": "Three60", + "RV2505AF-JP": "Three60", + "RV2505AW": "Three60WetDry", + "RV2505AW-JP": "Three60WetDry", + "RV2505AWF": "Three60WetDry", + "RV2505AWF-JP": "Three60WetDry", + "RV2505F": "Three60", + "RV2505F-JP": "Three60", + "RV2505W": "Three60WetDry", + "RV2505W-JP": "Three60WetDry", + "RV2505WF": "Three60WetDry", + "RV2505WF-JP": "Three60WetDry", + "RV380LADev": "Three60", + "RV380LAFDev": "Three60", + "RV380LAFXDev": "Three60", + "RV380LAXDev": "Three60", + "RV380LDev": "Three60", + "RV380LFDev": "Three60", + "RV380LFXDev": "Three60", + "RV380LXDev": "Three60", + "RV360LAAPDev": "Three60", + "RV360LVAPDev": "Three60", + "RV360LAAXPDev": "Three60", + "RV360LVAXPDev": "Three60", + "RV2500AAP": "Three60", + "RV2500AAXP": "Three60", + "RV2500VAP": "Three60", + "RV2500VAXP": "Three60", + "RV2500AAP-JP": "Three60", + "RV2500VAP-JP": "Three60", + "RV2500AAP-EU": "Three60", + "RV2500VAP-EU": "Three60", + "RV2500AAP-UK": "Three60", + "RV2500VAP-UK": "Three60", + "RV2800AW": "Three60WetDry", + "RV2800AWF": "Three60WetDry", + "RV2800AWFX": "Three60WetDry", + "RV2800AWX": "Three60WetDry", + "RV2800W": "Three60WetDry", + "RV2800WF": "Three60WetDry", + "RV2800WFX": "Three60WetDry", + "RV2800WX": "Three60WetDry", + "RV2800A": "Three60", + "RV2800AF": "Three60", + "RV2800AFX": "Three60", + "RV2800AX": "Three60", + "RV2800": "Three60", + "RV2800F": "Three60", + "RV2800FX": "Three60", + "RV2800X": "Three60", + "RV2800AW-EU": "Three60WetDry", + "RV2800AWFX-EU": "Three60WetDry", + "RV2800AWX-EU": "Three60WetDry", + "RV2800W-EU": "Three60WetDry", + "RV2800WF-EU": "Three60WetDry", + "RV2800WFX-EU": "Three60WetDry", + "RV2800WX-EU": "Three60WetDry", + "RV2800A-EU": "Three60", + "RV2800AF-EU": "Three60", + "RV2800AFX-EU": "Three60", + "RV2800AX-EU": "Three60", + "RV2800-EU": "Three60", + "RV2800F-EU": "Three60", + "RV2800FX-EU": "Three60", + "RV2800X-EU": "Three60", + "RV2800AW-UK": "Three60WetDry", + "RV2800AWF-UK": "Three60WetDry", + "RV2800AWFX-UK": "Three60WetDry", + "RV2800AWX-UK": "Three60WetDry", + "RV2800W-UK": "Three60WetDry", + "RV2800WF-UK": "Three60WetDry", + "RV2800WFX-UK": "Three60WetDry", + "RV2800WX-UK": "Three60WetDry", + "RV2800A-UK": "Three60", + "RV2800AF-UK": "Three60", + "RV2800AFX-UK": "Three60", + "RV2800AX-UK": "Three60", + "RV2800-UK": "Three60", + "RV2800F-UK": "Three60", + "RV2800FX-UK": "Three60", + "RV2800X-UK": "Three60", + "RV2500AABDev": "Three60", + "RV2500AAXBDev": "Three60", + "RV2500VABDev": "Three60", + "RV2500VAXBDev": "Three60", + "RV2500AAPBDev": "Three60", + "RV2500AAXPBDev": "Three60", + "RV2500VAPBDev": "Three60", + "RV2500VAXPBDev": "Three60", + "RV2500AABDev-JP": "Three60", + "RV2500AAXBDev-JP": "Three60", + "RV2500VABDev-JP": "Three60", + "RV2500VAXBDev-JP": "Three60", + "RV2500AAPBDev-JP": "Three60", + "RV2500AAXPBDev-JP": "Three60", + "RV2500VAPBDev-JP": "Three60", + "RV2500VAXPBDev-JP": "Three60", + "RV360LAABDev": "Three60", + "RV360LAAXBDev": "Three60", + "RV360LVABDev": "Three60", + "RV360LVAXBDev": "Three60", + "RV360LAAPBDev": "Three60", + "RV360LAAXPBDev": "Three60", + "RV360LVAPBDev": "Three60", + "RV360LVAXPBDev": "Three60", + "RV2500AAB-EU": "Three60", + "RV2500AAXB-EU": "Three60", + "RV2500VAB-EU": "Three60", + "RV2500VAXB-EU": "Three60", + "RV2500AAPB-EU": "Three60", + "RV2500AAXPB-EU": "Three60", + "RV2500VAPB-EU": "Three60", + "RV2500VAXPB-EU": "Three60", + "RV2500AAB-UK": "Three60", + "RV2500AAXB-UK": "Three60", + "RV2500VAB-UK": "Three60", + "RV2500VAXB-UK": "Three60", + "RV2500AAPB-UK": "Three60", + "RV2500AAXPB-UK": "Three60", + "RV2500VAPB-UK": "Three60", + "RV2500VAXPB-UK": "Three60", + "RV2500AAB": "Three60", + "RV2500AAXB": "Three60", + "RV2500VAB": "Three60", + "RV2500VAXB": "Three60", + "RV2500AAPB": "Three60", + "RV2500AAXPB": "Three60", + "RV2500VAPB": "Three60", + "RV2500VAXPB": "Three60", + "RV2500AAB-JP": "Three60", + "RV2500AAXB-JP": "Three60", + "RV2500VAB-JP": "Three60", + "RV2500VAXB-JP": "Three60", + "RV2500AAPB-JP": "Three60", + "RV2500AAXPB-JP": "Three60", + "RV2500VAPB-JP": "Three60", + "RV2500VAXPB-JP": "Three60", + "RV2500AWFB": "Three60WetDry", + "RV2500AWFXB": "Three60WetDry", + "RV2500AWB": "Three60WetDry", + "RV2500AWXB": "Three60WetDry", + "RV2500WFB": "Three60WetDry", + "RV2500WFXB": "Three60WetDry", + "RV2500WB": "Three60WetDry", + "RV2500WXB": "Three60WetDry", + "RV2500AWFB-JP": "Three60WetDry", + "RV2500AWFXB-JP": "Three60WetDry", + "RV2500AWB-JP": "Three60WetDry", + "RV2500AWXB-JP": "Three60WetDry", + "RV2500WFB-JP": "Three60WetDry", + "RV2500WFXB-JP": "Three60WetDry", + "RV2500WB-JP": "Three60WetDry", + "RV2500WXB-JP": "Three60WetDry", + "RV360LAWFBDev": "Three60WetDry", + "RV360LAWFXBDev": "Three60WetDry", + "RV360LAWBDev": "Three60WetDry", + "RV360LAWXBDev": "Three60WetDry", + "RV360LWFBDev": "Three60WetDry", + "RV360LWFXBDev": "Three60WetDry", + "RV360LWBDev": "Three60WetDry", + "RV360LWXBDev": "Three60WetDry", + "RV2500AWFB-EU": "Three60WetDry", + "RV2500AWFXB-EU": "Three60WetDry", + "RV2500AWB-EU": "Three60WetDry", + "RV2500AWXB-EU": "Three60WetDry", + "RV2500WFB-EU": "Three60WetDry", + "RV2500WFXB-EU": "Three60WetDry", + "RV2500WB-EU": "Three60WetDry", + "RV2500WXB-EU": "Three60WetDry", + "RV2500AWFB-UK": "Three60WetDry", + "RV2500AWFXB-UK": "Three60WetDry", + "RV2500AWB-UK": "Three60WetDry", + "RV2500AWXB-UK": "Three60WetDry", + "RV2500WFB-UK": "Three60WetDry", + "RV2500WFXB-UK": "Three60WetDry", + "RV2500WB-UK": "Three60WetDry", + "RV2500WXB-UK": "Three60WetDry", + "RV381LAWFDev": "Three60", + "RV382LAWFDev": "Three60", + "RV381LAWFUSDev": "Three60WetDry", + "RV382LAWFUSDev": "Three60WetDry", + "RV2530AWFB": "Three60WetDry", + "RV2530WFB": "Three60WetDry", + "RV2530AWB": "Three60WetDry", + "RV2530WB": "Three60WetDry", + "RV2530WFB-JP": "Three60WetDry", + "RV2530AWB-JP": "Three60WetDry", + "RV2530WB-JP": "Three60WetDry", + "RV2530AWFB-EU": "Three60WetDry", + "RV2530WFB-EU": "Three60WetDry", + "RV2530AWB-EU": "Three60WetDry", + "RV2530WB-EU": "Three60WetDry", + "RV2530AWFB-UK": "Three60WetDry", + "RV2530WFB-UK": "Three60WetDry", + "RV2530AWB-UK": "Three60WetDry", + "RV2530WB-UK": "Three60WetDry", + "RV2810AWFB": "Three60WetDry", + "RV2820AWFB": "Three60WetDry", + "RV2810AWFB-JP": "Three60WetDry", + "RV2820AWFB-JP": "Three60WetDry", + "RV2810AWFB-EU": "Three60WetDry", + "RV2820AWFB-EU": "Three60WetDry", + "RV2810AWFB-UK": "Three60WetDry", + "RV2820AWFB-UK": "Three60WetDry", + "RV2810LEGAL": "Three60WetDry", + "RV2820LEGAL": "Three60WetDry", + "RV2830WFB": "Three60WetDry", + "RV2830WFB-JP": "Three60WetDry", + "RV2830WFB-EU": "Three60WetDry", + "RV2830WFB-UK": "Three60WetDry", + "RV2800AWF-EU": "Three60WetDry", + "RV363LAWBDev": "Three60WetDry", + "RV363LAWFBDev": "Three60WetDry", + "RV363LWFBDev": "Three60WetDry", + "RV363LWBDev": "Three60WetDry", + "RV383LWFBDev": "Three60WetDry", + "RV383LWFBUSDev": "Three60WetDry", + "RV2530AWFB-JP": "Three60WetDry", + "RV2530AWFB-MX": "Three60WetDry", + "RV2530-0000007D": "Three60WetDry", + "RV2530-0000007D-EU": "Three60WetDry", + "RV2530-0000007D-UK": "Three60WetDry", + "RV24SpLABDev": "Three60", + "RV24SpLBDev": "Three60", + "RV2100AB": "Three60", + "RV2100AB-AZ": "Three60", + "RV2100B": "Three60", + "RV2100AB-MX": "Three60", + "RV2100B-MX": "Three60", + "RV2100AB-JP": "Three60", + "RV2100B-JP": "Three60", + "RV2100AB-UK": "Three60", + "RV2100B-UK": "Three60", + "RV2100AB-EU": "Three60", + "RV2100B-EU": "Three60", + "RV2800AWF-JP": "Three60WetDry", + "RV2800AWFX-JP": "Three60WetDry", + "RV2800AW-JP": "Three60WetDry", + "RV2800AWX-JP": "Three60WetDry", + "RV2800AF-JP": "Three60", + "RV2800AFX-JP": "Three60", + "RV2800A-JP": "Three60", + "RV2800AX-JP": "Three60", + "RV2800WF-JP": "Three60WetDry", + "RV2800WFX-JP": "Three60WetDry", + "RV2800W-JP": "Three60WetDry", + "RV2800WX-JP": "Three60WetDry", + "RV2800F-JP": "Three60", + "RV2800FX-JP": "Three60", + "RV2800-JP": "Three60", + "RV2800X-JP": "Three60", + "RV2500AWFB-MX": "Three60WetDry", + "RV2500VAB-MX": "Three60", + "RV2500AAB-MX": "Three60", + "RV2500AAPB-MX": "Three60", + "RV2710AWFB": "Three60WetDry", + "RV2710AWFB-EU": "Three60WetDry", + "RV2710AWFB-UK": "Three60WetDry", + "RV2720AWFB": "Three60WetDry", + "RV2720AWFB-EU": "Three60WetDry", + "RV2720AWFB-UK": "Three60WetDry", + "RV2720-0000B43F": "Three60WetDry", + "RV2720-0000B43F-EU": "Three60WetDry", + "RV2720-0000B43F-UK": "Three60WetDry", + "RV2720-0000B43F-Dev": "Three60WetDry", + "RV2720-0008343F": "Three60WetDry", + "RV2720-0008343F-EU": "Three60WetDry", + "RV2720-0008343F-UK": "Three60WetDry", + "RV2720-0008343F-Dev": "Three60WetDry", + "RV2720-000C143F": "Three60WetDry", + "RV2720-000C143F-EU": "Three60WetDry", + "RV2720-000C143F-UK": "Three60WetDry", + "RV2720-000C143F-Dev": "Three60WetDry", + "RV371LAWFDev": "Three60WetDry", + "RV372LAWFDev": "Three60WetDry", + "RV2900AWFSB": "Three60WetDry", + "RV2900AWFB": "Three60WetDry", + "RV2900AWFSB-EU": "Three60WetDry", + "RV2900AWFSB-UK": "Three60WetDry", + "RV2900AWFB-EU": "Three60WetDry", + "RV2900AWFB-UK": "Three60WetDry", + "RV390LAWFSDev": "Three60WetDry", + "RV390LAWFDev": "Three60WetDry", + "RV3000-001D5F7F": "Three60WetDry", + "RV3000-001F5F7F": "Three60WetDry", + "RV3000-001D5F7F-EU": "Three60WetDry", + "RV3000-001F5F7F-EU": "Three60WetDry", + "RV3000-001D5F7F-UK": "Three60WetDry", + "RV3000-001D5F7F-CA": "Three60WetDry", + "RV3000-001F5F7F-AZ": "Three60WetDry", + "RV3000-001D5F7F-AZ": "Three60WetDry", + "RV2800WA3ACAWAD": "Three60WetDry", + "RV2800WA3ACAWHD": "Three60WetDry", + "RV2800WA3ACHWHD": "Three60WetDry", + "RV2900WA3ACAWAD": "Three60WetDry", + "RV2900WA3ACAWHD": "Three60WetDry", + "RV2900WA3ACHWHD": "Three60WetDry", + "RV2900WA3HCAWAD": "Three60WetDry", + "RV2900WA3HCAWHD": "Three60WetDry", + "RV2900WA3HCHWHD": "Three60WetDry", + "RV2900WA3ACAWAD-EU": "Three60WetDry", + "RV2900WA3ACAWHD-EU": "Three60WetDry", + "RV2900WA3ACHWHD-EU": "Three60WetDry", + "RV2900WA3HCAWAD-EU": "Three60WetDry", + "RV2900WA3HCAWHD-EU": "Three60WetDry", + "RV2900WA3HCHWHD-EU": "Three60WetDry", + "RV2900WA3ACAWAD-UK": "Three60WetDry", + "RV2900WA3ACAWHD-UK": "Three60WetDry", + "RV2900WA3ACHWHD-UK": "Three60WetDry", + "RV2900WA3HCAWAD-UK": "Three60WetDry", + "RV2900WA3HCAWHD-UK": "Three60WetDry", + "RV2900WA3HCHWHD-UK": "Three60WetDry", + "RV2900WA3ACAWAD-AZ": "Three60WetDry", + "RV2900WA3ACAWHD-AZ": "Three60WetDry", + "RV2900WA3ACHWHD-AZ": "Three60WetDry", + "RV2900WA3HCAWAD-AZ": "Three60WetDry", + "RV2900WA3HCAWHD-AZ": "Three60WetDry", + "RV2900WA3HCHWHD-AZ": "Three60WetDry", + "RV2500HOP": "Three60WetDry", + "RV2500HOP-EU": "Three60WetDry", + "RV2500HOPP2B": "Three60WetDry", + "RV2500HOPP2B-EU": "Three60WetDry", + "RV2500HOPP2NB": "Three60WetDry", + "RV2500HOPP2NB-EU": "Three60WetDry", + "RV2500HOPP4CB": "Three60WetDry", + "RV2500HOPP4CB-UK": "Three60WetDry", + "RV2500HOPP4CB-EU": "Three60WetDry", + "RV2800FW3HWHD": "Three60WetDry", + "RV2800FW3HWHD-EU": "Three60WetDry", + "RV2800FW3HWHD-UK": "Three60WetDry", + "RV2800FW3HWHD-CA": "Three60WetDry", + "RV2800FW3AWAD": "Three60WetDry", + "RV2800FW3AWAD-EU": "Three60WetDry", + "RV2800FW3AWAD-UK": "Three60WetDry", + "RV2800FW3AWAD-CA": "Three60WetDry", + "RV2800FW3AWHD": "Three60WetDry", + "RV2800FW3AWHD-EU": "Three60WetDry", + "RV2800FW3AWHD-UK": "Three60WetDry", +} + +_CLASSIFICATIONS: Dict[str, str] = { + "Gaia": "LaserBot", + "Poseidon": "LaserBot", + "Mesa1": "MapBot", + "Mesa2": "MapBot", + "Mesa3": "MapBot", + "MesaAir": "MapBot", + "Valley1": "MapBot", + "Valley2": "MapBot", + "Valley3": "MapBot", + "ValleyAir": "MapBot", + "RandomBounceAV75": "RandomBounce", + "RandomBounceRV75": "RandomBounce", + "RandomBounceRV85": "RandomBounce", + "RandomBounceRV87": "RandomBounce", + "RandomBounceOther": "RandomBounce", + "Three60": "Three60", + "Three60WetDry": "Three60", +} + +# Exact model lists (all Three60-class), as the app's switch statements have them. +RV3000_MODELS: FrozenSet[str] = frozenset( + { + "RV3000-001D5F7F", + "RV3000-001F5F7F", + "RV3000-001D5F7F-EU", + "RV3000-001F5F7F-EU", + "RV3000-001D5F7F-UK", + "RV3000-001D5F7F-CA", + "RV3000-001F5F7F-AZ", + "RV3000-001D5F7F-AZ", + } +) + +OPP_MODELS: FrozenSet[str] = frozenset( + { + "RV2500HOP", + "RV2500HOP-EU", + "RV2500HOPP2B", + "RV2500HOPP2B-EU", + "RV2500HOPP2NB", + "RV2500HOPP2NB-EU", + "RV2500HOPP4CB", + "RV2500HOPP4CB-UK", + "RV2500HOPP4CB-EU", + "RV2530-0000007D", + "RV2530-0000007D-UK", + "RV2530-0000007D-EU", + } +) + +SPOT_LIDAR_MODELS: FrozenSet[str] = frozenset( + { + "RV24SpLABDev", + "RV24SpLBDev", + "RV2100AB", + "RV2100AB-AZ", + "RV2100B", + } +) + +THREE60_EZ_MODELS: FrozenSet[str] = frozenset( + { + "RV360LVADev", + "RV360LVAXDev", + "RV360LAADev", + "RV360LAAXDev", + "RV2500VA", + "RV2500VAX", + "RV2500AA", + "RV2500AAX", + "RV2500VA-JP", + } +) + +ODM_MODELS: FrozenSet[str] = frozenset( + { + "RV2800WA3ACAWAD", + "RV2800WA3ACAWHD", + "RV2800WA3ACHWHD", + "RV2900WA3ACAWAD", + "RV2900WA3ACAWHD", + "RV2900WA3ACHWHD", + "RV2900WA3HCAWAD", + "RV2900WA3HCAWHD", + "RV2900WA3HCHWHD", + "RV2900WA3ACAWAD-EU", + "RV2900WA3ACAWHD-EU", + "RV2900WA3ACHWHD-EU", + "RV2900WA3HCAWAD-EU", + "RV2900WA3HCAWHD-EU", + "RV2900WA3HCHWHD-EU", + "RV2900WA3ACAWAD-UK", + "RV2900WA3ACAWHD-UK", + "RV2900WA3ACHWHD-UK", + "RV2900WA3HCAWAD-UK", + "RV2900WA3HCAWHD-UK", + "RV2900WA3HCHWHD-UK", + "RV2900WA3ACAWAD-AZ", + "RV2900WA3ACAWHD-AZ", + "RV2900WA3ACHWHD-AZ", + "RV2900WA3HCAWAD-AZ", + "RV2900WA3HCAWHD-AZ", + "RV2900WA3HCHWHD-AZ", + } +) + +MPP_MODELS: FrozenSet[str] = frozenset( + { + "RV2800FW3HWHD", + "RV2800FW3HWHD-EU", + "RV2800FW3HWHD-UK", + "RV2800FW3HWHD-CA", + "RV2800FW3AWAD", + "RV2800FW3AWAD-EU", + "RV2800FW3AWAD-UK", + "RV2800FW3AWAD-CA", + "RV2800FW3AWHD", + "RV2800FW3AWHD-EU", + "RV2800FW3AWHD-UK", + } +) + +# Bits of the hex feature mask in models like RV3000-001D5F7F, least significant first. +_MASK_BITS = ("WetDry", "FanJet", "AED", "Series 3", "Bluetooth", "Lidar", "Floor Detect") + +_MAPPING_FAMILIES = frozenset( + {"Gaia", "Poseidon", "Three60", "Three60WetDry", "Mesa1", "Mesa2", "Mesa3", "Valley1", "Valley2", "Valley3"} +) +_EXPLORE_FAMILIES = frozenset({"Gaia", "Poseidon", "Three60", "Three60WetDry"}) +_DND_FAMILIES = _MAPPING_FAMILIES | {"MesaAir", "ValleyAir"} +_VALLEY_FAMILIES = frozenset({"Valley1", "Valley2", "Valley3", "ValleyAir"}) +_MOPPING_FAMILIES = frozenset({"Poseidon", "Three60WetDry"}) + + +@dataclass(frozen=True) +class RobotProfile: + """The app's view of one robot model.""" + + model: str + + @property + def family(self) -> Optional[str]: + """The model's family, or None when the app's table does not list it.""" + return MODEL_FAMILIES.get(self.model) + + @property + def classification(self) -> Optional[str]: + return _CLASSIFICATIONS.get(self.family) if self.family else None + + @property + def known(self) -> bool: + return self.family is not None + + # -- product lines (exact lists; an unlisted model is simply not one) ---- + + def _in(self, models: FrozenSet[str]) -> bool: + return self.classification == "Three60" and self.model in models + + @property + def is_rv3000(self) -> bool: + return self._in(RV3000_MODELS) + + @property + def is_opp(self) -> bool: + return self._in(OPP_MODELS) + + @property + def is_spot_lidar(self) -> bool: + return self._in(SPOT_LIDAR_MODELS) + + @property + def is_360ez(self) -> bool: + return self._in(THREE60_EZ_MODELS) + + # -- hardware from the model string ------------------------------------- + + @property + def _prefix(self) -> str: + return self.model.split("-")[0] + + def _mask(self, bit: str) -> Optional[bool]: + """A bit of the hex feature mask, or None when the model carries none.""" + parts = self.model.split("-") + if len(parts) < 2 or len(parts[1]) < 8: + return None + try: + value = int(parts[1], 16) + except ValueError: + return None + return bool(value >> _MASK_BITS.index(bit) & 1) + + def _family_in(self, families: FrozenSet[str]) -> Optional[bool]: + return None if self.family is None else self.family in families + + def _class_in(self, *classes: str) -> Optional[bool]: + return None if self.classification is None else self.classification in classes + + # -- features, as the app gates them ------------------------------------ + + @property + def has_map(self) -> Optional[bool]: + return self._family_in(_MAPPING_FAMILIES) + + @property + def has_explore(self) -> Optional[bool]: + return self._family_in(_EXPLORE_FAMILIES) + + @property + def has_recharge_resume(self) -> Optional[bool]: + return self._family_in(_MAPPING_FAMILIES) + + @property + def has_auto_empty(self) -> Optional[bool]: + """A self-emptying dock, so Evac & Resume.""" + if self._in(ODM_MODELS) or self._in(MPP_MODELS) or self.is_opp: + return True + masked = self._mask("AED") + if masked is not None: + return masked + if "A" in self._prefix and "VA" not in self._prefix: + return None if self.family is None else self.family not in _VALLEY_FAMILIES + return False + + @property + def has_fan_jet(self) -> bool: + """CleanEdge air jets.""" + masked = self._mask("FanJet") + return masked if masked is not None else "X" in self._prefix + + @property + def do_not_disturb(self) -> Optional[bool]: + return self._family_in(_DND_FAMILIES) + + @property + def has_volume(self) -> Optional[bool]: + """The app shows the volume slider to every robot but random-bounce ones.""" + return None if self.classification is None else self.classification != "RandomBounce" + + @property + def has_pin_drop(self) -> Optional[bool]: + """Spot clean by dropping a pin on the map.""" + if self.is_spot_lidar: + return False + return self._class_in("Three60", "LaserBot") + + @property + def has_ultra_clean(self) -> Optional[bool]: + """Matrix (two-pass) cleaning.""" + if self.is_spot_lidar or self.is_opp: + return False + return self._class_in("Three60", "LaserBot") + + @property + def has_mopping(self) -> Optional[bool]: + return self._family_in(_MOPPING_FAMILIES) + + # RV3000-only settings rows, and OPP-only carpet controls. + + @property + def has_underglow_lights(self) -> bool: + return self.is_rv3000 + + @property + def has_button_sounds(self) -> bool: + return self.is_rv3000 + + @property + def has_carpet_boost(self) -> bool: + return self.is_opp + + @property + def has_carpet_detect(self) -> bool: + return self.is_opp diff --git a/tests/test_compat.py b/tests/test_compat.py new file mode 100644 index 0000000..a943014 --- /dev/null +++ b/tests/test_compat.py @@ -0,0 +1,67 @@ +"""Tests for sharklocal.compat: the app's per-model feature rules.""" + +import pytest + +from sharklocal.compat import MODEL_FAMILIES, RV3000_MODELS, RobotProfile + + +def test_the_app_table_is_carried_whole(): + assert len(MODEL_FAMILIES) > 600 + assert MODEL_FAMILIES["RV1000A"] == "Mesa1" + + +def test_rv3000_gets_its_settings_rows_and_mask_features(): + p = RobotProfile("RV3000-001D5F7F") + assert p.classification == "Three60" + assert p.is_rv3000 and p.has_underglow_lights and p.has_button_sounds + # 0x...7F: WetDry, FanJet, AED, Series 3, Bluetooth, Lidar, Floor Detect. + assert p.has_auto_empty is True + assert p.has_fan_jet is True + assert not p.has_carpet_boost + assert RV3000_MODELS >= {"RV3000-001D5F7F"} + + +def test_opp_gets_carpet_controls_but_not_matrix(): + p = RobotProfile("RV2500HOP") + assert p.is_opp and p.has_carpet_boost and p.has_carpet_detect + assert p.has_ultra_clean is False + assert p.has_auto_empty is True + assert not p.has_underglow_lights + + +def test_random_bounce_gets_almost_nothing(): + p = RobotProfile("RV750") + assert p.classification == "RandomBounce" + assert (p.has_map, p.has_volume, p.do_not_disturb, p.has_pin_drop) == (False, False, False, False) + + +def test_spot_lidar_has_no_pin_drop(): + p = RobotProfile("RV2100AB") + assert p.has_map is True + assert p.has_pin_drop is False + assert p.has_ultra_clean is False + + +def test_prefix_letters(): + # A (not VA) = self-emptying dock; X = FanJet. + three60 = next(m for m, f in MODEL_FAMILIES.items() if f == "Three60" and "A" in m.split("-")[0] and "VA" not in m) + assert RobotProfile(three60).has_auto_empty is True + valley = next((m for m, f in MODEL_FAMILIES.items() if f.startswith("Valley") and "A" in m.split("-")[0] and "VA" not in m), None) + if valley: + assert RobotProfile(valley).has_auto_empty is False + assert RobotProfile("RV2500VA").has_auto_empty is False + assert RobotProfile("RV2000DX").has_fan_jet is True + + +def test_a_model_missing_from_the_table_is_unknown_not_false(): + # A retail SKU is not the cloud model string: family answers are unknown, + # exact product lines are simply not matched. + p = RobotProfile("RV2610BFCA") + assert not p.known + assert p.family is None and p.classification is None + assert (p.has_map, p.has_explore, p.do_not_disturb, p.has_volume, p.has_pin_drop, p.has_mopping) == (None,) * 6 + assert (p.has_underglow_lights, p.has_carpet_boost, p.is_360ez) == (False, False, False) + + +def test_a_malformed_mask_is_ignored(): + assert RobotProfile("RV3000-ZZZZZZZZ")._mask("AED") is None From 5880ebbef221615146ab540d1299c3b01a542a58 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:50:51 -0400 Subject: [PATCH 19/33] Version 0.3.0.dev7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dece4d..3ea366a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev6" +version = "0.3.0.dev7" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 2ff89e730c7860ce031b92088f52643f729b9514 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:52:29 -0400 Subject: [PATCH 20/33] Seven robot types that stand in for the model table Every model in the app's table falls into one of seven types (lidar, lidar_carpet, rv3000, spot_lidar, map, air, basic); Capabilities(type, self_empty_dock, clean_edge) answers the gated features as booleans, and a test checks that for all 611 models the type plus its two extras matches the per-model rules exactly - so a user can configure a robot without knowing its cloud model string. --- docs/vacuum-client.md | 2 + sharklocal/compat.py | 108 ++++++++++++++++++++++++++++++++++++++++++ tests/test_compat.py | 49 +++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/docs/vacuum-client.md b/docs/vacuum-client.md index 9328d6b..cc360ce 100644 --- a/docs/vacuum-client.md +++ b/docs/vacuum-client.md @@ -178,6 +178,8 @@ The SharkIQ settings block (`PbConfig` in the app's schema) is written one field `sharklocal.compat.RobotProfile(model)` answers what the SharkClean app would show for a robot, from the cloud model string (`oem_model`, e.g. `RV3000-001D5F7F`): its family and classification, and features such as `has_auto_empty`, `do_not_disturb`, `has_volume`, `has_pin_drop`, `has_ultra_clean`, `has_underglow_lights` or `has_carpet_boost`. It is a port of the app's own rules and model table (SharkClean 6.27.0). The model is not reported over local MQTT, so it has to be supplied; a string the table lacks (retail SKUs such as `RV2610BFCA` are not cloud model strings) gives `None` for family-based features rather than a guess. +For configuration, every model falls into one of seven robot types (`compat.ROBOT_TYPES`: `lidar`, `lidar_carpet`, `rv3000`, `spot_lidar`, `map`, `air`, `basic`), and `Capabilities(robot_type, self_empty_dock, clean_edge)` answers the same features as plain booleans; the two hardware extras vary within a type, so they are asked rather than derived. Across all 611 models in the table, a model's type plus its two extras gives exactly the per-model answers (`capabilities_for_model` does the lookup for a known model string). + ### Room, Matrix and spot cleaning ```python diff --git a/sharklocal/compat.py b/sharklocal/compat.py index 938cbc7..c416047 100644 --- a/sharklocal/compat.py +++ b/sharklocal/compat.py @@ -906,3 +906,111 @@ def has_carpet_boost(self) -> bool: @property def has_carpet_detect(self) -> bool: return self.is_opp + + +# --------------------------------------------------------------------------- +# Robot types: the few groups every model falls into, for easy configuration +# --------------------------------------------------------------------------- + +#: Robot types, in the order a picker should list them. +ROBOT_TYPES = ("lidar", "lidar_carpet", "rv3000", "spot_lidar", "map", "air", "basic") + + +@dataclass(frozen=True) +class Capabilities: + """What a robot of one type offers, as plain yes/no answers. + + The two hardware extras that vary within a type - a self-emptying dock and + CleanEdge air jets - are asked separately. + """ + + robot_type: str + self_empty_dock: bool + clean_edge: bool + + @property + def _lidar(self) -> bool: + return self.robot_type in ("lidar", "lidar_carpet", "rv3000", "spot_lidar") + + @property + def has_map(self) -> bool: + return self.robot_type not in ("air", "basic") + + @property + def has_rooms(self) -> bool: + return self.has_map + + @property + def has_explore(self) -> bool: + return self._lidar + + @property + def has_pin_drop(self) -> bool: + return self._lidar and self.robot_type != "spot_lidar" + + @property + def has_ultra_clean(self) -> bool: + return self._lidar and self.robot_type not in ("spot_lidar", "lidar_carpet") + + @property + def has_recharge_resume(self) -> bool: + return self.has_map + + @property + def has_auto_empty(self) -> bool: + return self.self_empty_dock + + @property + def has_fan_jet(self) -> bool: + return self.clean_edge + + @property + def do_not_disturb(self) -> bool: + return self.robot_type != "basic" + + @property + def has_volume(self) -> bool: + return self.robot_type != "basic" + + @property + def has_underglow_lights(self) -> bool: + return self.robot_type == "rv3000" + + @property + def has_button_sounds(self) -> bool: + return self.robot_type == "rv3000" + + @property + def has_carpet_boost(self) -> bool: + return self.robot_type == "lidar_carpet" + + @property + def has_carpet_detect(self) -> bool: + return self.robot_type == "lidar_carpet" + + +def robot_type(profile: RobotProfile) -> Optional[str]: + """The robot type a known model belongs to, or None for a model the table lacks.""" + if profile.is_rv3000: + return "rv3000" + if profile.is_opp: + return "lidar_carpet" + if profile.is_spot_lidar: + return "spot_lidar" + if profile.family in ("MesaAir", "ValleyAir"): + return "air" # camera robots without a saved map + return { + "Three60": "lidar", + "LaserBot": "lidar", + "MapBot": "map", + "RandomBounce": "basic", + }.get(profile.classification or "") + + +def capabilities_for_model(model: str) -> Optional[Capabilities]: + """Capabilities straight from a cloud model string, when the table knows it.""" + profile = RobotProfile(model) + kind = robot_type(profile) + if kind is None: + return None + return Capabilities(kind, bool(profile.has_auto_empty), profile.has_fan_jet) diff --git a/tests/test_compat.py b/tests/test_compat.py index a943014..37abdf8 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -65,3 +65,52 @@ def test_a_model_missing_from_the_table_is_unknown_not_false(): def test_a_malformed_mask_is_ignored(): assert RobotProfile("RV3000-ZZZZZZZZ")._mask("AED") is None + + +from sharklocal.compat import ROBOT_TYPES, Capabilities, capabilities_for_model, robot_type + + +@pytest.mark.parametrize( + "model, kind", + [ + ("RV3000-001D5F7F", "rv3000"), + ("RV2500HOP", "lidar_carpet"), + ("RV2100AB", "spot_lidar"), + ("RV1000A", "map"), + ("RV750", "basic"), + ("RV1100AA", "air"), + ("RV2000DX", "lidar"), + ("RV2610BFCA", None), + ], +) +def test_every_model_falls_into_one_robot_type(model, kind): + assert robot_type(RobotProfile(model)) == kind + + +def test_every_table_model_has_a_type(): + assert {robot_type(RobotProfile(m)) for m in MODEL_FAMILIES} <= set(ROBOT_TYPES) + + +@pytest.mark.parametrize("model", sorted(MODEL_FAMILIES)) +def test_a_robot_type_answers_like_its_models(model): + # The simple configuration must agree with the app's per-model rules on + # every feature the app decides for that model. + profile = RobotProfile(model) + caps = capabilities_for_model(model) + for feature in ( + "has_map", "has_explore", "has_pin_drop", "has_ultra_clean", "has_recharge_resume", + "has_auto_empty", "has_fan_jet", "do_not_disturb", "has_volume", + "has_underglow_lights", "has_button_sounds", "has_carpet_boost", "has_carpet_detect", + ): + assert getattr(caps, feature) == bool(getattr(profile, feature)), (model, feature) + + +def test_unknown_model_has_no_capabilities(): + assert capabilities_for_model("RV2610BFCA") is None + + +def test_extras_are_asked_not_derived(): + caps = Capabilities("lidar", self_empty_dock=True, clean_edge=False) + assert caps.has_auto_empty and not caps.has_fan_jet + assert caps.has_pin_drop and caps.has_ultra_clean and caps.do_not_disturb and caps.has_volume + assert not (caps.has_underglow_lights or caps.has_carpet_boost) From 8fe82f757702967716cbbb3336090f8317cddabd Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 22:52:29 -0400 Subject: [PATCH 21/33] Version 0.3.0.dev8 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3ea366a..86ba71a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev7" +version = "0.3.0.dev8" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 65b7db63ca1a7ffbcd6221c38021e5b2de443407 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 23:17:56 -0400 Subject: [PATCH 22/33] Decode event-log entries streamed mid-job Status frames can carry the robot's event log mid-job (field 34, repeated PbDiagnoseInfo, JSON in its field 5), not only at the end with the persisted map. VacuumStatus.log_entries holds them; vacuum_map.parse_log_json is the shared parser. Fixture: a real frame from a spot clean. --- sharklocal/models.py | 2 ++ sharklocal/mqtt_client.py | 19 +++++++++++++++++++ sharklocal/vacuum_map.py | 11 +++++++++++ tests/fixtures/sharkiq_diagnose_frame.b64 | 1 + tests/test_codes.py | 16 ++++++++++++++++ 5 files changed, 49 insertions(+) create mode 100644 tests/fixtures/sharkiq_diagnose_frame.b64 diff --git a/sharklocal/models.py b/sharklocal/models.py index 38524e0..71342d3 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -73,6 +73,8 @@ class VacuumStatus: clean_edge: Optional[bool] = None # CleanEdge (fan jet) enabled carpet_detect: Optional[int] = None # codes.CARPET_DETECT_MODES relocation: Optional[int] = None # codes.RELOCATION_STATES + # Event-log entries streamed mid-job (field 34), when the frame has any. + log_entries: Optional[List["VacuumLogEntry"]] = None @property def is_cleaning(self) -> bool: diff --git a/sharklocal/mqtt_client.py b/sharklocal/mqtt_client.py index d0a29ed..b0549c2 100644 --- a/sharklocal/mqtt_client.py +++ b/sharklocal/mqtt_client.py @@ -120,9 +120,28 @@ def _decode_sharkiq_protobuf_v1( clean_edge=_toggle(raw.get(43)), carpet_detect=raw.get(44), relocation=raw.get(41), + log_entries=_diagnose_entries(fields.get(34)), ) +def _diagnose_entries(values: Optional[list]) -> Optional[list]: + """Field 34 (repeated PbDiagnoseInfo): its field 5 holds JSON log entries.""" + if not values: + return None + entries = [] + for value in values: + if not isinstance(value, bytes): + continue + try: + info = protobuf.decode_fields(value) + except Exception: # noqa: BLE001 - a malformed record is skipped + continue + for text in info.get(5, []): + if isinstance(text, bytes): + entries.extend(vacuum_map.parse_log_json(text)) + return entries + + def _submessage(fields: Dict[int, list], num: int) -> Dict[int, list]: """The fields of a nested message, or an empty dict.""" value = fields.get(num, [None])[0] diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py index 0e00392..843ed78 100644 --- a/sharklocal/vacuum_map.py +++ b/sharklocal/vacuum_map.py @@ -220,10 +220,21 @@ def _decode_log(data: Optional[bytes]) -> List[VacuumLogEntry]: text = _first(_fields(inner), 6) if not isinstance(text, bytes): return [] + return parse_log_json(text) + + +def parse_log_json(text: bytes) -> List[VacuumLogEntry]: + """Parse the robot's JSON log format: ``[{"key", "time": "s", "code"}]``. + + The persisted map carries the job's whole log this way; status frames can + carry entries mid-job too (field 34). + """ try: entries = json.loads(text.decode("utf-8", errors="replace")) except ValueError: return [] + if not isinstance(entries, list): + return [] log: List[VacuumLogEntry] = [] for entry in entries: if not isinstance(entry, dict): diff --git a/tests/fixtures/sharkiq_diagnose_frame.b64 b/tests/fixtures/sharkiq_diagnose_frame.b64 new file mode 100644 index 0000000..923b3b1 --- /dev/null +++ b/tests/fixtures/sharkiq_diagnose_frame.b64 @@ -0,0 +1 @@ +CLuxyQEQSiAGSggIA0BYSABYFFABggEhCgoNvJ9FPhWly1S+GFogACg3MCxYAGAAgAEAiAECmgEAkgLfDyrcD1t7ImtleSI6IkRUX1dBVEVSX1RBTksiLCJ0aW1lIjoiMHMiLCJjb2RlIjoiRFRfQ0FMX0lOSVRJQUxfUE9TRV9TVUNDRVNTIn0seyJrZXkiOiJEVF9XQVRFUl9MRVZFTCIsInRpbWUiOiIwcyIsImNvZGUiOiJEVF9DQUxfSU5JVElBTF9QT1NFX1NVQ0NFU1MifSx7ImtleSI6IkRUX1JFTE9DX1NUQVRFIiwidGltZSI6IjBzIiwiY29kZSI6IlJTX05PTkUifSx7ImtleSI6IkRUX1dFVF9DTEVBTl9NT0RFIiwidGltZSI6IjBzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfU1RBVEVfQ0hBTkdFIiwidGltZSI6IjBzIiwiY29kZSI6IlNZU19TVF9DTEVBTklORyJ9LHsia2V5IjoiRFRfQkFUVEVSWV9WQUxVRV9BVF9TVEFURV9UUkFOUyIsInRpbWUiOiIwcyIsImNvZGUiOiIxMDAifSx7ImtleSI6IkRUX1JFTE9DX1NUQVRFIiwidGltZSI6IjBzIiwiY29kZSI6IlJTX1VOUkVMT0MifSx7ImtleSI6IkRUX0NIQVJHSU5HX1RZUEUiLCJ0aW1lIjoiMHMiLCJjb2RlIjoiQ0hBUkdFX05PTkUifSx7ImtleSI6IkRUX01NX1NUQVRVUyIsInRpbWUiOiIwcyIsImNvZGUiOiJNTV9TVEFUVVNfTE9XTElHSFQifSx7ImtleSI6IkRUX1dBUk5JTkdfQ09ERSIsInRpbWUiOiIwcyIsImNvZGUiOiJXQVJOX01NX0xPV0xJR0hUIn0seyJrZXkiOiJEVF9HT19aT05FX05VTSIsInRpbWUiOiIwcyIsImNvZGUiOiI1In0seyJrZXkiOiJEVF9OT0dPX1pPTkVfTlVNIiwidGltZSI6IjBzIiwiY29kZSI6IjAifSx7ImtleSI6IkRUX0dPX1pPTkVfRU5BQkxFRF9OVU0iLCJ0aW1lIjoiMHMiLCJjb2RlIjoiMSJ9LHsia2V5IjoiRFRfUkVMT0NfU1RBVEUiLCJ0aW1lIjoiMHMiLCJjb2RlIjoiUlNfUkVMT0MifSx7ImtleSI6IkRUX1JFTE9DX1NUQVRFIiwidGltZSI6IjFzIiwiY29kZSI6IlJTX1VOUkVMT0MifSx7ImtleSI6IkRUX1JFTE9DQUxfU1VDQ0VTUyIsInRpbWUiOiIxcyIsImNvZGUiOiIxIn0seyJrZXkiOiJEVF9SRUxPQ19TVEFURSIsInRpbWUiOiIxcyIsImNvZGUiOiJSU19TVUNDRVNTIn0seyJrZXkiOiJEVF9JTVVfU09GVF9DQUxJX1NVQ0NFU1MiLCJ0aW1lIjoiMnMiLCJjb2RlIjoiMSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTFzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTJzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTJzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTZzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiNzdzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiNzdzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiODRzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiODVzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiODhzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiODlzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiOTZzIiwiY29kZSI6IkRUX1ZUX0RBUktfVElNRSJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiOTdzIiwiY29kZSI6IkRUX0NBTF9JTklUSUFMX1BPU0VfU1VDQ0VTUyJ9LHsia2V5IjoiRFRfWl9TSUdOQUwiLCJ0aW1lIjoiMTAwcyIsImNvZGUiOiJEVF9WVF9EQVJLX1RJTUUifSx7ImtleSI6IkRUX1pfU0lHTkFMIiwidGltZSI6IjEwMHMiLCJjb2RlIjoiRFRfQ0FMX0lOSVRJQUxfUE9TRV9TVUNDRVNTIn1dmAICoAICsAICwAIByAID6AIC \ No newline at end of file diff --git a/tests/test_codes.py b/tests/test_codes.py index bdf11b9..7651849 100644 --- a/tests/test_codes.py +++ b/tests/test_codes.py @@ -114,3 +114,19 @@ def test_feature_toggles(): assert status.clean_edge is True assert codes.CARPET_DETECT_MODES[status.carpet_detect] == "CD_AUTO" assert _decode(protobuf.encode_varint_field(43, 2)).clean_edge is False + + +def test_mid_job_log_entries_are_decoded(): + # A real frame from a spot clean: field 34 streams event-log entries. + status = _fixture_status("sharkiq_diagnose_frame.b64") + assert status.log_entries + assert all(e.key.startswith("DT_") for e in status.log_entries) + assert any(e.key == "DT_WATER_TANK" for e in status.log_entries) + + +def test_frames_without_log_entries_say_so(): + assert _fixture_status("sharkiq_status_frame.b64").log_entries is None + garbled = protobuf.encode_bytes_field(34, b"\xff\xff") + protobuf.encode_varint_field(34, 1) + assert _decode(garbled).log_entries == [] + not_a_list = protobuf.encode_bytes_field(34, protobuf.encode_bytes_field(5, b'{"key": "x"}')) + assert _decode(not_a_list).log_entries == [] From dd07c0462638e465078330104f615871dba5d38d Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 23:17:57 -0400 Subject: [PATCH 23/33] Version 0.3.0.dev9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 86ba71a..434b7e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev8" +version = "0.3.0.dev9" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 98150b5401fa8bb66a210890a3806822499c4557 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sat, 26 Sep 2026 23:30:57 -0400 Subject: [PATCH 24/33] Update the RV2610BFCA compatibility report Charging comes from the system state (the old footnote read the Wi-Fi state), state 13 is charging on the dock and 4 is paused, and the report now covers what was verified since: warnings, temperature, motor speeds, firmware versions, mid-job log entries, room/Matrix/spot cleaning runs and the saved PinDrop zone. Commands sent but not yet run on the robot are marked as untested. --- docs/compatibility/rv2610bfca.md | 41 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/compatibility/rv2610bfca.md b/docs/compatibility/rv2610bfca.md index 27e7204..2afa258 100644 --- a/docs/compatibility/rv2610bfca.md +++ b/docs/compatibility/rv2610bfca.md @@ -1,4 +1,4 @@ -# RV2610BFCA — Compatibility Matrix +# RV2610BFCA (Shark Matrix Plus) — Compatibility Matrix --- @@ -9,7 +9,7 @@ | Start cleaning | ❌ | ✅ | MQTT: `sharkiq_v1` | | Stop | ❌ | ✅ | MQTT: `sharkiq_v1` | | Return to dock | ❌ | ✅ | MQTT: `sharkiq_v1` | -| Explore / Map | ❌ | ❌ | | +| Explore / Map | ❌ | 🟡 ³ | MQTT: `sharkiq_v1` | | Get status | ❌ | ✅ | MQTT: `sharkiq_v1` | | Get event log | ❌ | ✅ ¹ | MQTT: `sharkiq_v1` | | Get robot ID | ❌ | ❌ | | @@ -20,8 +20,12 @@ | Evac & Resume on / off | ❌ | ✅ | MQTT: `sharkiq_v1` | | Clean rooms (incl. Matrix Clean) | ❌ | ✅ | MQTT: `sharkiq_v1` | | Spot clean | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Pause / resume | ❌ | 🟡 ³ | MQTT: `sharkiq_v1` | +| Do not disturb, volume | ❌ | 🟡 ³ | MQTT: `sharkiq_v1` | -> ¹ Via the event log embedded in the persisted map frame the robot publishes when it docks (`VacuumMap.log`), not via a request. +> ¹ Via the event log embedded in the persisted map frame the robot publishes when it docks (`VacuumMap.log`), not via a request. Entries are also streamed during a job (`VacuumStatus.log_entries`). +> +> ³ Sent in the same format as the verified commands, but not yet run on this robot. --- @@ -34,10 +38,19 @@ | Charging status | ❌ | ✅ ² | MQTT: `sharkiq_v1` | | Job active / deep clean | ❌ | ✅ | MQTT: `sharkiq_v1` | | Recharge & Resume / Evac & Resume | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Warnings active now | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Faults active now | ❌ | 🟡 ⁴ | MQTT: `sharkiq_v1` | +| Temperature, Wi-Fi state | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Suction / brushroll / side-brush motor speeds | ❌ | ✅ | MQTT: `sharkiq_v1` | | Live map (grid, cleaned path, robot pose with heading) | ❌ | ✅ | MQTT: `sharkiq_v1` | -| Persisted map (rooms, room raster, walls, doors, dock, job stats, event log) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Persisted map (rooms, room raster, walls, doors, dock, cleaned area, clean time, event log) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Firmware versions | ❌ | ✅ ⁵ | MQTT: `sharkiq_v1` | -> ² `charging` reads `true` in every status frame on this model, including mid-clean — `BatteryInfo.ChargingState` is a constant `3` here, so do not infer docked from it. +> ² Derived from the robot's system state (charging on the dock). The device-info field once read as a charging state is the Wi-Fi state: it reads `3` in every frame, including mid-clean. +> +> ⁴ Decoded from the status frame; no fault occurred during testing, so not yet seen with a value. +> +> ⁵ From the event log (`DT_VERSION_L01`, `DT_VERSION_MCU`). --- @@ -47,22 +60,26 @@ |------|:----:|:----:|--------------------| | `cleaning` | ❌ | ✅ | MQTT: `sharkiq_v1` | | `returning_to_dock` | ❌ | ✅ | MQTT: `sharkiq_v1` | -| `docking` | ❌ | ✅ | MQTT: `sharkiq_v1` | +| `docking` | ❌ | ❌ | | | `docked` | ❌ | ✅ | MQTT: `sharkiq_v1` | -| `idle` | ❌ | ✅ | MQTT: `sharkiq_v1` | +| `idle` | ❌ | ❌ | | +| `paused` | ❌ | ✅ | MQTT: `sharkiq_v1` | +| `error` | ❌ | ❌ | | | `exploring` | ❌ | ❌ | | --- ## Known Issues / Notes +- **Robot type:** `lidar` in `sharklocal.compat` terms, with a self-emptying dock. The retail SKU is not in the SharkClean app's model table (the app keys it by the robot's cloud model string); the firmware line (`P7`) is the app's Three60 lidar platform. - **REST API:** Port 443 is closed/refused. Port 80 is open, but `/get/status` and `/get/wifi_status` return `404 Not Found`, and `/` returns a forbidden HTML page. -- **MQTT:** Uses standard `sharkiq_v1` protobuf format. Status requests, passive monitoring, and basic commands work. -- **Observed MQTT status:** `mode=docked`, `battery_level=100`, `charging=true`. -- **Command test:** `start_cleaning` returned `True` and status changed to `cleaning` within about 2 seconds. `stop` returned `True` and status changed to `returning_to_dock` within about 2 seconds. `go_home` returned `True`; the robot reported `docked` about 28 seconds after the first return-to-dock command. -- **Lifecycle** (firmware `V6.6.10-P7.N3308.17.0`): `docked (14)` → `cleaning (6)` → `returning_to_dock (7)` → `docking (13)` → `docked (14)`; the persisted map frame is published on the `13 → 14` edge. Mode `4` (`idle`) is reported when stopped off the dock or lifted. +- **MQTT:** Uses standard `sharkiq_v1` protobuf format. Status requests, passive monitoring, and commands work. +- **Command test:** `start_cleaning` returned `True` and status changed to `cleaning` within about 2 seconds. `stop` returned `True` and status changed to `returning_to_dock` within about 2 seconds. `go_home` returned `True`; the robot reported `docked` about 28 seconds after the first return-to-dock command. Room clean, Matrix room clean and spot clean each cleaned the right area and returned to the dock on their own. +- **Lifecycle** (firmware `V6.6.10-P7.N3308.17.0`): battery full on the dock (`14`) → cleaning (`6`) → returning (`7`) → charging on the dock (`13`) → battery full (`14`). The persisted map frame is published as it reaches the dock. State `4` (paused) is reported when stopped off the dock or lifted. - **Suction:** accepted while docked (the app only greys the control out). Echoed once on change; no status field. - **Battery:** counts down through a job and snaps back to 100 within seconds of docking. - **Maps:** a ~27 KB live frame every ~4 s during a job (0.06 m grid, cleaned path, pose log with heading); a ~92 KB persisted frame on docking with rooms, room-id raster, wall/door polylines, dock pose, job start time and the event log. Cleaned floor is marked `0x00` in the grid (every point of the cleaned path lands on one), and the log's `DT_*_CLEAN_TIME` entries give the time spent cleaning. The persisted frame's field 11 also carries two counters that look like a duration and a cell count, but they barely move between different jobs, so they are not decoded. +- **Spot clean:** the robot keeps the spot square in its saved map as a room named `PinDrop`, marked as the room the last job cleaned. `VacuumMap.named_rooms` leaves it out. +- **Warnings:** a dark room reports `WARN_LOW_LIGHT` live and logs `WARN_MM_LOWLIGHT` for the job. - **Room commands re-upload the room definition**, names included. Build them from the latest persisted map. -- **Not observed:** pause, an in-job error field (no fault occurred during testing), `exploring`. +- **Single map only:** every map seen carried one floor; the app's rules give multi-floor maps only to newer lines. From 1a44b13cf9dbad96d457dcae6a8c68cef466b6f2 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 10:41:33 -0400 Subject: [PATCH 25/33] Treat a REST timeout as unreachable Only a refused connection became ConnectError, so a robot that dropped packets instead (aiohttp ConnectionTimeoutError, seen at the nightly network restart) escaped raw: the MQTT fallback never ran and Home Assistant failed the config entry for good instead of retrying it. Every aiohttp connection error and a bare timeout now count as unreachable. --- sharklocal/rest_client.py | 6 +++++- tests/test_rest_client.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/sharklocal/rest_client.py b/sharklocal/rest_client.py index 7561200..c9451b9 100644 --- a/sharklocal/rest_client.py +++ b/sharklocal/rest_client.py @@ -102,7 +102,11 @@ async def call(self, action: str) -> Any: except Exception: return True - except aiohttp.ClientConnectorError as exc: + except (aiohttp.ClientConnectionError, TimeoutError) as exc: + # Not just a refused connection: a robot that drops packets (seen + # during the nightly network restart) times out instead, and that + # is just as unreachable. Anything else escaping here skips the + # MQTT fallback and fails the caller's setup outright. raise ConnectError( f"Cannot connect to vacuum at {self.host}:{self.mapping.port}" ) from exc diff --git a/tests/test_rest_client.py b/tests/test_rest_client.py index 18be7f1..1f91df6 100644 --- a/tests/test_rest_client.py +++ b/tests/test_rest_client.py @@ -222,6 +222,30 @@ async def test_call_connect_error_on_connector_error(rest_mapping): await client.call("get_status") +@pytest.mark.parametrize( + "error", + [ + aiohttp.ConnectionTimeoutError("Connection timeout to host"), + aiohttp.ServerDisconnectedError(), + TimeoutError(), + ], + ids=["connection-timeout", "server-disconnected", "total-timeout"], +) +async def test_call_connect_error_on_unreachable_host(rest_mapping, error): + client = RESTVacuumClient("host", rest_mapping) + with patch.object(client, "_get_session") as mock_get: + session = AsyncMock() + session.request = MagicMock( + return_value=MagicMock( + __aenter__=AsyncMock(side_effect=error), + __aexit__=AsyncMock(return_value=False), + ) + ) + mock_get.return_value = session + with pytest.raises(ConnectError): + await client.call("get_status") + + # --------------------------------------------------------------------------- # call() — CommandError on HTTP error # --------------------------------------------------------------------------- From b2cf993103c3e770b2680a4688658ef211da3089 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 10:41:33 -0400 Subject: [PATCH 26/33] Version 0.3.0.dev10 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 434b7e1..501ff03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev9" +version = "0.3.0.dev10" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 792ddf6b6e641636a4e6c604bac229c7ab87485c Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 11:08:55 -0400 Subject: [PATCH 27/33] Ask the robot for its saved map PbInput field 39 = 1 (FloorControlT FL_CTR_LOAD_ALL) makes a docked robot publish its persisted map within a second: floor type 4, carrying the last job's path and summary but not the event log or the top-level report flag. That recovers a job whose end-of-job frame was missed, e.g. while Home Assistant was restarting. VacuumMap.persisted now also covers floor type 4, VacuumMap.report marks the end-of-job frame, job_minutes comes from the summary (PbMapSummary.currentCleanTime) and backs clean_time when there is no log, and a requested map never replaces the report of the same job. Fixture: the real reply after a whole-home job. SIG_EXT_REQ_ALL_MAP (field 29 = 33) was tried too and draws no reply. --- docs/compatibility/rv2610bfca.md | 1 + docs/data-models.md | 4 +- docs/mappings.md | 2 +- docs/vacuum-client.md | 2 + sharklocal/client.py | 26 +++++++- sharklocal/mappings/mqtt/sharkiq_v1.yaml | 6 ++ sharklocal/models.py | 20 ++++-- sharklocal/vacuum_map.py | 27 ++++++-- .../fixtures/sharkiq_requested_map_frame.b64 | 1 + tests/test_client.py | 41 ++++++++++++ tests/test_vacuum_map.py | 65 +++++++++++++++++++ 11 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 tests/fixtures/sharkiq_requested_map_frame.b64 diff --git a/docs/compatibility/rv2610bfca.md b/docs/compatibility/rv2610bfca.md index 2afa258..6b3f77f 100644 --- a/docs/compatibility/rv2610bfca.md +++ b/docs/compatibility/rv2610bfca.md @@ -44,6 +44,7 @@ | Suction / brushroll / side-brush motor speeds | ❌ | ✅ | MQTT: `sharkiq_v1` | | Live map (grid, cleaned path, robot pose with heading) | ❌ | ✅ | MQTT: `sharkiq_v1` | | Persisted map (rooms, room raster, walls, doors, dock, cleaned area, clean time, event log) | ❌ | ✅ | MQTT: `sharkiq_v1` | +| Persisted map on request (`request_map`: the same, less the event log) | ❌ | ✅ | MQTT: `sharkiq_v1` | | Firmware versions | ❌ | ✅ ⁵ | MQTT: `sharkiq_v1` | > ² Derived from the robot's system state (charging on the dock). The device-info field once read as a charging state is the Wi-Fi state: it reads `3` in every frame, including mid-clean. diff --git a/docs/data-models.md b/docs/data-models.md index 1f97ae5..522ddd6 100644 --- a/docs/data-models.md +++ b/docs/data-models.md @@ -122,7 +122,8 @@ class VacuumMap: grid: MapGrid path: list[MapPoint] # Cleaned path so far, ~1 point per 0.12 m poses: list[MapPose] # Pose log with heading, ~1 pose per 0.22 m - persisted: bool # True for the end-of-job frame + persisted: bool # True for the saved map: the end-of-job frame or a requested one + report: bool # True for the end-of-job frame only (the one with the event log) map_id: str | None # e.g. "3CDBBC59" — persisted only name: str | None # persisted only dock: MapPose | None # persisted only @@ -130,6 +131,7 @@ class VacuumMap: features: list[MapFeature] # persisted only: wall "edge" and "door" polylines log: list[VacuumLogEntry] # persisted only: the robot's event log job_started: int | None # Unix epoch seconds — persisted only + job_minutes: int | None # whole minutes of the job, docking included — persisted only raw: dict @property diff --git a/docs/mappings.md b/docs/mappings.md index 64e2e72..d607015 100644 --- a/docs/mappings.md +++ b/docs/mappings.md @@ -63,7 +63,7 @@ The table below shows which features are available per transport for the built-i > > ² Built at runtime from the persisted map's room definition (`VacuumClient.clean_rooms()` / `clean_spot()`), not a fixed mapping action. > -> ³ Carried by the persisted map frame the robot publishes when it docks, available through monitoring (`VacuumStatus.map`), not by request. +> ³ Carried by the persisted map frame the robot publishes when it docks, available through monitoring (`VacuumStatus.map`). `VacuumClient.request_map()` makes the robot publish it again, without the event log. **Recommendations:** - Configure **both transports** (`rest_mappings` + `mqtt_mappings`) to get full feature coverage: REST for device info and explore; MQTT for real-time monitoring, maps, rooms and settings. diff --git a/docs/vacuum-client.md b/docs/vacuum-client.md index cc360ce..5bdeca2 100644 --- a/docs/vacuum-client.md +++ b/docs/vacuum-client.md @@ -99,6 +99,7 @@ print(vacuum.via) # "REST", "MQTT", or "NONE" depending on what responded | `get_device_info()` | `GET /get/robot_id` | *(not in MQTT mapping)* | | `get_wifi_status()` | `GET /get/wifi_status` | *(not in MQTT mapping)* | | `find_robot()` | *(not in REST mapping)* | `find_robot` (command) | +| `request_map()` | *(not in REST mapping)* | `request_map` (command; the map arrives through monitoring) | | `set_suction(level)` | *(not in REST mapping)* | `set_suction_eco` / `_normal` / `_max` (command) | | `set_recharge_resume(enabled)` | *(not in REST mapping)* | `recharge_resume_on` / `_off` (command) | | `set_evac_resume(enabled)` | *(not in REST mapping)* | `evac_resume_on` / `_off` (command) | @@ -161,6 +162,7 @@ await vacuum.start_monitoring() ```python await vacuum.find_robot() # play the locate sound +await vacuum.request_map() # re-send the saved map (last job, no event log) await vacuum.set_suction(SuctionLevel.MAX) # "eco" | "normal" | "max" await vacuum.set_recharge_resume(True) await vacuum.set_evac_resume(False) diff --git a/sharklocal/client.py b/sharklocal/client.py index c288384..8f0bfc2 100644 --- a/sharklocal/client.py +++ b/sharklocal/client.py @@ -173,6 +173,16 @@ async def explore(self) -> bool: """Begin a mapping/exploration run.""" return await self._execute("explore") + async def request_map(self) -> bool: + """Ask the robot to publish its persisted map (MQTT only). + + The reply arrives through monitoring like any other map frame, and + carries the last job's path and summary but no event log — a way to + recover a job whose end-of-job frame was missed. Sent while docked; not + tried mid-job. + """ + return await self._execute("request_map") + async def get_status(self) -> VacuumStatus: """Return the current vacuum status.""" return await self._execute("get_status") @@ -409,7 +419,7 @@ async def start_monitoring(self) -> None: async def _on_monitor_status(self, status: VacuumStatus) -> None: """Cache the latest status and persisted map, then forward to the callback.""" self.last_status = status - if status.map is not None and status.map.persisted: + if status.map is not None and status.map.persisted and not self._same_job_poorer(status.map): self.last_map = status.map callback = self._status_callback if asyncio.iscoroutinefunction(callback): @@ -417,6 +427,20 @@ async def _on_monitor_status(self, status: VacuumStatus) -> None: else: callback(status) # type: ignore[misc] + def _same_job_poorer(self, vacuum_map: VacuumMap) -> bool: + """``True`` if *vacuum_map* would replace a report of the same job. + + A requested map repeats the last job without its event log, so it only + supersedes the cached map when it records a different job. + """ + current = self.last_map + return ( + not vacuum_map.report + and current is not None + and current.report + and current.job_started == vacuum_map.job_started + ) + async def stop_monitoring(self) -> None: """Stop background status monitoring if it is running.""" if self._monitor_stop: diff --git a/sharklocal/mappings/mqtt/sharkiq_v1.yaml b/sharklocal/mappings/mqtt/sharkiq_v1.yaml index 012921e..8c02c25 100644 --- a/sharklocal/mappings/mqtt/sharkiq_v1.yaml +++ b/sharklocal/mappings/mqtt/sharkiq_v1.yaml @@ -72,6 +72,12 @@ actions: type: command payload: "gAEm" + # Field 39 = 1: FloorControlT FL_CTR_LOAD_ALL. The robot answers with its + # persisted map (floor type 4) on the status topic. + request_map: + type: command + payload: "uAIB" + # Field 25 = 104 find_robot: type: command diff --git a/sharklocal/models.py b/sharklocal/models.py index 71342d3..38d10ac 100644 --- a/sharklocal/models.py +++ b/sharklocal/models.py @@ -201,6 +201,9 @@ class VacuumMap: path: List[MapPoint] = field(default_factory=list) poses: List[MapPose] = field(default_factory=list) persisted: bool = False + # The end-of-job frame, which alone carries the event log. A persisted map + # sent on request is not a report. + report: bool = False map_id: Optional[str] = None name: Optional[str] = None dock: Optional[MapPose] = None @@ -208,6 +211,7 @@ class VacuumMap: features: List[MapFeature] = field(default_factory=list) log: List[VacuumLogEntry] = field(default_factory=list) job_started: Optional[int] = None # Unix epoch seconds + job_minutes: Optional[int] = None # whole minutes, docking included raw: Dict[str, Any] = field(default_factory=dict) @property @@ -232,18 +236,21 @@ def cleaned_area(self) -> float: @property def clean_time(self) -> Optional[int]: - """Seconds spent cleaning in the last job, from the robot's event log. + """Seconds spent cleaning in the last job. - The sum of the log's ``DT_*_CLEAN_TIME`` entries (normal, wall-follow - and escape cleaning; docking is logged separately). ``None`` when the - log carries none — live frames have no log. + The sum of the event log's ``DT_*_CLEAN_TIME`` entries (normal, + wall-follow and escape cleaning; docking is logged separately). A map + without a log (one sent on request) falls back to the job summary's + whole minutes, which include docking. ``None`` when neither is there. """ times = [ int(entry.code) for entry in self.log if entry.key.endswith("_CLEAN_TIME") and entry.code.isdigit() ] - return sum(times) if times else None + if times: + return sum(times) + return self.job_minutes * 60 if self.job_minutes else None def to_dict(self) -> Dict[str, Any]: """Return a JSON-serialisable form of the map, for storage. @@ -279,6 +286,8 @@ def from_dict(cls, data: Dict[str, Any]) -> "VacuumMap": path=[MapPoint(**p) for p in data.get("path", [])], poses=[MapPose(**p) for p in data.get("poses", [])], persisted=data.get("persisted", False), + # Stored before this field existed: only reports were persisted then. + report=data.get("report", data.get("persisted", False)), map_id=data.get("map_id"), name=data.get("name"), dock=MapPose(**dock) if dock else None, @@ -297,6 +306,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "VacuumMap": ], log=[VacuumLogEntry(**e) for e in data.get("log", [])], job_started=data.get("job_started"), + job_minutes=data.get("job_minutes"), ) diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py index 843ed78..9e1acb7 100644 --- a/sharklocal/vacuum_map.py +++ b/sharklocal/vacuum_map.py @@ -8,6 +8,10 @@ * **Persisted frame** (~92 KB, once, as the robot reaches the dock; flagged by top-level field ``3 = 1``) — the saved map with rooms, wall and door vectors, the dock pose, job statistics and the robot's own event log. +* **Requested map** (~97 KB, on request: ``VacuumClient.request_map()``) — the + same saved map, including the last job's path and summary, but without the + event log or the field ``3`` flag. The floor's own type (``7.4``) says it is + the persisted map. Coordinates are metres in the map frame; ``cell = (m - origin) / resolution``. Everything here was reverse-engineered from live captures, so field meanings @@ -36,6 +40,11 @@ FIELD_MAP = 7 FIELD_PERSISTED_FLAG = 3 +# Field 7.4 is the floor's MapTypeT (the app's schema): 1 MT_RT_UPLOAD (live), +# 2 MT_REPORT (the end-of-job frame), 4 MT_PERSIST (the saved map, as sent on +# request). +MAP_TYPE_PERSIST = 4 + # Room-record fields the app adds to a room selected for Matrix (deep) Clean. _DEEP_CLEAN_FIELDS = ((7, 2), (9, 0), (10, 2)) # Spot Clean is a synthetic room of this name with a square polygon around the pin. @@ -84,13 +93,15 @@ def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: return None grid = _decode_grid(m[5][0]) - persisted = _first(fields, FIELD_PERSISTED_FLAG) == 1 + report = _first(fields, FIELD_PERSISTED_FLAG) == 1 + persisted = report or _first(m, 4) == MAP_TYPE_PERSIST vacuum_map = VacuumMap( grid=grid, path=_decode_points(_first(m, 8)), poses=_decode_poses(_first(m, 10)), persisted=persisted, + report=report, map_id=_text(_first(m, 3)), name=_text(_first(m, 2)), dock=_decode_pose_f32(_first(m, 7)), @@ -106,14 +117,16 @@ def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: if isinstance(room_ids, bytes) and len(room_ids) == len(grid.cells): grid.room_ids = room_ids - # Field 11 is a job summary; .1 is the job's start epoch. Its .3 and .6 - # looked like duration and cleaned cells on one capture, but they barely - # moved between two different jobs (335 -> 306, 3947 -> 3978) while the - # robot's own log said 101 s and 70 s of cleaning, so they are not decoded: - # cleaned area comes from the grid and clean time from the log. + # Field 11 is PbMapSummary (the app's schema): .1 beginTime, .2 + # currentCleanTime, .3 currentArea, .4-.6 lifetime totals. .2 is whole + # minutes of the job, docking included: 3 for a job that ran 216 s and + # logged 101 s of cleaning. .3 is not decoded: cleaned area comes from the + # grid (2709 against 25.6 m2 of cleaned cells suggests 0.01 m2, unconfirmed). stats = _first(m, 11) if isinstance(stats, bytes): - vacuum_map.job_started = _first(_fields(stats), 1) + summary = _fields(stats) + vacuum_map.job_started = _first(summary, 1) + vacuum_map.job_minutes = _first(summary, 2) # Field 20.3.6 is the robot's event log as a JSON array of {key, time, code}. vacuum_map.log = _decode_log(_first(m, 20)) diff --git a/tests/fixtures/sharkiq_requested_map_frame.b64 b/tests/fixtures/sharkiq_requested_map_frame.b64 new file mode 100644 index 0000000..55b4f3a --- /dev/null +++ b/tests/fixtures/sharkiq_requested_map_frame.b64 @@ -0,0 +1 @@ +CNrkZBBKIA461/UFCLfXAxIHdW5uYW1lZBoIM0NEQkJDNTkgBCq/ogENj8J1PRIKDUfhBsEVHoXLvxjAASBsMoCiAUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw9kZGRkZGRLS0tLD0sPDw8PD2RkZA9kDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD0tLS0tLD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkD2RkZGRkDw8PDw9kZGRkZGRkZGRkZGQPDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLDw9kZGRkZGRkZA9kZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkD2RkDw8PDw8PDw8PDw8PD2QPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PD2RkZGRkZGRkZGQPZA8PS0tLSw8PDw8PS0tLS0tLS0tLDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkXGRkZGRcXFxcXFxcXFxcXFwPZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEsPDw8PD2RkZGRkZGRkZGRkZGRkS0tLSw9kZA8PS0sPS0tLS0tLD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAGRkZGRkZGRkZGRkXGRkZAAAAABkZFxcZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PZGRkZGQPZA8PDw8PD2RkS0tLSw9kZGRLS0sPS0tLS0tLD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGQPDw8PZA8PDw8PD2RkD2RkDw9kD0sPDw8PZGRLSw8PS0tLS0sPDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWmQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PD2QPZGQPDw8PZGRkDw9kZGRkZGQPD2RLSw8PS0tLS0tLDw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPS0tLDw9LS0tLS0tLD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PZA8PDw8PDw8PDw9kZGRkZGQPZGRLSw8PS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPS0tLDw9LS0tLS0tLDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD2QPDw8PD2RkZA9kZGRkZGRkD2RkD2QPS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPS0tLDw9LS0tLS0tLDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PD2RkZGQPDw8PD2RkZA8PDw8PDw8PD2RkDw8PS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLD0tLS0tLS0tLDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZGQZD2QPGRkZGQ8PDw8PDw8PDw9kZA8PS0tLS0tLDw8PD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLD0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLZA8PDw8PDw8PS0tLS0tLS0tLS0tLS0tLS0sPZA9kZA8PDw8KChkZZGQPDwoKCgoKCg8PDw8PDw8PZA8PS0tLS0sPDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLD0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkS0tLS0tLS0tLSw8PDw8PDw8PDw8PDw9LS0tLS0tLSw9LD2RkDw8PZAoKCgoKGRkZCgoKCgoKCgoPDw9kZA9kZGQPS0tLSw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPD0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD2QPDw8PZA9kGQoKCgoKCgoKCgoKCgoKCgoKDw9kDw9kZGRkZGRkZGRkDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPD0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPZA8PZA9kDw8PZAoKCgoKCgoKCgoKCgoKXFxWVlYPXGRkZGQAWgBcXFxcXA9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPD0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0sPS0tLS0sPD0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDwoKCgoKCgoKCgoKCgpcXAAAAABWXFxkZAAAAAAAAAAAD1xcZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPS0tLS0tLS0tLSw9LDw8PZGQPDw8PDw9kZGRkZGRkZGRkZGRkD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkZA8KCgoKCgoKCgoKChlcAAAAAAAAAAAAAAAAAAAAAAAAAFpkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPS0sPDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkD1xcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkZA8PCgoKCgpkZGRkZGQKAAAAAAAAAAAAAAAAAAAAAAAAAFxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0sPDw8PDw8PD2RkZGRkZA9kDw9WVmRkDw8PZGRkDw8PZGRkZGRkZGRkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PZGRkZA8PDwoKCmRkCgoKGWRkAAAAAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PS0sPDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPSw8PD2RkZGQPDw8PGRlkDw8ZZGRkAAAAAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PSw8PD2QPD2RkZGRkZGQPZA8AAAAAAGQAAAAAAAAAAA8PZA8PDw9kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA8PD2RkZA8PDw8PGWRkDw8PZGRcXFxcAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PS0sPD2RkZA8PDw9kDw8PAAAAAAAAAAAAAAAAAAAAAABkZA8PD1xkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PD2RkDw8PDw8PDw9kZA8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw9LS0tkZA8PD2RkD2RkDw8AAAAAAAAAAAAAAAAAAAAAAABcZA8PDw9cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGQPD2QPZA8PDw8PDw9kZGRWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFxkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLSw8PDw9LS0sPDw8PD2RkZGRkDwAAAAAAAAAAAAAAAAAAAAAAAABkZA8PD2RcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw9kZA8PDw8PDw8PVmRkAAAAAAAAAAAAAAAAAABWVlZkZGRcDw9kD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9LS0tLS0tLS0tLS0tLS0tLSw8PDw9LZA8PD2RkD2RkZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGRkXGRWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD1xcAAAAAGQAAAAAAAAAAAAAAAAAAAAAVlZWZGQPDw9kZEtLS0tLS0tLS0tLS0tLS0tLSw8PD0tLDw8PD0tkDw8PD2RkD2RkZA9LSw8PDw9LS2QPD2QPDw9kXGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRcXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA9kDw8PZGRkZFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFxcZGQPD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZA8PZGRkZGRkZGRkZGQPDw9kDw8PZGRkZGRkD2RkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PZGRkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXFxcZGQPD2RkD2RkZGRkZGRkZGRkZGRcZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRcXGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw8PWgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkXA8PD2QPD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw8PWgAAAAAAAAAAAAAAAAAAAAAAAAAAAFxcDw8PZGRkD2RkZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw9WXAAAAAAAAAAAAAAAAAAAAAAAAAAAAFwPDw8PZGRkZA8PD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw9kXAAAAAAAAAAAAAAAAAAAAAAAAAAAXFwPDw8PZGRkZGRkDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PXAAAAAAAAAAAAAAAAAAAAAAAAGRkZGRkZGRkZGRkXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8PDw8PDw8PXGQAAAAAAAAAAAAAAAAAAAAAAGRkZGRkZGRkZGRkXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PXFwAAAAAAAAAAAAAAAAAAAAAAGQPZGQPZGRkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw8PWgAAAAAAAAAAAAAAAAAAAAAAWmQPDw8PD2RkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw8PXAAAAAAAAAAAAAAAAAAAAABaXGRkXA8PDw9kZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PXAAAAAAAAAAAAAAAAAAAAAAAAABkXGRkZA9cZFoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw8PDw9WXAAAAAAAAAAAAAAAAAAAAAAAAAAAZFxkZA9cXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8PDw8PDw9WXAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw9WXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PDw8PDw9WZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFxcZFxcXFxcXGRcXGRkZFxcXFxkZGRcAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PDw8PDw9cZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFYAAABcXFxcXFxcD2QPDw9kDw8PDw9WXAAAAAAAAAAAWWRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PD2RkDw9cZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWgAAAAAAAAAAAAAAAAAAAABcXFxcXFwAAFxcXFxkZGRkZGRkZGQPZGRkZGRkZFxkZFxcXFxcXGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PZGRkDw9WZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tkZGRkZGQPZGQPZGRkZGRkZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PZGRkDw9WZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkZGQAAAAAAAAAAAAAAABcZFxcXFxcAABaWgBcXFpcXGRkZGRkZGRkZGRkDw8PS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLD0tkS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPD2RkDw9WZGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXGRkZGQAAAAAAAAAAAAAAABcZGRkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRaWlpaWlpaXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcGWRkZGRkAAAAAAAAAAAAAFpcZGRkZGRkZEtLS0tLS2RLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVlZWAAAAAAAAAABcD2RkZFxkAAAAAAAAAAAAAABcAABkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1pkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFZWAAAAAAAAAFZcD2RkZFxcZAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFkAAABWVg8PD2RkZFxcAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFpaDw8PD2RkZFxcAAAAAAAAAAAAAAAAAAAAZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXA8PDw8PZGRkD1xkAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXFxcDw8PZGRkD1xkAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFpWXFxcVg8PZGRkD1xkAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFpaAAAAWg8PZGRkD1xkAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFxWAAAAAFYPZGRkD2QAAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVlYAAAAAAFZcZGRkD2QAAAAAAAAAAAAAAAAAAAAAZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAAAAAAABcZGRkD2QAAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZAAAAAAAAAAAAAAAAAAAAAABcZGRkD2QAAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaXGRkAAAAAAAAAAAAAAAAAAAAAABcZGRkWlwAAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkZAAAAAAAAAAAAAAAAAAAAABcZGRkXAAAAAAAAAAAAAAAAAAAAAAAZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkZAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkAFYAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAWmQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAXGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAXGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAABZZGRcS0tLSw8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAZGRkZGRkZGRkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkAAAAAAAAAAAAAAAAAAAAAAAAZAAAZGRkZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGQPDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFwPZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1xkAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkAAAAAAAAAAAAAAAAAAAAAABaXFxcAAAAZA8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZFxcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaZGRkAAAAAAAAAAAAAAAAAAAAAABkZGRkZA9cXA8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaXGRkAAAAAAAAAAAAAAAAAAAAAFxkZGRkZGRkZGQPZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PD1xcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaXGRkAAAAAAAAAAAAAAAAAAAAAGRkZA8PZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA8PDw9cWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaXGRkAAAAAAAAAAAAAAAAAAAAAABkZFwPDw8PD2RkDw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPDw8PVlpaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaXGRkAAAAAAAAAAAAAAAAAAAAAAAAAGRkDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkDw8PDw9aWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXGRkAAAAAAAAAAAAAAAAAAAAAAAAAFYPDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PD1paAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcXGRkAAAAAAAAAAAAAAAAAAAAAAAAAFYPDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw9aWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PWlpaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkZAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9kZGRkZGRkDw9aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcD2RkZGRkZGRkZGRkZFpaXFpaWloPDw8PDw8PZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkS2RkZA8PWloAAAAAAAAAAAAAAAAAAAAAAAAAAABWVlYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFwAAFlcD2RkZA9WD1YPDw8PD2RkZGRkZGRkZGRkZGRkZA9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tLS2RkZGQPD1paAAAAAAAAAAAAAAAAAAAAAAAAAABcVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFxWAA9cD2RkZA8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9LS0tLS0sPZGRkDw9aWgAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcD2RkDw8PDw8PDw8PDw8PDw8PZA8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD0tLS0tLSw8PZGRkZGQPWloAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcD2RkDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA9kZA8PD1paAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcD2RkDw8PDw8PDw8PDw8PDw9kDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PD0tkZGQPDw8PXFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZXGRkD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS0sPZGQPDw8PD1xcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw9cXFoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw9cXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkDw8PDw8PDw8PDw8PDw8PZA8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkD1xcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZFwPAAAAAAAAAAAAAAAAAFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFwAAABcXGRkD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPAAAAAAAAAAAAAAAAAFxcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFxcDw8PXGRkDw8PZA8PDw8PDw8PDw8PDw8PDw8PDw8PZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGQPAAAAAAAAAAAAAAAAAFYPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PD2RkD2RkZGRkZGRkZA8PZA8PZA8PZA8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGRkZABcXFwAAAAAAAAAVg9cXFwAAGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS2RkS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tkZGQPD2RkZGRkZGRkZGRkZGRkZA8PS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZA9kZGRkD0tLS0tLS2RkZGRkZEtLZEtkZA9kS2RkZEtkZEtLS0tLS0tLS0tLS2RkZEtLS0tLDw9LS0tLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0oPDYWr974VTDT0vR0/DjC8WJMfeAGAAQGVARdfhz+9ATQVfT86Dw2CcAG/FSn6470dXOgwvUKmNA1vEgM7EAIanDSw/9X/rv/KAJX/3AB3/9MA//67AML+sQCm/r4AcP7bAFT+5QA1/uAA2P3iAF395QAB/ecAhfzlACr86QAM/OcA7fvoAK/75gBT++EA1vrjAHr65wBb+ucAaPoDAY/6NAGl+kkBuvpeAc36dQHa+pIB6/qrAf36xAEG++EBB/sAAgn7HwIQ+z0Ca/s/Aqj7QALD+1AC2ftlAvP7vgIR/PQCEfwSAw38MAMD/E0D+PtqA/j7iQP8+6cD8PvjA+37HwTr+3sE4fuYBM37rwSy+7wErPvbBK37VwWw+3UFsPvzBa/7Twav+20Gp/urBq37ygax++gGx/v9Bub7+gYj/PoGYfz0Bp789Aa8/PgG2/z2Bvr88gYY/fkGNv34BjH92QYH/a0G9/ySBvH8dAYN/RwGHP0BBjD96wVK/doFpP3IBeH9ygXr/a4Fzf2nBa/9pQWR/akFcv2iBVj9kwVF/XsFNP0/BTj9IQVA/QQFRP3lBDz9igQ5/WsEPf1NBD79LQRC/bMDRP1VAzj9OgMZ/TkD+/w0A+H8IgPL/A0DvfzxArn81AK+/LUCvfyXAsX8egLV/GEC7/xPAgz9QwJI/T0CxP09AiD+OgKc/jkCtf4lAs7+FQLs/g0C//70AR3/9QE2/wgCKQADAoUABAIfAQQCmAECAhMCAwIgAugBGwLKARYCjAENAlABHwL1ACEC1wAnAnsAJQJdAEICZQBZAnkASAKSADECpQAxAoYALgJnABACYgD1AW8A/AGMAOsBpQDOAa8AsQG3AJIBswB3AaQAOAFeABkBXgDoADkA4wD+//UA5f/WAOD/twDh/3oA4f98AP//QAD+/yAA+f8BAPf/xP/2/67/CQCu/ycAxv86ACMANADgAC0A/gAtAP0ASwANAWYA7wBtADQAbwDY/3EAxv+JANT/pADz/6oATgCrAIABpACdAawAtAHBANIByQDvAcEABwKuACcCrgAJAqsAMAGzAHUAsAC7/6sAnf+sAJL/jwCX/3EAev95AGP/jwBW/6oAWv/oAD3/8wDd/vIAvv72AKD+8wAp/ucAj/3pAHD95wAV/fAA9/zyAHz86wA9/OkAIPzvAAH88ABn++kAKvvmAO367ABw+uoAUfrsADP68ABN+gABafoMAYf6EQGk+gcBvPr1ALj6FAHH+i4BgvsdAXz8GwE4/R4B+f0eAe/+IgGu/yUBZwAmAWQBJAEhAiYBEQJCAfcBVwHfAWgBQwFlAUsAZAFv/2IB0/5eATf+XAEZ/l8B+v1eAf78XAFD/F8BhvtZAQr7XAHs+mAB0/pxAdj6jwH1+pgBE/uaAQv8kwHE/JUBgv2UAXv+mQEk/50B8f+eAecAmwGmAaABIQKhATYCuQE1AtgBHgLtAQgC2AHrAcsBzgHFAa8BxgGRAcwBdQHZAVsB6wFOAQYCUwHmAVQByAEWAc4BnADbASEA4gGJ/90BLv/ZAfP+zAHW/sIBvv6wAb3+zwGi/uEB5/3cAev81gEu/NcBcfvSATP70QEX+9sBDfv4ASL7DgJB+xACf/sQAnr8DAIy/Q0Cjv0NAsv9DgKt/RYCM/0hArn8HQKr/DkClvxPAhn8TwL7+1QC7PtuAvH7jQIP/I8CavyJAor8igKg/J8CmPy8Anv8xgI9/McCKvzfAjX8+wJU/AADsfz9AsL8FgPN/DIDsPw/A5D8QAMS/EEDBfxdA//7ewMe/HwD2Px2A/r8dwMW/YIDIv2eAwv9sQPO/LADUfy9AzL8uwMU/MADB/zbAxr88wPz/PADEf34Axz9FAQJ/SwEb/wyBDH8LwQT/DUEA/xPBBT8aAQy/GoE8fxrBBD9awQd/YYEDv2fBDL8rAT0+6gEtvurBLj7yQSu++YE7fviBKb83wQC/d0EGP3xBBj9EAUA/SIF3/weBYH8JAUi/CEFAfwiBeP7JQXM+zgF0vtVBfH7WQUQ/FUFrPxXBQn9WwUn/VgFRP2QBSf9mgXI/J8FqPybBUv8lwXt+54Fz/uhBcr7vwXi+9EFAfzOBfz80AUW/d8FDf39BQj9Ggbr/BIGL/wRBhH8FAbz+xIG1fsVBsr7MQbd+0gG/PtKBnv8RAaa/EYGufxDBtf8Sgbe/GcGzvyABrD8iAZS/IIGM/yFBtb7jQbK+6kGsfu6BvD7vAar/MAGvfymBrz8iAa1/GgGqPzOBZr82QSN/B8EhfxmA4T8KAOG/AkDgfzrAn38cQKA/FACw/wSAt38/wH5/PMBUf3UAej9pAFB/okBX/6EAZn+cwFR/0YByP8qAdT/DQHS/+0A7P/cAAkA3gAnAOQARwDjAIYA6wAAAegAPgHpAF4B7ACcAegA2QHeAPcB2AAIAvEAEgIOAR8CKgEyAkMBSAJYAWECagF7AnoBuAKGAdYCiAETA4gBbwObAY4DoAHqA6EBCgSiAUcEqwFiBLkBdgTRAYIE7gGUBAcCswQIAg8FBQIWBYoBFAVsAREFTQEVBRABDwXSABYFtAASBXgAFAVZABMF//8RBWf/DgUL/wgF7f4IBVL+BgUV/ukEH/7MBCX+sQQW/hYEFf64Axf+PQMV/qMCE/5lAhT+TQIo/lACR/5JAoP+SwKi/kgCwf5HAv/+bwIv/30CSv+EAmj/gAKG/3kCpP9sAr7/cgLd/2YC+f9OAhcAVgI0AFECUwA/AmsAOgKAAD0CoQA8Ar8AWwLBAPcCtACxA64AqgSzAMoEsgDoBLYA/QTLAPQE6ADZBPYAuwTxAAAE9gBCA/IAZgLwAEcC7wAqAvUAIAISATECKwHNAioBhQMlAUEEKQHaBCcB8wQ5Af0EVQHpBGsBygRsAS8EawGTA2gBdQNrAWgDhgF8A50BNgSdAdIEnAHjBIQB5wRmAeUEKAHdBMwA2wStAN4EjQDdBG8AvwRnAEMEdQCIA30AzQJ7AJACeQB6AmQAgAJGAJoCNgDVAzQAjgQzAO0EMwD8BBgA6QQAAC4EAwBSAwAAuQIAAJ8C8P+VAtT/rQLB/0oDwP/pA7v/yQTA/+cEwP/7BKr/8gSN/9QEif+1BIz/2gOI/x4DiP+jAoj/hQKG/38CSv+cAkP/uwJE/7MDSP9tBEr/yQRL/+cESP/8BDP/9gQW/9kEDv+aBBP/WwQT/54DEP+kAhD/ZwIM/0wC/v5OAuD+ZwLM/gIDz/69A87+pgTX/tgE0v71BMv+/QSu/uQEnP7DBJ3+BgSW/koDmP5yApP+YgJ5/mwCXf6KAlj+BQNY/sADVP56BFT+uARY/q8Edv4GBCn/fQOs//ACKQAvAs8AGQLjAP4B8gDiAf4AwgEEASUBFAFJABsBKgAaAXP/GgG5/hIBXP4MASD+EwGJ/TIBMP1IAdP8ZAE8/JEBxPu0AUj71gEq+9wBF/vDARD7hQEC+2kB7vpSAd76OAHL+iEBtfoLAYb65AAL+uEArvnjAFH54QDX+OIAevjgAGz4/ABb+BYBRvgtASz4PQEQ+EkB1PdRAb73aAGy94QBo/efAZv3vAGP99kBgPf0AV33JwJK9z8CLvdOAhD3UwLV9kcCufY7Aqn2IQI79usBIfbbARD2wgHz9Y0B3/V2AdL1WgHX9T0B6PUkARP29gAj9twALvbAAFP2kQBo9noAcvZcAIn2RgCi9jUAvvYpANb2FwDk9t3/+/bI/xb3uf8097T/O/eW/zT3eP8m913/CPdf/+n2V//N9lz/t/Zw/372hv9g9o3/RfZ+/yn2if8L9oT/0PVz/9/1jv/49aH/Cva6/xT21v8L9vP/3/UdAML1JACn9TQAifUwAG/1IgBb9ToAU/VXAD71bgAg9XEAAvV4AKX0egCH9HwAgvSaAIH0uQB89NYAlvQNAZr0LAGM9EcBjfRmAX/0gAF39J4BevS8AXX02gF19BYCc/Q1Amr0UgJq9HECZfStAmr0ywJr9OoCYPQGA0T0EwMn9BsDC/QnA83zIwOu8yoDj/MrA3DzKgNT8x0DOPMtAz3zSgM182cDNPPDAzfzHwQz8z4ENvN8BDPzmgQ187gEM/PXBDPzUQUv880FTvPHBWzzygWg8+oFyfMZBv3zNwYo9GQGQvR1BqH0wwYU9REHYfVgB6z1mAfC9a4HJPb3Bzn2DghI9igISvZGCD/2Yggv9nwIHPaUCDv2kQha9pUI9PaXCA73iAgc920IM/dYCEz3Rwhq9z4IivdBCKT3Twi692YIw/eDCN73kwj895kIN/iMCFX4iQh0+IoI0PiLCA75hQhr+YEIp/mBCOX5hAgE+oUIF/puCCn6VAhB+kAIXPo0CHz6Lgia+jMIsfofCKz6AQis+sMHlPrYB3f63Qdc+s8HPvrEBy76qQcc+pAHGvpxBx/6Uwcs+jUHQ/ohB176FAd8+g4HmPocB7P6Dwe4+vAGtvqTBrj6cwa2+lQGuPo1Brb6+AW1+n0Fs/ohBbP6pwSx+ogEtfpqBLL6SwSn+i4Ehvr7A3n6FgRh+igEK/pGBA36TQTu+UoE0flDBL35KwSt+REEofn0A6f51gO5+b8DzvmnA+j5mAP1+XwD2/lpA8v5UAO4+TcDq/kbA6b5/AKl+d4Cr/nBAsP5qgLe+ZoC/PmYAhr6nwIq+rkCP/rQAkz66wJq+ucCgfrSApr6wgKp+qcCqPqIArL6agLG+lMC4fpFAuH6JgLe+ggCoPoOAuH5GAIH+SEC5/gfAir4GAKt9xgCjfccAm73GwJ79/4Bj/fnAaD3zgHh988B//fTAfj42wFW+dcBdPnbAbP52AFN+tkBbfrdAej63gEF+9gBD/u8AQP7oAHk+pkBy/mkAS/5pQEQ+aIB7/iiAdD4ngEz+KMBFfifAfb3nwHZ95gB1Pd6Aen3ZAEI+GABwvhhAZ35XAG8+V8BWfpfAbb6XAHQ+k0ByvovAa76IgFx+iUBtPktAbn4KQGb+CQBi/gKAZr48AC4+OsAzPgDAcf4IgHP+H0B//jGAUf5AwKc+TkCq/lQAqf5bgKp+Y4Cj/meAjD5kwKy+JIC9fePAvr2iQJA9osCg/WMAq70hgKQ9IoCgPSkAof0wgKk9MsCvPW/Anf2uwKV9r8CcPfEAiz4yQLo+MsCZ/nKAn753gJ2+fsCWfkCA1/4BwMC+AsD4/cIA8P3CQOk9wUDZvcGA6r2AgPx9QQDFvUCA/f0BQO69AADffQEA1/0AQNP9BsDM/QoA9bzKwO38y8DefMuA1vzKAM98ycDNvNFA1TzPgN080UD0PM5A+/zMAMN9DEDLfQrA2v0MANm9TgDH/Y6A972PgPV90ADkvg/A075PwOL+UEDpflSA8P5WwPi+V0D2/l8A7z5hAOd+YIDXfmEA4P4fwPG93sDC/d8A+z2gAMT9noDU/V5A5j0egN69HkDW/R/Azv0gAP883gDn/N5A0PzeAM385UDN/OzA1XzuwNz87cDk/O4A7LzuQPQ87IDLfStA+z0rQOn9a8Dv/a1A3n3tgM4+LgDF/m3A1X5twN0+b8DhPnZA3b59ANY+foDOPn8A/v49QPc+PUD9/gBBBD5EgQm+ScERfkpBE35RwRM+WYEZ/l0BMb5bgTl+W8EBfpxBGH6awR/+nIEi/qNBH76qQRf+qwEQPqrBML5rwRl+agER/mqBC75vQQG+ewEQ/nlBKH55ARf+uUEffriBIz6/gR9+hgFX/ofBUH6HgUD+iQFR/kkBar4JgUt+CIFD/geBRP3HQVW9iEFmfUgBZv0HgXf8xcFv/MXBYHzGwVj8xYFSPMjBT7zPwVT81UFcvNZBfHzWAXp9FQFo/VUBV72WAVX91oFI/hZBc/4XAXn+VwFB/pbBSb6VwVl+lgFfvppBYb6hgV0+p8FF/qcBdr5owUg+Z0FJ/icBWr3lAWq9pIFtPWUBff0lAV79JUFW/SYBTv0lgX485YFnPOXBX7zkAV3860FifPGBabzzgXF88sFBfTTBUT00gUA9dIFvPXSBRj20wU29s4FtPbWBXD31wVM+NcF5/jUBQH61gVe+tQFefrhBYX6/QV1+hYGV/oaBrf5Gwb4+BUGAPgTBkf3EwaK9hEGkvUSBtX0EAY89AwGHvQNBgn0IgYS9D8GL/RIBpD0Rgbs9EsGC/VQBob1TgZD9kcGZPZHBoP2SwYB90wG+/dOBtb4TgZ3+U4GLvpTBk/6TwZt+lEGg/plBoL6gwZn+pEGCfqOBur5kwbK+ZQGq/mSBiz5kQaO+IwGkPeNBrP2igb89YgGCPWEBqr0hwac9KEGqPS9BsT0ygbj9MYG4fXABpz2wAZZ98UGVfjGBg/5xgbN+cYGSfrMBmb60wZw+vAGV/oCBxn6Agf7+QYHf/kJB8L4BQcC+AgH4fcIB0f3Aweo9gAH6/X+BlD1Agcy9QsHNPUqB0r1Pgdp9UIHY/Y8B6D2OAe+9jwHHvc7BxT3WAcT93cH9faBB5f2ggd59nwH4PV6B8b1jwfR9awH8fW6Bwr3swc499sHVvfjB5H31Qer98YHw/ezB833lQfR93cH5/djB/D3gAcO+IgHqfh6B8f4fAdk+XcHhPlyB8L5cgcB+nMHE/qLBx36qAc2+rgHFvq0B9f5vweZ+bsHXPnCBx75xAei+LsHyPe4B673yQeX994HeffhB1v33QdA98sHJPe9Bxn3oQcS94QHIPfABx733gci9/0HA/cDCOT2AgjH9v0Hivb6B2v2/Qdk9hoIevYvCNn2KQgz9xQIbfcDCG/3IgiJ9zQIBfgwCML4KQh9+SgIu/kuCBr6MghX+i0Is/orCL/6Rwik+ikIh/ohCGr6KAhV+j8IPfpRCCz6bwgR+n4IEfpACBr6IwgY+gMIF/rkB/j56wfY+fAHHPn0B174/gfh9/YHx/fmB8b3xwfd97QH+/etBxP4mwci+IAHIfhiByH4Qgc8+DUHu/g6B5f5QAe1+T8HtPkgB6n5Awej+eUGlfnLBor5rQYh+coF8PhZBeP4PQXg+AAF+fgTBSX5PgVC+UoFX/lRBX35TAWZ+T0FrfknBb35DAXD+e4ExvmwBMP5kgSv+XoEf/lVBG/5OwRp+R0EbfnkA2T5pgNb+YgDRPkpAyr50AIx+bYCSfmiAkv5gwJH+WECM/lLAlX4WAKb914CXvdqAgX3hALn9okCx/aJAqn2hAKQ9nICevZdAmn2RAJ59ikCevZJAmr2YwLN9VUCrvVWAtT0UwK09FQClvRXAon0cgKa9IsCsvR4ArH0GwLI9AgC5/QMAgX1DALC9RICBPYQAhz2/gEV9uAB9/XbAdn12gGa9eABO/XbAf/02gGi9N8BifTNAYb0rwGe9J0BvfSaAXb1mQGU9ZsBs/WTAcH1eQGs9WQBjfViAdD0ZAGy9GEBn/RKAaf0LAHD9CAB4fQmATz1IwH19R4BB/YDARD25wDx9eUAdvXsALr07QB99OcAbfSsAIr0pgDn9K0ABvWoAAH2qwA/9qoAUPaRAGX2egBH9nAA6/V1AKr1dgC+9WAA9fVDACr2JgAp9gcAOvbt/1n27f959vP/lvb4/7X2+f/U9vr/4fbe//T2xv/Z9rb/m/a8/332xf9n9tz/Tfbt/8L1aQBw9ZQAU/WdADX1ngAW9aQA+fShANv0mQDG9IQAsPRwAKf0rACk9CcBqfRlAar0AQKw9LkCsfT2AqL0EAN09DwDW/ROAx/0ZwOT86YDePO2A3Lz9AON8wQEq/MBBOnz9ANk9O8DIvXwA+H18QPa9vUDl/f0AzP4+ANx+PgDj/jzA3H4+gNe+BIEUfgvBDP4OQT19zsEO/c4BIH2MgRr9TAEDvUvBO70NATP9DIEsfQzBDT0MAQW9CsE+PMrBNjzKASZ8yoEefMnBFrzKwRE80AENfNbBE/zbARt828ErPNnBEz0ZwRs9GIE5/RoBAb1ZgQl9WkE//VqBL/2bgR592sEPfhnBDf4kARG+KsEaferBA72sQQT9awEV/SwBJzzrQRf864EQ/O5BDbz1QQ58/MElvPmBNPz5gTx8+kE7vTnBIj15QRJ9ugEBvfmBCX34wRF9+UEpvfsBMP36QTi9+wEAvjqBEH46wR9+PMEaPgIBUr4DwUB+NcE8fe9BOv3nwTw9yME+PfmAwD4xwMO+KwDIPiSA174TgN2+DoDN/mZAn75XwKd+VwCu/lfAtb5awLr+YIC7fljAgb6UwJk+lICgfpJAoP6KwJp+h0CIvv+AV/78gF++/AB+PvlATn84AGz/NUBTv3SAW79zwGO/c8Brf3JAen9yAGH/sEB4/6+ASD/vAE//7wBXf+3AZv/twEZALkBdQC5AdEAuQFMAbgBqAG3AQMCuAEiAroBQAK/AVgC0QFmArQBYQKVAYACkQG+ApcBVwOeAdIDpQEuBKUBSgSZAVIEfAFABGQBMQR/ATEEngEsBLsBDQS8Ae8DxAHQA8QBVAPBAfcCxgGcAsgBggK2AW8CngEyAo4B0wGJARcBkAEeAJkBYP+dAab+pAGP/a4B0vyyARf8uwEa+7sBfPq5AZ75ugFg+bkBw/isAYT4qwFG+K4BCfioAfj3qgHe97wBdPctAkD3YwIu93sC8vaKArT2jgKV9owCOPZ9Ahz2cAIB9l8C0vU1Ar/1HAKp9QMCifXOAYD1sQF69ZMBdfVVAXz1GQF/9foAifXdAKj1pwC+9ZIANfYvAE/2HgBo9gwAgPb3/8v2w//r9sf/Bfe2/z33nv9c96X/XveH/2T3pP9I97L/Kfe3/wv3tv+39pD/mPaQ/4D2ov9r9tv/Tvbn/zT2+f8O9icA/vVBAO/1XACz9ewAovUpAZX1RAGC9Z8BifW8AZ710gG29eQBP/Y2AsT2gwJh934Cn/dzAjv4WwJa+FoC1fhEAsz5IQIB+/oBX/v1Af779wF1/fABs/32AdH98wH5/cQBJf6XATf+fAHB/tkA1v7CAPf+jgAd/10ANf9IAF7/GgBSuicNbxIDOxACGrAn7//o/xMA3//0/xMA6//p/xYA6f/o/xYA6f/p/xYA6f/o/xUA6f/o/xYA6P/p/xYA6f/p/xcA6v/q/xcA6v/p/xcA4//s//b85P/s/5IF5//w/zoB5//q/8T8rv/a/+0CsP+NACMDtv/iAB/6FP/AAE76rP67AHkETP7jABz62f3gABwGbP3lAOX5Bv3lAOz5oPzlAOf5OvzpABUGzPvnAPT5W/viAO/57frjABwGgfroAPP5TfrmAHkCmfo6Ab0B2PqNAfoBB/vxAeICUvs/AhEAvvtJAjQB9PvDAgADCvxAA88D//urAzcD7fsTBCwD7PuEBKoDrPvYBAkDrvtyBcECsPvgBRgDrvtEBhoDrfuyBiQDz/sBB/X/XvzyBhQA1Pz3BhAAPP32BhMAPP36BkcBQf31Bib98vyDBp/8Kf3xBVz+mf3JBer/+f3JBWL8U/2QBY77NP0xBWf9Qf21BLH8PP1EBAb9Qf3CAwL9Qf1CA/X8P/1HA0D8ufz1Akf80PxnAnD+Nv07Av7/rv09AgQAK/45AgYAof45AgAAOf8JArcAFwACAggAlAAEAg4AEAEEAgkAjgECAgoACQICAg0AIwIFAsv8FQKIAan8HAIDAVf9JwJ+AO/8WwJ8AGAEMQKOAPX8KwJZANIFAgJ4ABYEigGvAAL7NgFmAKr66wD9/yv+0QDh/wX6HAD6/wH6sP/4/3oF+f83AOH/xAAuAPz/AwFuAO75MgBwAO75zf97ADsE0P+qAAUBPQCqAAEAEgGkAAYAlwGkAGEABQKuAML/PAKtAI0DbwG1AOf5oQCzAPn5yv+sAPX5kv+bAC79b/+CAJwEU//1AP75mv7yAPn5If7nAPj5ov3qAPf5Df3zAOT5j/zsABH6BfzvAAsGi/vsAA76G/vkAJ8FhvrsAAf6L/rvAMcBsvr9AH/+u/rxAFEDufowAcj/c/scAQEARfwZARcAF/0dAQ0A5P0cARYAuv4fARsAi/8jARMAXQAmAQ0ALAEmAQwA/gEmARUAOwIkAb0E5QFrAfQC5QFpAQ4GSwFnAfj5fABlAff5p/9jAfX51/5fAer5B/5fAej5M/1fAfb5Y/xhAfz5j/taAe754/pbAV4F0/qIATcC0PuSAQwAo/yUAQ4Acf2TAR8ARP6ZARcABP+cARAA4/+eAQ4AtgCaAQ0AhwGfARcAPAKgAev+NgKeAY4EEgL1AZgAKgLzAWL8jwHMAa8FUQHoARD9MgHLAfsFsQDaARIGNADjAev5t//fAfz5RP/dARX6zP67Af76vP6yAfcCwf7hAff59/3eAfb5Jv3YAfj5VfzYAfX5gPvTAfj5FPvhATsEW/sRAvD/L/wKAgcAAf0MAhgAj/0NAhsAyv0OArEDL/0hAuP5vPweAi/6qvwsAuwCrfxQAvj59vtTAtgF5vthAjICc/yLAgAAl/zBAtwEKvzcAq4DhPz8AgQAyPxAA/35E/xCA/z5/vt/A+z/tfx2A/b/Fv2iA0MELvy7A+z5D/ztAz8BAv3uA6kAAv0zBMMFe/wzBO75D/w2BMIEJPxrBNX/3fxqBBUAFP2bBLYEBfypBPv5rvvnBMMDyvvkBNf/kPzfBAYAD/3kBEgB+/wgBQb6SPwiBeL51fsoBdgE6PtZBeT/mPxYBQgANv1TBW0BRf2dBfQFvPycBfH58/ugBfn50vvJBVEB3vzOBSYACf0TBgIE7/sWBvH51/tEBj8ByPxCBlYAzPyGBnYFVPyEBuD51vuNBgQF7fu6BhQAvvzBBh0Au/yPBsX8qfzLBdD8mfwBBdf8jfw2BOL8hPxpA+P8fvybAvX8//zwAUP/tv2xAXv/ff56AYr/Qv9IAZn/1v8mAbX/0QDnAA8AcQHoABgA1QHfALz/XAJlATsB0QKGARUAXQOWAXsA7gOiAQsAXAS0ATABngQJAvH/FQUFAhIACgUAAvT+EwVnAfj8EwXtAOf8FAVrAPz8DQX//6L8EAVq//38DQX5/kf9CAVm/vf8AwUh/sj91gQn/uv5OwQW/un5uwMX/uv5QAMW/u35xgIT/vb5UQIV/hT6SwIc/tECSwKT/i4DRwIC/90CgQJ7//ICXwIKALcDNgJ5AOIEPALDACkA0wK1AO//oAOtAAsAdASxABMA7AS6AFMB2QT1AAr6IwT3AO35VgPzAPf5iALyAPz5IwL/ADIELgIpAQIBmQIpAQYAZQMkARIAMwQoARYA6AQsAYAA6ARsAYgFbQRrAfD5ogNpAfL5fwOgAVgADQSbAQQA1gSbAQ0A5gQ7Ae382wTCALP8ZwRwAPIFnAN+AOT5zQJ8AP75gQI8AMD+oAM0AAQAbAQzABgA7gQ1APz+4gQBAPj5PgQEAPn5cwMBAPb5sgIAACT6rQLA/1n/HQPB//j/6wO6/xEAuwS//xUA8wSQ/3D78QOJ//H5IQOI//f5iAKG/yT7pQJD/x0AcANH/xgAQARH/x4A5wRM/2//4gQM/1j6ZwQS/+X5kgMT/+b5xQIS//X5WwIK/7X7TgLK/gAA7QLO/gYAvAPM/hUAeATX/hQA9QTM/oH+2ASd/g8GFQSX/uL5RAOZ/un5gAKT/vT5bQJZ/hz/2QJa/v3/qANT/goAeARU/g4AxQRf/iUDKgQI/68ElgOW/7oE+gIhAMIEYAKnANIE4gH/ALIFMgEVARsGYgAcAen5k/8bAfT5zP4UARD6Pv4PAfcFt/0pAb0F8vxbAaYFNPyWAakF0PuzAa0FbPvPAbcFFfvmAcj8Cvt6ATr80volAWf7g/rkACb7GfriAOP5tPnkAPL5SfnjAAf64vjiAPP5ZvjhALoFIvhDAXAFv/duAR8El/fNAfwDY/ckAiEEA/dRAvb5p/YdAmb7OvboAdj6+/WZAf773PU5AY39G/bpAAD+U/aQAGD+kvY6AJD+6vYHABz/Mfeu/2v/NPd2/yP81fZU/1gF3fZd/+UFgPaH/5MFGPaG/0T6zfV1/xEDFPbI/9EC2PUfAOD5cPUjAEj6P/VpACAFjvR2AMMFfvTeALADkvRHARkDdvSwAa4Db/QnAtwDZvSyAu4DOvQaA9sFk/MuA/b5OvMsA1ECNfPHAycDN/MsBE4DM/PMBCMDNPNPBSMDL/PMBSEDw/MUBpoBLfRkBlwBkvS2Bm0BDPULB1wBWPVXB2QBuvWoB1UBG/bwB20BIvaACFIEG/aUCKQA3vaWCA4AOPdPCDP/ufdzCOoCdPiICBAA8PiGCAsAavmACA0A4/mDCBwASvo/CET/q/rSB/j8rPrHBxUGN/q6B5L7IfpIB5H+h/oOB3oAtfoOB1L9t/qQBvz8tvoXBv78tfqXBfv8s/ocBfr8s/qjBPr8r/o+BAv8ifoBBH0DK/pHBNMFwfkxBJj7t/m/A4L+9vmBA9j7qvkUA/r8zfmkAjX/Kfq0AowCaPrtAvD+nvq1Aqf+0vpPAq3+uPoLAu8F7/kZAhsGHvkiAvH5RPgYAu75gPcbAvT5W/caAr3+8/fOASUAxPjWASYAmPnWASAAafrbARYAAvvYAeb+CPumAbX7+fmmAeH5KPmjAfr5VvilAfL53PeYAUr7+fdfATEAtfhgAQMAg/lbARQAUvpcAQ0AzPpVAXj+jPolAQ8Gv/ktAej57PgsAe35j/jyAHX+yvj+AC4DzPh3AUkCJ/noAWwBqPk/AksBjfmfAh76zfiUAu/5AfiNAvT5LPeKAv35YPaMAvX5jPWMAvj5xPSGAvD5i/TAAnQBpPW+AgMAa/a8AiQAOve/AisAB/jIAhEA3vjJAhsAc/nQAjABgPn2AmQEavgKA+v5mvcFA/j5yPYDA/L5/PUHA+n5JPUCA+35XPQAA/j5tvMxA+X5QvMoA+z5OfNDA34CYPQwAyQAKfU2Ax4A+vU5AxsAzfY+Ax4AnvdBAw8Abvg+AwwARPk/AwoAt/lVAzgA3fmHA6wDtPh/A+r55fd8A//5Fvd/A+f5QvZ6A+35c/V6A/f5oPR9A/j51fN4A/z5OvNzAwcFSPO8A83/FPStAwMA5PSrAyUAs/WvAyEAhPa0AxkAVve1AxwAKPi3AxQA+/i2AxIAdvnGA7EBevn1A+QE3Pj1A9gDL/kvBA36M/kwBIX+TPksBOoCmPlwBPX/ZPppBP3/gvqoBA8FWfmqBOH5BfnwBHAAi/nkBBIAXfrmBAAAgfoVBfAEY/klBez5u/gnBfP5CPgfBe75NvceBez5Z/YiBfD5kfUhBfX5wPQeBer58PMXBfD5VPMOBXAFNPNYBfj/5fNSBQYAtPRVBQUAifVSBRoAXPZWBRcALPdXBRsADPhZBQwA0vhaBQQAqflcBSsAcfpcBVIAePqeBXwFAfqjBen5L/mfBf35XvibBfb5jveVBfX5vfaTBev56vWTBez5FvWVBer5RfSUBfn5kPOPBZgFjvPHBZgAC/TSBREA2vTRBRUAsPXQBREAgvbVBRsAU/fXBQsAKPjWBRgA+vjWBRwAzPnVBRsAc/rbBe4Aa/ocBvYF0/kdBvL5A/kWBvP5MfgWBvj5XvcVBvj5jvYSBuv5u/UTBuL56PQRBvj5JPQMBugFF/Q9Bs4AjfRGBggAYfVPBgUAMPZFBhUAAvdLBhMA1fdOBh0AqfhLBhwAe/lNBggATfpRBgUAevqJBuUEXfmRBv/5jviOBuv5u/eQBvT56faIBub5F/aKBvf5SPWJBuv5pvSGBhkFpfS5BuEBsPXABgEAgfa/BhkAUvfDBhEAJPjEBhAA9/jFBhwAyfnHBhkAYPrKBh4BTvoFB+r5o/kJB+f51/gGB+35BvgKBwD6bPcFB/b5ufYBB+r55vX/Bun5Q/UCBzwFOfU2BwwCN/Y8BwYACfc6BwkANvc2Bw0FsPaDB/f55PV5BwcG2/W0B54BDPexBwIAoPfPB+/+2vduB77+8fdiBywDCfiFB9n/1fh5BwEAp/lxBxIAFvptBx4DM/q1BxH6ePnDB+b5rPi9B/355fe5B/z5YPfYB0/7E/d6B5oEI/cFCOkFlPb+Bwf6b/YqCEIBI/cYCH//3/cvCPX/sfgoCBMAf/kqCDQAQ/ouCAMAvPooCGcAvPpACFr6M/pgCA8EEvp6CLP8F/oPCNH8A/rsB/QFPPnzBxoGbfgACPn52ffzB/H66vexB/L/I/iLB9P8G/gxBwkAw/g7ByoAlfk/ByUAjvm1Bjj8Ovn8BSf86/hTBTf83vj8BGcF7vgJBXsBfflOBTf/wfmRBJH8afkbBO78XfmXA6T8L/noApj8TPmKAtT8efhXAg8Gq/dfAvn5Nvd2Aq4FsPaHAl76ePYtAg7+fPYfAjcDxvVYAv/5+/RUAgX6lfRZAq8EpvSMAt3/rvQeAgD93PQJAjoAp/UQAiwAEPYJAoX++fXbAQQGOPXdAf35mvTeAcH6ovSYAcf/NvWYAQcAs/WRAcn+nvVhARAG8fRlAe75ovQ7AYD9DvUlAQAA2PUfARgAE/bhAAr7ofXpAB0G1fTuAPj5a/TnAOT7cvSkAOX/DvWpAAYA4vWqABQAR/amAHv9+PV0AOn50vVTACf/J/YAAMj8U/bw/ysAvfb5/xEABve3/9/8tPa7/wcG0fVeAO4ER/WdAOAF4vSeAOD6svRxAPT7r/R4AIUEpfRMARgDqvT0ARADsfSYAhUDr/QBAzcESfRYA1QFkvOqA2cFffMHBML/NfTvAwMABvXvAxEA1vXvAxwAp/bzAxAAd/fyAxMAQvj1Ax0AmvjwA/8FGvg8BO75Tfc5BPn5fPYzBPT5qPUvBOn51/QzBPX5CPQtBPv5VPMuBLMFOPNXBJMCHvRkBAYA8PRkBBgAvvVmBCIAkvZrBBkAYfdpBBgAL/hoBB0ASfiwBBQGpvesBOv51/avBOz5GvawBPL5R/WsBOD5dfSwBOr5pfOvBOj5PvPQBOYC4vPqBAMAtPTkBBEAgfXkBBUAVfboBBMAJ/fjBBkA9/fpBAcAhPj0BFUBfvj2BNYE9vfQBPr77fdSBBv9B/i0A+39efg4A5n+D/m4Arz+fPlfAmz/8fmBAoH+UvpSAgEAePodAiH7aPocAvz/9voFArn/bPvyAcz/0vvnAez/NvzdAfL/mvzVAfL/Bf3XAff/a/3PAfr/0v3IAfv/Qv7HAfz/uf6+Af//IP+6AQAAh/+3AQIA8P+5AQcAYAC5AQkAxQC4AQoAKgG4AQ0AjgG2AQ8A9AG3AR0AWgLNAUEBagLQAdr8YwKOAQMA0wKZATgAUwOcARsAzAOlAR0AOgSiAZ7/OgRmAYb6MgSVAU4DdAPBAeD5+ALHASIGbgKaAfn66QGKAfL5HgGQASEGTQCYASAGff+cAR8Gqv6lASIG0/2tAeL5AP2yAeH5L/y8Aeb5XPu5Ae/5jvq6Ae35vfm8AfX57fiyAQH6IPirAQ36j/cQAo4ENfd2AoIEQfaAAmf6zfUtApT7fvWwAXj8efUUAU39sfWeAHD+P/YoAM7+0vbC/xL/Ofeg/6z/Xfd7/1cDZve5/zP6vfaS/2n6Uvbl/24FBvYzABYEqPUaAcgDgPWzAWACE/YbAhoBxfaAAoAAp/dyAsj/ufhHAsL/yvkeAsr/3fr8Ad7/5fv3AQwA+fzuAQ4At/35ATcACP63AW/+oP4DAUz+KP9UAHz+YP8PAJ4FYf8RAJwFR/+9/xMERv/G/9H8Pv/D//D/Av/I/+r/A//I/+r/Af/J/+n/Av/J/+j/Av/J/+r/Av/I/+n/WiEIgNLj1QYQIRiVFSChwQIomuy6ATCTHzgBSgBQAVgBYAFg13VowQ9wZHpREgpCYXRocm9vbS4gGgpCYXRocm9vbS4gIjAKCg0PqB7AFau6dUAKCg1actq+Fau6dUAKCg1actq+FfL8tj8KCg0PqB7AFQH9tj8oAjV2jGI+ekASBFJvb20aBFJvb20iMAoKDSSI38AVHziSQAoKDV+/J8AVHziSQAoKDV+/J8AV/Q2UvwoKDSSI38AV/Q2UvygCelASDExhdW5kcnkgUm9vbRoMTGF1bmRyeSBSb29tIjAKCg2WqHo/FZzB5D8KCg3xL5BAFZzB5D8KCg3xL5BAFQuq3r8KCg2WqHo/FQuq3r8oAnpGEgdIYWxsd2F5GgdIYWxsd2F5IjAKCg2o0ivAFSPeuD8KCg2Oa34/FSPeuD8KCg2Oa34/FWp0AsAKCg2o0ivAFWp0AsAoAooBBAoAGCuKARYKDw3I3wu/FSPTiT8dh06pPRgIKJkBigEHCgAYKyjBD5ABAaIBkJ0BCoydAQ2PwnU9EgoNR+EGwRXherS/GL8BIGky15wBS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRLZEtLSw9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tkZGRkZGRkZGRkZGRkZEtLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPZGRkZGRkZGRkZGRkZEtkZGRkS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA9LS0tLZGRkZGQPZA8PDw8PD2RkS0tLS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZA9LS0tLDw8PDw8PZGQPZGQPD2RLS0tLSw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA9LS0tLS2RkDw8PD2RkZA8PZEtLS0tLDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw9LS0tLZA8PDw8PDw8PDw9kZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw9LS0sPZA8PDw8PZGRkD2RkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw9kZGRkDw8PDw9kZGQPDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZGRcD2QPVlZaWg8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tkDw8PDw8PDw9LS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDwAAWlxkZA8PAAAAAAAADw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0sPDw8PDw8PDw8PDw8PS0tLS0tLS0tLS0tkZA8PD1wAAAAAAFpaWgAAAAAAAAAADw8PZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD2QPS0tLZA9kWgAAAAAAAAAAAAAAAAAAAAAADw9kDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PZA9kDw9kD2QPDw9kAAAAAAAAAAAAAAAAAAAAAAAAVg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8AAAAAAAAAAAAAAAAAAAAAAABWVg8PZEtLS0tLS0tLS0tLD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkZA8AAAAAAAAAAAAAAFYAAAAAAAAAVg9kZGRkZGRkZGQPDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZA8AAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLZGRkDw8AAAAAAAAAAAAAXAAAAAAAAAAAD2RkDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS2RLS0tkZGRLS0tkZGRkZGRkS2RkAAAAAAAAAAAAAFxcDw8PDw8PDw8PDw8PDw8PZGQPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8AAAAAAAAAAFxkAAAAAAAAAAAADw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQAAAAAAAAAAAAAAFwPDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PXFxkDw9cZGRkAAAAAAAAAAAADw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS2RkZGRkZGQPZA9kZA8PD2RkZA8PDw8PDw8PZA8PDw9kZAAAAAAAAAAAAAAAXA8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLZGRkDw8PDw9cZGQPDw9kZFYPAAAAAAAAAAAAAAAADw8PZGQPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PD2QPDw8PDw8PAAAAAAAAAA8PDw8PD2RkDw8PD1xkAAAAAAAAAAAAAABcDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD0tkZA8PDw8PDw8PZGQPDw9kDw8AAAAAAAAAAAAAAAAAZGRkZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA9kZA8PDw8PDwAAAAAAAAAAAA8PDw8PD2QPDw8PD2QPAAAAAAAAAA9cXA8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPD2QPZA8PDw8PDw9kZGQPZGQPDw8AAAAAAAAAAAAAAABkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDw8PAAAAAAAAAAAAAAAAAA8PDw9kZA8PD2QPZA8AAAAAAAAAXFxcDw8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PD2RkDw8PDw8PDw8PZGRkZGQPXA8AAAAAAAAAAAAAAGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PZGQPZGRkZGQPDwAAAAAAAAAAAAAAAAAAAA8PDw9kZGQPZA8PDw8AAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw9kZGRkZGRkZA8AAAAAAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tkDw9kDw8PZA9kZA8PAAAAAAAAAAAAAAAAAAAAAA8PD2RkD1xcDw8PDwAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kS0sPZGRkZA8PDw8PZGRkZGQPDw8PAAAAAAAAAAAADw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLD2RkZGRkDwAAAAAAAAAPDw8AAAAAAAAAAA8PDw8PDwAAAAAAAAAAAAAAAAAPDwAAAAAAAAAPDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkSw8PD0tkZGRkZGQPD2RkDw9kZA8PDwAAAAAAAAAAAAAPDw9kZEtLZGQPS0tLS2RLZGRkZGRkZA9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tkS0tLS0tLS1xcZGRkZA8AAAAAAAAADw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPS0sPD0tLDw9kDw8PDw8PD2RkDw8AAAAAAAAAAAAAAA8PDw9kS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZA8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD0sPDw9LD2QPDw8PDw8PD2QPAAAAAAAAAAAAAAAPDw8PZGRLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PZGQPDw8PDw8PDwAAAAAAAAAAAAAADw8PDw9kZGRLS0sPZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PZA9kZA8PDw8PDw8PAAAAAAAAAFxcXFwPDw8PD2RkZGRkZA9kZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw9kDw8PDw8PDwAAAAAAAABcXGRkZGRkZGRkZGRkD2RkDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PZGQPDw8PDw8PAAAAAAAAAFxkZGRkZGRkZGRkZGQPD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8AAAAAAAAAAFYPDwAAAAAAAA8PDwAAAAAAAAAAAAAAAAAAAAAPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PZGQPDw8PDw8AAAAAAAAAXGRkD2RkD2RkZGRkDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAWVZWWgAAAAAPDw8PDw8PDw8PDw8AAAAAAAAAAA9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw9kZA8PDw8PAAAAAAAAAA8PD2QPDw8PD2RkZGRkDw8PD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAABWDw8PWg8PD2QPDw8PDw8PDw8PDw8PAAAAAAAAD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PD2QAAAAPDwAAAAAAAAAADw8PZGQPDw8PD2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAFoPDw8PDw9cZGRkZA8PDw8PDw8PD1xcAAAAAAAPZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9cAAAAAAAAAAAAAAAAAAAPDw9kZA9kZGQPD2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAWg8PDw8PDw9kDw8PZGRkZGRkZGRkZGQAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD1wAAAAAAAAAAAAAAAAAAA8PD2RkZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAA8PDw8PDw8PDw8PDw9kZA8PDw9kZGRkZA8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PZAAAAAAAAAAAAAAAAAAADw8PD2RkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kDw8PD2RkS0tLDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw8PDw9kAAAAAAAAAAAAAAAAAAAADw8PZGRkZGRkZGQPDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkDw9kDw8PZA8PDw8PZGRLSw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS2RkAAAAAAAAAA8AAAAAAAAAAA8PZGRkZGRkZA8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PDw8PD2QPD2RkZA8PDw9kZEsPDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PS0tkZGQAAAAAAA8PDwAAAAAAAAAAAA9kZGQPD2QPDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PD2QPDw8PDw8PSw9kDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PS2RkZAAAAAAADw8PDwAAAAAAAAAAAA8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkD2RkZGRkZGQPZGQPDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9LZGRkAAAAAAAPDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXA8PD1xcAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tkZGRkZGQPZGQPS2RkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZGQAAAAAAA8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PAAAAAAAAAAAAAABcZGRkZGQPAAAAAAAAAAAAAABcDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRLS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kZA8PD2RkZAAAAAAADw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDwAAAAAAAAAAAAAAAFxkZGRkZGQPAAAAAAAAAAAAAGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAPXGRkZGRkZA8PAAAAAAAAAABcZGRkZGRkZEtLS0tLS2RLS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PZGRkD2RkZA8PAAAAAAAAD1xkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAADw8PDw8PDw9kZGQPD2RkDw8PAAAAAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PD2RkZA8PZGQPDw8AAAAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PDw8PZGRkDw9kDw8PDwAADwAAAAAAAABcZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw9kZA8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAADw8PDw8PDw8PD2RkZA8PZA8PDw8PDwAAAAAAAAAAXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw8PZGRkDw9kDw8PDw8PAAAAAAAAAABcZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAPDw8PDw8PDw8PDw9kZGQPD2QPDw9kZA8PAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAADw8PDw8PDw8PDw8PD2RkZA8PZA8PDw9kZA8AAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAADw8PDw8PDw8PDw8PZGRkD2RkDw8PDw9kDwAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAADw8PDw8PDw8PDw9kZGQPZGQPDw8PD2RkAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PD2RkZA9kZA8PDw8PXAAAAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PDw8PZGRkD2RkDw8PAAAAAAAAAAAAAA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8AAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkAAAAAAAADw8PDw8PDw8PDw9kZGQPD2QPDwAAAAAAAAAAAAAADw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPAAAAAAAPDw8PDw8PDw8PD2RkZA8PDw8PAAAAAAAAAAAAAAAPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZA8AAAAAAA8PDw8PDw8PDw8PZGRkDw8PDw8AAAAAAAAAAAAADw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8AAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9cZGQPAAAAAAAADw8PDw8PDw8PDw9kZGQPDw8PDwAAAAAAAAAAAA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAPDw8PDw8PDw8PD2RkZGQPDw8PAAAAAAAAAAAADw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PZGRkDw8PDw8PAAAAAAAAAAAPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw9kZGQPDw8PDw8AAAAAAAAAAA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8AAAAAAA8PAAAADw8PDw8PDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAA8PDw8PDw8PDw8PD2RkZA8PDw8PDwAAAAAAAAAPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAADwAAAAAADw8PDw8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PZGRkDw8PDw8PAAAAAAAAAA8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8AAAAAAAAADw8PDw9kZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDwAAAAAAAAAAAA8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PAAAAAAAAAAAADw8PDw8PDw8PDw9kZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8AAAAAAAAAAAAPDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwAAAAAAAAAADw8PAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PAAAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8AAAAAAAAADw8PDw8PDw8PDw9kDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwAAAAAAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPDw8PDw8AAAAAAAAPDw8PZGRkZGQPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8AAAAAAAAPDw9kZGRkZGRkZGQPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PVgAAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw8PDw8PDwAAAAAAAAAPZGRkS0tkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9WAAAAAAAAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PAAAAAAAAAABcZGRLDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PD1kAAAAAAAAPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8AAAAAAAAAAABWZGRkDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PD1oAAAAADw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8AAAAAAAAAAA8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGQPDw8PDw8PWVoAAA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8AAAAAAAAADw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDwAAAAAAAA8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGQPDw8PDw8PDw8PDw8PD2RkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZEtkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkSw8PDw8PDw8PDw9kZGQPDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0sPDw8PDw8PDw8PZGRkDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLDw8PDw8PDw8PDw8PD2QPDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PDw8PD2QPDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkSw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEsPD2QPDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2RkZGRkZGRkZA8PZA8PZA8PZA8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLZGRLS2RkZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLZGRkS0tkZGRkZGRkZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZEtkZGRkS0tLS0tLS2RkZGRkZEtLZEtkZA9kS2RkZEtkZEtLS0tLS0tLS0tLS2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQAFKDw18dOy+FfXTwbwdhp0ZPVUAAIA/WOMSqgHtuQINj8J1PRIKDUfhBsEV4Xq0vxi/ASBpMtecAUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkS2RLS0sPS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkS2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tLZGRkZGRkZGRkZGRkZGRLS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkD2RkZGRkZGRkZGRkZGRLZGRkZEtLZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPS0tLS2RkZGRkD2QPDw8PDw9kZEtLS0tLZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPZGQPS0tLSw8PDw8PD2RkD2RkDw9kS0tLS0sPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPS0tLS0tkZA8PDw9kZGQPD2RLS0tLSw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PS0tLS2QPDw8PDw8PDw8PZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PS0tLD2QPDw8PD2RkZA9kZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PZGRkZA8PDw8PZGRkDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PD2RkXA9kD1ZWWloPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLZA8PDw8PDw8PS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8KClpcZGQPDwoKCgoKCg8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLDw8PDw8PDw8PDw8PD0tLS0tLS0tLS0tLZGQPDw9cCgoKCgpaWloKCgoKCgoKCg8PD2RkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLSw8PDw8PDw8PDw8PDw9kD0tLS2QPZFoKCgoKCgoKCgoKCgoKCgoKCg8PZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD2QPZA8PZA9kDw8PZAoKCgoKCgoKCgoKCgoKCgoKClYPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKVlYPD2RLS0tLS0tLS0tLSw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw9kZGQPCgoKCgoKCgoKCgpWCgoKCgoKClYPZGRkZGRkZGRkDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLD2QPCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9LS2RkZA8PCgoKCgoKCgoKClwKCgoKCgoKCg9kZA8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS0tkS0tLZGRkS0tLZGRkZGRkZEtkZAoKCgoKCgoKCgpcXA8PDw8PDw8PDw8PDw8PD2RkD0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PCgoKCgoKCgpcZAoKCgoKCgoKCg8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkCgoKCgoKCgoKCgpcDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PD1xcZA8PXGRkZAoKCgoKCgoKCg8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tkZGRkZGRkD2QPZGQPDw9kZGQPDw8PDw8PD2QPDw8PZGQKCgoKCgoKCgoKClwPDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS2RkZA8PDw8PXGRkDw8PZGRWDwoKCgoKCgoKCgoKCg8PD2RkDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw9kDw8PDw8PDwoKCgoKCgoPDw8PDw9kZA8PDw9cZAoKCgoKCgoKCgoKXA8PDw8PDw8PDw8PDw8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9LZGQPDw8PDw8PD2RkDw8PZA8PCgoKCgoKCgoKCgoKCmRkZGQPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGQPZGQPDw8PDw8KCgoKCgoKCgoPDw8PDw9kDw8PDw9kDwoKCgoKCgoPXFwPDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw9kD2QPDw8PDw8PZGRkD2RkDw8PCgoKCgoKCgoKCgoKZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkZA8PDwoKCgoKCgoKCgoKCgoPDw8PZGQPDw9kD2QPCgoKCgoKClxcXA8PDw8PDw8PDw8PDw8PDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw9kZA8PDw8PDw8PD2RkZGRkD1wPCgoKCgoKCgoKCgpkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PD2RkD2RkZGRkDw8KCgoKCgoKCgoKCgoKCgoPDw8PZGRkD2QPDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGQPCgoKCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RLS0tLS0tLS0tLS0tLS0tLS0tLZA8PZA8PD2QPZGQPDwoKCgoKCgoKCgoKCgoKCgoPDw9kZA9cXA8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPZEtLD2RkZGQPDw8PD2RkZGRkDw8PDwoKCgoKCgoKCg8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLSw9kZGRkZA8KCgoKCgoKDw8PCgoKCgoKCgoPDw8PDw8KCgoKCgoKCgoKCgoKDw8KCgoKCgoKDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEsPDw9LZGRkZGRkDw9kZA8PZGQPDw8KCgoKCgoKCgoKDw8PZGRLS2RkD0tLS0tkS2RkZGRkZGQPZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tLZEtLS0tLS0tcXGRkZGQPCgoKCgoKCg8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkD0tLDw9LSw8PZA8PDw8PDw9kZA8PCgoKCgoKCgoKCgoPDw8PZEtLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGQPDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw9LDw8PSw9kDw8PDw8PDw9kDwoKCgoKCgoKCgoKDw8PD2RkS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD2RkDw8PDw8PDw8KCgoKCgoKCgoKCg8PDw8PZGRkS0tLD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PD2QPZGQPDw8PDw8PDwoKCgoKCgpcXFxcDw8PDw9kZGRkZGQPZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PZA8PDw8PDw8KCgoKCgoKXFxkZGRkZGRkZGRkZA9kZA8PZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw9kZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPDw8PDw8PD2RkDw8PDw8PDwoKCgoKCgpcZGRkZGRkZGRkZGRkDw9kZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PCgoKCgoKCgpWDw8KCgoKCgoPDw8KCgoKCgoKCgoKCgoKCgoKD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkDw8PDw8PDw8PD2RkDw8PDw8PCgoKCgoKClxkZA9kZA9kZGRkZA8PDw9kZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCllWVloKCgoKDw8PDw8PDw8PDw8PCgoKCgoKCgoPZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPDw8PDw8PDw8PZGQPDw8PDwoKCgoKCgoPDw9kDw8PDw9kZGRkZA8PDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKVg8PD1oPDw9kDw8PDw8PDw8PDw8PDwoKCgoKCg9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw9kCgoKDw8KCgoKCgoKCg8PD2RkDw8PDw9kZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgpaDw8PDw8PXGRkZGQPDw8PDw8PDw9cXAoKCgoKD2RkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PXAoKCgoKCgoKCgoKCgoKDw8PZGQPZGRkDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCloPDw8PDw8PZA8PD2RkZGRkZGRkZGRkCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw9cCgoKCgoKCgoKCgoKCgoPDw9kZGQPZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoPDw8PDw8PDw8PDw8PZGQPDw8PZGRkZGQPDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA8PDw8PDw8PD2QKCgoKCgoKCgoKCgoKCg8PDw9kZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZA8PDw9kZEtLSw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSw8PDw8PZAoKCgoKCgoKCgoKCgoKCg8PD2RkZGRkZGRkDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkZA8PZA8PD2QPDw8PD2RkS0sPDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLDw9LS0tkZAoKCgoKCgoPCgoKCgoKCgoPD2RkZGRkZGQPDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPDw8PDw9kDw9kZGQPDw8PZGRLDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0sPD0tLZGRkCgoKCgoPDw8KCgoKCgoKCgoPZGRkDw9kDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2QPDw9kDw8PDw8PD0sPZA8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2QPD0tkZGQKCgoKCg8PDw8KCgoKCgoKCgoPDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZA9kZGRkZGRkD2RkDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PS2RkZAoKCgoKDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKClwPDw9cXAoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS0tLZGRkZGRkD2RkD0tkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGRkCgoKCgoPDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDwoKCgoKCgoKCgoKXGRkZGRkDwoKCgoKCgoKCgoKXA8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkS0tLS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkS0tLS2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PZGQPDw9kZGQKCgoKCg8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8KCgoKCgoKCgoKCgpcZGRkZGRkDwoKCgoKCgoKCgpkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKD1xkZGRkZGQPDwoKCgoKCgoKXGRkZGRkZGRLS0tLS0tkS0tkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PD2RkZA9kZGQPDwoKCgoKCg9cZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PZGRkDw9kZA8PDwoKCgoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw9kZGQPD2RkDw8PCgoKCgoKCgoKClxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw8PD2RkZA8PZA8PDw8KCg8KCgoKCgoKXGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PZGQPDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw9kZGQPD2QPDw8PDw8KCgoKCgoKClxkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PD2RkZA8PZA8PDw8PDwoKCgoKCgoKXGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKDw8PDw8PDw8PDw8PZGRkDw9kDw8PZGQPDwoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PDw9kZGQPD2QPDw8PZGQPCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw8PD2RkZA9kZA8PDw8PZA8KCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCg8PDw8PDw8PDw8PZGRkD2RkDw8PDw9kZAoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw9kZGQPZGQPDw8PD1wKCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PD2RkZA9kZA8PDwoKCgoKCgoKCgoPD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZAoKCgoKCg8PDw8PDw8PDw8PZGRkDw9kDw8KCgoKCgoKCgoKCg8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8KCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkDwoKCgoKDw8PDw8PDw8PDw9kZGQPDw8PDwoKCgoKCgoKCgoKDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZGQPCgoKCgoPDw8PDw8PDw8PD2RkZA8PDw8PCgoKCgoKCgoKCg8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PXGRkDwoKCgoKCg8PDw8PDw8PDw8PZGRkDw8PDw8KCgoKCgoKCgoPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8KCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKDw8PDw8PDw8PDw9kZGRkDw8PDwoKCgoKCgoKCg8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PD2RkZA8PDw8PDwoKCgoKCgoKDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDwoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PZGRkDw8PDw8PCgoKCgoKCgoPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PCgoKCgoPDwoKCg8PDw8PDw8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoPDw8PDw8PDw8PDw9kZGQPDw8PDw8KCgoKCgoKDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCg8KCgoKCg8PDw8PDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PD2RkZA8PDw8PDwoKCgoKCgoPDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PCgoKCgoKCg8PDw8PZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8KCgoKCgoKCgoPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDwoKCgoKCgoKCg8PDw8PDw8PDw8PZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PCgoKCgoKCgoKDw8PDw8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDw8KCgoKCgoKCg8PDwoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDwoKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw8PZA8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8KCgoKCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGRkDw8PDw8PCgoKCgoKDw8PD2RkZGRkDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGQPDw8PDw8PDw8PDwoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZA8PDw8PDw8PCgoKCgoKDw8PZGRkZGRkZGRkD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PD1YKCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkDw8PDw8PDw8KCgoKCgoKD2RkZEtLZGRkZGRkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PVgoKCgoKCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGQPDw8PDw8PDwoKCgoKCgoKXGRkSw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw9ZCgoKCgoKDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PCgoKCgoKCgoKVmRkZA8PDw8PZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw9aCgoKCg8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PCgoKCgoKCgoPDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkDw8PDw8PD1laCgoPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkDw8PDw8PDw8PCgoKCgoKCg8PDw8PDw8PDw9kZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw8KCgoKCgoPDw8PDw8PDw8PD2RkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRkZGRkZGRkZGRkDw8PDw8PDw8PDw8PDw9kZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZEtkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZGQPDw8PDw8PDw9kZGRkZGRkZGRkZGRkZGRLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkZEsPDw8PDw8PDw8PZGRkDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGQPDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtLDw8PDw8PDw8PD2RkZA8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZA9kZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2QPDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLSw8PDw8PDw8PDw8PDw9kDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEsPDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD2RkS2RkDw8PDw8PDw8PDw8PDw8PDw8PDw8PZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZA8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PZGRLDw9kDw8PDw8PDw8PDw8PDw8PDw8PDw9kS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw9kZEtkZGRkZGRkZGQPD2QPD2QPD2QPDw8PD2RLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZA8PDw8PDw8PDw8PDw8PDw8PD2RkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkS2RkS0tkZGRkZGRkZGRkZGRkZGRkZGRkZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRLS0tLS2RkZEtLZGRkZGRkZGRkZGRkZGRkS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS2RkZGRkZGRkZGRkZGRLZGRkZEtLS0tLS0tkZGRkZGRLS2RLZGQPZEtkZGRLZGRLS0tLS0tLS0tLS0tkZGRLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLZEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSz0AAMhCQAFKDw18dOy+FfXTwbwdhp0ZPVUAAIA/WOMSmgHXnAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAAAAAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEAAQEBAQEBAQEBAAABAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAAABAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAAAAAwMDAwMDAAAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAADAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAAABAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAAAAAAEBAQEAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgICAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAgICAAICAgICAgICAgICAgICAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAgICAgICAgICAAACAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAwMDAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAMDAwMDAwMDAwMDAwADAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAADAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAADAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAADAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAMDAwAAAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAADAwMDAwADAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAMDAwMDAAADAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAwMDAwMAAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAADAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAADAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAADAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAADAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwAAAAADAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAQG4AQHIAQTQAQHYAQHiAYsNEgRlZGdlIoINCgwNFK6vwBUpXE+/GAAKDA0ehbPAFdejML8YAQoMDShct8AV16MwvxgCCgwNMjO7wBWF6xG/GAMKDA08Cr/AFYXrEb8YBAoMDcL1wMAVrkchvxgFCgwNULjGwBWuRyG/GAYKDA3Wo8jAFYXrEb8YBwoMDdajyMAVcD2KvhgICgwNULjGwBVACle+GAkKDA3C9cDAFUAKV74YCgoMDTwKv8AVcD2KvhgLCgwNonC1wBVwPYq+GAwKDA0ehbPAFcT1qL4YDQoMDR6Fs8AVFK7HvhgOCgwNmJmxwBVmZua+GA8KDA0Urq/AFRSux74YEAoMDRSur8AVcD2KvhgRCgwNjsKtwBVACle+GBIKDA2Owq3AFfBRuL0YEwoMDf//p8AV8FG4PRgUCgwNhOupwBWYmRk+GBUKDA2Owq3AFZiZGT4YFgoMDRSur8AVQApXPhgXCgwNFK6vwBXE9ag+GBgKDA2YmbHAFRSuxz4YGQoMDaJwtcAVcD2KPhgaCgwNtx69wBVwPYo+GBsKDA08Cr/AFcT1qD4YHAoMDTwKv8AVhOsRPxgdCgwNtx69wBWuRyE/GB4KDA23Hr3AFdajMD8YHwoMDcL1wMAVKlxPPxggCgwNwvXAwBWicH0/GCEKDA08Cr/AFWdmhj8YIgoMDTwKv8AVzcysPxgjCgwNwvXAwBXherQ/GCQKDA3MzMTAFeF6tD8YJQoMDVC4xsAVzcysPxgmCgwNW4/KwBXNzKw/GCcKDA3geszAFeF6tD8YKAoMDepR0MAV4Xq0PxgpCgwNbz3SwBX1KLw/GCoKDA1vPdLAFR+Fyz8YKwoMDfQo1MAVMzPTPxgsCgwN9CjUwBW4HlVAGC0KDA1lZs7AFdijYEAYLgoMDeB6zMAV2KNgQBgvCgwN1qPIwBXsUWhAGDAKDA3MzMTAFexRaEAYMQoMDUbhwsAV4npkQBgyCgwNRuHCwBXMzFxAGDMKDA3C9cDAFcL1WEAYNAoMDbcevcAV2KNgQBg1CgwNPAq/wBXiemRAGDYKDA08Cr/AFfYobEAYNwoMDa1HucAVFK53QBg4CgwNKFy3wBUUrndAGDkKDA2icLXAFR6Fe0AYOgoMDaJwtcAVH4WDQBg7CgwNmJmxwBUpXIdAGDwKDA0Urq/AFSlch0AYPQoMDXoUpsAVw/WQQBg+CgwNo3CFwBXD9ZBAGD8KDA0ehYPAFT0Kj0AYQAoMDTwKJ8AVPQqPQBhBCgwNMjMjwBW4Ho1AGEIKDA0yMyPAFaRwTUAYQwoMDTwKJ8AVmplJQBhECgwNPAonwBVH4do/GEUKDA0yMyPAFTMz0z8YRgoMDTIzI8AV9Si8PxhHCgwNPAonwBXherQ/GEgKDA08CifAFSpcTz8YSQoMDY7CRcAVxPWoPhhKCgwNCNdzwBXE9ag+GEsKDA0SrnfAFRSuxz4YTAoMDRKud8AVhOsRPxhNCgwNHIV7wBWuRyE/GE4KDA0mXH/AFa5HIT8YTwoMDR6Fg8AV/v8/PxhQCgwNo3CFwBXWozA/GFEKDA2tR4nAFdajMD8YUgoMDTwKj8AVUrhePxhTCgwNPAqPwBVnZoY/GFQKDA1G4ZLAFY/ClT8YVQoMDeB6nMAVj8KVPxhWCgwNZWaewBV7FI4/GFcKDA1lZp7AFWdmhj8YWAoMDepRoMAVonB9PxhZCgwN6lGgwBV6FG4/GFoKDA1wPaLAFVK4Xj8YWwoMDXA9osAV/v8/PxhcCgwNehSmwBWuRyE/GF0KDA16FKbAFVyPAj8YXgoMDXA9osAVFK7HPhhfCgwN4HqcwBUUrsc+GGAKDA1bj5rAFWhm5j4YYQoMDczMlMAVaGbmPhhiCgwNRuGSwBUUrsc+GGMKDA1G4ZLAFfBRuD0YZAoMDczMlMAVgML1PBhlCgwNzMyUwBXwUbi9GGYKDA1G4ZLAFZiZGb4YZwoMDUbhksAVcD2KvhhoCgwNzMyUwBXE9ai+GGkKDA1bj5rAFcT1qL4YagoMDeB6nMAVcD2KvhhrCgwNZWaewBVwPYq+GGwKDA3qUaDAFcT1qL4YbQoMDXA9osAVxPWovhhuCgwN9CikwBUUrse+GG8KDA16FKbAFRSux74YcAoMDf//p8AVZmbmvhhxCgwN//+nwBVcjwK/GHIKDA16FKbAFYXrEb8YcwoMDXoUpsAVAABAvxh0CgwN//+nwBUpXE+/GHUKDA0Urq/AFSlcT78YduIBuwMSBGVkZ2UisgMKDA2AFI4/FXsUjr8YAAoMDWhmhj8VZmaGvxgBCgwNaGaGPxUpXE+/GAIKDA2wcH0/FQAAQL8YAwoMDbBwfT8VXI8CvxgECgwNgBSOPxUUrse+GAUKDA2AFI4/FYDC9bwYBgoMDWhmhj8VgML1PBgHCgwNaGaGPxWE6xE/GAgKDA2QwpU/FYTrET8YCQoMDbgepT8V1qMwPxgKCgwN6Hq0PxXWozA/GAsKDA34KLw/Fa5HIT8YDAoMDRDXwz8V1qMwPxgNCgwNODPTPxXWozA/GA4KDA1I4do/Ff7/Pz8YDwoMDfgoDEAV/v8/PxgQCgwNAAAQQBXWozA/GBEKDA0M1xNAFdajMD8YEgoMDRiuF0AV/v8/PxgTCgwNGK4XQBVSuF4/GBQKDA0M1xNAFXoUbj8YFQoMDQzXE0AVZ2aGPxgWCgwNIIUbQBWPwpU/GBcKDA1ACidAFY/ClT8YGAoMDUjhKkAVexSOPxgZCgwNSOEqQBWkcH2/GBoKDA1ACidAFWZmhr8YGwoMDUjh2j8VZmaGvxgcCgwNODPTPxV7FI6/GB0KDA2AFI4/FXsUjr8YHuIBxwsSBGVkZ2UivgsKDA2AmRm+FUAKV74YAAoMDQAKV74VmJkZvhgBCgwNWI8CvxWYmRm+GAIKDA2A6xG/FfBRuL0YAwoMDYDrEb8VmJkZPhgECgwN0KMwvxVwPYo+GAUKDA34/z+/FXA9ij4YBgoMDSBcT78VxPWoPhgHCgwNeBSOvxXE9ag+GAgKDA2MwpW/FRSuxz4YCQoMDUTh2r8VFK7HPhgKCgwNWI/ivxXE9ag+GAsKDA2C60HAFcT1qD4YDAoMDTIzI8AVKlxPPxgNCgwNMjMjwBWPwpU/GA4KDA0mXB/AFaNwnT8YDwoMDRKuF8AVo3CdPxgQCgwNCNcTwBWPwpU/GBEKDA3+/w/AFY/ClT8YEgoMDfQoDMAVo3CdPxgTCgwN9CgMwBW5HqU/GBQKDA3qUQjAFc3MrD8YFQoMDepRCMAV4Xq0PxgWCgwN4HoEwBX1KLw/GBcKDA3gegTAFUfh2j8YGAoMDdSjAMAVW4/iPxgZCgwNlJn5vxVbj+I/GBoKDA1sPeq/FYXr8T8YGwoMDWw96r8VmZn5PxgcCgwNWI/ivxXWowBAGB0KDA2A6/G/FexRCEAYHgoMDZSZ+b8V4noEQBgfCgwNlJn5vxWZmfk/GCAKDA3UowDAFYXr8T8YIQoMDepRCMAVhevxPxgiCgwN9CgMwBWZmfk/GCMKDA30KAzAFfYoDEAYJAoMDQjXE8AVCtcTQBglCgwNCNcTwBUehRtAGCYKDA0SrhfAFShcH0AYJwoMDRKuF8AVkMJFQBgoCgwNCNcTwBWamUlAGCkKDA0SrhfAFaRwTUAYKgoMDRKuF8AVuB5VQBgrCgwNCNcTwBXC9VhAGCwKDA0I1xPAFeJ6ZEAYLQoMDf7/D8AV7FFoQBguCgwNbD3qvxXsUWhAGC8KDA1Yj+K/FfYobEAYMAoMDZhwfb8V9ihsQBgxCgwNcBRuvxXsUWhAGDIKDA1IuF6/FexRaEAYMwoMDSBcT78V4npkQBg0CgwNIFxPvxXMzFxAGDUKDA1wFG6/FbgeVUAYNgoMDWRmhr8VuB5VQBg3CgwNjMKVvxXMzFxAGDgKDA3IzKy/FczMXEAYOQoMDfAovL8VuB5VQBg6CgwN8Ci8vxWuR1FAGDsKDA3cerS/FaRwTUAYPAoMDdx6tL8VmplJQBg9CgwNyMysvxWQwkVAGD4KDA1kZoa/FZDCRUAYPwoMDZhwfb8VhutBQBhACgwNmHB9vxVmZjZAGEEKDA1kZoa/FVyPMkAYQgoMDbQepb8VXI8yQBhDCgwNyMysvxVSuC5AGEQKDA3IzKy/FT4KJ0AYRQoMDbQepb8VMjMjQBhGCgwNtB6lvxUUrhdAGEcKDA3IzKy/FQrXE0AYSAoMDcjMrL8VcT3qPxhJCgwN3Hq0vxVbj+I/GEoKDA3cerS/FR+Fyz8YSwoMDfAovL8VCdfDPxhMCgwNCNfDvxUJ18M/GE0KDA0chcu/FfUovD8YTgoMDRyFy78V4Xq0PxhPCgwNCNfDvxXNzKw/GFAKDA0I18O/FbkepT8YUQoMDfAovL8Vo3CdPxhSCgwN3Hq0vxW5HqU/GFMKDA2oRyG/FbkepT8YVAoMDViPAr8Vj8KVPxhVCgwNgBRuPxWPwpU/GFYKDA2wcH0/FXsUjj8YVwoMDbBwfT8VUrhePxhYCgwNgBRuPxUqXE8/GFkKDA2AFG4/FYTrET8YWgoMDbBwfT8VXI8CPxhbCgwNsHB9PxXwUbg9GFwKDA2AFG4/FZiZGT4YXQoMDeCjMD8VmJkZPhheCgwNsEchPxXwUbg9GF8KDA2wRyE/FYDC9TwYYAoMDZDrET8VgML1vBhhCgwNkOsRPxWYmRm+GGIKDA1gjwI/FUAKV74YYwoMDUAKVz4VQApXvhhkCgwNwJkZPhWYmRm+GGUKDA0AxPU8FZiZGb4YZgoMDQDC9bwVQApXvhhnCgwNgJkZvhVACle+GGjiASQSBGRvb3IiHAoMDY7CRcAVxPWoPhgACgwNPAonwBWjcJ0/GAHiASQSBGRvb3IiHAoMDWhmhj8VgML1PBgACgwNaGaGPxWE6xE/GAFKCAgDQGRIAFgUUAGCARcKABgAIAAoADAAWABgAIABAIgBApoBAPgBAZgCAqACAugCAA== \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 1c5973d..ea823ed 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -598,6 +598,7 @@ async def test_stop_monitoring_cancels_task(): "method_name, args, expected_action", [ ("find_robot", (), "find_robot"), + ("request_map", (), "request_map"), ("set_suction", (SuctionLevel.ECO,), "set_suction_eco"), ("set_suction", ("normal",), "set_suction_normal"), ("set_suction", (SuctionLevel.MAX,), "set_suction_max"), @@ -815,6 +816,46 @@ async def _cb(status: VacuumStatus) -> None: assert client.last_map is persisted # a later map-less status keeps it +def _job_map(*, report: bool, job_started: int) -> VacuumMap: + vacuum_map = _persisted_map() + vacuum_map.report = report + vacuum_map.job_started = job_started + return vacuum_map + + +async def test_requested_map_does_not_replace_the_report_of_the_same_job(): + client = _make_vacuum_client_with_mocks() + client.on_status_update(lambda s: None) + report = _job_map(report=True, job_started=100) + await client._on_monitor_status(VacuumStatus(mode=VacuumMode.DOCKED, map=report)) + await client._on_monitor_status( + VacuumStatus(mode=VacuumMode.DOCKED, map=_job_map(report=False, job_started=100)) + ) + assert client.last_map is report # keeps the event log + + +async def test_requested_map_of_a_newer_job_replaces_the_report(): + client = _make_vacuum_client_with_mocks() + client.on_status_update(lambda s: None) + await client._on_monitor_status( + VacuumStatus(mode=VacuumMode.DOCKED, map=_job_map(report=True, job_started=100)) + ) + requested = _job_map(report=False, job_started=200) + await client._on_monitor_status(VacuumStatus(mode=VacuumMode.DOCKED, map=requested)) + assert client.last_map is requested + + +async def test_report_replaces_a_requested_map_of_the_same_job(): + client = _make_vacuum_client_with_mocks() + client.on_status_update(lambda s: None) + await client._on_monitor_status( + VacuumStatus(mode=VacuumMode.DOCKED, map=_job_map(report=False, job_started=100)) + ) + report = _job_map(report=True, job_started=100) + await client._on_monitor_status(VacuumStatus(mode=VacuumMode.DOCKED, map=report)) + assert client.last_map is report + + async def test_start_monitoring_wraps_callback(): client = _make_vacuum_client_with_mocks() client._status_callback = lambda s: None diff --git a/tests/test_vacuum_map.py b/tests/test_vacuum_map.py index 832fdfd..9ab89da 100644 --- a/tests/test_vacuum_map.py +++ b/tests/test_vacuum_map.py @@ -238,9 +238,32 @@ def test_decode_job_start_only(): fields = protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field(11, stats) vacuum_map = decode_map(_frame(fields)) assert vacuum_map.job_started == 1789867008 + assert vacuum_map.job_minutes is None assert not hasattr(vacuum_map, "job_duration") +def test_decode_job_minutes_and_clean_time_without_a_log(): + stats = protobuf.encode_varint_field(1, 1790503168) + protobuf.encode_varint_field(2, 33) + fields = protobuf.encode_bytes_field(5, _grid_message()) + protobuf.encode_bytes_field(11, stats) + vacuum_map = decode_map(_frame(fields)) + assert vacuum_map.job_minutes == 33 + assert vacuum_map.clean_time == 33 * 60 # no log: the summary's minutes + + +@pytest.mark.parametrize("map_type, persisted", [(4, True), (1, False), (2, False)]) +def test_floor_type_4_is_persisted_without_the_report_flag(map_type, persisted): + fields = protobuf.encode_varint_field(4, map_type) + protobuf.encode_bytes_field(5, _grid_message()) + vacuum_map = decode_map(_frame(fields)) + assert vacuum_map.persisted is persisted + assert vacuum_map.report is False + + +def test_report_flag_marks_a_report(): + vacuum_map = decode_map(_frame(protobuf.encode_bytes_field(5, _grid_message()), persisted=True)) + assert vacuum_map.persisted is True + assert vacuum_map.report is True + + # --------------------------------------------------------------------------- # decode_map — event log (field 20.3.6) # --------------------------------------------------------------------------- @@ -316,6 +339,33 @@ def test_decode_real_persisted_frame(): assert vacuum_map.cleaned_area == pytest.approx(3.17, abs=0.01) assert len(vacuum_map.log) == 83 assert [e.code for e in vacuum_map.log if e.key == "DT_WARNING_CODE"] == ["WARN_MM_LOWLIGHT"] + assert vacuum_map.report is True + assert vacuum_map.job_minutes == 3 # a 216 s job, docking included + + +def test_decode_real_requested_frame(): + # The reply to request_map() while docked, after a whole-home job whose + # end-of-job frame Home Assistant missed: the same saved map with that + # job's path and summary, but no event log and no report flag. + vacuum_map = decode_map(_fields(_fixture("sharkiq_requested_map_frame.b64"))) + assert vacuum_map.persisted is True + assert vacuum_map.report is False + assert vacuum_map.map_id == "3CDBBC59" + assert [room.name for room in vacuum_map.rooms] == ["Bathroom. ", "Room", "Laundry Room", "Hallway"] + assert vacuum_map.job_started == 1790503168 + assert vacuum_map.job_minutes == 33 + assert vacuum_map.log == [] + assert vacuum_map.clean_time == 33 * 60 + assert len(vacuum_map.path) == 1671 + assert vacuum_map.cleaned_area == pytest.approx(25.61, abs=0.01) + # A whole-home run: all but 3 of its 1671 path points land on cleaned cells. + grid = vacuum_map.grid + on_cleaned = sum( + grid.cell(int((p.x - grid.origin.x) / grid.resolution), int((p.y - grid.origin.y) / grid.resolution)) + == MapGrid.CLEANED + for p in vacuum_map.path + ) + assert on_cleaned == 1668 @pytest.mark.parametrize("name", ["sharkiq_live_map_frame.b64", "sharkiq_persisted_map_frame.b64"]) @@ -381,6 +431,21 @@ def test_map_survives_json_round_trip(persisted_map): assert restored == persisted_map +def test_map_stored_before_report_existed_reads_as_a_report(persisted_map): + # Before request_map() only end-of-job reports were persisted. + stored = json.loads(json.dumps(persisted_map.to_dict())) + del stored["report"], stored["job_minutes"] + restored = VacuumMap.from_dict(stored) + assert restored.report is True + assert restored.job_minutes is None + + +def test_requested_map_survives_json_round_trip(): + requested = decode_map(_fields(_fixture("sharkiq_requested_map_frame.b64"))) + restored = VacuumMap.from_dict(json.loads(json.dumps(requested.to_dict()))) + assert (restored.report, restored.job_minutes) == (False, 33) + + def test_stored_map_still_builds_the_app_room_command(persisted_map): # The reason the map is stored at all: after a restart, room cleaning must # send exactly what the app sends, from the stored copy. From b14e4b7192eaf3f478791d89f5044a8e9b3afbe6 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 11:08:56 -0400 Subject: [PATCH 28/33] Version 0.3.0.dev11 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 501ff03..22d9868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev10" +version = "0.3.0.dev11" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From bb035fe40637a2584d8bf6a4f219652d897db235 Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 11:47:51 -0400 Subject: [PATCH 29/33] Import aiomqtt once, at module level The four call sites imported aiomqtt on first use, and inside Home Assistant that first use is the monitor starting in the event loop, which HA flags as a blocking call (the import reads the package from disk). It is imported once when the module loads, still optional: without it the MQTT calls raise the same ConnectError. Also asyncio.iscoroutinefunction -> inspect.iscoroutinefunction, deprecated for removal in Python 3.16. --- sharklocal/client.py | 3 ++- sharklocal/mqtt_client.py | 48 +++++++++++++++++++-------------------- tests/test_mqtt_client.py | 31 ++++--------------------- 3 files changed, 30 insertions(+), 52 deletions(-) diff --git a/sharklocal/client.py b/sharklocal/client.py index 8f0bfc2..d257710 100644 --- a/sharklocal/client.py +++ b/sharklocal/client.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import inspect from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Union @@ -422,7 +423,7 @@ async def _on_monitor_status(self, status: VacuumStatus) -> None: if status.map is not None and status.map.persisted and not self._same_job_poorer(status.map): self.last_map = status.map callback = self._status_callback - if asyncio.iscoroutinefunction(callback): + if inspect.iscoroutinefunction(callback): await callback(status) else: callback(status) # type: ignore[misc] diff --git a/sharklocal/mqtt_client.py b/sharklocal/mqtt_client.py index b0549c2..0d67798 100644 --- a/sharklocal/mqtt_client.py +++ b/sharklocal/mqtt_client.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import inspect import base64 from typing import Any, Callable, Dict, List, Optional @@ -11,6 +12,20 @@ from .models import VacuumMode, VacuumStatus from . import protobuf, vacuum_map +# Imported once, here, rather than at first use: importing reads the package +# from disk, and a first use inside Home Assistant's event loop is flagged as a +# blocking call. Still optional, so a REST-only install works without it. +try: + import aiomqtt +except ImportError: # pragma: no cover - exercised by patching this name + aiomqtt = None # type: ignore[assignment] + + +def _require_aiomqtt() -> Any: + if aiomqtt is None: + raise ConnectError("aiomqtt is required for MQTT support. Install with: pip install aiomqtt") + return aiomqtt + # Registry mapping decoder name -> callable(payload_bytes, modes) -> VacuumStatus. # Additional decoders for new models can be registered with @register_decoder. @@ -240,17 +255,11 @@ async def call(self, action: str) -> Any: spec = self.mapping.actions[action] - try: - import aiomqtt - except ImportError as exc: - raise ConnectError( - "aiomqtt is required for MQTT support. " - "Install with: pip install aiomqtt" - ) from exc + mqtt = _require_aiomqtt() try: if spec.type == "command": - async with aiomqtt.Client(self.host, port=self.mapping.port) as client: + async with mqtt.Client(self.host, port=self.mapping.port) as client: await client.publish(self.mapping.command_topic, payload=spec.payload) return True @@ -277,17 +286,14 @@ async def send(self, payload: bytes) -> bool: Raises: ConnectError: If the MQTT broker cannot be reached. """ - try: - import aiomqtt - except ImportError as exc: - raise ConnectError("aiomqtt is required for MQTT support") from exc + mqtt = _require_aiomqtt() encoded: Any = payload if self.mapping.encoding == "base64": encoded = base64.b64encode(payload).decode("ascii") try: - async with aiomqtt.Client(self.host, port=self.mapping.port) as client: + async with mqtt.Client(self.host, port=self.mapping.port) as client: await client.publish(self.mapping.command_topic, payload=encoded) except Exception as exc: raise ConnectError( @@ -297,12 +303,9 @@ async def send(self, payload: bytes) -> bool: async def _request_status(self, command_payload: str, timeout: float) -> VacuumStatus: """Publish a status-request command and return the decoded first response.""" - try: - import aiomqtt - except ImportError as exc: - raise ConnectError("aiomqtt is required for MQTT support") from exc + mqtt = _require_aiomqtt() - async with aiomqtt.Client(self.host, port=self.mapping.port) as client: + async with mqtt.Client(self.host, port=self.mapping.port) as client: await client.subscribe(self.mapping.status_topic) await client.publish(self.mapping.command_topic, payload=command_payload) try: @@ -332,13 +335,10 @@ async def monitor( stop_event: Optional ``asyncio.Event``; when set, monitoring stops cleanly after the current message. """ - try: - import aiomqtt - except ImportError as exc: - raise ConnectError("aiomqtt is required for MQTT support") from exc + mqtt = _require_aiomqtt() try: - async with aiomqtt.Client(self.host, port=self.mapping.port) as client: + async with mqtt.Client(self.host, port=self.mapping.port) as client: await client.subscribe(self.mapping.status_topic) async for message in client.messages: if stop_event and stop_event.is_set(): @@ -347,7 +347,7 @@ async def monitor( status = self._decode_status(bytes(message.payload)) except (DecoderError, CommandError): continue # Skip malformed messages silently - if asyncio.iscoroutinefunction(callback): + if inspect.iscoroutinefunction(callback): await callback(status) else: callback(status) diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index f6348cb..72bfec6 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -618,14 +618,7 @@ async def test_call_aiomqtt_import_error_raises_connect_error(mqtt_mapping): """If aiomqtt cannot be imported, call() raises ConnectError.""" client = MQTTVacuumClient("host", mqtt_mapping) - original_import = __builtins__.__import__ if hasattr(__builtins__, "__import__") else __import__ - - def _failing_import(name, *args, **kwargs): - if name == "aiomqtt": - raise ImportError("No module named 'aiomqtt'") - return original_import(name, *args, **kwargs) - - with patch("builtins.__import__", side_effect=_failing_import): + with patch("sharklocal.mqtt_client.aiomqtt", None): with pytest.raises(ConnectError, match="aiomqtt is required"): await client.call("start_cleaning") @@ -639,15 +632,7 @@ async def test_request_status_aiomqtt_import_error_raises_connect_error(mqtt_map """If aiomqtt cannot be imported in _request_status, ConnectError is raised.""" client = MQTTVacuumClient("host", mqtt_mapping) - import builtins - real_import = builtins.__import__ - - def _failing_import(name, *args, **kwargs): - if name == "aiomqtt": - raise ImportError("No module named 'aiomqtt'") - return real_import(name, *args, **kwargs) - - with patch("builtins.__import__", side_effect=_failing_import): + with patch("sharklocal.mqtt_client.aiomqtt", None): with pytest.raises(ConnectError, match="aiomqtt is required"): await client._request_status("DDDD", 5.0) @@ -661,15 +646,7 @@ async def test_monitor_aiomqtt_import_error_raises_connect_error(mqtt_mapping): """If aiomqtt cannot be imported in monitor(), ConnectError is raised.""" client = MQTTVacuumClient("host", mqtt_mapping) - import builtins - real_import = builtins.__import__ - - def _failing_import(name, *args, **kwargs): - if name == "aiomqtt": - raise ImportError("No module named 'aiomqtt'") - return real_import(name, *args, **kwargs) - - with patch("builtins.__import__", side_effect=_failing_import): + with patch("sharklocal.mqtt_client.aiomqtt", None): with pytest.raises(ConnectError, match="aiomqtt is required"): await client.monitor(lambda s: None) @@ -820,6 +797,6 @@ async def test_send_wraps_broker_errors_in_connect_error(mqtt_mapping): async def test_send_without_aiomqtt_raises_connect_error(mqtt_mapping): client = MQTTVacuumClient("host", mqtt_mapping) - with patch.dict(sys.modules, {"aiomqtt": None}): + with patch("sharklocal.mqtt_client.aiomqtt", None): with pytest.raises(ConnectError, match="aiomqtt is required"): await client.send(b"\x00") From 27455aace1a725c6cf201115395b782c281d396d Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 11:47:51 -0400 Subject: [PATCH 30/33] Version 0.3.0.dev12 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 22d9868..1e9e4c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev11" +version = "0.3.0.dev12" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 816dd25e4a79309ee5f24886526874af2976c12f Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 12:55:03 -0400 Subject: [PATCH 31/33] A job's empty opening grid is no map Each job opens with a 78-byte live frame whose grid has no resolution, size or cells (captured on a Matrix clean, 2026-09-27). It decoded as a 0x0 map at resolution 0, and Home Assistant's renderer divided by it: every picture request failed with a 500 until the next real frame. has_map and decode_map now need a grid with cells, size and resolution. Fixture: that frame. --- sharklocal/vacuum_map.py | 14 ++++++++++++-- tests/fixtures/sharkiq_empty_live_map_frame.b64 | 1 + tests/test_vacuum_map.py | 9 +++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sharkiq_empty_live_map_frame.b64 diff --git a/sharklocal/vacuum_map.py b/sharklocal/vacuum_map.py index 9e1acb7..a3e0d04 100644 --- a/sharklocal/vacuum_map.py +++ b/sharklocal/vacuum_map.py @@ -72,7 +72,17 @@ def _text(value: Optional[bytes]) -> Optional[str]: def has_map(fields: Dict[int, List[Any]]) -> bool: """``True`` if a decoded status frame carries an occupancy grid.""" blob = _first(fields, FIELD_MAP) - return isinstance(blob, bytes) and 5 in _fields(blob) + return isinstance(blob, bytes) and _drawable(_first(_fields(blob), 5)) + + +def _drawable(grid_blob: Any) -> bool: + """A grid with cells to draw. A job opens with a live frame whose grid is + empty (no resolution, size or cells, measured on a Matrix clean): that + says the job's map starts afresh, not that there is one to draw.""" + if not isinstance(grid_blob, bytes): + return False + g = _fields(grid_blob) + return bool(_first(g, 6)) and bool(_first(g, 3)) and bool(_first(g, 4)) and 1 in g def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: @@ -89,7 +99,7 @@ def decode_map(fields: Dict[int, List[Any]]) -> Optional[VacuumMap]: if not isinstance(blob, bytes): return None m = _fields(blob) - if 5 not in m: + if not _drawable(_first(m, 5)): return None grid = _decode_grid(m[5][0]) diff --git a/tests/fixtures/sharkiq_empty_live_map_frame.b64 b/tests/fixtures/sharkiq_empty_live_map_frame.b64 new file mode 100644 index 0000000..0a2f203 --- /dev/null +++ b/tests/fixtures/sharkiq_empty_live_map_frame.b64 @@ -0,0 +1 @@ +CL+LeBBKIAY6JCABKgUSAIgBAUIHDW8SAzsQAlIHDW8SAzsQAmDmtAJoAKIBAEoICANAS0gAWBRQAYIBBVgAmgEAmAICoAICyAIE6AIC \ No newline at end of file diff --git a/tests/test_vacuum_map.py b/tests/test_vacuum_map.py index 9ab89da..639e52b 100644 --- a/tests/test_vacuum_map.py +++ b/tests/test_vacuum_map.py @@ -305,6 +305,15 @@ def test_decode_log_tolerates_missing_or_invalid_parts(): # --------------------------------------------------------------------------- +def test_empty_grid_at_job_start_is_no_map(): + # The first live frame of a job carries a grid with no resolution, size or + # cells; drawing it divided by zero. + fields = _fields(_fixture("sharkiq_empty_live_map_frame.b64")) + assert 7 in fields + assert has_map(fields) is False + assert decode_map(fields) is None + + def test_decode_real_live_frame(): vacuum_map = decode_map(_fields(_fixture("sharkiq_live_map_frame.b64"))) assert vacuum_map.persisted is False From d90d9b9f86667b67c94c9f27727c9e0b0bd8bd2c Mon Sep 17 00:00:00 2001 From: Andrew Hunt Date: Sun, 27 Sep 2026 12:55:03 -0400 Subject: [PATCH 32/33] Version 0.3.0.dev13 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1e9e4c8..bf8ef52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev12" +version = "0.3.0.dev13" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } From 76d335bdc8ead49ab8b1c3f833c0aebbb3abedbc Mon Sep 17 00:00:00 2001 From: Ryan Ellerton Date: Mon, 26 Oct 2026 17:18:35 -0400 Subject: [PATCH 33/33] Harden local discovery on Windows --- pyproject.toml | 2 +- sharklocal/__main__.py | 6 ++++++ sharklocal/rest_client.py | 7 ++++++- tests/test_main.py | 13 +++++++++++++ tests/test_rest_client.py | 15 ++++++++++++++- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf8ef52..b6df91e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sharklocal" -version = "0.3.0.dev13" +version = "0.3.0.dev14" description = "Local control library for Shark robot vacuums, for use with Home Assistant" readme = "README.md" license = { text = "MIT" } diff --git a/sharklocal/__main__.py b/sharklocal/__main__.py index 71ec1c7..825c291 100644 --- a/sharklocal/__main__.py +++ b/sharklocal/__main__.py @@ -356,6 +356,12 @@ def save_report(results: Dict[str, Any], save_report_arg: Any): print(f"\nReport saved to: {os.path.abspath(filename)}") def main(): + # aiomqtt requires the selector event loop on Windows. Python otherwise + # defaults to the proactor loop there, which fails before an MQTT probe can + # connect. + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + parser = setup_argparse() args = parser.parse_args() diff --git a/sharklocal/rest_client.py b/sharklocal/rest_client.py index c9451b9..5f121d6 100644 --- a/sharklocal/rest_client.py +++ b/sharklocal/rest_client.py @@ -11,6 +11,8 @@ from .mappings.base import RESTMappingConfig from .models import DeviceInfo, VacuumEvent, VacuumMode, VacuumStatus +REST_REQUEST_TIMEOUT = 5.0 + class RESTVacuumClient: """Async HTTP/HTTPS client for local vacuum control via the REST API. @@ -50,7 +52,10 @@ def _make_connector(self) -> aiohttp.TCPConnector: async def _get_session(self) -> aiohttp.ClientSession: if self._session is None or self._session.closed: - self._session = aiohttp.ClientSession(connector=self._make_connector()) + self._session = aiohttp.ClientSession( + connector=self._make_connector(), + timeout=aiohttp.ClientTimeout(total=REST_REQUEST_TIMEOUT), + ) return self._session async def close(self) -> None: diff --git a/tests/test_main.py b/tests/test_main.py index 9fd3964..cad2f74 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -434,6 +434,19 @@ def test_main_probe_dispatch(): mock_run.assert_called_once_with("1.2.3.4") +def test_main_uses_selector_event_loop_on_windows(): + """aiomqtt is incompatible with Windows' default proactor event loop.""" + policy = MagicMock() + with patch("sys.platform", "win32"), \ + patch("sys.argv", ["sharklocal", "1.2.3.4", "--probe"]), \ + patch("sharklocal.__main__.asyncio.WindowsSelectorEventLoopPolicy", return_value=policy, create=True), \ + patch("sharklocal.__main__.asyncio.set_event_loop_policy") as set_policy, \ + patch("sharklocal.__main__.run_probe", new_callable=AsyncMock): + main() + + set_policy.assert_called_once_with(policy) + + def test_main_save_report(): """Test --save-report flag.""" results = {"host": "1.2", "model": "M1", "rest": {}, "mqtt": {}, "fw": None} diff --git a/tests/test_rest_client.py b/tests/test_rest_client.py index 1f91df6..246ad03 100644 --- a/tests/test_rest_client.py +++ b/tests/test_rest_client.py @@ -12,7 +12,7 @@ from sharklocal.exceptions import ActionNotSupportedError, CommandError, ConnectError from sharklocal.mappings.base import RESTMappingConfig from sharklocal.models import DeviceInfo, VacuumEvent, VacuumMode, VacuumStatus -from sharklocal.rest_client import RESTVacuumClient +from sharklocal.rest_client import REST_REQUEST_TIMEOUT, RESTVacuumClient # --------------------------------------------------------------------------- @@ -117,6 +117,19 @@ async def test_ssl_context_is_built_at_construction_not_in_the_event_loop(rest_m await connector.close() +async def test_session_has_bounded_request_timeout(rest_mapping): + """An unreachable REST endpoint must not block MQTT discovery forever.""" + rest_mapping.transport = "http" + client = RESTVacuumClient("host", rest_mapping) + + with patch("sharklocal.rest_client.aiohttp.ClientSession") as session_cls: + session_cls.return_value.closed = False + await client._get_session() + + timeout = session_cls.call_args.kwargs["timeout"] + assert timeout.total == REST_REQUEST_TIMEOUT + + # --------------------------------------------------------------------------- # _get_session() # ---------------------------------------------------------------------------