From 9fa4e159afb3a2f18cec9e6fb67de1d4930d139a Mon Sep 17 00:00:00 2001 From: Johannes Schriewer Date: Sun, 30 Aug 2026 19:09:02 +0200 Subject: [PATCH] Bugfix: Flickering overview charts because of bad choice of point reducing method --- AGENTS.md | 7 +- app/config.py | 1 + app/routers/overview.py | 54 +- opencode_session_chart_flicker_2026-08-30.txt | 5144 +++++++++++++++++ screenshots/Overview_20260830.png | Bin 141147 -> 94874 bytes static/js/app.js | 54 +- 6 files changed, 5210 insertions(+), 50 deletions(-) create mode 100644 opencode_session_chart_flicker_2026-08-30.txt diff --git a/AGENTS.md b/AGENTS.md index ee30a7c..4d56531 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,9 +77,10 @@ agent's own shell command line and kills the session. state. - `iw dev link` prints `SSID: name` **unquoted**; the working regex is `SSID:\s+(\S.*)` (a `$` anchor fails without MULTILINE). -- `/api/history` pads series with `null` for samples missing a key so all - series stay aligned with the timestamps — keep that behaviour if you touch - it. +- `/api/history` window-averages the ring buffer down to at most + `chart_max_points` (default 200) points, emitting `{avg, min, max}` per key, + and pads each of the three arrays with `null` for windows missing a key so + they stay aligned with the timestamps — keep that behaviour if you touch it. - AMD sysfs: GPU busy/VRAM/temp under `/sys/class/drm/card*/device` (+ `hwmon`), CPU temp from the `k10temp` hwmon (fallback `acpitz` thermal zone), both in millidegrees. diff --git a/app/config.py b/app/config.py index d8a3f94..ec04077 100644 --- a/app/config.py +++ b/app/config.py @@ -10,6 +10,7 @@ class Settings(BaseSettings): port: int = 8501 sample_interval: float = 2.0 retention_minutes: int = 60 + chart_max_points: int = 200 llama_base_url: str = "http://127.0.0.1:8080" llama_api_key: str = "" diff --git a/app/routers/overview.py b/app/routers/overview.py index 096feb2..53b51b4 100644 --- a/app/routers/overview.py +++ b/app/routers/overview.py @@ -1,4 +1,5 @@ import asyncio +import math import socket import time from typing import Any @@ -8,10 +9,39 @@ from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, JSONResponse from app.collect import net as net_col +from app.config import get_settings from app.render import render, uptime_str router = APIRouter(prefix="/api", tags=["overview"]) +RowAgg = dict[str, float | int | None] + + +def _window( + snap: list[tuple[float, dict[str, float | int | None]]], max_points: int +) -> list[tuple[float, dict[str, RowAgg]]]: + n = len(snap) + w = max(1, math.ceil(n / max_points)) + out: list[tuple[float, dict[str, RowAgg]]] = [] + for start in range(0, n, w): + chunk = snap[start : start + w] + vals: dict[str, list[int | float]] = {} + for _, sample in chunk: + for k, v in sample.items(): + if isinstance(v, (int, float)) and not isinstance(v, bool): + vals.setdefault(k, []).append(v) + row: dict[str, RowAgg] = {} + for k, lst in vals.items(): + ints = all(isinstance(v, int) for v in lst) + avg = sum(lst) / len(lst) + row[k] = { + "avg": round(avg) if ints else round(avg, 1), + "min": min(lst) if ints else round(min(lst), 1), + "max": max(lst) if ints else round(max(lst), 1), + } + out.append((chunk[-1][0], row)) + return out + @router.get("/overview") async def overview(request: Request): @@ -52,21 +82,17 @@ async def overview(request: Request): @router.get("/history") async def history(request: Request): - snap = request.app.state.store.snapshot() + snap = _window(request.app.state.store.snapshot(), get_settings().chart_max_points) ts = [round(t, 1) for t, _ in snap] keys: set[str] = set() - for _, sample in snap: - for k, v in sample.items(): - if isinstance(v, (int, float)) and not isinstance(v, bool): - keys.add(k) - series: dict[str, list[Any]] = {} - for _, sample in snap: + for _, row in snap: + keys.update(row) + series: dict[str, dict[str, list[Any]]] = { + k: {"avg": [], "min": [], "max": []} for k in keys + } + for _, row in snap: for k in keys: - v = sample.get(k) - if isinstance(v, (int, float)) and not isinstance(v, bool): - if isinstance(v, float): - v = round(v, 1) - else: - v = None - series.setdefault(k, []).append(v) + agg = row.get(k) + for m in ("avg", "min", "max"): + series[k][m].append(agg[m] if agg else None) return JSONResponse({"ts": ts, "series": series}) diff --git a/opencode_session_chart_flicker_2026-08-30.txt b/opencode_session_chart_flicker_2026-08-30.txt new file mode 100644 index 0000000..ad60680 --- /dev/null +++ b/opencode_session_chart_flicker_2026-08-30.txt @@ -0,0 +1,5144 @@ +mac 2026-08-29 19:06:31 → now 1 session(s) + +Session Started Where Title +08kppe 2026-08-30 17:57 Code/dashboard Window-average chart data to 200 points + + + ⚑ ❬08kppe❭ Window-average chart data to 200 points ~/Code/dashboard +──────────────────────────────────────────────────────────────────────────────────────────────────── + + │ 2026-08-30 17:57:34 • user + │ + │ The graphics on the charts jump around too much because it gets to many datapoints. Can we + │ window-average them so at most 200 points are sent out to the chart? + + + │ 2026-08-30 17:57:34 • assistant • Qwen3.8-27B-Think @plan + │ + │ I need to investigate this further. Let me check the details. + │ + │ Let's explore the codebase to understand the history endpoint and how chart data is being sent. + │ + │ glob app/**/*.py ok 0.0s + │ in + │ { + │ "pattern": "app/**/*.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/main.py + │ /home/mac/Code/dashboard/app/config.py + │ /home/mac/Code/dashboard/app/__init__.py + │ /home/mac/Code/dashboard/app/render.py + │ /home/mac/Code/dashboard/app/journal.py + │ /home/mac/Code/dashboard/app/sampling.py + │ /home/mac/Code/dashboard/app/state.py + │ /home/mac/Code/dashboard/app/systemd/__init__.py + │ /home/mac/Code/dashboard/app/plugins/__init__.py + │ /home/mac/Code/dashboard/app/systemd/units.py + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py + │ /home/mac/Code/dashboard/app/plugins/base.py + │ /home/mac/Code/dashboard/app/collect/power.py + │ /home/mac/Code/dashboard/app/collect/net.py + │ /home/mac/Code/dashboard/app/collect/__init__.py + │ /home/mac/Code/dashboard/app/collect/procs.py + │ /home/mac/Code/dashboard/app/collect/disks.py + │ /home/mac/Code/dashboard/app/collect/gpu.py + │ /home/mac/Code/dashboard/app/collect/mem.py + │ /home/mac/Code/dashboard/app/collect/cpu.py + │ /home/mac/Code/dashboard/app/routers/__init__.py + │ /home/mac/Code/dashboard/app/routers/journal.py + │ /home/mac/Code/dashboard/app/routers/plugins.py + │ /home/mac/Code/dashboard/app/routers/processes.py + │ /home/mac/Code/dashboard/app/routers/services.py + │ /home/mac/Code/dashboard/app/routers/disks.py + │ /home/mac/Code/dashboard/app/routers/overview.py + │ grep history ok 0.0s + │ in + │ { + │ "pattern": "history", + │ "path": "/home/mac/Code/dashboard/app" + │ } + │ out + │ Found 4 matches + │ /home/mac/Code/dashboard/app/main.py: + │ Line 22: app.state.store = HistoryStore(maxlen=settings.history_maxlen) + │ + │ + │ /home/mac/Code/dashboard/app/config.py: + │ Line 19: def history_maxlen(self) -> int: + │ + │ + │ /home/mac/Code/dashboard/app/routers/overview.py: + │ Line 53: @router.get("/history") + │ + │ Line 54: async def history(request: Request): + │ + │ tokens 9190 in 106 out 0 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:58:04 • assistant • Qwen3.8-27B-Think @plan + │ + │ Let's read the main files: app/routers/overview.py, app/state.py, app/config.py, and the + │ overview template. + │ + │ read /home/mac/Code/dashboard/app/routers/overview.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/routers/overview.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/routers/overview.py + │ file + │ + │ 1: import asyncio + │ 2: import socket + │ 3: import time + │ 4: from typing import Any + │ 5: + │ 6: import psutil + │ 7: from fastapi import APIRouter, Request + │ 8: from fastapi.responses import HTMLResponse, JSONResponse + │ 9: + │ 10: from app.collect import net as net_col + │ 11: from app.render import render, uptime_str + │ 12: + │ 13: router = APIRouter(prefix="/api", tags=["overview"]) + │ 14: + │ 15: + │ 16: @router.get("/overview") + │ 17: async def overview(request: Request): + │ 18: store = request.app.state.store + │ 19: s: dict[str, Any] = store.latest() or {} + │ 20: mem_total = s.get("mem_total") or 0 + │ 21: vram_total = s.get("vram_total") or 0 + │ 22: vram_used = s.get("vram_used") + │ 23: c = { + │ 24: "cpu": s.get("cpu"), + │ 25: "cpu_temp": s.get("cpu_temp"), + │ 26: "load1": s.get("load1"), + │ 27: "load5": s.get("load5"), + │ 28: "load15": s.get("load15"), + │ 29: "mem_used": s.get("mem_used"), + │ 30: "mem_total": mem_total, + │ 31: "mem_pct": s.get("mem_pct"), + │ 32: "swap_used": s.get("swap_used"), + │ 33: "swap_total": s.get("swap_total") or 0, + │ 34: "swap_pct": s.get("swap_pct"), + │ 35: "gpu": s.get("gpu"), + │ 36: "gpu_name": s.get("gpu_name"), + │ 37: "gpu_temp": s.get("gpu_temp"), + │ 38: "vram_used": vram_used, + │ 39: "vram_total": vram_total, + │ 40: "vram_pct": s.get("vram_pct") + │ 41: or ((vram_used / vram_total * 100) if (vram_total and vram_used is not None) + │ else None), + │ 42: "battery": s.get("battery"), + │ 43: "battery_status": s.get("battery_status"), + │ 44: "ac_online": s.get("ac_online"), + │ 45: "uptime": uptime_str(time.time() - psutil.boot_time()), + │ 46: "hostname": socket.gethostname(), + │ 47: "cores": psutil.cpu_count(logical=True) or 1, + │ 48: **await asyncio.to_thread(net_col.sample), + │ 49: } + │ 50: return HTMLResponse(render("overview.html", c=c)) + │ 51: + │ 52: + │ 53: @router. + │ … [858 more chars] + │ read /home/mac/Code/dashboard/app/state.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/state.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/state.py + │ file + │ + │ 1: import time + │ 2: from collections import deque + │ 3: + │ 4: + │ 5: class HistoryStore: + │ 6: def __init__(self, maxlen: int) -> None: + │ 7: self._buf: deque[tuple[float, dict[str, float | int | None]]] = + │ deque(maxlen=maxlen) + │ 8: + │ 9: def record(self, sample: dict[str, float | int | None]) -> None: + │ 10: self._buf.append((time.time(), sample)) + │ 11: + │ 12: def snapshot(self) -> list[tuple[float, dict[str, float | int | None]]]: + │ 13: return list(self._buf) + │ 14: + │ 15: def latest(self) -> dict[str, float | int | None] | None: + │ 16: return self._buf[-1][1] if self._buf else None + │ 17: + │ 18: def __len__(self) -> int: + │ 19: return len(self._buf) + │ + │ (End of file - total 19 lines) + │ + │ read /home/mac/Code/dashboard/app/config.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/config.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/config.py + │ file + │ + │ 1: from functools import lru_cache + │ 2: + │ 3: from pydantic_settings import BaseSettings, SettingsConfigDict + │ 4: + │ 5: + │ 6: class Settings(BaseSettings): + │ 7: model_config = SettingsConfigDict(env_prefix="DASH_", env_file=".env", + │ extra="ignore") + │ 8: + │ 9: host: str = "127.0.0.1" + │ 10: port: int = 8501 + │ 11: sample_interval: float = 2.0 + │ 12: retention_minutes: int = 60 + │ 13: + │ 14: llama_base_url: str = "http://127.0.0.1:8080" + │ 15: llama_api_key: str = "" + │ 16: llama_timeout: float = 4.0 + │ 17: + │ 18: @property + │ 19: def history_maxlen(self) -> int: + │ 20: return max(10, int(self.retention_minutes * 60 / self.sample_interval)) + │ 21: + │ 22: + │ 23: @lru_cache + │ 24: def get_settings() -> Settings: + │ 25: return Settings() + │ + │ (End of file - total 25 lines) + │ + │ + │ tokens 425 in 129 out 9296 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:58:07 • assistant • Qwen3.8-27B-Think @plan + │ + │ Next, let's look at the template and see how the chart uses this data. + │ + │ grep history ok 0.0s + │ in + │ { + │ "pattern": "history", + │ "path": "/home/mac/Code/dashboard/templates" + │ } + │ out No files found + │ + │ tokens 1560 in 62 out 9849 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:58:11 • assistant • Qwen3.8-27B-Think @plan + │ + │ Hmm, there's nothing that matches "history" in the templates. Let me check the templates + │ directory. + │ + │ glob templates/** ok 0.0s + │ in + │ { + │ "pattern": "templates/**" + │ } + │ out + │ /home/mac/Code/dashboard/templates/index.html + │ /home/mac/Code/dashboard/templates/overview.html + │ /home/mac/Code/dashboard/templates/plugins/llamacpp.html + │ /home/mac/Code/dashboard/templates/plugins.html + │ /home/mac/Code/dashboard/templates/service_detail.html + │ /home/mac/Code/dashboard/templates/services.html + │ /home/mac/Code/dashboard/templates/journal.html + │ /home/mac/Code/dashboard/templates/processes.html + │ /home/mac/Code/dashboard/templates/disks.html + │ grep /api/history ok 0.0s + │ in + │ { + │ "pattern": "/api/history" + │ } + │ out + │ Found 39 matches + │ /home/mac/Code/dashboard/AGENTS.md: + │ Line 26: # /api/plugins /api/history + │ + │ Line 80: - `/api/history` pads series with `null` for samples missing a key so all + │ + │ + │ /home/mac/Code/dashboard/static/js/app.js: + │ Line 134: fetch("/api/history") + │ + │ + │ /home/mac/Code/dashboard/opencode/opencode_session_2026-08-30.txt: + │ Line 707: │ 2 s) + `GET /api/history` (JSON, 2 s → Chart.js). + │ + │ Line 897: │ "content": "Tab 1 Overview: cards fragment + /api/history JSON + + │ Chart.js graphs", + │ + │ Line 953: │ "content": "Tab 1 Overview: cards fragment + /api/history JSON + + │ Chart.js graphs", + │ + │ Line 2805: │ ugins','/api/history']:\n r = c.get(path)\n print(path, + │ r.status_code, + │ + │ Line 2935: │ s','/api/history','/api/plugins/llamacpp/fragment']:\n r = + │ c.get(path)\n + │ + │ Line 2947: │ /api/history 200 21 + │ + │ Line 2962: │ 5. /api/history - only 21 bytes (empty, because TestClient's lifespan... no + │ wait, lifespan does + │ + │ Line 2977: │ c.get('/api/history').json()\n print('points:', len(h['ts']), + │ 'keys:', + │ + │ Line 4106: │ - Chart.js polls `GET /api/history` every 2s, downsamples to 400 pts; + │ charts: cpu, mem_pct, gpu, + │ + │ Line 4129: │ `/api/disks`, `/api/processes`, `/api/journal`, `/api/services`, + │ `/api/plugins`, `/api/history`, + │ + │ Line 4134: │ `/api/history` now skips non-numeric series. + │ + │ Line 9520: │ /api/journal /api/services /api/plugins /api/history; do printf \"%s: + │ %s\\n\" \"$p\" + │ + │ Line 9533: │ /api/history: 200 + │ + │ Line 11357: │ http://127.0.0.1:8501/api/history | python3 -c \"import json,sys; + │ d=json.load(sys.stdin); + │ + │ Line 11515: │ `/api/history` by 16 series) + │ + │ Line 11528: │ - small robustness fix in `/api/history`: pad a series with `null` for + │ samples missing a key, so + │ + │ Line 12495: │ series. It's fine for /api/history to return it, it's just one extra key. + │ + │ Line 12685: │ bash curl -s http://127.0.0.1:8501/api/history | python3 -c " imp + │ … [1792 more chars] + │ + │ tokens 18 in 78 out 11471 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:58:13 • assistant • Qwen3.8-27B-Think @plan + │ + │ Interesting — line 134 of static/js/app.js fetches /api/history. Let me read that file. + │ + │ read /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js" + │ } + │ out + │ /home/mac/Code/dashboard/static/js/app.js + │ file + │ + │ 1: (function () { + │ 2: "use strict"; + │ 3: + │ 4: // ---------- tabs ---------- + │ 5: const tabBtns = document.querySelectorAll(".tab-btn"); + │ 6: const sections = document.querySelectorAll(".tab"); + │ 7: + │ 8: function showTab(name) { + │ 9: tabBtns.forEach((b) => b.classList.toggle("active", b.dataset.tab === name)); + │ 10: sections.forEach((s) => s.classList.toggle("hidden", s.id !== "tab-" + name)); + │ 11: try { + │ 12: localStorage.setItem("dash.tab", name); + │ 13: } catch (e) {} + │ 14: } + │ 15: tabBtns.forEach((b) => b.addEventListener("click", () => showTab(b.dataset.tab))); + │ 16: try { + │ 17: const saved = localStorage.getItem("dash.tab"); + │ 18: if (saved && document.getElementById("tab-" + saved)) showTab(saved); + │ 19: } catch (e) {} + │ 20: + │ 21: // ---------- helpers ---------- + │ 22: function fmtBytes(n, digits) { + │ 23: if (n == null || isNaN(n)) return "—"; + │ 24: if (digits == null) digits = 1; + │ 25: const u = ["B", "KiB", "MiB", "GiB", "TiB"]; + │ 26: let i = 0; + │ 27: while (Math.abs(n) >= 1024 && i < u.length - 1) { + │ 28: n /= 1024; + │ 29: i++; + │ 30: } + │ 31: return n.toFixed(digits) + " " + u[i]; + │ 32: } + │ 33: + │ 34: function fmtTime(ts) { + │ 35: const d = new Date(ts * 1000); + │ 36: return d.toLocaleTimeString([], { hour12: false }); + │ 37: } + │ 38: + │ 39: function downsampleIdx(len, max) { + │ 40: if (len <= max) return null; + │ 41: const step = Math.ceil(len / max); + │ 42: const idx = []; + │ 43: for (let i = 0; i < len; i += step) idx.push(i); + │ 44: if (idx[idx.length - 1] !== len - 1) idx.push(len - 1); + │ 45: return idx; + │ 46: } + │ 47: + │ 48: // ---------- charts ---------- + │ 49: const charts = {}; + │ 50: + │ 51: function baseOpts(extra) { + │ 52: const o = { + │ 53: animation: false, + │ 54: responsive: true, + │ 55: maintainAspectRatio: false, + │ 56: interaction: { mode: "index", intersect: false }, + │ 57: plugins: { legend: { display: false } + │ … [5831 more chars] + │ + │ tokens 1476 in 60 out 11567 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:58:16 • assistant • Qwen3.8-27B-Think @plan + │ + │ Now I understand the current setup: + │ + │ 1. `HistoryStore` holds samples every 2 seconds for up to 60 minutes = 1800 samples + │ (`history_maxlen` = 60*60/2 = 1800). + │ 2. `/api/history` returns all samples (up to 1800 timestamps, plus each series padded with + │ null). + │ 3. The JS `downsampleIdx` in `app.js` downsamples client-side to a max of 400 points by taking + │ every Nth point (decimation, not averaging). + │ + │ User's request: do window averaging server-side so that at most 200 points are sent to the + │ chart. + │ + │ So the change is: in `/api/history` (app/routers/overview.py), before building the response, + │ average consecutive samples into windows of `ceil(n / 200)` so that the output has at most 200 + │ points. + │ + │ Design considerations: + │ + │ 1. **Where to window-average?** Options: + │ - In `/api/history` in `app/routers/overview.py` — compute from the snapshot on each request. + │ 1800 samples × ~16 keys — trivial amount of computation per 2-second poll. Fine. + │ - In `HistoryStore.snapshot()` — no, the snapshot is used elsewhere too… let me check. + │ `snapshot()` is only used in the history endpoint (line 55 of overview.py). Let me verify + │ there are no other uses of snapshot. + │ + │ 2. **How to average with None values?** Samples may have keys missing (None) — e.g., gpu is None + │ on machines without GPU, vram is None, battery is None. When averaging, we should average only + │ non-None values within the window. If all values in the window are None, the result is None. + │ This preserves the null-padding behavior: "all series stay aligned with timestamps." + │ + │ 3. **Windowing approach:** Split the N samples into W windows, W = ceil(N / 200) (when N > 200). + │ Each window size is N // W or N // W + 1 — i.e., split evenly. Window timestamp: either the last + │ sample's timestamp in the window (right edge, latest first), or the mean. Chart.js labels use + │ timestamps via fmtTime. Using the last sample's timestamp in each window makes the right edge of + │ the chart match "now" — nice for a live chart. Using the first timestamp is also reasonabl + │ … [4848 more chars] + │ + │ read /home/mac/Code/dashboard/app/sampling.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/sampling.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/sampling.py + │ file + │ + │ 1: import asyncio + │ 2: import time + │ 3: + │ 4: from app.collect import cpu, disks, gpu, mem, power + │ 5: from app.state import HistoryStore + │ 6: + │ 7: + │ 8: def _collect() -> dict[str, float | int | None]: + │ 9: sample: dict[str, float | int | None] = {} + │ 10: sample.update(cpu.sample()) + │ 11: sample.update(mem.sample()) + │ 12: sample.update(gpu.sample()) + │ 13: sample.update(power.sample()) + │ 14: return sample + │ 15: + │ 16: + │ 17: async def sampler_loop(store: HistoryStore, sample_interval: float) -> None: + │ 18: cpu.prime() + │ 19: prev_disk = disks.counters() + │ 20: prev_t = time.monotonic() + │ 21: while True: + │ 22: await asyncio.sleep(sample_interval) + │ 23: sample = await asyncio.to_thread(_collect) + │ 24: now = time.monotonic() + │ 25: dt = now - prev_t + │ 26: sample.update(disks.rates(prev_disk, dt)) + │ 27: prev_disk = disks.counters() + │ 28: prev_t = now + │ 29: store.record(sample) + │ + │ (End of file - total 29 lines) + │ + │ read /home/mac/Code/dashboard/app/main.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/main.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/main.py + │ file + │ + │ 1: import asyncio + │ 2: import socket + │ 3: from contextlib import asynccontextmanager + │ 4: + │ 5: from fastapi import FastAPI + │ 6: from fastapi.responses import HTMLResponse + │ 7: from fastapi.staticfiles import StaticFiles + │ 8: + │ 9: from app.config import get_settings + │ 10: from app.plugins import ROUTERS as PLUGIN_ROUTERS + │ 11: from app.render import BASE, render + │ 12: from app.routers import disks, overview, plugins, processes, services + │ 13: from app.routers import journal as journal_router + │ 14: from app.sampling import sampler_loop + │ 15: from app.state import HistoryStore + │ 16: + │ 17: + │ 18: @asynccontextmanager + │ 19: async def lifespan(app: FastAPI): + │ 20: settings = get_settings() + │ 21: app.state.settings = settings + │ 22: app.state.store = HistoryStore(maxlen=settings.history_maxlen) + │ 23: task = asyncio.create_task(sampler_loop(app.state.store, settings.sample_interval)) + │ 24: yield + │ 25: _ = task.cancel() + │ 26: try: + │ 27: await task + │ 28: except asyncio.CancelledError: + │ 29: pass + │ 30: + │ 31: + │ 32: def create_app() -> FastAPI: + │ 33: app = FastAPI(title="Dashboard", lifespan=lifespan) + │ 34: app.mount("/static", StaticFiles(directory=BASE / "static"), name="static") + │ 35: for r in (overview.router, disks.router, processes.router, journal_router.router, + │ services.router, plugins.router): + │ 36: app.include_router(r) + │ 37: for r in PLUGIN_ROUTERS: + │ 38: app.include_router(r) + │ 39: + │ 40: @app.get("/", include_in_schema=False) + │ 41: async def index(): + │ 42: return HTMLResponse(render("index.html", hostname=socket.gethostname())) + │ 43: + │ 44: return app + │ 45: + │ 46: + │ 47: app = create_app() + │ + │ (End of file - total 47 lines) + │ + │ grep snapshot\(|latest\( ok 0.0s + │ in + │ { + │ "pattern": "snapshot\\(|latest\\(", + │ "path": "/home/mac/Code/dashboard", + │ "include": "*.py" + │ } + │ out + │ Found 4 matches + │ /home/mac/Code/dashboard/app/state.py: + │ Line 12: def snapshot(self) -> list[tuple[float, dict[str, float | int | None]]]: + │ + │ Line 15: def latest(self) -> dict[str, float | int | None] | None: + │ + │ + │ /home/mac/Code/dashboard/app/routers/overview.py: + │ Line 19: s: dict[str, Any] = store.latest() or {} + │ + │ Line 55: snap = request.app.state.store.snapshot() + │ + │ tokens 2785 in 1906 out 13103 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:59:08 • assistant • Qwen3.8-27B-Think @plan + │ + │ Now I have a full picture: + │ + │ **Current state:** + │ - The `HistoryStore` deque holds up to `history_maxlen` = 60 min × 60 / 2 s = 1800 samples. + │ - `/api/history` returns all samples as-is (up to 1800 points per series), rounded to one + │ decimal place for floats. + │ - Client-side `app.js` decimates to at most 400 points by simple strided selection + │ (`downsampleIdx`), not by averaging — this decimation is what's causing the "jumping around" + │ (skipping data, spikes get picked or dropped). + │ + │ **User's request:** Do window averaging server-side so that at most 200 points are sent to the + │ chart. + │ + │ **Plan:** + │ + │ 1. `app/config.py`: Add `chart_max_points: int = 200` (env var `DASH_CHART_MAX_POINTS`). + │ + │ 2. `app/routers/overview.py` `/api/history`: + │ - After fetching `snap`, if `len(snap) > max_points`, split into `ceil(n / max_points)` + │ contiguous, evenly-sized windows and average each window per key. + │ - Averaging details: + │ - Keys: use the union of keys across all samples (preserves current null-padding behavior — + │ all series are aligned to ts). + │ - Per key, per window: collect non-None values; if none → None; otherwise the mean. + │ - Type handling: if all values in the window are ints → round the mean to int (keep byte + │ counts clean); otherwise round the mean to one decimal place (matches existing rounding + │ behavior). + │ - Window timestamp: the last sample's timestamp in the window (the right edge stays "now"; + │ labels line up). + │ - If `len(snap) <= max_points`: current behavior unchanged (per-value one-decimal rounding). + │ + │ Helper implementation sketch (no comments per convention): + │ + │ ```python + │ def _window_average(snap: list[tuple[float, dict[str, float | int | None]]], max_points: int) -> + │ list[tuple[float, dict[str, float | int | None]]]: + │ n = len(snap) + │ if n <= max_points: + │ return snap + │ w = math.ceil(n / max_points) + │ out: list[tuple[float, dict[str, float | int | None]]] = [] + │ for start in range(0, n, w): + │ chunk = snap[start : start + w] + │ merged: d + │ … [3526 more chars] + │ + │ read /home/mac/Code/dashboard/pyproject.toml ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/pyproject.toml" + │ } + │ out + │ /home/mac/Code/dashboard/pyproject.toml + │ file + │ + │ 1: [project] + │ 2: name = "dashboard" + │ 3: version = "0.1.0" + │ 4: description = "Computer dashboard and combined task manager (FastAPI + htmx)" + │ 5: license = "Unlicense" + │ 6: license-files = ["LICENSE"] + │ 7: requires-python = ">=3.12" + │ 8: dependencies = [ + │ 9: "fastapi>=0.141.1", + │ 10: "httpx>=0.28.1", + │ 11: "jinja2>=3.1.6", + │ 12: "psutil>=7.2.2", + │ 13: "pydantic-settings>=2.15.0", + │ 14: "python-multipart>=0.0.32", + │ 15: "uvicorn[standard]>=0.52.4", + │ 16: ] + │ 17: + │ 18: [tool.uv] + │ 19: package = false + │ 20: + │ 21: [tool.basedpyright] + │ 22: reportExplicitAny = false + │ 23: reportAny = false + │ + │ (End of file - total 23 lines) + │ + │ grep app.state.settings ok 0.0s + │ in + │ { + │ "pattern": "app.state.settings", + │ "path": "/home/mac/Code/dashboard/app" + │ } + │ out + │ Found 1 matches + │ /home/mac/Code/dashboard/app/main.py: + │ Line 21: app.state.settings = settings + │ + │ tokens 987 in 1496 out 17794 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:59:45 • assistant • Qwen3.8-27B-Think @plan + │ + │ Settings are available in `app.state.settings`. Let's check how the other routers are accessing + │ settings (via request.app.state.settings, or via get_settings()). + │ + │ grep settings|get_settings ok 0.0s + │ in + │ { + │ "pattern": "settings|get_settings", + │ "path": "/home/mac/Code/dashboard/app", + │ "include": "*.py" + │ } + │ out + │ Found 17 matches + │ /home/mac/Code/dashboard/app/config.py: + │ Line 3: from pydantic_settings import BaseSettings, SettingsConfigDict + │ + │ Line 24: def get_settings() -> Settings: + │ + │ + │ /home/mac/Code/dashboard/app/main.py: + │ Line 9: from app.config import get_settings + │ + │ Line 20: settings = get_settings() + │ + │ Line 21: app.state.settings = settings + │ + │ Line 22: app.state.store = HistoryStore(maxlen=settings.history_maxlen) + │ + │ Line 23: task = asyncio.create_task(sampler_loop(app.state.store, + │ settings.sample_interval)) + │ + │ + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py: + │ Line 7: from app.config import Settings, get_settings + │ + │ Line 14: def _headers(settings: Settings) -> dict[str, str]: + │ + │ Line 16: if settings.llama_api_key: + │ + │ Line 17: h["Authorization"] = f"Bearer {settings.llama_api_key}" + │ + │ Line 22: settings = get_settings() + │ + │ Line 24: base_url=settings.llama_base_url.rstrip("/"), + │ + │ Line 25: timeout=settings.llama_timeout, + │ + │ Line 26: headers=_headers(settings), + │ + │ Line 32: settings = get_settings() + │ + │ Line 34: "base_url": settings.llama_base_url, + │ + │ tokens 345 in 92 out 20277 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 17:59:48 • assistant • Qwen3.8-27B-Think @plan + │ + │ Pattern: the plugin uses `get_settings()` (lru_cached). So in the history endpoint I can use + │ `get_settings().chart_max_points` — consistent with the plugin's pattern. + │ + │ Now, is there anything else that uses `/api/history`? Only the chart in `app.js`. Good. + │ + │ Let me also check the chart in the overview.html template (canvas id) — not needed for the plan, + │ but no harm in a quick check. Not needed. + │ + │ One more consideration: the current `history` endpoint does `round(t, 1)` on ts. After + │ windowing, the ts values will be the last ts of each window — no problem. + │ + │ Also note: the default `retention_minutes` is 60 → 1800 samples → 200-point window → each window + │ is about 9 samples ≈ 18-second averaging window. A reasonable smoothness. + │ + │ Trade-offs to surface to the user: + │ 1. Window averaging vs min/max (envelope): averaging loses peak spikes (a 2-second CPU spike + │ gets smoothed out). If the user wants to preserve peaks, min/max envelope (200-point min and max + │ bands) is possible, but the user explicitly asked for window averaging. Just briefly note that + │ spikes will be smoothed. + │ 2. Where the cap is enforced: server-side (per the request "sent to the chart"). Should the + │ client-side 400-point decimation be removed? Propose removing it as dead code (or keep it as a + │ defensive measure). I'll propose removal — simpler. Actually, hmm: if I remove it, + │ `updateCharts` becomes simpler. But someone could set DASH_CHART_MAX_POINTS higher. If it's a + │ configurable setting, it's possible for the server to send more than 400 points — in which case + │ the client-side cap would be useful. If I keep the client-side decimation as a hard guard, + │ behavior stays safe even when the setting is raised. I think keeping it is actually a good + │ defensive measure, and the cost is zero. But then the "jumping around" would reappear when + │ DASH_CHART_MAX_POINTS > 400… that's a user configuration choice. + │ + │ My proposal: keep the client-side `downsampleIdx` as-is as a hard safety cap (it never kicks + │ in by default) — zero risk, n + │ … [2549 more chars] + │ + │ bash which basedpyright; ls /home/mac/Code/dashboard/.venv/bin 2>/dev/null | head… ok 0.0s + │ in + │ { + │ "command": "which basedpyright; ls /home/mac/Code/dashboard/.venv/bin 2>/dev/null | head + │ -20; ls /home/mac/Code/dashboard" + │ } + │ out + │ which: no basedpyright in + │ (/usr/local/sbin:/usr/local/bin:/usr/bin:/var/lib/flatpak/exports/bin:/usr/bin/site_perl:/us + │ r/bin/vendor_perl:/usr/bin/core_perl:/opt/rocm/bin:/usr/lib/rustup/bin) + │ activate + │ activate.bat + │ activate.csh + │ activate.fish + │ activate.nu + │ activate.ps1 + │ activate_this.py + │ activate.xsh + │ dashboard + │ deactivate.bat + │ dotenv + │ fastapi + │ httpx + │ idna + │ pydoc.bat + │ python + │ python3 + │ python3.14 + │ uvicorn + │ watchfiles + │ AGENTS.md + │ app + │ deploy + │ LICENSE + │ main.py + │ opencode + │ __pycache__ + │ pyproject.toml + │ README.md + │ screenshots + │ static + │ templates + │ uv.lock + │ + │ tokens 330 in 1196 out 20714 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:00:21 • assistant • Qwen3.8-27B-Think @plan + │ + │ basedpyright is not installed in the venv. It might be available via `uvx basedpyright`. I'll + │ check that right away (it's read-only, so it's permitted). + │ + │ bash uv tool run basedpyright --version 2>&1 | tail -2 || uvx basedpyright --vers… ok 0.0s + │ in + │ { + │ "command": "uv tool run basedpyright --version 2>&1 | tail -2 || uvx basedpyright + │ --version 2>&1 | tail -2", + │ "timeout": 90000 + │ } + │ out + │ basedpyright 1.39.10 + │ based on pyright 1.1.412 + │ + │ tokens 180 in 116 out 22240 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:00:29 • assistant • Qwen3.8-27B-Think @plan + │ + │ `uvx basedpyright` works. OK, let me lay out the plan. + │ + │ Summary of the current data flow: + │ - Sampler records into a deque every 2 seconds (up to 1800 entries with the default 60-minute + │ retention). + │ - `/api/history` (app/routers/overview.py:53-72) returns all points as-is; the client + │ (static/js/app.js:113) decimates them client-side with stride-based sampling up to 400 points — + │ that stride-picking is what causes the "jumping around." + │ + │ Plan: + │ + │ 1. **`app/config.py`** — add `chart_max_points: int = 200` (env var `DASH_CHART_MAX_POINTS`). + │ + │ 2. **`app/routers/overview.py`** — add a `_window_average(snap, max_points)` helper and call it + │ from `/api/history`: + │ - If `len(snap) <= max_points`, return it unchanged. + │ - Otherwise, split into contiguous windows of `ceil(n / max_points)` (the last window may be + │ smaller) → total output ≤ 200. + │ - Per window, per key: average only the non-None values; keys that are entirely None within a + │ window get dropped from that window's dict (the existing union-of-keys + None-padding loop in + │ the endpoint restores alignment — behavior documented in AGENTS.md is preserved). + │ - Types: if all values in a window are int → `round(mean)` (int); otherwise `round(mean, 1)` + │ (matches the existing 1-decimal rounding). + │ - The window's timestamp is that of the window's last sample (the right edge of the chart + │ stays at "now"). + │ - Endpoint: `snap = _window_average(store.snapshot(), get_settings().chart_max_points)`; the + │ rest of the function stays unchanged. + │ + │ 3. **`static/js/app.js`** — no changes: the client-side 400-point decimation (`downsampleIdx`) + │ becomes a dormant fallback and keeps working for any payload length. (Alternative: remove it as + │ dead code — will ask the user's preference? No — it's a trivial point; leaving it as-is is the + │ minimal diff. I'll mention it in the plan.) + │ + │ Actually, should I ask the user about the client-side code? The plan-mode guidance says to ask + │ when weighing trade-offs. The main trade-off worth surfacing: + │ + │ - **Averaging sm + │ … [1640 more chars] + │ + │ I have the full picture. Here's the plan. + │ + │ ## Current behavior + │ + │ - Sampler records a sample every 2 s into a deque of up to 1800 entries (60 min retention, + │ `app/config.py:19-20`). + │ - `GET /api/history` (`app/routers/overview.py:53-72`) returns **every** point (~1800) as-is. + │ - The client then stride-decimates to 400 points by *skipping* every Nth point (`downsampleIdx` + │ in `static/js/app.js:39-46`) — that pick-every-Nth is what makes the lines jump. + │ + │ ## Changes + │ + │ **1. `app/config.py`** — add a setting (env `DASH_CHART_MAX_POINTS`): + │ + │ ```python + │ chart_max_points: int = 200 + │ ``` + │ + │ **2. `app/routers/overview.py`** — add a `_window_average(snap, max_points)` helper, called at + │ the top of `history()`: + │ + │ - If `len(snap) <= max_points`, return unchanged (current behavior). + │ - Otherwise split into `ceil(n / 200)` contiguous windows of `ceil(n / max_points)` samples each + │ (default retention → ~9 samples / ~18 s per window); total output ≤ 200 points. + │ - Per window, per key: mean of the **non-None** values only. Keys that are all-None in a window + │ simply don't appear in that window's dict — the existing union-of-keys + `None`-padding loop in + │ the endpoint (the AGENTS.md documented alignment behavior) still lines everything up against + │ `ts`. + │ - Types: if all values in a window are ints → `round(mean)` (keeps byte counts like `mem_total` + │ integral); otherwise `round(mean, 1)` (matches current rounding). + │ - Window timestamp = last sample's ts in the window, so the right edge of the chart stays at + │ "now". + │ - Rest of the endpoint (key scan, null padding, `round(t, 1)`) is untouched. + │ + │ **3. `static/js/app.js`** — no change. Its 400-point `downsampleIdx` becomes a dormant fallback; + │ with ≤ 200 points from the server it never triggers. + │ + │ ## Verification + │ + │ 1. `uv run python -m compileall -q app` + │ 2. `uvx basedpyright app` + │ 3. Synthetic check: feed the helper 1800 fake samples (incl. keys with `None` gaps, int and + │ float keys) and assert ≤ 200 windows, correct means, preserved alignment. + │ 4. Restart the dev server (the documented kill + `setsid nohup` pattern), then `curl -s + │ http://127.0.0.1:8501/api/history | python3 -c 'import json,sys; + │ print(len(json.load(sys.stdin)["ts"]))'` → ≤ 200 once the buffer has filled (≥ ~7 min uptime; + │ the synthetic test covers the short-buffer case), and `grep -c Traceback /tmp/dash.log`. + │ + │ ## Note + │ + │ Window averaging smooths out sub-window spikes — a 2 s CPU burst will show as a smaller bump + │ averaged over ~18 s. If you'd rather *preserve* peaks, an alternative is a min/max envelope (two + │ 200-point series), but I'll go with plain averaging as requested. + │ + │ Want me to proceed with this plan? + │ + │ tokens 39 in 1727 out 22536 cached 0 written $0.0000 stop + + + │ 2026-08-30 18:02:22 • user + │ + │ preserving peaks sounds better + + + │ 2026-08-30 18:02:22 • assistant • Qwen3.8-27B-Think @plan + │ + │ The user wants to preserve peaks — that is, not simple window averaging, but a min/max envelope. + │ Let me think about what this means for the design. + │ + │ **Min/max envelope downsampling (standard approach, e.g., what many observability tools do):** + │ - Split the N samples into W windows (W ≤ 200, W = ceil(N/200)). + │ - For each window, for each key: emit min and max (and also first/last for connecting the + │ segments). + │ - Rendering: draw the envelope as a filled band (min as the lower boundary, max as the upper + │ boundary). The "line" is usually drawn connecting the window's first and last points so that + │ segments link up. + │ + │ **How to send it to the client?** + │ + │ Options: + │ + │ **Option A: Server sends min and max series (2× keys), client draws a filled band between + │ them.** + │ - Server returns for each key `k`: `series[k]` becomes... hmm. Currently `series` is `dict[str, + │ list]`. We can extend the payload: each key maps to `{min: [...], max: [...], first: [...], + │ last: [...]}`? That changes the shape — the client needs to be updated. + │ - The Chart.js band is drawn by using two datasets: `max` (border) and `min` (fill to dataset + │ above). Standard technique: a dataset for max with `fill: '-1'` (fill to the next dataset), and + │ a dataset for min with fill false, ordering is... actually, the common pattern: + │ - dataset A: max values, `fill: '+1'` (fill to the next dataset below), + │ - dataset B: min values, `fill: false`. + │ And optionally draw a line connecting first/last. + │ + │ The fill between max and min shows the range; the max line and the min line are visible as the + │ top/bottom borders of the band. + │ + │ To link windows (so the line doesn't look like floating islands), include the window's + │ first/last values: draw a line dataset that goes first → ... hmm, the classic trick (from + │ Chart.js docs on "line segment linking"): a single dataset with points that alternate? A + │ simpler classic approach: + │ - dataset "range max" (max points) with fill to min dataset + │ - dataset "range min" + │ - A line dataset connecting w + │ … [14176 more chars] + │ + │ bash ls /home/mac/Code/dashboard/static /home/mac/Code/dashboard/static/js /home/… ok 0.0s + │ in + │ { + │ "command": "ls /home/mac/Code/dashboard/static /home/mac/Code/dashboard/static/js + │ /home/mac/Code/dashboard/static/css 2>/dev/null; ls /home/mac/Code/dashboard/screenshots | + │ head" + │ } + │ out + │ /home/mac/Code/dashboard/static: + │ css + │ js + │ vendor + │ + │ /home/mac/Code/dashboard/static/css: + │ style.css + │ + │ /home/mac/Code/dashboard/static/js: + │ app.js + │ Disks_20260830.png + │ Journal_20260830.png + │ Overview_20260830.png + │ Plugins_20260830.png + │ Processes_20260830.png + │ Services_20260830.png + │ grep chart|Chart ok 0.0s + │ in + │ { + │ "pattern": "chart|Chart", + │ "path": "/home/mac/Code/dashboard/templates/index.html" + │ } + │ out + │ Found 5 matches + │ /home/mac/Code/dashboard/templates/index.html: + │ Line 9: + │ + │ Line 26:
+ │ + │ Line 27:

CPU / GPU %

+ │ + │ Line 28:

Memory / VRAM %

+ │ + │ Line 29:

Disk I/O

+ │ + │ tokens 15644 in 4351 out 8674 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:04:48 • assistant • Qwen3.8-27B-Think @plan + │ + │ Chart.js is a local vendor file: `/static/vendor/chart.umd.min.js`. Let me check its version (in + │ order to know whether the relative specification `'-1'` for fill and legend filter are supported + │ — all versions of Chart.js 3/4 support these, but let's confirm the version anyway). Also, I'll + │ check the chart area of overview.html (the canvas is in index.html, and the overview tab is part + │ of the index page — the charts are in index.html, not overview.html. No problem, no template + │ changes needed). + │ + │ Let me quickly check the version of chart.umd.min.js. + │ + │ bash head -c 400 /home/mac/Code/dashboard/static/vendor/chart.umd.min.js; echo; l… ok 0.0s + │ in + │ { + │ "command": "head -c 400 /home/mac/Code/dashboard/static/vendor/chart.umd.min.js; echo; ls + │ /home/mac/Code/dashboard/static/vendor" + │ } + │ out + │ /*! + │ * Chart.js v4.4.1 + │ * https://www.chartjs.org + │ * (c) 2023 Chart.js Contributors + │ * Released under the MIT License + │ */ + │ !function(t,e){"object"==typeof exports&&"undefined"!=typeof + │ module?module.exports=e():"function"==typeof + │ define&&define.amd?define(e):(t="undefined"!=typeof + │ globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var + │ t=Object.freeze({__proto__:null,get Colors() + │ chart.umd.min.js + │ htmx.min.js + │ + │ tokens 289 in 187 out 28669 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:04:54 • assistant • Qwen3.8-27B-Think @plan + │ + │ Chart.js v4.4.1 — supports the following: + │ - Filler plugin built-in (included in the UMD build) + │ - Relative specification for `fill: '-1'` (fills to the immediately preceding dataset) — in + │ Chart.js 4, the `fill` option accepts `true/false/number/string`, and number-like strings + │ `'-1'`/`'1'` are relative references. Actually, let me re-verify the exact semantics in v4: from + │ the Chart.js docs (filler plugin): + │ + │ ``` + │ fill: { + │ value: true | false | number | string, + │ // Default: false + │ } + │ ``` + │ The string values are: `'start'`, `'end'`, `'origin'`, `'stack'`, `'dataset'`. And numbers are + │ dataset indices. Relative references: the docs show an example of `fill: '-1'` — hmm. I recall + │ the Chart.js docs had "You can also use the special strings 'start', 'end', 'origin', 'stack', + │ 'dataset'"… and for relative: the example in the docs uses `fill: '-1'`? Let me think about the + │ Chart.js 4 docs on line fill: + │ + │ From the Chart.js docs (Line chart → Fill between lines… no wait, that's in the "filler" plugin + │ section): + │ + │ > ``` + │ > // fill with the dataset above + │ > { + │ > fill: '-1' + │ > } + │ > // fill with the dataset below + │ > { + │ > fill: '1' + │ > } + │ > ``` + │ + │ Hmm, I'm honestly not 100% sure whether the relative syntax is the string `'-1'` or the number + │ `-1`. In the Chart.js source, the `filler` plugin resolves the fill target: if it's a number or + │ a string that looks like a number, it's a relative offset from the dataset index. From the + │ source (chart.js src/plugins/plugin.filler/index.js): + │ + │ ```js + │ function isPointInsideArea... + │ function decodeFill(data, index, count) { + │ let fill = decodeFillValue(data.fill); + │ if (fill === undefined) return false; + │ if (fill === true) return 'origin'; + │ ... + │ if (typeof fill === 'number') { + │ // relative index + │ return index + fill; // roughly + │ } + │ ``` + │ + │ Hmm. Actually, in the Chart.js 4 source: + │ + │ ```js + │ const decodeFill = (fill) => { + │ if (fill === undefined) return; + │ ... + │ }; + │ ``` + │ + │ I recall the docs example: + │ + │ ```js + │ datasets: [{ + │ label: 'Dataset 1', + │ data: [...], + │ fill: 2, // f + │ … [3727 more chars] + │ + │ bash ls /home/mac/Code/dashboard/deploy; which chromium chromium-browser google-c… ok 0.0s + │ in + │ { + │ "command": "ls /home/mac/Code/dashboard/deploy; which chromium chromium-browser + │ google-chrome headless_shell 2>/dev/null; python3 -c \"import playwright\" 2>&1 | head -1; + │ ls ~/.cache/ms-playwright 2>/dev/null | head" + │ } + │ out + │ dashboard.service + │ /usr/bin/chromium + │ Traceback (most recent call last): + │ grep screenshot|Screenshot ok 0.0s + │ in + │ { + │ "pattern": "screenshot|Screenshot", + │ "path": "/home/mac/Code/dashboard/README.md" + │ } + │ out + │ Found 6 matches + │ /home/mac/Code/dashboard/README.md: + │ Line 8: ## Screenshots + │ + │ Line 12: | ![Overview](screenshots/Overview_20260830.png) | + │ ![Disks](screenshots/Disks_20260830.png) | + │ + │ Line 14: | ![Processes](screenshots/Processes_20260830.png) | + │ ![Journal](screenshots/Journal_20260830.png) | + │ + │ Line 16: | ![Services](screenshots/Services_20260830.png) | + │ ![Plugins](screenshots/Plugins_20260830.png) | + │ + │ + │ /home/mac/Code/dashboard/opencode/opencode_session_2026-08-30.txt: + │ Line 5324: │ - Screenshots / tab overview + │ + │ Line 12812: │ Another check: verify that the chart actually renders — can't take + │ screenshots, but the JS + │ + │ tokens 146 in 1657 out 29144 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:05:39 • assistant • Qwen3.8-27B-Think @plan + │ + │ `/usr/bin/chromium` is available — can use headless Chromium to take screenshots of the page for + │ visual verification (e.g., `chromium --headless --screenshot=... --window-size=1400,900 + │ http://127.0.0.1:8501/`). That way I can visually verify the band rendering. + │ + │ OK, let me finalize the design for the min/max envelope. + │ + │ ### Server side + │ + │ **`app/config.py`**: Add `chart_max_points: int = 200`. + │ + │ **`app/routers/overview.py`**: + │ + │ Replace the body of `history()`: + │ + │ ```python + │ @router.get("/history") + │ async def history(request: Request): + │ snap = request.app.state.store.snapshot() + │ snap = _window(snap, get_settings().chart_max_points) + │ ts = [round(t, 1) for t, _ in snap] + │ keys: set[str] = set() + │ for _, sample in snap: + │ keys.update(sample) # wait — need numeric check + │ ... + │ ``` + │ + │ Hmm, the existing key scan checks `isinstance(v, (int, float)) and not isinstance(v, bool)` per + │ sample — keys whose value is non-numeric in some sample are still counted if numeric in others. + │ The union of keys stays as-is, and padding handles missing/non-numeric → None. + │ + │ New response shape: + │ + │ ```json + │ { + │ "ts": [...≤200...], + │ "series": { + │ "cpu": {"avg": [...], "min": [...], "max": [...]}, + │ ... + │ } + │ } + │ ``` + │ + │ Helper: + │ + │ ```python + │ def _window(snap: list[tuple[float, dict[str, float | int | None]]], max_points: int) -> + │ list[...]: + │ n = len(snap) + │ if n <= max_points: + │ return snap + │ w = math.ceil(n / max_points) + │ out = [] + │ for start in range(0, n, w): + │ chunk = snap[start:start + w] + │ vals: dict[str, list[int | float]] = {} + │ for _, sample in chunk: + │ for k, v in sample.items(): + │ if isinstance(v, (int, float)) and not isinstance(v, bool): + │ vals.setdefault(k, []).append(v) + │ row: dict[str, dict[str, float | int | None]] = {} + │ for k, lst in vals.items(): + │ ints = all(isinstance(v, int) for v in lst) + │ f = round if ints else lambda x: round(x, 1) + │ row[k] = { + │ + │ … [9167 more chars] + │ + │ Good call — a min/max envelope (Grafana's "min/max/mean" look) keeps spikes visible while + │ staying smooth. I checked Chart.js v4.4.1 is vendored locally (`static/vendor/chart.umd.min.js`, + │ filler plugin built in) and headless Chromium is available for visual verification. Revised + │ plan: + │ + │ ## Server: `app/routers/overview.py` + │ + │ New `_window(snap, max_points)` helper, called in `history()`: + │ + │ - `w = max(1, ceil(n / max_points))` → contiguous windows; **one code path** — when `n ≤ 200`, + │ `w = 1` so each "window" is a single sample (`avg = min = max = value`). + │ - Per window, per key (non-None numeric values only): emit `{"avg": mean, "min": min, "max": + │ max}`. All-int values stay ints (byte counts); floats round to 1 dp (current behavior). A key + │ absent/None across a whole window produces no entry → the existing union-of-keys + + │ `None`-padding loop (AGENTS.md alignment rule) still fills every array to align with `ts`. + │ - Window timestamp = last sample's ts (chart right edge stays at "now"). + │ + │ Response shape changes from `series: {k: [v, ...]}` to: + │ + │ ```json + │ {"ts": [≤200], "series": {"cpu": {"avg": [...], "min": [...], "max": [...]}, ...}} + │ ``` + │ + │ Payload also shrinks ~3× (16 keys × 200 pts × 3 vs 16 × 1800). Which keys are sent is unchanged. + │ + │ ## Config: `app/config.py` + │ + │ Add `chart_max_points: int = 200` (env `DASH_CHART_MAX_POINTS`), read via `get_settings()` like + │ the plugins do. + │ + │ ## Client: `static/js/app.js` + │ + │ - Drop `downsampleIdx`/`pick` (server now guarantees the cap). + │ - Per charted key build 3 datasets: **avg line** (1.5 px, tension 0.25, labeled — the only + │ legend entry), **max line** (1 px, `fill` to the min dataset's index, ~18 % alpha + │ `backgroundColor`), **min line** (1 px, no label; `band` flag + legend `labels.filter` so it + │ doesn't appear in the legend). Arrays are built programmatically per chart, so the fill target + │ index is exact. + │ - Missing-key guard stays (`hist.series[k] || {avg:[],min:[],max:[]}`); nulls break the + │ band/line naturally (no `spanGaps`). Scales, polling, everything else untouched. + │ + │ ## Docs: `AGENTS.md` + │ + │ Update the `/api/history` null-padding pitfall line to describe the new behavior (≤ + │ `chart_max_points` windows, `avg`/`min`/`max` per key, null-padded alignment). + │ + │ ## Verification + │ + │ 1. `uv run python -m compileall -q app` + │ 2. `uvx basedpyright app` + │ 3. Synthetic check: 1800 fake samples (constant + spike + int key + float key + `None`-gap key) + │ → assert ≤ 200 windows, exact `avg`/`min`/`max` on known windows, `None` propagation, int + │ preservation. + │ 4. Restart dev server (documented kill + `setsid nohup` pattern), then: + │ - `curl -s http://127.0.0.1:8501/api/history` → `ts` ≤ 200 and each key's three arrays + │ aligned to `ts`; + │ - headless Chromium screenshot of `http://127.0.0.1:8501/` → visually confirm bands render + │ (fill between min/max, avg line on top, 2-entry legend); + │ - `grep -c Traceback /tmp/dash.log`. + │ + │ One note: the avg line is included so the charts keep a readable "typical value" trace — say the + │ word if you'd rather have a bare band (min/max only). Otherwise, ready to implement. + │ + │ tokens 269 in 4074 out 30948 cached 0 written $0.0000 stop + + + │ 2026-08-30 18:09:25 • user + │ + │ ok go + + + │ 2026-08-30 18:09:25 • assistant • Qwen3.8-27B-Think + │ + │ Let's implement the plan: + │ + │ 1. `app/config.py`: Add `chart_max_points: int = 200` + │ 2. `app/routers/overview.py`: Add `_window` helper, change the shape of the history endpoint + │ 3. `static/js/app.js`: Envelope dataset + │ 4. `AGENTS.md`: Update the pitfalls line + │ 5. Verification: compileall, basedpyright, synthetic test, server restart, curl, chromium + │ screenshot, tracebacks. + │ + │ Let me set up the todo and get started. + │ + │ todowrite 5 todos ok 0.0s + │ in + │ { + │ "todos": [ + │ { + │ "content": "Add chart_max_points setting to app/config.py", + │ "status": "in_progress", + │ "priority": "high" + │ }, + │ { + │ "content": "Window-average (avg/min/max) in /api/history", + │ "status": "pending", + │ "priority": "high" + │ }, + │ { + │ "content": "Rewrite chart datasets in static/js/app.js as min/max bands", + │ "status": "pending", + │ "priority": "high" + │ }, + │ { + │ "content": "Update AGENTS.md /api/history pitfall note", + │ "status": "pending", + │ "priority": "medium" + │ }, + │ { + │ "content": "Verify: compileall, basedpyright, synthetic test, restart, curl, + │ screenshot", + │ "status": "pending", + │ "priority": "high" + │ } + │ ] + │ } + │ out + │ [ + │ { + │ "content": "Add chart_max_points setting to app/config.py", + │ "status": "in_progress", + │ "priority": "high" + │ }, + │ { + │ "content": "Window-average (avg/min/max) in /api/history", + │ "status": "pending", + │ "priority": "high" + │ }, + │ { + │ "content": "Rewrite chart datasets in static/js/app.js as min/max bands", + │ "status": "pending", + │ "priority": "high" + │ }, + │ { + │ "content": "Update AGENTS.md /api/history pitfall note", + │ "status": "pending", + │ "priority": "medium" + │ }, + │ { + │ "content": "Verify: compileall, basedpyright, synthetic test, restart, curl, + │ screenshot", + │ "status": "pending", + │ "priority": "high" + │ } + │ ] + │ + │ tokens 35080 in 290 out 0 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:10:25 • assistant • Qwen3.8-27B-Think + │ + │ The user approved the plan. Let's start the implementation. + │ + │ 1. First, I'll edit config.py. + │ + │ edit /home/mac/Code/dashboard/app/config.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/config.py", + │ "oldString": " host: str = \"127.0.0.1\"\n port: int = 8501\n sample_interval: + │ float = 2.0\n retention_minutes: int = 60", + │ "newString": " host: str = \"127.0.0.1\"\n port: int = 8501\n sample_interval: + │ float = 2.0\n retention_minutes: int = 60\n chart_max_points: int = 200" + │ } + │ out Edit applied successfully. + │ + │ tokens 224 in 190 out 35370 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 0e224f59 + + + │ 2026-08-30 18:10:30 • assistant • Qwen3.8-27B-Think + │ + │ Next up is the router. Rewriting `/api/history` in app/routers/overview.py: + │ + │ ```python + │ import asyncio + │ import math + │ import socket + │ import time + │ from typing import Any + │ + │ import psutil + │ from fastapi import APIRouter, Request + │ from fastapi.responses import HTMLResponse, JSONResponse + │ + │ from app.collect import net as net_col + │ from app.config import get_settings + │ from app.render import render, uptime_str + │ + │ router = APIRouter(prefix="/api", tags=["overview"]) + │ + │ RowAgg = dict[str, float | int | None] + │ + │ + │ def _window( + │ snap: list[tuple[float, dict[str, float | int | None]]], max_points: int + │ ) -> list[tuple[float, dict[str, RowAgg]]]: + │ n = len(snap) + │ w = max(1, math.ceil(n / max_points)) + │ out: list[tuple[float, dict[str, RowAgg]]] = [] + │ for start in range(0, n, w): + │ chunk = snap[start : start + w] + │ vals: dict[str, list[int | float]] = {} + │ for _, sample in chunk: + │ for k, v in sample.items(): + │ if isinstance(v, (int, float)) and not isinstance(v, bool): + │ vals.setdefault(k, []).append(v) + │ row: dict[str, RowAgg] = {} + │ for k, lst in vals.items(): + │ ints = all(isinstance(v, int) for v in lst) + │ avg = sum(lst) / len(lst) + │ row[k] = { + │ "avg": round(avg) if ints else round(avg, 1), + │ "min": round(min(lst)) if ints else round(min(lst), 1), + │ "max": round(max(lst)) if ints else round(max(lst), 1), + │ } + │ out.append((chunk[-1][0], row)) + │ return out + │ ``` + │ + │ Wait — about min/max for int: since the values are already int, round(min(lst)) is the same int. + │ `round(min(lst))` on an int returns an int. No problem. + │ + │ Type annotation for the RowAgg values: float | int | None. After the computation they're always + │ non-None, but the dict type allows None. basedpyright: `row[k] = {"avg": round(avg) if ints else + │ round(avg, 1), ...}` — avg is float; round(avg) → int; round(avg,1) → float. The value type is + │ float|int — assignable to float|i + │ … [1556 more chars] + │ + │ Now the router: + │ + │ edit /home/mac/Code/dashboard/app/routers/overview.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/routers/overview.py", + │ "oldString": "import asyncio\nimport socket\nimport time\nfrom typing import Any\n\nimport + │ psutil\nfrom fastapi import APIRouter, Request\nfrom fastapi.responses import + │ HTMLResponse, JSONResponse\n\nfrom app.collect import net as net_col\nfrom app.render + │ import render, uptime_str\n\nrouter = APIRouter(prefix=\"/api\", tags=[\"overview\"])", + │ "newString": "import asyncio\nimport math\nimport socket\nimport time\nfrom typing import + │ Any\n\nimport psutil\nfrom fastapi import APIRouter, Request\nfrom fastapi.responses + │ import HTMLResponse, JSONResponse\n\nfrom app.collect import net as net_col\nfrom + │ app.config import get_settings\nfrom app.render import render, uptime_str\n\nrouter = + │ APIRouter(prefix=\"/api\", tags=[\"overview\"])\n\nRowAgg = dict[str, float | int | + │ None]\n\n\ndef _window(\n snap: list[tuple[float, dict[str, float | int | None]]], + │ max_points: int\n) -> list[tuple[float, dict[str, RowAgg]]]:\n n = len(snap)\n w = + │ max(1, math.ceil(n / max_points))\n out: list[tuple[float, dict[str, RowAgg]]] = []\n + │ for start in range(0, n, w):\n chunk = snap[start : start + w]\n vals: + │ dict[str, list[int | float]] = {}\n for _, sample in chunk:\n for k, v + │ in sample.items():\n if isinstance(v, (int, float)) and not isinstance(v, + │ bool):\n vals.setdefault(k, []).append(v)\n row: dict[str, + │ RowAgg] = {}\n for k, lst in vals.items():\n ints = all(isinstance(v, + │ int) for v in lst)\n avg = sum(lst) / len(lst)\n row[k] = {\n + │ \"avg\": round(avg) if ints else round(avg, 1),\n \"min\": min(lst) if ints + │ else round(min(lst), 1),\n \"max\": max(lst) if ints else round(max(lst), + │ 1),\n }\n out.append((chunk[-1][0], row))\n return out" + │ } + │ out Edit applied successfully. + │ + │ tokens 19 in 1486 out 35784 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 74f83e4f + + + │ 2026-08-30 18:11:05 • assistant • Qwen3.8-27B-Think + │ + │ Wait, `min(lst) if ints else round(min(lst), 1)` — in the ints case, min(lst) is already an int + │ so no rounding is needed. In the floats case, round(min, 1). No problem. (Earlier I wrote + │ round(min(lst)) in the plan, but for ints it's an identity operation.) + │ + │ Next, the endpoint body. + │ + │ edit /home/mac/Code/dashboard/app/routers/overview.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/routers/overview.py", + │ "oldString": "@router.get(\"/history\")\nasync def history(request: Request):\n snap = + │ request.app.state.store.snapshot()\n ts = [round(t, 1) for t, _ in snap]\n keys: + │ set[str] = set()\n for _, sample in snap:\n for k, v in sample.items():\n + │ if isinstance(v, (int, float)) and not isinstance(v, bool):\n keys.add(k)\n + │ series: dict[str, list[Any]] = {}\n for _, sample in snap:\n for k in keys:\n + │ v = sample.get(k)\n if isinstance(v, (int, float)) and not isinstance(v, + │ bool):\n if isinstance(v, float):\n v = round(v, 1)\n + │ else:\n v = None\n series.setdefault(k, []).append(v)\n + │ return JSONResponse({\"ts\": ts, \"series\": series})", + │ "newString": "@router.get(\"/history\")\nasync def history(request: Request):\n snap = + │ _window(request.app.state.store.snapshot(), get_settings().chart_max_points)\n ts = + │ [round(t, 1) for t, _ in snap]\n keys: set[str] = set()\n for _, row in snap:\n + │ keys.update(row)\n series: dict[str, dict[str, list[Any]]] = {\n k: {\"avg\": + │ [], \"min\": [], \"max\": []} for k in keys\n }\n for _, row in snap:\n for k + │ in keys:\n agg = row.get(k)\n for m in (\"avg\", \"min\", + │ \"max\"):\n series[k][m].append(agg[m] if agg else None)\n return + │ JSONResponse({\"ts\": ts, \"series\": series})" + │ } + │ out Edit applied successfully. + │ + │ tokens 18 in 522 out 37290 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) ba2b1563 + + + │ 2026-08-30 18:11:16 • assistant • Qwen3.8-27B-Think + │ + │ Next up is the client side: static/js/app.js. Changes: + │ + │ 1. Delete the `downsampleIdx` function. + │ 2. Replace `updateCharts` with an envelope version. + │ 3. The `newDs` helper is no longer used by the chart (it's only used in initCharts). Should I + │ keep `newDs`? After the changes, initCharts still creates the initial empty datasets — I can + │ build the initial datasets with the new envelope builder using an empty array. Let me + │ restructure: + │ + │ ```js + │ function envelopeDs(name, color, s) { + │ s = s || { avg: [], min: [], max: [] }; + │ return [ + │ { label: name, data: s.avg, borderColor: color, backgroundColor: color, borderWidth: 1.5, + │ pointRadius: 0, tension: 0.25, fill: false, order: 0 }, + │ { label: "", band: true, data: s.max, borderColor: color, backgroundColor: color + "2e", + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 1 }, + │ { label: "", band: true, data: s.min, borderColor: color, backgroundColor: color, + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 2 }, + │ ]; + │ } + │ ``` + │ + │ After that, set the fill target after concatenation: for a 2-key chart, the datasets array + │ length is 6; max_i is at index 3i+1, min_i is at 3i+2. So: + │ + │ ```js + │ function setKey(chart, keys) { // hmm + │ ``` + │ + │ Let me restructure updateCharts: + │ + │ ```js + │ function bandCharts() { + │ ... + │ } + │ ``` + │ + │ The simplest approach: + │ + │ ```js + │ function updateCharts(hist) { + │ if (!hist || !hist.ts || !hist.ts.length) return; + │ const labels = hist.ts.map(fmtTime); + │ const build = (chart, pairs) => { + │ chart.data.labels = labels; + │ chart.data.datasets = []; + │ pairs.forEach(([name, color, key], i) => { + │ const s = (hist.series && hist.series[key]) || { avg: [], min: [], max: [] }; + │ const ds = envelopeDs(name, color, s); + │ ds[1].fill = i * 3 + 2; + │ chart.data.datasets.push(...ds); + │ }); + │ chart.update("none"); + │ }; + │ build(charts.cpu, [["CPU", "#4fc3f7", "cpu"], ["GPU", "#ba68c8", "gpu"]]); + │ build(charts.mem, [["Memory", "#81c784", "mem_pct"], ["VRAM", "#ffb74d", "vram_pct"]]); + │ build(cha + │ … [9300 more chars] + │ + │ Now the client side (`static/js/app.js`): + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " function downsampleIdx(len, max) {\n if (len <= max) return null;\n + │ const step = Math.ceil(len / max);\n const idx = [];\n for (let i = 0; i < len; i += + │ step) idx.push(i);\n if (idx[idx.length - 1] !== len - 1) idx.push(len - 1);\n + │ return idx;\n }\n\n // ---------- charts ----------", + │ "newString": " // ---------- charts ----------" + │ } + │ out Edit applied successfully. + │ + │ tokens 18 in 3447 out 37831 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 2ae871ab + + + │ 2026-08-30 18:12:45 • assistant • Qwen3.8-27B-Think + │ + │ Alright, let me check the current state of app.js around the chart code. + │ + │ read /home/mac/Code/dashboard/static/js/app.js [output truncated] ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "offset": 39, + │ "limit": 90 + │ } + │ out + │ /home/mac/Code/dashboard/static/js/app.js + │ file + │ + │ 39: // ---------- charts ---------- + │ 40: const charts = {}; + │ 41: + │ 42: function baseOpts(extra) { + │ 43: const o = { + │ 44: animation: false, + │ 45: responsive: true, + │ 46: maintainAspectRatio: false, + │ 47: interaction: { mode: "index", intersect: false }, + │ 48: plugins: { legend: { display: false } }, + │ 49: scales: { + │ 50: x: { ticks: { maxTicksLimit: 7, maxRotation: 0, color: "#7d8a9c" }, grid: { + │ display: false } }, + │ 51: y: { beginAtZero: true, ticks: { color: "#7d8a9c" }, grid: { color: + │ "rgba(42,51,66,.5)" } }, + │ 52: }, + │ 53: }; + │ 54: if (extra) Object.assign(o.scales.y, extra); + │ 55: return o; + │ 56: } + │ 57: + │ 58: function newDs(label, color, extra) { + │ 59: return Object.assign( + │ 60: { label, data: [], borderColor: color, backgroundColor: color, borderWidth: 1.5, + │ pointRadius: 0, tension: 0.25, fill: false }, + │ 61: extra || {} + │ 62: ); + │ 63: } + │ 64: + │ 65: const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c" } }; + │ 66: + │ 67: function initCharts() { + │ 68: if (typeof Chart === "undefined") return; + │ 69: charts.cpu = new Chart(document.getElementById("chart-cpu"), { + │ 70: type: "line", + │ 71: data: { + │ 72: labels: [], + │ 73: datasets: [newDs("CPU", "#4fc3f7"), newDs("GPU", "#ba68c8")], + │ 74: }, + │ 75: options: baseOpts({ max: 100 }), + │ 76: }); + │ 77: charts.cpu.options.plugins.legend = LEGEND; + │ 78: charts.mem = new Chart(document.getElementById("chart-mem"), { + │ 79: type: "line", + │ 80: data: { + │ 81: labels: [], + │ 82: datasets: [newDs("Memory", "#81c784"), newDs("VRAM", "#ffb74d")], + │ 83: }, + │ 84: options: baseOpts({ max: 100 }), + │ 85: }); + │ 86: charts.mem.options.plugins.legend = LEGEND; + │ 87: charts.io = new Chart(document.getElementById("chart-io"), { + │ 88: type: "line", + │ 89: data: { + │ 90: labels: [], + │ 91: datasets: [ + │ … [1470 more chars] + │ truncated 3470 chars shown, the rest was not kept + │ + │ tokens 19 in 78 out 41296 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:12:47 • assistant • Qwen3.8-27B-Think + │ + │ OK, so let's replace newDs + LEGEND + initCharts + updateCharts. Note: LEGEND is shared across + │ all 3 charts, and `labels.filter` will be applied to each chart — no problem. + │ + │ Edit A: newDs → envelopeDs, add filter to LEGEND. + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " function newDs(label, color, extra) {\n return Object.assign(\n { + │ label, data: [], borderColor: color, backgroundColor: color, borderWidth: 1.5, + │ pointRadius: 0, tension: 0.25, fill: false },\n extra || {}\n );\n }\n\n const + │ LEGEND = { display: true, labels: { boxWidth: 10, color: \"#7d8a9c\" } };", + │ "newString": " function envelopeDs(name, color, s) {\n s = s || {};\n return [\n + │ { label: name, data: s.avg || [], borderColor: color, backgroundColor: color, borderWidth: + │ 1.5, pointRadius: 0, tension: 0.25, fill: false, order: 0 },\n { label: name + \" + │ max\", band: true, data: s.max || [], borderColor: color, backgroundColor: color + \"2e\", + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 2 },\n { label: name + │ + \" min\", band: true, data: s.min || [], borderColor: color, backgroundColor: color, + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 1 },\n ];\n }\n\n + │ const LEGEND = { display: true, labels: { boxWidth: 10, color: \"#7d8a9c\", filter: (item) + │ => !item.dataset.band } };" + │ } + │ out Edit applied successfully. + │ + │ tokens 1306 in 455 out 41392 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 06c1cac5 + + + │ 2026-08-30 18:13:01 • assistant • Qwen3.8-27B-Think + │ + │ Wait — the array order in `envelopeDs`: [avg(idx0), max(idx1), min(idx2)]. `order` values: avg + │ 0, max 2, min 1. In Chart.js, the higher the `order` → the earlier it's drawn (the further back + │ it goes). So the drawing is: max (order 2) first, then min (1), and finally avg (0) at the very + │ front. + │ + │ Next, initCharts: replace the use of newDs with envelopeDs. + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " charts.cpu = new Chart(document.getElementById(\"chart-cpu\"), {\n + │ type: \"line\",\n data: {\n labels: [],\n datasets: [newDs(\"CPU\", + │ \"#4fc3f7\"), newDs(\"GPU\", \"#ba68c8\")],\n },\n options: baseOpts({ max: 100 + │ }),\n });\n charts.cpu.options.plugins.legend = LEGEND;\n charts.mem = new + │ Chart(document.getElementById(\"chart-mem\"), {\n type: \"line\",\n data: {\n + │ labels: [],\n datasets: [newDs(\"Memory\", \"#81c784\"), newDs(\"VRAM\", + │ \"#ffb74d\")],\n },\n options: baseOpts({ max: 100 }),\n });\n + │ charts.mem.options.plugins.legend = LEGEND;\n charts.io = new + │ Chart(document.getElementById(\"chart-io\"), {\n type: \"line\",\n data: {\n + │ labels: [],\n datasets: [newDs(\"read\", \"#e57373\"), newDs(\"write\", + │ \"#fff176\")],\n },\n options: baseOpts({", + │ "newString": " charts.cpu = new Chart(document.getElementById(\"chart-cpu\"), {\n + │ type: \"line\",\n data: {\n labels: [],\n datasets: + │ [].concat(envelopeDs(\"CPU\", \"#4fc3f7\"), envelopeDs(\"GPU\", \"#ba68c8\")),\n },\n + │ options: baseOpts({ max: 100 }),\n });\n charts.cpu.options.plugins.legend = + │ LEGEND;\n charts.mem = new Chart(document.getElementById(\"chart-mem\"), {\n type: + │ \"line\",\n data: {\n labels: [],\n datasets: + │ [].concat(envelopeDs(\"Memory\", \"#81c784\"), envelopeDs(\"VRAM\", \"#ffb74d\")),\n + │ },\n options: baseOpts({ max: 100 }),\n });\n charts.mem.options.plugins.legend + │ = LEGEND;\n charts.io = new Chart(document.getElementById(\"chart-io\"), {\n type: + │ \"line\",\n data: {\n labels: [],\n datasets: + │ [].concat(envelopeDs(\"read\", \"#e57373\"), envelopeDs(\"write\", \"#fff176\")),\n + │ },\n options: baseOpts({" + │ } + │ out Edit applied successfully. + │ + │ tokens 20 in 686 out 43152 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) c45c1c24 + + + │ 2026-08-30 18:13:17 • assistant • Qwen3.8-27B-Think + │ + │ Next, I'll rewrite updateCharts: + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " function updateCharts(hist) {\n if (!hist || !hist.ts || + │ !hist.ts.length) return;\n const ts = hist.ts;\n const idx = + │ downsampleIdx(ts.length, 400);\n const pick = (arr) => (arr && idx ? idx.map((i) => (i + │ < arr.length ? arr[i] : null)) : arr);\n const labels = (idx ? idx.map((i) => ts[i]) : + │ ts).map(fmtTime);\n\n const set2 = (chart, keys) => {\n chart.data.labels = + │ labels;\n keys.forEach((k, i) => {\n chart.data.datasets[i].data = + │ pick(hist.series[k]) || [];\n });\n chart.update(\"none\");\n };\n + │ set2(charts.cpu, [\"cpu\", \"gpu\"]);\n set2(charts.mem, [\"mem_pct\", + │ \"vram_pct\"]);\n\n charts.io.data.labels = labels;\n + │ charts.io.data.datasets[0].data = pick(hist.series.io_read) || [];\n + │ charts.io.data.datasets[1].data = pick(hist.series.io_write) || [];\n + │ charts.io.update(\"none\");\n }", + │ "newString": " function updateCharts(hist) {\n if (!hist || !hist.ts || + │ !hist.ts.length) return;\n const labels = hist.ts.map(fmtTime);\n const series = + │ hist.series || {};\n const build = (chart, pairs) => {\n chart.data.labels = + │ labels;\n chart.data.datasets = [];\n pairs.forEach(([name, color, key], i) => + │ {\n const ds = envelopeDs(name, color, series[key]);\n ds[1].fill = i * 3 + + │ 2;\n chart.data.datasets.push(...ds);\n });\n chart.update(\"none\");\n + │ };\n build(charts.cpu, [[\"CPU\", \"#4fc3f7\", \"cpu\"], [\"GPU\", \"#ba68c8\", + │ \"gpu\"]]);\n build(charts.mem, [[\"Memory\", \"#81c784\", \"mem_pct\"], [\"VRAM\", + │ \"#ffb74d\", \"vram_pct\"]]);\n build(charts.io, [[\"read\", \"#e57373\", \"io_read\"], + │ [\"write\", \"#fff176\", \"io_write\"]]);\n }" + │ } + │ out Edit applied successfully. + │ + │ tokens 18 in 574 out 43859 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 8cafc926 + + + │ 2026-08-30 18:13:30 • assistant • Qwen3.8-27B-Think + │ + │ Next, I'll update the pitfalls line in AGENTS.md. Let me read the relevant section. + │ + │ Now the AGENTS.md pitfall note: + │ + │ read /home/mac/Code/dashboard/AGENTS.md ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/AGENTS.md", + │ "offset": 76, + │ "limit": 14 + │ } + │ out + │ /home/mac/Code/dashboard/AGENTS.md + │ file + │ + │ 76: `/sys/class/power_supply/*` (type/capacity/status/online) for battery + AC + │ 77: state. + │ 78: - `iw dev link` prints `SSID: name` **unquoted**; the working regex is + │ 79: `SSID:\s+(\S.*)` (a `$` anchor fails without MULTILINE). + │ 80: - `/api/history` pads series with `null` for samples missing a key so all + │ 81: series stay aligned with the timestamps — keep that behaviour if you touch + │ 82: it. + │ 83: - AMD sysfs: GPU busy/VRAM/temp under + │ 84: `/sys/class/drm/card*/device` (+ `hwmon`), CPU temp from the `k10temp` + │ 85: hwmon (fallback `acpitz` thermal zone), both in millidegrees. + │ + │ (End of file - total 85 lines) + │ + │ + │ tokens 18 in 92 out 44452 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:13:32 • assistant • Qwen3.8-27B-Think + │ + │ Let me update the AGENTS.md pitfall notes about /api/history. The current notes state that + │ series are padded with null for samples where the key is missing. Now, the new behavior is as + │ follows: the series is windowed/averaged into avg/min/max, and it's padded with null so that + │ each of the three arrays stays aligned with the timestamps. + │ + │ edit /home/mac/Code/dashboard/AGENTS.md ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/AGENTS.md", + │ "oldString": "- `/api/history` pads series with `null` for samples missing a key so all\n + │ series stay aligned with the timestamps — keep that behaviour if you touch\n it.", + │ "newString": "- `/api/history` window-averages the ring buffer down to at most\n + │ `chart_max_points` (default 200) points, emitting `{avg, min, max}` per key,\n and pads + │ each of the three arrays with `null` for windows missing a key so\n they stay aligned + │ with the timestamps — keep that behaviour if you touch it." + │ } + │ out Edit applied successfully. + │ + │ tokens 257 in 250 out 44561 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 90cf2bf1 + + + │ 2026-08-30 18:13:40 • assistant • Qwen3.8-27B-Think + │ + │ Alright, let's verify. Steps: + │ 1. `uv run python -m compileall -q app` + │ 2. `uvx basedpyright app` (or `uvx basedpyright app/routers/overview.py app/config.py`) + │ 3. Synthetic test for `_window`. + │ + │ Let me update the TODO and run compile + pyright in parallel. + │ + │ todowrite 1 todos ok 0.0s + │ in + │ { + │ "todos": [ + │ { + │ "content": "Add chart_max_points setting to app/config.py", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Window-average (avg/min/max) in /api/history", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Rewrite chart datasets in static/js/app.js as min/max bands", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Update AGENTS.md /api/history pitfall note", + │ "status": "completed", + │ "priority": "medium" + │ }, + │ { + │ "content": "Verify: compileall, basedpyright, synthetic test, restart, curl, + │ screenshot", + │ "status": "in_progress", + │ "priority": "high" + │ } + │ ] + │ } + │ out + │ [ + │ { + │ "content": "Add chart_max_points setting to app/config.py", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Window-average (avg/min/max) in /api/history", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Rewrite chart datasets in static/js/app.js as min/max bands", + │ "status": "completed", + │ "priority": "high" + │ }, + │ { + │ "content": "Update AGENTS.md /api/history pitfall note", + │ "status": "completed", + │ "priority": "medium" + │ }, + │ { + │ "content": "Verify: compileall, basedpyright, synthetic test, restart, curl, + │ screenshot", + │ "status": "in_progress", + │ "priority": "high" + │ } + │ ] + │ bash uv run python -m compileall -q app && echo COMPILE_OK ok 0.0s + │ in + │ { + │ "command": "uv run python -m compileall -q app && echo COMPILE_OK", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out COMPILE_OK + │ + │ tokens 45129 in 311 out 0 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:15:02 • assistant • Qwen3.8-27B-Think + │ + │ Compile OK. Next is basedpyright and a synthetic test of `_window`. + │ + │ For the synthetic test, I'll write a small script to /tmp/opencode and do the following: + │ - Import `_window` from app.routers.overview. + │ - Build 1800 samples with known structure: + │ - Key "cpu": floats, values cycling 0–100; with a spike of 100.0 at a known index. + │ - Key "mem_total": constant int 1_000_000. + │ - Key "gpu": None for the first 300 samples, then floats (tests None gaps + windows that are + │ all None → keys absent → padding None). + │ - Key "flag": True (bool — should be completely excluded). + │ - Key "name": string "x" (excluded). + │ - max_points=200. + │ - Asserts: + │ - len(out) == ceil(1800/200) = 9? No wait: w = ceil(1800/200) = 9; number of windows = + │ ceil(1800/9) = 200. So len(out) == 200. + │ - Each window's ts = the ts of the last sample in the chunk. + │ - cpu window 0 (samples 0..8): known values → verify avg/min/max exactly (1 decimal place). + │ - Spike: put a 100.0 spike at index 44 (window 4: samples 36–44 → w=9 → window 4 covers + │ 36..44, last index 44). That window's max must be 100.0 (peak is preserved!). + │ - mem_total: all windows are int 1_000_000, int type. + │ - gpu: windows fully contained in the first 300 samples (windows 0..32: sample 300 is window + │ 33 (297..305)) → windows 0–32 must not have the "gpu" key; window 33 has gpu (samples 297–305, + │ first 3 are None → 6 values present). + │ - bool/string keys are not included in any row. + │ - Also test the n ≤ max_points path: 50 samples, max_points 200 → 50 rows, for each row + │ avg==min==max==the sample's value. + │ + │ Let me compute the expected values in the script itself (independently recomputing from the raw + │ samples — well, that's a bit circular, but it's a validation of window boundaries; better to + │ hardcode a few). Do both: for window 0 and the spike window, hardcode the expected values, and + │ additionally do a full independent recomputation with a different implementation approach + │ (groupby using divmod). Actually, a truly independent check: for each win + │ … [446 more chars] + │ + │ write /tmp/opencode/test_window.py ok 0.0s + │ in + │ { + │ "filePath": "/tmp/opencode/test_window.py", + │ "content": "import math\n\nfrom app.routers.overview import _window\n\nN = 1800\nMP = + │ 200\nw = math.ceil(N / MP)\nsnap = []\nfor i in range(N):\n s = {\n \"cpu\": + │ float(i % 50),\n \"mem_total\": 1_000_000,\n \"gpu\": None if i < 300 else + │ float(10 + i % 20),\n \"flag\": True,\n \"name\": \"x\",\n }\n if i == + │ 44:\n s[\"cpu\"] = 100.0\n snap.append((1000.0 + i * 2.0, s))\n\nout = + │ _window(snap, MP)\n\nassert len(out) == math.ceil(N / w) == MP, len(out)\nfor i, (t, row) + │ in enumerate(out):\n last = min((i + 1) * w, N) - 1\n assert t == snap[last][0], (i, + │ t, snap[last][0])\n\ndef vals(i, key):\n a, b = i * w, min((i + 1) * w, N)\n return + │ [snap[j][1][key] for j in range(a, b) if isinstance(snap[j][1][key], (int, float)) and not + │ isinstance(snap[j][1][key], bool)]\n\nr0 = out[0][1][\"cpu\"]\nv0 = vals(0, + │ \"cpu\")\nassert r0[\"avg\"] == round(sum(v0) / len(v0), 1), r0\nassert r0[\"min\"] == + │ round(min(v0), 1)\nassert r0[\"max\"] == round(max(v0), 1)\n\nr4 = + │ out[4][1][\"cpu\"]\nassert r4[\"max\"] == 100.0, r4\n\nfor i in range(MP):\n mt = + │ out[i][1][\"mem_total\"]\n assert mt == {\"avg\": 1_000_000, \"min\": 1_000_000, + │ \"max\": 1_000_000}, mt\n assert isinstance(mt[\"avg\"], int) and + │ isinstance(mt[\"min\"], int)\n for k in (\"flag\", \"name\"):\n assert k not in + │ out[i][1], (i, k)\n\nfor i in range(0, 33):\n assert \"gpu\" not in out[i][1], + │ i\nassert \"gpu\" in out[33][1], out[33][1].keys()\nvg = vals(33, \"gpu\")\nrg = + │ out[33][1][\"gpu\"]\nassert rg[\"avg\"] == round(sum(vg) / len(vg), 1), (rg, vg)\n\nshort + │ = _window(snap[:50], MP)\nassert len(short) == 50\nfor i in range(50):\n for k in + │ (\"cpu\", \"mem_total\", \"flag\"):\n if k == \"flag\":\n continue\n + │ a = short[i][1][k]\n assert a == {\"avg\": snap[i][1][k], \"min\": snap[i][1][k], + │ \"max\": snap[i][1][k]}, (i, k, a)\n\nempty = _window([], MP)\nassert empty + │ … [265 more chars] + │ out Wrote file successfully. + │ + │ tokens 233 in 1647 out 45439 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:15:47 • assistant • Qwen3.8-27B-Think + │ + │ Wait a sec — regarding `short = _window(snap[:50], MP)`: when n=50 ≤ 200, w = max(1, + │ ceil(50/200)) = max(1, 1) = 1. ceil(50/200) = 1 (0.25 → ceil = 1). OK, w=1 → 50 rows. + │ + │ However, note: in the short case, the gpu for samples 0..49 is None → the gpu key doesn't exist + │ in any row. No problem, I'm only checking cpu and mem_total. + │ + │ Also, in the `vals` helper I'm excluding bool — flag=True is bool → excluded. OK. + │ + │ Run it, and also run basedpyright in parallel. + │ + │ bash uv run python /tmp/opencode/test_window.py ok 0.0s + │ in + │ { + │ "command": "uv run python /tmp/opencode/test_window.py", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out + │ Traceback (most recent call last): + │ File "/tmp/opencode/test_window.py", line 3, in + │ from app.routers.overview import _window + │ ModuleNotFoundError: No module named 'app' + │ bash uvx basedpyright app 2>&1 | tail -15 ok 0.0s + │ in + │ { + │ "command": "uvx basedpyright app 2>&1 | tail -15", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 180000 + │ } + │ out + │ /home/mac/Code/dashboard/app/config.py:7:5 - warning: Type annotation for attribute + │ `model_config` is required because this class is not decorated with `@final` + │ (reportUnannotatedClassAttribute) + │ /home/mac/Code/dashboard/app/main.py + │ /home/mac/Code/dashboard/app/main.py:41:15 - warning: Function "index" is not accessed + │ (reportUnusedFunction) + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py:18:12 - warning: Return type, + │ "dict[Unknown, Unknown]", is partially unknown (reportUnknownVariableType) + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py:66:46 - warning: Type of parameter "m" is + │ unknown (reportUnknownLambdaType) + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py:66:49 - warning: Return type of lambda is + │ unknown (reportUnknownLambdaType) + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py:108:29 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ /home/mac/Code/dashboard/app/plugins/llamacpp.py:119:31 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ /home/mac/Code/dashboard/app/routers/services.py + │ /home/mac/Code/dashboard/app/routers/services.py:98:19 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ /home/mac/Code/dashboard/app/routers/services.py:99:14 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ /home/mac/Code/dashboard/app/routers/services.py:100:17 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ /home/mac/Code/dashboard/app/routers/services.py:101:18 - warning: Function calls and + │ mutable objects not allowed within parameter default value expression + │ (reportCallInDefaultInitializer) + │ 2 + │ … [28 more chars] + │ + │ tokens 20 in 300 out 47319 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:16:01 • assistant • Qwen3.8-27B-Think + │ + │ There are 2 errors, but they're cut off by `tail -15`. I need to check the errors. Let me try + │ running basedpyright with full output filtered to "error". Also, I'll fix the test's import + │ issue (it's an issue with the script's working directory / Python path — even though I'm running + │ it with workdir=dashboard, `uv run python /tmp/opencode/test_window.py` is executed with + │ cwd=dashboard, but since the script is in /tmp/opencode, `sys.path[0]` is /tmp/opencode. The + │ repo root is not included in sys.path. I'll set PYTHONPATH=. ). + │ + │ bash uv run python -c "import sys; sys.path.insert(0, '.'); exec(open('/tmp/openc… ok 0.2s + │ in + │ { + │ "command": "uv run python -c \"import sys; sys.path.insert(0, '.'); + │ exec(open('/tmp/opencode/test_window.py').read())\"", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out + │ window size: 9 windows: 200 + │ window0 cpu: {'avg': 4.0, 'min': 0.0, 'max': 8.0} + │ spike window4 cpu: {'avg': 46.2, 'min': 36.0, 'max': 100.0} + │ gpu gap window32/33: None {'avg': 12.5, 'min': 10.0, 'max': 15.0} + │ SYNTHETIC_OK + │ bash uvx basedpyright app 2>&1 | grep -E "error" ok 0.0s + │ in + │ { + │ "command": "uvx basedpyright app 2>&1 | grep -E \"error\" ", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 180000 + │ } + │ out + │ /home/mac/Code/dashboard/app/__init__.py: error: Cycle detected in import chain + │ /home/mac/Code/dashboard/app/__init__.py: error: Cycle detected in import chain + │ 2 errors, 12 warnings, 0 notes + │ + │ tokens 560 in 288 out 47639 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:16:16 • assistant • Qwen3.8-27B-Think + │ + │ Synthetic tests pass: + │ - 1800 samples → 200 windows (w=9) + │ - window0's cpu average/min/max are correct + │ - Spike is preserved: window4's max = 100.0 + │ - gpu's None gap: windows 0-32 don't have gpu, window 33 is averaged over the non-None values + │ - Short paths, int preservation, exclusion of bool/str — all asserted OK. + │ + │ basedpyright: 2 errors "Cycle detected in import chain" in app/__init__.py. Is this + │ pre-existing? Let me check app/__init__.py and check git status/stash to see if it's + │ pre-existing. Probably pre-existing (app/__init__.py is importing something circular — e.g., + │ main.py creates the app, and the router imports it?). Let me check the file and verify whether + │ these errors existed before my changes (I can check with git stash, but more simply: my changes + │ don't touch app/__init__.py or main.py's imports. I only added `from app.config import + │ get_settings` to overview.py. config.py only imports pydantic_settings. Cycle: app/__init__.py → + │ ? Let's take a look. + │ + │ Synthetic test passes — spike preserved (window max 100.0), gaps and int handling correct. Now + │ checking whether those 2 basedpyright errors are pre-existing: + │ + │ read /home/mac/Code/dashboard/app/__init__.py ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/app/__init__.py" + │ } + │ out + │ /home/mac/Code/dashboard/app/__init__.py + │ file + │ + │ 1: from app import routers # noqa: F401 + │ + │ (End of file - total 1 lines) + │ + │ bash git -C /home/mac/Code/dashboard status --short; git -C /home/mac/Code/dashbo… ok 0.0s + │ in + │ { + │ "command": "git -C /home/mac/Code/dashboard status --short; git -C + │ /home/mac/Code/dashboard stash -q && uvx basedpyright app 2>&1 | grep -cE \" error\"; git + │ -C /home/mac/Code/dashboard stash pop -q", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 180000 + │ } + │ out + │ M AGENTS.md + │ M app/config.py + │ M app/routers/overview.py + │ M static/js/app.js + │ 3 + │ + │ tokens 192 in 450 out 48486 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:16:35 • assistant • Qwen3.8-27B-Think + │ + │ Stash test: even with my changes stashed, basedpyright still reports errors — `grep -cE " + │ error"` counts lines containing " error" → 3 lines: 2 cycle error lines + 1 summary line ("2 + │ errors, 12 warnings"). So this cycle error is pre-existing (app/__init__.py imports app.routers, + │ and the routers import app.config etc. — a cycle visible from basedpyright's perspective, + │ pre-existing, and I haven't touched it in my changes). After stash pop, my changes are restored. + │ + │ So: no new basedpyright errors from my changes (same 2 pre-existing errors, and no new warnings + │ in the files I edited — the warnings listed are in config.py:7, main.py:41, llamacpp.py, + │ services.py, all pre-existing. Wait, the warning at config.py:7 — is that pre-existing? + │ config.py:7 is `model_config = SettingsConfigDict(...)` — yes, pre-existing line, I just added a + │ field at line 13. Let me confirm there are no new warnings on the lines I edited: the warnings + │ enumerated are config.py:7 (pre-existing), main.py:41, llamacpp, services — none in overview.py + │ or on the lines I added. Good. + │ + │ Let me double-check that stash pop worked and the files are back. git status shows 4 modified + │ files — but wait, that status output was from before the stash. Let me verify the files are + │ intact after the pop. + │ + │ Next: restart the dev server and verify the live endpoint + screenshot. + │ + │ Restart procedure per AGENTS.md: + │ ```sh + │ PID=$(pgrep -f "python main\.py" | head -1) + │ [ -n "$PID" ] && kill "$PID" + │ setsid nohup uv run python main.py > /tmp/dash.log 2>&1 < /dev/null & disown + │ ``` + │ + │ After that, curl the endpoint, verify the shape and length, then a chromium screenshot. + │ + │ The 2 cycle errors are pre-existing (they exist in the stashed baseline too; the 3 matches are 2 + │ error lines + the summary line). No new issues in the files I modified. I'll verify that the + │ stash pop restored my changes, then restart the server: + │ + │ bash git -C /home/mac/Code/dashboard diff --stat ok 0.0s + │ in + │ { + │ "command": "git -C /home/mac/Code/dashboard diff --stat" + │ } + │ out + │ AGENTS.md | 7 ++++--- + │ app/config.py | 1 + + │ app/routers/overview.py | 54 +++++++++++++++++++++++++++++++++++------------- + │ static/js/app.js | 55 ++++++++++++++++++++----------------------------- + │ 4 files changed, 67 insertions(+), 50 deletions(-) + │ + │ tokens 103 in 521 out 49127 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:16:50 • assistant • Qwen3.8-27B-Think + │ + │ The user repeated the same request. It seems the previous session got interrupted partway + │ through (I was in the middle of verification). Let me check the current state: my edits to + │ config.py, overview.py, app.js, and AGENTS.md have already been applied (confirmed via git diff + │ --stat). The synthetic tests passed. basedpyright only shows pre-existing errors. + │ + │ Remaining work: restart the dev server, run curl to check the shape/length of /api/history, take + │ a screenshot, and check the traceback. Let me continue with the verification steps. + │ + │ Wait a sec — but the user resent the same message. Maybe they couldn't see the progress, or the + │ session restarted. Best move: quickly check the current state (are the edits reflected? is the + │ server running?), then finish the verification and report back. + │ + │ Let me check git status/diff to confirm the changes are still there, and check the server + │ process. + │ + │ bash git -C /home/mac/Code/dashboard diff --stat; pgrep -af "python main\.py" || … ok 0.0s + │ in + │ { + │ "command": "git -C /home/mac/Code/dashboard diff --stat; pgrep -af \"python main\\.py\" || + │ echo \"server not running\"" + │ } + │ out + │ AGENTS.md | 7 ++++--- + │ app/config.py | 1 + + │ app/routers/overview.py | 54 +++++++++++++++++++++++++++++++++++------------- + │ static/js/app.js | 55 ++++++++++++++++++++----------------------------- + │ 4 files changed, 67 insertions(+), 50 deletions(-) + │ 33078 uv run python main.py + │ + │ tokens 85 in 247 out 49752 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:16:58 • assistant • Qwen3.8-27B-Think + │ + │ Status check: all four edits from the previous session are present, and the old server (PID + │ 33078) is still running with the old code. Remaining work: restart the server, verify the live + │ endpoint, screenshot, check for tracebacks. + │ + │ Proceeding according to the restart procedure in AGENTS.md. + │ + │ The implementation is complete (edits to `config.py`, `overview.py`, `app.js`, and `AGENTS.md`). + │ I'm currently running the final verification against the live server: + │ + │ bash PID=$(pgrep -f "python main\.py" | head -1); [ -n "$PID" ] && kill "$PID"; s… ok 0.0s + │ in + │ { + │ "command": "PID=$(pgrep -f \"python main\\.py\" | head -1); [ -n \"$PID\" ] && kill + │ \"$PID\"; sleep 1; setsid nohup uv run python main.py > /tmp/dash.log 2>&1 < /dev/null & + │ disown; sleep 4; pgrep -af \"python main\\.py\"", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out 84742 uv run python main.py + │ + │ tokens 97 in 225 out 50084 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:17:10 • assistant • Qwen3.8-27B-Think + │ + │ The server has been restarted (new PID 84742). Since the new process starts with an empty ring + │ buffer, /api/history will return fewer than 200 points for the first ~7 minutes (the + │ no-windowing path). I'll do the following: + │ + │ 1. Verify that all endpoints return 200. + │ 2. Verify the shape of /api/history right now (few points, but it should be in the {avg,min,max} + │ format). + │ 3. Wait until the buffer exceeds 200 samples? That would take about 7 minutes of real time. + │ Alternatively, I could verify the windowing behavior on the live server by checking the length + │ of ts, but... it'll only be 1–2 points at this point in time. + │ + │ Better approach: verify the shape right now, and either verify the cap on the live server later, + │ or trust the synthetic tests (which precisely cover the 1800→200 path). In fact, the synthetic + │ tests have already validated _window with 1800 samples. For the live endpoint, I can verify the + │ following: + │ - Right now: shape correctness + alignment (each key has avg/min/max arrays of length == + │ len(ts)). + │ - After about 7 minutes: len(ts) == 200. + │ + │ Hmm, 7 minutes is a long time to wait. Options: temporarily set DASH_CHART_MAX_POINTS to a small + │ value (e.g., 20) on a second instance? The port is in use... I can run a test with a FastAPI + │ TestClient in a script: create the app, record 1800 samples into app.state.store, and then call + │ /api/history via the TestClient. This would test the full endpoint (not just _window) end-to-end + │ without touching the running server. + │ + │ This is a great idea — the AGENTS.md session log mentioned TestClient lifespan behavior (line + │ 2962: "empty because TestClient's lifespan..." — interesting, lifespan runs when using + │ TestClient with a context manager). With `with TestClient(app) as c:`, the lifespan runs and the + │ sampler task starts (recording every 2 seconds — harmless). After that, I can manually record + │ samples into the store. + │ + │ Let me write that test: + │ + │ ```python + │ import sys + │ sys.path.insert(0, "/home/mac/Code/dashboard") + │ from fastapi.testclient impo + │ … [7124 more chars] + │ + │ I've confirmed the changes are in place and the server is running. Now I'll run an end-to-end + │ test of `/api/history` using TestClient with a pre-seeded store, then visually verify the charts + │ with a headless Chromium instance. + │ + │ write /tmp/opencode/test_endpoint.py ok 0.0s + │ in + │ { + │ "content": "import sys\n\nsys.path.insert(0, \"/home/mac/Code/dashboard\")\n\nfrom + │ fastapi.testclient import TestClient\nfrom app.main import app\n\nwith TestClient(app) as + │ c:\n store = app.state.store\n for i in range(1800):\n s = {\n + │ \"cpu\": 100.0 if i == 44 else float(i % 50),\n \"mem_total\": 1_000_000,\n + │ \"gpu\": None if i < 300 else float(10 + i % 20),\n \"flag\": True,\n + │ \"name\": \"x\",\n }\n store.record(s)\n r = c.get(\"/api/history\")\n + │ assert r.status_code == 200, r.status_code\n d = r.json()\n ts = d[\"ts\"]\n + │ assert len(ts) == 200, len(ts)\n for k, v in d[\"series\"].items():\n assert + │ set(v) == {\"avg\", \"min\", \"max\"}, (k, v.keys())\n for m in (\"avg\", \"min\", + │ \"max\"):\n assert len(v[m]) == len(ts), (k, m, len(v[m]))\n assert \"flag\" + │ not in d[\"series\"]\n assert \"name\" not in d[\"series\"]\n assert 100.0 in + │ d[\"series\"][\"cpu\"][\"max\"]\n assert d[\"series\"][\"mem_total\"][\"avg\"] == + │ [1_000_000] * 200\n assert all(isinstance(x, int) for x in + │ d[\"series\"][\"mem_total\"][\"max\"])\n gpu_max = d[\"series\"][\"gpu\"][\"max\"]\n + │ first_present = gpu_max.index(next(v for v in gpu_max if v is not None))\n assert + │ first_present == 33, first_present\n for i in range(33):\n assert gpu_max[i] is + │ None and d[\"series\"][\"gpu\"][\"avg\"][i] is None\n print(\"ENDPOINT_OK points:\", + │ len(ts), \"keys:\", len(d[\"series\"]))\n", + │ "filePath": "/tmp/opencode/test_endpoint.py" + │ } + │ out Wrote file successfully. + │ + │ tokens 24 in 3060 out 50407 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:18:39 • assistant • Qwen3.8-27B-Think + │ + │ Let's try running an end-to-end test on the endpoint. Note that the TestClient starts the + │ lifespan and runs the sampler loop, which could interfere, but since we're directly injecting + │ 1800 samples into the store, it should be fine. Let's give it a shot. + │ + │ bash uv run python /tmp/opencode/test_endpoint.py 2>&1 | tail -20 ok 0.0s + │ in + │ { + │ "command": "uv run python /tmp/opencode/test_endpoint.py 2>&1 | tail -20", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 60000 + │ } + │ out + │ /home/mac/Code/dashboard/.venv/lib/python3.14/site-packages/fastapi/testclient.py:1: + │ StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; + │ install `httpx2` instead. + │ from starlette.testclient import TestClient as TestClient # noqa + │ ENDPOINT_OK points: 200 keys: 3 + │ + │ tokens 21 in 139 out 53490 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:18:44 • assistant • Qwen3.8-27B-Think + │ + │ Endpoint test passed: 1800 recorded samples → exactly 200 points, {avg,min,max} structure, + │ aligned arrays, bool/str keys excluded, spikes preserved (100.0 in max), int preserved, gpu's + │ None gap up through window 32 (first present at index 33 — matches synthetic test). + │ + │ Next, visual check. Write a throwaway screenshot server that pre-seeds the store with + │ plausible-looking data, run it on port 8599, and take a screenshot with headless Chromium. + │ + │ The endpoint is working correctly with 1800 samples compressed to 200 points while preserving + │ all data. Now I'll create a screenshot server to test the full UI. + │ + │ write /tmp/opencode/shot_server.py ok 0.0s + │ in + │ { + │ "content": "import asyncio\nimport math\nimport random\nimport sys\nimport + │ time\n\nsys.path.insert(0, \"/home/mac/Code/dashboard\")\n\nimport uvicorn\nfrom app.main + │ import app\n\nrandom.seed(7)\n\n\ndef seed() -> None:\n store = app.state.store\n + │ now = time.time()\n base = now - 1800 * 2\n for i in range(1800):\n t = base + │ + i * 2\n s = {\n \"cpu\": max(0.0, 25 + 18 * math.sin(i / 90) + + │ random.uniform(-6, 6) + (85 if i % 250 == 120 else 0)),\n \"gpu\": max(0.0, 15 + │ + 10 * math.sin(i / 60) + random.uniform(-5, 5)) if i % 9 else None,\n + │ \"mem_pct\": 42 + 12 * math.sin(i / 400) + random.uniform(-2, 2),\n + │ \"vram_pct\": 30 + 15 * math.sin(i / 150) + random.uniform(-3, 3) if i % 6 else None,\n + │ \"mem_total\": 16 * 1024**3,\n \"mem_used\": int(0.55 * 16 * 1024**3),\n + │ \"load1\": 2.1,\n \"load5\": 2.4,\n \"load15\": 2.2,\n + │ \"swap_used\": 1024**3,\n \"swap_total\": 8 * 1024**3,\n + │ \"io_read\": random.uniform(0, 180e6) if i % 4 else random.uniform(0, 10e6),\n + │ \"io_write\": random.uniform(5e6, 90e6),\n \"cpu_temp\": 55.0 + 8 * math.sin(i + │ / 200),\n \"gpu_temp\": 48.0 + 6 * math.sin(i / 170),\n \"battery\": + │ 87,\n }\n store.record_at(t, s)\n\n\nconfig = uvicorn.Config(app, + │ host=\"127.0.0.1\", port=8599, log_level=\"warning\")\nserver = + │ uvicorn.Server(config)\n\n\nasync def main() -> None:\n async def seeder() -> None:\n + │ await asyncio.sleep(1.5)\n seed()\n task = asyncio.create_task(seeder())\n + │ await server.serve()\n task.cancel()\n\n\nif __name__ == \"__main__\":\n + │ asyncio.run(main())\n", + │ "filePath": "/tmp/opencode/shot_server.py" + │ } + │ out Wrote file successfully. + │ + │ tokens 100 in 818 out 53649 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:07 • assistant • Qwen3.8-27B-Think + │ + │ Referenced `store.record_at(t, s)`, but HistoryStore doesn't have that method — it only has + │ `record(sample)`, which uses time.time(). Need to either add a method or append directly to + │ `store._buf`. Options: + │ + │ 1. Directly append to `store._buf.append((t, s))` — accessing a private attribute from a + │ throwaway script is fine. + │ 2. Add a `record_at` method to HistoryStore — modifying production code just for a test is… + │ actually it could be legitimately useful, but it expands the scope. + │ + │ For a throwaway script, the simplest thing is to use `store._buf.append`. Let's fix that. + │ + │ edit /tmp/opencode/shot_server.py ok 0.0s + │ in + │ { + │ "filePath": "/tmp/opencode/shot_server.py", + │ "oldString": " store.record_at(t, s)", + │ "newString": " store._buf.append((t, s))" + │ } + │ out Edit applied successfully. + │ + │ tokens 19 in 217 out 54568 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:13 • assistant • Qwen3.8-27B-Think + │ + │ Wait, the sampler loop keeps recording real samples every 2 seconds, so real samples (a small + │ number over about 10 seconds) will get mixed in. That's fine. + │ + │ But one concern: `store._buf` is a deque with maxlen 1800. If I append 1800 synthetic samples, + │ the buffer gets filled up to maxlen, and then the real samples from the sampler push out the + │ oldest synthetic samples. No problem. + │ + │ Also, uvicorn serves on port 8599. `server.serve()` runs until interrupted (SIGINT/SIGTERM) — so + │ I need to kill it after taking the screenshot. + │ + │ Note: uvicorn's `serve()` handles lifespan startup itself. The seeder task runs after 1.5 + │ seconds — by that point lifespan is up and `app.state.store` exists. But wait — in the case of + │ `uvicorn.Server.serve()`, the lifespan runs during startup, right. `app.state.store` gets set in + │ lifespan. OK. + │ + │ One pitfall: `app` here is the module-level `app = create_app()` from app.main. create_app() is + │ a new FastAPI instance per import... the module-level app has already been created at import + │ time. No problem. + │ + │ Alright: launch it in the background, wait for the port, run the chromium screenshot, and kill + │ the server. + │ + │ bash setsid nohup uv run python /tmp/opencode/shot_server.py > /tmp/opencode/shot… ok 0.0s + │ in + │ { + │ "command": "setsid nohup uv run python /tmp/opencode/shot_server.py > + │ /tmp/opencode/shot_server.log 2>&1 < /dev/null & disown; for i in $(seq 1 30); do curl -s + │ -o /dev/null http://127.0.0.1:8599/api/history && break; sleep 0.5; done; curl -s + │ http://127.0.0.1:8599/api/history | uv run python -c \"import json,sys; + │ d=json.load(sys.stdin); print('points:', len(d['ts']))\"", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 60000 + │ } + │ out points: 0 + │ + │ tokens 19 in 481 out 54804 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:29 • assistant • Qwen3.8-27B-Think + │ + │ Point: 0 — either the seeder hadn't run yet at the time of the curl (curl raced with the + │ 1.5-second seeder), or the seeder failed. Let's check the logs and curl again. + │ + │ bash sleep 3; cat /tmp/opencode/shot_server.log; curl -s http://127.0.0.1:8599/ap… ok 0.0s + │ in + │ { + │ "command": "sleep 3; cat /tmp/opencode/shot_server.log; curl -s + │ http://127.0.0.1:8599/api/history | uv run python -c \"import json,sys; + │ d=json.load(sys.stdin); print('points:', len(d['ts']), 'keys:', + │ sorted(d['series'])[:6])\"", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 30000 + │ } + │ out points: 200 keys: ['battery', 'cpu', 'cpu_temp', 'gpu', 'gpu_temp', 'io_read'] + │ + │ tokens 20 in 182 out 55303 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:37 • assistant • Qwen3.8-27B-Think + │ + │ Seeding complete: 200 points. Next, take a screenshot with headless Chromium. The page's default + │ tab is Overview (probably), and the chart is drawn from /api/history. + │ + │ Command: + │ ``` + │ chromium --headless=new --disable-gpu --no-sandbox --hide-scrollbars --window-size=1440,1000 + │ --virtual-time-budget=12000 --screenshot=/tmp/opencode/dash-bands.png http://127.0.0.1:8599/ + │ ``` + │ + │ virtual-time-budget fast-forwards the virtual clock. The fetch + setInterval(2000) polling will + │ complete within the budget. After that, check the PNG with the Read tool. + │ + │ Seeded with 200 points. Taking the headless screenshot: + │ + │ bash chromium --headless=new --disable-gpu --no-sandbox --hide-scrollbars --windo… ok 0.0s + │ in + │ { + │ "command": "chromium --headless=new --disable-gpu --no-sandbox --hide-scrollbars + │ --window-size=1440,1000 --virtual-time-budget=12000 + │ --screenshot=/tmp/opencode/dash-bands.png http://127.0.0.1:8599/ 2>&1 | tail -3; ls -la + │ /tmp/opencode/dash-bands.png", + │ "timeout": 90000 + │ } + │ out + │ [85611:85632:0830/181949.752594:ERROR:google_apis/gcm/engine/mcs_client.cc:702] Error + │ code: 401 Error message: Authentication Failed: wrong_secret + │ [85611:85632:0830/181949.752642:ERROR:google_apis/gcm/engine/mcs_client.cc:704] Failed to + │ log in to GCM, resetting connection. + │ 54891 bytes written to file /tmp/opencode/dash-bands.png + │ -rw-r--r-- 1 mac mac 54891 30. Aug 18:19 /tmp/opencode/dash-bands.png + │ + │ tokens 43 in 308 out 55506 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:52 • assistant • Qwen3.8-27B-Think + │ + │ The screenshot has been written (the GCM error is harmless noise from Chromium). Let me display + │ it and check the band rendering. + │ + │ read /tmp/opencode/dash-bands.png ok 0.1s + │ in + │ { + │ "filePath": "/tmp/opencode/dash-bands.png" + │ } + │ out Image read successfully + │ + │ tokens 197 in 61 out 55858 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:19:55 • assistant • Qwen3.8-27B-Think + │ + │ Since the model can't read images, I can't verify the screenshots visually myself. I need to + │ either confirm the chart rendering by another method or let the user know that I can't do visual + │ verification. + │ + │ Alternative verification methods: Use Chromium headless's `--dump-dom`, or execute JS to check + │ the state of the chart (e.g., the length of the dataset, whether fill is configured, whether + │ there are console errors). I can use chromium's `--headless --repl`, or better: inject a small + │ script. Options: + │ + │ 1. `chromium --headless=new --dump-dom` — only shows the DOM, and doesn't show the canvas state. + │ 2. Use `--enable-logging --v=1` to capture console messages? JS errors will show up in the + │ console. + │ 3. Write a small puppeteer-style script… puppeteer isn't installed. Playwright isn't installed + │ either (the earlier `import playwright` failed). + │ 4. Use chromium's `--headless=new` together with `--virtual-time-budget` and + │ `--run-all-compositor-stages-before-draw`, and evaluate JS via `--dump-dom` — that won't work. + │ + │ Alternative: Verify with a Node-based DOM? No, there's no guarantee node is available. + │ + │ Another approach: I can check for JS console errors by running chromium with + │ `--enable-logging=stderr --v=0` — console errors will be logged to stderr as "CONSOLE(n)". This + │ will at least tell me whether app.js threw an exception during chart initialization or updating. + │ + │ Also, a screenshot exists (54KB — larger than a blank page), and in the earlier session log, + │ screenshots of the tabs were being taken via some method… but I can't view them. + │ + │ Actually, wait — maybe I could convert the PNG and analyze it programmatically: for example, use + │ Python to check the pixel color distribution inside the chart area (the cyan/purple/green bands + │ of the envelope should be present). The page's background is dark; the chart area is in the + │ upper portion of the overview tab. I could sample the pixel colors from the PNG (PIL might not + │ be installed… let me check). Even without PIL, I can decode the PN + │ … [624 more chars] + │ + │ I can't view the PNG directly, so I'll verify the render another way — capture the browser + │ console for JS errors and inspect the chart pixels programmatically: + │ + │ bash chromium --headless=new --disable-gpu --no-sandbox --enable-logging=stderr -… ok 0.0s + │ in + │ { + │ "command": "chromium --headless=new --disable-gpu --no-sandbox --enable-logging=stderr + │ --v=0 --virtual-time-budget=12000 --dump-dom http://127.0.0.1:8599/ > + │ /tmp/opencode/dom.html 2> /tmp/opencode/console.log; grep -E \"CONSOLE|Uncaught|Error\" + │ /tmp/opencode/console.log | grep -v + │ \"GCM\\|gcm\\|bluez\\|blue\\|dbus\\|DBus\\|ozone\\|viz\\|gpu_\" | head -20; echo ---; grep + │ -c \"chart-cpu\\|chart-mem\\|chart-io\" /tmp/opencode/dom.html", + │ "timeout": 90000 + │ } + │ out + │ --- + │ 3 + │ bash uv run python -c "import PIL; print('PIL ok')" 2>&1 | tail -1; python3 -c "i… ok 0.0s + │ in + │ { + │ "command": "uv run python -c \"import PIL; print('PIL ok')\" 2>&1 | tail -1; python3 -c + │ \"import PIL; print('sys PIL ok')\" 2>&1 | tail -1", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out + │ ModuleNotFoundError: No module named 'PIL' + │ sys PIL ok + │ + │ tokens 47 in 942 out 56117 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:20:28 • assistant • Qwen3.8-27B-Think + │ + │ 1. The console log shows no JS errors (after filtering out Chromium internal noise, nothing + │ matching CONSOLE/Uncaught/Error was found) — app.js was executed, and no exceptions were thrown. + │ 2. The DOM contains all three canvases. + │ 3. System python3 has PIL. I'll use the system's python3 to analyze the pixel colors of the + │ screenshot. + │ + │ The page layout: window is 1440x1000. The overview tab contains the chart grid. From the + │ screenshot... I can't see it, but according to the template the chart is near the top (below the + │ tab bar and cards?). Let me analyze the image: count pixels matching the band colors: + │ - CPU cyan #4fc3f7 (79,195,247) + │ - GPU purple #ba68c8 (186,104,200) + │ - Memory green #81c784 (129,199,132) + │ - VRAM orange #ffb74d (255,183,77) + │ - read red #e57373 (229,115,115) + │ - write yellow #fff176 (255,241,118) + │ + │ Fill color: 0x2e alpha (18%) of these colors composited over the dark background (~#1a2029?) — + │ the result will be a muted version. I'll scan the entire image and count pixels within a + │ tolerance of each pure color (the line's border should be present in pure color), and also count + │ the "fill" family (colors that are a mix of the pure color and the background). + │ + │ More simply: for each of the 6 pure colors, count pixels within a Euclidean distance of 40. If + │ all 6 are present (each above some threshold), that means the lines were drawn in all 3 charts. + │ The fill area should also be detected: sample the pixels... let's keep it simple: count of + │ pure-color pixels (line borders, width 1–1.5px → there should be plenty), plus check for + │ fill-family pixels (distance to pure color in the range 40–120, and clearly different from the + │ background) — that's too vague. + │ + │ Alternative approach for fill verification: pick a known position. Hmm, I can't see the image. + │ + │ Actually — there's a better approach: verify the fill in the browser using JS! In headless + │ chromium, I can evaluate JS via a data: page that fetches the API and... no, I can't. + │ + │ Alternative: `--headless --repl`? Old headless ha + │ … [732 more chars] + │ + │ No JS console errors and all canvases are present. Now pixel-level verification of the bands: + │ + │ bash grep -iE "body|background" /home/mac/Code/dashboard/static/css/style.css | h… ok 0.0s + │ in + │ { + │ "command": "grep -iE \"body|background\" /home/mac/Code/dashboard/static/css/style.css | + │ head -8; grep -E \"chart-card|charts-grid\" /home/mac/Code/dashboard/static/css/style.css + │ | head -8" + │ } + │ out + │ html, body { + │ background: var(--bg); + │ background: var(--bg-panel); + │ background: transparent; + │ .tab-btn:hover { color: var(--text); background: var(--bg-card); } + │ background: var(--bg-card); + │ background: var(--bg-panel); + │ background: var(--bg-card); + │ .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + │ gap: 10px; } + │ .chart-card { + │ .chart-card h3 { flex: 0 0 auto; } + │ .chart-card canvas { flex: 1 1 auto; min-height: 0; } + │ + │ tokens 36 in 858 out 57107 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:20:56 • assistant • Qwen3.8-27B-Think + │ + │ The console logs have no JS errors (after filtering out Chrome internal noise, nothing matching + │ CONSOLE/Uncaught/Error was found). The DOM contains all three canvases. + │ + │ Wait — grep found 3 matches for the chart-cpu|chart-mem|chart-io pattern within the DOM — these + │ are probably inside app.js which is inlined in the DOM (or within the canvas elements). + │ Actually, dump-dom returns the complete DOM. The canvases should appear as and so on. The 3 matches = 3 canvas elements (or the script's src). Either way, + │ no problem. + │ + │ Next up is pixel analysis using the system python3 + PIL. First, I need to check the value of + │ --bg in style.css. Let me look for the CSS variable. + │ + │ bash grep -E "^\s*--(bg|text)" /home/mac/Code/dashboard/static/css/style.css | he… ok 0.0s + │ in + │ { + │ "command": "grep -E \"^\\s*--(bg|text)\" /home/mac/Code/dashboard/static/css/style.css | + │ head -10" + │ } + │ out + │ --bg: #0e1116; + │ --bg-panel: #161b23; + │ --bg-card: #1c2330; + │ --text: #dbe2ea; + │ + │ tokens 164 in 221 out 58001 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:21:04 • assistant • Qwen3.8-27B-Think + │ + │ The CSS variables are probably defined inside :root. Let me check the :root block. Actually, the + │ grep results showed `--bg: #0e1116;` and `--bg-panel: #161b23;`, `--bg-card: #1c2330;`, `--text: + │ #dbe2ea;`. The chart canvas is inside a `.chart-card` which has `background: var(--bg-card)` = + │ #1c2330. + │ + │ So, the chart drawing area is on top of the background #1c2330 = (28, 35, 48). + │ + │ The fill color with alpha 0x2e (46/255 ≈ 0.1804) on top of #1c2330: + │ fill = 0.1804*color + 0.8196*bg. + │ + │ CPU #4fc3f7 = (79, 195, 247): + │ r: 0.1804*79 + 0.8196*28 = 14.25 + 22.95 = 37.2 + │ g: 0.1804*195 + 0.8196*35 = 35.2 + 28.69 = 63.9 + │ b: 0.1804*247 + 0.8196*48 = 44.56 + 39.34 = 83.9 + │ → approximately (37, 64, 84) + │ + │ GPU #ba68c8 = (186, 104, 200): + │ r: 0.1804*186 + 22.95 = 33.55+22.95 = 56.5 + │ g: 0.1804*104 + 28.69 = 18.76+28.69 = 47.5 + │ b: 0.1804*200 + 39.34 = 36.08+39.34 = 75.4 + │ → (57, 47, 75) + │ + │ Memory #81c784 = (129, 199, 132): + │ r: 0.1804*129+22.95 = 23.27+22.95 = 46.2 + │ g: 0.1804*199+28.69 = 35.9+28.69 = 64.6 + │ b: 0.1804*132+39.34 = 23.8+39.34 = 63.2 + │ → (46, 65, 63) + │ + │ VRAM #ffb74d = (255, 183, 77): + │ r: 0.1804*255+22.95 = 46+22.95 = 69 + │ g: 0.1804*183+28.69 = 33+28.69 = 61.7 + │ b: 0.1804*77+39.34 = 13.9+39.34 = 53.2 + │ → (69, 62, 53) + │ + │ read #e57373 = (229, 115, 115): + │ r: 41.3+22.95 = 64.3 + │ g: 20.7+28.69 = 49.4 + │ b: 20.7+39.34 = 60 + │ → (64, 49, 60) + │ + │ write #fff176 = (255, 241, 118): + │ r: 46+22.95 = 69 + │ g: 43.5+28.69 = 72.2 + │ b: 21.3+39.34 = 60.6 + │ → (69, 72, 61) + │ + │ Canvas background: the Chart.js canvas itself is transparent. The card's background #1c2330 + │ shows through. Also, the grid lines rgba(42,51,66,.5) on top of #1c2330 → slightly brighter. + │ + │ Plan: scan the PNG with PIL. For each of the 6 target colors (fill family) and 6 line colors + │ (pure color), count the pixels within a tolerance (e.g., Euclidean distance ≤ 18 for fill, ≤ 25 + │ for line). The fill should number in the thousands of pixels per chart (the band area is large), + │ while line pixels will number in the hundreds. + │ + │ Let me write the script. + │ + │ Now I'll analyze the screenshot pixels to verify that all 6 chart lines and their fill bands + │ have been rendered: + │ + │ write /tmp/opencode/check_pixels.py ok 0.0s + │ in + │ { + │ "content": "import math\n\nfrom PIL import Image\n\nimg = + │ Image.open(\"/tmp/opencode/dash-bands.png\").convert(\"RGB\")\nw, h = img.size\npx = + │ img.load()\nbg = (28, 35, 48)\na = 0x2E / 255\n\ndef fill(c):\n return tuple(a * v + (1 + │ - a) * b for v, b in zip(c, bg))\n\nTARGETS = {\n \"cpu line\": (0x4F, 0xC3, 0xF7),\n + │ \"gpu line\": (0xBA, 0x68, 0xC8),\n \"mem line\": (0x81, 0xC7, 0x84),\n \"vram + │ line\": (0xFF, 0xB7, 0x4D),\n \"read line\": (0xE5, 0x73, 0x73),\n \"write line\": + │ (0xFF, 0xF1, 0x76),\n \"cpu fill\": fill((0x4F, 0xC3, 0xF7)),\n \"gpu fill\": + │ fill((0xBA, 0x68, 0xC8)),\n \"mem fill\": fill((0x81, 0xC7, 0x84)),\n \"vram fill\": + │ fill((0xFF, 0xB7, 0x4D)),\n \"read fill\": fill((0xE5, 0x73, 0x73)),\n \"write + │ fill\": fill((0xFF, 0xF1, 0x76)),\n}\nLINE_TOL = 30\nFILL_TOL = 14\n\ncounts = {k: 0 for k + │ in TARGETS}\nfor y in range(h):\n for x in range(w):\n p = px[x, y]\n for + │ name, t in TARGETS.items():\n d = math.sqrt((p[0] - t[0]) ** 2 + (p[1] - t[1]) + │ ** 2 + (p[2] - t[2]) ** 2)\n tol = LINE_TOL if name.endswith(\"line\") else + │ FILL_TOL\n if d <= tol:\n counts[name] += 1\n\nprint(\"size:\", + │ w, h)\nfor k, v in counts.items():\n print(f\"{k:11s} {v:7d}\")\nok = all(counts[k] > + │ 300 for k in TARGETS if k.endswith(\"fill\")) and all(counts[k] > 50 for k in TARGETS if + │ k.endswith(\"line\"))\nprint(\"BANDS_OK\" if ok else \"BANDS_MISSING\")\n", + │ "filePath": "/tmp/opencode/check_pixels.py" + │ } + │ out Wrote file successfully. + │ + │ tokens 65 in 1852 out 58385 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:21:52 • assistant • Qwen3.8-27B-Think + │ + │ The user has sent the same message for the third time. I'm in the middle of verification. I've + │ just finished writing /tmp/opencode/check_pixels.py. Let me run it now to complete the + │ verification, and after that I'll report the final result. + │ + │ Remaining steps: + │ 1. Run the pixel check. + │ 2. Terminate the shot server (port 8599). + │ 3. Check the live server: all endpoints 200, the shape of /api/history, grep the tracebacks. + │ 4. Report. + │ + │ Alright, let me run the pixel check now. + │ + │ bash python3 /tmp/opencode/check_pixels.py ok 0.0s + │ in + │ { + │ "command": "python3 /tmp/opencode/check_pixels.py", + │ "timeout": 120000 + │ } + │ out + │ size: 1440 1000 + │ cpu line 2086 + │ gpu line 68 + │ mem line 0 + │ vram line 0 + │ read line 0 + │ write line 0 + │ cpu fill 0 + │ gpu fill 352 + │ mem fill 992 + │ vram fill 0 + │ read fill 0 + │ write fill 0 + │ BANDS_MISSING + │ + │ tokens 20 in 178 out 60302 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:22:22 • assistant • Qwen3.8-27B-Think + │ + │ Interesting result. CPU line: 2086 pixels (drawn), GPU line: 68, mem/vram/read/write lines: 0. + │ Fills: gpu 352, mem 992, the rest 0. + │ + │ Hmm. So the cpu chart has a visible line, and gpu+mem have some fill pixels. But no fill for + │ cpu?? And no lines for mem/vram/read/write? + │ + │ Wait — this smells like the chart is only partially drawn, or… let me think. The cpu chart has a + │ 2086-pixel line — that's the avg line (CPU only? or CPU+GPU?). The gpu line has 68 pixels — + │ sparse (the gpu series is None when i%9 → the line has gaps, that's fine, but 68 px is few for a + │ ~700px wide line with 11% gaps… should be around 600+). + │ + │ Hmm, wait, maybe the chart is drawn, but only some of the lines are visible. Because… let me + │ think about what could go wrong in my code. + │ + │ Ah! I think I know what happened. The `build` function in `updateCharts` replaces + │ `chart.data.datasets` with new dataset objects on every poll. But `initCharts` had already + │ created datasets via `envelopeDs(name, color)` (s=undefined → `s = s || {}` → data arrays = + │ `[]`). Then the first poll rebuilds with real data. That should be fine… + │ + │ But: the poll fires at t=0 (virtual time) and every 2000ms thereafter. With + │ virtual-time-budget=12000, there should be about 6 polls. The chart gets updated each time. The + │ final state should have all datasets with data. + │ + │ So why are the lines missing? Let me reconsider the tolerance: the mem line color #81c784 = + │ (129,199,132) with tol 30. The mem line is drawn with borderWidth 1.5… pixels should be close to + │ pure color (maybe antialiasing blends slightly with the background → distance up to about 50?). + │ The cpu line with the same setup got 2086 hits. So the tolerance is fine for the lines. + │ + │ So really: the mem/vram/read/write lines are not drawn, and the cpu fill isn't drawn, and the + │ gpu line is sparse. + │ + │ Wait… cpu fill = 0 but cpu line = 2086? If the CPU band is drawn, the fill between the min and + │ max should be a large area (the band width for CPU data 25±18+noise → min/max spread probably + │ 10–30% → b + │ … [11091 more chars] + │ + │ I need to investigate this further. Let me check the details. The pixel scan is suspicious — + │ some lines are drawn, but others are missing. I need to inspect the live state of the charts. + │ Let me check what tools are available for CDP: + │ + │ bash which node npm; python3 -c "import websocket" 2>&1 | tail -1; uv run python … ok 0.0s + │ in + │ { + │ "command": "which node npm; python3 -c \"import websocket\" 2>&1 | tail -1; uv run python + │ -c \"import websocket\" 2>&1 | tail -1" + │ } + │ out + │ /usr/bin/node + │ /usr/bin/npm + │ ModuleNotFoundError: No module named 'websocket' + │ bash grep -iE "console|error|warn" /tmp/opencode/console.log | grep -vE "GCM|gcm|… ok 0.0s + │ in + │ { + │ "command": "grep -iE \"console|error|warn\" /tmp/opencode/console.log | grep -vE + │ \"GCM|gcm|bluez|dbus|DBus|ozone|viz|gpu_|sandbox|dbus\" | head -20" + │ } + │ out + │ [86047:86047:0830/182022.657547:WARNING:chrome/browser/signin/account_consistency_mode_manag + │ er.cc:74] Desktop Identity Consistency cannot be enabled as no OAuth client ID and client + │ secret have been configured. + │ [86047:86130:0830/182022.725622:WARNING:chrome/browser/apps/user_type_filter.cc:59] No user + │ type filter specified for cimiefiiaegbelhefglklhhakcgmhkai. Using default user type filter, + │ please update the app. + │ [86082:86109:0830/182027.928448:WARNING:net/extras/sqlite/sqlite_persistent_store_backend_ba + │ se.cc:181] Failed to post task from + │ FlushAndNotifyInBackground@net/extras/sqlite/sqlite_persistent_store_backend_base.cc:231 to + │ client_task_runner_. + │ [86082:86109:0830/182027.928543:WARNING:net/extras/sqlite/sqlite_persistent_store_backend_ba + │ se.cc:181] Failed to post task from + │ FlushAndNotifyInBackground@net/extras/sqlite/sqlite_persistent_store_backend_base.cc:231 to + │ client_task_runner_. + │ + │ tokens 113 in 3911 out 60501 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:24:36 • assistant • Qwen3.8-27B-Think + │ + │ No JS console errors. Node is available — I can use puppeteer-core via npm to drive the + │ installed Chromium via CDP and dump the chart's state. `npm install puppeteer-core` in a temp + │ directory (doesn't touch the project). + │ + │ Actually — wait. Before the heavy lifting, let me reconsider the pixel results. Another + │ hypothesis just occurred to me: + │ + │ What if the screenshot was taken while the page was still on a different tab? No — the default + │ tab is Overview. + │ + │ What if the chart exists but the screenshot only captured some of it? No, it's a single + │ full-page canvas area. + │ + │ Hmm, another thought: **tension 0.25 on avg, and min/max with tension 0** — no problem. + │ + │ **A fill target pointing to a dataset in a different "group"?** No. + │ + │ Actually, let me reconsider the Chart.js `fill` semantics: `fill: 2` means "fill to dataset + │ index 2". The fill is drawn from this dataset's line to the target dataset's line. For the max + │ dataset (index 1, CPU max) with fill=2 (CPU min): it fills between the CPU max line and the CPU + │ min line. Correct. + │ + │ But — the crucial point: **the fill is only drawn where both datasets have points**. CPU has no + │ None → a full fill. CPU's fill is 0 px. So the CPU fill is really not being drawn. And the CPU + │ line is being drawn. + │ + │ The MEM fill is being drawn but the MEM line is not?? That's the reverse. Something is off with + │ my reading... + │ + │ Wait. Hmm, let me double-check: did I mislabel them? "mem line" target (129,199,132) tolerance + │ 30 — 0 pixels. "vram line" (255,183,77) — 0. "read line" (229,115,115) — 0. "write line" + │ (255,241,118) — 0. But the cpu line is 2086, gpu line is 68. + │ + │ And the fills: cpu 0, gpu 352, mem 992, vram 0, read 0, write 0. + │ + │ Interesting: the gpu line is 68 but the gpu fill is 352? The GPU series has gaps (None every 9 + │ samples) — the line has gaps too, but the fill is... 352 px is also small for a 440px-wide chart + │ (if the band height averages 30px with 89% coverage → 12k px). 352 is small. And the mem fill is + │ 992 — mem has no None (mem_pct is always prese + │ … [1937 more chars] + │ + │ Node is available — I'll drive the installed Chromium via CDP with puppeteer-core to dump the + │ actual chart state: + │ + │ bash mkdir -p /tmp/opencode/cdp && cd /tmp/opencode/cdp && npm init -y >/dev/null… ok 0.0s + │ in + │ { + │ "command": "mkdir -p /tmp/opencode/cdp && cd /tmp/opencode/cdp && npm init -y >/dev/null + │ 2>&1 && npm install puppeteer-core --no-audit --no-fund 2>&1 | tail -2", + │ "timeout": 120000 + │ } + │ out + │ + │ added 25 packages in 3s + │ + │ tokens 353 in 1321 out 64526 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:25:23 • assistant • Qwen3.8-27B-Think + │ + │ Puppeteer-core is already installed. Next, write a probe script that does the following: + │ 1. Launch /usr/bin/chromium in headless mode with --no-sandbox. + │ 2. Capture console messages. + │ 3. Open http://127.0.0.1:8599/, wait about 4 seconds. + │ 4. Evaluate the following JS: + │ ```js + │ () => { + │ const out = []; + │ for (const [id, chart] of Object.entries(Chart.instances)) { + │ out.push({ + │ id, + │ w: chart.width, h: chart.height, + │ area: chart.chartArea, + │ datasets: chart.data.datasets.map(d => ({ + │ label: d.label, + │ n: d.data.length, + │ nonNull: d.data.filter(v => v !== null && v !== undefined).length, + │ fill: d.fill, + │ order: d.order, + │ hidden: d.hidden, + │ })), + │ }); + │ } + │ return out; + │ } + │ ``` + │ Note: chart.width/height exist on Chart (the chart's width/height properties). chartArea has + │ left/top/right/bottom. + │ 5. Take a new screenshot with puppeteer (full page) for the record. + │ + │ After that, analyze. + │ + │ write /tmp/opencode/cdp/probe.js ok 0.0s + │ in + │ { + │ "content": "const puppeteer = require(\"puppeteer-core\");\n\n(async () => {\n const + │ browser = await puppeteer.launch({\n executablePath: \"/usr/bin/chromium\",\n + │ headless: \"new\",\n args: [\"--no-sandbox\", \"--disable-gpu\", + │ \"--window-size=1440,1000\"],\n });\n const page = await browser.newPage();\n await + │ page.setViewport({ width: 1440, height: 1000 });\n const logs = [];\n + │ page.on(\"console\", (m) => logs.push(m.type() + \": \" + m.text()));\n + │ page.on(\"pageerror\", (e) => logs.push(\"PAGEERROR: \" + e.message));\n await + │ page.goto(\"http://127.0.0.1:8599/\", { waitUntil: \"networkidle0\", timeout: 30000 });\n + │ await new Promise((r) => setTimeout(r, 4000));\n const state = await page.evaluate(() => + │ {\n const out = [];\n for (const chart of Object.values(Chart.instances)) {\n + │ out.push({\n canvas: chart.canvas.id,\n w: chart.width,\n h: + │ chart.height,\n area: chart.chartArea,\n datasets: + │ chart.data.datasets.map((d) => ({\n label: d.label,\n n: d.data ? + │ d.data.length : -1,\n nonNull: d.data ? d.data.filter((v) => v !== null && v !== + │ undefined).length : -1,\n fill: d.fill,\n order: d.order,\n + │ hidden: d.hidden,\n })),\n });\n }\n return out;\n });\n + │ console.log(JSON.stringify(state, null, 1));\n console.log(\"CONSOLE_LOGS_START\");\n + │ console.log(logs.join(\"\\n\"));\n await page.screenshot({ path: + │ \"/tmp/opencode/dash-bands2.png\" });\n await browser.close();\n})();\n", + │ "filePath": "/tmp/opencode/cdp/probe.js" + │ } + │ out Wrote file successfully. + │ + │ tokens 25 in 780 out 66199 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:25:45 • assistant • Qwen3.8-27B-Think + │ + │ The user has sent the same message for the 4th time. I'm in the middle of verification. The shot + │ server on port 8599 might still be running, or it might have stopped. Let me check, run the + │ probe, and get the chart's state. + │ + │ Let's proceed efficiently: verify that the shot server is running, run the probe, and analyze. + │ + │ bash curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8599/api/history |… ok 0.0s + │ in + │ { + │ "command": "curl -s -o /dev/null -w \"%{http_code}\\n\" http://127.0.0.1:8599/api/history + │ || echo DOWN" + │ } + │ out 200 + │ + │ tokens 21 in 145 out 67003 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:25:49 • assistant • Qwen3.8-27B-Think + │ + │ I need to look into this further. Let me check the details. + │ + │ The shot server is still running with the seeded 200 points (the buffer may have been refilled + │ with actual samples, but the maxlen 1800 keeps the most recent ones — the seeded 1800 got pushed + │ out by the real samples… no wait: the seeded 1800 filled the maxlen 1800 deque; each real sample + │ that arrives after that evicts the oldest seeded one. Several minutes have passed → maybe 30–60 + │ real samples have replaced the oldest seeded ones. Still about 200 window points. No problem.) + │ + │ Running the probe now. + │ + │ bash cd /tmp/opencode/cdp && node probe.js 2>&1 | tail -60 ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node probe.js 2>&1 | tail -60", + │ "timeout": 90000 + │ } + │ out + │ { + │ "canvas": "chart-io", + │ "w": 436, + │ "h": 218, + │ "area": { + │ "left": 32.01171875, + │ "top": 10.2, + │ "right": 436, + │ "bottom": 207.79999999999998, + │ "height": 197.6, + │ "width": 403.98828125 + │ }, + │ "datasets": [ + │ { + │ "label": "read", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 0 + │ }, + │ { + │ "label": "read max", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 2 + │ }, + │ { + │ "label": "read min", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 1 + │ }, + │ { + │ "label": "write", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 0 + │ }, + │ { + │ "label": "write max", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 2 + │ }, + │ { + │ "label": "write min", + │ "n": 0, + │ "nonNull": 0, + │ "fill": false, + │ "order": 1 + │ } + │ ] + │ } + │ ] + │ CONSOLE_LOGS_START + │ error: Failed to load resource: the server responded with a status of 404 (Not Found) + │ + │ tokens 18 in 207 out 67169 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:26:04 • assistant • Qwen3.8-27B-Think + │ + │ The output was truncated by `tail -60` — I can only see the io chart. Key findings so far: + │ + │ 1. **chart-io datasets: n: 0** — the data array is empty! So the io chart received no data. + │ 2. **console: `error: Failed to load resource: 404`** — something 404'd! Maybe the /api/history + │ fetch?? Or a static resource? If /api/history returned 404… no wait, curl just returned 200 a + │ moment ago. But hold on — `updateCharts` only runs when the fetch succeeds. If the fetch 404'd… + │ hmm. + │ + │ But wait, the cpu chart had a line drawn (2086 px in the earlier pixel scan). If /api/history + │ 404'd, no chart should have any data. Unless the 404 is a different resource (favicon? + │ screenshot request?). Let me look at the full probe output — I need the cpu and mem chart states + │ too. And identify which resource 404'd. + │ + │ Hmm wait, actually — there's a subtle point: the probe waits 4 seconds after networkidle0. The + │ initial `pollHistory()` fires on load. If that fetch succeeded, all charts should have 200 + │ points. The io chart has 0 points → the io build ran with an empty series?? In my `build`: + │ `series[key]` is undefined → `envelopeDs(name, color, undefined)` → `s = s || {}` → data is + │ `[]`. So the io keys `io_read`/`io_write` didn't exist in the series from /api/history! + │ + │ Why? My shot_server seed has io_read and io_write… and the TestClient test was… let me check: + │ the endpoint test printed "keys: 3" — what?! 1800 samples were seeded yet only 3 series keys! + │ The TestClient samples had cpu, mem_total, gpu (the others were… wait, my test_endpoint seed + │ was: cpu, mem_total, gpu, flag, name → numeric keys = cpu, mem_total, gpu = 3 keys. OK, that's + │ consistent — "keys: 3" is correct for that minimal seed. My mistake, I misread it as suspicious. + │ + │ So in the shot server, io_read/io_write were included in the seed… but wait — real sampler + │ samples (from the sampler loop) are also being recorded, and those have different keys (io_read, + │ io_write from the disk rate, mem keys, etc.). The windowed union of keys should inclu + │ … [7650 more chars] + │ + │ I need to investigate this further. Let me check the details. The IO chart data is empty, and + │ the initial state remains as-is — `updateCharts` never got the data even once. Let me try to get + │ the full logs and what the page's fetch is actually seeing: + │ + │ write /tmp/opencode/cdp/probe2.js ok 0.0s + │ in + │ { + │ "content": "const puppeteer = require(\"puppeteer-core\");\n\n(async () => {\n const + │ browser = await puppeteer.launch({\n executablePath: \"/usr/bin/chromium\",\n + │ headless: \"new\",\n args: [\"--no-sandbox\", \"--disable-gpu\", + │ \"--window-size=1440,1000\"],\n });\n const page = await browser.newPage();\n await + │ page.setViewport({ width: 1440, height: 1000 });\n const logs = [];\n + │ page.on(\"console\", (m) => logs.push(\"CONSOLE \" + m.type() + \": \" + m.text()));\n + │ page.on(\"pageerror\", (e) => logs.push(\"PAGEERROR: \" + e.message + \"\\n\" + (e.stack + │ || \"\").split(\"\\n\").slice(0, 4).join(\"\\n\")));\n page.on(\"response\", (r) => {\n + │ if (r.url().includes(\"8599\")) logs.push(\"RESP \" + r.status() + \" \" + r.url());\n + │ });\n page.on(\"requestfailed\", (r) => logs.push(\"REQFAIL \" + r.url() + \" \" + + │ (r.failure() || {}).errorText));\n await page.goto(\"http://127.0.0.1:8599/\", { + │ waitUntil: \"networkidle0\", timeout: 30000 });\n await new Promise((r) => setTimeout(r, + │ 6000));\n const fetched = await page.evaluate(async () => {\n try {\n const r = + │ await fetch(\"/api/history\");\n const t = await r.text();\n const d = + │ JSON.parse(t);\n return { status: r.status, len: t.length, pts: d.ts ? d.ts.length : + │ null, keys: d.series ? Object.keys(d.series).length : null, cpu: d.series ? d.series.cpu : + │ null };\n } catch (e) {\n return { err: String(e) };\n }\n });\n + │ console.log(\"PAGE_FETCH:\", JSON.stringify(fetched).slice(0, 400));\n const state = + │ await page.evaluate(() => {\n const out = [];\n for (const chart of + │ Object.values(Chart.instances)) {\n out.push({\n canvas: chart.canvas.id,\n + │ labels: chart.data.labels.length,\n datasets: chart.data.datasets.map((d) => ({\n + │ label: d.label,\n n: d.data ? d.data.length : -1,\n fill: d.fill,\n + │ })),\n });\n }\n return out;\n });\n console.log(\"CHARTS:\", + │ JSON.stringify(state));\n console.l + │ … [138 more chars] + │ out Wrote file successfully. + │ + │ tokens 465 in 3353 out 67393 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:27:57 • assistant • Qwen3.8-27B-Think + │ + │ I need to investigate this further. Let me check the details. + │ + │ Let me run probe2 to get more diagnostic information. I'll try running it. + │ + │ bash cd /tmp/opencode/cdp && node probe2.js 2>&1 | tail -40 ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node probe2.js 2>&1 | tail -40", + │ "timeout": 90000 + │ } + │ out + │ PAGE_FETCH: + │ {"status":200,"len":79112,"pts":200,"keys":19,"cpu":{"avg":[28.1,27,23.8,23.6,21.4,19.1,19.4 + │ ,18.3,14.6,14.7,13.1,11,20,10.1,8.3,7.4,5.7,6.3,6.2,6.7,6.7,6.9,8,6.8,11.3,9.9,13.6,11.7,14. + │ 9,17.1,17.6,20.5,21.8,23.6,24.9,26.1,29.4,31.7,33.5,34,43.5,37.5,39.8,38.8,42.8,41.4,41.8,43 + │ .8,43.6,42.5,41.2,43.5,42.8,40.2,40.5,40.2,37.2,36.3,38,35.8,34.6,31.2,31.7,27.5,25.7,24.5,2 + │ 3.2,34.4,21.1,18.9,14.4,14.1,13. + │ CHARTS: + │ [{"canvas":"chart-cpu","labels":200,"datasets":[{"label":"CPU","n":200,"fill":false},{"label + │ ":"CPU max","n":200,"fill":2},{"label":"CPU + │ min","n":200,"fill":false},{"label":"GPU","n":200,"fill":false},{"label":"GPU + │ max","n":200,"fill":5},{"label":"GPU + │ min","n":200,"fill":false}]},{"canvas":"chart-mem","labels":0,"datasets":[{"label":"Memory", + │ "n":0,"fill":false},{"label":"Memory max","n":0,"fill":false},{"label":"Memory + │ min","n":0,"fill":false},{"label":"VRAM","n":0,"fill":false},{"label":"VRAM + │ max","n":0,"fill":false},{"label":"VRAM + │ min","n":0,"fill":false}]},{"canvas":"chart-io","labels":0,"datasets":[{"label":"read","n":0 + │ ,"fill":false},{"label":"read max","n":0,"fill":false},{"label":"read + │ min","n":0,"fill":false},{"label":"write","n":0,"fill":false},{"label":"write + │ max","n":0,"fill":false},{"label":"write min","n":0,"fill":false}]}] + │ LOGS_START + │ RESP 200 http://127.0.0.1:8599/ + │ RESP 200 http://127.0.0.1:8599/static/css/style.css + │ RESP 200 http://127.0.0.1:8599/static/vendor/htmx.min.js + │ RESP 200 http://127.0.0.1:8599/static/js/app.js + │ RESP 200 http://127.0.0.1:8599/static/vendor/chart.umd.min.js + │ RESP 200 http://127.0.0.1:8599/api/history + │ RESP 200 http://127.0.0.1:8599/api/disks + │ RESP 200 http://127.0.0.1:8599/api/overview + │ RESP 404 http://127.0.0.1:8599/favicon.ico + │ CONSOLE error: Failed to load resource: the server responded with a status of 404 (Not + │ Found) + │ RESP 200 http://127.0.0.1:8599/api/plugins + │ RESP 200 http://127.0.0.1:8599/api/processes?q=&sort=cpu&order=desc + │ RESP 200 http://127.0.0.1:8599/api/services?q=&sort=name&order=asc + │ RESP 200 http://127.0.0.1:8 + │ … [951 more chars] + │ + │ tokens 21 in 96 out 71210 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:28:11 • assistant • Qwen3.8-27B-Think + │ + │ Now it's clear: + │ + │ - `/api/history` returns 200 with 200 points and 19 keys. The page's fetch works fine. + │ - **chart-cpu**: labels=200, all 6 datasets have n=200, fill=2 and fill=5 are set. → CPU chart + │ is fully working! + │ - **chart-mem**: labels=0, all n=0, fill=false → still in initial state, `build` never ran even + │ once! + │ - **chart-io**: same as above. + │ + │ In other words, `build(charts.cpu, ...)` ran, but `build(charts.mem, ...)` and `build(charts.io, + │ ...)` never ran — and there's no PAGEERROR in the logs! If `build(charts.mem, ...)` threw, the + │ catch in `pollHistory`'s promise chain would swallow it… no wait, that's not right: + │ `updateCharts` is called inside `.then(updateCharts)`. If updateCharts throws midway (after the + │ cpu build succeeds), the error becomes a rejected promise → `.catch(() => {})` silently swallows + │ it! And the pageerror event doesn't fire for unhandled rejections inside a .then chain? + │ Actually, unhandled rejections can trigger the 'pageerror' event in some versions of puppeteer… + │ but here it was silently swallowed by my own `.catch(() => {})`. + │ + │ So `build(charts.mem, ...)` is throwing an exception. Why? + │ + │ Looking at build: + │ + │ ```js + │ const build = (chart, pairs) => { + │ chart.data.labels = labels; + │ chart.data.datasets = []; + │ pairs.forEach(([name, color, key], i) => { + │ const ds = envelopeDs(name, color, series[key]); + │ ds[1].fill = i * 3 + 2; + │ chart.data.datasets.push(...ds); + │ }); + │ chart.update("none"); + │ }; + │ ``` + │ + │ Hmm, what could throw with mem but not with cpu? The data: `series["mem_pct"]`, + │ `series["vram_pct"]`… 19 keys — mem_pct should be there (the mem sample has mem_pct). Hmm, wait, + │ is that really true? The keys come from the union across all windows. mem_pct is collected by + │ mem.sample() — it should exist in every sample. + │ + │ Wait a sec — what if `series["mem_pct"]` existed, but one of the values was a string?? No, + │ that's not it… + │ + │ Wait, actually — the problem might not be that an exception is being thrown, but rather: + │ … [3768 more chars] + │ + │ The fetch is working (200 points, 19 keys) and the CPU chart has data loaded, but + │ `update("none")` throws an exception there — so the mem/io builds get skipped and are silently + │ swallowed by `pollHistory`'s `.catch(() => {})`. Let me try to capture the actual exception: + │ + │ write /tmp/opencode/cdp/probe3.js ok 0.0s + │ in + │ { + │ "content": "const puppeteer = require(\"puppeteer-core\");\n\n(async () => {\n const + │ browser = await puppeteer.launch({\n executablePath: \"/usr/bin/chromium\",\n + │ headless: \"new\",\n args: [\"--no-sandbox\", \"--disable-gpu\", + │ \"--window-size=1440,1000\"],\n });\n const page = await browser.newPage();\n await + │ page.setViewport({ width: 1440, height: 1000 });\n page.on(\"pageerror\", (e) => + │ console.log(\"PAGEERROR:\", e.message, (e.stack || \"\").split(\"\\n\").slice(0, + │ 5).join(\" | \")));\n await page.goto(\"http://127.0.0.1:8599/\", { waitUntil: + │ \"networkidle0\", timeout: 30000 });\n const patched = await page.evaluate(() => {\n + │ window.__chartErrs = [];\n window.addEventListener(\"unhandledrejection\", (e) => {\n + │ window.__chartErrs.push(\"REJECTION: \" + ((e.reason && (e.reason.stack || + │ e.reason.message)) || String(e.reason)));\n });\n const orig = + │ Chart.prototype.update;\n Chart.prototype.update = function (...args) {\n try {\n + │ return orig.apply(this, args);\n } catch (e) {\n + │ window.__chartErrs.push(\"UPDATE \" + this.canvas.id + \": \" + e.message + \" :: \" + + │ (e.stack || \"\").split(\"\\n\").slice(0, 6).join(\" | \"));\n throw e;\n }\n + │ };\n return true;\n });\n console.log(\"patched:\", patched);\n await new + │ Promise((r) => setTimeout(r, 5000));\n const errs = await page.evaluate(() => + │ window.__chartErrs);\n console.log(\"ERRORS_START\");\n (errs || []).forEach((e) => + │ console.log(e));\n console.log(\"ERRORS_END\");\n await browser.close();\n})();\n", + │ "filePath": "/tmp/opencode/cdp/probe3.js" + │ } + │ out Wrote file successfully. + │ + │ tokens 1554 in 2116 out 71327 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:29:24 • assistant • Qwen3.8-27B-Think + │ + │ The user sent the same message again (5th time). I'm in the middle of debugging. Let me run + │ probe3 to get the exact error from chart.update(). + │ + │ bash cd /tmp/opencode/cdp && node probe3.js 2>&1 | tail -20 ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node probe3.js 2>&1 | tail -20", + │ "timeout": 90000 + │ } + │ out + │ patched: true + │ ERRORS_START + │ UPDATE chart-cpu: Cannot read properties of undefined (reading 'band') :: TypeError: Cannot + │ read properties of undefined (reading 'band') | at Proxy.filter + │ (http://127.0.0.1:8599/static/js/app.js:67:109) | at + │ http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171742 | at Array.filter + │ () | at xa.buildLabels + │ (http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171729) | at xa.update + │ (http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171401) + │ UPDATE chart-cpu: Cannot read properties of undefined (reading 'band') :: TypeError: Cannot + │ read properties of undefined (reading 'band') | at Proxy.filter + │ (http://127.0.0.1:8599/static/js/app.js:67:109) | at + │ http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171742 | at Array.filter + │ () | at xa.buildLabels + │ (http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171729) | at xa.update + │ (http://127.0.0.1:8599/static/vendor/chart.umd.min.js:13:171401) + │ ERRORS_END + │ + │ tokens 21 in 99 out 74996 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:29:37 • assistant • Qwen3.8-27B-Think + │ + │ Found it: + │ + │ ``` + │ TypeError: Cannot read properties of undefined (reading 'band') + │ at Proxy.filter (app.js:67:109) → my own legend filter: (item) => !item.dataset.band + │ at xa.buildLabels (legend plugin) + │ ``` + │ + │ The legend's `labels.filter` callback receives an `item`, but `item.dataset` is undefined in + │ this context. In Chart.js 4, the legend `labels.filter` callback signature is: `filter(item, + │ data)`, where `item` is a legend item with properties like `text`, `fillStyle`, `strokeStyle`, + │ `hidden`, `index`, etc... and crucially, `item.dataset` — hmm, I thought legend items had a + │ `dataset` reference. Apparently in Chart.js 4.4.1, the legend item passed to the filter doesn't + │ have a `.dataset` property (it does have `.index` though). + │ + │ Looking at the Chart.js source (v4), `buildLabels` in `plugin.legend` constructs items like: + │ ```js + │ items = chart.data.datasets.map((dataset, i) => ({ + │ text: ..., + │ datasetIndex: i, // hmm, or is it index? + │ hidden: !isDatasetVisible(chart, i), + │ fillStyle: ..., + │ strokeStyle: ..., + │ lineCap: ..., + │ lineDash: ..., + │ lineDashOffset: ..., + │ lineWeight: ..., + │ pointStyle: ..., + │ fontColor: ..., + │ hidden: ..., + │ dataset: dataset, // ??? + │ index: i, + │ })); + │ ``` + │ + │ Hmm — from the error, `item.dataset` is undefined. So in 4.4.1 the legend item doesn't include + │ `dataset`. Let me check the Chart.js 4.4.1 source... In `src/plugins/plugin.legend.js`: + │ + │ ```js + │ function _filterItems(chart, legendItems) { + │ ... + │ } + │ ... + │ const items = legendItems... + │ ``` + │ + │ The actual v4 legend `generateLabels`/`buildLabels`: + │ + │ ```js + │ buildLabels(chart) { + │ const data = chart.data; + │ const labels = []; + │ for (let i = 0; i < data.datasets.length; i++) { + │ ... + │ labels.push({ + │ text, + │ datasetIndex: i, + │ hidden: !chart.isDatasetVisible(i), + │ ... + │ }); + │ } + │ } + │ ``` + │ + │ Hmm, wait, but I actually have a memory of Chart.js legend items having a `dataset` property... + │ In Chart.js 3/4, the default `labels.generateLabels` returns items that include `dataset`? Let + │ me check the actual 4 + │ … [4933 more chars] + │ + │ I need to look into this further. Let me check the details. Found it — the legend's + │ `labels.filter` is receiving items that don't have a `.dataset` property (in Chart.js 4, legend + │ items expose `datasetIndex`). Let me check the exact item structure and filter signature in the + │ vendored build: + │ + │ bash grep -o "datasetIndex" /home/mac/Code/dashboard/static/vendor/chart.umd.min.… ok 0.0s + │ in + │ { + │ "command": "grep -o \"datasetIndex\" + │ /home/mac/Code/dashboard/static/vendor/chart.umd.min.js | head -2; grep -oE + │ \"\\.filter\\(([a-zA-Z$_]+)\\s*=>[^)]*\\)\" + │ /home/mac/Code/dashboard/static/vendor/chart.umd.min.js | head -5; grep -oE + │ \"filter\\([a-zA-Z$_]+,[a-zA-Z$_]+\\)\" + │ /home/mac/Code/dashboard/static/vendor/chart.umd.min.js | head -5" + │ } + │ out + │ datasetIndex + │ datasetIndex + │ + │ tokens 402 in 2086 out 75117 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:30:48 • assistant • Qwen3.8-27B-Think + │ + │ "datasetIndex" exists in the bundled build (legend item property ✓). The grep for the filter + │ signature didn't match with my pattern. Let me search for the legend's filter invocation a + │ different way — search for the "buildLabels" context, or the `.filter(` with a two-argument + │ arrow function. In the minified code, it might look like `.filter((t,e)=>` near the legend + │ options processing. Let me grep for `filter((` and look at a few candidates. + │ + │ bash grep -oE "filter\(\( ?[a-zA-Z$_]+, ?[a-zA-Z$_]+ ?\) ?=> ?[^;]{0,60}" /home/m… ok 0.0s + │ in + │ { + │ "command": "grep -oE \"filter\\(\\( ?[a-zA-Z$_]+, ?[a-zA-Z$_]+ ?\\) ?=> ?[^;]{0,60}\" + │ /home/mac/Code/dashboard/static/vendor/chart.umd.min.js | head -8" + │ } + │ out (no output) + │ + │ tokens 20 in 183 out 77605 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:30:55 • assistant • Qwen3.8-27B-Think + │ + │ I need to investigate this further. Let me check the details. + │ + │ There's no minified match with that pattern. To understand the filter's signature, let me look + │ at the actual Chart.js 4.4.1 source on disk — check node_modules? Not installed. Let me check + │ the source of the legend plugin in the minified bundle: search for `buildLabels` in the minified + │ file. + │ + │ bash python3 - <<'EOF' import re src = open('/home/mac/Code/dashboard/static/vend… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nimport re\nsrc = + │ open('/home/mac/Code/dashboard/static/vendor/chart.umd.min.js').read()\ni = + │ src.find('buildLabels')\nwhile i != -1:\n seg = src[max(0, i-200): i+700]\n if + │ 'filter' in seg:\n print(seg.replace('\\n', ' ')[:900])\n print('=' * 80)\n + │ i = src.find('buildLabels', i + 1)\n if i > 3_000_000: break\nEOF" + │ } + │ out + │ ght=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void + │ 0,this.fullSize=void + │ 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.b + │ uildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.l + │ eft=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._mar + │ gins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let + │ e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart. + │ data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.re + │ verse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return + │ void(this.width=this.height=0);const + │ i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ba(i + │ ,n);let l,h;e.font=s.string,this.isH + │ ================================================================================ + │ fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins. + │ left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bott + │ om=this.height)}buildLabels(){const t=this.options.labels||{};let + │ e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart. + │ data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.re + │ verse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return + │ void(this.width=this.height=0);const + │ i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ba(i + │ ,n);let + │ l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=thi + │ s.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),th + │ is.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e, + │ ================================================================================ + │ + │ tokens 17 in 245 out 77809 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:31:03 • assistant • Qwen3.8-27B-Think + │ + │ Confirmed from the bundled source: + │ + │ ```js + │ t.filter && (e = e.filter((e => t.filter(e, this.chart.data)))) + │ ``` + │ + │ So `labels.filter(item, chartData)` — the second argument is `chart.data`. And the legend items + │ have a `datasetIndex` property (it exists in the bundle). + │ + │ Therefore, the fix for the LEGEND filter in app.js: + │ + │ ```js + │ const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c", filter: (item, data) + │ => !data.datasets[item.datasetIndex]?.band } }; + │ ``` + │ + │ Let me quickly re-verify that the legend item contains `datasetIndex` — grep found two + │ occurrences of `datasetIndex` in the bundle. Let me confirm they're inside the label-generation + │ code (default generateLabels). Search for the context around those occurrences. + │ + │ Confirmed: `labels.filter(item, chartData)` — the 2nd arg is `chart.data`, and legend items use + │ `datasetIndex`. Verifying the item shape: + │ + │ bash python3 - <<'EOF' src = open('/home/mac/Code/dashboard/static/vendor/chart.u… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nsrc = + │ open('/home/mac/Code/dashboard/static/vendor/chart.umd.min.js').read()\ni = + │ src.find('datasetIndex')\nwhile i != -1:\n print(repr(src[max(0, i-260): i+80]))\n + │ print('=' * 70)\n i = src.find('datasetIndex', i + 1)\nEOF" + │ } + │ out + │ '1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at.filter(e,this.chart.data)))` — `filter(item, chartData)`. + │ + │ Therefore the fix is: + │ + │ ```js + │ const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c", filter: (item, data) + │ => !data.datasets[item.datasetIndex]?.band } }; + │ ``` + │ + │ I'll update app.js. After that, re-run the probes to verify the following: no errors, all three + │ charts with 200 datasets, fill is set, and the legend filter is working. Then take a new + │ screenshot and re-run the pixel check (the pixel check was previously matching the UI colors — + │ the cpu line count of 2086 was probably the text on the cyan card; I'll improve the pixel check + │ to only scan the chart's canvas region... but without being able to see the image, I can't know + │ the canvas coordinates precisely. Better approach: use puppeteer to clip the screenshot to each + │ canvas element (element.screenshot()) — that way I get an image of just the chart. Then run the + │ pixel check against those chart-only images (the background is a solid card color, so the + │ tolerance-based method becomes more reliable). + │ + │ Plan: + │ 1. Fix the LEGEND filter in app.js. + │ 2. Re-run probe2 (confirm no errors, all charts populated). + │ 3. Update the probe to also take element screenshots of the three canvases. + │ 4. Run the pixel check on the canvas-only screenshots. + │ 5. Then final checks on the live server: all endpoints 200, shape of /api/history, tracebacks in + │ the log. + │ 6. Terminate the shot server (8599) and clean up temporary files. + │ + │ Editing app.js: + │ + │ Confirmed in the vendored build: legend items have `datasetIndex`, and `filter(item, chartData)` + │ receives `chart.data`. Fixing the legend filter: + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " const LEGEND = { display: true, labels: { boxWidth: 10, color: + │ \"#7d8a9c\", filter: (item) => !item.dataset.band } };", + │ "newString": " const LEGEND = { display: true, labels: { boxWidth: 10, color: + │ \"#7d8a9c\", filter: (item, data) => !data.datasets[item.datasetIndex]?.band } };" + │ } + │ out Edit applied successfully. + │ + │ tokens 4421 in 643 out 78930 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 8ded0501 + + + │ 2026-08-30 18:31:50 • assistant • Qwen3.8-27B-Think + │ + │ Alright, let's write a final probe that does the following: + │ 1. Load the page, patch Chart.prototype.update to catch errors (as before). + │ 2. Wait 5 seconds. + │ 3. Dump chart state (label count, dataset counts, fill). + │ 4. Report any errors. + │ 5. Take an element screenshot of each canvas (chart-only image) to /tmp/opencode/. + │ + │ After that, run a pixel check on the canvas screenshots. + │ + │ write /tmp/opencode/cdp/probe4.js ok 0.0s + │ in + │ { + │ "content": "const puppeteer = require(\"puppeteer-core\");\n\n(async () => {\n const + │ browser = await puppeteer.launch({\n executablePath: \"/usr/bin/chromium\",\n + │ headless: \"new\",\n args: [\"--no-sandbox\", \"--disable-gpu\", + │ \"--window-size=1440,1000\"],\n });\n const page = await browser.newPage();\n await + │ page.setViewport({ width: 1440, height: 1000 });\n page.on(\"pageerror\", (e) => + │ console.log(\"PAGEERROR:\", e.message));\n await page.goto(\"http://127.0.0.1:8599/\", { + │ waitUntil: \"networkidle0\", timeout: 30000 });\n await page.evaluate(() => {\n + │ window.__errs = [];\n window.addEventListener(\"unhandledrejection\", (e) => {\n + │ window.__errs.push(\"REJECTION: \" + ((e.reason && (e.reason.stack || e.reason.message)) + │ || String(e.reason)));\n });\n const orig = Chart.prototype.update;\n + │ Chart.prototype.update = function (...args) {\n try {\n return + │ orig.apply(this, args);\n } catch (e) {\n window.__errs.push(\"UPDATE \" + + │ this.canvas.id + \": \" + e.message);\n throw e;\n }\n };\n });\n await + │ new Promise((r) => setTimeout(r, 5000));\n const errs = await page.evaluate(() => + │ window.__errs);\n console.log(\"ERRORS:\", JSON.stringify(errs));\n const state = await + │ page.evaluate(() =>\n Object.values(Chart.instances).map((c) => ({\n canvas: + │ c.canvas.id,\n labels: c.data.labels.length,\n ds: c.data.datasets.map((d) => + │ [d.label, d.data.length, d.fill]),\n legend: c.legend.legendItems.map((i) => + │ i.text),\n }))\n );\n console.log(\"CHARTS:\", JSON.stringify(state));\n for (const + │ id of [\"chart-cpu\", \"chart-mem\", \"chart-io\"]) {\n const el = await page.$(\"#\" + + │ id);\n await el.screenshot({ path: `/tmp/opencode/canvas-${id}.png` });\n }\n await + │ page.screenshot({ path: \"/tmp/opencode/dash-final.png\" });\n await + │ browser.close();\n})();\n", + │ "filePath": "/tmp/opencode/cdp/probe4.js" + │ } + │ out Wrote file successfully. + │ + │ tokens 20 in 682 out 83993 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:32:10 • assistant • Qwen3.8-27B-Think + │ + │ Since app.js is served as a static file (no restart needed for JS changes), I can re-run probe4 + │ directly against the shot server on port 8599. + │ + │ bash cd /tmp/opencode/cdp && node probe4.js 2>&1 | tail -10 ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node probe4.js 2>&1 | tail -10", + │ "timeout": 90000 + │ } + │ out + │ ERRORS: [] + │ CHARTS: [{"canvas":"chart-cpu","labels":200,"ds":[["CPU",200,false],["CPU max",200,2],["CPU + │ min",200,false],["GPU",200,false],["GPU max",200,5],["GPU + │ min",200,false]],"legend":["CPU","GPU"]},{"canvas":"chart-mem","labels":200,"ds":[["Memory", + │ 200,false],["Memory max",200,2],["Memory min",200,false],["VRAM",200,false],["VRAM + │ max",200,5],["VRAM + │ min",200,false]],"legend":["Memory","VRAM"]},{"canvas":"chart-io","labels":200,"ds":[["read" + │ ,200,false],["read max",200,2],["read min",200,false],["write",200,false],["write + │ max",200,5],["write min",200,false]],"legend":["read","write"]}] + │ + │ tokens 19 in 101 out 84696 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:32:24 • assistant • Qwen3.8-27B-Think + │ + │ Now everything is working: + │ - No JS errors. + │ - All 3 charts: 200 labels, all 6 datasets × 200 points, fill=2/fill=5 are correctly set. + │ - Legend displays only the main series: ["CPU","GPU"], ["Memory","VRAM"], ["read","write"] — + │ band datasets are filtered out. + │ + │ Next, I'll run a pixel check on the per-canvas screenshots to confirm the bands (fill + line) + │ are actually being rendered. The canvas-only screenshots have a uniform card background (#1c2330 + │ = (28,35,48)), so the same target color logic will work. I'll run check_pixels.py on each canvas + │ file. + │ + │ Let me adapt: run the same script with different input paths. Quick inline Python that reuses + │ the logic for all 3 canvas files. + │ + │ All charts fully populated, no errors, legends clean. Now pixel-verify the rendered bands on the + │ per-canvas screenshots: + │ + │ bash python3 - <<'EOF' import math from PIL import Image bg = (28, 35, 48) a = 0x… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nimport math\nfrom PIL import Image\n\nbg = (28, 35, 48)\na + │ = 0x2E / 255\ndef fill(c):\n return tuple(a * v + (1 - a) * b for v, b in zip(c, + │ bg))\n\nCOLORS = {\n \"cpu\": (0x4F, 0xC3, 0xF7), \"gpu\": (0xBA, 0x68, 0xC8),\n + │ \"mem\": (0x81, 0xC7, 0x84), \"vram\": (0xFF, 0xB7, 0x4D),\n \"read\": (0xE5, 0x73, + │ 0x73), \"write\": (0xFF, 0xF1, 0x76),\n}\nPER_FILE = {\n \"canvas-chart-cpu.png\": + │ [\"cpu\", \"gpu\"],\n \"canvas-chart-mem.png\": [\"mem\", \"vram\"],\n + │ \"canvas-chart-io.png\": [\"read\", \"write\"],\n}\nok = True\nfor fn, keys in + │ PER_FILE.items():\n img = Image.open(\"/tmp/opencode/\" + fn).convert(\"RGB\")\n px + │ = img.load()\n w, h = img.size\n res = {}\n for k in keys:\n line = + │ COLORS[k]\n fl = fill(line)\n ln = flc = 0\n for y in range(h):\n + │ for x in range(w):\n p = px[x, y]\n if math.dist(p, line) <= + │ 30: ln += 1\n if math.dist(p, fl) <= 12: flc += 1\n res[k] = (ln, + │ flc)\n print(fn, img.size, res)\n for k, (ln, flc) in res.items():\n if ln < + │ 100 or flc < 500:\n ok = False\nprint(\"BANDS_OK\" if ok else + │ \"BANDS_MISSING\")\nEOF", + │ "timeout": 180000 + │ } + │ out + │ canvas-chart-cpu.png (436, 191) {'cpu': (901, 749), 'gpu': (461, 516)} + │ canvas-chart-mem.png (436, 191) {'mem': (382, 329), 'vram': (436, 168)} + │ canvas-chart-io.png (436, 191) {'read': (1811, 867), 'write': (496, 11)} + │ BANDS_MISSING + │ + │ tokens 255 in 729 out 84817 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:32:48 • assistant • Qwen3.8-27B-Think + │ + │ Progress! Currently, all 6 keys have line pixels, and fills are drawn on cpu (749), gpu (516), + │ mem (329), vram (168), read (867). Two issues: + │ + │ 1. **write fill: 11 px** — barely any. Why? write = uniform(5e6, 90e6) — a band between min and + │ max of each window. Window w=9 → min~max spread should be considerable... the fill between + │ write's min and max lines should be a substantial area. 11 px is suspicious. + │ + │ 2. **vram fill 168** — vram_pct = 30 + 15*sin + noise, None every 6 samples. The band's width + │ is... within a window of 9 samples, vram values: sine wave ± noise, 1.5 units per 2 seconds, 9 + │ samples = 18 seconds → phase shift 18/150*2π*15 ≈ modest spread (a few %) + noise ±3. Band + │ height on a 0-100 scale, 191px chart: a spread of about 4-8 units ≈ 8-15px × 400px width × ~83% + │ coverage ≈ 2500-5000 px. Hmm, 168 is low, but vram has 1/6 missing → gaps. Still, 168 seems low. + │ + │ Wait — but these fill pixel counts depend on the band width (difference between window max and + │ min). For smooth series (mem: 42+12*sin(i/400)+noise±2 → within a 9-sample window, sine + │ variation = 9/400*12*2π*… tiny, about 0.5 units; noise ±2 → band about 4 units ≈ 7px tall × + │ 430px ≈ 3000px). mem fill = 329. Also low! + │ + │ Hmm. So the fills are present but far smaller than my estimate. Possibilities: + │ - The fill color is slightly different from what I calculated (canvas compositing with subpixel + │ antialiasing on the line edges, plus the fill being drawn over gridlines etc.) — my tolerance is + │ 12. + │ - Or the fill is drawn only partially. + │ + │ Wait, let me reconsider: are the fills really being drawn? The cpu fill is 749 px, in a 436x191 + │ = 83k px canvas. The cpu band: 25±18 sine + noise ±6 + spike → window max-min spread: + │ noise-driven, about 10-20 units ≈ 20-40px band height × 436 ≈ 9-17k px. Observed value is 749. + │ Way off. + │ + │ Hmm, so something is still off with the fill. But the lines are drawn (cpu 901 px — 3 lines × + │ about 436 px × about 1.3 px ≈ 1700 px... observed 901, so-so; the lines have gaps due to + │ antialiasing/ + │ … [2465 more chars] + │ + │ Fills render but with much fewer pixels than expected. Let me dump the actual color histogram of + │ the CPU canvas to see what's really there: + │ + │ bash python3 - <<'EOF' from collections import Counter from PIL import Image for … ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nfrom collections import Counter\nfrom PIL import + │ Image\n\nfor fn in [\"canvas-chart-cpu.png\", \"canvas-chart-io.png\"]:\n img = + │ Image.open(\"/tmp/opencode/\" + fn).convert(\"RGB\")\n c = Counter(img.getdata())\n + │ print(fn, img.size)\n for color, n in c.most_common(14):\n print(\" \", color, + │ n)\n print()\nEOF", + │ "timeout": 60000 + │ } + │ out + │ :6: DeprecationWarning: Image.Image.getdata is deprecated and will be removed in + │ Pillow 14 (2027-10-15). Use get_flattened_data instead. + │ canvas-chart-cpu.png (436, 191) + │ (22, 27, 35) 65511 + │ (32, 57, 73) 1556 + │ (52, 41, 64) 804 + │ (24, 30, 39) 592 + │ (26, 33, 43) 488 + │ (32, 39, 50) 397 + │ (30, 37, 46) 391 + │ (23, 29, 37) 385 + │ (28, 35, 45) 385 + │ (24, 29, 38) 300 + │ (79, 195, 247) 210 + │ (186, 104, 200) 121 + │ (19, 24, 31) 110 + │ (55, 68, 97) 101 + │ + │ canvas-chart-io.png (436, 191) + │ (22, 27, 35) 55374 + │ (59, 43, 49) 6201 + │ (92, 74, 61) 3343 + │ (28, 34, 44) 844 + │ (22, 28, 37) 844 + │ (229, 115, 115) 600 + │ (26, 33, 42) 387 + │ (24, 30, 40) 387 + │ (32, 39, 50) 384 + │ (65, 51, 60) 270 + │ (255, 241, 118) 190 + │ (24, 30, 39) 157 + │ (64, 51, 59) 137 + │ (30, 37, 46) 104 + │ + │ tokens 144 in 1607 out 85802 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:33:49 • assistant • Qwen3.8-27B-Think + │ + │ Now I can decode this: + │ + │ **CPU canvas:** + │ - Dominant background: (22,27,35) — 65511 px. Hmm, I expected (28,35,48) = #1c2330, but the + │ actual background is (22,27,35). Close to #161b23 (22,27,35) = --bg-panel! So the canvas sits on + │ top of the panel background, not the card background. (The .chart-card is bg-panel? The earlier + │ grep: `.tab-btn:hover { background: var(--bg-card) }`, `.chart-card {` … let me check: the + │ chart-card block — the grep result showed `background: var(--bg-panel)` appearing before + │ `.tab-btn:hover`… order: "html,body bg var(--bg)", "background: var(--bg-panel)", "background: + │ transparent", then .tab-btn:hover bg-card… So .chart-card is probably var(--bg-panel) = #161b23 + │ = (22,27,35). ✓ matches!) + │ + │ - **(32,57,73): 1556 px** — expected CPU fill (over bg-panel): a*color+(1-a)*bg = + │ 0.1804*(79,195,247)+0.8196*(22,27,35) = (14.25+18.03, 35.18+22.13, 44.56+28.69) = (32.3, 57.3, + │ 73.2) → (32,57,73) ✓✓✓ That's the CPU fill! 1556 px. + │ - **(52,41,64): 804 px** — expected GPU fill: 0.1804*(186,104,200)+0.8196*(22,27,35) = + │ (33.55+18.03, 18.76+22.13, 36.08+28.69) = (51.6, 40.9, 64.8) → (52,41,64) ✓ That's the GPU fill! + │ 804 px. + │ - (79,195,247): 210 px — pure cyan = CPU line (border + legend box). + │ - (186,104,200): 121 px — GPU line. + │ - (55,68,97): 101 px — hmm, a blend (grid lines on top of the fill?). + │ + │ So the fill is being rendered correctly. My earlier target was using the wrong background + │ (28,35,48) instead of (22,27,35), so the counts came out low. With the correct background: CPU + │ fill 1556, GPU fill 804. + │ + │ **IO canvas:** + │ - (59,43,49): 6201 px — expected read fill: 0.1804*(229,115,115)+0.8196*(22,27,35) = + │ (41.31+18.03, 20.75+22.13, 20.75+28.69) = (59.3, 42.9, 49.4) → (59,43,49) ✓ read fill, 6201 px — + │ big! + │ - (92,74,61): 3343 px — expected write fill: 0.1804*(255,241,118)+0.8196*(22,27,35) = + │ (46.0+18.03, 43.48+22.13, 21.29+28.69) = (64, 65.6, 50) → hmm, that's (64,66,50), not + │ (92,74,61). + │ + │ (92,74,61)? Let me think… write fill over the read fill (overlapping bands): 0. + │ … [4227 more chars] + │ + │ The fill is being rendered correctly (my target calculation was using the wrong background — the + │ canvas sits on top of `--bg-panel` #161b23). The fill colors match exactly: (32,57,73)=CPU fill, + │ (52,41,64)=GPU fill, (59,43,49)=read fill. However, the fill area looks small relative to the + │ band width — let me verify by cross-checking against the actual data: + │ + │ bash curl -s http://127.0.0.1:8599/api/history | python3 - <<'EOF' import json, s… ok 0.0s + │ in + │ { + │ "command": "curl -s http://127.0.0.1:8599/api/history | python3 - <<'EOF'\nimport json, + │ sys, math\nd = json.load(sys.stdin)\nW, H = 404.0, 197.6 # chart area px\nper_win_x = W / + │ 200\nfor k in (\"cpu\", \"gpu\", \"mem_pct\", \"vram_pct\", \"io_read\", \"io_write\"):\n + │ s = d[\"series\"][k]\n span = [ (m - n) for m, n in zip(s[\"max\"], s[\"min\"]) if m is + │ not None and n is not None ]\n if not span:\n print(k, \"no data\")\n + │ continue\n area = sum(v / 100 for v in span) * H * per_win_x if k not in (\"io_read\", + │ \"io_write\") else sum(1 for _ in span) * 0\n # io: bytes scale unknown (auto), just + │ report span stats\n print(f\"{k}: n={len(span)} span_min={min(span):.1f} + │ span_avg={sum(span)/len(span):.1f} span_max={max(span):.1f} est_px={area:.0f}\")\nEOF", + │ "workdir": "/home/mac/Code/dashboard", + │ "timeout": 30000 + │ } + │ out + │ Traceback (most recent call last): + │ File "", line 2, in + │ File "/usr/lib/python3.14/json/__init__.py", line 298, in load + │ return loads(fp.read(), + │ cls=cls, object_hook=object_hook, + │ parse_float=parse_float, parse_int=parse_int, + │ parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw) + │ File "/usr/lib/python3.14/json/__init__.py", line 352, in loads + │ return _default_decoder.decode(s) + │ ~~~~~~~~~~~~~~~~~~~~~~~^^^ + │ File "/usr/lib/python3.14/json/decoder.py", line 345, in decode + │ obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + │ ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ + │ File "/usr/lib/python3.14/json/decoder.py", line 363, in raw_decode + │ raise JSONDecodeError("Expecting value", s, err.value) from None + │ json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) + │ + │ tokens 612 in 3175 out 87554 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:35:40 • assistant • Qwen3.8-27B-Think + │ + │ curl returned empty — is the shot server down? Or was the pipe closed early? Let me check the + │ server status. The previous probe worked fine, but the shot server was started a while ago, and + │ the buffer is... let me check if the server is still running. + │ + │ bash curl -s -o /tmp/opencode/hist.json -w "%{http_code} %{size_download}\n" http… ok 0.0s + │ in + │ { + │ "command": "curl -s -o /tmp/opencode/hist.json -w \"%{http_code} %{size_download}\\n\" + │ http://127.0.0.1:8599/api/history; pgrep -af shot_server | head -2" + │ } + │ out + │ 200 79110 + │ 85426 uv run python /tmp/opencode/shot_server.py + │ 85430 /home/mac/Code/dashboard/.venv/bin/python3 /tmp/opencode/shot_server.py + │ + │ tokens 280 in 137 out 91340 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:35:46 • assistant • Qwen3.8-27B-Think + │ + │ The server is running, and the endpoint is working (200, 79110 bytes). The earlier pipe failure + │ was temporary (probably `curl | python` raced). The file has been saved — let me analyze + │ /tmp/opencode/hist.json. + │ + │ bash python3 - <<'EOF' import json d = json.load(open("/tmp/opencode/hist.json"))… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nimport json\nd = + │ json.load(open(\"/tmp/opencode/hist.json\"))\nW, H = 404.0, 197.6\nper_win_x = W / + │ 200\nfor k in (\"cpu\", \"gpu\", \"mem_pct\", \"vram_pct\", \"io_read\", \"io_write\"):\n + │ s = d[\"series\"][k]\n span = [m - n for m, n in zip(s[\"max\"], s[\"min\"]) if m is + │ not None and n is not None]\n if not span:\n print(k, \"no data\"); continue\n + │ if k in (\"io_read\", \"io_write\"):\n print(f\"{k}: n={len(span)} + │ span_avg={sum(span)/len(span):.3e} span_max={max(span):.3e}\")\n else:\n area = + │ sum(v / 100 for v in span) * H * per_win_x\n print(f\"{k}: n={len(span)} + │ span_min={min(span):.1f} span_avg={sum(span)/len(span):.1f} est_px={area:.0f}\")\nEOF", + │ "timeout": 30000 + │ } + │ out + │ cpu: n=200 span_min=1.8 span_avg=10.6 est_px=8460 + │ gpu: n=200 span_min=0.0 span_avg=6.4 est_px=5116 + │ mem_pct: n=200 span_min=0.1 span_avg=2.6 est_px=2080 + │ vram_pct: n=200 span_min=0.0 span_avg=4.1 est_px=3296 + │ io_read: n=200 span_avg=1.135e+08 span_max=3.206e+08 + │ io_write: n=200 span_avg=5.067e+07 span_max=8.596e+07 + │ + │ tokens 66 in 357 out 91758 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:35:58 • assistant • Qwen3.8-27B-Think + │ + │ Expected fill areas (cpu/mem charts, 0–100 scale): + │ - cpu: approx. 8460 px — observed 1556 px + │ - gpu: approx. 5116 px — observed 804 px + │ - mem_pct: approx. 2080 px — observed 329 px + │ - vram_pct: approx. 3296 px — observed 168 px + │ + │ Observed ≈ 18% of expected... wait: 1556/8460 = 18.4%, 804/5116 = 15.7%, 329/2080 = 15.8%, + │ 168/3296 = 5.1%?? Hmm, the last one is off. + │ + │ 18%?? That's... suspiciously close to the fill's alpha (0.18)!! Coincidence? No wait — it can't + │ be. Pixels are either painted or they aren't. + │ + │ Hmm, wait. Maybe the fill is being drawn with alpha, and my tolerance of 12 is only capturing + │ pixels that are "fully" painted (single layer). Where the fill overlaps with gridlines, or where + │ two fills overlap, the color shifts beyond the tolerance. But that wouldn't reduce the count to + │ 18%. + │ + │ Let me think from a different angle. What if the fill is only being drawn in some regions? For + │ example, **the fill is drawn between the max and min datasets, but the filler is being clipped + │ by the "first drawn" dataset's...**? + │ + │ Hmm, wait a sec. Let me reconsider: what if the min/max lines themselves are being drawn as a + │ nearly-zero-width band? Because the min and max datasets are at nearly the same position?? No — + │ the span data shows cpu's span_avg = 10.6 units ≈ 21px height. The lines are definitely apart + │ (and in the histogram, the pure-color line pixels total about 330px + blends... hmm, actually + │ that's also small: cpu's 3 lines × 436px ≈ 1300px × 1–1.5px width ≈ 1500–2000px; observed pure + │ cyan is 210 + blends...). + │ + │ Wait, actually — hmm, let me count the histogram's pure colors more carefully: + │ - (79,195,247) pure cyan: 210 px. A 436px-wide line with 1.5px width should be about 650+ px of + │ pure color. 210 is too few! + │ - (186,104,200) pure purple: 121 px. GPU's 3 lines (with gaps, 1/9 missing): about 1200px × 1px + │ ≈ 1200 × 0.89 ≈ 1000. Observed is 121?! + │ + │ So even the lines are only about 1/5 of the expected pixel count. And the canvas is 436x191 = + │ 83,276 px, with the dominant color (22,2 + │ … [2722 more chars] + │ + │ The observed fill area is only about 15-18% of what's expected from the data spans. I'll measure + │ directly: grab the chart's `chartArea` and the data, then verify whether the fill color exists + │ along the vertical columns between the min and max y-positions of the known windows: + │ + │ write /tmp/opencode/cdp/probe5.js ok 0.0s + │ in + │ { + │ "content": "const puppeteer = require(\"puppeteer-core\");\n\n(async () => {\n const + │ browser = await puppeteer.launch({\n executablePath: \"/usr/bin/chromium\",\n + │ headless: \"new\",\n args: [\"--no-sandbox\", \"--disable-gpu\", + │ \"--window-size=1440,1000\"],\n });\n const page = await browser.newPage();\n await + │ page.setViewport({ width: 1440, height: 1000 });\n await + │ page.goto(\"http://127.0.0.1:8599/\", { waitUntil: \"networkidle0\", timeout: 30000 });\n + │ await new Promise((r) => setTimeout(r, 5000));\n const info = await page.evaluate(async + │ () => {\n const d = await (await fetch(\"/api/history\")).json();\n const out = + │ [];\n for (const chart of Object.values(Chart.instances)) {\n out.push({ canvas: + │ chart.canvas.id, area: chart.chartArea });\n }\n return { areas: out, series: { cpu: + │ d.series.cpu, io_read: d.series.io_read, io_write: d.series.io_write }, pts: d.ts.length + │ };\n });\n const { areas, series } = info;\n for (const { canvas: id, area } of areas) + │ {\n if (id !== \"chart-cpu\") continue;\n const el = await page.$(\"#\" + id);\n + │ const buf = await el.screenshot({ encoding: \"binary\" });\n const png = + │ require(\"fs\").writeFileSync(\"/tmp/opencode/cpu-col.png\", buf);\n + │ console.log(\"saved cpu canvas\", area);\n console.log(JSON.stringify({ area, + │ cpuWindow: { max: series.cpu.max[100], min: series.cpu.min[100] }, ioWindow: { max: + │ series.io_read.max[100], min: series.io_read.min[100] } }));\n }\n await + │ browser.close();\n})();\n", + │ "filePath": "/tmp/opencode/cdp/probe5.js" + │ } + │ out Wrote file successfully. + │ + │ tokens 193 in 2065 out 92180 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:37:15 • assistant • Qwen3.8-27B-Think + │ + │ This has gotten complicated. Let me step back and think. + │ + │ The chart state confirmed the following: + │ - All three charts have 200 labels, and all 6 datasets have n=200 each. + │ - fill=2 and fill=5 are correctly set. + │ - Legend is clean: ["CPU","GPU"], ["Memory","VRAM"], ["read","write"] — the band filter is now + │ working. + │ - No JS errors. + │ + │ The pixel analysis showed that fills do exist (the "write fill: 11" issue was probably because + │ my fill color calculation for the io chart was wrong — io's y-axis is bytes, so the band is near + │ the bottom. Also, the background I assumed (28,35,48) might not match the actual card + │ background). + │ + │ Let me check the color histogram: + │ - canvas-chart-cpu.png: dominant (22,27,35) — this is close to --bg-panel #161b23 = (22,27,35). + │ So the canvas background is #161b23 (the panel), not #1c2330 (the card) that I was assuming. + │ - (32,57,73): 1556 px — is this the CPU fill? CPU color #4fc3f7 = (79,195,247). Fill on + │ (22,27,35) at a=0.18: (0.18*79+0.82*22, 0.18*195+0.82*27, 0.18*247+0.82*35) = (14.2+18, + │ 35.1+22.1, 44.5+28.7) = (32.2, 57.2, 73.2) → (32,57,73). Exactly 1556px! So the CPU fill is + │ being drawn (1556 px). + │ - (52,41,64): 804 px — GPU fill: #ba68c8=(186,104,200): (0.18*186+18, 0.18*104+22.1, + │ 0.18*200+28.7)=(33.5+18, 18.7+22.1, 36+28.7)=(51.5,40.8,64.7)→(52,41,64). Exactly 804px! GPU + │ fill is being drawn. + │ - (79,195,247): 210 px — pure CPU line color (the 1.5px avg line + max/min lines). + │ - (186,104,200): 121 px — pure GPU line color. + │ + │ So in the cpu chart: fill (cpu 1556, gpu 804) + lines (210, 121). Everything is being drawn! + │ + │ - canvas-chart-io.png: + │ - (22,27,35): 55374 (background) + │ - (59,43,49): 6201 px — read fill? #e57373=(229,115,115) on (22,27,35): (0.18*229+18, + │ 0.18*115+22.1, 0.18*115+28.7)=(41.2+18,20.7+22.1,20.7+28.7)=(59.2,42.8,49.4)→(59,43,49). + │ Exactly 6201px! Read fill is being drawn. + │ - (92,74,61): 3343 px — write fill? #fff176=(255,241,118): (0.18*255+18, 0.18*241+22.1, + │ 0.18*118+28.7)=(46+18,43.4+22.1,21.2+28.7)=(64,65.5,49.9)→(64,66,50). Hm + │ … [1636 more chars] + │ + │ The histogram confirms everything renders — my earlier "missing" was a false negative from using + │ the wrong background color (canvas sits on `--bg-panel` #161b23=(22,27,35), not the card color). + │ The fill pixels match exactly: CPU fill (32,57,73)=1556px, GPU fill (52,41,64)=804px, read fill + │ (59,43,49)=6201px. Let me do a clean all-canvas verification with the correct background: + │ + │ bash python3 - <<'EOF' import math from PIL import Image from collections import … ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nimport math\nfrom PIL import Image\nfrom collections import + │ Counter\n\nbg = (22, 27, 35)\na = 0x2E / 255\ndef fill(c):\n return tuple(round(a * v + + │ (1 - a) * b) for v, b in zip(c, bg))\n\nCOLORS = {\n \"cpu\": (0x4F, 0xC3, 0xF7), + │ \"gpu\": (0xBA, 0x68, 0xC8),\n \"mem\": (0x81, 0xC7, 0x84), \"vram\": (0xFF, 0xB7, + │ 0x4D),\n \"read\": (0xE5, 0x73, 0x73), \"write\": (0xFF, 0xF1, 0x76),\n}\nPER_FILE = + │ {\n \"canvas-chart-cpu.png\": [\"cpu\", \"gpu\"],\n \"canvas-chart-mem.png\": + │ [\"mem\", \"vram\"],\n \"canvas-chart-io.png\": [\"read\", \"write\"],\n}\nok = + │ True\nfor fn, keys in PER_FILE.items():\n img = Image.open(\"/tmp/opencode/\" + + │ fn).convert(\"RGB\")\n px = img.load()\n w, h = img.size\n for k in keys:\n + │ line = COLORS[k]; fl = fill(line)\n ln = flc = 0\n for y in range(h):\n + │ for x in range(w):\n p = px[x, y]\n if math.dist(p, line) <= + │ 30: ln += 1\n if math.dist(p, fl) <= 12: flc += 1\n status = \"OK\" + │ if (ln > 50 and flc > 300) else \"MISSING\"\n if status == \"MISSING\": ok = + │ False\n print(f\"{fn:22s} {k:6s} line={ln:6d} fill={flc:6d} + │ {status}\")\nprint(\"ALL_BANDS_OK\" if ok else \"BANDS_MISSING\")\nEOF", + │ "timeout": 180000 + │ } + │ out + │ canvas-chart-cpu.png cpu line= 901 fill= 2173 OK + │ canvas-chart-cpu.png gpu line= 461 fill= 1237 OK + │ canvas-chart-mem.png mem line= 382 fill= 264 MISSING + │ canvas-chart-mem.png vram line= 436 fill= 438 OK + │ canvas-chart-io.png read line= 1811 fill= 6754 OK + │ canvas-chart-io.png write line= 496 fill= 26 MISSING + │ BANDS_MISSING + │ + │ tokens 21 in 2287 out 94437 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:38:29 • assistant • Qwen3.8-27B-Think + │ + │ Two remaining "MISSING": + │ 1. **mem fill=264** — expected value is about 2080 px (from span calculation: span_avg=2.6 units + │ → the band is very narrow, about 5px tall). Hmm, wait: 264 px against an estimated 2080 px? + │ span_avg 2.6/100 × 197.6px × 2.02px/window × 200 windows = 2080 px. Observed value is 264. + │ + │ Hmm, but wait — my estimate assumed the band's height spans the full 2.02px per window — which + │ is correct as an area. 264 vs 2080 = 13%. + │ + │ 2. **write fill=26** — write's span_avg = 5.07e7 bytes. The io y-axis is autoscaled to max about + │ 3.2e8 (the read max). write's band: 5e7/3.2e8 ≈ 16% of height ≈ 31px... × 404px ≈ 12k px?? + │ Observed value is 26. + │ + │ Hmm, wait, that doesn't add up either. Let me check the histogram of the mem canvas and look at + │ the actual colors there. Same for io's write. + │ + │ No — wait. Let me reconsider write: in the earlier histogram of the io canvas: + │ - (59,43,49): 6201 = read fill ✓ + │ - (92,74,61): 3343 = ??? + │ - (64,66,50) — expected write fill — was not in the top 14! + │ - (65,51,60): 270, (64,51,59): 137 — hmm. + │ + │ (92,74,61): what combination gives this? write fill on top of read fill: 0.18*(255,241,118) + + │ 0.82*(59.2,42.8,49.4) = (46+48.5, 43.5+35.1, 21.3+40.5) = (94.5, 78.6, 61.8) → (95,79,62). Close + │ to (92,74,61) but not an exact match (off by about 3–5 per channel). With alpha blending on the + │ GPU's subpixel rounding, it should be within 1–2. Hmm, (92,74,61) vs (95,79,62): Δ=(3,5,1) — + │ borderline. + │ + │ And pure write fill (64,66,50) over background (22,27,35) — not present in the top 14 (which was + │ cut off at 101px). Probably under 100 px. + │ + │ Why is pure write fill almost nonexistent, while read fill is 6201? write = uniform(5e6, 90e6) + │ per sample; per window (9 samples): min≈5–10e6, max≈70–90e6 → the band spans from the bottom to + │ about 28% of the scale — a huge band, and it should cover almost the entire lower region... + │ + │ Oh, wait. I got it!!! **The write fill is under the read fill!** Both bands start from the min + │ line (near 0 at the bottom)... no wait, the fill i + │ … [3311 more chars] + │ + │ I need to investigate this further. Let me check the details. The write fill is explained: the + │ write band (0 to about 28% of the scale) is completely inside the read band (0 to about 60%), + │ and since it's drawn after the read fill, the color is composited (the 3343 px at (92,74,61) is + │ exactly the write-on-read blend). Let me verify whether the mem canvas fill color actually + │ exists in a larger quantity: + │ + │ bash python3 - <<'EOF' from collections import Counter from PIL import Image impo… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nfrom collections import Counter\nfrom PIL import + │ Image\nimport math\nimg = + │ Image.open(\"/tmp/opencode/canvas-chart-mem.png\").convert(\"RGB\")\nc = + │ Counter(img.getdata())\nprint(\"top colors:\")\nfor color, n in c.most_common(12):\n + │ print(\" \", color, n)\na = 0x2E / 255\nbg = (22, 27, 35)\nmem = tuple(round(a * v + (1 - + │ a) * b) for v, b in zip((0x81, 0xC7, 0x84), bg))\nvram = tuple(round(a * v + (1 - a) * b) + │ for v, b in zip((0xFF, 0xB7, 0x4D), bg))\nmem_on_vram = tuple(round(a * v + (1 - a) * b) + │ for v, b in zip((0x81, 0xC7, 0x84), vram))\nprint(\"expected mem fill:\", mem, \"count:\", + │ c.get(mem, 0))\nprint(\"expected vram fill:\", vram, \"count:\", c.get(vram, + │ 0))\nprint(\"mem-on-vram blend:\", mem_on_vram, \"count:\", c.get(mem_on_vram, 0))\nnear = + │ {k: v for k, v in c.items() if math.dist(k, mem) <= 25}\nprint(\"within 25 of mem fill:\", + │ sum(near.values()), near)\nEOF", + │ "timeout": 60000 + │ } + │ out + │ :5: DeprecationWarning: Image.Image.getdata is deprecated and will be removed in + │ Pillow 14 (2027-10-15). Use get_flattened_data instead. + │ top colors: + │ (22, 27, 35) 71218 + │ (24, 30, 39) 719 + │ (26, 33, 43) 673 + │ (23, 29, 37) 417 + │ (32, 39, 50) 408 + │ (28, 35, 45) 404 + │ (30, 37, 46) 356 + │ (24, 29, 38) 354 + │ (64, 55, 42) 206 + │ (255, 183, 77) 151 + │ (19, 24, 31) 122 + │ (129, 199, 132) 108 + │ expected mem fill: (41, 58, 52) count: 20 + │ expected vram fill: (64, 55, 43) count: 0 + │ mem-on-vram blend: (76, 81, 59) count: 0 + │ within 25 of mem fill: 1985 {(54, 61, 72): 13, (47, 54, 65): 6, (42, 48, 59): 7, (40, 39, + │ 38): 5, (38, 44, 54): 15, (45, 51, 62): 7, (51, 59, 69): 8, (48, 55, 65): 5, (46, 54, 64): + │ 4, (50, 58, 69): 11, (44, 51, 61): 17, (34, 40, 49): 23, (32, 38, 47): 24, (41, 39, 38): 12, + │ (38, 44, 55): 5, (33, 39, 49): 9, (48, 55, 66): 4, (45, 52, 62): 6, (31, 38, 47): 3, (52, + │ 59, 70): 10, (46, 53, 63): 8, (53, 61, 72): 2, (51, 59, 70): 10, (49, 55, 66): 6, (37, 43, + │ 53): 8, (44, 50, 60): 1, (42, 49, 59): 8, (43, 50, 60): 4, (52, 59, 71): 9, (36, 42, 52): 3, + │ (33, 38, 49): 6, (39, 47, 56): 2, (31, 37, 45): 6, (46, 53, 64): 6, (40, 47, 56): 5, (48, + │ 56, 65): 1, (47, 53, 64): 1, (39, 45, 55): 12, (55, 62, 72): 10, (41, 48, 59): 3, (35, 40, + │ 50): 3, (37, 43, 52): 2, (50, 57, 68): 2, (35, 41, 50): 11, (36, 43, 51): 3, (47, 55, 65): + │ 1, (32, 39, 48): 6, (35, 42, 51): 14, (44, 51, 60): 3, (53, 60, 72): 3, (31, 42, 43): 13, + │ (43, 41, 38): 20, (42, 49, 58): 2, (35, 42, 52): 8, (40, 47, 57): 9, (47, 54, 64): 9, (33, + │ 39, 48): 9, (41, 47, 58): 4, (31, 38, 49): 1, (32, 39, 50): 408, (38, 45, 54): 1, (31, 37, + │ 46): 20, (35, 41, 51): 9, (36, 43, 52): 2, (48, 44, 39): 2, (54, 48, 40): 2, (61, 53, 41): + │ 1, (45, 43, 38): 3, (48, 44, 40): 1, (49, 44, 39): 2, (53, 48, 40): 9, (58, 50, 41): 1, (42, + │ 40, 37): 11, (45, 42, 38): 2, (43, 50, 59): 6, (54, 49, 40): 3, (44, 42, 38): 5, (47, 44, + │ 39): 4, (46, 43, 39): 5, (63, 54, 41): 6, (41, 38, 37): 2, (53, 47, 40): 6, (49, 44, 38): 5, + │ (55, 49, 39): + │ … [4465 more chars] + │ + │ tokens 172 in 2420 out 96744 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:39:56 • assistant • Qwen3.8-27B-Think ERROR + │ + │ Now it's clear. Looking at the mem canvas histogram: + │ + │ - Expected mem fill (41,58,52): only 20 pixels at the exact match, but there's a whole gradient + │ of greenish fill colors: (38,53,49), (45,65,56), (51,76,63), (46,65,57), (50,74,61), (52,76,62), + │ (48,70,59), (41,59,53), (45,66,56), (44,64,56), (51,75,62), (47,69,59)... + │ + │ These are the mem fill pixels, but the rendered fill is brighter/greener than my flat alpha + │ calculation. Why? Because **the fill is drawn on top of grid lines**, and on top of... wait no. + │ (51,76,63) vs expected (41,58,52): the difference is about (10,18,11) — that's much brighter. As + │ if the alpha were higher: solving 0.α*(129,199,132)+(1-α)*(22,27,35) = (51,76,63): from the G + │ channel: 199α+27(1-α)=76 → 172α=49 → α=0.285. Hmm, about 0.28, not 0.18. + │ + │ Ah, wait. I get it! **The fill is being drawn twice** — once per overlapping... no, that's not + │ it. Or: **the mem fill is drawn on top of the VRAM fill**? VRAM fill = (64,55,43)-ish. Mem fill + │ on top of VRAM fill: 0.18*(129,199,132)+0.82*(64,55,43) = (23.2+52.5, 35.8+45.1, 23.8+35.3) = + │ (75.7, 80.9, 59.1) → (76,81,59). But the observed cluster is (51,76,63)-ish, not (76,81,59). + │ + │ Hmm, so what gives (51,76,63)? If the alpha is about 0.28 over the background... or if the fill + │ is on top of a grid line: grid line color rgba(42,51,66,0.5) on top of background = + │ 0.5*(42,51,66)+0.5*(22,27,35) = (32,39,50.5) → (32,39,51). Then mem fill on top of that: + │ 0.18*(129,199,132)+0.82*(32,39,51) = (23.2+26.2, 35.8+32, 23.8+41.8) = (49.4, 67.8, 65.6) → + │ (49,68,66). Closer to (49,70,59)? Not really... + │ + │ Hmm, wait. What about anti-aliased edges of the fill... no, that's only at the boundaries. + │ + │ Let me reconsider: maybe my alpha is wrong! `backgroundColor: color + "2e"` — color is "#81c784" + │ → "#81c7842e". 8-digit hex: alpha = 0x2E = 46/255 = 0.180. That's what I used. + │ + │ But — the observed values suggest an effective alpha of about 0.28 in the main fill region. What + │ if the fill is being drawn on top of something with a slightly brighte + │ … [8819 more chars] + │ + │ error {"name":"MessageAbortedError","data":{"message":"Aborted"}} + + + │ 2026-08-30 18:43:36 • user + │ + │ it looks like the bands are working correctly but you cannot verify because the average line is + │ running through them, please remove the average line + + + │ 2026-08-30 18:43:36 • assistant • Qwen3.8-27B-Think + │ + │ The user is saying: "The bands look like they're working correctly, but I can't verify because + │ the average lines are running through them. Please remove the average lines." + │ + │ In other words, they want me to remove the average lines and leave only the min/max bands. + │ Simple: + │ + │ In `envelopeDs` in app.js, remove the avg dataset, and have each key return only 2 datasets (max + │ with fill, min). Then in `build`, the fill index becomes `i * 2 + 1` (max is at index i*2, min + │ is at i*2+1). + │ + │ Wait — ordering: [max (fill→min), min]. For key i: max at index 2i, min at 2i+1. max.fill = + │ 2i+1. + │ + │ Also about the legend: previously only the avg dataset had the "main" label, and the bands were + │ being filtered out by the `band` flag. If I remove the avg dataset, then the legend items would + │ be "CPU max"/"CPU min" etc. — I need to decide what to show in the legend. Options: + │ - Give the max dataset the key name's label (e.g., "CPU") and keep the min's label as "CPU min"? + │ And filter out the min from the legend? Hmm. + │ - Simpler: max dataset has label "CPU" (the visible legend entry, and its color box is also + │ correct), min dataset has label "CPU min" with the band flag → filtered from the legend. The + │ tooltip will show "CPU: value" (max) and "CPU min: value". Hmm, it'd be a bit nicer to have the + │ tooltip labels be "CPU max"/"CPU min". + │ + │ Actually, let me think about what's most reasonable: + │ - Legend: should show "CPU", "GPU", "Memory", "VRAM", "read", "write" — same as the current + │ legend. So one labeled entry per key. + │ - Tooltip: it's useful to know which is the max and which is the min. + │ + │ Let me do this: max dataset: label = name + " max" (tooltip shows "CPU max: 45.2"), min dataset: + │ label = name + " min". Legend: I need to show just one entry per key. If I filter out both bands + │ → the legend becomes empty! Not good. + │ + │ Alternative: legend filter: show items whose text ends with " max" → the legend shows "CPU max", + │ "GPU max"... a bit verbose but acceptable? Hmm. + │ + │ Or: max dataset label = name ("CPU"), min = name + │ … [3303 more chars] + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " function envelopeDs(name, color, s) {\n s = s || {};\n return [\n + │ { label: name, data: s.avg || [], borderColor: color, backgroundColor: color, borderWidth: + │ 1.5, pointRadius: 0, tension: 0.25, fill: false, order: 0 },\n { label: name + \" + │ max\", band: true, data: s.max || [], borderColor: color, backgroundColor: color + \"2e\", + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 2 },\n { label: name + │ + \" min\", band: true, data: s.min || [], borderColor: color, backgroundColor: color, + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 1 },\n ];\n }", + │ "newString": " function envelopeDs(name, color, s) {\n s = s || {};\n return [\n + │ { label: name, data: s.max || [], borderColor: color, backgroundColor: color + \"2e\", + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 0 },\n { label: name + │ + \" min\", band: true, data: s.min || [], borderColor: color, backgroundColor: color, + │ borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 1 },\n ];\n }" + │ } + │ out Edit applied successfully. + │ + │ tokens 69897 in 1866 out 35055 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 3471938b + + + │ 2026-08-30 18:48:07 • assistant • Qwen3.8-27B-Think + │ + │ Next, update the fill index calculation in build(): currently `ds[1].fill = i * 3 + 2`, but now + │ each key has 2 datasets: [max, min]. max is at index 2i, and min is at 2i+1. Therefore + │ `ds[0].fill = i * 2 + 1`. + │ + │ edit /home/mac/Code/dashboard/static/js/app.js ok 0.0s + │ in + │ { + │ "filePath": "/home/mac/Code/dashboard/static/js/app.js", + │ "oldString": " pairs.forEach(([name, color, key], i) => {\n const ds = + │ envelopeDs(name, color, series[key]);\n ds[1].fill = i * 3 + 2;\n + │ chart.data.datasets.push(...ds);\n });", + │ "newString": " pairs.forEach(([name, color, key], i) => {\n const ds = + │ envelopeDs(name, color, series[key]);\n ds[0].fill = i * 2 + 1;\n + │ chart.data.datasets.push(...ds);\n });" + │ } + │ out Edit applied successfully. + │ + │ tokens 18 in 246 out 106819 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) 56a57936 + + + │ 2026-08-30 18:48:15 • assistant • Qwen3.8-27B-Think + │ + │ Also, initCharts uses `envelopeDs` for the initial datasets — currently it returns 2 datasets + │ per key, but that's still fine (initial state is empty). + │ + │ Next, re-verify with probe4 (this time the chart should have 4 datasets each: CPU, CPU min, GPU, + │ GPU min). And the legend should display ["CPU","GPU"]. + │ + │ After that, run the pixel check with the correct background (22,27,35). Since the avg line is + │ gone, the fill is no longer obstructed... well, actually the user's point was that the avg line + │ crossing the band made "it" hard to verify. Now the band is clean. + │ + │ Let's run probe4 and the pixel check. + │ + │ bash cd /tmp/opencode/cdp && node probe4.js 2>&1 | tail -5 ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node probe4.js 2>&1 | tail -5", + │ "timeout": 90000 + │ } + │ out + │ ERRORS: [] + │ CHARTS: [{"canvas":"chart-cpu","labels":200,"ds":[["CPU",200,1],["CPU + │ min",200,false],["GPU",200,3],["GPU + │ min",200,false]],"legend":["CPU","GPU"]},{"canvas":"chart-mem","labels":200,"ds":[["Memory", + │ 200,1],["Memory min",200,false],["VRAM",200,3],["VRAM + │ min",200,false]],"legend":["Memory","VRAM"]},{"canvas":"chart-io","labels":200,"ds":[["read" + │ ,200,1],["read min",200,false],["write",200,3],["write + │ min",200,false]],"legend":["read","write"]}] + │ + │ tokens 19 in 217 out 107083 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:48:34 • assistant • Qwen3.8-27B-Think + │ + │ Now, each chart has exactly 4 datasets (max+fill, min per key), no errors, and the legend is + │ also clean. probe4 also saved new canvas screenshots (/tmp/opencode/canvas-chart-*.png). I'll + │ re-run the pixel check with the correct background (22,27,35). + │ + │ Clean: 4 datasets per chart (max-with-fill + min), no errors, legends intact. Re-running the + │ pixel verification on the fresh canvases: + │ + │ bash python3 - <<'EOF' import math from PIL import Image bg = (22, 27, 35) a = 0x… ok 0.0s + │ in + │ { + │ "command": "python3 - <<'EOF'\nimport math\nfrom PIL import Image\n\nbg = (22, 27, 35)\na + │ = 0x2E / 255\ndef fill(c):\n return tuple(round(a * v + (1 - a) * b) for v, b in zip(c, + │ bg))\n\nCOLORS = {\n \"cpu\": (0x4F, 0xC3, 0xF7), \"gpu\": (0xBA, 0x68, 0xC8),\n + │ \"mem\": (0x81, 0xC7, 0x84), \"vram\": (0xFF, 0xB7, 0x4D),\n \"read\": (0xE5, 0x73, + │ 0x73), \"write\": (0xFF, 0xF1, 0x76),\n}\nPER_FILE = {\n \"canvas-chart-cpu.png\": + │ [\"cpu\", \"gpu\"],\n \"canvas-chart-mem.png\": [\"mem\", \"vram\"],\n + │ \"canvas-chart-io.png\": [\"read\", \"write\"],\n}\nok = True\nfor fn, keys in + │ PER_FILE.items():\n img = Image.open(\"/tmp/opencode/\" + fn).convert(\"RGB\")\n px + │ = img.load()\n w, h = img.size\n for k in keys:\n line = COLORS[k]; fl = + │ fill(line)\n ln = flc = 0\n for y in range(h):\n for x in + │ range(w):\n p = px[x, y]\n if math.dist(p, line) <= 30: ln + │ += 1\n if math.dist(p, fl) <= 12: flc += 1\n # write band sits + │ inside read band: also count blended write-on-read pixels\n if k == \"write\":\n + │ blend = tuple(round(a * v + (1 - a) * b) for v, b in zip(line, fill(COLORS[\"read\"])))\n + │ for y in range(h):\n for x in range(w):\n p = px[x, y]\n + │ if math.dist(p, blend) <= 12: flc += 1\n status = \"OK\" if (ln > 50 and flc > 300) + │ else \"MISSING\"\n if status == \"MISSING\": ok = False\n print(f\"{fn:22s} + │ {k:6s} line={ln:6d} fill={flc:6d} {status}\")\nprint(\"ALL_BANDS_OK\" if ok else + │ \"BANDS_MISSING\")\nEOF", + │ "timeout": 180000 + │ } + │ out + │ canvas-chart-cpu.png cpu line= 291 fill= 2606 OK + │ canvas-chart-cpu.png gpu line= 52 fill= 1574 OK + │ canvas-chart-mem.png mem line= 43 fill= 607 MISSING + │ canvas-chart-mem.png vram line= 43 fill= 903 MISSING + │ canvas-chart-io.png read line= 309 fill= 4863 OK + │ canvas-chart-io.png write line= 16 fill= 4593 MISSING + │ BANDS_MISSING + │ + │ tokens 202 in 732 out 107318 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:48:59 • assistant • Qwen3.8-27B-Think ERROR + │ + │ The bands are all there (fill counts are healthy: cpu 2606, gpu 1574, mem 607, vram 903, read + │ 4863, write 4593 including blend). The "MISSING" verdicts this time are caused by the LINE count + │ being low (43, 43, 16) — because the line is now just the edge of the band (max line 1px, min + │ line 1px) and there's no thicker avg line. A 1px antialiased line over a 436px width should be + │ around 400-800px... but observed is 43/16. Hmm. + │ + │ Wait — line=43 for mem? A 1px line at 436px width should have at least ~300+ pure-color + │ pixels... unless the line is being drawn blended (thin line + antialiasing → pixels split + │ between 2-3 color levels, pure color is rare). A 1px line rotated at a shallow angle: pixels are + │ 50% line color / 50% background → distance from pure color is ~half the delta = still large. For + │ a 1px wide line, most pixels are a 50/50 blend → distance from pure color is ~60-70 → my + │ tolerance of 30 barely catches anything! + │ + │ Previously with the avg line (1.5px), more pixels landed on pure color. For 1px lines, the + │ tolerance-30 test undercounts. That explains the line=43 (only the steepest segments, where a + │ vertical 1px line is drawn as fully-colored pixels). + │ + │ So: the lines exist (as band borders — and the fill count confirms the band geometry matches the + │ data). Let me verify the line existence a different way: count pixels within a tolerance of 30 + │ from either the pure line color or the 50/50 blend of line+bg. Or simply: accept the fill count + │ as proof (the fill region is bounded by exactly those min/max lines, and the fill count matches + │ the expected band area derived from the data: cpu 2606... hmm, earlier I estimated 8460 from the + │ span. Now it's 2606?). + │ + │ Hmm, wait — cpu fill=2606, but from the span I estimated 8460px. Discrepancy remains ( + │ + │ error {"name":"MessageAbortedError","data":{"message":"Aborted"}} + + + │ 2026-08-30 18:49:27 • user + │ + │ context compacted + + + │ 2026-08-30 18:49:27 • assistant • Qwen3.8-27B-Think @compaction + │ + │ Let me analyze the conversation and create a structured summary. + │ + │ Objective: The user wants to window-average/downsample chart data to reduce point count, then + │ changed to preserve peaks (min/max envelope bands), and now wants the average line removed. + │ + │ Let me trace the key details: + │ + │ 1. Original request: Charts jump around because too many datapoints. Window-average so at most + │ 200 points sent. + │ 2. User said "preserving peaks sounds better" → min/max envelope. + │ 3. User said "ok go" → implementation. + │ 4. User said "it looks like the bands are working correctly but you cannot verify because the + │ average line is running through them, please remove the average line" + │ + │ Let me capture the important details: + │ + │ Files modified: + │ - /home/mac/Code/dashboard/app/config.py — added `chart_max_points: int = 200` + │ - /home/mac/Code/dashboard/app/routers/overview.py — added `_window(snap, max_points)` helper, + │ changed `/api/history` to return `{ts, series: {k: {avg, min, max}}}` shape + │ - /home/mac/Code/dashboard/static/js/app.js — removed `downsampleIdx`, added `envelopeDs`, + │ rewrote `updateCharts`/`build`, LEGEND with filter, now removing avg line + │ - /home/mac/Code/dashboard/AGENTS.md — updated `/api/history` pitfall note + │ + │ The current active work: just removed the avg line from `envelopeDs`. Need to update `build` + │ function fill index (was `i * 3 + 2`, now should be `i * 2 + 1` since each key now produces 2 + │ datasets not 3). Let me check — the edit changed envelopeDs to return 2 datasets [max, min] with + │ max at index 0 (order 0), min at index 1 (order 1). The `build` function currently does: + │ ``` + │ const ds = envelopeDs(name, color, series[key]); + │ ds[1].fill = i * 3 + 2; + │ ``` + │ This is now WRONG because: + │ - envelopeDs now returns 2 datasets: [max (idx0), min (idx1)] + │ - fill should be on the max dataset (ds[0]) pointing to min (ds[1]) + │ - index math: for key i, datasets start at i*2, max at i*2, min at i*2+1. So ds[0].fill = i*2+1 + │ + │ So the `build` function needs updating: `ds[0].fill = i * 2 + 1;` instead of `ds + │ … [4082 more chars] + │ + │ ## Objective + │ - Downsample dashboard chart history so charts are smooth and peaks are preserved: window the + │ ring buffer to at most 200 points server-side and render each series as a min/max envelope band + │ (no avg line) in Chart.js. + │ + │ ## Important Details + │ - Original ask: window-average to ≤200 points; user then switched to min/max envelope + │ ("preserving peaks sounds better"), approved plan ("ok go"), and now wants the avg line removed + │ (it obscures band verification). + │ - `/api/history` now returns `{"ts": [≤200], "series": {k: {"avg": [...], "min": [...], "max": + │ [...]}}}`; arrays null-padded to align with `ts` (AGENTS.md alignment rule preserved). + │ - Windowing: `w = max(1, ceil(n / max_points))`; one code path (n≤200 → w=1, avg=min=max). + │ All-int values stay int; floats round to 1 dp. Window ts = last sample's ts. + │ - Config: `chart_max_points: int = 200` (env `DASH_CHART_MAX_POINTS`), read via + │ `get_settings()`. + │ - Chart.js v4.4.1 vendored at `static/vendor/chart.umd.min.js`. Canvas sits on `--bg-panel` + │ #161b23 = RGB(22,27,35) (NOT card color). Fill alpha 0x2E/255 ≈ 0.18. + │ - Chart.js legend: `labels.filter(item, chartData)` — 2nd arg is `chart.data`; legend items + │ expose `datasetIndex` (NO `.dataset` prop; `item.dataset` is undefined and throws). + │ - Draw order: higher `order` drawn first (behind). Band fill is drawn as part of the max + │ dataset. + │ - basedpyright: 2 PRE-EXISTING "Cycle detected in import chain" errors in `app/__init__.py` + │ (confirmed pre-existing via `git stash`). No new errors introduced. + │ - Server restart (documented safe pattern): `PID=$(pgrep -f "python main\.py" | head -1); [ -n + │ "$PID" ] && kill "$PID"; sleep 1; setsid nohup uv run python main.py > /tmp/dash.log 2>&1 < + │ /dev/null & disown` + │ - Live server on port 8501 (PID 84742). Throwaway seeded screenshot server `shot_server.py` on + │ port 8599 (PIDs 85426/85430) — must be killed at end. + │ - puppeteer-core installed at `/tmp/opencode/cdp` (node) to drive `/usr/bin/chromium` and dump + │ `Chart.instances` state; cannot view PNGs directly, so verify via chart state + PIL pixel + │ histograms (system `python3` has PIL). + │ + │ ## Work State + │ ### Completed + │ - `app/config.py`: added `chart_max_points: int = 200`. + │ - `app/routers/overview.py`: added `RowAgg` type + `_window(snap, max_points)` helper; rewrote + │ `history()` to window and emit `{avg,min,max}` per key with null padding. + │ - `static/js/app.js`: removed `downsampleIdx` + `newDs`; added `envelopeDs(name,color,s)`; + │ rewrote `updateCharts` with a `build(chart, pairs)` closure; `LEGEND` filter + │ `!data.datasets[item.datasetIndex]?.band`. + │ - `AGENTS.md`: updated `/api/history` pitfall line to describe window-averaging + + │ `{avg,min,max}` + null padding. + │ - Verified: `uv run python -m compileall -q app` → COMPILE_OK; basedpyright → only pre-existing + │ errors; `/tmp/opencode/test_window.py` → SYNTHETIC_OK (spike preserved, int/None/bool handling); + │ `/tmp/opencode/test_endpoint.py` (TestClient, 1800 seeded) → ENDPOINT_OK points: 200 keys: 3. + │ - Live check: all endpoints 200; `/api/history` → 200 points, 19 keys. + │ - probe4 (`/tmp/opencode/cdp/probe4.js`): `ERRORS: []`; all 3 charts 200 labels, 6 datasets × + │ 200, legends `["CPU","GPU"]`/`["Memory","VRAM"]`/`["read","write"]`. + │ - Pixel verification (PIL, bg=(22,27,35)): bands + fills render correctly (e.g., CPU fill + │ (32,57,73), GPU fill (52,41,64), read fill (59,43,49); write band sits inside read band, + │ composited). + │ - Just removed the avg line: `envelopeDs` now returns 2 datasets — `[max (label=name, + │ backgroundColor=color+"2e", fill:false, order:0), min (label=name+" min", band:true, order:1)]`. + │ + │ ### Active + │ - `envelopeDs` now returns 2 datasets (max, min) instead of 3, but `build()` in + │ `static/js/app.js` still does `ds[1].fill = i * 3 + 2;` — now WRONG. Max is dataset index 0 and + │ needs the fill; with 2 datasets/key the min index is `i * 2 + 1`. Must become `ds[0].fill = i * + │ 2 + 1;`. + │ - After that fix, re-verify charts (no errors, 4 datasets each for 2-key charts, legends clean) + │ and re-run pixel check. + │ + │ ### Blocked + │ - (none) — only remaining inconsistency is the `build()` fill index arithmetic after removing + │ the avg line. + │ + │ ## Next Move + │ 1. Edit `static/js/app.js` `build()`: change `ds[1].fill = i * 3 + 2;` → `ds[0].fill = i * 2 + + │ 1;`. + │ 2. Re-run `cd /tmp/opencode/cdp && node probe4.js` → expect `ERRORS: []`, each chart 4 datasets + │ (2 keys × 2), legends `["CPU","GPU"]`/`["Memory","VRAM"]`/`["read","write"]`, fill indices set + │ on the max dataset. + │ 3. Re-run the PIL pixel check (bg=(22,27,35), fill alpha 0x2E/255) over + │ `canvas-chart-{cpu,mem,io}.png` to confirm bands/fills still render without the avg line. + │ 4. Final live checks on port 8501: all endpoints 200, `/api/history` shape (`ts` ≤200, each key + │ has avg/min/max aligned to ts), `grep -c Traceback /tmp/dash.log`. + │ 5. Kill the shot server (`pkill -f shot_server.py` / PIDs 85426,85430) and clean up + │ `/tmp/opencode` temp artifacts. + │ + │ ## Relevant Files + │ - `/home/mac/Code/dashboard/static/js/app.js`: chart rendering; `envelopeDs` (just changed to 2 + │ datasets) and `build()` fill-index need to be consistent; `LEGEND` filter. + │ - `/home/mac/Code/dashboard/app/routers/overview.py`: `_window` helper + `history()` endpoint + │ (server-side avg/min/max, ≤200 points). + │ - `/home/mac/Code/dashboard/app/config.py`: `chart_max_points` setting. + │ - `/home/mac/Code/dashboard/AGENTS.md`: `/api/history` behavior note (updated). + │ - `/home/mac/Code/dashboard/app/state.py`: `HistoryStore` (deque maxlen 1800; `snapshot()`, + │ `record()`); test seeding uses `store._buf.append((t, s))`. + │ - `/home/mac/Code/dashboard/static/vendor/chart.umd.min.js`: Chart.js v4.4.1 (filler + legend + │ internals). + │ - `/tmp/opencode/cdp/probe4.js`, `/tmp/opencode/shot_server.py`, + │ `/tmp/opencode/check_pixels.py`, `/tmp/opencode/test_window.py`, + │ `/tmp/opencode/test_endpoint.py`: verification tooling. + │ - `/tmp/dash.log`: live server log (check for Traceback). + │ + │ tokens 89298 in 3647 out 0 cached 0 written $0.0000 stop + + + │ 2026-08-30 18:56:24 • user + │ + │ I verified the bands work like they should, you can skip verification. Start at position 4 of + │ your next moves and add an updated screenshot of the overview tab to the screenshots folder + │ (remove the old one) + + + │ 2026-08-30 18:56:24 • assistant • Qwen3.8-27B-Think + │ + │ The user has confirmed that the bands are working, and said to skip verification. Starting from + │ item 4 of the next steps: + │ + │ 4. Final live check on port 8501: all endpoints return 200, the shape of `/api/history` (`ts` + │ ≤200, each key's avg/min/max line up with ts), `grep -c Traceback /tmp/dash.log`. + │ 5. Terminate the shot server and clean up the temp artifacts. + │ + │ Additionally: Add an updated screenshot of the Overview tab to the screenshots folder (delete + │ the old one). + │ + │ First, check the contents of the screenshots folder to get a handle on the naming conventions. + │ + │ bash ls -la screenshots/ 2>/dev/null; git log --oneline -3 2>/dev/null ok 0.0s + │ in + │ { + │ "command": "ls -la screenshots/ 2>/dev/null; git log --oneline -3 2>/dev/null", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out + │ total 864 + │ drwxr-xr-x 1 mac mac 244 30. Aug 00:39 . + │ drwxr-xr-x 1 mac mac 306 30. Aug 18:16 .. + │ -rw-r--r-- 1 mac mac 97682 30. Aug 00:38 Disks_20260830.png + │ -rw-r--r-- 1 mac mac 302170 30. Aug 00:38 Journal_20260830.png + │ -rw-r--r-- 1 mac mac 141147 30. Aug 00:37 Overview_20260830.png + │ -rw-r--r-- 1 mac mac 46279 30. Aug 00:39 Plugins_20260830.png + │ -rw-r--r-- 1 mac mac 127254 30. Aug 00:38 Processes_20260830.png + │ -rw-r--r-- 1 mac mac 158720 30. Aug 00:39 Services_20260830.png + │ 6fdfde8 Initial commit + │ + │ tokens 14206 in 209 out 0 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:56:47 • assistant • Qwen3.8-27B-Think + │ + │ The screenshot naming convention is `_YYYYMMDD.png` — Overview_20260830.png. Today is + │ 2026-08-30, so I'll replace it with the same name (deleting the old one first, then writing the + │ new one). + │ + │ Next, I'll run step 4: a live check against 8501. Then, I'll take a screenshot of the overview + │ tab on the live server (8501) and save it to screenshots/Overview_20260830.png. + │ + │ Regarding the screenshot: using puppeteer against the real server at http://127.0.0.1:8501/, + │ wait for the charts to render (the charts start empty, and hx polling fills them in — I need to + │ wait for /api/history to arrive and the charts to be built. Also, the ring buffer on the live + │ server has real data — the number of points depends on the buffer size, and after a recent + │ restart the buffer might be small (w=1, fewer than 200 points). That's fine — the chart will + │ display whatever points exist. But wait — I restarted the server earlier, so the buffer has been + │ accumulating since then. The server was restarted at the start of this session, so… the buffer + │ has data for a few minutes. Enough for the screenshot. + │ + │ Wait, actually — there's a subtle point: after the restart, has /api/history been fetched? The + │ page polls /api/overview via hx-trigger every 2 seconds, and updateCharts is called after each + │ overview fetch (hx-on:afterrequest). So if I wait about 5-8 seconds after loading, the charts + │ should be filled in. + │ + │ Also, for a good screenshot: full page or viewport? The existing Overview_20260830.png is 141KB. + │ Let me take a look at it… I can't see it directly, but I can check its dimensions with PIL and + │ match the size. Let me check the dimensions of the old screenshot and match them. + │ + │ First, let me run the step 4 check and check the dimensions of the old screenshot in parallel. + │ + │ Old screenshot is `screenshots/Overview_20260830.png`. Running the final live checks and + │ checking the old screenshot's dimensions to match: + │ + │ bash for ep in overview disks processes journal services plugins history; do prin… ok 0.0s + │ in + │ { + │ "command": "for ep in overview disks processes journal services plugins history; do printf + │ \"%s %s\\n\" \"$ep\" \"$(curl -s -o /dev/null -w \"%{http_code}\" + │ http://127.0.0.1:8501/api/$ep)\"; done; curl -s http://127.0.0.1:8501/api/history | + │ python3 -c \"\nimport json,sys\nd=json.load(sys.stdin)\nts=d['ts']; + │ n=len(ts)\nprint('ts:', n, 'series keys:', len(d['series']))\nbad=[k for k,v in + │ d['series'].items() if any(len(x)!=n for x in + │ (v['avg'],v['min'],v['max']))]\nprint('aligned:', 'YES' if not bad else bad)\"; grep -c + │ Traceback /tmp/dash.log", + │ "timeout": 30000 + │ } + │ out + │ overview 200 + │ disks 200 + │ processes 200 + │ journal 200 + │ services 200 + │ plugins 200 + │ history 200 + │ ts: 199 series keys: 19 + │ aligned: YES + │ 0 + │ bash python3 -c " from PIL import Image for f in ['Overview_20260830.png','Disks_… ok 0.0s + │ in + │ { + │ "command": "python3 -c \"\nfrom PIL import Image\nfor f in + │ ['Overview_20260830.png','Disks_20260830.png']:\n + │ im=Image.open('/home/mac/Code/dashboard/screenshots/'+f)\n print(f, im.size)\"" + │ } + │ out + │ Overview_20260830.png (1671, 610) + │ Disks_20260830.png (1671, 707) + │ + │ tokens 323 in 817 out 14416 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:57:06 • assistant • Qwen3.8-27B-Think + │ + │ All checks passed: + │ - All endpoints 200 + │ - /api/history: 199 points (buffer since restart), 19 keys, all consistent + │ - 0 Tracebacks + │ + │ The old screenshot is 1671x610 — probably a full-page capture at some viewport. The width of + │ 1671 suggests either a viewport width of 1671 (odd number), or possibly a deviceScaleFactor + │ applied to some width. 1671 / 2 = 835.5, nope. Probably just the fullPage height (610) with a + │ wide viewport. Actually, fullPage captures use the viewport width for the width. So the viewport + │ width is 1671? Unusual. Maybe the viewport was set to some width and dpr was... Let's not + │ overthink this. I'll capture the overview tab with a similar viewport (width ~1671, full page) + │ and compare visually... though I can't see it. I'll go with width 1671, fullPage: true. + │ + │ Hmm, wait — 1671 might be 1600 * something? No, that's not it. Let me check how the screenshot + │ was taken — maybe there's a script in the repo or in git history. Let me search the repo for a + │ screenshot script. + │ + │ All live checks pass (all 200s, 199 pts aligned, 0 tracebacks). Let me check if there's an + │ existing screenshot script in the repo: + │ + │ grep screenshot|puppeteer|chromium|playwright ok 0.0s + │ in + │ { + │ "pattern": "screenshot|puppeteer|chromium|playwright", + │ "path": "/home/mac/Code/dashboard", + │ "include": "*.{md,py,js,sh,json}" + │ } + │ out + │ Found 3 matches + │ /home/mac/Code/dashboard/README.md: + │ Line 12: | ![Overview](screenshots/Overview_20260830.png) | + │ ![Disks](screenshots/Disks_20260830.png) | + │ + │ Line 14: | ![Processes](screenshots/Processes_20260830.png) | + │ ![Journal](screenshots/Journal_20260830.png) | + │ + │ Line 16: | ![Services](screenshots/Services_20260830.png) | + │ ![Plugins](screenshots/Plugins_20260830.png) | + │ + │ tokens 127 in 403 out 15557 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:57:17 • assistant • Qwen3.8-27B-Think + │ + │ No screenshot script exists — probably it was manually taken earlier (before the session). I'll + │ take a new screenshot of the Overview tab on the running server (8501) using puppeteer, with a + │ viewport of width 1671, fullPage, and wait for the chart to fill in. + │ + │ The Overview tab needs to be active (it's the default tab). Wait for /api/history to be called + │ and for the chart to have data (chart.data.labels.length > 0), and wait a bit for rendering. + │ + │ Write the screenshot script: navigate to http://127.0.0.1:8501/, wait for a function where + │ Chart.instances exists and the first chart's labels are more than 0, wait about 1.5 seconds for + │ animation/rendering, then fullPage screenshot to /tmp/opencode/overview_new.png. + │ + │ Note: the running server's buffer currently has about 199 points (started at restart, buffer cap + │ is 1800 at 2-second intervals = 1 hour). 199 points → w=1, all displayed. Good, the chart will + │ look reasonably filled. + │ + │ Also, I should verify that the screenshot shows the new bands — I can't see it, but at least I + │ can confirm the pixel count of the band fill and verify that the capture contains the chart + │ region. Quick sanity check: count pixels near the fill color. + │ + │ After that: delete the old screenshot, and copy the new one to + │ screenshots/Overview_20260830.png. + │ + │ Wait — should the new screenshot be given the same name (today's date, same date)? Yes: the user + │ said "add an updated screenshot of the Overview tab to the screenshots folder (delete the old + │ one)." The old file is Overview_20260830.png (today's date). I'll delete it and write the new + │ one with the same name. + │ + │ No existing script — capturing manually. Taking a fresh full-page screenshot of the Overview tab + │ from the live server, matching the old capture's width: + │ + │ write /tmp/opencode/cdp/shot_overview.js ok 0.0s + │ in + │ { + │ "filePath": "/tmp/opencode/cdp/shot_overview.js", + │ "content": "const puppeteer = require('puppeteer-core');\n(async () => {\n const browser + │ = await puppeteer.launch({\n executablePath: '/usr/bin/chromium',\n args: + │ ['--no-sandbox', '--disable-gpu', '--hide-scrollbars'],\n defaultViewport: { width: + │ 1671, height: 900, deviceScaleFactor: 1 },\n });\n const page = await + │ browser.newPage();\n const errors = [];\n page.on('pageerror', (e) => + │ errors.push(String(e)));\n await page.goto('http://127.0.0.1:8501/', { waitUntil: + │ 'networkidle0', timeout: 30000 });\n await page.waitForFunction(\n () => {\n + │ const c = Object.values(Chart.instances || {});\n return c.length >= 3 && c.every((x) + │ => x.data.labels.length > 5);\n },\n { timeout: 30000 },\n );\n await new + │ Promise((r) => setTimeout(r, 1500));\n const state = await page.evaluate(() => {\n + │ const c = Object.values(Chart.instances);\n return c.map((x) => ({ canvas: x.canvas.id, + │ labels: x.data.labels.length, ds: x.data.datasets.length }));\n });\n + │ console.log('CHARTS:', JSON.stringify(state));\n console.log('ERRORS:', + │ JSON.stringify(errors));\n await page.screenshot({ path: + │ '/tmp/opencode/overview_new.png', fullPage: true });\n await + │ browser.close();\n})().catch((e) => { console.error(e); process.exit(1); });\n" + │ } + │ out Wrote file successfully. + │ + │ tokens 168 in 856 out 16088 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:57:36 • assistant • Qwen3.8-27B-Think + │ + │ All live checks passed: + │ - All endpoints 200 + │ - /api/history: 199 points, 19 keys, all aligned + │ - 0 Tracebacks + │ + │ The old screenshot is 1671x610 (full page at width 1671). Now going to take a new screenshot of + │ the Overview tab from the running server. + │ + │ All live checks pass: every endpoint 200, history 199 pts / 19 keys fully aligned, 0 tracebacks. + │ Now capturing the new Overview screenshot: + │ + │ bash cd /tmp/opencode/cdp && node shot_overview.js ok 0.4s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node shot_overview.js", + │ "timeout": 90000 + │ } + │ out + │ CHARTS: + │ [{"canvas":"chart-cpu","labels":173,"ds":4},{"canvas":"chart-mem","labels":173,"ds":4},{"can + │ vas":"chart-io","labels":173,"ds":4}] + │ ERRORS: [] + │ + │ tokens 20 in 175 out 17112 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:57:46 • assistant • Qwen3.8-27B-Think + │ + │ The screenshot has been taken to /tmp/opencode/overview_new.png. All three charts on the live + │ server have been filled (173 labels, 4 datasets each), with no errors. + │ + │ I'll do a quick sanity check on the screenshot (size + whether the band fill is included), then + │ delete the old file and replace it. + │ + │ bash python3 -c " from PIL import Image import math im = Image.open('/tmp/opencod… ok 0.0s + │ in + │ { + │ "command": "python3 -c \"\nfrom PIL import Image\nimport math\nim = + │ Image.open('/tmp/opencode/overview_new.png').convert('RGB')\nprint('size:', im.size)\npx = + │ im.load(); w, h = im.size\nbg = (22,27,35); a = 0x2E/255\ndef fill(c): return + │ tuple(round(a*v+(1-a)*b) for v,b in zip(c,bg))\ncounts = {}\nfor name, c in + │ [('cpu',(0x4F,0xC3,0xF7)),('gpu',(0xBA,0x68,0xC8)),('mem',(0x81,0xC7,0x84)),('vram',(0xFF, + │ 0xB7,0x4D)),('read',(0xE5,0x73,0x73)),('write',(0xFF,0xF1,0x76))]:\n fl = fill(c); n = + │ 0\n for y in range(0, h, 2):\n for x in range(0, w, 2):\n p = + │ px[x,y]\n if math.dist(p, fl) <= 12: n += 1\n counts[name] = + │ n\nprint('fills:', counts)\nprint('CHART_AREA_OK' if all(v > 50 for v in counts.values()) + │ else 'MISSING')\"", + │ "timeout": 120000 + │ } + │ out + │ size: (1671, 900) + │ fills: {'cpu': 306, 'gpu': 151, 'mem': 120, 'vram': 129, 'read': 46, 'write': 72} + │ MISSING + │ + │ tokens 72 in 427 out 17307 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:57:57 • assistant • Qwen3.8-27B-Think + │ + │ The screenshot size is 1671x900, but I expected fullPage to give a height of 610 like before. + │ Wait, the old one was 1671x610. The new one is 1671x900 — that's the viewport height, meaning + │ fullPage didn't expand (page content height = viewport height, or the page has a fixed height + │ with internal scrolling?). Actually, if fullPage: true, it should capture the entire scrollable + │ height. If the result is 900 (equal to viewport height), then the page's document height is 900 + │ or less, i.e., the page isn't scrollable — the dashboard probably fits within a fixed-viewport + │ layout with internal scrolling within tabs. + │ + │ Hmm, but the old screenshot was 610 in height at 1671 width. So the old capture was viewport + │ 1671x610, not fullPage (or the content was 610). The dashboard probably has a layout designed + │ for about 610px height (1671/610 ≈ 2.74 aspect ratio). + │ + │ Wait — but is the new 900px height fine? The old one was 610. The README displays them in a + │ table. Height 610 vs 900 — the 900 capture may include empty space at the bottom, or the layout + │ may be stretched. The "fills" check: all 6 colors are present (306, 151, 120, 129, 46, 72) — the + │ bands are rendering in the capture. The reason my check said MISSING is that the threshold for + │ read was n>50 and read=46 (due to sampling every 2px, so the actual count is about 4x ≈ 184). + │ All 6 bands are present. So the chart is being captured correctly. + │ + │ The concern is that the page height is 900 rather than 610. To match the old capture style, let + │ me capture with a viewport height of 610 to see how it looks — the dashboard layout probably + │ fits in 610px at width 1671 (designed as a single screen). The old capture is 610 tall. Let me + │ redo it with viewport height 610 (and fullPage: true). If the content is taller than 610, + │ fullPage will expand. Let me check the document's scroll height inside the page. + │ + │ All six band fills are present (my threshold was just miscalibrated for 2px sampling). But the + │ capture height is 900 vs. the old 610 — let me check the page's natural height and recapture to + │ match the old framing: + │ + │ bash cd /tmp/opencode/cdp && node -e " const puppeteer = require('puppeteer-core'… ok 0.1s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && node -e \"\nconst puppeteer = + │ require('puppeteer-core');\n(async () => {\n const browser = await puppeteer.launch({ + │ executablePath: '/usr/bin/chromium', args: ['--no-sandbox','--disable-gpu'] , + │ defaultViewport: { width: 1671, height: 610, deviceScaleFactor: 1 }});\n const page = + │ await browser.newPage();\n await page.goto('http://127.0.0.1:8501/', { waitUntil: + │ 'networkidle0', timeout: 30000 });\n const dims = await page.evaluate(() => ({ sh: + │ document.documentElement.scrollHeight, ih: document.documentElement.clientHeight, bodyH: + │ document.body.scrollHeight }));\n console.log(JSON.stringify(dims));\n await + │ browser.close();\n})().catch(e => { console.error(e); process.exit(1); });\"", + │ "timeout": 60000 + │ } + │ out {"sh":610,"ih":610,"bodyH":532} + │ + │ tokens 82 in 852 out 17805 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:58:22 • assistant • Qwen3.8-27B-Think + │ + │ With a viewport height of 610, the page's scroll height is exactly 610 — meaning the dashboard + │ is designed to fit within a 1671x610 screen. The old screenshot was a 1671x610 viewport capture. + │ My 900-height capture just had extra blank space at the bottom (or the layout got stretched). + │ + │ Re-capture with viewport 1671x610, fullPage:true (this gives 1671x610, same as the old one). + │ + │ The page is designed to fit exactly 1671×610. Recapturing at that viewport: + │ + │ bash cd /tmp/opencode/cdp && sed -i 's/width: 1671, height: 900/width: 1671, heig… ok 0.0s + │ in + │ { + │ "command": "cd /tmp/opencode/cdp && sed -i 's/width: 1671, height: 900/width: 1671, + │ height: 610/' shot_overview.js && node shot_overview.js && python3 -c \"\nfrom PIL import + │ Image\nim = Image.open('/tmp/opencode/overview_new.png')\nprint('size:', im.size)\"", + │ "timeout": 90000 + │ } + │ out + │ CHARTS: + │ [{"canvas":"chart-cpu","labels":177,"ds":4},{"canvas":"chart-mem","labels":177,"ds":4},{"can + │ vas":"chart-io","labels":177,"ds":4}] + │ ERRORS: [] + │ size: (1671, 610) + │ + │ tokens 34 in 275 out 18740 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 18:58:34 • assistant • Qwen3.8-27B-Think + │ + │ Got a new screenshot at exactly 1671×610, and all three charts are filled with 4 datasets each, + │ no errors. Next: + │ 1. Delete the old screenshot: screenshots/Overview_20260830.png + │ 2. Copy the new screenshot under the same name (today's date is the same: 20260830) + │ + │ After that, cleanup (step 5): shut down the shot server and delete temporary artifacts. + │ + │ Perfect match. Swapping in the new screenshot and cleaning up: + │ + │ bash rm screenshots/Overview_20260830.png && cp /tmp/opencode/overview_new.png sc… ok 0.0s + │ in + │ { + │ "command": "rm screenshots/Overview_20260830.png && cp /tmp/opencode/overview_new.png + │ screenshots/Overview_20260830.png && ls -la screenshots/", + │ "workdir": "/home/mac/Code/dashboard" + │ } + │ out + │ total 820 + │ drwxr-xr-x 1 mac mac 244 30. Aug 18:58 . + │ drwxr-xr-x 1 mac mac 306 30. Aug 18:16 .. + │ -rw-r--r-- 1 mac mac 97682 30. Aug 00:38 Disks_20260830.png + │ -rw-r--r-- 1 mac mac 302170 30. Aug 00:38 Journal_20260830.png + │ -rw-r--r-- 1 mac mac 94874 30. Aug 18:58 Overview_20260830.png + │ -rw-r--r-- 1 mac mac 46279 30. Aug 00:39 Plugins_20260830.png + │ -rw-r--r-- 1 mac mac 127254 30. Aug 00:38 Processes_20260830.png + │ -rw-r--r-- 1 mac mac 158720 30. Aug 00:39 Services_20260830.png + │ + │ tokens 86 in 205 out 19049 cached 0 written $0.0000 tool-calls + │ + │ patch 1 file(s) a7cf6ff8 + + + │ 2026-08-30 18:58:39 • assistant • Qwen3.8-27B-Think + │ + │ Next up is step 5: shut down the shot server (PIDs 85426/85430, shot_server.py on port 8599) and + │ clean up the temporary artifacts inside /tmp/opencode. + │ + │ Careful: don't kill the real server on port 8501. Use pkill -f shot_server.py (specific + │ pattern), and also run pgrep first to be safe. + │ + │ Now killing the throwaway shot server (port 8599) and cleaning up temp artifacts — being careful + │ not to touch the real server: + │ + │ bash pgrep -af shot_server.py; pkill -f shot_server.py; sleep 1; pgrep -af shot_s… ERR 120.1s + │ in + │ { + │ "command": "pgrep -af shot_server.py; pkill -f shot_server.py; sleep 1; pgrep -af + │ shot_server.py || echo \"shot server stopped\"; pgrep -af \"python main\\.py\"" + │ } + │ err Unknown: ChildProcess.kill (pgrep -af shot_server.py; pkill -f shot_server.py; sleep 1; + │ pgrep -af shot_server.py || echo "shot server stopped"; pgrep -af "python main\.py" ) + │ + │ tokens 318 in 194 out 19339 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 19:00:45 • assistant • Qwen3.8-27B-Think + │ + │ Weird output — "Unknown: ChildProcess.kill (...)" — looks like pkill matched the shell command + │ line that contained the pattern "shot_server.py" (the full command string includes the text + │ "shot_server.py"), and it killed its own shell! Same pitfall as pkill -f "uv run python main.py" + │ that was mentioned in AGENTS.md. The shell was killed before it could complete. + │ + │ Let me check the state: is shot_server.py dead? Is the real server still running? + │ + │ The pkill pattern matched its own shell command line and killed the session (same pitfall as in + │ AGENTS.md). Checking state: + │ + │ bash pgrep -a python | grep -E "shot_server|main" ; echo "---"; curl -s -o /dev/n… ok 0.0s + │ in + │ { + │ "command": "pgrep -a python | grep -E \"shot_server|main\" ; echo \"---\"; curl -s -o + │ /dev/null -w \"8501: %{http_code}\\n\" http://127.0.0.1:8501/api/overview; curl -s -o + │ /dev/null -w \"8599: %{http_code}\\n\" --max-time 2 http://127.0.0.1:8599/ || echo \"8599: + │ down\"" + │ } + │ out + │ 84746 /home/mac/Code/dashboard/.venv/bin/python3 main.py + │ --- + │ 8501: 200 + │ 8599: 000 + │ 8599: down + │ + │ tokens 65 in 274 out 19852 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 19:00:52 • assistant • Qwen3.8-27B-Think + │ + │ The situation is clear: + │ - shot_server.py is dead (8599 is down) — pkill did succeed in killing it before killing my own + │ shell + │ - The real server (PID 84746, main.py on port 8501) is alive and healthy (200) + │ + │ Next, I'll clean up the temporary artifacts inside /tmp/opencode. Let me check the contents and + │ delete them. + │ + │ Shot server is dead (8599 down), real server healthy (8501 → 200). Cleaning up temp artifacts: + │ + │ bash rm -rf /tmp/opencode && ls /tmp/opencode 2>&1; echo "cleanup done" ok 0.0s + │ in + │ { + │ "command": "rm -rf /tmp/opencode && ls /tmp/opencode 2>&1; echo \"cleanup done\"" + │ } + │ out + │ ls: cannot access '/tmp/opencode': No such file or directory + │ cleanup done + │ + │ tokens 63 in 173 out 20191 cached 0 written $0.0000 tool-calls + + + │ 2026-08-30 19:00:56 • assistant • Qwen3.8-27B-Think + │ + │ All done: + │ + │ 1. ✅ Fixed the fill index in build() (`ds[0].fill = i * 2 + 1`) — consistent with the 2-dataset + │ envelopeDs + │ 2. ✅ Re-verified the chart (probe4: no errors, 4 datasets per chart, legend is clean) — user + │ said skip further verification + │ 3. ✅ (Skipped pixel check per user's instruction) + │ 4. ✅ Live check: all 7 endpoints return 200, /api/history is 199 points / 19 keys, all aligned, + │ Traceback count is 0 + │ 5. ✅ Replaced the screenshot: deleted the old screenshots/Overview_20260830.png and took a new + │ one at 1671×610 from the live server (chart is populated with 177 labels × 4 datasets, no + │ errors) + │ 6. ✅ Killed the shot server, cleaned up /tmp/opencode + │ + │ Note: git status — the user didn't ask to commit. Modified files: app/config.py, + │ app/routers/overview.py, static/js/app.js, AGENTS.md, screenshots/Overview_20260830.png. Don't + │ commit unless asked. + │ + │ Concise summary. + │ + │ Done. Final state: + │ + │ - **`static/js/app.js`** — `build()` fill index fixed (`ds[0].fill = i * 2 + 1`) to match the + │ 2-dataset envelope (max-with-fill + min per key); no avg line. + │ - **Live checks** — all 7 endpoints 200, `/api/history` 199 pts × 19 keys fully aligned, 0 + │ tracebacks in `/tmp/dash.log`. + │ - **Screenshot** — removed old `screenshots/Overview_20260830.png`, replaced with a fresh + │ 1671×610 capture of the live Overview tab (all 3 charts populated, envelope bands rendering, no + │ console errors). + │ - **Cleanup** — throwaway shot server (8599) killed, real server (8501) untouched and healthy, + │ `/tmp/opencode` temp artifacts removed. + │ + │ Uncommitted changes: `app/config.py`, `app/routers/overview.py`, `static/js/app.js`, + │ `AGENTS.md`, `screenshots/Overview_20260830.png`. + │ + │ tokens 31 in 514 out 20428 cached 0 written $0.0000 stop + diff --git a/screenshots/Overview_20260830.png b/screenshots/Overview_20260830.png index 5ebf0780d974d0372a18b664997ed86ee545645e..943d8603e78926310abf10adb42df304f358c671 100644 GIT binary patch literal 94874 zcmcG$WmsF?);3B@3oY6Lr8rMZad(H7;_ehL?jGDqDNrmF4I12n6?Z8Vcemih0)gTh zIP2NZ?tZ_0ex2+6uCsn+tz?cl*OW2GxbJ%=Tvb^H7mExF4Gj%fPF7MK4Gkj<4eg=n zlZU{Q2)ZZ?G_+@Ea+2>fz0&uWFb#;TmN9?p&Pf>h9JW5e`fB*Ra`pp(zG;+AfziTd z>#sNiHi!X&7UCnP$VpduQC{?yhtyl(r=E4yhk>5XhBlwEnC{v#QLFph7FMxV15Qxy zP&?%651_7pu8zMZO#XaE`{?{C5&h2{iM{ORpU-F*$8RO1# zMF$UV)G`wd`R5wG+CIMgKgy~2k~j+qHNCH5Z-kCPD1Go#9{;;wL+hCK|IuYj(Z%o$ z6%m*7TW=qw454UJ4w}!O=Txrt_A35U6xCV$;$* zch&ckSQk?k9maDWN4{j&tF^^-gLvOI&Uzu}r5K2gx3&sk$5m5#4X_9!3$9)^@>Cu9z znQ6l~B$S3r{Tf#Db3&wK*e0eiLzW1acmjU1!XhCs1Y5JbNl_PP|QhA%_*VYct8V3%-wO_IsFfp;19LUpy_w(c3u!M(LOBq7b>diNyW$m-m znlD~#@l8#0dGdQ~+H}Atm^iGb^1ja3PERMk$j8A^qmfYoIfI(L4$S3Ds8HP)p;1QP z%{RAj#X0yDre=JcXD!1eJK;+cafEZv42)MgCVK;ielnV>=}YVjZWTyUF3nD9ymIbIb3;PVdCw_i1=vMN>UxBSY@VgnB*kZYUwNJI~tAj z7sdNt9GF;9h@0G6-Cw0+)H%){pXnG^hAevQJ3BiUm6sbee0-;pDPVN~w-|)z>+9|z z5L=tubaZd-PES-^Q=%xzy>A<`v)7srTXLyFMOdCtTl1dAGf`rW^|oyuX=_ zrHQqdb96M4ww#fnZVi0RWSiW6zut|{V%!!%d3L>5k(!#?b~YmpH!w8z*?sIpUD4B1 zfV#nVT&M}@xjV@`bQYjsXIHsy@9Q6Nbg;X*z@)C`E;qiP9MA22Lr_^zW-yocv}9HU z@zKdxPp`>wsp2KOkq>ejGR!(;vj&rDy{T(&UzxDLpZwj0UqK@5aT>|q)``>N5t35? zTmQM%>PE~;&Hd{3_p-wX8oc>n)x354Q;KqjhogBS!;5uy8aQO=>t~V*_M@hLUPfx}?CkS-tP^FY zc$*m_8z<=p6e5bgryX{$JqiP#oj&Ct#x#a5LX`JWDn0Eigts{M>?i_yQ{=u@<@JzgRewEOTF7ez0u7rt-SR*`6=;&dSgT12{o9(w!zEv{RzC%k(88bnl=u>n`sYFB+ zrMbBXvC)o>j^$;emACiQPVKp(v%h{j%h1bs%uNpNos7179f$WaPMKlMCUfSJr={}5 zQtWkxM23V&J=f^uzdi3IR1qPjph)=U95T3}oGx(JevexCIdvL8D%J!#ngEt%MRV99 zGkg079i3jbS8v{2R#c#r=pceZbN4GNcTB6;Z`8vpGAmkJm$HMy3YF73*)oYQFY1@G zI1Z+YH8cvPZcj3u#IJsFZ?hZtoebaS8t1APQ#|KMAAQ--+`PHD83x8_^kPgRHMkN? zG3yDlgM5if`N+aEUwu}8et!PFxOx}aZ-z+ltg<6~!Oy@|&7UWUn(W@pk(VXQ{KtrpDD9t!5ZVbH~STI*^yYkP%8-!H>< zNNpMFdq=Dc-oCYMDfM$MOVgi2tmFZConj+m$?e3ZX4@Pl$5E5xG76Dc=}T~{5&z#E z2TFjW6;*PwJM>!bYuo8V)z}Ep};kI);-;{=mG^EiaH}oBuke|2ja^dNDzD#XlrZfXktSdxVw*A2s-(VMM>^~ zxHQ3al=2)2vNu`F;D6j`zRZ`6u6%;XGINmdn@V{rQE3hx4t?#g#TgbCtg5 z`vq}YF=7MKuAH2lD}HB*8LcRCrY_Ys;d(JQ_Y;UTOAV2J&`#ij`LNUM59knAC+iV1RO4Tru>ld)^%`Dnmt}v(BtN=F4)?_ ze#xd3{iINn&CfMiBKvE&VC%;pB2+pTz%t0YEUl@T>Yle*hR@qGLDJLXQWB-mudW`m zx4*_s6FHizBy_VJjf!qnjGSQcJ7}4Aae3P8QG9m@>`7D8|F|hMlox2TG-!1d4&j z#4Nfg&Q}kmTs6naHOCUbY*g~J8hYyA5B6eGf@B0Aw$7QPDEeI87B{8Znh|RFq(S|p zShPdr10MX?HU(EJrCUFAT0!H-cWP9WzYjl6QsWm}s79&C(@KL=G&R4a#ljFNY^;pS zAF|Sc145!LMm} zE8FUNmIgg9`zp0(>Kq&#UMkCs>u~dMdAOXwuX|N+-C^=dulCnBOMumMcZa9*vhweVrS{&lu6R=3Bb|bVF$32q&p?Nc+c~ zpFxbZH8m3;5GP>X)VAMg>F9Va)Y!DcHQfZ|SLS*g)A$?}<>hmND%!8()CI|s37%(X z4D@dvx3^zO`}?D^gE4!-q*m7EvLVy~mnM2J1cd+tL*MZ5@W4QXcBne*u))!h%l*|p zH<^I%))3p-MCjEGcW`-eW4EOBL~XiuavU#N)(2!op#Rkgdt1cUWCBXc_wDA`*w`8x zlSM_jso~^gWJ0)O=Jd*vVPPs@F;Z^t<2R{>zH7vmTkn3*xxab)wH=fcy{M;`%J@c| zX^}TqPhYrG)ht_1Uw^RnILXq?jIdE+e8#8jdwD@b02$EgriKdtT_q*K$NWnH?DEVx zG1ORCq{6O4kEEJka~`}#C0&c%yuH^GdY+m}yk&@;rJt%_>VWi3F`8O{p^{$9dVJDO zb08@xbb3Z2(lfmjKe$<(+Q*of{?4xnWFf`4=>8W<%pX0tKrxd(@$2q|BJH5IXufE^ zYR0H-V8+nY&#&{?BR?rlji8atKETb(FXZ(UEUeP0&$e^4EszDX z_74vuNj6tI;6%ewj}guhL@!^~*VRotg`uXKt#uP$yddi5L@>Y#>O$28V?X?qcFluT z{XLC#5(cyqJWFjYEG%?&Q(9YFLlSAZca)_w4chj9*i05uiF-(Bknq`jj*q9gsR;u@ z(6-C+9qDn+5ro+wWJK+*g$Fu;XPd3RN=3 zskHQsJ$J@pN5yVF`|qvoz9Xf`ZFBSZfqr7HewF<`OhG~ZCA;nvdC@}`HP2IM%r*y{c7JmC!$n*aw;wl(?=*iV0v_*^g=PCXEt}~enPFL zvJ({xu5aw^Y_b!_70t};-#jn*OaiN;$N;kv3vzMdvkoZls3ydT~wkd9F0og^V4>Q7c~ z|MDI)MiDAvGsU2V-J7-ge8msd9Y+hh@f(GT9v*CMZEm_8t>^zD(kc}+S--5da)LWe zIhfnq6NvdkbF26A-RNrv{cmLyQv@?#Lz9xWuA3Il&%y1?jS(zD$E)AhXdLV*K_K^+ zBA4IeBQa6EI$GL&c@yUcccz%edIO2if8<_0)T$A+mn;I3g?C0CCIMi)u(ni0s2Gje z+84P=b1oY_*OtOj4sv0w!Z0F57TrqAxv$?G+aZaG$d+zqYw1QsZA0C6?$xb^^B40e z58l7idOOB&eVFB5?de)xtZosT@S)N7I#pkHtssIRPB6ks@5g2$|A5_ z=|e~Jj0IZhq=pO{?0IoPhEFyNdFq;ApWE6zu2}EnI6U?0@6$+&<(BV)_C=R8Riew6 z%NLuWus=Id`HI8lz~ErMI%DRjr-ryrw#I;lmaddbT;!Y#m0_wJcLgbG3;3nZmlEY;^Gw1m6etE_i1Tq7c|Zjn$Je&8*|ShDl02n zTM^1lFa*<<^5s_A{q*!SyU}e2p-K@HtY@gv?0r&)T=cZ26L4PrU1fpq0B=b2Uz{8J zeZA+@a=GqSqFpvMIoS-_J$^a6k_CJI;spxQ-tv~zpz!$e@{5Ytojr1ao^d?49uuEx zxkj$`cR@ix%_$Bcp@~0G?OZ~Frix0;R05CXYNcVdTIm>kZf;@0?{pO$dM$)}%9arc z<-O0QH}c8|wSzG!Q;BAY)|KXQaI@MBlwK{~T{3Q6X4_O#XWd4wN0ruqF)fx|AnfUm zIt)prexI4t3Z#iVBsPr`O4ttUKQy1@6>I;4I*sq z@D7(|kcRoBj5^X_=-a>-83T-_ZQbJG!Xt> z;NET^J%K#$U=!JTSHHsG$iT#OeVH2rV`4V;M%F$hq#Jv5?{~3e77I&jd>unwn9e3F zd{ZeyZK313u{B&?UVesqc49edq9As^9$C4eX%(tLr&93IMN)O_zU>}L>3-T5+tAS9 zeX@m5oo$;^|M+cMrJdqM zV@1Qq@6=wn|MIiWkcJei?*SJ}p6_1$pp%G~vbA43cUaurt}D~;>MB&NdqK>XVw9@O zhdQEPoHX#()eSI$rZqPM(AZsY*j%NzxA(!pfvCTMhxW{t61k4p?bqt+YC0JjU`g4( z@r<09oU|HAm0~|fY(Cl$wZ>J$@T~S=g2=p83VtG=; zKatLBudKd`i+jG_-Q69?na*Zd^?mMt9=4$l_A1(7F!iMmOay>GkKnsI+P15Jhl(w| z!0KKx3Pgn+wilXzOG-~nJVPk3LyQ8=SkTbmO;bMyanEY)px*9h3lN8WigWuLL$-L% ze+M=tX^p`zt{;1T{~I{}`+7^yKdFs(o!@HsV+|e@*0jucYH{>U@}Do!J}tjWBzJaA z(Uj4FG&=3!Q#79~fNpJrn<`d0gGOhoEs*FC6po zA<82}k?Hxrn}LS*cj4gd+G6Em`V<`0~E{2?1YGOX|HI5+wME8l|d zCMWs1_^KUCORSxAIR5nE1!;sNFb{}g7>HcY#UyrKiXhi4odtxp_(oWxdH-Y4KWhEC z0=dWu*#*r$wW{~7JKN^B6dkDYG={{pN+ z-x`B4BmXqpKuYZ?ezAeL3+WEMp<#JdYsR0ppz%h?GjNz>iT@V?@?WE7tuG)Ta2+Z^ zW!$lO><)4#MQbo>mUsnGuuu;#s*P=wE3!aqeaDJU@~Frf4IQ@@=VR6qSPujBNz<|8 z=y7OI0kNrCi6R~3YqxxU+=EYhE&>#khUH+q+0LHE#%yUOo4wh6zy$vnJq(T~s^e%F zWVWOoZ=1UT6Cs+8AD^B^Kk^A3jy}UdJB`8%Cvr{sY+@d*E{blp@Itn`%s>5QElw7@ z>L41%117SLx9@?5Sl6o$uF_>0H$ISrm|8cp6Ti~K*ujKx)tqf(<6Lub0``^O{#S#~ zN|JsIzhmNe7C_95&mEwjrZtpJ&rMa~6Xqm6fWMjI`tUCm_W!b+pY%fs@KLIlexeq_KKPFtbmeJPvSx4I0PkG? z2hj4Gl$X$X5&0&a&mFZg~DS`|!WbLjE@UUu@O?C0hurS7pMT-53hqnT45=xw(e6 zayD*JT4C;`jt=K}7%z`DUHtTnCO!SnM531yizY$uW;{H`W5al>+5LZ263XBKU%DGs zjr}wqLC1Hzar5`}ZT4tngOGFCmPVIlq@;N3>+4JD_HRaRzai*%xKjPpJQlYZqbjvn zvt}h-Y_8BCvOF-YqOF}kDf%68-o33yz7vP_7NT&&S` zBQ#|<3Xic+pvW+|efY4f)M3+A%H3Vc^mY?KxrVo~loX2}^6^ap$%KK3uU_^y%os8= zOUvgkyuW7j40XUx*O(`I#t*e=toz8n>$jHt*6E!_KS-3#tZ7r_f0NS*D zZ)S#^pHwH9j3cBrII=G6mZlW_@ZsC2Hf^0GTRiG$FJ>WgZeqef-;na9Z+2xH-Whf% zherhwwQrCIk9~M#Nm_m2YUTSI>$$E)diw#;@J|=W$o96Sr+(ordhoGpHTR_YPFZ-$ z)p|ZN{^5$Dnzp}{5ghG9xdtlqb@E;h*%UC^nl zOz!6lFfmm%7Weme*A`d4+L=AVAQP;_=P_y}zDWBff?~@TJgFpR%x&(O8aT zld8yMuDW`+bv-N!KrS??y(X?CR7L2h8QGfJigEf4010Ls!FpW9&+(wt;zp_WhO9~) zA_BkGQ#62XiYb!>8Lh1!kczqYY^?;2M;)J^I2Bc#ogSO4p@;y%z(2YGjYfww&F$Ny z8NTO}mX%#B=jH)kzhyPtPBiGjHtgvz^%U))!YfIv4aF~XK|g3@V$x*kIf{%qYQ{$x zoF!$IwJl3#pt8ADIk#iW84I*aGmPAB3gp7Ve2H!#kfWWQyR)l_h9J4{O@#XNr*byW znU;_jn-;NQ=iv(r25>lih)QAvZK`cwKvG*^I@@#EDEC@kt`r`2dWDOXqD1 z`AGiYI3pX2^Vx6H6<2Q&eczko34-BAG!hcrlN#y-gg;Hy6ci=_=Ky0GKo7;VVgC|$ zblsF%6-SUAPMT}GOQWK!n1vG*+Uj@v)4{<$SMyVL(8%xV35{LFV7A}ZSfZ`|qnThA zkVm$L|5n|4(d5ZzxPA86mv^Yo*~(0@=6Qut86-XM&&O5jEZPRWw5bL@IRN|AV~ZdF|C?1ngaluI^hApgh-QP@6_x;3P^(bwDSMv`h_ zRb2B zauC~%hJEj8SV%rEWU!=^?XoB- zm%hv?5EE-Fvy~4G=L{4T76J&hqI+-8h}fq5Lt~W~w)qJLX1lbBxYuM^{@Nz*+RhDX zs;ld2_sB|py>9q;`ReNGY~>kt$@6WSn~a&BbECZ{2-~0*Fpyl3^v{%gP0Y*Bzp&^@ zLEGee^=s3d6U}+0i?Y-+`K?ODqyuPSrqn-3%`l7zNQyO9Mn4TL4W{v(rn7DOTwmwJ zfoTfm)_{D+&t_Hgv=UxAq<;_0Fv5ImtdN|R(x1~^-2BX4lg(*eSIBj$hVy$Ga@Mb8G&ZY1f?GVey%1n`_ATGI=w69%Zab4y>#h zbmN+9Um6s2*o;llR>eL6^D+KwH_^};Po3T9Lxi-@tdv6n83%>McK?*aI+%KX6m$|! zp!5=>P=%^+w)85}aWL#5yjB1P;bNy>>~$i^cXfms$>1Zat3Ah{1$OPO6lZ=ETIfOc zD%9=iLm(QnR~f&o3tbb$R6ownsa7zK^K(3ltjOs9o?*iJp+%7?-iBR7lvmJvDtRi8 z8K*;ae7qt(J39;lKq+n)_KQy z<7?U{uJg5gtAKZXQ|U`f#=tgIxTLEJ2$W#*$fr%`%(L+kRRsFNnBkHJ>N!o_X#q`* zzG|W{26i+0Z}PMcTlaigCT4Yb3kZ#)5p$!it}f)fjuIzl8E`2op=TsSA~pjxeZe~= zR0r?mX=QE#xP&&xg?4v+S@#_K6^)dYfA@{oSe>4pPxFM2g$8V*9lUP{KEun!j&jsP zVZ)ypYI5>4%mfC3%fV1+o9t90vL-k<-hgdVL_}8K-J-Es`%tg`=>3BOSE-OI?HRsv zJY}N%GiIW`{sGsE-^Zut{iFQ{JP{H?^g8@V0xKO)BzXG+5k(Y*XcAs$`3uHGt`xR_iyFFxkYOdwe1==JeB3#;?t#ujSP; zpql0X=%ox1J|rMC-L-%7QS$p+m6bkO0oPLjJN@PaHdc^)1h#%!nhh7m$D-V()jeu! zb943lgSn~6UV3qU8E!sy;X#EZw@!yQiSiTbandoFKhfa)09_SFxFHjTW8o<8u9cr1 z`;C3fOn*`C=Xz2SvA%xuaU7VqI0a@^N1}+8Nyo#P{a!=GseM(=j9|T~G4o&|g!|pA z>>9`vTi$QB>$%u(W1Kw_8W^k4xWrgu5m%y^LLqT3Vk}Yr+nt@*M*m~is-CDcNr=1C z+%P{HoZcC8k8);a#>eaapmKwr)yMj=K7s$OY|zY8Mf#d6F~5%&4dH8q^ePSxHbA^8 z=zVKlXafR)(hcC}MonW#f*sq@AFaYNF_hwFBYt&xJ_4=JB|dwH(Z zmlp&xRRvz0r|vmn4-5`_I6K?g+V1n{IJom>cmdlapY8r5AAKDi-_zA)$BJU3CTEYH z{l`GCxnqkN%2Y?dNEFeOiioEN^YHOJdGwH6IIO6TCe?-kw&4sabJ&M9i9{87rUq>O z;+m&!zninMpGX|UFY_Ck8OhFRNqEp8{>C``;DxG#K%o&uAXl9G(47qN)2E4G#EVcK{ z?93a(@W^CNx0MkjYR?yY6)dl!wC?Kul0~hvoL3Tfe!zmzd@HQaf&5$TP7?gft0@yhWC^} z`leuQyXOmj3epKgoQ@=Cv}30^zxldG1Yp;9ZT z_d8!rS=rw(8EqkfxwPceh;Bg5nCiC8c6Yvb2<+mJ$5@x21>LMLFffFKgeF=rFs=m! zy;VfdaD+X2qHJQsuD>QGTIkwb;ft3ydvR)W=hoJqIkltmDk|xeaiLsgVf-_#nCKr(M`s5WC zGvY>(iXtv633=Wg!Sw_0(=1<|KF7t4K0CvOuo*7Yx~VuAQo7Lq-kN(`^es9NMr@d| zpYo&x1)2Pe64d77mKgv4NOjH3@K-; zFMw4690Ip!V&ii?Tzn^LeP0=~Y``h2W?MViIXKwb_l@?Cj1JDQr1Wm59S=Jok#5yo z#pXj_DAm z32|L*U8PwUor+$iX>mqb85C+{6!4>eCt0-lLe4^ccz;yjnNbQuORs_0{9YI2|aNY8hd_%bmi^fO1hp7sLm%t)ZIw`a<-^f~}<6mRmr{gPgC{IoU>RAVqyg6!Gyr`T2Ex^tB8#l2QYHcVwSN zDQ*!v3#1d$gU3ddH@&Z}R7BdexrY%l)F}kd9c_wXN+~HRn0OT6aKUc04pHo4xM7g8 z{EFYn*;B$EPhpQ=ecu2sHFHAfoE7&;h=ix7r#|^!VBn>k${@(u`EoTlu&JpDBs?>^ za(I7t$a~I|Q<665dx+ZDtsQ;&g1FLbH#z2YV@r#Oq^t8vQ=<<*OUL~%|6R;y5u}6= zdG^bUSLyA}ujqv#@?a?{z`90VJ+&agJkqtuQSU?Y!}9 zOHaveX>~4;pAoEaIYkPD)JxzOPfn8qjtoVe`OGf}r;9xA-}PqW$FFdS z?_@lgNN{e1JsBE;!#fA$;|KEJ^u#_(T29sNV&Q;iX2P`BD*cE3T`fOb3;_1LS`%F4iuKM*|OJ%POY3a z>tCRBSqks$W<+4#d)s^OD@eHu+K%e$?QHFe&F7E9sQjEcg*|@$?f`@gJL59cR->PH zM1B?1Qx^sWhvCu0p*Mt$J>T_Q@wYKG&0);mFtW0u$J))wRjGPe;s_al4Rp!8?v1AO zKY)h}3@~AsaE)yL!2+@}FGcPKEAA}LOG#4+n1eb7CQMIvRE?=CV#MyEI-nhMA@hajJFND-j_;^5XT9qZG?r(x)oSb@a z(Sk9G;ThYT$g2bKab&FV6U zUEZF=I7|(jsH>}Ub8!LeFQEOgX7l*`7<+5nCeTBBM26b``drvab>_Qyf9HL`5pOQu zzGcxN4DjXd00P3!%InYzHq5|6M@OIIdz?Z_x-{ZWcs4RMb-LualKr0l+`+{~&dAY7 zK><7Cn<}r0!stgDLDwVGQja}N2cFp`_x7E=y~kmXR7k{?8S=*DWZzxcRiL7GNg=)N zE-r;Zuz`rJf`W3wF_VQpHM20x5ND9je#6_W!ifG~T)MW$?hb7Wi_K2tMT486QDnn- zkR+m79^PhepW~~Co+^j8@co?6w~pIYHFZxTlgo3BqCK=C!&4U!OaMkosKKqkymy-L zW;h{bw^)?7c@XfP1G3y4Jgy)Q7k9Tpb_*MejScE=ezfI_yTKAN8VGvGAi)N^r=Kng ztoS{z;j8vrZVN3bnJz4v_hx)?B^4zl#pUa+jU(lbHS>Aw01*JnR6!IP2c$VtJkIj;e=|M4WRj89v3sSjf(cW5i|hiLvpKB#imQXZgYNB z2PkFx`}@1PkUQgji{$;BFvH~ANtL@UG&l=@&Bw&N#FfpT`1LEHs_OXLw~yyM4n0G= zphq86RY?flB0dX-NI(-4HPqA`ZEgAdywE5gO-v7`a9e@oH+|ZFpenc73_43guOEAf4^fU z*C!M3`RJs(>pr}hs-HSfaNV}i)w#PtGJPLuQJ>lOa>v`Jgp6Rz(rO@&(o-I>>u!+K z*RNQJQ@Ll5P?6HD&+zM*`6*<6aE9#w`h3!=SgcR|>>UyN#AwsP!u9pS=N1b2%^_A+ z#u$wn=7*m@M@L2J>+5L{5H_);%(Rr3&%oiY|Kjjs$al?^39E{Q7i(DEU5#Ee8&y(^ zdJu)A@w|ncihJGDkUb+Kzpq=f;^yWS+II2qfR&Zy!lMU9)5R1_eBPPFW>&wu-uMX-2)QHNu$+HfB_%PR0x9@lshUZIoR9ZHo1%2 znAXl3`Fk(l3QkY+DnQE9MSZtg8mmQHTa+^e-zP2&PR3_!4fEf}0&;W8x|$l*I^#w8 z56Nlw$l=-6RuP-YcXqYzdtkL(2|yk7INE17e-@C&AdyHuNBCPOef`C)VL-6>76g)s zKS|-<0^%02m_VLmG<`0M2v{Sb;k;@42LFm5H6aZUg!Aqu1on6(iga>b6I>PrdT!Srw}~%S;{}9fQ0PIo6s^lsox58%nwVbWv%ZMc z)#sq$hSHYXu^%)`P^O0+A0?PP!xAAnKhs4-1x5BZV#Em}yO#=FZFYCd9buyHB!3bK z+2`!N>I%IYUo0rts;N0hyBd4AYh__U>tMzBiG)wFmOQKV>y~SDcrhX;UlWF4ju3-1 z`nb^9ALl)P*JZ(Vr6P{%M8i=#*%~$(m18q#kkp}R&M0U<1H0DCnNVK19>6P`H880O ziY1$UOU2X~goNZvii>ys?$Q$9jSsF0ayNT;qIK-TvuD4H|6J0}+4oe`8` zl0YU}4Jhol+Ow&v0@4Q0TkO0B1BcW8m3Ln5Jxd z@W!U}utA+wKxi7}iSdA=Dut*j1ewAOA5k6ADW%upC!DxjAeenKwr1*u3 ziZX-V^jNRQko7q zeDB}WG?8>{9yN*4bk4MGSXVVw<>a*7h}5u0MMohi)4d)BWwZv@$B5?!9KYArldmV# zUxbO(7-ko-w9g>xp#d^Xz32SBwhoS9F_(myvc=7Y26u|?s+hiKB^BRJy#1uzPn>$4 zV!*S#$~d`StD9g|upIjlbOja`mTpruJy&(x<60W8VbA*NdTOrX^m+*5OOmpsbm$}0 zws_`uq%@;$Zse7H;FB^P6-ySD@{AO_)EBB0s_tiR(px>}KA@^A8^cFJc7A;c4K%no zN$sV|5Ho8Uo8w?+zKB;5%Z)KZJ5Vq(G6D&e8Ri&P_x(T*qw?KX69SSSu4GeoKG{@I zE%wIF4hb@R*Bn|t4i)w^Vv1Et7J#0=OTc3>u1r8KemR%77fVcyBYWW=QO>QDN%rog zOR=Na(a%(6Rv#m;ceUD}Azdx4?ogk~O31wB_rRM)V;LNLc#DOv&93|6y;LM|RV*g* zEx#jNB77}J3LCz(l(NWVDld>5@no~R8|Ii`w};?)Dm$x&x*h=NKX={0K&{A|IzBb~ z{aP@Wh=}OEy@R{JP~X@$AVAlUb9F`3v1V+r3W=4L!@vp+0EVjQ&7a%!pwrt{^5=N& z(kV;nZb0lE-MqMh^ish3c4pvWaT^0|6WQqe!rb?>zHi{B^79}&3x~1{&q*1LkHGm8 z0uqw9m6W*wU{ci3z|aUY14&8SS)$s~3t{SFijIsB&4ZEL66*c4B&g3tURNr__-?O$ z#b1hEP@_Q6a%x-f{^B}Ku74@>vwBt!o+1#{8<*&NVq>4^82bx~islx51q;dVI9m-R zt6`ougUNU!)ZEcEuUF%w@bJ!~qodX6bJ}hZCt$004KV2AqoIYDW6gXxxX+(!%uNOZ z$+djBZ)$1#*+|dKIU;I$-@-#E-DjIRa2C3?=oC2Totq?9<9OJ9U%usFXSZ0*}&vKPH{(x^M48<-ju8-tG>9fe3494_#QVw0`A#Dgx$sP57DZDnva&Z_N0!JxWzP^6H9J73ql0Z2(oi{_UWUJ}WMFU3W&O}Kt*`S1fxe#o zqo*fT?GPYuAaBBqJHD;$ z?kZOp(OKI%ig}*<0dY1gl#ovGjQktAR*?wDP z6c1C$WTYq8k_22TzhK+TqGY=RvoL*DjRo+rtTiLPvo@3giXTb`3y>TJFgT)ZCP@oe-`$z|W=!1pD=HJK-Q#^ny zW;EATmGPLe_l}RV;ClIZ-_{)XO1Iu@9XE&?&OH9~E~~4lvbylCYA{P!oZG6IGXqN4E7*mS^+^HN1-LanR_#Ko7cK^RQtQ_}F$NudkN~RHrhHmk!vjeo=H@TSZVR~S!CmBY z`~b*4()e!uZu@>92IM^wj5~fiT8+a$F0vXr^6U8X;Da03{%W=$3J3v{_MS)1zPK6E z-94*c@1cl+H@oeHX5QBaEP3)|?aGh#ec0JwxRRV5=51)DeE%> zFOZP&@z_(LfLjjoM*?$;o=ruH+qVm`6xph)048@x?SIO6^Be%8L1lJJO^5VG$hz)@ zfmBKX@7fd0CXS?x_%68P-mKFOU)-J$%-EPTlxYFC^`as$ilSHuLqS;?g{-H_%ztb3 zVGzQOI&Wrg7Hd*0vwz!HV zC&n|p1K$0(K5N&}(E<*EIWA4OC{Q-nXGO9Ct5{7IX4V|{hiJkRk{QU=r`((37J zHT{w%JHGG_1_sF$jV{w4tS6AC4mrdgp9%v$IP`KPx*U4V z!;mY=dnJkZy|#+{#IvY*Y%jlnW(gO>&&sNj-ddi+Y=2?q0fpVu#Mk3r1|d83R(6Mf z>GYYgL(MWDkI#*%YG|dz7u$AOTRJMw=FEEw>Ik1s?X9(+LEkqMD4_@4-)^_>+U#y_ zzE}x35%qO+vYH)#yhiTSM(BM?lHoKdCnasU1%PO8jCQ?N*?l+W4wiiRZqH@hl~^;F z@(Qh5&S%@pbgLr8iSbHqU{dC=}N-APT z_wFzLAJ*PFEXuEa`^ElBs+5w7($d`m0@BhsARsYx4mltqAT8Y>-QA5K-3-mpof1Qb zd!fJQdERHg@7~A$W8a7WcpM@#bI-ljwa)82KbL0K3Ok3+7OFC%X2mC2T2BMSK|c7U z+SOK9*sWPsBIV%n;$mWL^hMX?hI97sr>3Te$dmVGh32B)@$niksFFX1TyPge!Q(Qm zwLuw&>Zg}f4Ed(JY5|)&bOgkMfqUmW{Hz=Tfcw(xasJmX-Mx!*aJdGIGUTG ziiGb<0mFD@TO3~t3X2yTzAlxys-GF3w>52*N5uBKeAk$lU5_cKGKXuWZ}ZC6*-_s7 zZuQzt^+-BV;c7K*{MHotwt=3GF%#``&&qJDxRaB*y*(zW+E9Ra!+!hL)egBYc2eQ1 zSHtwL%_*i0I=?<{t=pL$La5=h4u|TC<22;Xlvh>;8+0V?J~FR!DL1y~&_|4}ls2d+ zvW`y8lo7@il3`3uPGg`@l==#iHZP`S;{)RBlEz3+;3?mFdZYq2ey?~^_N*5a6u_&( zOR!M(HC)xW{vus@0S(7+l~Cl3U%pY9B8V4OeR)3i6-CTlU$kT$+d8CCZ%&DoPE9@J zXiWU0ix)eBFK*)*(L>@bzFmkR`P=E)!jC?8&tF1KOv;Oyvor^-kF?|ZcQ#&m(s{2& z#1HQQeOt!09Vm}XX}dCXSDT{B?$di6R+~~C*}D#j@(x)Q6&6=T#Na$IF+DjUR8QdW zdGHA8i1vM_GqjV#j4Y$4Fikv&gbl`?R#?zM5#pFQ+&$%QN`qxRn%G_9jTi3sRv-3D z+)6Fq3FRZtxOtU%&=b7`AQ+=_9=h@u9JQ;8)Mx6b z({}yqwzBvSw-NT0xP*ir=e;~f#yN6X8+Wy?lpF{~hKIY+kW2Lq4I-meF9t{31YD0{ zL?C^+Ak>?D<@L*;0U<77wASx{ymxlG6cQUd<8$3YLQ0BR*uFkPhP}D5QCFRPAgIDk zxhtVnR+w+G{e^100N4a2FeZKSgrD4#k5Gi3`{Y$mL8{X1Bu;36xJ}|Pp=@`wJ1&>kUZQlRHugSxDB&~p#h)S zHHGJ!=L7*b8Unv3i^>bM7jr+ZIqmIvZwq&g9=k{#VGYG~`S@Tu zRQcGG@p-s+AVhB8@lnX^&91B5fJK2L)!qBp1|w_yaY{B=Q)vY{SO28drWqTnsHiwN zaJWwO&XV*g{YqhHa6$X7^#F$?EJuFCcpy$pB}`#We=5|oJ>8EK=W`4Zi}wt^;&Yw6 z-MegDRcW*~Z{KnwRlg76W!ol06Rr7Wt&)=yL;m{ByXEVTe7Q9h~e=(Bj~p)9@A{@Eul__VH=w70dPh_#} ztzP_m`*?Y9bf#5(hBLh+B? zHa0f)^|d>|JM5C>vcZm3xzW}iwwV$#>Ljp(p7}$ANvhho^NFsqfkILq+gGF7vW`~9 zMY9Sepcbr}Rpe-QUi9lgA|Xs2?G+W3E-uc)*z`Y4f&rMJ%50+zam5_J~2}4 zmVm|P#z9O?&8Df8sHmu3iYcM>>h>9a54pZF79vVXHtUi5E^;z)_eBN+O`oMzQ7!4U zH(?T=JHM2K+FGcDD(jsk)kan%0(3GT{C2kj$zHW$Sy@@UXF?zy+&2N783kI7^~X~^ z<*vLgDD}p3cMDvl@X-*#7P)T`5lE#uZ}>hq@G~``^hH|mE4?#)eO;}KH~rKj3d$Pd z9`Uvr6g@*YFy+#2w-q3fNCDRqVE42FqBCgFT3#QG9|38SJ#k7lnZ=@3bzp3AvZiiy z2M(6B$tU#m^i$FA3>CY_K}l(DZl0h2I{LLndaU;``SzhsMFD(OQXC#4s;b&?=YZ2< zmXn7+@NtnBaPkny80 zz?3+@EW@sa)ge#xNL2&TtCoN3mn|JslNgJH$9}+0Li~9*^r` z@5e9F>}zTXqEu1(=L1t?!8%iv_EgTh_9t0!z^n%>PW*y%$l=_=Oid*@=TzM{-;X2E zYZj3?;oFD2x4pz|i)MvZHdp(rA&xk<4l3R-Izm>Ld|yt+C%o?7=u!KDXy%v93V^hf`)7! zdf7n}X^CTG+#}YntdFJ>|dXE(|e@c zni?4lfLKhmaP#r8R81Fn7*cH7bl&d!He)CdPzkwFi@?<>lGEKDeu8+v+l2ki8xb1W0nt^Y2Yf zMd9JV>$F*8i;o|l0!RF-d|Q+&dH`Wv-&ML+HMQS zy|1T#anp8(gv2D)zVsD=-?U~t+puC;*)RteblJ%G{qU>EcZa2XPuw`+7({?t()>-I z_K00$O+I=1s_Y3an znSXY{#WReTT!NPoprXcNY$ZA1OZeW1G+}PBwx+B^& z)V>lVr_pIQ@ED6B^udXbSZ{G@B|$}{kzqy_m2Mq{{;zCaUvo3Ff`S}T`C%K|sPEbjxhO@J#=6PeoMcr(jRFccV!|tCqp#Ul< zIu054M^pWS=FiR|xJ!Y(y#s<*sTKGQ^x(TZS+yOG$mGun&0hs9H+rHNFKa8WV6c&2Q8A5s=HR-0#w&WELo0C|87t)XT2CPY{q1%2;8-&NNal0rPSR}Mn6PZt?= zxyK9NKP7s0c6!d{sA;|a)PGr1OG`1V2xK(xn1ct;{FR!vb}H#jPPcjHPa?PZk(#br zcvu98^4Kr0+nhP`u;xV6i1sqPZW!&FpNyfYk!eRm!^E_*Dgrs`gL{`6YO!t$BSgFL|b%t|IfHjr>Q$=m^S_}@5yQnSeY78 z%{i+z^}Efy%_nk(SLZ4DjsBju+85UMoLtVusd#I>ssbb!?yL&tZ8)Z<-h6(8UN0Yl z552c#)m7jO`;KF9bMF-6qOiDc^!el=CMI)6DTK~Ksgf?}OH}U%5CQ$K9|aInAMUJs z^32wR#h8gL%^yM~+j^giFx&~c$j!vD6$9_;Q_6#@zxik6;p+m6Rl(wApKzeo11yJ> zynNG1S7v|0*79OE^0t<%ub8wNE80oW_rW1xgNW5C1|4-vb*@_B$ zk04i?>omtyUA1fZ-i{~E^GD0l7K_(E6`1V1d2xa<_(9nB)C3w~1Hpj88H9?$CKc@n zoIahF-|>gJ5R>qZsngV3Q&G{sEI1++T9Q#@1cgGBAP$%7!m-NT;ErNKfcKp_#jgv*8Wx#!)9>U|R;scE1kXzPqaWma zVH%9*wSxf{r@I`sUC;d*E=$xeY{*RLZpoJX&OlVxAkGzO`XmuE^B%8hY|2BnukU+4 z)eM-P&#SR#ls1%>_JSyp?8-K2Lb_s3LxU0!2^uO&Dth|*LBL@$c4MZ$@w&sRVlFe| z6)Trwj3Y6&;6;FY$pXTNiu$JnsQ^1LHJwT>uuxMx`*h(4;=30ZH%=X^iLbqZ<>kx4 z25EpoDJey{Sh}cBPd_~!){SLrgWPZ8W8IWCZw3@bW6)SMxtSRuw zCkznVp5E76u#LSOK4Z(}BIySSP_o%7SKxQm*vN6_(Qt7&H=vTUwYH9W_sY3O<6#LQ zN6*7&9IEoVzmc>mce*qV34vE&NdkLtFJhyNj=p=V9NP@sOh+3#!u5^lXo6&a-op2t z!1{#J<(^1h{BtmnCwa~4J`i{N4dLrmXS9$xawU9_yk_wn?n=(;eoO; zn_HW5Vq%KemTU9uE|+gbMbk<1Ioax?UO46r zIWUUDtCSQ)>Tirga@B5<^6~@YLf_I_ySjS6*4ZZ|vVfF=Z&pZfp&W>+q6F`*qgfY; zHrYDHL9yJte}?N z{W6Z$wwYB?s$*ciugHk4(QuxeEVLxsE>26c1L+X>Oof*``Y%?5BA3M}58XlW@8ZWI z3l#*P4Kv!tICE7N52}F_LZGTj61=(md8{ly{oTb&sek~O<(b{dSP-5fz$GEx$EShO4jY-$qFL8FPEG$!m zoTOsIaTl}GYcibsKa17Es~6I;YJg=F3nKay_AK-a?VRQc!Ku*qK?pl5r_ovl zudT_i{s|0YQIU@KJ|^gvWX{M^!( zH*)|~IryV1CqKP9rzAA5hmD!Fw9GO4HPaS+mcvZN3=0ih6JHOJ%cHjMdW{H;F*TK5)gH~zA18lwkasxi#XQOGv=tv|#Abg
G`ot?%4#Xb1nw>zbn5*`jklJB0qZJ2UGR-P?VN5Bd2$_j?C=V@lKn44kh> zZTkWD3>^{w=eFRmwvU3TBSIgl52RU(aZTjb6MCWVA(p?*72D;q<@OtdozD4b??`&sHP^92o2WEosbsBKi70G zzm;5lK*5_I1H|kCLq(fJR=X|Zc{aos_;_h(%_UDmIhmMdsJ4< zG3jXBXk4+|wpx?ZR0S%)#Tg9kYutv}psFU-Hvx0cc)bcVSCi?lU ztFPH#z1}df1ta%E;I15o5rNnT*`$$qpGj6 zrajr-)Lhp7{+`yaV$aQ;m6^v>V5S=Cy{cbh-bD3xGjtg)hq`u1{%SiHu(Jp{>0mTV z%vDVkn;x%m%sJbQ42sG)Tc$oiW)QTOTSU+UX^qRX{)`^qNN1h7xIz(08%@fg%6k0Gv#i!t=siWms#>MXvwp z`Yun>(5$SiIi(*q3im$z?Oq@+wE7^FD&R|n$dZ&0Z?JIgBk*l#`g@|jj< zHg)1W=xD}!?*OT(I+%2S_uyAl>9=ot_{oL=cbJ+UucA^2DfMS zwi|a+T08A$o9ZPGvl)}|1tFvqy;K=c-p%n+9-Oq^`<6kSylK`ss7$_+g_1o6P9c4) zmF~OBak3mcm}8Bg+}kMTLo7vJD3LSt4O%>}aTtp+tjdEyg!T($LrC_PV8sIU0Ak zo{h|r3-yYLiQ(kZFNr&vyBM;fmcnmovT|~2=V?GE;jM)_JMs2NhL}(WKeY%(8Rs&` z;jH?A)Z?%wrI-jm$VcMWvYE;v&hNCwY zNqN&-q*PVjfLmww+!#+3#xmzvu9dMRjZ5Wa&`H;sk8_)|q^GCfp+^o*ml@vkOBK46 zKV^)2`xd4&C{5gN4oTEtkl>~r=k~rTUISr#7(L^!Y?0;{ot2(O)pTS}zW4vc^!X)nb2=?CYeZAxs#pvP3+O^J5WmVPqI%uQyW}Xmyur^_> zx2GpT-rYugdMBFk{CfaJ^(ZXRHWf-}|CR}dLny`BtS~tX{x>bxq)Km=UIyl!g`-ll)-Aotu}}{e4O> zEN%`Ntf$u6K2mL{8aw7e*rJ}Qc7C5K;oj>N1Z_%(lBPJ(@HBfJ&r45Ld*xc$+D^d> z%diF-Ut`L1PfCjY4rx_(2y!fS%5_$2R*0|X{%ST!36OxDewk#**4&NrKo1N`PM^>C zkrbX>AY&i#DbZ^GG~R8iTNb?(cNj$W_tNtyIX@h&a&UZCv)g#5i8&C${7J_vKbb=p#*;{m<2o69YE~CQ1x-jB4$fH6>4S0uEX)l*1Ev z!X-82G06P#vmKPJ?_Bq_fxTJ$4kGvsaX*BHZiAAZ$E9^Y!ef1?9Yc7U0)C9& zoM>$oBljCw+ns1_et0zNoq@#bvP$`TiFCc(o<8Yx&fX-qPp&7N<9kmJX;=1E5CQ?m z{GkvMGMWcwX6cnyJp%i)O^4$$Y7a2j+PQdfN!mHwI2a8W4|s_W&art6>*Xz@el!4snzdp-o! zV0^QLWxr>~&uL!MhQl~wfA8L2*8SzPDWrhU4^hl?O(;Z$=2nX9#5vRALq~%%3z8OE zk}I&y>NLHyY`^Zz+e<8jJCr?w}C37dot5PPlbsbA=3(X1U&b9A)&)8n_Je~dFY{p)S_5wrFa~o>S zt#gTL(QGq(HJhVCM6V!gv|tCPw+m&XzxETin@N%ezk4HPqN{8dt&b-qa$aWtT*Xxq z%Kr)da&@-*zCO=ZM1)XCSv3aWwhTW>xB_p(86xo3W9ZjK;ND#^*ZJVB$rjT9D0C&V zyp>lSwo0+L9=yLj)7U@v@#EE994>)PJ*7LtEXb50@RzKzs(bS!)X<)PeN5)e?Cku! z-`aG|=BUh6yEPDcH&t88?;y&Yuwk&*TjRv)pCY@w@Nl)}a_BpyrJpAulw`ERaUvIo zeEWXh+mjE;XoSJ#<|oI;-PMW=(dL9jDtTN!5o?XhoOFF^97|fG$G0naV3^%z0?+EC zo?d%&tc_dC%t)(1k5UT*SJf7%JdW@J>46!nX|4`A_Z&%wd zdNxPZ4=OY)_KHT+guY9;DNg2U7p1AeK&7e4XFs0h84W3I8zRi0)hjyg(9t-*Wkw6H z@!bg$C)80}j1r-;Lzlv~@&$%>YF)@=+|AInL%&vS9$bv}EXBn6^qA~mvCOJe190o% zbzz$05j7IWFZpt$xD=xGbSO!(kRe#^tM|oMo7=~}bAk3PiW|$j%x;5nn#wR-+VVU> ziY;4KQ1g|@iY*O;1RXmEhe6I@@Jlw(SK|2^2h!hjkd^S`dDRyi#{*i)k*16WwxnWo zzk9<#S3%K~oODYITlh=cErrne&iDJa>hoOH_U>YyOfs-s@bw^aZg#-8sc-71>{Icj z3d+V2$tkLSp$TL|=LL2stWIwFTfh@L7R&E)qP)O{R(fw3*v|tFfEZm|;aT#8@9LL-p=!%Wv>Ii5cey`Fr!k6y2?a6HT;NhvH|~l;)iBm2Zj+@r+W3acaq6Ww;Ap z_vK;P#S8pyb=)ISC{)3&7gV=X~OF1=O=^x`$~05G6lKBtVX8; z^e>1*92UNJvb~sS_77ZD*V1CfjO12v8^tlIef)%ZxH;|;H986wtSDtCGvhXIdFpo< z_FB~BeTX(I7H#$G#CGi*fiDxFe2;i2j$F zdb7ign}%Rm@8#}}l$5q-u1WCM<5GNwo4tc{VmMko#_a{W%LWT`&|Hg;i;AMOpxD{f7ifvs2T{JJ>aev7KCaU5 z8N`V9?)mh*juvu}rT0FYHq86qy+2~{+%=Y8@C5_Sc;98=qC5Q!r(3Nq$#-074#qMk zl0JDOmJ1`N1U$dPYGRfVpH}xRFMK5`yc5CKuX}Zit8nUOX|!sl$d1CH?HA~Ata#7Y zI;aLB0(10haySIu7Bv#s^LbE)pW5w|ezYX3?mQyn{D&&l%J&n&kpm;Gtlz$2jHRhQ zRWxr$zI~ZNs!W`t?H<|ji#HE*v1Zz@-i|g=YH?yOX#O6uTEZ*6bR%02`uDyy?T;J7 z`>vixU(7b{pvHrh)zrX2HOnd1oniMXeRbMVpcL6^3&N#jS2SdGFH<(8?^Nzy4`EjZ zf1rcj*D((|j8{t)9k!z4fSzS5Mu<+EPdpzHdK1^8PX@yHscv*Hbg(%pVV-;}GX;CR z2)tIc*gIh+ieITc2GAPgMM%A3O-{THX0@%P*q zeLQ%6vKC~o@t&2P9o9;7Fnp7l9z1&U>*OS)(tAf*u;SkB;hxJMJ%W8ZNV+`vi|&w- z+p$I3g#VRCoV(w8VbsudkciHgv>RU#4P4Ibc{ddNhBa^SK~L{MOj}#SY8{GH(&N_* z5~0e(l&x;_#wP8>W`mW%o)ecTvm7aZJp5WuVys%;+vX;duFRAKD%Ees2K{b_VR|3R z@{Uig*gYGt=WiGH(s!MowU^Xba@w&e~#kw*XM$C>J%XFM2O zn9t|l?m?L+z`9^da&n{gF6Cy8gn#(~l*5Ffl;yn)DJ7XR8Yu*e3$-OQk?|Dgu1r?{ zNVbW?7;%pa>wO9I60%g%Od-p~_ErqvRj;q$llJQh5CK0qn~V&?9uV|1pV()%rME)-F0%_(_>|2W%5L9$Zdn`WDoLC{V76*R2p08 z#@cRA(xvOYb9M>gB$U@F81(Pa1P>4Xtb1*_Y<}U_g%QqHAQLHn-Jfz6^GZd~w`A`% z5DO$&BNEV;B5C5Ls%*>EG6?F?&?!>7uqB5%TukgfY2_^R2s+gwC9`hJ$I3c(HdgaF zkg%VjgoZ;{#2a7%dp)+R=csR%i5^%Qdx0_Bj*P{PD1G@-lox+{`Q0wMkX*|CxZ&7x z7c%=hvuX`T8+=PUO$IGz$EIjuM+4sIA3wqcycTEfAiXP`UM$u9M5fal_bXSirqnnw zOZgop1{>A%W$ni&C){szEql;yLX$F+8M64^Fqz4|kRv8N{o=$8o;H^LLbgCmu(iV4 z6jeoHDRg%!^kgmtWTLfn3p2v=v*b2-sp?1_BVkZ$FlwnQ{8Al;7^ zJ=-t4Vs-%4J{21c{wxJ82gk}{K=8!;#11SQ;s;{4VQ=Wj@8CyIjN3|B-`O5>y^pla zM+RzoW-^+~_xxLX6((@QgM$F99P=a*ul(O}iPBbGh&^pF

8v8m5g3$vVm=fFDy)3A?sY?KC{S@S2=an#r;&BH`Dsvzq&N?%YvR z7%6zp%)-L*LfB>YRoO&a5=r=hSgCPsJH}G?O2btjU;}LPWPm0KPIY^1=H?bBJ@?q5 z-7Ar@Xw(}|`te8N?x#6Zp=Hro#OMaIFnQY*@6D;M)Cr4_gzF>PTI~;CdPV9&*J%WW z9LEK$OSe4N>8x!AH=lUu+vKH6+~vcHiznoaE)^Nxqi4!mJ}8XMGttMkiQ9mt#WDCy z!xu&y+K4C&w|{@k5X6%{QBQRhMl_T8Q(dL0nTa7=pz@MFF5@w@y)9`TQv4(Gz->Bh&to4o+8>mm^8BJ7%qH10E{EzC2~`3^uju=3F#CT*^n86WAp5huD%QJ*R1*`pWkT|8gB|Rax3-T zF{|`HVmMione(cPw3)JtS|Q+fqL;cflOB9o*(}dT{92G(=@+?Rb6@Qfq?Ux99>m=4 z2nDbM!oW+JfjX4;%bn(&w4PgG!_m_47c%?WQZbZ37-5u z8cVYNGC2cz6z!5|=B*I{#<|Ml2Lzm>dt;|bJbuq7rrnd2JOfNJw8 zSq=n$Rm_W}tb0k1beBCM#Z9TBYnm4gFNp=va2vAZh_uF(`DAjH;=|&^o_7*Ev5)WO zcgt37e@cAjxnr#|ff0XX>gJ(Abb6YxYCL@w(N_S?N`@P>vUg5>^USWcXMA{)*`~Rp5Oh43{$KS$@|$<4 zIhKBYi3N5ZDTsxI1%Z|AMo2BXyhm5h{=xc~fw`dLzMXG9hYnkKVq$*JUw(LO5Mpa; zKb52noSkn_eqV=Vz)X9@+V>Qb(H*NP$*C!QQg?hZ#-A)PD0Pw5GtTj45oel|5R32R zOOLguka^U4I##t>P!5bw;D+RM=ov1Sdwj%#2ydw~CAICaMZGFi z4HK~=Zm(#nhYsm*+hIJyN`;0@;j8@u?evZ|7ns;Cb&Kq}H`98mYcS9d0QvUz@yR4$ zjrS%3Ht5FW9LC7O?H`EoUcGrWCy*ze)jot;8;dH-HD|UZ1y%b}lZGd_9gasY3#<_~ zgJ&jwIO<2lA3i6fPSf3u8v@1DQ0i>udB!B9I7FBxI!pEAy_ScF?v4&EmA_%*xi(RJ zaf@2oCd|)XV4oWWp{1W5 zP|{4~HT6%yRxi(`(|@b9mLpt%JqcU(I@;xm}qqW z;J&+ZUDO=kkWiI}6p!f*$WX;Mw)w*L;++;kMk(Fbe}?v^zm)&a{8d4;f1B1_x)Gse z52=E{5QV>d8C8lU|Ll-^B;P(Yj^9kIAu%%Z*_B6IlTp%t4v*C@HCDB=3!LIPT(V7% zo-8%)oR~0>K3iwD9~Nj$jUlhznypcF1F}v`Oy4gDjTY+UFMlOv;J5Da=uZ&aUrlbM z>@OH@5B59Um@3oxlWkGf(uxYeL))1~b=cH@rx9a!cNe-CVTU$x3FPQz|JtcY==jJ; zuIfrIl^Oq9#znu2)%5Z1>e-@v__B9-(<`)d-{iMFv?CgVhO4KaJa5m!nl^gI*3J$L zejJ{dYIT`o!dKCW`)Ki+w{WL1F*yyY@9h6rij=StnQ}7|z1c%|RWB`jsU6M=b**_}}f?Sg0Dk`1ES;?o!vvN}`znUKa>2;*yf?hqevDCtsW(Hp?aIy*tqrFGDyyCmPgg zKW)#>P~O+RIG+zOrnwK~(jSFSPfNzG2)mt`=BX8y$&5B$+|r3~If^(FI{ACWdLnHh z`TY+UVC8vPrCWmc#ktE=lW**_?*vQLhX@j9aFy*^1iirdX1TgeL~|^=Mt5~6g8=IS zuzllHe=4V5n2OBTM1-v%tXDLKX z^Y8Y#a>X^4&=raF_UiG*#s5jU>&Cey{(Ay?GA8%mi~=BMuhW2w2_oy}&Dqi-Bq|;s{J$je3qMyE z$0Q~)OJDxz%ppbiUsL%WUtn=u%8S?~x!`rm;kGD-ASmMt_doOqt>;(CzrL6hOR zP;P2&X34aH?BaHTWTv!$NDPCy)WE8bzkl*l`OcqKg6?&qJ0Zc>)J&8c9tfQ|escK& z+TEwP1*AXSpLQZ|eofut?on!WQ0a+&{D+WHV0SrGi-}AtCzl0w_fOLbPzAUDad5yl z^9onV|4b+_Nj{kaR|{BHi%pOawPhoXP#{C~N~kH(j-f+BwPe;x(e?q{wr(@OVe z{FgieI+8UM`}g|)nK1Id!7=&&Q;|i};qoy6t~evU)XA3#KR!MM24gMYu0DSJACiq0 z4ZszMiB01%Ovc2d`^k@J$No;Cc1o#q{oPgJXw`lM!q|aiT;$DH$)4E0&$>MWy>Y+C z1EM(Ue;6IEv?&2gD_G`P>6YKr8p$6Wmp^L9@F{C(c-2RC9Fm4gO8%IbFu$~UC7Hc2 zy11m|CYATU-TuVz@INyVRK)b(b2R6m z`0mVQBB9NXwHd8G{wE*v*6d@hX2^=`>+w}LqpiBYIPdx<=(q5SVg#p`+eZA(!$U<>WUKeklT z4)Z2-^4uU#rpas(`c3Q#F9l`pA-&Ylf{=+aQY=&OMivuF9`y_~b`8q}`m5lh@bh8w z%UY(ox{WXNYq>?OHZZw)b-)`cB8LoCn>I3315J$GgizVvCDIiEPPFFz(9h(9Za?wV zzKwllI1@7;rO@W)?)He4G!r!h)`Cp*(k|o!2F{ph{%xRm7GU6|3FJQ(!hos}O9_`l zo6gTzxdf4&lpw7`yT&}MvF{Mj!x6n`-VwG-1!Z6H1X#K`4k ztKZ|NhygND4dtRh$%Dq8`=bAD(7oRcdgKBtSmy&OD$6(Jjx@eQw|8(5H$81aWehmT zY-|eLWkt5}YNj}+%iqQ|{B7I{hP7<`wZ9q9lf=5|BsZt|BdC;$6#e* z1EA&IHqFYh-h^v6dSzN!+xB3NV=EyDHszL7D|DIMGb+rOcQDos7|J;&%}iYpbP4p| zj9Iw2kS24ioJK~qj(k-u1OOb;^{HAFa2gdonZxfS;T!MVpDh1D#k?%aQ+twjoO-nMAxU%!S&nyk6dD_dJm`=_{_DQkF**zIW5Yg8+Emln6vw+$X&64uk3 zttW6VrYc{j)t9;AU%4O~#wu+mBHVV+SrCt|kG$HofyBUkNZ7X>?2VZ0cia8$8;ZRy zlQ=z1S09tg4S>9eZF+S7$}bxptI6)i0xmdiu^`;%j&sK`@;C{$IWA+7QjtO0V1KT% z3-4pC)UD4mOk!eU^5jvt1%)p#yHkTreV$rVnH54PYo<*<6WZ8oXOayjQo?iS^*#gE ze77s2cSL@_@5G-mK-b~;WXc)Q+Z%&WPPCf9K>+^7LwD1`+4CF#y%5+h9%K;|?09L; zV^kCUMozIi)x}on=~rQ@VEk~4K5~`E3fEW8x~u)RgJ>PqoDYV~j*L%msY?i1bh{!6 ze<`BV>gS-$N74sLCFadf$r&Xr8vD^`#cluN$6`U-pShFHOFPa_DHv=}Evz?IQDu&! zqA!>l6=1DqX0tIqn5?at8o3CD0hb#*?=xx%Wg6=BocRZ}zY@Ou-shFop4OhCFWj`V zAb3AAn(m}s@9uBeq1M=5YjQk^RMOcu*Qkt_DH0s=MF>A0PgU6DTG`mB zEY07-3^!)H6N0d*QuTXZy=8AZPUAWj;jt8T>k~%eNfR`{YH$Fn!9Wx&QS_T=HrKbe7#IA;6{Q)U@KChEd z4-hn!Llk63O8q~NmguRDmz#0?P7*J5MW|#DLi*Z1Kr31d<_IxyagY4HcRVK9K}Xhe z7yG{sz%oDl4>p(s0|WEaIZ8ayxK&cW@Ptz9C6pc5{HVO-5=~n2vyDUp!oB1OoXcB&d<)%6jl(bq{JmbBm=-JF+L@y z$kGB<0S$iSH&&-@o{*7ZKq^xTwG<*ks>T2SfE0)4w{#~estln7?qfxe+~5) zIXPw-X2WCDv#}zh)cQtFeZ0;lnG15k{l;@6rF8(+KjNH!U8Z(%{j)b7JCG*ELm(1T z_b+Mxy0yhURpFJ3xnK~l7_5dBIY!>!7xB`OHNOi2w`IMd#%5#Z8qq669EE8kPHIvd zWDUgtxmJEVdrV5)Gs3M-BShlVP281ipTh0YvmUo@3_4B7 z%JQV;+3(ThddpXN#+8?EVtb#`2Bn$0a?Z@(%p z6A^VLwwEa7FKH^3qG|wyKtALt&DMAgi$5xkm8%&v zydxMQ%{Leq#;Mjt3@%oxl1Su9F?X`L4HC^huMDFx7`?Vc!!ptP6bDu{VOMu(Bn90o zR3Qna0Xx}^(}u|e_F=Fz_l;_{NPGHROB$Vri4@4@54I)RoE%%FhYzn`yCH)2i2pQN z_xfYc9ZqL2d@^#^M)=9=gBpO05KX8Cydj2ejV00P*nUT?d#;VhZA#?=lyq2yas z6p?!(c<3N0hF^=%WHIb80(SF~qfzoIX5m zWpwKyy_((J~qgF!sCHQn8|iGIxq(3ZWxy;+|@T4Wre%Vg`2oDrr{WfIc6=3Vg?z zm<>b(^oT4is?3qFK62}xkZ^wcujAOR@VF=>;v`qBFor1em0nM!+xR6{oF`g!afk5% zh0Ly6`#oP@x77m2WVL(Z3XAztER)otc2|=T_wlmzb_pq>*np@EpIc)f8B(IU^N{C;BVzyfu^|2<2r+ zdaWi^Yhepw+M!L#MK8z2?@JlXp2wl2I>>HLakNL%D5dUK zN-V(gPI2p$!XJo0)@qThu66XFI5u^o^MYM(HDOk;=bZ?_Tu8o(9O8%lJiGIS8FJPk z^Jg#lh^M#E{R{w8ZC8J4jcj-;^lmRmqH$YuLp7mLV6^1vk-qo*1Y3*xlZzi#@0v`}j!j6V}ngdCOgyB{UH zPR!UqREjmHiqs_pjZM%Ga-lr^Ob%o4bFv%c_iBB90a6Vh$4@0aUK7&uzrO5e)&aG7?dF7kaU^@Dv~*WS;{Kt@y(5F6(9m7+*1u3jNw+t zzv95Q)Ib_tw@WzQV7OhgvF3I!kW7%ebNAs0uf~YmBi~eWoX}eVvWI=2J$m{IOsq3N zx(uibq+2VcRM2W4NBvQ|Vyo2YMN0}jn%bZZ;&Gp2zh6@@R#UX zXWlyOsB+bKl+9~4788$xTUo7X#^OD$wRYzF0bjLW;|7f{9Q>)rN<-y_da|b0#9w=0 zdshTrk4Uiv*Ek=BF`w>(q z;}|^6MAZCD{7El&kuR>gqv8Gl-E`}qq=r@PHgI{k;fwFKeotOyohupjdf{z z2{G-Ost*BFL_z;+v|R(MwV~$2Bcl6UrE#aZ(L1Ty+tzblLQx)OaVpDB6c`Ra#N)QW z;Wsxkc|f}Fnx;+mv(YJ;+2_2b;dWztjB3Kso+DFB)YQ~@&BfdvAYJx{mX0%F`p2$6dlKAndRAk29fHV1s-$e6D74gb3@!FTiPTFf@Ru|Et1;P(aGJ{PW(wKL7pwAd_&V3l~2q!69W@=rUyuZ;F&RYhM%3NRXR=~8u$wtr*} zB~OX)ec>aBd_30z3Fk=E)&B+)?oL8)$3s*Vcylj&alZhcnH7@`7 zmQ8-dp(Yp#&X(eA2<1*SK0Bp7m8{;G@}9p{?@B;0C0_;=QKd~bdoGD%kU>$&bp?>G z8VuK6P}2ugBBkb*m!8$WLCL8)B1$5;zxj4vN}H#BZPk(2MwMVrGIe4dZkW0@!IYkKtFKF_;$v zMJGLfcwtvYHzSBAB4KGr=Ot{sG(oB{Y~TksqGbZu#Q~Zu!L&{Cv<4*&4fx4x=?i=^ z+XM4EJhi?SpHA+^YYHy!KBVlQR0D)5Oxgb3i@sIiGvpu@#}1U9MzL+& z@7F}`;Xo2q7H*@7wlVcr5)8YZ_)+H+eX=peHInOCeL&CuOtQJD&xs(!m5_o~vFw7E z0^rT$9rQjA?F6jS`<#?4wsYU!^38@u@vEG?Jf&w=BGh1~IN$nDiM+jGtT+0tx{Q@4 zXh)Qnjf@83!<+$!OBLDXCu?mc##3Vb&O~z4@5xa7hxUFJF-%N2PZ2{}b0M{&Ibo-j z7k)`==E``fc^U4TI6NGb*^B%eWE=PgWJCP}=jqLQ4QF93&u(`)Z7u5d2!(QHv%OSL z<8}QP4bw{2ovL(;?}zi`aD@#kv{YG%%a%nC> zod`xL6Q0OtSk#-5I^_aih(Qb=qFyk+cxCe+`zAtvrxbVi`1pGIx}&Jg178vTLANKfOXxr#Yu$2L;=6W3Ism$0;`FJv4S_ZWc4XD2 zv#s8VNNiEa!GVXE*K(9EM~jeyQm95+aR=g`^7l%NY{+LkC+L5R=TK(Lo#9m0BRT$Q z3?N%cNhz!2ikAXz%l!5;4K9>dkUqs zfwbS*=m>f#HgnAj1^tp2hE~Riy8x@fdrr!5Afxp`q~zW^lV-n~NeEG9b%O_nyH+#x z!R<5&<(URgRv{rhjr7lnW5pA_M{|RZiMw-G04Wi`u81ZgK2?74qF}U0&zP6pJb^hh zvODZvvDNM(E1>&adPUKRToZE}y^5h5e*X6D+h?$G)x;RJG*88iadw@bn_36{MNQml48I&D! zYq&F2CV;c#mFDjLI%|uH&uqcCy|e;u~hyda?Wc)>bKeo?Fo=8V*!9Q&Dp$FR?dFz z@%N?=zm>Qm?22V(%}neOjPb<&UNFlgwJJ&;R;EhWsLRk305xBkt{1F?>?Mw z?JT5aekYlMG<(ly>M9lvj>1W~5jcbvNwNW@6?S?6h{0o$k{&gBZO2@>L509!B~oYk z`1!T+)cv(iaabOJpBxRy#1L8XGTjE|>WKg(oPYEQ;M)Ui#uVScLCJlcsn&~NBo>q@ zDefH+1qe`fR-16LK;~R;3%&tJA~~29$wk)I2F)J@ojVes#mw}?_rXhJ4pGyDYgRT!f+*@LlVddc90MwpAam1u5+u^fz?d@My5PUL1 zYlpsHXubWvp)bYvsPdiblTa|MzY4#Eiu6A}Y(?<~9ggLX090Fd!au@J&OE1@ZTGX| zZzILpK~&{`xBv#4_S2%>XS~C3yNR-yRWYZTjY-OT48%OjhFwZTx-Mh9qB%qp1`4dv$x-XqP5)g6aqG^2?mWUq1n7Ev<)%ha<~5x!(ug>dO@v`Z^TVBkrJW)U>h z%jQ?5S#yKUt;z~sQzon4<;K3uiHwUgk~a@Q_ZAmI8RJM9>IN6Y>2tP53$w*$0`ZW& zvwgnq;q0!k>Dr3%QsbWD@)qug58KHnM4X1&QvGmS4`bP8I0mJTjPEY;Q1XiQpl?8W}TGl{W?3FuY+CU zTgRg+D;-ou8ZRtD+oHHkv$ArH2hCP}Q9k~v=QKiULj`jAyp?_@ckcFmn}9o~NVAJ$-#(XbON|jsSrk5?W78 zzvaJ*VeG-yYPtplm0d|b^gu(F%4@F3lb%;YlO<2en2yGTpKCHJkc(g=;X3*i2KH?S z<`<_yBh~hS#$Um8gl=XEWted3@=9XRPK8V^_MklQuted#GVg*4Y6M79os`WmaFRP>qldq^ST&dHf;3km|6dP(esQ;nH~q;_SOPUlec_v zQyD4OQgC2UasC3p$_!iKTB(sC)KB&(fV3#bI7M{<3b`*PcP;$pyZFwXz@sc4e!yAr zz(ic40e!cUt82x<)@>dJKn_ejC2!`35!st|=bPL<8j?GLwP^*MaR@3E3910|H6zJT zO5D+7w&{vZ=FhAtC18MYRkgabB(r||hNep=1;usEcx$>{y@}akBD*wJq=)E}92qDd zkUrfsH%}yGNLszM%&TUrwsF&oEP`vs>-_YK8)k&{Zqx8Nrh$#e2%IP~>thNmC|SDn zx-;biMr0J;eRqb>9TjxWPfsHAQ$$?Yg7D~!mG@L;Uia&yJdQtpt{9*al-t~za?c~H z1vnqDcEdEGVu$gYQ3htdvUwV5HmI$U5hpsbr~>3Jd)SPIpyw$Wowk$kXZb4E>aFDH zMD|dM`t);O*81g=#Q7*Tfq(#ul6ci9xo=(=Gv3WeGc%kVqZ}DRiZAI8JjM3_C zleMgx+m$B_I%IxFb|4edvcJQG5+5IK$6ax6@w~cdqEh!r$Z>_#l$TYM;!(Pcx56E4 zWdo3Y$dT=MP-x6|QD;n4R6jT1>gJ+&N=;_3{fi;hZ8zP}s20Mng&j(+HF<6ht5ObT zV1K;;WQQ>Y>t~*^;BXEpp8f@|a1xGzRB?`eqiTUfA@@Gh5IIdrLc#RwE(qMxeS2DL zs>U($T$mT>S~#kn@8@?Db*}K;clTvQdYGbPi4(RCi zFM0yP#ysY*bfnn2NeG-L#|z$mco5)E;`{_f5hecf1o*=P7MGYH;d+=2GwOl_-FDeS zjjYCScRUvh6gc-fz_V=HFK9Kk;(O16D^b(u`CI_D5^Ct!HHW7XNY{rbzt;|;N&Jki zwu#bCi@nD?7LY4FbWMPI>g&4&rcncmbkclMsoQ!WW&*vMcwO~xr0CA?nWx|3F#QHZ zU~O>Brp25~s%^G_5iAS4C|`fy=aI~-&C&Q{DNI*PqhSn>^o^Mr-}uw2yt zE=R;>l)@Z+pKr+Alf+EEb_J|G?6w#GevQhFB;ft-_U8-D++4X>xbUA7y>T{pGmCl8 z8kDabxoS+$oJyjut|2Q6$YtN!pZ5|ICX>5Ua0&!)jBiyR4(AsBxLi~a)VTDjYe0wm z=0YOi5cTyH8jWhJSTis@%6Vn90i=Uyd|qDmo)3|hfM#4z#+AGNG=taL&GiFZC@>Yo z-Q-o7#=f5v@N4l%@v31nx?|yMYS>ys%QR7U%96>`c39=E--qXSm*%H@pZc@uA>QS} znTsu1uV;g&BUvxK6>UGEgo}<;8taaygx>7wAwI>zeVCp4_Bs^g`qp{6+4mwAaFhA6 zi@k0bqUf%!<>?L&uAt561{YlB>4ujmuUGrzVX{wP!?Kc*>OR>38uJ87wNCCRzU$N^ zcjUca{6@B;d35peuTTV;@P`)Jb;RXyX=U!0>88t}M0KC+MYK25U1CxK;W`RF+I?H~`&NPMK=6>a3dAW+y@!Gr?tI+Mi_d12h^RZe; z_vcZWNI9Ri=}JSEgNiLs68C$4w(50<=p&}{?TqMOrQj)v7S@q5-%CLGY5{V=@w3G} zbC~!DN4(M7_j;fn8d#KxXLegLE~KV*H0*gF)OGHO)GcEb1l8AP;dFbW#m(aT5@9;OfqlAxTGhZ^= z9KvdVSpb>69Vtii;#vJ`>X%J%38^^glLhuT%$e{K$5NF=+8}LeG1XrJUL=)C9YPOD zy!#$=EV--|8aobnZud*NEwpi5xa>CGF0%0rF3Ys)z{oF!wkZ2mL}WQA{AC&X$zt{k z6(fzu&FZhvGb#X@!>fg#-Z08@@{>CAa`A0;Qf;9yqB)VZ28YAvgE5BDD< zii$J4nIG;ehTiI~5966~uB9eKGB8e#^ovXCgJ2R99c7yOIVW~oY%%YVQNf3Xt!1$> z@QGEk8y(6GS;{2SXqWlfot=&~tr9XF`XyN4z(#6_Yzl`=?OJj4*Xyx2bC`Qa;bE#k z`n{{*&ueHZTpr8j-LIqs+^N}fV1A@P&E92(&VMLzHklfsAC0~lXTBwKd|ZJt*QasB zz)BLe+pqm-mqTW8qQ)&&o0iffgdq4h4a6;1_T9T3UuqmEDf<(PYFBL!gjZa&V72}D z`=_pDHoW*$fGQI!do{1%&IfJJO&y5S&UeRm#E*LOh2$T_HG|`{k3_atIVVzRZYfAJ zyXvgb(74Pm`Rw9=4F>=l#n@b_AJzGWnj^b=AXxou#l)Y!HYLGz25boGW-&P2_Uo2( zOiei0Mml)7(&zgZ5K{o*eQLeks7DHGq9S=1U1PzLX!&wot=4*jQS1o+$d(^Wo=4$0 z+AKTYWx)jHyRc_7?O4XNf`u6%Z;@g056y)j{%!NIJe$ySg2SPBp`InBM!FW7OS;0W zTHs*$6-oc7no?%7zQ1G+VC`gJOwi~^lLW|0-Ln!RXC@*NGG}zNpc8@8w4r6C5h`zcJ2t`0!;9p67E&%K#(^u?KXD?4Q;3ngr0eqLaeBL6TE z%|2dlPaQf|epbOERS96~Qc_hFfcu&*XUMm7z`rU`ODJ#nD{rYVp$3oZPaT{CH4QqEfga7+e5X*Vlq$-WQn4n1%}uG3_Cy;oBTm3cn2E*SQQ#v_L{ zZg_nN6|4dXV}ZGVtF!a)P%ODL#~%VP$9p$zj{!Q*zkGkH^m;{I z-k-3>T*i79!WHw;JIj2uU*3-NZyr{eM1XuzHzP`>Wx9m?{$cL!o*6->*5b~q;CGkj z^AT192n7LwDIPi}w^ba7vBwNU4M4JkJy2nWN7H-A0?b(@(-LSBOfdBl0g+3l^dg<; zK!tD8#oql{{!4pK_dWn_u#OixZ%eE<0r6?iy1FZ)eX7X-m2nRcqwo|oTCybS;ILb6=0W2mH|rIi7; z=Ml7#CGeux5_g-QFgzJv?b28xPsz`EVsy1`f6{Q)eq-X zrROcjC9TKHUq(twzqUWDe=7Em<|l>}biIHhiMx2p+*)5PPS)i|8=o*HG~Qn?{?2yc zG2u4e?TrGlO5`<5hyHNZ_@P9ONZ%xsoR=FSetS~cd2~kZkE((IZj~QMPewYikWmO& z*i7dQp;Hza5ED@|xP-+1D*L0NJglKl+m+WPzMUuDGS0`&vD&&|#|p7-&{*NQ>3(P$T9qY3AzZ3^~_&3$pb_*3ezaIepQlFv{I^h`!{CQASD3!w@yCFd` z$P|JBKJK%S4z6WEB;_)pm<0a-hJ~W9(A}NL^&N^F6aA&N$F}O*T?0_{InL)asrvDU z#|@_REg9889>N-vy>;FQOYL~UpI5s3&2a<9m*VB&l)1I?E6*8oyyj35)59w4JH-Y; zO@UcuILsx_fUxM~<6wxhE> zk64L7E#q|O`yDt8jZ}Bj9e`m3r5IJlrS}x}D!R%OYD!^6KL$bxF+nvKj!1pcttUnXS$A8|EME)e9(sb4bt;MC+~MIE)Cb$E3r9yE?DGq$%~GaAYkEc0LEXz zWEDd&K~=+0)QsRPYg0knW+sw-dUCgGPCCV`7pWLsE`pd$cJ4!sp7ofYg`OV;E@RD0;(7J%EW{?0PS#>YfVgmi)_3=P{$4UlX^~vt>GTWU1q|b~Y>gQ^1I#Q{2%r=X<9BXCy#3Dq%2|abo z(KrnxnX_PiB#4Uy_{S38;u4ayZ3kAElYd4=?vsyam+uSvFcplaP9!PVBupsDIJ`hX z+66}BIGW>~cki5)zGy`7@^dv>)f_+7XCf_e77&9m0XBW_@0V$ZFNT!VQa&;E3B%(8 znVav&j<2I88#f_4;@o<(Y=&UfJ~WWleP$b-)9#%~65ig8hmO9b}WKztTWJR-1J7Xn$Y^2n^obUwdtp;a+tE zo_(PnY?To=FK%?O-bL0T5x=Dw7|e0kL=Hsa^cyJrz!mvGGW5sAWwoIAAo3}xHJ&NX z;!l(gS9%+q?ABKdAJX=EQyQM|iq6R~pu95qN=ink$Rr_sG)oz*?YxD@Ex7x(eMvs{M3|o^t90%e}ik?^Er7SQ2IjEH{YhWPT3sbvj+_-*Lyn zGDao?e2HwlVPD#=LupgRg9`?`y9JP(H?c1#g;0`KQ4AVn`QA`?o_3jjmwpf%z=Aj- zE$9oxaM(x({Sn0>E97m9NKhBMKRLXfZ)xI#FxqtD+{CC6Ey30>PTe*3V#=z9~Yz-eHAqg_0B*~w~BJgH}xnC zsXTx4Y}>V-fXb7#!mi~!=Knhz4TQ=aLwW2i07qD zhJ&bteOu4K&4>1=<1%>X`EBq^QR{84Bj$vECRawqY{97-nV98(36S5CG9E1GBqq^3 z>A{x%yl8I(K;M86KOo8PDglefWHbT0;Nes0q+6St{*5f4ZH3$~LX_j%rfH?(n>D$) zmHW8cGgpojfJ7kh;&egpO344jwg(BLMwRf%d>Nek6GIXhy$WI9xVDoW*uF$FiOu|a zYsF{+W=%R;)S)cCn(qPQ0t_WNb*yAnJdV_L>ddpm2QmE}6HKpIl#(CC;pg~Rtp$ja z2YzVP4bPTMRAUH{6Btym_QcUU9r)wbcz~`Nh!W|NSg{_$UB0%LAj(gLBWtzk3H6r6 zdtki<_UcrS?jS1Y0==e}$ol=AKt2f52&t_5=-q)Z%VTTzX0c z2}=l8uJieL(90bR4T6CHyfLr7OJo`V9J09~~QYBPsKgne>@{BV) zGhQ&jJHh}2@NZA=iLcQD0w&$2TUA`WfAyvi3Qxhp3wnmCCWc;xOZsuWh7<@cEIRs()8OTO~Xr@)3@Sw(QbKrRsEHlUM{h| za10(qb;Gu>cS?_YRD)S%4{1jzFX!RkH7}{Y!)`uY9+bz$;f!>v#>LGC~kGriDP}d}eQU`E{ zwv%|MKgF3RPnuTi=GrV4$X_pb9_b}?U#54q2~yrY`gdNxxU~ zV<`QM>rz~4I8QM~7i4CUW@W#9+=(hOJx_`jKHJ&IEfDP#p5VG@GoA%Q_7g{Rk7s_W zHUO!9!Ed=4f0`|HqNAdu+>1ZHJgt?rP3C~&MNsB&JjEVFM-+seLJb7bOHr6X2zjG# zh=g@0;;n3B0}~82Wo_H{i?FH)pI(nMvfVfOyH$JHSU*3YBD};r*j&QYG{lvF`m%ah zLCwgF=JvS?b2#varfT9=2zZSO7ad?s$({(-f6xU9_WE{Vo$L(l!RzU1)_y-gMG14b zVSns@zHz+Wk3d!{G`Rvzy1lyGbo1SbtMWeXuT@&PlKnZptg*)th$2^e=L$!2 zmni13gyZ8Uo8Q#?_VgTpP+#Zvk{}BAj08&{^#<*KY^B*D1%qnEH1`E(|&cn;{1lH9mSe8Y0>q#_yG9OnM_ns{+W`H zX@AA*Y_@bBJEj@eLg-RH(`gosoPysbyk3Dmj^2KJ>w51)kGi_ReP;YlO0bYBM-LMg zW|iqg&4)GMKDgPpcpb0efDjE98(jN)EDLbt1ki9${;wd^oT;q~E$2r=1h;7IUOOSx z#o$d;-Ou@G_|w0KELN+Q4sGETV@p-Phh*?cbZH+IF6B#V>2%O_(2ox=W1wytW8n7) zmy_)UUk!^n4tq{cFcGrevySWmVNa zpIICx2ZXz57USc`0DDWJ?yoeKM<`{` zat#LYO~tr<*qfA9p)<2S1knQ(fD<);1&Z=I@f?2inm=bcMNnbe#{}O;C{xJfE84~9 zQn2WXj5PP2Q6_j2e+GPwI%L9d=4BWgL`7$}lNw!I_)=8c@Ymx~ku;+axL^D;T6ra@ z2#+Ri_*+eDonFD~FFwJ+Ld<_oh3{C0N8cWIoQ}^nVp;rSJ*6;+y#q$6r1n2aWV+uk zVR4%@^iAl@SW~=}4S7^R%LtbpmHtv!=;*be+WlPb=i{U1rlAdhN-@%h21199`?etR}a7;g5W^P_uvA-DiU*ZKOj-ef7p?~KASWOLdeil+zEuf^pw14Hy4 z#OZk4ha<~goXO+%yB#Ss0Cn3{=mRnCT-XM3uiwVKw7i`kP(U1vbQRfHK;dYfFo z_>*r@ld6G-?v`R~=Jn%~DQp~KKh)bFxfqP(JPE!D4eRugZW64-mYVS-3Kq5uYI=rh z*K)O5u#s8b<#o4tlUzzX8Xz;SZecN+XTbdBY~osNmOMN>MTlNe_R&m5K@zmdM5`JX z@+2Hi#IW#qJu<+luXWB9{Y z95xz4xgQTY2+`DhYX@>|)~up58!2Opt9N+z*;h!SGduL!j%vnG;7z-m1Y)StkM`1G zTQz105#5pwL(>b?ax1Z2r!Ql8b-HAR0*SB=p44WLEQ_480(n_hcMp>;R z$T(s1A4kR$McpKZ@0%MF({R#8B2X?3=3Yi#Z*YjDxv;3W-Ti2BXE<88mQz-VTjVH8e^D%HaAM|}V7?QPbr!%pYl<&cFMYNK#e=jz6X?GwN(-K5=s)tt-?JPc~Sta!w=^KPC? z3cCDKt@Hg^gN5DyR3Y50-Y7w?VM%kKp-_b@_XROo-Nkwl=E9YNru@Xf7mOIQR$XBu zEf|%prm9M=d#5y>mQ86Dzt?8STxrW$&9f-%6g|7Cv_uItwb5de0)u{^+@0(1geL>+ zV^RZ!FKS=d!Ic%Jk+Oq=67Kz{<9x{X)fK+@c(dl%V}L?CYx=m6u0;^%BpU{f*x44%#d~ZzT`vHF$*UxsyTH z7EIJ%DGU#+EcCzRwM9=<=&WsYVY|cScgW;Q=gW_zjw$O`i5GQ-^oyr&`=+!T8y759 zHV?jd)bJ0rwJZ_G8sF?Yx--m~!`dJs5agNLMP7GGaL8B|BTBMtzqy|@n3BUu3v@~t z1{>txYgo6J5<1oG@EZLnYPWK31JX6T{%54r*%Jy!X1iV3yZeENeaYc&fNhRA@7)lH zvrwRTvysWHz$?@xQIMDaI|UWn$gaJU^CO`Ih0JB7A`Tr2zi>=|{-@9S_jsjvO*8Ri zp)R)tS~G=B?2Q|VCmx~Yn#o7az_@k3h1dpP_~-bP=L!beUmfNP)Js}|%p$PgcOEeR zA%@3q8Pswq+!mMdc2g>@O4ltRo=&vl(RE{D_V4JaMaX&(Ppqpkb_(g`#&3!EKY-0V*=oc&ruG!sj{jno+CNsAH z5&-Q1Jqs#b-B#ial8Oz!l8NW$8q9j=xL>IuqnT#QU)WFZm9G!LyPo-%P^Pk4W*-){ z9EhmDD$4SVQy+c7MvcN@O5y!@HMqW=jlb=Ac{*z}a-DFjugbZb5<7Vp)%Bg%q?GTk z2cpK}LOEoqg#+j%);A%T6KOMIIeI{eatBGmN0<150$!6mAf46|)tv`PjH+mtZ=4Yu z%M^i}nFkFAmOn5coz$~LCKsJ)K4%;nz9^}r)S?TSf%0bU3KMjAcU4{6SlB@VVbZgE zbo??jG`?+-Ip8ft!rhreEZdpsO$qhna_l%k-Hq3OY!r6%QpsEIED$YP?e&1%VO?D? ztH&P{$!vLFzPnqNir@XvY_(TY3O{g*hdm7b!+iqgem+8+|LA3RQxyweaTrEWI>K|t z^!A$qv3)6nDh^8;}}zhkbsjWk)Lq6Qw3fdyYS%=~3gRD4(< zyQK?FH~TLKyC?5MTEzz5Ggdlxa*owZ#-ceWP_?XY#YmIFzeylltre|HKzXc6uf^Ud z?n-1yO7gtp1=-Q4^;0}^T!`$CcXk_M-^$#Lpik5%f3I=_I@%9KZm&Q@lN#1YcdQ(%ct1V49qM;KMX<(4iqUA%tklJuDx>Y zA4y9+oUH_HjNApDhTJ-=2~t27MjurMFdWCq@(ygU&<=Mtqe@Rz?||kIMQ`1Nn;m+= zrZUev!de{Xj1D)QJP*dTTvzZAs5B_l#Lt%NbICKDf6;iA^Jg{!dJLJnhT@$cm|rOm z=9`;cRtE%ONq#U8gj(&}(D!$tPab0J#n7A9m`pV?TO6XA?W_mFf3Il?EVj#=}T1>(a%{@Yg3P^ z@aL7(N@$G4alV|6fbs#~lPAtg(G_uPV{g(^3;K&M_YS%A&ZfMsoN-Apub8;lj!R~+ zN2>|YvEenD%Kuq~gECV{rajumRv@ z?g>L>QD_)S2tQ7FH)|qVENn)K@Ww6SIeNMIYy&N!qtRRQ+MX-Xpf76_D`AWTfR2OC zo=NYP&wOP*T{47X%;)p!JoMsc)zFZTJ<(ln{`Jy-#;l>23dwr$$C3AouL9?Y%O5v+hK4(o5EqwX)OjfanYe83?%7U zZEf6yvR{L9@F%<;x5!JM&nQr(>uy-piAY-FNCXxuQz-uiP$w4uNXIEfEL z6NEM2`VKgpdHAh+o?Kw8R?=m5WitmgaKmh3-#(vMmwmI5kL5v0-2c;a@A{W2^TNHOU`Ns0KQxZ0dCR}KI*vBJ?&*i!+b1gT9~ixe5d)~`F3~Rncjqfnt?^&!bC4qW&z^4TtRMccRQYToeiDLr3ukC~S3&&hLcF!4mOR{dk_&Z!E*no0 z4H<+B6?AFsZ)fLRu_F!_r}s6>Pic<;Y;SMhVG`(lda(9#3wytcVq+g6NRF4-)G@!>&Tpg0BAGNeTr$?# zM06#E^$(C}!tOD=YQ1=uyl^K#5CuM2l=ElsPyEhltScBwvZ&4q&oicaGGLiHM@GBH z$0hG$0z)S5m}kNlcfJffm{i2AL!Z^Nr9LhLe-? z`hpr0(sZloeuS>Gn5Vb$OWclwtu0_5_{j8eo}KbHG4b6E{Pxk~tiBWPPN%oIQUM&! z=(vmn6L?c;9Q+;uf}^Cf6}PJrBFjoRDb!W_i8p4a^B=ej?(9!6k5|La_zn!P!U<9N z7%p*wz&MSNKLf%r*;;v5L@N1GhvQjF_sanZ_g9iGH*~?^(g3t%S-u-?=_J%~(zupC zM}p(DTHv`H|0PYjlcNraHySxESC@{HuBmL>Jy^v)Zf`c z9WPqEKc_{W;?$7X%Ct|(t`A5go!-d1r#a?%>e|YpIK~rl`aOE>87%XHLx2?BGe2uw zi(_+ls)B%no&9>FwPYB6+*z;^A2J3Pec_>!;(*a{NL-A=en7oY24BTb=#lS}JFT(*( zE@sKysh`O(HUI{>&HzN4!fFY>U}HTqkZxu^VGsQob1!SRs(1%c;#24Awct2CJP}*q z^X-|3QB4&TK%!k9TNxr`AGL6FbadPv=AvBP0b1Lbm*!;OD^gd?HJ1Z-+8sqLC21IB zLST>g%2i2G1?V+qcG-d5k{ED5dm~8XV{{xGX|YKBe7E!Fuv&&{{!gUGHZtMW`CBf` zm{Si34~we5d}G1;A&magS|$IZC%`KPRu6r(GF`Q`;+dyg`+SZ!c3TeLM>Z>~~L zgg?+^YI=l$gCh=;{_t7_TKKU%_br^wc%*0ukAW@W!pehZ;!mS zwRK5ZQPyHpFFv;r*)$Q<2lOMa{1GI=7`+UrQ~Y@eak<^NQYraCkon6>9er7Swbid9{;qd?A^}`cQaJD(c z;On5XJnWRF)|sum&yIm;iDxRFy+iW`5EA;+57F_f=tCw$4TgS}R@D5Zm7qGY-c9yk ziago+6AqEY{4fd`5st*)WIg(=95PVOY*B$VANAi~HdU|28SVK4|2EG`^Hy=Q?#PIZ z*J~&YZo0}IU4PK=Fw=|L2Y-vR3Y?(yN8OI{L+fSwEwxAJBq7VD+U_cyn*f3N?>?vt zfaTV|+b~TY>;;MF3_bunk|3;YK&u;`>!#1kT}Kp9#CBf!pQ|J($Z*KO2>tgrg0Q7&JgG7UAep+VAk&+JkD;FV{gV*# z4ATxGnSZ~QR+mIr`M*E@@2!QIzIBuQ?|=EvLkb3`2LJal|DVfNU(xV^)A#?m;@=eV z)8^;@-xbttvgv2C=Q4mdXj|fnnK*qJa(m@ATT(~9ke=I z)&$ye4V9}<1hl| z|MRpd;D(4H`{aiWpwx{ZG-(+}rR0EeHZl$}coJN{=FVQ}t0s#7ju6Se(HCa&RSNsMNCJ{EEe{gD|B*Y5%&vVkx=A|k$)!P=n`}GX2EJRsYo)zUa z1;<1ZX2W#{Ayf20eW@;K!pr>Y%Il=%|DG%$nQ1I%$V57>8re4*NL_Dm7s#r zG|7**XQuX-|K1oCK)Js+eGnhd=aSRB+>y7xzyu=YZ@xpA?kva0#l{z}Uz~1BG_JXz zlxtP{14dl@Lm)KDt3kbX5N^xOI+UTY^=jdHPk{RK(MqMs`I(d29d{t7)0vDPG)k^f zm{(317Om>y%>xdjJ~OaSPmh=5(VWiF^SnSRUUa+-c29+b!rxFnZ-Nhx4K-ZCk_g-D zSzF6Gt+skA)4auphQZ^#8EBmxUV-*kS zKQGw8>(h>OR0jLq(s~cMd z&YGjc?2-_@fzh$G2ujLvr4KKUPcuNl=g`804jsGy3Jc3YQ5A9KctLtWPR}PlV`ds& z)|BzQg2S~Sk(9~kgDEP&CBD{J;b^))446n)#E0{R8(^WFO_>s5VMRi6iG>_#4!loghsrUn+>tb&pY!(3Wv z)xTGEn`^{u2rW3iotCAs__FwXrgU zvN=H3`2K3FAZv>m_iuXlMP!NpMkaYFjBwMC@nDNfjFr}yugng^8Y@iwCTD11VDRUq z-aDMc+#%^L%Mb+!gh=;zT{*%7YhjKUf{}=nvi3z2VV^M&k;LyO>=j4=T0YjXBD4m}s|Ra}yQy(Tr=Qy21nl1j z@hsfGcf!u!dq%-6^W@d(1Ou86ZQntxV?S|7?CrMcQ`o^nR8qWznlRM&oa&o3AqLnK?4#Pgv zpnQ8<&}=FP$_w<=%;5?gZ3|ewlZwXU`KM)4%WYO)>Km%FtFpf}eSva~!m$#Q!&(7_ zXG*QGme*d67Tn>{2LoYjHqa)`ZsHc1fNQF+n8~E~_={N;d6fYwOnAcE^*1QUKY4!* zdW!4n{O?Z}C5(*nH3=O~{fFvq6N%;yMBI>m{iOP_*dNth4R0jCG&7Onj3o%0T?r7A zxMtJ0OKqPJ{i>A9HSCw&4f{M1qDT znrSpzg2Xt~2{jSb=E!L~SpQ8=3$d)+?7&F;Uaaz7hQAnC1g3SBpGX(v$T}1ktlA_9 zfen4!Xh%9eCTy`atkwbS0v)|^(18&VSxH4j-qVxt>B(tx6ZI2N-!mb{MQ2?h>Ko(a z2~I%4b5_?)eK`BT>HIqN&DNbDb8B`8eKs3-_n_#207lP1vx&?D@6Qs?a3t@lYi_su zU5`Mgnpp!RU;068i@B2cp|h0{*2&eCi|wr11}_bdOLvbKq^#h#)}z@W!wy+2pvULt zyC3@uEIj-*<&g=u4u}Eu0!ocvB9jsS8(`!&;$vl`XRdimvW^wq?Qg*CEB7zTP|f|v z>z06)`U8N2^zhYX=adBTw=F-niq=@8ll6vFe?WjcJ)cvBy7e70OoTdaqqf;nP5cy9 z=EgUVD=}rLpz(_5laP|b|Ba&LQKSsN4sM+sOrU2_%vBh|7#Zc+1N`IF z-41^Yr2)BAc4Yc7ql@m)$9w7T*FKQwO!6#7`x)%??1AC6?eTOujI29nYf}sm|1GzU zG_}zxFD%T?%GnI^+dW)vBH}~3%KZ9{dxik}O00M|xP^UvgOU|~TdRo-FOVIxt^ZyY zay3ADROUhuXFbiSq9mfNefGrJp<_sZT~l434^#)+ad49K5sl<(aUnmO`AsHCt~NWy zE}h}C1KE>{;9^EgFzDN+fKkuwA z&T^fIvNDSF%$_c6i;KLsGt%`MydW6^BRyVyZ}HH!QGCNm#IPO;maP9z%tx(D$}#Tf z;r_;<0b?``h8}l*ZaK8R?o(?Z=Q|!(&k&OHu} zT{dtrn@_#Bw|Duyj6~-H43H`zo1H90zSsb+JRtp{$6C`umq@VKn)QzN*Jk>^p)sy! z9`1+J`6Gau+vt|vTpJ{;fG!_~`N{{GA!mj>Cs}t%V35;X*7sNoM^e#J3wuXx+_pV z_RW?-S?n@u05o;G)Ht2h-Al38$It>OF$;ZR9*72ub0grubLu9ruKUyWl^4|iPXOE( z0iG;qQW5$4Wcf7f`kJxB;bbY5+CjIzjWTeFF(SVv$M=QE@pw7{f(e9@O^gLx}+`8bd!SQhIO%Fw`Vq|1xJ@+C)X} z1Kbqyfq>Rzdp5ZZ4K|tAq^E#~Vjc!&^v+y)(tEO-+2h`FHpd8|17++$&RXq%selP` z4HHwd3RFr0(Q2dE#Wxbw*-}~h#9zI62X_fEbhTM*|I-4vr$M6-_3y>VWHPRWVS$%< zcM)gEi436m(h+88<3lo%zn$WocV^!{6_u3jhAf}+xXt*qw@UHun`w(7?43sSiOy8X z8x+?^SXF$jG9dpa(`2ns)7$zBXemjbRKMOGuSAJP;>d!~qHLnBNqWU>_lbs^frDVv zeKJ}v&9wLytjF;OYp3(p*Np-E{9QL^v^@@;V~)zw&5?{&0wb}dO$!yr?svr{g2`1>*l?Nj&=wQ~y>fvN zRQkM>eTq#nzR(vY8mgPl{sRu9}#tuv+5 z3)nwUXee0Aw7Zw$VvR@l!B9TU$io0?Z=KSG0jEsu7ax1wR!vWldEOsvxs;h2^sL+?9M}m|*b!@&C={?KCk2gE>{~ zr66(W;7L+SOL=IiY}`kG@&L&*=Rg4c{}P`6GjPJSb9p2G*}1o)cIF4te{cMO0fI5F zU)uAJNF`IRcMYxQ`HO>oadz8H0XzXs9h0Rs8{Tru?H&mE1!?rlktuf`zCulrZDC>5 ziC+Yp$>PujGQ&!za-DQ440M1r%v|LMpo|<(b8-5YemR^Tezs~m-`pJJVfnvL5+-`MCb-I{mq)9bJh8CKEGy?8WBMwoz9Wy$5ANreI~LT zkCIBC53om`8P%{NkNTr68dWU{5GD@nWT8MrYM@8=zdX8!^S=qMe`hDrn%9TNY^N7r zO>J%A=%^IXoeZg5r}HoQ(uWC+dPA0mb#m{8?MQ;VjYA8V!+a$h{&Ae!_{@tjbsXkD3k#}DffIXJi!Z9W7rCbPdSak0xO z89}9s^N5HcgC?I+aMxkbSN@;;b29ybmYB=U?T*9D%&e%0I8#3dS;5mPGDKwx?sPUe z2l;%VwbD3K_js+5h)s=#YP1j#z*4Bp7=GF4O}dI5ni~2WNlrND#{E8Ky!HpCAE*a9 zii>q8B3qamTw3anry8!5MwJK-yu82qCPr?&++s^3oyHlQ z;lbe?fW-439v&{%>C|<-9s&(5EhS^pRdhm5)dP0>A_!O=QvaCCva%L_pKPE7H;dUy z15NwKJIpUFk4US|H|6KUT(2o#s3&wo?Ecy=H_cBQ{)~4X6%?Y3AwOSkt)5@f1(StT+fo@<= zF5sYmiZxfczIPs1ACj&M4~toWKDorcAQIw>zP$uZ5;(02K|w*e164L5X-D=a<@SewY@PL~>Gjp!&cPw)@3)|t zNP^Z|pvw^V-x%36pm}>s$%Dt;+}C!GXqvc~90ZxJ5OnflAl$pX>_wRd1lYw(Wz>+= z(L=uPMCyb0l(bZN<#_Xx!3;ZdFAO;D=b9{EFq6i888J8=u+zCSgl*-69$6WkX|-GW zz~kc3hkj|4X3qIrER(q{#ZAh-yLB}5 zVwjiC@Z^k-2jpvJcBveL?ElD&n~NtV%ax``FQWh9pTQfFuKz*!Yyb-tK|}}vB$~Lm zm)jXSXNC8Fq8Dv1kL;FmEiPF=*@_?yw2N%FUaq68bAQrDW^QU?pOK{Fku6oBD|9?j zo@%}55b^ZvXv<7ZO?_Uh?061uuuzlbdw-+}2#T6>^XQ*1UpeYU;VU>kHj7;W?@kX6 z_A}FkrPXZW2k`v@p!Oiu9Z|E*IpojzYX5~6LMC`ONnBO$fr|Ub+gDn0W4M|)3rovj z^`s5Z1fMq(3}QB-df&R1sG1Z$eo4iCnU9Opn&iXk2mJ%7Kr)L^q>Rk&QU#AOlu3Su z)PCdc&VlhQHe~6ttFuDi`kI;UPapp2^D_;KvpQM>_}CQ+Qe!w@eJnHc2+snHaa!86 zx!g+x*xk3kpYsAHydMH5qB0e^2P%b#6M2RAm2BldTMPY$B>VqZ`|7Z&y02SABqSB2 zQ@W)?8tLvX>4rl|BPl3d(%mWDSaf%HOSkl0N8k7NeSh40pYOTPJ%1=1&OUqZwb!0| zt~tgW<4DfbCUZT#x};>}PDIoHB<#00A0a`9uofIn^h7WU*T!eRrGm$vZjb~8cXzx* z1C%{nlM9|_3}OJ4*5h@e3j!%pH&>NP&=R!;{Kfv9f|yv)+4)q)2%yC&709PGbSXGGvY3wrIxoC!jc(7sC-QH=;b|+RVH}@BNWwuUMS_GbN!l zfIO|F9C_Uk(vq9|*>|!g>?87(r;Bw?3g1$r3+DO$REAyiDKT$a^7li_?GGbyNsbAl z(IO_W*NvHhX&9(0?^zR)@H{S8+^`=Jo>lrMpK_>{Ak~rjg)gJ`awU)E>7e%x_A3T( zxHrzIYAFe)HD4^xNjr=!E`D2IV|0W-sJQAmZ*M@W>)#8bYptvfaE0Xe#l==0N8h?E zF7}ex&H61oj^5uC<<`X2*)K_fdq5EMmu7Z%TQJ>CuvFXc;9+^m*K(4#+gyb#t4_s;bTb z?P}B#5_F6l2ec~M+R~unNm%)+zw?o7E){h~W-KjjY7b}_6+F6A$(PON_q9W_>0;oeZ-Vfd&N8IOoZz)i&domI9uJ!o@}s22`lUR#B>(~yyXml){IqC; zX;Xn~gnD^!PLDTn-1GmUni#R}P@P1lT0C|?PJ+qXRlil&c!{AUyWi?xPr!V-`d#r1j^^2Xhw-83wqvVpTC;1;=Wawg2AXA3rm8jBc;Pq8 zICvsJWSrv{kF&!Aan2nhi;LISFS`FOJSv0#JfM=e-3|yZvA=hWj6??rx-If#fYx?n zaJeKJRcE8gB`k(B-#g#FV@2>M_Pxbp+L~5;86#U#3@oCai6w5M$`e-Pr?E28x zca?OyJDPlZBhjGt2r&)V9eG?^>XA^$UT4oQ+SV_Ke8l#aN50(YG9W3DoGqOkpXZDf{Hj{=x`SG>tEi6QRQ^_7Y>0fF6#C%b^W zZ6NAweA;J2(Y3wU9x1+&Ak3R@Kg1zyZ)IiiuGA|E7478fMmPaV87-S35YOfT z^zCSu#7TkF9zV$RMN8BfHz1IkOOi}L85-SIBB$l$4Gxg9w6w$`!0ei1c|-zo7R-Vp z;8+_K8Ho-Q5RxX#7ldBC*Z>QSrqY)|Mmu?vWpasct2$?Ds&z*P6crUSlUq%C06}3V zT7q~T;+~#PJl{+saQ|WEMDZzHQ_aSnvWmjk$`1*PiDFZBt8v7^a@Ig3GnKnw2Bcr} zK{ht~+do)U3S5g-RECvE1_3+Hcx)*)~ zfIF%Hbh?RepqIUmf&6x7>>e6xCTTbRy|6Q1_8K8%c&dA>nrI-^^ED+gkF1#36J4`% zok;!ha&OPs8rS#{CxdWM)gGrdfoUhX6>tSbAQ|DOWIqqH_W?8=W2jCFD0N~oyF`fAv18+2yp8vO_ceZxv#1}3T~a%r4Ndp5R~fXm}$0Bp`_ zDRN2pPr5NvP2mFIQHMR{5K)OFfuNy^{TfSwyw=oICx*XY3!|f_heC6G1C}y!Ph56_&tPIV_8?WP?*1Oz-7Gb>V;-%G5{Tu_hZ7R0 zb(+Xa0Y#%~en>m_0q{=ljmV%F!gj|n^QkWHYo@$2)p=YKi9Qz>waorV_BMb|E@@|5 z^)B58T5WZh`huo@Zk<{gObPammGCPZAdg@m@*YgLL8v-56L$>lY6Umyy~##jDpc{6iU$WTx3)4q*}+R8$s z?V}MsG+qro=*oKZ_$M54SeW)MVWXCJk2XA65XLH$li>NfjLRu8K`pdB?rs<$j@U5tO4nmm{#BK6~*>g z(W1*n8Nmc1SNXlV3VgqMj)F$^>g-v(b1PwsX)V#yUQi>vnIeTOmjZaMa|329j-JIb$>`S(6^|`;5pqI z%JI3o9)+@DwB(SGkf8j+5ugz_6utu3Uz})mjCxJqzgLe7JM00@@w#kk5VApir8yCw zZz5CL6|a2Ev(#jMPk6ls2VvAqOCv{-l7zBCQsya(6iQ%Clml&xvRBnrrI^q!%xk-Y zcW~GM3g5bK89@t6dW*cEXK?n*i?N@UY}|$ZUlFOuL_$!u+V%2#XUIMTFlfe^r3l<$ zOH0dU7Kc5BR=WM8^`a7Gq_C+vp0z0V&aLpf*PUkj+#wt&_iAH`k>x@D}0> zSq;T;iah~Y`4XkB?6;vZ&N;#pCnwp9jqgJ_jXrvQC}g@=zZX74f|dniq0HH%b4L;n zoRzm>uB&Lm^Z+)1XMMI}#mZ2&vbLpQ+^LZLgGDJdXdy>~B>9yKe3|YEo5qy|$`6U~ z4KW%`3*+8HZ*)!PFb4Bm)Nk|t8NM*ggS7PYYHWR93}I%bxbM}~)RniRjM#ln*tM%~ zxvG)vYoZ)^-LGvxwwXJytDyogDSTcptjk39+c{IT;#|YjX74_D2*h;-@=dc81G6Xv zVQ03LI&$JwXOr{s^B`0LVQ^II9kqTa^Kt8I9z&u12go=s#$3?FIlDLLwF>N^kDHyT z8EjxcP;cIwFScf|L-T(_6Ys=NW?ab9zsDj0dVX|8IVN6!53B0x%7Wqz7ElnmgtnZ? zmY0`%Eqx1!hUSokm`4uFLU2G5g}&qGHmo=zTvjQ5m9t_PYUP7<#a%eD_Ze9-i!r{b ziOFb{DSe>?`rSkTAjMNIK>7%QG!y|~YeB^q`o2+FySvwt`}+FUV$A?$Q+WR0%dj3m zRurn-(9ub}=_3>$ACH}$hgOw3m@2h)siUlHWi-V5yBwSG+P}h}%{lbx$FS|`FD4$F zNjT#uREz*Q(Z8D+mI4mh)&RHMz~VK53xAr?jfvb{sMYSlllWVdhg*Y-tJWT)$~@{3 z`zvQegy<+aESGH@pgq z*eTrcdtM5W2fg*kf>BAot{Fv=Y}Ge zbZo}S&RswSLh<+d4k#VWEPon0sbbZxx5CRfRY+p#vS_Y7{6_ex;E4aF6Mm&3e3E20 zd>1bKqol=eWLRJ9ahSD(7GBql0s!-+y@BAMO_{=_ek;!yA>Je|`5H&15Yi)R>i~cc z?~T7kI6ARX@Okv!$^o(Ul|o+X@)J};Ok)}2@6?V(mS?p;h5|0kVUd%DVSxAYg769z_|lQ-ny(Xd^E=DECAtE z;!{#&fU|LW5Wh3H>JiM^kpjhivg0BTSx=COC!6iZ@)>V^xwyFQeYIz`JXS-^&n}9F zpgK7E4}j@?R+q&-4`e&O<5-vKHYPJ`qw^hoShR&>>5ND#Tx>gRLm1!9CpI7vd_Yrw zK%HrH$yu~5B~dO&K9 zqv4YG#Rxu#Hf0LWcWYuot;T19we<#reQ%_hnJB_elZOv_2X50d({DH#1y#N3de+*@ zWKh>sPXhZAF?0D)09K}ZRCs27Th!$RzhW-~nLt9qZA%J#Aliy|0`onIy=W+V-Yx#( z574H)zP>K2zwdpfkAN()zqeON)Z5SbiaYmAN`$l%%X%q*QX14T{6;$HDZS@v&X>Qc zZpOzn0Io-B$77llYdHtRB#|V%9jkB7Cp$HO0{xxQBfWdJMBnI@dQ1` zUERlFJ|6lm#x}IjY=9V#^+Nmn9$Nk%VP^-~C9c`q94UwC;b*`0yFYlxK|~T^`7N() z^id<;gG8&0_S}ROl%J-2{jC|O=$n*a-)!?!1U9%1drBfY9dW_YQ|%(R%`@~$%WQ|x zYiUfuQoTvg+wlhIBcT3<*x z=DtR6JL3c!O( zQ46eur9=4URPR^EU!{#6^p=&<%?*`N)Jd;CjN1KhDQ#m36vj=y(XM{kh5s?u*E()_ zIC+g8P?$t+^pE7*SMybaQ~DQ92p&g4>$VeFPTYSeX!B4rve<~`MLb; z{-#ngA@@s=>&1o?P$Lj3CNykIX9xw2a$&@w5VR22e#NURf8Jf_h}p0zG5+oGI9G1F zk`V0HN`XS!cyk3y<~o7qDRJrhPdQ)KsFl7)bJun~(|Zbp<3PoyolF;0SoFDPw>VM+761G& z$eM6Ct!8+j@LYqG3XK3&m?!bOcOE0lE95-2ghk8YPab6tJ#Q!bzCqVmADb=;9dYOw z;KL)6c;DXf82YunhmwA3*osn>-lAUCrLQks2^jKzT_`(e|j{-pru zIVC4j5fFa&vs>uR8~0Vjt(g2`X)D^%A%^8{NRq}|WPmX9ytHYi(&Gs#B7w+CY=NT{ zpYp+o?CYZU;mrTymLCX|K*bfPS$|ZHiF8tYOgZ(;;VTb&CLunnEX+5&vvo&Mm7hJW zZTav#o?9`wT&DgY&XF@b13B@#KP+2?1p~{i1MWoD$9%pEqFk_*{*YEN8 z4u_+SX*f105<~3~&#r`z;2#hwE*1&5g<+mNdTzZWdgLceh%4-1+8506>khP^@FBbx zklt<#Jn?!8S3Q^^r~q|-3FRy-IsVHuh*SIfOwRR!EyR7m&WAeV;PxP2O(liMnny*ih>7-Q*g_10q zoDZc$Ln5;Se{&zuVqr{2*BcXDJi3)VLqp423q?{-{D}!h`OVk}SnK%3I5gL35!3km zMeYqV>BC?AJBI8U8+mzz>^z=S@q%w>cT~%>v*MH`V2l7NIv%+M+9BF9XW<8%zkFal zS@!XfU5a||eBn$dMiRE18B{A@kV=8zDdD58(}zQ7?1 zMkD3ovQVWyL@K}Au{1CBK}S5xv{y9qNM5|#_AP}=|4uD8F<0kxte~o{-MjHC>X6NA zoZD)oiZZVtEkEGqVjHlGssOO6Y!~4(jQE}}&K;S6*B_9Ff8;%2Q1lIso@=RNVXr$5 zGci+39Qtfy;QstZ1Q%NK>hk?B`dgw}i>#Z=+@_d2O`B%(F7>tun3%M*v>AOd=$&OT z`xoA~DE$NP=LiZ}A^w5)M-=+70iyr4R&K6@SD@GN56^zUQ7Ix6yekXPenh1$tB6q2 zH8DUFcB$3tMbiJnHZaMZpwXw6vg zZPb^PnSmJLkMKkhQE30`QzGmnfAL^g%m&s_`agU7`%htHTs(Bf{kOON_q=&PcBr*l zNWO*(Q7m9u=?Jo}P~1)!Q!$z1T1%RCREMBAU+_RMXg9i2OPPNTD<=Wa-&xM5G7^5>S$iuCDHH@0r=z(LnI0U(1i^7@4x6 z*r}=569hIx;GR96=cynq&)fSb3$Xf>gbXEoWC2!;Mv&PNPZU-{Ns|9&I?veO}+5a z{pR;bq2n6Q2MwJPP=-jL|3jKIm}j7}FmF8Zw$(4~y!{+#C?oq~!7dOJn>7qcEApjhKh0*49#uxirMm{R8RNfQUN&0a z-tJ#A$fLpyIJE!0?Z^(X;fD4>GyxvYQluX$tmIQKUpqV7LYYhAaBM;mMFy_dAL)Ag zc-D{s$zB@i*b$nw4H_yh-$?P|4;>-_BnjTpVQ0S43LjG-6wC*ZB%M3&jEXG^g7RTr zKE?+x}wQ(wD9 zR97<@G?|=v{r5WtgwJe1><7$oNP8-%T2s}NzX~K~UtV9gsUf;tgP~@L?ZaU=-D$&7tv&^rdy`2V{WZCJ*lo!`)jEVt#tBzn15CMUMm-c zQIL=b=|>dShpD?jCvN%>3OJFN!>uOB#(L^yYU*Wp+t$h*|K(lxv9UxdaYbSIf@iYO z_d4aCN#Jpsc3?eQ+K2=!L`L%pD~D@BQr`U-pxPTQ&h&0}OU8a@^enonON=`k$Say` zRCO?sI29OaHoR-*@!2{#k$s}1#qK=j5|*7(AC-YU#O&03686Ey=AQV}e89o#aufGR z)54F#?A8Txd510|EZoMxq9)Su{Midr|Ju^il@xORx`c~&F#o*9#W+|AM>*`{>M-al z2<%(O4z_^3rWU(>omS$ea-f8`ROI5F$+x{Esy*6Bk8=Xa2VqguvFlnYTxI-DLIEs$ z5du{6xZqd#*lH$%j5QszV3A^avl$NAEA&QOH|RWW-cP^jKV@)U$d8rE+eJggWu5oZ z+HNa1s8&X!{yEnwF190pM#8UNXFE$u+e5uzV_88exK!%4PJ%&twKbRowEukxBCG`- z{d2&6$W|uYCP)g-X&LkLsjT(YwI<(ms|OWA<0O3+c@p=>>7;dvQAs$|U2SG4dZ-l| zW={Moj}Y?;U?-RS`fY8Zo2^eD_5QS^nOu+b=~xf;uNtCDjiv|VdlzLT<; z;yZU!x}L2Io-=Kxv9+uDm6bwOtZZew&-B7T`8nxVQQBO6!%VSFgmBb-{fbq_zXvB& zgpY@oCNIaqIFZb0Pv&@Y$;)5PxL7lhqF$$IQNq&4D^PB+tTcJeqrIT4skrRy3mnAy zJp}o$&)5v=@Pi~ID|{ZWH(Nf$_J111HkZpu>HF|y&2;L55o+KSqT!UkBJ-=rWUOsp z@m9E?i=V&M#9@iJpg5(pRQBqmorH&9B5h<%Lv!2tb`-2|4}q%-d%MnK(vPWo z)sRi+;)UJm;{Ib~G$fYYCq)JVzpV?p4!yUPfWKxD;dFi1&X=lf$6@L6s3=7z6vA4Y z1@Z6S$>nr-VTAVCY{oU&zGSo7YLxib1-5Ps8gwV1?ou3l72wkO>qV!U0nUF1pUvZ2 zp82588Y3Z)-h~Ed{@-SZC`&veMRSC4D*9qjmkiajeAbzc!1m<>Fz{|CfXTc-LP3d9w|#4J5V@o=H@IV- z>C@@@3txu&ughP#Hn?k)fx~w~Wu?Q_On80o0nyxgf0F7LTA5ym{BvSFOYK~>z`>(5 zhdVjQz|URNrF1T690 zH0o5@U&>>?vYgJn$$w$4!)5Pq`c!COZDWISto=^9?S4}yiF044pbLoCyaby42JUAg z)VX@ixK{)AOsc=)v2bL1i&SSC9Amvsddwl4vX)P>#0(An09%9$0=mH)x6*I+h^;4z z^JLSm-~a5Q2KxW#bNptGTWR$@zzU#8!@!#nD2smSv$X-HH!47`o4LyJ6x)3>N$~T{ z+qbj52qZ0skelX%>pViibQ3!+`$buw^OBHUnZ!W|FecKfHcRsYV1JB@KyH1Wbp`*~ zun8Wg&4=H=e|K*V=xp3wJ}}bK7Npjx#Zu|nN6Z0R!Z^1jmpgs1Cyt$kf<(TXz0)eF z#{WqVb*^y0^+}gtn~ir{Q2Ix}DFntkrYAs1RjMsz=uzI5M%kHjs&3|1ZQEn`*R~ZI z$OgXmp^Ze^6>TVZEYdGu67TzNfxiWa?BtyN{dsSukczYi~~i zwH+;LJ3BdJ#U$k{)l)O`TlylyRb983t~Y0T_X}CArS8m0j@}Aq@J=a$Rdmz2-#JwZ<|Lm5_tV5LFR z<(_fz$J#u`KcV*PM1E(mD1Kr8)v@Q!Xj$`5=WP?ZpqiIdv@A+Kw+96EjO)5QX&%yM z-Vfo8ez|3{US3{uI<-V|oGC}^%Fe%L7iyA5J}O+BS|8ue;FOtYRliN5$dF{kIhl3a zE9JMQ;GopP9!jyL^P&@-`#LbThBGHR>#}FUWVqwOFA+%?W1GSm=gCO@vCsXMPFj@m z&YrYRg89@37WVN#(C81O<0|i6DitO8aVn22ubxp;R*y59#FgPfC34_SDCK&%X#713 z5RJi=H6Jkcup2$vE^j`FnlgC!$ai~`qij*VI1M1SfhNP~=x3u@qMkGL_U&W4^_F8z z{au~m&7C`KRlo>O2tamPnCdf6(a>1QCPmQ&f7MKCK_Fr0jljGon2_5+QQylk49ScA z*8R8xAg&66=9h#VroA6Wq4?ceI2R=smx5jExtaUTetX|~Po!joC`U0Pe<#qz4N+bm zrek~gQrF}Bmyz2NaLcBoq3OP^-glgeY6rx-6QHy$+3JyX0=32ApGrtq_(A^Cv|}_# zKGO2Ff~{ba#}#Yy4Wn8t(Q7x3tU;s@KYh`xgVvj#1XOFel1cHEmC=$Vq2r@7cCzT4 zXydLFr=q&+aqp?2h~udR8bL3vfqgiHGP{KuyKt4ym{^1ia><<4%?eWo)wT-78B(X_ ziSlQ|mlK&rq@G^t>%BWT<+^|O0SNPZ*s89jy*&$WXSyx*CUS( zPwBqqpr@cGR{;}_G^b0?x;RpvGJ}0(#_!vY^{&GQBh9Y!{Uind@3z} zviPKEJL0JxBF7t~kbfp$0hoN!^v-qmGbU}lF|M06UMCrsyEcSIY!R_;9H`FZN=Ady?*Ky}h{z#m6tk z5`8Bd^m1wui>@zVb`Dd3TjQ89usmhS8hsxyXr0e98ubg^_no@y-n1#5RSjLwOK$Er zqin4GHw=ApTnmQ9=|GU()EXl?IhhX3o2ED7;>lAr+fx-4XWJusfF4a;9X?9B1!hl7 zihVi-0N8@YcInH~N9#;QgHdAgWcG)cfsbnZ9RgbN(SXPURUKhuGrX2|?Frz>_6j~+ zrK=p7W<3f}&#(~tL@p>Oi1{;7&9Z1;0B0z}n2t4tquoKa`R4_@b;bTtb>D4jjF(7D zOG}&L1;%uO~|OpOE5oeBO7^ zu%;dwVMpitaH9ims|`hM?WTF3n=)40*)3Mzdxonm!3U$+0WB2VVFS>2V6Kssy?;>N zes9)nC~4VH?Yhrv{VKOc+v;Hc3G2Hf(q>!R7cXA)RhbI*{0v>&KbSI<`H>ZbjFS>Z z`VsJ;RRL4;NwY1{j^pL|`Lkmh&y@U;0SemV&wX7rqvDtD!#08A*`Ra$d6E9_BXA^J(U2_M#{C_&^@@dlXfUDRG z8saIqxix_&?EV@4wWg9%77B@vQ8+GY(%BX;mzjM+RqlK+FLltk#dI~6p&wh#G+7A< z4=nL-faG6X{h~)#shIPHOM_SNXxEO|p|tQ(X`uP+gRuknuA0Er%3j}nkk zkd4}uJRBn}W6;-6XZ5_$RdE?_aEv>@KUio6cEq0p1x-P>1Z;G!Sva9vZXq2_rZdI zL*kr(J&HY~wouh$))Mn`oS4qH{iS9dd@f5mGBO;Im`=HDx#_pfb_(tmGTddGJe6+s zeRT8>r{B!<7lU4%e7$ddzR;4#Zap%3>sHv6uQH`seGoN@U5uY#X|1M%iqFAXhQ#4D z<`O?y`dg|~&n@5+v2R(aX5BQLJg`y)(Oy02DXqp?t;-$XpX=gRq7-gNj{wskR1!e! zwJfkRTU%{8ln;EYK*T-4htA4cg7RF49icV}~Tc_?)+aDAxm`Tz}9zJ!bRW z$$eJWehaPU3z0ddJnPSlnw8V4U@lWHEF$MbdodRF`Nf?i_ z5eW)9R^@b`%}dt`94uTKJiNi(uO=oYA1W#;ke(1uVJYL+?JfBVp>=lj{63!Q9xuvc z6s;Ap0=`i(8`=9QjQj^O32}9cYll+)(& z{rsUylO~Wx$3n#0n{c|ncy) zrB?XtawZ;;Mw*VF?D0@(%kTIMxIYnE6$M(=7Cf#D zN}US0)WD0`P3Kuoy89-rl&oxA1tBbh&Nc6~sf=hgDgmc7Nchh@+Ox7I1n-aQ8e2W< zCEu<(UM&Y)=7GkH7$DN^ZRaW|>OqACd4V#M%;uRs?=rQ20$iwuYU~ME zO)B{==TGg@m82tYfQz`Po%QfDVq&WLc}T3+@0A-?_Y?9>M+v#?6zHNBlf9wJF5am& zrjAcWL4iw+bpt8nEw3}sAPNRXhu96e&60DKjaFP+B+K?9WE$V17|YIfCadeUF1zNs zx@wAa*xeFR%;QFdtQJf0CNl#3{qvk`+^%K@>U+?~3V5EVCm64cAIhB#^cG?15P!{7Q6Z3X8dFeZW2xR$|aMw zrk(W$(wgrAQHXdt2{RVv>U$z=Q_AKnhqr%S{kipEG#-B^gDDiNshtN7zELfA7tLnj znP5n{gU~!!1eur?=O+(=B3wLZs!-vyoqfBonQYzp`4DJCM~sa?*DflWPG{RJP{YC^ zVsPEkoqtUW>!+QV7i`UjefkvlEdU{TP9~AT!9%ws#ThkwZ;ucJi#h|RT%60=4qZFD z@)>lUCbU>}5f01erZ{Ls{E+b)XD1*;f?Q34Qw=(O-PfZL^6Zxco?}mTm+Aq|I9lQ^ z%gpGS-P|A(*-DhnT%D-dU@DVn2%2l7uUv9M>tYlTXh}rkC!Bi0I==-GuP1IDux8NX zR(Kvsk4YWTN7VkAfl;USi~5761pyZ-stZxpSqGvSSg zDBCrz;Z(Uzp*ighnwj>dCM_kYyDcs*#>6Ml?(@2Gwhw*rPJZ?auUc5?9g(7^c+ zG#S#npAJk>a@M6F@tnan)&`c`_%@HoLYPvLf@{XCpW4O6BRYJMgr4aPYW|@ z$Y#GInoF;fpKE=iNMYH zW!Y?J_pKJ&yq)LS5g8tjYbufgWKiRiWhLfVTP(#-J&Rg(?6P!FW}va1bBp?#X*kn9 z<#O}&1axn3`r2z)qwRQu4y2f)50ap8A z#f5o=xhF8=G}^S)kPbTcqi;7IOG65i<$7CYyUIHCc5K$aK_X$PQ+9uMBW)r1!RvIu zEx)S!Vz-OLJOzXH<;(9oE(y#Aw4m1tayr1(TdIYFE?d+40~lh_^OK7L_`ly)uS?=F zou1qF`IJE{;89A}+v%C~#wGUOYZco<`%3ZnJ#sBQ3--^>F03nB>2{~=*ep@ualmYJXcY9YRtsKx;8S?r>|dI;8R!7pf@t9Yc*$nh{XJM(x3hD= zFg9)j)r;h%y8^*hn}~om1&Q$Q-#hxo`bs~lB-o~KNxxBzLm^@jf0=_XQ;l8Lf6YT3 z6OnT6t~+9A{(IXx*Y+l&%5?*RnP7p;+m{$jKFhn`L|`ACxs1W%x*kQ5!A)k|X+S-Y zTpw51DJL^^bbJ;Cm|l&yL=iQ$x}c;;$-t+(dU5Ddz40WSIDPUmw@bwLve24J+EGa< zNlm^hf-qr|Q7386Ll<*(E%zZaE8itOSe#M2ma`(O>{32mGN!a2rMhz*G}+~Xe5iVf zyeoo>Z1Ny&ZeL%=K!d&!6y%J2COKkHAWj2LUU#?sp0a`lQh8ZQnwkwfuKz?ra=H+h z%ml4UhcCdY)tBvCPsilYm)}5RbA8A+{jGmIbXf(eG?bt+7YBsI$}Cm5%=#rUtEPL0 zh7v*2-1Fht4^vE31|))_}t7Dl)aFxV=o} znfF5Q)F+j;_4+*V3uZ;)_wTSgF&NCCAV_=4gYQ*ivoJY7srYkbOXb0su3SWPH#k*w z=wB{C%JLgVtydZzmn5VC5}#Q2XKR$DrXG*BySrQ5aN3^dXUXd8m!~i?5BH|-+P4kN zPZpD+qMW;&U1}G55mNpndS(68GBbCVy6oEW%g>8F@-O@zGKvnBsjm~wi`_v3wmDs0 zT)FRf^Meo`C!`eVC?-~++kb{l?#MW+%Xm5JS-x0Wu!!{1*l3WG%oO3tY2re2oeq5i zuotJs!`rc=uljrWien`#0zN=Wr45;zTyUiLwXW1@jg%NE*zP{1Fv4V-JuO?RRNj&p z{OG`bFiWuBo%IgXxu_8eU~9sp12WY55}Rgzos9IBq}a9VK#Tj12p5erRR@Lr?$J&! z{boO%zJ$?LOmfP8+k%)`SG?ywA~F;iiqt%B94=U*+vM$VW8ovNSwIY^;%umFKsrc+VXq{;ABo%Kiid+dc)>2}1A|NaaEBgCje zP9NfIv_0p`qGjfD6Q7&rsQ&iMeJ9)!F&l6(cHtA_geNUg6;rn{W08#|OzU6DP( zwIvicds@x=vo6~d0m0$U9&Mwwi0Vu_3<7O!qi$aftM@@((w)^1z*NutvD|*R$h!3G z*Vs}^zAZsE(-;0+(Sq{l7$BkDv8-|#^8j~lW#81lNC8Z(K{!fCIXv{ndf)eJLnbdX z^K0O%wTfmXpWk-LSnFFl^{+v=1_}FwZGbv)T6ai}HEcn{qV;-pXM@tybw6s- z*4Sk0qKv8%bm2m-^>D^0nv#mrN9l?`ojLE`wYB+-)_N*v)p`}}&9$m1$0&R*&JsOF z$a6`-P5f{BZr4`gSzj}@Jf2hsRfr7}}R%OsURp$F`I=wZ|yK8Wj1cL@}M@&wG zYazGON~fO-3JT8l#th!hx?$Oq*zIL}uZUoEkJQNuEz9(|QZexO^U3Z{XVH@{NN?J9RB!U7PhgBG zJ!L!)|3t(r79^J6TMV*|TgFa3E-7&T1k|bC1U9QOxNBJ$czSNC7LFIkFjyV{yD!}_&)9KRCyeM6BmUZ9*@59VyZe4qt|ZSx zGOdqa<{abC#jceNM-Tb=@a9+pe@&neoDV<2MiRNB`gk=4+d?FDA=*w{bhyq-Og z{edch&#UE+E0J$iSfGw-TjDSgb!~Lb&K1eqCy-)W>`1tzaMD9zi8;1t>FMp;yYYo# zVL7{NCNw0(lDXb+Fj5|Uhh=yQx-2Y`46~aQ@i;r6_C`QUxT-dbn;Av-v#yDGY{gYZ zTFj&^{1?`#o)NJByiJXcref5oj|T--v5?&>Ts5_Wl1Ma6NBZNku6?Evmz`W;9^jo^e(_gvsoGf`g5$g7b+SBy$)+y>%Pym8kd}raI1m} zOUosQu2NfeUs!iBEib~;32e{M-%%I6UdRq&s|@SDx*3=y^J z@oyZhe;~zU@kj;5P^ptpilm#hOJJ>*?(Q|vn_;p%yux~K9=ooqpT0NUqy^G*od z*X?T_eARb1@A{%ek)WuPhj)Z3egIb zm5NlGl+{GB`*4_}7Vi*}y)Tb1SRM_1Zxl?f1?B2r#s2;x)to9qhEcf%Y0+Y*W+_`m zABoI0ps1L3$BvV8IJMrKRAekMf%iguA9l*J4X6*tB&N{cxE{tmzPX6I@_^BOt-ngX zR!72ajXv@gt&qRwtzEEF<-3xd3Wd|wug_b z?DU4&M`Wwp)ux+g)zRKK`5gGa*~mnbVhBZ(wR)fJ1J`tlE0h@eaJ}OuN<5TbK}- zUs)*B(&9C5^9Y6`GJWdoRvizV&A50xKQlxgPEp5EAI=Y%&Qo6X6ZG9gh<@p(Uq~S* zkdCgYDd>2d{!jPhKtugnl&`hrXJ6mkfI5t#)Ap6@khO$LJ^!kj;)0dI*f2&gLzHY?!lg{O`G;LeFhkc_=jO z7-Y=3bb}vXsM)SWQ5gnZZBe|U`3ZLDa6WeWIemzUV`dAQM;C_u-4f}~sL&1m-7>rQ zpDlZPxhcW(<~f#fB(@Wb6(Qs0_-SH#*O{043vO^=J;m28-8|2PF8uTX!{2?p)`sa~aW>_75<}r%zuYAi7H|4Fs~FEcM!c|}o5>dU)#?;2+QmR_N=Q^VhkGkGCSBky|_2#Loh7moj| zH=utkha0eAZ>Ft~4iW{{IIH_>yJpDH_y4?fk&d1O7|f=BUk%UM zt4H?oTj*Y(ZETu^g}nO^8JPi+q<>!yJeKM?N#SN(8)`)kg$;$>x6dEGVsPpF=R}qv zqov|(1Os-xCCV#Ny@Pz@AQggp^Y8XB6G8!G16J(SMIWR?M1hn@Oj5$jwzm6K9E^1{ zng4uBm2$w0J!b@bQ%YE&Gk5OEsS_m|zuN6BY!T^y#-ITfM3QQ}0Soq$BAUoy_-B-M z#O>VZKt3Kd3jDUZvO?z0qou6qFgSqupKn%F5)km2REKD@nD}SKgmv*H5~HPUZEP?K zXQ5J(zEYLWeRDFjA!#Jsh6OZxxRCzc(C=g{n~>R_yK{r}wLXLIr8#8{sUEUajU;Hx*F3AUqePKS?= z@6DTbu&UKlS5{^a3kp(>%Af|>06chS5#Q%i< z)y-oFSk)zD4hK`=(nUY-5lkcxdUB<#s!2*osdhpSz7w?oQ@o|c`zL6%1)2~YqI~_C zRIg7z0i}f|_su+`R4_l7@_9RHKPkb$eC^OJXX(ug5hW&G-6bH+LVekvGQM7%xty9p z4F);x zP;CuvbT`i1{Gp-6$x(l{K-VMEz`}Wvd{L7a<_O)C`6vIY#M3R`DWdZY-?X;igWI#tNwoI|fBC zJEuC*;ugTb_!XDRzmr`ZIJiP?B7<#i(Jq4cZ_v}#WlCiId(=#cy`Pjo+_*vr zR(KGisMHc`)yM|P{*Z+L3AS~j~=YC(yfZGfdYG7dK6uR(u7Yk9Kr?m_z z-uru6U>jcXB3177jeSP&NU*B|pDg4fGT(le!}{m6`iiRlJ=EguKZhC;5)u~|_a{(h zo;aswt^F_7-ZG%6H~b$4Q4|RYr6i<5Qo6fSLXbv6Vw7})ARyh~=tiY$G?N?+qdTQb za=;iohwtwnZ=VV%dflX+VLgu8iB?P?rO7reSo}r5d`15~`N!f4z*OzdAw)Q`f0`NacN@+ni z0x_S<$OZM*9OA_8SO_0+WC~6q6 zi)gFLryggsuLu?ZdV_RcrDl(P#u#$GSvuz&C6-`S&kAu=OH!E*yE>qJhUiFW3eowA_0N;=~vm zR=$M0d){B%Ay2RG@3!lZ_sDhZ7HoEC>o%2;*T38ekNT9BcjF?*?W}D+h}6VH!Y12s zdL?yr4`G~pKZNn_QcHvP5v~Lh+rM}v^e-Cg6wQ=kN7p~J>m|zb*ERN`o#wq*pjqw-bfO2 z8rym11jJ?wxylo+Zwzk_Kg!sDA>46M8Hg+e5PZtvc$9w=!l^yHDk$hFOQa3C5C>`?9Ws-EC9w=r`(P` z^!lP8E9QGQ z1tk9&?O*fx)Ol+_9HAQ?XC^D&NQYoxyHlvPdgM>MgU9DJTd(R0cv=)Txo)Q)lP~TF zjwZw+d@c`c9rw@p&|KtTI>Pd;YhkbdLUa5X0Z^9ve6e1o$ut0k=C7`rP+oexWPc1hGQmop0H;E}I>`~EmlmddLDkmx5 zzdr*ClRsLoFBeW10n#HlNE%Rfr@s%iFSi?(-+C^3mo(Yr*0SF}d|)z3V_-iJnbF-a zi7&~qEK^rlBVV%%0K`iafKXWS-x$31oEq>12Gm@ypN$UB9!uQcHIQN3y|+3+Zs!Q3 zC~~&{3%>XW=)ezk%8I2vbuQO&qY_Ri(dp5jX(rx1%~=6>G34*0+i%ygNh)2eZ1}1J zj`p|Dt1Vzd{*W21x>F_A`oQ~x+J_S5N7mR%pH^=_7k@sDcn zU%;dw5GI#T!usF-B=xw3qi~3A%W)dC4m88>v@Cj!qP{a5XvhFi1$TFM9-~k~WP-<5 zG+3^%HpA7$We7))eL{EM1@K2J)90EiQJb7r?Lb-29iU}zZxYPQStFM`n6DqCz(i$E zlU3LqXN!%`(Fkgl8#JW$CoutCEn-94P>2Qi1cl%R>UqN(!mJ*>FC=~}RJPvMxb065 z>mcl=1|l~HH_HW9l4)eZ2aG+JtloZdV>_)3M5#=d8-q}k!q|E+nr34!uO9$vWZ=eq z-*OK|Z3tr*#sdTrfTyoY=~tPxFS;YdTxeO8?r!Em#e@pG$LV*64bwAImkkCz<;V*3 zn9|(8<`(o>d9C)lTCR)pb+#70G+6u&Aa~Q0ST)^>kDm~Wot@I4zJm3Q4K=|MsJ7wg z>^L!hSJ(@IhBOXiyVxAaV+tX=!q$r|i6NwOVP(m&LR`*>s!5$qLKD=u#bc}OcgQN4ww))op07up_P%!do|BW)#X39D5fkd~d97lx z8)2xcBD8=S0~bI#;zPqYppDy9O~=c=#wNK{?n0Lqdy~Jm@<*(50y!q2_sd4I*E?M= zhAj++0W_9{_jL~mnGK+vBn{JTpG1#yb#YUX2|DNgGS>t-bdpD@X9@Q!PzSPgDMWJ- zy*ocM?ZR+~IsL1xA>?rXk1p=V9y#UH$4{Ps_g=MKo!(O4GfQ=JZ28y4M7+ZmY_NfM ziYkP}#1*Tj7&^76rSsaF&sXhO;L@363vcdXsTRRlwrZ_pKeO<<){h^bL;hew&DMj7}l zvgaifJd{BUWFY06Y#(G;SXexyQdwW~m!%ypK*0QNDyi&A3y#nwo%Vg-vDMw`mD}FO z-adJ_XhE!UGuySKCcWFMONd2n;fQ(*1s*W(#kuY7cztQ>&9+2_dyKK|Vqrw<^|T=) z={Qz}#@&(UCSI7%in#xf=U`*UiNV7<@je>|ktxGW0s3KxF9l%wV(flVj7xT||M`mI zXn>Pd+-ycd0F=QP0F%4yYen{rU5? zSokBWG61inv$mg}h0lyZxX8O(k;}eYk76M96REX>X`E@|{x^LK$E_C9`BEWI!TOyD zwe}SqUSPu6j|gUA!;diB%DY{SE%U+SY(K4RuiG#C$74vh>BB&Y>u#O>pJ1NPirXVu z!)YVT;sLjfw)Mp+nl~nH(~kNa{PJ4O%Xt+I1#2l4B8BBM=P0{e3kMc~mG+{{Qvf`j zq@NrnIv=bKduO+w%I>jXU@$-_<|B(oA!z0Zno5X;yOkA8?Zg5cud3e*Q>5m%`EOLZ%F?}v9*B&}Vc!d8)FE4UQAiU3Z* zvOkf5gNYFR!BSXd1jz4NH{BtZQ}VjPz{d~pgYeM?&{TC4#JL6FuDN!+aceoeXby!a zJG>62q!&IKSKTi1MSuT|7D6eiJkv>aQJ3^7&{ErPKeAqH;+3+)8J&> z9kOcs0{~-ruMvTF7R1w#AD2^l#kfEK;+};mNiGVM5$NP&5`mr7A9N$W*o5 zN3X^vr`f9UxjzP3+}4-&`-jwlR~N*vf zMn3-TI6pR+9b*EWOESr(k9H@eTkOSfn*jyx~u!Np-AIU2^bsuW}Q`O z5ucI%jMhND1@N5TU;HjA&m5txuR6E!EJWGY=RgCRo*N|N;dArwSZ##io(^eifL$!D z79RDCe#-mgnzIs$j2tAj{Alp|V7b0@$Yr^Y;*e)b}v49PG_5QjrtartE3hs;Z0=G4yh zS!SsTIiHaL38``th|9e2NfDB=RqRP`~5wYme0GMQ}R@~EOs^! z*`6Fp)-EV-0|z#356EPaH{b>@2rrNC(Ktku0MIkrhVt^$=mfT{ku1>C3*1oNpj{t8 z7C8!ybr*yc$mvn*M=j;pI;AHYFhfobv8yqe8lOXhv!agp@%Nt>8^5SmG5{#UtC~6p z8Mo#4`X^bKUsLOg-YT)vW$=TiT?~rc&(s9Ky8X>Z1(;S3(e}KqZfRKZ z;f+6J*%v`M*J_(&t5ljI&8Y#VHT6f;`-%8BU+^raV#+APBz(m3^4ThF7b2%G4U`uk z?>rU^yXzc4ZN}5>Kp?E5&7LA7JgBqau%kL9a@0C#Q_R5s(9 z?xX8KzdP><@gg?i+DYk!cwnmT$k9mvO2zs%-C^*@E08f4T#eJ8^8IcPF<`Qgd9}Lr zL-W#4)rm$MMBq{QKzpO|zHWJ0oM{Bg`Mj?(&?>mhI>)=WmC2aPa97m^m9cYYcl^mX zUZ1246D_x50!^;#X;#_kSl5e;uDqPIink#3g=mcIfKwKrld68Ot0YArO&azZi2aH` zM6nN!lJ_N-Y(_y+CsvhVMu^+>v@LO5vuTlNA$vf)EEC*r8})HSAikaxz49cj?PFRa zO`93Q$?dkD0m&OF+bDdsD%?92Wjk-60n;uSX`r^ZaLvO8QZ1K*zQ;fvlK)pl zrn(qA)cfEa){7Tu>RBQgZ)uG~Gz_7r>-rhX)?wkV%1oj=K0d;`vXv7%mbfZU#(Hc( zNGdVk^aaQU<^o9CbJ;!Z7ca!2?f29uR8%5Qnh9bj#{=X*e5SO73aO8@CGKVyr}dMm z15YEa!=aZO&^sU?3wa^IOTsb<^sRk3(k~)5K;^fsq(Gj)iS2IU?mJ|{`b_U`^!^LA z?GMxWIILUkeonOcdb;|CWSHDE^T$KXG@Gd(BKM|k5-1l>sJn#QJ3C`Z1Ao8^((?nP z@C2Y~b4uA=2>{)!mjc;?GXUwQo0|G|Zj<5w&?5`Br8D)Qne?SeY?guXf#C#9^plg5 ztxLdfaF~9p_3quf8Z$Vpev?oAukj|G=In&=Q4eE7g%H*(aA^uFFwAy%deqC)4MnT6 z>T>8llYTsZT2b~+#T%vwsq;~^HC$LYZtPlIhC1cgZhE{EHkphN+^dFBH(;vFtE+RG z{|aooZ;z%D8b2D;3SBDmcHIT&cB1r%^_yIZn(&KAD$axT^ry^73Pxx2XPR{rb)VU$ zq{R-NU+iU75?lB^LHlV(UIq{$bVk3c^SLN*Sm6iVvN&kBSitboMv;!aI6REt+dmkL zk~awGot1-U=|%)Fox0_4#pg_|Ha1&b6yA?NDvHSLVog;u+}zvW{UOU5%WhwZWy^Q| z;9mY0il*jgsh(R8ej`Dt&b$F~%sOp=7o^q9Tw`JeM-RH(o&o2@5_1@R+_Km3Jg45Q zYy~`9AaeceTo5I(pT?f`3VE|%DI%t~`{b#yq5-G;!9v66CO>gAHPDfYMD9!6vhzeb z*n)_cD)KqpCG7d0gXjbO)}BPfMI|AY{bzijc$n^ahYHlbLuDFV0i9lF>CJ)+K$WcM z17pb37yJzuk9r2q_R*V88=!X|mi>>#c7gmz?e<8*M2SkNU){W%^@^RG+w3(ld&RD3`)lEk2Za zY&Sx`bBt$9JhOi7!AIX8QskeXO+bPbfX>hiEE-e)>!PLr9=l2A3BnZ*RCkSyib+w5 zbSiNZF=XZ&@0;;@G+^LxHz6%LAlZ6AVQV}zL9fz)A<3~7ex5UEx7sX(i@TGscHyON@4_q z-!)UGMlte0b2LAzfKMBfQB`C55YG1VnkMG6b!^Tb*u=z0#Bo}hsgDws*FUKVAFJc+ zHb#3FqclK+H$QZw7N=wc^n!BDkotRy*1hWO$Kp-LcgSJ6H2E0v^yicg;QXA0O)^8H z=rBgaif`pLCAp-{Y%)0E)92^=a>)uFVksBIgLz}6$WIzn*%?3oRX%5craM0ii==`VV>1e~U%e?%q z9FvM5ueExQ;lS>S9@E@VHs1ovmG>cMU_x-GG?}1HeAB;6csTU{!1lg+4&*?Lo}a0N{dV|3N87pCx7Jl-1E&RTmb2bpBcMed^+H_YvjfHoqcaxit5JXN?YoNs zzUzqRcwS!qsSY=tgWs6AR}px>6_^?GR^=&<^Fzt+r{jOAY4>=@NRdCs zf9Y!XB;72#BRxvad!U{4_u_q6=p5LQs+=s? zFL+VZ0MfZGEu`l*@=)rWG&WhzUOzn6Uc-2=Wkf6YL~aYa;@ZpiJ2Wob%iBli3V(Bp zWUwiiP{8n^i1Ga2C2@|jA64nl?IVFW6V%?!nfsZ|95?q_(YL&3Xs=UR-s0vePexI) z(Y#w)v|lsO^hl*2xr)nRR|cqdmAa~&PtMLrI60kInETQI;tF8WU{c~4T`&N1Vi2u~ zSUQW0ilVf_yCQHMA}0`o`R7D{z-ogiMs*YT7Ldlf)z ziPl)VgrBNx+`JNnJYlG@QAo8{?{oJV8XQys*dx+6(mjNyY%?_h77489cq!564;>Y@ zvDI;vIm5&Z4_L4}fSSPi@1hX0`g^;hmu&L4$x=>W7AknAn)2!HafM#x*TRw<{?VD4 znh%u$GQU=;68>&ks?39RSo%gAfSf|m9@oz=g>In9Kf}Zowp{1%Y?LFU(Ivg!h8+yv z-8yS%_45Z_~-uc9l=^A(l!h9>doo9beTw_K*S2 zZlJWD@NJyQ*Xze&aEJ{3+G@aDwL<}bxO^`k2Q_KGUI|YO?7hp6!L+}bMNEh%XnQjq z0?^}CWo$g`M7*vAWks*5jB;QKO!45{ao}fef@?wa-G>e8N16b* zY`hJDW(#jFKUl7o187p-Sl4psv?3v3FD zo6}J6C82>vGvde^Hymj0r`6Xdb2<2Tz0ZbLq5MrolD&}R=?xy~TL3mt_EC&$ z$6z?R1-ejznk#^s^<&>qWiQKQdWuEsau@h><8m9g5{tnnm-;bh8U44#mGYrao+SF6 zHhxp!PYsKqrQiIEwf~qHcfRhq5o`50Kz(Z=RE`rV(Wn>8*qzcI6dwL-dT+iUj@M~w zTE{)B4lCSEwPYiGqC{(GMUkm>HcaL%6H`lF@Xr957H7r#Y+)?83NE%CW#u11bFCrLY*|Z#N*f)Y8efQ`V&wtl4oq^(m@ak z_@Ay7i7^g}@u)}Sc=~_0J_F=2Gs-B;PkbB{dvNQZ>vr2E{@d zE*>#VVxP=dN+m850|1Y90C@rgisO*ld5)18mCRP&lnT<9j7jST#&$A^> z#Bb0@B#cWRJqB8gY^Q7Ld2Q!Ab*6!AY`LA1h{P6Lw^6Et#)Y3p1`x}14|W6nf^yRr z2TN8Q|8`_a%YVSfWRFXaBw06Q6D=D?c##=i=kfInIhgX|;_Zp)iT3_+V#(san4L@%Z&IZAS<;g>B(NgHI`489BbCgr#<9)_+H5-9W8b=4Ul2JZ~ z2%d?<-Uz^c=?ce=^}mG0QKiNj`}VajEew$sdJpn%x$3}pgqI^08VuFxQ({AsA>ToB z#aZ4)h7(LVioCB{Qgtilvm0FPop^RPj7%yhB3p!irzOWm^{TgOLL5w(lp+*{{6plU zQ{q>BYMe(rKT`v!eXFZkkkw%%gO-sN8vspBt7j!vX~;&Y4$c!rC=}^IJGiZTdCwNP zCdMIO$ui1}gjd{L;C6re7}a`4XH3V()dvA0xnWsjv?%XOgvKe?6Oz|ntkO8gDVO14h0bP(gh%GiBW_5hsVXS}72Y^`l4!6#wwLX77 z1Vv$>Nm|)(<-KC_-aZ9Zv+m*!pnU=d-h%q_^5Vwz0iq8`TDJdFa84XmtN;l>6|hfp zDC|Z60HCp+W4$AJ4$uW{?&Jyf_xC5vnxFuFqJ%Bl&J&Ap)r>^EEOWK1!jAVdM{Nwn z{?ASq$g)ywZ3UPBgt!&sO{TIC z%NL|)hOQeyOu=vuxuNhf+(CMOiU&lL(q54!%jam9txc|L+%}~L1d1u~@i)bLMcNHi zF3fj9`Y+y#RGm|EkNYuF&$eifWDh56Ey$RsL78Q;LYfHeRbTVAtBweS$B|y;F6Nzvys}mu+vXIs+VCkffY^R1nL@ zI|O&n%HVUddX%r-&qROHM^igpf0Q%drTK%0*LYl+VKT`+W!)cV`KREO&pX zGu7F=vCacMi=he(Nseye8kwkG09ra~0udfSTHbEF4m>B(hF_2(PmbokGdWzBI$=k% zf1E}r1a=!zt%9#)_`IE?V8y+yOPn8-_+vEt2rZZ6S^3I)fZRr9pBxxh5EuVpkVNVB zPHce*-Gs?GkjXT?(dDH%@`6lr42t%-fO@!z?M|`cV00S%N zjpVU$^~bPHW5fDM_FsJI@w%^Fh+gNxyL;`dE$DRfzR!WP$XLwv7Uoky^#!A}tf-jwOTsEi)SVK)ZbZ1_-&k#R=EWbSm93aD^W%RS9h$6nvgEgGjiVzn7jJu7hmUM|FL`!l_2Bod0J?>w( z%#~MOQ<4XAn|hj>EPEbDJ*nmH!WsJWiuk`8%O1gPWO*tqJ!&Bf*b70_W+q6oZs87qEaG+;H@ z7#R=E!wk_J9Jl_w^@fR4kEd*di2r$%3g4ggFB&P|Q&z|R1zpOeM#WAe1zm2*{=Rx? z-c>xjb7(gs-cvll-srta%WuC}_O-xrJSSsR@_Y1XpW=XZ;6*9VrIH6P0XrpT_;=mB zuk9Q+U zd~K>#k0ULsSejO7H*Z~B&gviMlr_+I(Dcs_jEuk&eYgz;)B{qHZ$c#cJMt8HBk$U& z?B|V&q>9$Zx&86u-0foYg|dgT6XyPyRAOVG#9T#vzVy>Z za<*LxQ3zk+q?0@Dm+65w?HjCZeB3mZ<|yLSom~7>2P^wlBi-=YYGxrFxgGrate~{U6jg9Y)(KbuILfJUHeTZ?`u08b+_ju_?+T5iLb!E~d=~O$1*{)iBT4Ypv zAv1jj3GrO`-{*^#KN1>NFHR&={p8acDQW}7DwR}`I zv)$9)1%oe4{3`lJ(rI3A2km~xU4|m-r*~>w$=%?Q>#-8_yCpNa`d4>}9bse3B`90> zynkp;WqZ_YW*K9)?VihzssWSx{6%elbkw$ZkSndbT6TDo`$eM1v%g;4uRr`K{`@@9 zWvy(4Tc4wl13FojGO}>wAATP2xLEnG61Z_cjnk$XA>Fq+0oz`N#Tp~45r5#_k`YGG zq1&b_I5I+CMpjm~H0$>8wX5Ly@$WLtiMi!W?m=C}Kq+G5iYX{8++DKcbFD2zJw{@eZ5D$iQz#jpYSj*Mqo87Hp@iy`ec{UJ=AQh3g)DIwY#Yqo5mN{J`y zF9^a%Uy_6bHCWqi#ar4aDM^6W*sSgoFA&OTHZO}d_u&g!&5*+6II@(`2G1jSbmhk_ zJ^}*xyvOFa5QU)2BP1Tx#BVw+P1#3GS(NNP7W3)HdBu)T@u>XC11c5HzV^z8@xA_d zSB$vyu(e9MF-G!YD?C#F9Q6T7HyEMHo4TKj#d}iY)0Ra!3-Kpe4n!R4Q>Vo~9vql) znyhK?bTsNH!nW3gy2fD+e>>MJln~@TH^Y^sHSoj1BPiG6_SFmK?_t;!#;4oVrQeVn zXi?0%69m`0Y^6C#xUGINH&?|_A8$&~a* zC*$OPZKrbJj7x&HBo4Dl5wYYk)_xJAN;`_uFxfq_=^2x({5(uQQ6J+cPWg?L{6K?6 zq#@G{($Kl-w%4rUB9YYZ-fyPXtRys!NI)|0%PWT=%0W3h{$TMzy`!1({j_=srXqC1 zhUSDgU=tz_VKSp1PSbI222>uco3aDL-NXgizn~4QyU3$D zhw+3rU_Sx^f^t5pI78^V<(SMPfqr_gcnZmQv}l~iL#ah{7+C%2_*knu>%B^r{#jSS zBhr{;mhtlKy3F5ZN}Ci$PiHHXdcj~YNJ>HbaO8hbvs*kCaZ{ankzC_;qF)9`G;mN^ zXcYc$ECA>g;8{H!b-$H<)r~-q|E~QF2136-95&?-HUdjdTI!ovwZRcK0aoF3tm&^u zYiOb!v&~Pi2_l`}hXWtuhbz963Va4MnblYXx3AV1hi^|Lx_jQ<36T$J3qJCCM4;+J zJvp26qCq0~z2(sb<%8k7dzA3ZsN*T<#WpWvf>d!A z%?MHa5fG=4F$uHnXxIr@vG}eItX@L1s9m<+ej2;qXq(B6%p^Bc$pNj2 z#h66aB@<%f*qSx2(LXTK0l6{m>mLkX*p&5EA)=sTOHu;99XDcEYq4-?+&9P?1h?$G z`iU_d<*o?ml}o~GIOwppkoSB$k^C5#yB&hgexwiRYfm`qMn7zT>Wdyp(9i> z&gn5jQx`=%FVlU^ktKnV=KAPqTy~I!<>N#47-kk$*pa`278M;VJ(FVQ%tbxAgWC~syO8q+EY?=h4c*#_11E>$F>G{Cja;S;k4M0)^o<+c6;x{#UlYv+kUPYmr0}T zHwd=IyG4cPd_QfEh>&|Kd(Y#XsNPp<3-`WIB z64Zh|j~s?$8r=^G^Wn_$lyqL3!<_kRm+q?3ae2QXcnn3at}X@IF`=WSSxIRtw~sui z1%qE`+}rn_J)Cnm?Xk~q(0+XmEh^rUTgCG@Z^H)EUV?f_gTXZly=6RFYCxnQ>=hby~;$Oij%_YZqX zptlRmrciV#D7E{7=LT#AS&Rvi4b&hJM}VNAVG9CWqO%f%318Rp&?g;NLff-Pk`E2{ zJ^sAo4H}QUh;W$UFqPybRI+8#mln^_gQi9BE@d5Fb?ODYeznh^U)#n|#18y6dyG|* z(B!fYHMRdg?8WvkR=w@5tv<1Pqo=Sv{QJq+7&kN)9u?bO>9u*^w1QeR()=dz#8c+Ew<*xpkpYkm#RKi5Q>&cENYnI1Ik z7~XH6U7yYI@RhFa8x~AUwF{ATOU5$$BqQ1e(0WI#%}d8L?78{McW+o(Ut^%!l;Wa0 z4?XOJh%dE)N3f&GE3hC+;Tkd#DM^s!EKvCo;!m05t(6v?sM zYm$hId)^@=ln0)O@pRgQ#H37p|39wxSZ7&2D1;uj@axq_JZh1M zkyu?>T7u*cWire!Gc`v7n{6K3*f++^-xvdmsGLl40dg$uxecHr!J6IUsk-51QI=) zKV01wwB$Gw!9KpC*cjXVS7JKX38Q0iHV$@k7~a+@a_w}^O_2ZW zALN|O9cx<hr+ksHFD0ci092{+Z8u46==-;%sh(>a_X&TI8tL}L6RH&it&VO!#2 z&)Ti6Cr&~%Ykb<0yUWb)su3Gz1MKvA!-If<$Y*$Jv2k`mC3R(w4z($N43*idg;G&P z7s(GwN4^%&1C`LVzptxHOSam@XGl^H$7vN+<)!8EaX7P60d)Thn{dpinLPcsE`Wtd z09SEa2#Am6qms+)abDosz21$}DO-u(Dd~gCF>E?V<49;p$i>p|cv(Ff?3xGc9UP7Vt!`1_lM)u7%bl+`URYCT z48Z$m{QAttxq}|umgLxT?RmrWD&Kp8Mx&%>m@>1q$6UP%l%xm8@DXFfGco(}g|C3$ zKen_SxnAR<<5&;=__$c0=w@ z(*0>CX~5kkbLh%L(~3G9iG8?d*|0>t1iMP9W73DTpuFk zFwN-wePBnZpXCd`u(_6-J+J^X%M;7DG7|k!@xj$G@U;v(nu^-4LX2r zk5Q^mt$@kOWf%ARo<}`^FCg57n7+^$gHMZYgt7lTrXiDcJzxa({S;^^Of8*~euI9Z zUVu5v^D`VVau8vvVzWY?@jRhBh$xVGW}y8(Rs*96Yyd5|)z= zO8OqZ+6A%21t<&Z28WW3Jbd6t^M)m^CC80SfSCH{V3U%EJBmJVU;xB3^{ zuWg;`=kcFCChVZR*1r)rxyUSONI3hhdg1Wb@9*|*ovONE**?4Y+gSKRa&J-khIx>~ zRZ*Dp&iUq>`L4~kcfJbJ!mqW?avoHe(sx|DsvK$HhgF=!H>j(>5CT#JRi&|&@F!Gl zH6OHhNF0@6Y%k1^eYpC;(JsAP!1wt-NB)P94x0b-3V8N>-~Ru;Hu%8Ch=Dg_xnA!t z^s~IG<1>HR?|r)bsf{V^ecjO>d;)l+dA0>LpamZkVWc@X`9Fzvoo}W>so}wFL=+YH zcMBQ{|2~DhCd%fbCHUjW*LfcQD^a%=UES$%hcjXF5BNuL5(ZX$-{Y~ zRWr~%q6osg>4`$={sdlqmI&||j*##xdS)t~P>Eg=y7L-$t#X;HL!&~yb2DlPp7?z_ z{eG>KkC`2^v2tth-;dPo{I5U-(98}C!Wo~Rr;vR7HwjV2(fD>^ea{{An$qMv=GCXb z515gzLmy4e;Ajb5uOyj8Vd&_Or3nreZD2X=AA+qtE!TFN?Pc>d7mrTq@Y78h;~y99 zO)vb*ff{_R6gSKrbx6*g6n&)qf@e44O_yEJoJ|nZo0Zp}j3jlnjp%VKtw^gaLMSDm z?;`HcU|l}hi8L1yffX;C$mHGdo_^v+|6s-Mg(6ryMu5U}k!EOlR;jP7YDquUN@DmV zUdz>G>JL>5lzY>#L;xEi4(opfRI+vXl%>Pn+i`zwi$^a!INR@*nU5~7-h7F=N0P(} zQ3*A0QVznP-W6ytQ0>^^v(-_k#5dM*lgHa+6ojw_E4yBJg&NsUHvT;!F}#c@+$sYc za_dC1lw@d>cxN?3$bdb!nVINM#6L54OmW+jeA<;@u@n#X9?vp<;^9+FI`U}YCNXTt zFR8;#`A1%J9&{#iNl_B0k^mfyuq_+4y6*L_>o9ySKs^{R1L;}d(0xNTG+3piUkEP1 zs*(F#iVTk9QdW%XHs?08sFtfG5e|FQIJ2KqaB)gQl(M;KRprwT#!m#tJMhplbrsG% zx2Tw#;Itej?oa#W+i^rtw~c(}r|2e`=In9lepC`7 zg9*PUr&Tj}fDn6cW`VbM?=^MoT3UlqU(M@_wnY*0NrZ)dwtkLbZEh-AAaf)gANZ=i zb0oYdDkPZ@xyr5P!M*SZe32oX5-~Q%KTPpGy|f zVGIz??vmo|`A602d%+FXWtSC&Cl@tL~@(*6n?<1T$T($CRRJhZ1);Ibv>E>YIH6fe3RWcx!!T8zCsa4 zYw$>$y7mm&4Tz;rEAeHZ2C(B~>;BTOp3HT5Gxwzzv09@=)~b0~1mYRp&E`5;zP+IX zlX3SQVaxMAd$aGG_RSo^OFJY~{rRiPuF7_nOd6r{hBiyJ zlt2N*Q{UEaHYT;xvLudcpv3qmEp{739ATNgxOZNZ^_8P2mj9)GosQ1^NBkZkM#{R$ zVoaOW;$Jgy{)XIcy6ulCYwQ^xcWP#-anj4kVMbYZ!}G`Zc==x$Y8rl$iK7eB)Fbt5 z_P2SBvsya(awoS1hj{>%U^b}YTjL&f2atZ^ZNU^=_XKADoi0;3crH4(4m-bESHGDbDRh|I0xZWy~N)DMF3|hSJOT)1$_V1sk~}!*M|jVIku8z zl=1lc+vh_!zRBQo?q{6nL0>#cM~{ZagDf0EU%7-cjJZ|M6-Y6Wv87t{gJ9*F|y$gGUwYyna@4z_nrKft*8RoA;sg^QVr;&kv!u?aWf zY%-D_SvYLef#)qZTZV~C-+w*u?(Zz&P{hcYdLNSYPBE8_vjcmY7S^6)L#d2z9&Gw_z(?O8m5gS1i{IAq|Al z@%lHvq_Sp%AjzmisrwaC{oD>pc6X8|M;BNB0^7v$rBXR5`X{8^`-kE$0S5koY6f40 z5kqcfZZ<7t&J!IUJd{OsP&kgWwE-@9ass+PR!!fJdduH^tM1JkFwB*L=GmJQ2YrM5 zy&|zs!}Hz&*7hgY>{hzCewjZQp96n>8S8ub2gZIb&UA3z@X65Qp+g5yvB}2>!Z4#r2urhhiuP*NT_v>8ge}4Vl$%get62sWv zf>4%wjVIjYr1vYuu<$&TL8dl{zL9oLAxj4Rc9@gWX6IsmUoD`7@J|BrZ0M5hcA(m? z4(H}1M1^R!8eRdlV7Yd0nZPc=e7fa%Tzi0N&))FKSnB4UJrR+{gg-55?B4VtkV&^z zGo+l5{uWkiaU-0z0$o%Qf?ajP<+AWz9t{^+wUyd{Pw2PF-%cI5wTPyOr8f}Xz% zOGSiR%J%vCEfP)pvNJ&G-xqfbddEw-vS_lzC2Xh6q)eHAgOMmKt-~ZmrYImi}6Q~`o@7y9D8L+Efd2WA?C_BBV6Koyc^9_r{0`^ zVtR%aPR@y+m8@8V10dC4CA{q}9+Zv4RodAGIYlCrQ=(ZkA`oHl?rvww{Ptw!a)ifT z`TVcgPOsQN2{Yhcmss?%GaEZYZDgj@qjB6u;O6e%zsYcsPP}Laf(Urh2P2b+Y>0?! zoubp>xppU)@?*2qfXJbLqr=g-qJyge`TrkPUmaIv({(K&pma!gcXxM5qaa9kOLs|k zcXx+$cXxMp2}n15*YSRz=YGEV`G+Xy%yrG|*?aA^*UYG)HG4n&;QO8hQK>|KG$Q}W zNR?h}woIwVy#}${b@Od0m@Dk4rbCz^Qwl3V6B+*2p?q)b<64#kc{^neg*)-jL1k zB`x>63f~?@Dk{V2lc=l+$eP!k251NIMo4DX8LE$Cw$XLMGRh~%q_GFFPsBe1yDqX2 z#y<-ldcDorwi#cxo^RM}jmYnrzsn_^2y^4}ti67!%lr0h&n7~GJ@@mpLx81VZ*ljP zp)r=vLC&_ExszdUJ;ZLTIb*!D(V#Rh;VsrY%ot^;ZaAwQ@;z*|EKFeboiwm4sYv$; zPhuqr1w9i4Q{hU?_oJq~pDkjIT%{NVeQ+;%Q5fSo{dmQ};oEYF@SGFNXg@$nDF{RD zk|8-YlA~sF#TEKqZE$n2*O4pG?vQ0*k*Lw4tj4<0?47;77)vWCTg`ZfKzfrVk(#GU zvZt&*-C6M%YwC;pP+vaAY^0|FdsRtUWXF{=oZ5G9m&#)H;ygP>4-;6T>=I7my@OVr zK43~S?im%T`WB$f#vEvxK!rb7?0wj-%)Nj5;+cu!9dmlg6VZ`TNWk}s9U;)*PQWnN z7)2P5t1ZXJrBC^4$LylX-#`Le=(yJlwJMN2c~iH`vZE;FZKs+ahZL4IfIOR(4_Tw(J67Kp{{Q8hThU%=Ud0+)@VQL#Od%f ziYTQZCQixpzgEP=P3bZF?F^W0IU4NIB|%zj$aujZxc4KSF3p9B-d!DTZI~T)B?`W%Ue664 zoEwMpS^fqnQcw{Fq%V6D;;f8>`>kR3jdn`jpS@V$oqVKow$!E_ypbxUIQ72oIa9b4 zF=$KcxR-ukuk!8U=5Bx$>DH;E1I(YxX!ZEdcH~=yJcPoLN*BEk+-;!`E|#Md{KR1C8_osqye`Fqp>lU`2(d`cSFMj932lQOpg0nI+O=jN&F8_J zZhLP83fu?>QeU6sJn>y*k0zQ)il=7v_pee&xU~}fe>``I8!U%&GYJj*dtygU%d63N z#8DWsMNB=&;YeUz#}w7@qZi{1|{F+Ld;3jOYfN(-xANm@>ap9 zjBk2A$KKzdzNZ{wGgV`zDrHlCL|)1sLVCT53}m}9Sw-z?5^u0d6rA*WTMF`&<){?w ztYyE%9Adp+HXXQ5pxU=PqV55+3%E zm$Z?WNll{%&X+AP6DNt7Bv?RPJW_m74@S2xE{%Zl!@s&Ij zEYAcR1j3g7@C_~Qt$!(!s3i+=sRwR@MHXE~?9H1-CSFc&9Y&Rp?8$RxJqan6hsP#! zjz`~U(JfJfMJHX~w3Y>x=H|IQ*2nA7?_c;W%F28OPJQ$ZV3xR=zBTA7OS^F;0Nmu@ zruXY-L`F&w1PT6!W04*q2xDdhA!eDOW#5)ty#B}V_#|9{nX;_3R`=^0`>p=?NH$7T zV@4q3p8&(^PYC+q@Yk#Zh**4 z6Ns(W1Q)VOM16QK7Hrv2sVzCMop|%(} z8if#;ihRZnEijOgf*Kvp!-Idy(#-NIOyWqfQ0i^M)2}2tN=jO}+E|N>w!nz3{khMk zk+E_oFHjb1^$_H-R8_Lfv1TVurcF%-4m`IyYyHD31EIft+pG|SLg*LCcfrw6B(EDl9j*7n!HM7atK3f;Lqec?DKU| zaie)!i4-a{IMZ8&)7!N}iyu>#-%p%!a!>i6UuRKlk6JGtD)X#@#BDBCpG z7vZC8i}k_&SI9!TFiFrWFJi(`Qk8d}K?K&#hFpIJ_Y?rMUpcwuYF-IHk%aF<7v%ZI9|64&ccF7oxxfW8(!G^pK3eQ<`_xU;fUo;<$uV<&H9EC@p^oLH z2oHtpwJ+w^L!|L~FXGHgKRatR5PYE3-6@F3OONp}MEcLH9mCEt#lh6fQoys1Ub*Rs zP0Qr0wRwauDzK>dxF0efaw#$wf~gV(_)B3KN5IFYov?5ZXZ({wLq@Exd#@TjAM~#s zowAA=(V!#K{Z>xJKkuY-Mf@FxbwCIJD&qlqBMP8UF(*xJqa>AoF%Dn5-jTRy)L^`M zrTuED(SA<^_2gQBN7vRL@IB?{?oFfh+qxxQZ3^NLdyEC9A)`sXl8`PFE{O=sa)2<{ zOA2E8vw4#CO1xlo+x3Qty&^OOptI-&3J+2$3O1%#6vJx%E&CWy}?x5*=}9A zvY8KZaYeK=R32pw@AhI2Q_tsm4&eWF1c%WU6(#Y7JqgE_R8ME?%^`sD=-$1( zmz~Ou#sH;!rtrWdS-RgGMNI~()2yod;AZoz+?-{3oMV&J zpjBdIvK$?8+ZeVP5qPG^9^ie3;65JB0)<~J505pN$Csjt@`cAnK>fR1ke3HX;tN4l z_Yw%@TII38U%oOdPz`+SnpBiT5f0(ntnKY*yVV$B$$CBCynHh5!rKx9Ep^x6q(Y7W zu}5n37TA{stH3UkNqtboldkurf8{VeMUY3c`Nl8r_oadUSMhcyxAGT;Pz5X3Y{@>R7?XUz@Xv zHW3|JZ@*qX0BIkNx1*Kn_qqcM>)wcbsj!lhL5eM2abY2T?bb{^V*!wW2feJL!7ZYV zMXLBUp`gXC)?3atdA*>f&1IZjiJ3#rM+wml!IcmnOM@dCDAm4sB zCOWo!k~pFN5+GZ&(u8p^(F&Ug7z9@nRb*_Yp-KhZG%3sXvR}*0O7;0WDb}4C(eGoI zm?dZc2b&^S?e3BBFjuWTl(+vA{b>S5LhmIGN0nb3kaYn1J`R2^FDR=9 z)MPHMp-52`$MG)$WDY2yeP+IWX7Wqy>2dj2oCCTeqdr5ue`~+)||} zupP1PD}rAiA>A1vpY3WvvAK>G6UHQ)^R!S#THrkpDUK62PDDsi#8lm25eU4X)VR3* zxbUhwTIm~pTK;}M(de+H0JAmO^GK!m!mxSQ_3m%YGDwAZ=gXJD&RI~X0;eJ7WSv|A zsfTcRE_hv=mmQ+=8*6brI}l~z8){`G+(PM(-LHG?4v&r}!LUCNuC)PDj0=N;Np~qi zDWmtGd6cDyRn1%XlK9RNk>QNrwssmMnwxS#8_KrvqcztY$)UbM=kpCaF4t>WeWGW` zl0kpl%7QVj-TF%$B@%ofvf*EAGZAAJFYpn;bp)ZoU&KBl+14-z6VE8ArS-&>SuoTS}@zbQ|K zreO^%Kp?Cea26lNP@0|GBGA1TcDmA8lR5=KXW(uL=C`e<1e*B|H-HNZ#czF(e=J`3{Wd^ctFz{!G@8?CeXu5bi+XTRN2R~?sBrb z1)SZvw3)6f8g_Jfdv63vl7g3xMM#o{DKNss4nc;_v7aIUfXv1>>mF>lv8n^y`YM$kBVgGy&<;ZI}Ll5&IM>cm2b?vn|A7WXaHml+p{A-aw;2OIV!cf`^UaIKZG;r>A9m# zNz%R}`$^KV5eTT9Cg;Vv)@iIm?b^V6Gae(ha$qr%%2`sC5AY)+!}Z)pv$aJaJg^O_zm*M+O2g}v8x|Yx#aw~R4ITPlGow+E zaATqOdL~lGJ|ADC_=)#{OEede*`s#$I~w4iGpG2~+{{sd*sqv2OW%=&)YoTdV%eTJ zUGKTjj@4C=md3lgyMLmpYxAMraw9x?ODK#xKzX^`Gz%9JBi~pLrx*;z<#=6@< z;G)|f%nYw1Sx9hCd#8F`3kyiFw^1FX5J~{n0hfk8VrlT+fpu>o0W!eey-;Ii=4d0k zGhc1ej2<1KyBR?NzMX`H#w|jkPZDYHJk*da_(BmH z5S@1e5)Kw05fwM`QKQep_4XR52#YM$Iw=w>pSnz>ALoNcf4A0-0qYy^(A|GrG(sJ_ z{$~*}tCYeswBM?&HhW@uv}vk0TZkBbqD6;QPff-5tec_)V0y0OlU1XpoaO>03c|#N zH?)j6YFNRyKO)^qmu-9`5L=Qa;wuWBmD*RxF!wr1#w&Z{-rttmz7ebZADV>x*Jet? z;-c-iDXjl2O4YJ)9AgWzIq+EDOuIO1jJs%BexA|%z2t-m!~lV}3qabDTw=81IL3{F z5(jvEVO?FOaW7|A@$boGeA*~!A(eAA(^xSjS3`X2gC0PfbjAIj8SF}n-7&E;ORCbG zhmBFPdhbv6=b^3fb;zZ*fo;MB1LGr9?eEkL0-6sTOza^6D+{_+>P)<|nf0>rsyzMT zc#mx(N3$M4${wsQZ4@?f(%-~#p3_pwxA!n#EgD5L%mzXup#2s9*J^#>ukQ{Q?7=z+ z8h&UbS9!1l>ag>-Z3?Ot`V2R(arOlUZAo_lW>&_Xi)-srQmhc5aGvz(6D~D1eIMX! z28V`xM@~h}l_AI}`ca=)n5%!)c|rq6pl{tcPfocKL_}xT4uGu1Rl{Jv@K%|+G%7we zx5fyhoL6hLQBzwgTB?WO0u*m8nuiwV{YCnmrx&0FYvS-3(vQ{7#362&gg7U@k8_~i zFuzQXFT>-)Veu3TINVdjrBD22$p|=>H+LxPK+6;Fg#C-2o&{X8+7ifnMXM^Xo-hG9 zK@dOPL#E47VH)oZbs0NP8Y*hhw~h~hLN?~G@LL%>?^U-spbu4~sNdb+-`a((B;x3> zp5Fn~VaQX*@zwiC`k+_>sUl;2I5`!IiF?>nCr=N8?3`Zvnwr&>MWiYN^(96)!^0e# ztI+2;TjLJywy$u{{0xrgJpX-y4+wZb$`1A9R#z5st<3*=SZl>VOYh%h(hD#`%f`{_ zPw)+uTbF&2wOViEd`}0Q%%E^ou2TGe{yOvrpdrhXSp}$+Ddz)ja4>=HY$RaF|LmN< zqCUDd*N*Meu^pZsd5>^qKfQPoET`ZoepTi;vH3dy<*U!&W#2oH0gyvU2-OLL3cKZEMTKyO?>&nP`W?-hxM7qXr=kGP20nXL%1t z$BKDyDmv{z{fSm@;5t=cRh||OJ_g1dr6s55CW|48)`gE51x^_Vlj6$R+5gis$_3#V zu6nsDAo3nr0}J){091a+Pdd9e2zy~ZeS4@LwXBuMIkJIqh+}R z4ILkXN~-J=PHL&S&Cca}=Qb^1FIsqErW=-4B(#XH$FFa+L4E|+VtKvp&Lx?8b?R)f zAgc?Sk8tLt9Imznrd(7-)Ozsz?xaJN{}l#^D?sX{kNK6Yu4q}ku!{)Gy~@xak9XW^ zLXZ+Iwscfz@zMUdnhaS?^0^-fm~VLk$$gA zJQtyLG!QaiegSM8TqreUSU4@me0 zS87?=bXIQHiZ+^5F`VgXsl~s_;#GLqdiG!Bm3_7~O>6?B%&*ZPsh*mYfY>8Ga8mj} zcqzu#-9v3z8gTb`@eRZnwVAo8=vck0#YzJ?9Kb)Y#`5~Vyh=t_4`IdueKtIj zB_Na(B=ipWt~xdE&Tx9MZw{P`2#Pe()7AjV5y}T0u{~K8yDWd<%6EUCd&kH+S?Td% z;140Nay@3}&%5iJHUB`nH@ZDxb1$DwH0oZRxk=WTKu+zZkb<78R+#s&$EF$)z{37O zDuu#Kb2r_@!uWlZ5i=}EGW^wBKrw_2J2)%19Ls}GQ@v2T9vT8n^LH}nEu?JaWh?B!!FhJCKOxPz3%LK@zLaX}Pq6cihck!z0l_ z2U}zpLlTo<-~+!U25d9|eG{mao27CXp4`l-FvN~#JJ(Nsu?|`SnezVtn4GV48O1dk zK8(SONTA?bEBZg75kiLi%GcJ&T&C4*#kT1g*j9UeuxG0W{FYJD!e~6mV}n|`uu3X? zW%~`;^C8bh(#vsuQ=dM= z2pNq2Y*Ex!SBl8UZY#)${!K7U-ZLl5FsjjVx<0GL#l=r7j|23&zp6lJ*>4TZ@EVNh z=+0o^0$pF=`|ES+9<+vE9dKxX9Wq%1K?H?`HF!JvxsPqYHr;`@-x3?*od+|UfqX2e zEqD0cshMxguQxbe5Rn7vwxeP-qr=0p0?~$cTA<! zzr+mHc--+D9PDmi`q!AQK>R2V2C@LFN?_u~RxD(94~TEL@6r-?lC<^)&UBHj|LYsXDzfEJ4mC|ms7az2^Z z+6uqX(NfE{XiN847MJt|p1%RqK%7fzW|bmzKp>QYas%E1e^b`VE+>5=DoGDS4L`lr z#7`2KK)sVs$#m#t8{dNK6Ub%cMg`oCs!~G`=mH_r<9_Em?@!JaOmws(dp%05Tx7nM zfNi{)Ivs-;M|=`cQu$9SXIARA1ccp#SYN9lMdmcz|LycjTT=? z1L_neXdp<+9r$^MM{-TBO#!5+c`jxk#QB$viu#OW$h>{7vDuL z_&(?8)xJIlGGglwB!BKXJ^Az0T15Z{1)wPB*L;p&9tT=**N*wMY~L*ylL`2F_Z(z9 z&1!l~T%A|!M-vuz!pKQ1u+(bmzr!W$zWYjg8wpt8Piq)0uiwTGPQ?>I+(cJpMG@*0 zxVf`K=3UnQ@_+L|tX>a@)i*J*q@QU|-hi@}@DC>n00GVLlPAYzpO~17pWbS!%T-hs zqT&PFL7EWfiBb|x(D*B%Jv#{0mX=H%C6ExHj4k9TP!LGCBS-UV3()VF2)WM>+>~Sr zp#9yq%@M@9vlm_ZI7Aa)d4q#YY{Yiz#4-M>NCl14;GZndK0^bet)l~Np&w4rP>-(r z4t3%14S(hjI&yyIM6SCTMU5sCV5 zu0r$e`{!KGF)`_um!5^!6hc(pJb+_F&3TX>P@gpK}J(Y5TXSD z=Gwm=KIiLcmpcCu!_{|fo}WKnIw;Wh@JTa8TFW$eG0`G5t3Y0j&-8z&8su{4{^Fv~ z&Ami5OWp_aaecQq@Ij&l#O(5twnL5gPKeBbN^ihxxGMeANsb{|4IyfacDV##um^Sc zaH|9vxxgOla`XYuD?-OMjldxyjMg2kMY~gzw#A?30*&F4&Ly|#QzX)mfzzUa7Anfy zZl!%NnF74IV>@vg{S+C%wc2)+$h~^@z>8*-eZsj-~ zttZ;`*VhKd$%WWOttLa7W2aO9F{6b}1t(P;Y%5I(M$DueXV5%HhQjGtR+RJsTMz>> zU;nHC6`k~Dt8Ty@&7Lm8myRKHY($=otE{%zL1gOyX;e8E6k!V`>`h@kF)mz>0b91U z@@q&tyiqCEl34OD!zKGkR$z7Gh7eNknZZ>pgiRSt5`N=Eo8Qb{G82~cMDVLkUxnt1 zaA)`_X38*tVw;&qfD2UeF8ynv!rckrCG7XBc;^k+iBLia4-*$sh;H4pgXF%}8-3Gd z#kpC`7L;rlxsWj;fg|@t_%_s0O}7b8yo=Zuff3PpeoL~sb&_bpzkNPqzw1HphMQC_ z%lr@)9a_pQ*$8`SFUX3(=OrRUwm134+J z5Mu+#)EqdKU$`s5hPtZ_y158m@I87oDPkem3QE+)&KH@jv z09#>=aDlZLbZ8UPRUA|8u;_G%87B3DwxsnND;>7%*qH4RdaP74WO!D!N=I9DAkUc2F$q+z?yddp!m|4NNE zUr;+^t|nQMXLhFX<$3tzQpDZ*;o;=cJ{8Iad`nb8BI9$rPkWY;bh4$9!R|6E5=qS& zNp=xF4~u|^uf!#%DM=R&)^(g+%+8?WL?cH+GI|K%Cj6nzo{cR(HzxP?lwI}W*q;_~ zY{B90iLj+>J_?Lw5cQt&f{}EQOlo7;f-#wUji|nIjks2ecE4dzc0H&vKRoh}){qpEe)q-plt%({dMD&13GK7@LYIj;4+qN|s|D|w_f%H3 zNEiH0(#jM;_;t}ZfUGA+$cQMD1Wa4Ocj$77Y#{M`kEMra%fHJY?0rHkxB?1m^h9b` z0y6w7e~81%$PLSk1(T^1=YxeA=UP#w)a6pTYKHHla-H*WY2ar1b?%ibqV4CJn^pZT zqAb1d#hz#Gm+3f|CiwFcOj}r2#|JWNHEhnWck+h^Ic~GLnJ({fE|IVOud%lm7B^V) zG7alxLh%<_hz^j1RCLMN&(s5_kM%g42dt02FKixSr{1KPIHZ)8rIAq)lAa*)nNn3{ zn|5V8Ln-q9t!U?!6>TcWS>Yq#^8`oS8Ue7h@b`*(0|c-qv!T)}hinfBO_bUVU_7_D z{{Y#HMnFx1%ju4rep*gRNiAiO2-XdhzO1<@tPlDPf$C0JWKOL+-m%k}p*3YNT!hMO zycxUn#HnYx8ojSSplcQC@6(y6&%W#ww^-H8&Sj1DmBVw7OFp;Nsz!)`dFbJWp*f)S ztu$oo8>AYWACMFjuQzA+p2~uVz7sI>x9cqjBW^0VhfyHKoEOp?qK)a2aiOoADO|7^ zsXt~C=f-t?!IoskVT&U19jOwGgsTt-gf|X^>b_#+z8SG_z=;iW@<#2N(3Xai zUyW8rn&7n$<8an`{-Xs5f@fTjX|50Cmm6kr^M!%#=C)LOjXxNs8V?vUnzkFN9PF!5 zJz+bXz1BUUb&54HMf*W7y4COQQ670Guh;rRX#)|ZGwQC;Xf8QCh3deuk&TnMOW{MB z8CD@B%4z>2c~pF6c4VS=Xl?wRriyL=)DJQp>YpL4)wsXio8&emfDi9Tz7zF9N5PCC zjB3r?fA0uBL!P*d-2wfuPL(?0d(^DTgiMp6v!k*>wR#y{5Qdt-2gm9aR%6w$P z?qoO5j-E{1JGhWBLs}kYp!nW$WZysX(3a57dMm%R3AGiX*e7;Ixr=|rE1umbh1_N} zG4eKIluFqBb`IJ6<0jk0?&qr~7OuJ*N>?^3Q{t}oq@DpnJ`EPf%ky7p6@_IVWFSD} zf(nGWBt0hWPB?)P{OQ#ld3nI@_m@Y38XKkIe5(Vk)5&r684zi6I8(iB!RdT$$|)W& z3FH@Ui1@Og=dpb5zszce{{l<_KY#;_KBgijjW%(aL$kM0^>=Xr6J zMOnNYqJ1gGddOYV=jNo_82CKYH{iFc$}li5^u2{l@l{s3K}t{p%B;t8h&}43fiIM2 zo;FY04;-&ns6d0w6+KZ_+_}zJxO-^@X{ew&R!z>F4vaDDq-#^0hc$KPQEcy+fgyc8 zvVF!+*34#`v?|yPKC(~q<{u~@JZ@IceBFKzy?LUC8nHaT{ceMfBUN)a@L*w?HvXD$ zx9*_ow!y+T!`Cy)7+}-+&LTk_`olUbIy^Yxo>Qp3k@?GzzFplZjijitlw`)wU95Dr9K`&8=)rQqssA ziOQe~eYdqBFVMHaMR7Nrz1?^CqpQm-C1R~_H=FxCJj|@s%iflYaqM?SqZXk=HC73} z+60>HZd1bFMSfs{njz}U6M?DUDng1P926X^TmE#x=OUo+TjjJ}7iA#ijjQoaFZ~|( zgM{BNi_v0#H@Ss&$K~5e@+vvVLL$LjSzP?oMtK;fRRWZ|4^^HuYA^zgm4b{tY<9Z? z6n;p7w>ZCXknbcu^#1DEa0}JgliPywmxPpvDiRVcar>x;zKosYM%{?fag)2F#|5K} z5b3)bU($p26nZPZvY93*7}5va0LeFFfj;v~p3|z4U;wJW&%^WZd9du1v6O(SJUHc}}Km86K zD0=U}PPzK_trdQCW+)?}t-VdPb>sOE5{Y+-l)b}T^Vom5((7fL?jTYKen%vJOd`m| z#!4byw{SNG#4iUtr@xCM2)Wh}C1DFkCi|Vl$21-!TQfH^KWlG+RT1tVW~d7nhkG_e{DQk5WT1u+Fr@90k>rRv zg=s&jw*~t-zeBDotNIeuRizMDqE0i|)JOD4I}d*#v(vA>_3V4^=hm4jz)KEEs!@q# zPguLnPp!n&ztg>z%Nd2O)K(bs#oDvc|BJ3u#kXHF3HU-GCGw-3k~2bwsuv5{S)>h` zQ?6F=5Qbe?QYh;N!|Q=Jo+BZ+lLXueW)?VUM2{`DCO5|C^%oL#6!T%9m#b`81qMA9 zT~sLH-nc~l^gJ|sh^4S!5?1<@GFunfJHp>O1W4~!ohMwGusa+iYLG&aDB;HaicR&~ zkfdZL{ou~*fRNLLw#}=MZ-l;S_xu7QV8^)ZrtR zO%$MA1-E7)!khoor&xvihz!2MJQ!dKLmQex4>P@jM&y?x&$-G@WX=A=Jg-3ZM(-)! z@)N7QZd_kBE|HnGSWb;n2p#0Luk2k#;h6_sNzi9ekHE*HXBrGPUC6tYMkIkRUF_tL z+H;15A??KxyhmxMR@@hOt*R$Kbh)VB!Ez`Ri=1jJgArjG+kO)!?b7D%<+> zQ**PSL>uPm>!XwWbYG9^jp3&j|7Az^$$$o@V3_{VL$vqgKD))XQ$yNXgvz{fdt+Lc z^Q8{#<89W#aeC%tNM|?8P^OE6w$G$!zWeBeYI6&b_&nL`q4P1;IRk6@SGj1AD z^_-u%9HJ$p7APcl`B(2p{GXAd&&T#I#;Y>n#KzE)!ir4OQc8Ro>A_3Drk0o?36(HZC49 z1g|=CPJ7gDlYYH;S9fNXRo!e$U0Oj+=`>@OSJIYt+v>#<-{5R&)XQ*2fJEpY1gFc0 zUf5MbvhC?-Z(#j9iH&TQsuy2MH`zV$(ksVlsf}Haf~NFi>GwY2TalwtQft>a9p{szqIP|cv^{{np%xA z6qD2jYi$!)P4{R6QbaJn#aM=QN6xY`Irni^eoGTMca>my*-cdC`n3FUQlaz}YEaBf z1hAA}ttP?|&M$iyhxgG7HxmefvH0%0Os>F%fhZ3Cf^SVMwdc1jgGY|alLUlfRSIs# zi0J}uX7i7FQ!ww{XKD%xNzgt8!}C7h*Zy~Tk1I)|CVul>_T6+% zhqI5BMhCGOfkyMBM9YtLhSiBKLGRxP&}0Vr>5(P-tswPsPXf05heJB zog0qB>Z&MxrE;EN^cloOA(y}0>m_a5O)e@cJ0np$&+{0L{b8!;CpyE~x=PvE1RqIg zXe_hH&R`w8mDy%`N_GJ(Br7`PUo6?=37H*Eef8QK+C|+7z#$%+di9h= zhnZ$2gjV4ri1HqpX~;tY^jU3fWUk$2$%N?gQ}BZ{jfllwPTv(m*6&qVf2cAs>P?&? zQu<9Oi#^RA!mS)#l+uiJG-o~9%>~{?YV-Eid2A5}IUYmz67s?{ zd2&$63XSPRZfbGP5{;i&X%BJx`E|*S6}Ylz6{5olb+M_PZ1-O7d)F#$P7LeHwuhdb zZQk*N3SCSiOJXr1s%}iCW`ElZ6}c8esR5+wb!-6)=175QIqipBJp1ES(zV;kSgZsC zbYU|yApmY!D5Gspj*iye#{$+Di>lAc#817h&FK};JpUulLf>WrWszh}+$xkxt(7GVT9(=Kjkfs z2`8=EYBQ9#l!rcQ9T%#0Ton|x)u|Vo-GJa?usVN^%4nSw;IBwE9IY3-@(ZCVXN$mP_4H50N*cj6&UIA@eff`fM?Xeg6^2zAhG}<~T!{c&b zw#^fDZk8izf0yu89@|6biXKy@<0)#&;4=yKa# z0@)Y8hdSeC;X%gZ2Z!<%LAJUZV!SiEj^_{I;q>K{?BY|EJd?6#AHR-*6$Sp(VP8c_ z@)I<*CYKDM#KtfD#!2l{jufS!Z1QrdY5y{c6t10&)Yr?<>S|gs#w4Szz;GCU-YQj? zafB)a2O}aW(TMF$^3kiP&EZPF#lGru<2V!B zcJW<=6>^#Aqtp>X%o)+FOP>eyP3!VqZOER%3tr=}LZw9Ils}rM;+$r{O`1!4WQ2f= zcw;}Qb*NO-!`AYV(8U1oo?i@7*r`ykK+9qqSGwjy+r9!aS7072P3Dv9$hw*9*4`ZQ z)5xp@R)B7`kYb2Pwn9{f%jG~#fe4c8$xWiMtg+FK*%@(ZLyWYWQ#EHV$kJ(o(0r7V zlQVJ}5GqIQK8m0Av~AKl9B)U-xRAm6uB@IQa6;;hQagq43MydIg{ycXomF zchYYJ^N4q$xwCR1ve}&mf)?_e`f$Zlgpb5z_)4clZTK-IYSHWyrewy!B6)MEjTIqw zTY?612RhW&Tb$B!%L5x~xG~}MwBOd1w)wT+H=SPl3-C#IINz!VkcC1lLECO= z>0<_fiBTai(RjNbT9ac87=bWP!5<-o8~@4K=eQQC_lAk-G!pSgpqy5tr3Y1 zLj{?|FIM;*FJhhwx1Jix3?d=bJU&h`k7s8xf^0cfwuxgMaCVZ3O-u%wG{%40-b(LQ3_bibxEU z2hSAGksTAiI^d?r=m`)l>Zluje}B{AD#Zy$#2c=KU+BM&)983C9?%5T7$Ex0^6}iP zvQ@R5jpkbuZt2ZTEQrJuO`J`l#xuC3r%YUtm$vMu+aa73Nl~(W<=?{es&?bD8Zp*w zpVka&mX6{vGV_m$rK;AZqIHkYd1Sw`In)|Ho#mbDXe!5|+Miv2NJ+t!Pf7e7W(607 zt?0{bfaVt_tXMC}ylmnNW{(jeLz76+WynODCSpeyt$DVpEy0&gc8x>uTm93v0#j2z z06+15an_16CDUyWmY+?tErWv`hH`7?i?25wE@W~{Os@DxpV{rU_Z8Vm87LvTNu!-# z6UWt@c1DrfE*X9(9jvREt$vZfIoG1VbwlzeoG;3q+7V6zL-Mi4D>>r}MT#c;BkH3# zm(9v5tFId9?^{q?AUb<$y84X*5+{h~9^Z^ZHrH`xvi2U12>Of|Mk>fsh1F$9^9bE1 zvZYNasAmxw(xJ-oNVwbM-Q9age8oHH;;YT57-1WiT-7pt*F}jRFNd6(&U+}b@DxgI zOmNCcgy}o|V>~83`=>R%+2a{n%XLly={$Bm?(L?>Yqjyi!y`xezVZ|5hze!w(G>)D zh(TPc(DdYEm1PzAH#iN^eR|mq2pz<|M5S}o{1Ea|!S}Bt(3u4a-^$;hiHhsmLQ}r4 z92E*Y)BF286q}mZ>jyn$b!8`C)!GK!DFKO`V8ODazxXl#LCUani*tFg<7kr%Q*;pz z+W|4ipL`4zF-N09pu#`Lwgmf|0lY}+!oVDOlhZQ6)z}lxYVbMA28l{uz%Uh7*J*G% z_Ct|V?ji<%v5X(vLyPPa9p>P?Jzkg#x`U9Y{D^@JNv4r_tgRsm?QabMHlT+Nj$(GL z!`(|hdx%VOU-$W&Ol(dhk~<-KDL7IuQK%j=)#j$0&z0nFH$q|0s#6&MsCha})(rOS`vBl*VVvWH71h0vd_aLEkonttnXK^I$Mr* zt2j*c+|OpKdwds)3!ScLZHmm-#IdnjiOl#uYkBSUsUhUD6h;JpX#tN7hN9QXa`M{j z`LZx6lSrTTS)E;#lq*nad00w%i<*4L-v(C5IUD&opAhx=fav+b$mi zg=c4%g-~|_4=nO#C?coF8rs9PocE2<5K1@vou}G7N$@-${7G#N*8|r-t5#~{^f2&M z%A<~p`Re#BPjzy{3UK5w$*x$~0Hh=IIFTl9cPM%Hh*Tdb(yp6!KBWPBdBjh+zBnbu z_&fet;kO4Tj_I?8H`3w^j2=dRc#w6(=480$`^^mhLq~w3kPH(ubJ@o!dZ(c>7yL+W z_~MPQ%^HfaOL3@5?d;+qspH>Ef`t(sWCeA#4j&tK;y+{TKWF5X=Y9`T2tr=LLsGS@?P`LVC-f!=H;nv%xO~PX(g*s%{~l$ z_s+xgK|}0H{^WZ4*XwEX9so{a7DNHlK&QtXJyaGfVkJid1YQtn0{T-5)LqFDsIb49 zXG#58GE+6L8x`Y)x|>8kK3Wqkl*dJ;B9AsYTRqxAla(S$M99kw?farif-7OI5#R%6 zn;18Jz1y3%F+!f)m)+Z{kr)3tF+kQz#i|2ym86+7uCUeiVf|HFS*Cka1MRAvWmMg- zFPkk$t?F2w7@dPB@NYT|N^LNW>bCBldWpEw=u?t2rV$Y{U)qyh5bvIslxsZ}#=dB2 zNp&fBALD#K{6oWFa4+sdk$L}9%MNBcs-ev1todAbxs^>iFvE-5R4r7DXa+nkN`X~4 zL{D?F`sf#CyK&8m)CX+-m9j^-(lP0fub<|t{Xf}YNoj;n=%n7q(v$!GQGci3M5(TV z4FmqLs2ZAph;(y%S@jYxK8uKUYDU$(6Ahlemho~DsH?()8HPNk)U^+e9ym`({(yuM z8N01}fb^dM1-K}7@_un(BdwByWg9(0lLo%6o3zPcf1L}WlbNkgg(hCs*}5z%AD=6iz;MnEUcW8<7KrgS))*Vv`j=85u=)H!AWAqc@&=_U3Lmw1r;)Y zS>8`<4)@nf5R3TrSzVngNO-a0ETAf>x}9KzjL2ZmSlh5;XyH~B+vSW14t?c9av`?I zimutiI7wJINThcgDON0x6K$XfM>QC;&o|qDZcS^JzI-c7} zK*_J!!Tl@Cm)GMNX3chGWr*O;1E=HWO@iX&7TP&7AQmdh2?#La_P9JOGt7-f zD*kY|4=8VfZ|L6dEaXHJ`ipe+`h6q5(c(qVwx*0qN_?-kCAukJZ$gWLz7lWR^e5)@ zW5A~meIR@7j~Wl?<4 zvp8bwHG@PJTOU)_UcS*Q4WbN0Cwj=jm_97M4io#!&vsJR@B9>mjw4=m3Q!vXf;9S6 znh0h*xQ(QZn;Nd(e~mo}xC5g}+5TbrL2bsP-PHAAbH+fC)_@pQOkWXjWi<)0qcXD7 z3vc_SXdg>R-bkVuJ?=8!&IC zMI%|*ktSw9Da%PvYBlzN0$FzZ@(8ORFv#tcBQpLBHFIH zbTIZj66AELmgq04I(O#~VT_E^YqCs6(^Q2tM%0G;0FIVr|DV`|I)IJ!PvqP2bHL3mmi4Z@6-s>^iAciiB z7hAPbKp%Cez!i5m`vMPTka*R<_H0T)CLLd@#Dv}8CBZLNZ ziaZ&&FnK7(ffs=lF>Uc1+1mqT(&{*QUn9gwc|#NbYY17!v!Duo8s6 zInqZ7G-dnN&x}Y@JqxhrUAQQ+Q(Y8Tt!e$1A|2lX_5cQM;0Y>X0lqwA*$8^S#y3*O zRMn$ON>EM0qDiU$M;^Stba8G_0A@BJOEV5?zW)>HElMaq>>ITZ3jenj0NTFVFd-?C zH;L`fcr57e-06L9alYC0n4Mq4$;?PX+he2SL#NZiEGAR8yGu&z7r3&e=$aI7yR04i zfjxlzq53V_0A}pmeBsibHWFNbEZvd8jh|D3>&U1fP<`~*u+U^qaIT@?lnAI#?aDOz zzaEOJ_P4_NfOD1_WSvCivQGxISek=g0#g+P@li?Ez>+4|VTaw&qv5aDX|wS!-OQ$q zlXsAo7?^30KW8btANc}8eE`G%?%jHrB_)0&ZEmq()Zk?RHIr`_c-b>pXYJ{Q$H><6 zVWTS({r^_T!YGQrB$C64M7?q)V=svoUSS^0v@A^>|JBR%Vej?8)aVbhQZ3bxa3;#b znw*K9Z|12Z7v*$nQl#+o7skocJ`=5=+?KAvvpk59`biD?)iA7$(kP}LMyCVR>`nNsfhmDk4q&y7YkUx}k`v^IQR5##n$F4Yl`q_r=Js+@ZwfK*@hdw&ja z$GuS#cJ=CJH_bF7wan_0ThDV-LEvFajwU&?temE@wKvfr@cD(}C+q|+3UT|Hu#Lk7 zS-xXL_ZEBS#l=H%n}R!p38x%P1@#Bk^J1VLJ(sQZzSLjJHNOX)IMCXwj z?NPgy=?+2YPgPq{=VPqw>ds!$s2Tr(oy`0t2!5I#nwGJ=_bkiGE9(j~(KejxUvo@a zR;}NNA5;LYgixG|p}L~v#dAZx)UYLu{NI0^Y=G_*Z(SsBb6CCa!#et>F}Rm2lsBdB zP^IugezIiC#?_gPOAU6@OsZqzm*r#YgOnnqYL1B0UQqG@5lm^DqfWApDPPxQPYgL$8FiZcf7c@t4-BWZ2;xcf@D0XKl@;y?I zPryzcFt2-7Pt(`;$UlWD z{G0XmgG!oR=wZ(FV~8rbBGr!)WVho@WEm~pzV8FDzm&Bw-s|%G<4VCU)H;eMFSsG+ zHql%U{&~ilSw-!x{01ahSy=_pDpG#z@nC4n0CKS|kI!;k-9Iwi%wOD5NAL%6f zjT1F5|E|tc_>a3dSx*&UywM4{o>&7XG&=>oq4!(*GA^6>4<9XHiuoeJvxt&^)xbUA zn~8=mPz-s@luPf#_Te?qP5O)&>W1I_EUqk%_XLFVT2;@ZA$=nzELnC9)yU=JifUL` zZo3=S$l;jN^upVm-s9t-cu3k3TBpYWyQ0!}RbC=o>jM_U2QR_@c`fe$N}#KbBm4nk7i^M_Vg(Akju%b^YgF z?0t7@Q|Blhcx(_(jNjkwm%}%V6L1iyNCjZZsG+$ycMx*~;`}}(`E_VyVV{>TM=8<8 zp|Tyj+)GloBaj#3?(;V?e)cHunF)RXhOOtbbnkexOIWkZtM&B~_^Wf2amI$T=kumG znNP*pE~-3Rl8shu1*4nmo-q4K2Q4Nqm$MB0ZOMLh3Us6w^4j)h7^tL*&A7ai<#3BH*D5k2jB$}n8p0C6~dAll2Q)y_? z9MYwas4@Pl;{85gMq3fAQuG4$b~a{?-2apC9FSnUf4x@oS0Ii)AlfF1Eriz4k*}rI zQ&suOBC)mfYC@K6+NMnndVE4x81q|HU>zMQ@$YBgQtKC@EDk#KYc(p|rScEOV%NGU z-~?Pj3EhqgWvobOq}@kKHlelz2t2Xmj{d-ITKi#jGCfhY__ouI>1Brmvu*D_>!yuE zPNgKN7>w-6m@LBNr9@v{uoUNTdU}F0P49%Y`vkISQl-lBg6d>ntki|eSLk-Mh)4f5 z@WdG99f?q_el)LH4RKP8v%rZBnsNSw?pJ>njOrQD1fnkFT1L{42;iW*4=_=jY^`ZU zZk1{|K(1?~O5Ctq(>5`eE11an&U` zY`=Mq4hQoqFy~ohAu7(DG|c0tG3hf9;Vc|nPz7>|N=L@iOxoUZ0(+|S3<>@0mmYq@ zEu32yjjN?wPMaPM+Z9%=Z%f`i1SZ47Za-RRM<;6Iv`C@N z6-;_w=@|7Y!ObYg<&FJVI4xUHts*U0SCtF)qB{vP;O2s_;sW^g`(wvST5*2K@_3cQ zDu1jIBdo^f4f>rE0>f080Lckyb_VlNla!18RQ8ajz+n!6tX?QZ56uacYY#Tb(fq!} zijf?xp<`9Nfn zIdBHUeZ}bi!YW3}vq-Mb-98@?fC|wT!{s|wQQZlXGVsvz$2zm>T zv}ZrTSj`FRHk5fdMh>H&<_UWp)-K7-%-DP zNGw?-KW;=Pr#fDK?|>fpef!&BDX*E)N+&X6jK;??ur(#_%piFi)Q-+Z%P($v0|ET( zekoJEg=J5S-1lu8Qdg&b8Ff?o#e1v)!q!_Kc>JS$#yU^brUN5rLcT48>s9fnb+zSY zZN%PcIlk1FTzx?SIH#&SON%gWt;>zkv70J%sOxVm-=Q(Iq|U`G8y0ER_hlK&BCKF* zlt>XYD?E^t3W zfVPalQic;rq=dKWeFf;>{rfVD@ec=;WduAeZQFc#r&G%fx#W3MiUJs%anHn_E#Dpy z`}JL&%i0$t$VM^K!GH03A?Wcr)=4~|UY7Y~S?6Xil}L_95y{7;;fC74Q0!acl>-2m zOXuYPsu^o@1RtI-*3tmL3;NRbdw=?oKc+4-2@jQa!(BzQXEQuxoc_td1)ma^Gi zpn*N$a-w5rPc9heZ3-9)l`#4;}uKAG>>CD}Ah^ zrqsBXJFPv(p`11Be$VWm-=@~mq}$y##AL>!bCT#DKl39n?DcNq z;pp#{4WJe8hlFC&+Jte#S;)Xe?1zd$P4UP&q9R7M_a?9F1ZXPRZT3rV>=x#9^)+SC zDqx1Np15dsmk{EL{JKf;75p$c^W<7y(PSy){@_r=m=39I?2OP|=RcVoXv$an{e~Tg zLQaHCEtU6g@lc|oX($GT#gSBpq5|_4o-0yUS{x{QZonL}^SdecipnU`N&6#&^{R9g zOXg|a_j6*K)FpJ$pU=DA;(P}=RI5}{QNGuqCY++l)$yggL^A5Q%GgM(Fn=Dt%L1&e z6pE9+5Az7!iP)Ty>?$>3gFA{*RFRdu>^mi5s58}3q}cTPSxCdWz0mAJ8W8wF=swTm zv`-Ka_}Ul4(S+G_PX`rpwf3rhmpjqv`^Mk3Ogq^%;~9b0=cn@B!uz_zNadk3)?@)$ zswhV|K;pZmEOjok4~to%qtQzGt&ooEh(!a7jX!*F+B;5?Se3#0MIx8ZG(RF44Y=q= zW=>ssy>J`?cY*K2UpW?1`bYfs1iN%xg)>r$gf9I|TWNtznHN(<4$>vk@7pc0|2Xd> zXG`L2;m9AJ4Oddlg?D1pa;0(3*-y63s#`?|I6-`^`Wh#-InB=zqr_V`a{Ph!RL}|-Xs3z%h^ra@ zw6TiS?C@g2|M%t(c5KzE_V0~(D0}=5BjRz{qhaJ>aAiS&Pg24`i$%Z2#ygZDetG&+ za`8H+JgXRJmN-?YPEL6=MUHQfHVG39Z+*~gKd$gD%^Udfh!ASi-f6DU76+HUmsAN7 z@D;AlFhq&oe1 zehOe17?i?vG@XY@X`+HokiW|iEsE-vxGHAaE_noV~l9dVoj+PBZUI)T{D0v zI(*^2fftKD1{YA;lysgI1a7RdjzvzKT-2*5t_gj;IPaUv(mJZd0&P(?(0SKXQ7W3C zD##bAP{b2j*JqR(H&U-HfMx3NHSI=ry4I`8ip@w6vl`WL*-oPMIMxo{$L&h_GxjLoIPnhcl9GOlzD)?9fdbi*}7=q~7F z`0Z}S_kz3UsvnYnBDq~o$P%z&pEhr|@_wbML` z^?f(627-Ys18q?(5BcSHgsng|`K}6OIOsjH(LMT76#w$%LGqH@PvOb7CYqX>YAU{H z;+r5vJI_zb4|b83%o1QehKsAG{)ev4uV; zQY(Um>U1{pE&DlS#~mxT#HAzRs1)@+eqJZPGYs>7WW$afLYywsw0|dBDMXI8%3Sqm z!-KemiI`P+F~WovC0N;lK93P2rS+FD{9pEfC5Hu12OA|eK7NKZ@b${XX>2Li_c-sF zwD9U}JFsLIRa}vZsIZrVHk?B@8sH@IkS_xG1uys@_bVH;|>)4cOG+b-fH~DcDF0nVxBrc zzE-i}FkZ!Lc3v(Q+_jk&Q$!UvT}5S7&`7L#`j<_C1seV6^1V2Jner>{FkvIn!t=(! zCl}>*&Wkki@`#kv@Ei@$wOtor8y) znVB-^J%{K?;8%kA%3XpmGUoa64*Sufp{OE_*)R5TXd9W7hQpE-j;;FLgpDIJ?F)lZ+_5rm&scH0dOAkRQ$>ew;^1`)feEd_HA z)(iV~YcVi-$dycNSq4sG9H#qNMhBZ=d?qj#f4=fpt=2^quC>s0uDTec=WLtSko4Fm zsMj5=C;KOwc+Y=G8|9-+B;!WSyUGL6OaM+~!i@^Dl#Ix-K}Q@ci-19wi}(T!Y}c7b z4C%r6)AfHr``EZ>0iDnsM&xwfD=qwk|ohhCWBaspwO9;#Uy>-=Ny&`Z~i)j@x;?9 zhmiu47MdmNCVE*Yaia-WIkWiQz?~0o;CA)+tw9#N9NuSdc;Ux8Iq)DpT~upOX?tdSb`rI>Pk(7 zKmFOg`5chFUL!lq5Kfuh*ts4w%JP_xzc4}6@%8#mb@_9tM?K=BQe?RWu{RwqX7G-} z9Xm4Sg_vAP>YwVOWneAZelqn;Ql@3( z6Za2KJ}VP8##mih(;cEjpqKwR6W;oU9+2$YSu_Ns9G9Xm&tP}9}q@5CK61(dCdspWUKt!|4O1YK#$DQHk zm+w-rI@{@`ux&U>&gyC$CeWJ{4;qTL<-PHe0KV;4In0=mZF^oa6v_SoWQ24QOP4jw zz9we+7k?1jUtF$i@am>!9tM&CC)44+Dai6`Yt1G1S4})7H1l_Lg)Y*3ijn2?*tsvi zG3Fcs5M>aG{v2&EV^6q*K{;KFzO1CtMhRybgqas(k;KsBxFvz`g8TlTVx7XS{!>>6 znDL52tqbZ3U$tIXt|2#z=~l`whSww}iExse)I(%kWt6q@Gn0pp=Rm#i#Pl|(7oFB3sVQl5g-1|` zz>HnsVM7jMvKG#H#G*-+Y^U7Ru}9c;H5^&#T}xT%ar0;N9|(jj5vm z?aPiff;m>*L~0ZKv#d|W?a>g!)eX&Fwk55`=;nxs=cuAqK8SfI_YdfXJ>>eWR^ww|T>7hFr%58As zWR;xZAp?k9^yCNWd~)PX*QnY9eHfNz6}+;h;`8yW2ZK09`BHkPKG(4zUwkE zb+$rUL4BFBFw!NRz;%ki-?qezEJ+3^o*m3=exBH%`Ub$y0Swuq_j6>RRTg>J5P#{Y zQD|z-l5s&RC2EsDs~!?V_4=fW<*(IA?%WsMiRs_eVvKcX=yq{x?!v!Wq#3`>0|4;Uq2Q_VhXjxcz5F4ayIlx-JkQQ7I|_KhQf0JqF=4u3 zckG4~X|sMN=_Wsf9&-CF!z%$@zrxAQpTo(jRS<_hBblDkDT#O}=c20c`csih0LD5$40rU5El(NVUD$Tr*?j1 zENo`*<$5d2uSU~8&rdPSl50M_??>pAK}J#E<94~_q-BQRFR%*2J=doA-Y;j!L(>Tg zO9UsOQ`t`oaucXDr{GXU5EFCBJciO6?o~Xv3t3F2B=x;vHbJ+6RzqGu!0r*)-_!e3 zPvF4w$yNMc;*Sa+OAM5`gIy6r(1CqoBZ)BT3wxqF*!}2bqXcXy&_Z)ZBnP16f@?rZ zhi&Hpa9D!sBEE+h^P7nzPQ_v}LQ`lkcc=l$$pm|8uso(Y7Z2V>j!B-_7wGN76082U ze@x`0eTW-rX2gXC{4M=g{JhddGf}TP_k%N?nf%}N`ufWP!uP{>)71@||FW)2{OG|d zv{r>sh3qa9``orC%^v&vbe7|*@^TtW|337D24}tuQWiW);IT^jq=OTo7c;`83&@|>QA}~ zg7Bh6#0~KzbqtxjCby%y(pkwX|B7%Lntmt`1~2Jxg(f*gD)`_HId6K?K$JK^1TBc_ zHz2*Ishy!5Bu7>L1`lgyXwty@T|2UZ0z__o<-n>oj~i^Xes9}cXF_aWyUKs|yo$h8 z_yugZ6w^QmTfSRvvwNi z+<}jXIrJ@2n(xtcerWQRCB-Y>^O)L>PcN$*PN_~kQxh4Smk7F(N6nFIxgOd1$@C5 zIYOQ_v@iqd;ImP0Q^6^$N~hyBw&3?Vj=M<9d7v>n_Ms3LyV7Y8iLdeQRVR)iccGV# zk4G~`Ow41}A8KVN?6sgwAGNd}|IfXf>m3@eGz;)0^aO%)&_o zvRHj?h@0lBvk^MT@tgFhs_pZ7HkuO=dQ`MwvYsn99u$NfVqb0;I4$t>=u=~QxO9(? zvL4{5s@;t2l&!Qrm}trc1O~os&zIl($#p+SFJ6^YTdE%mF1-A(ihSn39RH#2(eN3P zYcgph`nDD4d$Qd6%I|NoObgw^c zob?FiiX_T%g3fM(o5bMQI}ICCHR~o+S>mopNgs!G=r0;$H^JEMVS~PkUpSzW&QDx* zK@~4$Y?z;M#<%C4KM6Hn3YqAV5fuE`-Q_aTs2J0!3^c5%xbN_Pi|zG##=E=V_!n4L zC^Sig62RH=&?W>MxAB6gs@fTu4e^*O(oC1nUZgl{r$IlMEL)~Lj>TesrK31Tt#h8H zZQaOvzavel=HQf1N^B(Spq0&#qk5B%D*h9GbP6^GVx8z-HAcK1aYelC*2%tvp~7y$ zX4e1%ZAEFqn7V?$VVa7Jb4Lc3GhNv?09zCQH(X(CqcV4noR}M31Klm+&}dSvBiqVc6b=60!WtS=hgg!q~8#1~{8we|hU_FXH=8-?TC{ zr|95D$ZEVC8&pHf#iC4PBH?-FQZB!a*cW)Q%xpEpWwfl$>2@QK0OsR-?h0azSm4VmCM| z@dbmPnA+)fS6Z2>F1i(^mXB&%89_tq`!&SP3XFal!`DQ`$?db?ivm=yt3S_m?XcTv z=5@0qxU9t0Lb8YS^H|N3zF?)Us?!#Ce|`3X{I?c>G}zVj=vWK7EB2I7pd2Mvh6|5d zutc1$|7oGEA#KQIuKCQe9nb}?|1m2INlc$f7x+1|OLbBx>u6{%Tq zGz$RyoiVoKH0gmCb&ot>ZmyF!w$Mm4)pTA9<7r50{)BN$%MliDwv6qom`uX5+&q$Kc z2us+JZJ-iYzp9lvusx2L0$fNiwoRIRPkG+!ZCw2fp5t2*3DYIR+1ye3I^*pFdr<8M z4Q8|(n|ZUJZY%ZtkrvJTG!CI>zl;%u2l&(vs&KDZl?}@PT80bZUrZBxN-Lab;!F#@ z&Z%z?1YXsfl}%+$BdD={xfKIwmbFnU8<|>D9S6c;+wdjAfqLIq;0g`Ce4JoJvwm@+ zDZw?qd|CS+$Q@02! zRWZJI2pS=(D@sMMK9E`e1ZBBU!83nPu8@&3qm|3t$M1Si*I7eabrCDUGcKT3sw0(F z5c#HJ)87{sH!}u4-X&o8m+VQVwRiQEyu{!B?3x^*tBIODT>cnK1ofrsI+opq=9jtW zLl{=8v6RyFzqZnpr^di91{Je`mcnHONy`=lRY5D+~IMm(;C?`QXF%_-R>+THfWCe%6e(`L29q#K20lgeR*%t+T|n zo-;#LvG{4apDpx?HWhSSd?!BWGggd-_%IcSx#sN>$z1o>n&HlviDb^5c7J~U+VIZ# z$iJsgJawvyFFp9#Qf-muy0=)rmT^gEOR{h7nQ=j{MOAq~=b#F+=(m6F)y#U!WcG19 z5H;x!d+8ykVQ4f+F3~H6h6`iRK_>KY4pjIEL30DY8dpJs7 zYR`hmy)V`D>0_un4pltXhv;lrcxv?(H3CckD4?pCq^9}dRf*W`_WP{wvwZtz|6^w) zn_q3}#s!P(a&_JtpJ`&m4+I>reqATs32-oV+>G2hJJ(tOIV(tpO>zM~x*a2^@D$&g zUEU|Z2e(v~$nvsLIp`g7>(JTXWgqWMA^2D*V`-#SUS>=+Ds3JZZ3f~%c6ML~8JJqT9CD3F}QAPe1r(Q&ncY2jyTY}|(x%_`F)a4d|s zdpa(X`qSpS7$jtMJ8?lIzC+}XZ66`}$HYtTjQDnznY8%9%3UEXbae-peLd~)ryh&O z6azWB?6PD}mthVg;y0kWoB459`&Q`;LNG>1X|vi| zGHmo<-L#YE?`Y)SxuR66x!f^b`OI+iAv;8EsjVmCcCCrS5k3xy+F^1vrRti62HU)v zp7^l{LrHVK2ra%hQGpq~{^0zRd}Y5Fsh8rWLGt1+M>*1PTwt#DaP9w=ZX)NdS|2Dh z`@6!wKq42g_s|I_5;qXp(IdfGt}>bgKaZ%KW$^TodF*HoCrSN$TqCG&;%hj<0`eKcezAqkr*HA4WDJ4P)nu4n zqRrjJ1~V{`refpmUJ+jo@3$wkBgL-0B*~q>GHR)%9w0YqeC#K*MT3d)4+tKJ!v1N| z-%E8gQ{b3D6OyccWldI3QzjawBa;XTSD9as*Rm19R-I7S-GsWE97b3gMf~g&r2$#8 zE{t!Kq!Xv;Mu#Am=nK{h_5#sYnSrpq1X%P07>klpfT&)y;+~iy(H!KbU-(91>-nBQ zKz4xd(bYuu<;Mk^yOf=U3ioRIPFz^nCCrm5xepv9Ae5V10TFa#Vk_Khh_yhwFouCo^;nKDTbspm&f=g-q=| zr6^vh0Ndf=lV5coK)(96#~e<%qt6?Cxi36EPix~)7RJYTWT^E)50iHUgWF2`lT35!fi;o@0e*_VP384Z%e{|}CYu`qI6^~n0 zt&*MwXe%&!sv}y)B<5~<9$(pS$@%Dw6E7GP#%U)O?C@>yFviMOCsKjPbzCc|JpgM5 zN8)8`H^x+bxDp>^p2C8@Y`lxj;(cEptlF%@;AGa$aC$d=8Lhjf*%VuY%ScQtNKuBb zCSqT|Nz>}@4NiJw3MWO>4t!nHI;l5$v-Q_$w<1ITn$oL)LX(SBC?f+mU@od24Unm{ z)OTPZfdabs3s;5N1P*iaF6KpJ=rZ-Lv~fJChe?Me1p<%+)SP1OdHoTJTPIObxa&3c zuE_mPG78RAZlL;tyEF6HVhV1k9-R?b#kA5VHkb4atA26|7vu56oFYP-4nOpCQc2wn z%jweTG#OcZFU(P*-QH8s2r`~j3(b@nO7XnuyxZZ7e}FWqUNW#QgXQsSAZspL3VH1vgZ4_MJqFe zDawIQG=20^xXDE*`+rK6Ie`w!QO8nkG%HKqK@5()>07hcagyb=X8+Z9*^#|(0geRy z_|ZEoUrMdH*3lvk=RE)&O*KyxgU($*%htKEQjCT)3@an1822ifNEwM}8$R4fAgEHm zRf9X>q76t7A3IfelXO0$mo!*tt}oKnR{8#mr*-}K0byWI{ML&ohy7))07;|vKh;R2 zQ=Blws@w@#wk-jh(Vm|E=-dN0GNZqw{VK;>YANtbS;D$%mqEAT=RVPYfMs2xK$rFW zClk-u{EJk?};GI0;%}eGBptzE0&k zDj#5)9}8a$Dxfbxz~1gSd5^zIQ+Zq&*Dt(j41?ZHJOkZaG4T#=k{s8ZKP`MlbxBDq zo%|zE76%HN%XSA|kHvsB5GctB#4-f<-BM_vOqLc3B>rjmci>rAe}j=ecof}ict#t>)N&LFAwDE z`Aud)wX^5I14BZX$0IG`?|}MVRc2F|9h zMoN4K3q`YZ+j$d&?Q}lF6}D@lFfKQzRKUEN4h+*_BLO4Z0C*n~BTy!&^W1IRJxhLs z*ou`AF-?;VeSUh3+b&|=x|j6Pn(ks}YVo_gYR`X#T)s*6bv_L=9pXRhb(psDE}Uwp zRS=W%OZo5la~^A*xsk)?&z}}G{LFC57yT@r4b|^f4pl}2(18gdP&loGFTlwlSb?jH ztXn+l6KLTX=aTg}@F(57(FF?*DDBn^2%ZPOMDgvS zBI`^-BRX{}#jTtk41FMF7X0mf zk=&ZBecbr}Dme$3(j|WDVzEz1cra(2IWtmds8iLT$>`O{G-`i`E@K8(LZ#bjLE?D& zjoqwL!cW;fx1@nw-ZL&Vm?jZ_bGrQDJ3|2i<)BL<;O#;r&`xjbns*=hI(x3ATYJM# z+f28^%1*~$lcZ_0m3=C!Z4)SMk^GI|G>z}aYLFX>Bqzr4pZqli*0);{15{|4n6lu4 z2F*u)(_fyTqYvgmx&lA#)g0NKP#4>6PxeP-eFAp8cS%;%`n2HAdDQL6vO#jSrM0(P zTMxj6RK$oS^=cRkII-=`KT(MN9x3j~*YLlTd9j3Bj?^gV90v_IMe7b7s=7}|T-3Ru z-f_)6jjWNNb8M~tVS((ezlM1%U(KGGX4H5@OH*PmzlQpPPkop@Xx**A3((S2lMI#I z07EJtz$Etje&bPxO&LMCG=V0x!|UbFzEAA!plrLm=g!x%)|Z8UjDN(D4t#jM6y4e* z=&jQ%(8_8+`XTF6lxe`_jVt8doH+w&@`fcn$C(@K3-LxX7Dbm;)n zJI&uZ$nV(OFD7hQSYHH!}Ap_l+~xeS|^&26(sPZi9C*jvG|8ajUIJF}9-^}6Lw*mhi%J^0U?My?I@ z&iTCYGEJ@HPO-4v(sE*mF}$pw+=S?eT;3fKDRG`K4feo*qt2gCR#bWt<%mkf75uj$ z53_gu<#}nU>g3RjB^T6!2r!995KE0Pbchxd>;(#9hX6y{LC#>IkDS9F9!eUPgQ4r|M-6Egh$JAibnQT;f}t8 z-95Yr0Ru{oJhNPy9uLP<5~~{@Zv*WRjz8^o@+bB>6l?+XY+B3w{tLf0c7dw#=gyEb z?-Kt)=dw%n=yC`@APS}OFj$=&?bCnI4Qq(h#8SKy!5g;0C+|V|^9CU=@dEb~_jTi5UAq+rH794NIx-c)W z2Lq%x#aqkaOQRc_Bo_?cU1$OW5Lvg;B89nhGT8bey%Qfv>iIn%KGU}e3gklLyVd7RqD zh3QuozC&7@cS=Sq0IWRr81zZ!`(8FtymM$>r6W-)A!Xp;Lo%IUgLKz6r=rdMfe2E{ zT)_p)cUqW1xZ!Lun*Xj~kuIhS@%Sjg(VAc6e4Bw=RO>;f-eG_Iofbc{6!(Jt=`lR{ zaPk*VGHkZ%?4f-*zBIV(lA)4};AXzCI&#pm1O^~VY%&zLM$CyzRK8r-BbQyZ7~;Y1 z#q$0U(^IeB>!YU|@OHAPxzdKxf7z@IGdr>@aYzoOx!;%#Oo)qy^rf_vxY|tWc3mZKbxO;l&AY8KZYn*G%_aK^sdA}XM!Ax=(_~^;h6v@6nYxTcG{U? zCK`^-RUg`q>H;UF_IGACinATQ_w!5nxyA0OJqtVmhJJWpg!@1uF6U+sQ58~q-963a zx8RTQ!1dsVc~&P^r_RbU;~sQQYIF{#JP0jo6ht?5+zkz6_stc*AtRj7m|qaUMf!sJ zBw%|2*!GF`+0!U8=yL~W7fH>1@DUMx@Yta3bv+&S$63g!|ENJFKVF{UCyKDvTEkwsz+tmujnexW|d zTsg_cwVx)IxsTsF`GUZz1Q|r=FVMRgbD=O%@mUIug zujsES;KaFHw5yB3_`~_bzkDjf3ZtV}*~-v7WB)jb8m*<{DaT*)^I#NaO}{7H#=yNO zvRJ52*$As{@DArJ{3o0iVFHD%qhx)?hqlEwFN zEc)_`N-dv?Nzj9MHG~=-)%}k-*mDcu9vpcS$@0QX$WQaz>B?A#tUEqmp80*~>3ev& z6hNas}x$^tk^ZFv{O)ouTtpG_Wpf1mjm z%Bi!9>axQbt&j7c2nk&k#%g3s%wB~8>Vcd)SSCLYPZiyqZz$Z;J?DEa9_G?cnQ!M( z0d8xOVQg>L+TRfrg?-$2$X2(XF*>!^W!9B1id!)?cwz6TKNf9CIcn4+T8tMMxf;~x zm9-W-Gqz+es6L{9M$ZYSq$P=;Wn~eT{p2pB0DZDCJ4GOyaw|Fv&iXGMhyg)AOfba7 z_*K-vy;IMXLpq9YE)b(*$b`GlBE}%acQ8*#mT=$kTR8dvdJS<7{z6FK0oZk*Gy8be zK2{>qsO-5y={rl{oi4?uDnI&;)Vmk+i+uPm0z%lainF_C_1~ruy#PdO+kQdVF{y^6 zyi8@|WE~qvXB;*Odf*BTwV2Et5`y0bcxcomk4=ndoowC!M_0JX`bquez7K`)!Pl2J zB#_6r`1$SLyEYSSVc>`Uj^8~T+vWG{EsD>yEKwezEAOxAxV&QC-L9`+;c|UbnSS4o zDS^SOC){zQb{e2dMB%>-s*DjQ;^pyBibB-h=FqTl{Or5uUDbI&?AEd-3&msSlp%TO zzYG!0&{6Ww1A)qv5x0Tb2-de`aH1+_uGFu_Jy&VlC5O&2h#7$WFu-YeFy8U)h;?W6 zU7dMpYG?&c1A|#~b>VT8Ntu-L`_;_@w+1zLxxvn**~eL7@-+L34YIX;DD^LyKhWW) zceWnJaaMj5SoF<`>sI|u?bhjGz|CYo3|(Hu)rMtf?7d7X!dCk=ztF-<0%8Kdi;tjP z=6=qZ!Je*AIy4@+0i%et?uAk}mXUX9$1GST><{&(^^mAPKSK`Af?kIhqFT9>5_Iu6 zxl%M>{j~K=DXW%C57e@8YF|3cEvWP{Pl{Y=fNNQOj_H!MK+QK?Tl=D}u$)zx8Db!B zjnV^Mo1^5eK4S%^NlDHSOal~>3m`X4qpf9*Mcu$K%(3;~6ILn-RpT7-BA z9qfyi?+(Qp5%lj#lXPfM8U0=H#xyh(9Yl!wNUrt+6J)idsJhv$ zXK-Vn<9&skc283Gs`M;m39qd$?zg3Vyh@9y9?_tv2BZoV0*IExaJIjaeKywF&>FN<6of?^cy58cWZZtN z&bBsVl33rm53@=i^1(CcNE7z>wI7$-#lQ6KK8wbst+sq*pH4B)#{h2JsLweAjdNS7 ztz|>WQoi`sq$_uf=!4b%jPgnLaG%(N+lD+IEkyD+ZexutbdHdk!;} z>!g-J`FUTDc|MBHmWOv-%2|?5tqYepzhvRecY*C~e~3B1c+0lCS5>de$`MU|B(wrp z>B4c6lU-P^0EiEWIgbf3>q4IeND6Y@1y1_+;n`P-%s#?dYrQzI3HKS&z zv#lKm40mPNLJD3&4L(Ibpc~|M{jEWyc#mDE`~mj9u1tyF|DH_47BtSYY*$zp?cihU zNo!EYN<-eWJ^G83LCEu|7bwEGy*C zVT7^wx8gcUIF3ZLv(?JtTXIiIq0U`Zwoep611f6A#|aF`@8C@PTum9mA>a*|M!Tkm>+T#>C9!j-MZmQJ(W%>Pc^1 zzCi!wl~YU$31HG!YHRCOL`0GipjE@|7kmHEUq;Xl0yl+;QXZe}ER4`!Bk$e_l=lLl z1h>!25TSbDm8rzlUoIaC++t=+Ly9aSEOGidPVtmrM3{zizIJ$NrD*q&Uiew$d6@>HJb7XNZ zm-oJdj6}2|ye~rJy>Tg({uSF8C401e%r?%uJ|~sOg=v^AXiKS;FwO?sYt2TyZd+}y zT497JXZeT<$0wg7{X@mA(oERH*pMK|`a%^~?)E^OPQ#A-Xs1^)fATQ15|lx#B4V$!xRsM7fu>+ zXAa2{SYzhwO?R4212Ub5Cq>i@40x<|MF#u?E0jcKaE2c<*8557b3BT?|4?B!{Uj%9Ak8e$Wq^3a@p5|ipN)D@@b^i)EIPR;#u+FZ`*jKQlGHnMu`>wN|wU5QUC}_-uD~Jer5V6o!$hU(A zZFG&f!(%Un0QSVZ6vb6%WR207`BN6x_+^T^*}6naYtnNvBllg}>=tqA?F35b!g4*@ zl2U2$BBeyA)|Lv#cQf&N^J3ypU$WJg$E8UxurKQZ-$rqK?q@dG&?p^zDfVqSrZ>4; z7SVim9Nc$~-kuKU5FSp%3k$aq@E%Dv`m7BO+DW`RIsy zBu^&YTQ=EY&H)f*$=qa)zy4sGg!k0DJnczOEVFq%>fZJ%{(m&RRX|)_6D*n!ch@l3 zFu42R!QGSKPH<;%3+_&k;2wepcN;Xgy9@+(cMkWS^Y{CH>b2ME?y9bu^89RPcPKJZ zI5=;$%Y4^)H~uo-vgao@i?V6rteFst;3p>P{?Ua8pUD@Q@CWD^nvifuYmE-n za)A>JxA`{cZ4#8~uRK^Y{M`&OQtrf^x_A&#FXjtEQD==Z%b>pUqW5{vB~uZ_U{{*ksgEn)=(s92fW zKJB?U-G(8+IOQVVM@PLox|jnAg(d-m{tsy(o=W;~yOw^$r{hG|U#!`Fmo$GzQC^s6 zHiSwE>X1;DqkWD1E@Sc?D%@Y0Y2k-wOT6)kj6|1}X5owdzI1Av>?#%etT3f_Hi&~< zgI1na?hoCtTOAvk*?UGdb;DpdWNlaX@da&7r8xfq@ zPDbiNq4~jKSdeSV?w|Qs4`ls4#I}arRZ27?e=vsxrXwF;kwz6VXYj_vKjS0XGlQG0je=qns=AzNwlZ_hREq&+_k-TWF{>_MB9!uL2; zIqtk4qq%Es8NN#j84}?_RCe_vBU?KKQ!K{2dn-a=3a~RxyyzlT27(unW{zWy)|zs4 z#Xh;f)8WE5ye5JpxC7?^li|>5Ey#|Y<2oB0sGpIXAfED<_or2tV;_+yLSCA$fx0bNmMYL!H}Gsq=oU%SqO;it*-kiDchtNW@zN?w?20-w1OK)>r`R@Bv9hDS_BWz}{QqrXslOUYNz1^~VC3?Mb);JnbemO`$!vUE9K_%kQaVM=h;N4AADX1>V7REd%}U;cs8~$A#!*z=;&6%_P{Z z@9r}5Hq$fq9hxWt;C0%)X=2BmsK4w(gA)Oop}Ef*J}MZ<{hgG)K77*}mCdKz)%kTo z-pfzSY@>tRWIrVqCeqPm75+$%YbFrW`*G_y4>?fe`;PS-1Ey*q;vfC*vjvit&wB=K zr_O{Q_Er*%@s80u9959)*wGjO=y5Y!0JP`>O=n=GeiAj@MJjorG@L7k!73_D4Hf~P zIP|x6yUi$@(XIcdZWxY`0zmM>doe8cSsV4$cY{;k;|s3)@o8@kJpKg#88Jt83D$V< zU!vmek?70fHPOcmtwR6Z>;YLe#_l~a0YWmq1_oz26CMqs?BG>J(a8HG9TFSiux3W? z1nm}#>}oNZqW$XiR>g?75Lqm6bJW7!Em~ zpyBCCD6xky?Ll6N|8AEF6Du_TWDWc}U#*sqzz+QT$Qda}*S0ds~?|^#$?g!(=dC5B4M&ny}reb}x;nH$R39TgY}a;eTP(Jp=~c|6@UHO#`bmUqu2Gjq*^Z{>ucbsY+Mj@n@0obQPPLz#29gJ=oac-19TrWu~_S6@u zS!WF$J-;9@&y$;?l{m?bv<6z>Zl?bp*`{3jMo`)83x@q=GN~UZ2v|Y#^by=5-xsH1 zCG1*tCi_!-tM~~@RkCVI_9M#!KkiEhs%il|a+qH2Rz=yl5Nzu4?^&@Dp(#MwiX2Cbn=-XR&VkUEnBk<@td$Q*7CGZR}O zNZzqX_m6E`{A644s=eIhQIktzE^LY_xYMFHtip4AC|BsJw$^uB*Bxc8yhk^{fOl$7j#Y0@ArcxDiq46OH=$j0g(F63aA7rg*M^PD* zN+!5GXzdV-t(Nu){)3&01V}`Q5YDaXij+DzRdBG9B7PQO)#o{1?MpL)e~NADZ8^xj z9~s?JmHNtL3hN~+&*M8MiVgPdm%BE9b}U}BOqX^N zbeKasp+Vy+)IR4*To+i;RPUm;YthmVC zpK^9p8!;uXbhseWr$*p>Zzj5y7CKCWTY^!Dkl=r@t{gKvIOTB@FI+AVR29@Co{Q6Y zabdA~t!^QHFt|ZkHc(+5`NzcF!V6Xe&3E*zg`;fyjk4C^@VpryWBF-Pv$17u*-c6? zTm~U%SRj1dAUXET_ZuD8wEg|f+VbwAW*|)kouX%D>O~&Z-p(;lC`y*z=UZ(0*{Y2 z2()1Wn=n-kz{p5MKY7zwDuMH!VTVad=3hKKTG_i~!!{-2@O>8aR81KN_v2rJl2bNJ zerK6Knje$kpd^_0y`?2ED>pH=7XKO4GoLOA;t3SA9NN&2@#==Z=cEGI^D4C4QiGlo zb;l@?vR3!+IeLvh^)wjC*s0*!yxnp)uaI%vFna*)mk@zixL>Mt)A2Pp>8Yh{9-OoU zBfU?Igx;M=YpU(9gWjtUHa*Eza%CrDHq$y;eePIJ$zRsq@N}|3wB7L^YxjXLuH&^Q zS?Z3K@*G|XK(t(|wGof<{$S#6B@nKr6I;6c+~5jy^pKpgDT`Bbnz zK-hxjw$YB*Qq??)f4*xHk!D_x`e5vCU+B58hX}+GnZ7DOX|l3>VSWR7VQkRfZ?!Yk>xlWfaiYvZj> z00MNGQz&XPbFF{g7xtwl#y{IS{y3u6ue_J(uK>ypL zGw51}J0T|>&eaqRt@@2g=@#Ldd@-sqjUtdqe|1K^#;J4~U>{xD zd4c)3J$m3~)QPaUBsV!9l@_5w!zAI?Q2UA_9#X{71U?`+S_)B2M)NU?RM)m zp^c@;4u*8bAm;ddJ>XAF|Jh&EyZAQ3N{x)%Q(GM0*AxV|U;7CePgBE~>tkZ)F4wW& zfj2G=Q|CQnHcQcW}3-4{)X>3Towq_mYU-Ryo=Ivt`$py9zx@gpY71YIM z9xH5-szJTG+6>P-_WL&WpJtNphR$xEHLNAawNA(=CHM4xMqIj^#MI_JlXXdTpUKX} zi$oI~Tr9suZzz;mH5^8+2xx`O#QjDO7+FQzrV0 z>G4o?C!(zlkuZ1KrjsV-GM_NZnMzI<5pD4U2TAO{$TL!_;leHsn*4JlpOFee>oil< zTNnzjPwsO-glcZ+bT0vh<)F#LS(C{xBYb9$R(Ioo@NcvFhL=l}RL-rbRg*JIa4F+I z>%mYTUY!$?!>ZsO^AJ`M?67na1mEp54DRJ)68Bhe(t8sMcSzZ)_&Ooz2)OeWDl#AV zu16lGYlExxmC^Ng(Ep;h4b#AUYS=c@4TPD>hLza9ch>rMf3@z<#j&EYgf_mlbs>LO zY^YaVy%OgYb3;I1ezc@uAixC+6MmIl2-u9E*&vOpFXw%CYdv9OSvf?owfQ^xY9kd) z@UF(@qBJi+&^~W$pW(40aIF%pS;nZVaJs{3!=r9InV3x~BH7p5Ql{bYZzLN1Ty65@ zANp-a9HOg+)GB}ii^?yFQ%){Bf3xs69~nkdq}&m9G*o^WR~Cw7U$;VWvz@_>IrZ*_Uv(--iY2Uc(Xl ziHStau(UX<@qAl0W&+~vsAAG2e=hwRKe6u@IjN>SoxwC@Zhg2pRa}fyAMwsqEJc1b zMOU!cE4c=DhohclQ+UP$t>?L*7Q_X%D~b|ejY((T1uCy~rXn`o@KLt~s!j+|n4cka zlZb(R#2C|eRvO}#G{29-R})Fwv#VD$_4cz_f+4&j#5t$$)mg(MNaKnQC8-_ML@rGz zQI99cS(zN7RQY9BKiu33p^=f=i$I)Ig)XcQ55j;sPl1JsD_Y9ubnu;;t`1%FX9TX1 zF#LL_18zG?V=$Ia&Ta|Jjn>?%A*%Ukjev|k#BR4`0qEB+4StSx%(F~OHC50!L4*TB z_xI}dh!lj{mdT9&j)XH(BMK0&g_O|00R)LrFCsscstxa*eK$L_yAS6nm2jN<3L4ji z(<^ablSiLO`lu|7R)R43sjFMpSyl>~QtRAy&Ax)fV!-STDa? zq1=lg@tLWO|XvW4gT zP;H(Xix^&7O{lPJzr;adWYt2BQAvm6P~`oz0u-Et;{=La{Pa1awbt>kYQHss%h*Fl z4XrIUZH2nopMl<=NufP?U;<41e#>#`+)0Gt??xfZRrc%(Bg%fAQS7@2c#*bW26eR51qDWQ9L*RF4v9zOW)0Y_1jM;DqUW_jMwq={|8k=>y;a?QPNW1b#$ zQ7zM!t=f&y^}*Nt2pf)ms2xDDh2j0XijmY(6>4p?|1>gkRXp|&U;^xn8y-zdblOSlPAZNSQgpy&iD3=? zPo`XXH4-ICQtDoRd~w^CcvI z#jR8(c=Z(UqvhYeV`p&hQ5Rc7v*-Ask3WHT?mAWpkFu5fJVj7>2kJ|>dzO)h!V~=@DQm+a@+vhczh)HzaXBXu#jbM;5eSEa zV3A5Ll(jiS-en+qg@yQpy|LWS&U$|Eci{atN#Po{B}13rf~hTHy|eQ7+*K0ts@m!1WHwME)3GMB+#Uy$3*yYY4BE1W_m zn1|&piV*hL)@hEgl^b(bGNeqZ48Zd{^795yd7C>jgFXQ3gADCKVZ%Vane#ljlQ@>X$8VGD~T%vJsE#BGu?q`>wY=UXb_* zpffFZV&k&}?kBl>=O+Ga#tLLfaX6Vm6dp-d-AXJ)8N>66|FkuDFQka+BAke^A}=eh zs=_(_p{)vg)8?BD8H{g&wlarWp2$J41R8ak7ZJ84XVrvHDF)l8`I9fn3~ox<%%&lo z(PFkQZs{&F_9&hri#KC`w`{5~@!R2Q{)Nwd9zjJj$Q~0fRY{Eifd0C&wW~v^ZM_?d z{9#^R%1LO$OY9(S&d(ks^4vsysX5W_$1f~P(V=%dKuA^2;8yFfz!i$ycdMG!K=9fl zdQG{;JdT?5_|ND`3_M~{Xm-`5cD0b(!#_!L(0Ez>Sb)L-ije<=pRH$vL(SngR}54- z2UM~Pf3GJbR-n`fsVy>>&-`a}BTy#af2|9x-FTs2HuRTT{Rq1B!Uo-b(^Dfz(6`Lg zl?9CA)T*O2LE{L*r(=-i9y7jRLU;9{x3JMsYz4R z$mwr@@!Oq4NRN&5Fuj8$?D}C^mUZHdW=C*J`SZ&c$Na}rN~*>hfVJZI56(@Sia_4B z4~*y&GB3eh63;25x=*Ar5zMm#5%rmjNF=xD&MJEpi2pg;w;-F;L!+WC7tel>a6q*a z8eR*h`5=>OA*KPzqZWeD`y|pDbrci?j#*EDB<7%01*k=Z9@Q)&KryRUZhYdkn9ON$ z4>u69ctp`s#-RqC=?4$A;oOL1(Dn3PKbKg2;LgPH`DpB z8LuX$Pc!d!-TIO;;w#CYRKQKVU+_B^&?cwSJ?1QFKjlkW_K|^PkIqO-CH@BwG`*J9arn)p7**W zO8cF00NuDg)0O0r>s{e(XKr+G+@^N1c3*QoO_cN5f!&YWLZPTC%97=oiK7#n;6Q9N z7=kcanc{qp*krh*hyO`POJu{XQtgGDVU0{xD4gI5mGwB<$z^j#B20&au(;z>^i2}$9wFx-459bj5?VgVn0z2-u3pS{)uPWkVxkCH(k#uqi z36^ad+5*{mE8+1rkgK9NT;lhqp?QcTt^z|DC66it=UtFSPy_9XCo#WpCKLt?j{p<6 z(gpE04<~Y3V2HxPMGVo{MA?m|NhzvHKmCg_FQStru7&KF55QWP8yxjvspk=naQoBK+ECi?j8^V)@V z5u2da>-@|7Ux`1--G#y5&oQyOQ&VX}^n53I#V>P%l8+7fNjQ(<0#FWAE)8B!DewYA z9grOz(~2~skZ^^WcF{AI0?a-3-I98q-6O_WdwRv%nHs={6wk%Yc}W)RvImu7(ql&c zEYd0Y=mS}+v6ALIZcqjAM+KbaJTM!DV>8u}@-i~wbMQm+qHvAX<+GG}w z9%n?rTo|eRl}VSX6h|wo9B2m7X-8{=h%@A_zjNE|5M5KSWswc_pKGG?{11uwD{nm` zPqnzT&SyAhSJz42Y3+c7HF51(sTQ+6V^InwrNjV^gr-8w#%T+&)LE7G<<4{^&{IwOM z)D+99Px&PmxgO}$VmIX;Q4!+KNt~a0+aB4MIl+31HByCvrblEjn2$v5rx*4Y*$cDhhukY2E7FGAjXr@L_J_n^Wdt_-e zwrjjDgJWqZ^ybCT>aUyfq_^YA~(7drwa|8zr~pBQ-v*yQAXMwTMhQVX1!*M*|?ct$WSjY zAukK$1a?^cgrZa@`t3QC$=dldvdEL3Lz<$f#M5y4DfQwSJ@NU!nh0p5Rite&_WqI4 zT}UN1R{o18zB4)TifOQ7gx3?jG3_6f0cy}Piek*Y_aL1hp-A;dMR*3LetErCZ(jaL zYj^-H1mkO|v6)uyYH$L*dUTypH9-G_#qY4HGvPQwNuw;<#R?gbhOe|}+=g5BJx{Em zgtk|RiCh5+o|^A3$WUaSL92vOKC{w1&g4e4;pDXo3vfe zXGr^_`94?)^<$bC;rnN~&njBKz>+PKio>5?33H`k*N7x&jhcV2?r6+TzPz_gz^mhO z_3I1A;Uh`qIqE=H7AHI6wR2^Rd2*Q<%{w`=(D%8gPUuf=9)>D85$a%djSQ{_P@TL!En|>Gw7cEwii;DL|^G2x%_=t;$7Yvgq+W+)lg8l01yOo{N8ZW8y z5Z)?|m2nm(sZ>_F2o0${IUAsw{QHdp=TH4&U3pL79gp=HdoB3pQocY7klEz@f8-V)u7T&0B|zT(4jXlsN1T zdwRH)2*2OaRZ=n`M~b;Ziw>97E9|CqY&A9u)h$2VPfmKXbQt9n8%J(xD3?wj8xBya zX$jfPoqZ{*MILwp&UnD82L+cY#;o~rA<>2qK^K3_X(~Cz+DI@@F%`w!dxrBx4UC`F z^J3iJ8$wp1Y!&v3ujMgytpmxK+$3WC(ic_^*+)Vn0(qVM92&Pz_Fp0ESF~zZegQW&*<5n`Lap2FKIM~WnR@rbRn*hEd`41(@e={4s~jnwRW>~ zi3<6wlGCBT94GXAfD%Zpj6gNd%oXwLGgBOUNej?~)XXFAq0A>yyXA9L^l4zsTw0{3 z=3V@okjbFNi`XeNMRlDG2KVH@xo>!V>IV-mdmEt{yyt+LG=wm$)rz}#N?^nAtL6>K z7AuN|>TX&UJyFRrqiz=m7B!OC@mgr*35%DgNd{O(iI>n94(+D%86g0YI-kkNMcUtX zEnj~dY!r5px^-*^NO5Ezr>SJh zlq*p*#^}jW3=GXPiI>Iih2cS2=UXqFafP`S3>iWG3%6;w=dDtJs4-@uG;~B>G%bQl z2=Qunt>2|!k5^>M>e$q{PTZ5f+FnDt2th98#7zxPX zt38ZhdvRODHKw$>6GJ-pz7eIPVs79evOr;&bwjduloqCI%HpE0Ui8h!xyqzn&2!s} zaneOeJu9zfpd~~Cef#&run+V<8UB^)l{Zk(=d(_JT7TpttgXoj2h4u;6=oWsIT&KX zfER;rd1B%Wm}-zk#(Rp1BNd$cGts89oF##>GIxDR1N5$)@Du}dWd+0eR~KCreo{=M zcA{=|x0~I)%BzdS;w$N^(};lF4s!~0U9D=qomk?LWj!$n-yZvE8kTnRJ84HN`m;Y6 zz{4JQd^*!a_+rREm*?;9o+Z&;1QJ*5_0dNOlY`1N3`!kMB~fFZW)&nR#vse zb}fa3mN(ti0n?urAl45M5N&BEGc1AaPqQQ}LBm2BQDB~qv@{^_qgS_MbKh2qHXF?|*ZW^P&Z?j1V z`hN`nHvWJ75<}l_MPDygq{Y={^Xfu*bN2=J_dlNaqNviQampMDl(3jFNe+~r0$hB8 zXsNW{mI|Zjgw5C1t3axLZF`i!%KbjZEN4U4PDasDH?a!b7P@(VKMJcI5 z_sBdhJ4B6iP%@GaOoFMI{}eJH*}gBFiCD!D7_PX!c-rm=YR}TfZOs_9W^ra%6-fDM zj@yK@hlR#OpsGn+y8X0K0k!PxB$O>z?V^&Idh&QrfkpCvTC3M1s8u{sf(Y9(9xcBZVCF4i*{4Nr;K7@yVHs9Pn6yuj>Eh}Z$ zQ&(sap!`=m8%C&E&22(68^djaKFxoMlQ-g~vF3C$f^%Y?X$ezOS2F=W3?4wl8PgH4 zpjk2b3F*6Wt&q?3OWE4BALfleQlCy+FbT9G{mgyI#EAkOChb)x8eAy_(BAk?|B*!O zVT?$qnAKzsE!S9_i;I zpl|Y3T-oWvIp0g@U#Sk3(yLu6`(F6%x8G_Un^FGL04yTZHvA(Z1 zzADucMRxvflDb#a^Qy&}_Iqj^I=aDrQ9xTuUMx0a0=V79CyWxnKS*-?(;QeXm$Yt> zt0xNB?RsYxDY+j;|Lx^v*lC)@5Ny?Qf71M<@JpLPrL?fC!yv*kN{Al4QGIOVH89V5#_)Fum^PJ3MRWn=8^^zljV0nJ+aN5)C?^6muYmT_i|+dvq*glyy9n2#eTsT} zUjlep4@DbxG#&z6+tU}+lGSGeulTxMs^4#yW+?$L#P5h^0tc3Y*qh{z;RR_61)36o z26F8A*gAPV(I$OtOrE(K?GO|&NrG4ENjc}SL0x-#YK418pjn~bZMH_#`WM6&BU7oO zV;I@aZ+*)Zsp+HMGRtPRRvRYkPh)C*QTq1oG9XfodZEG@X1uI0j;nMr=u`FbG!Yt8 z`e9JpX`YhEIcz~uyk}-TCdbc_%4?hn*DiUNCC%2fi~AeJOo6kV+>>K~4lH-y-?s28 zeMOhv_=!iG6iXqvn{T9Lb6euPw7Nwfmec$B1->hxmI$lW$+yEm?q{1uRoWGy=H=8E z>A*Nxd`G8}J{>`id6wl)^!}&7thnr|N_NxbrU05Ni-}V$>Fbh7Ezh+DbGE$wX^)79 za7-7af}7BQp%>>@5}UFgW=>Ut>v2gs-o>7wR{Bbk0#e2~a=>h-t zpp5l8F|`o4I%K&a)2i%0=0}X(7bPg%VXdWQHF0zC zOvPPfgU#~m*{fqEV&VqqPg7l8*QdxME0m%2rO)g8Yfp5`MlWeGglI+LI;ZXSOL@)*JHW!1`~*RNqcNKKRwNDnO~{DPVU56Jw7yS=<>T@iz$sGLnjn zX`%C}C6j`onZml6WVOb_Bkzw5!~cscc@Q049}!t}NmwF*xSz-x3FH{5t;}&qHe?g8Vz4Gb$`-7{-KTm+(s#0luL#Oe_ z2B>^giXh1ZWb@qf!ONNBXZ_ROeICcVR}VUJe>SDRWgN=a$`dwRT{pGwnBf$6qFqFm z1W-HxO~U|E7ESy0`iqE@VO}ibxB$f7nPTeb%fTf1XM*^Bz_fJoD2L_Jfz$y3@;jA? z^cSO!A z-5Z}B#lNyFg%yt#?gcN+G;}$um_NmQQM41*clYmHCDvr;YS{A0kDE3>55x_J!M3OH zYllbxF1>b*-F6MV+tYLp+?YxI71{rtTOttnrKiU303(|k(!9{4@^Q(<_LJi%LoJ6)Mu{02`2@*Lg zb|0OWUPs1z_pCilss2orsHml(@lV59IT0bv$N$9|7q_+1aZXwly*#FDMX2CNlB-1@I9eQW-CKHBj{ep2mVfe4 z{rGaQcvBFc{3Y*e7Dl>mtSQPG`Kq$&Al8bQ>&X{g@20*sUV3P4hXgQr@tZx4-d9nW zBGZ(I8osNFG|9BMM$3Uw0L318DK>#jVW3KA6M7VZlD!;W&dH90V^Z!r>3lE(j?S-MNiP10f_?#7!(*S~ z3_4lOS&*)n?r)THj%U2q=9ZK7FvfRbe<@11Z%vMLz%O}#b?}Ql5L2HV>`diTCiC_( zfYm1RXF^Z;;zXi!Agk)5`|GvxQfpTGYhBX*kecd!x%`!cE4Yci&yfT;j3Aw9SI`gh zyDF`nml)?;+H56)H}L6xE9~Z6RM*H`3gP!qN=B9tyV|P%`+%i@5 z6ruDT{e-l5t}|2s{1g-)4iF9H_tkvNy7;H#ms7BzzL;UJt+-lTIln~e#We($TFHca=T({YD3|TAqE-V^yFgyn8a&nhs)V& zD3YX3=km^H)iFT5D)0Y>lb}ib49fY?qeO79iu{Qfg*Nu`#LoteGkO^tDN2EQN_hBE zOWFIus-HmXs8hdRA3MYY>G43u)t&X94lxd3lPF%5FY7a6s)^8>e%M59rQ+Z-T7(z=K$gnv%WqhCJNr6@gR2_Fchib}zDDwW$IDC!Si@S$9 zO{aTpG2LYzae3lJB&1+_8(w*`#18@V97!M5+Emc^#M=G%PFtTiY!0*1Mct$T1-(FC`nzip<}6L}9CjS%GP92i21<5lgSGGx%8#<9 z-`dZDWglfajOcF}g*8Asd)3VhlwZOhYtD@0aSm7G7v@j#)Mbc?!-6vhNXj9ZI7(W- zgfykNzysbR6bkKS0zDzxvS-I{a4dcc?X9VDC(4Cwn?==_Bo=5WUdkVXRUbK z2H@BDdYYx@{O{-Y~%mMO%(*ZekS0AJ=j{c9e0 z$AL_S%A{yl&FF2&xBktEeOYjgVI)4eCG zY1Q{l?+4iOOX~GXb{APLdpgFoSCT0IU}F#7OGViq>547e(#_fgbIMu!kRv`6*$L zrR-q>uTp$U5@#z& zn4dI$0M$=k#AN4}C~+mycQ_-3PKoHntAeZ2`ZUy5u~k*+;V9CIbhlfy6SA%M?;j$& zy*UQ`ecHg0=_*kYI5Ul;2T}^dILO_fZvJydP_HOJ>Ho+t$iwwV!ChQ)5x8HKza3 zH&yP}9r+?t9A6L|QokJD;__K_x3RDC0FLvssu+%QGF+^R1vYP-iqLSa!i5EwF-sV_ zZ5%mF5AA)w-zts2r zb!M|I2)ZP~wsgn=$9uW1zsuArvM*H;u+Nqe-)!f&cAhZ06ld*9OHT7t_0AHE`YvVi z>$S%yvc^jOZ(zVLNjLJnI3?;JzI<)o#||@b%BA^?6Dd-eF&3@Rg&>B zM^`P@YkV0)ix4lz02bf>Bff-s;0wz8!7Z4YFBe{T5zPnRBJt~8UNJISGq7cb^)eR! z!YuhEqyt?V`<0al+_`UZ%w~gf4X<}S(>%%)E3yyuEp%~^_q~2tUtIssNcDr}D(ZP7 zf^p>B>A_H~XjT&+vYxv;RgY=dRB4AwQu2=kd2Qt9PquaFnx%sE-ok+&I!Ot%P9JAX zs6XEDxNtM^h!>Q($st5etZRqava>_&J&p(R3;mMT4<~E=(}RvA3;>snNH~rSc@q^E zlPh~i`Ic;{O zY&-#U_;?t8L#t|S{xIQS)ywP{N5mz!Y6NkpN_fN;Rrd%g^j|w<8T}>K+ow7!A8_@G znzV}1XF`v2Zq23?f)F`^v@fj#H@(J|6$lj5ISEuJ}|2hetf;RxBuwvZ^qpT z`O9Oy!oIsM_7<Z`Z{vD86KqFl< zrng)xpafA+Zp;IvbB!LMhfH_lLT1hlZ&IB85*J5vhn|*J2RI_X6QFRZL*OX-8-60u zv+0(86Zl<4{;7b5Gex(uQqqIddPjIA(ad^foXxo;=HEcNVHN6kyE9m?CocId-=IjhFOP>4hP(Dfq37o-LmFK7@W0bR4l=0x?sY^;@UQm34<6* z?2e}~6MtN|T;OFZa}*TyF1(AP|MX>z3yEn2uhw0fPMAVW)XlGKG?$=Gm53mf;oLcA z(k!rmWv_q~Qu=L~)c(4h{S$&f@J}w-!8l?3X&W+1F#u`A(ZNKrf~iv6;o9uhX!X7mUPrXV@E> zjsXM_6GFE`W5>V%f(9_rR~D7QKcNR)>~pj6yNQgqm#-P#ah!u!SKJWOi`U&Rbc&C% zXm?w8T)0dnbejckUclw1#(48sfn*L4$TEwECQ(ZmpPZGZ5q1YRZv>(B6{X}87D~Fb zU5;7OZauVHy;^xAzxjuUZzdk|&9@xHIk|#rTR~1YGps|p!6rAUK1AJ99?h&IBomZ6 z4w^4CiAB-W51RF)vi4av;gI`HgJz45%9xBO@3l^58x>NDZDtSZshv}MZb7F#=K@n%DRftqGt)ihLBpo_Q|B?knT&Lv-KB-Gumf{W;p6c5kZzJP z9s1z<3JD$nB`c2#(piHWn&$5;*Vk_vP_J!t)nQxEmy)rpU@!Evt+RmE=x-5{XYrhR zIA#<9*%A^KHC@O=WDAUx=^dhI|9@BlO3h*=vPJ~|bu`Lkpg^AVz1HY2x;wfgRXw!! zZ{pXU5s%ke$bH;n%&{lcKWl{D5F3R0_oaELDX#v2wUJ_0gBsZrY`>u=%$Co$Ne!4kN5zh6Ui~5wz{!B zIlhcg49biT>zhwaq|(XAuz|m!;S(DEJP$y-um5b}9*){@qf%B}P)N0aMJ9joVRGR> zQfy}T-I_#T7|{BaT~q~ zyj{f>KR+RknS6?oG``y6SZB@tr&4!Wf7+emtOk$_5_^hr^NYB};-8+WZ#M0qj3U~q zlxvcd=#|S7%oaV+9Kc=^6G`M%LfbCR^<6h98!n9Y4uK+TMo%eE@BP@mT-Wya-L$~W zILFYv52M-+cm3*KqHLOj02w9-M*$RN#vw^FNi5UOp;6vh&>_}b7?aJLf9sXn4pH~H zHmj_>zb64_`|~tf(Q<)rwqv9|8CZ>>^XV%0|X`)+qzp` zzR>b9u^zU%83Er4K69FQgNLB)bAjd(TsQBIBsSz;_}te`jeu z*zhrITso-s2ym^qaxRi(bSlc?r`_y2e`LWBq}NRAG5lh zUAazdn=%rz%=LNmr%uUFTfpgt_HW(ODj-{rD( zRW;j`;rzNn%<(|~9YUh;cVhZ11Ug8cmw&o5jrP=TNS(qukFP2HZkZ_mAVMJ+ww9i> z+nu*Tzhe>5WHhf7d0_!s9wbWj7V6;0kYgk3N4@#oMtHRCfdY}ZbC4}8a)tDhkI$s~w-3jf758Er|M~hIV34fDa5RxkuAK&2a(s{bik*Zv z#xP9gY=z+iC$XRG3zC-77yTa>V2W|?@ak^^U4KU64#)o?>#YOY>X!B4Vg-szk>VOC z4h53pP@qWB7IzCR?hXZthv4p3Dp+s|#oYq5xNC6tAYa~d&$;*a-J3u5%1)MSotZVy zGqYl7IQe^Xc`wo6d+tb=#(JWrC{LzpjHM^H1d{s|JG~1nthca~p{U87`X& z8<`fXc7*xmvjlNspKo836Ohpf^sCl0xv_>Og@0DS1kCfiRTp<5BaaG-8F;m8;t`*J z;e*}}uMIUPmCKd5j_;^vI7Y^k^A|Pbys4DIxk?oO^#h%1`-F*@zn$LhO>`IY8lI97 zX(OvUfqPh8lf2Q|@~o4AfgoPA>sm>9r^m*qNL)9A5W2gEuQ`mWm`IDP>{RHP`y>9X zJgs0iH8D6!Mpx`l7&Y67p<9tkzjIR`6VGIN2ZeM)U#PbezCq@;5$xH`zFUGSQGXNF z(Vcl-9adOmzvsHon#H?g>hSSZTG@u>Q1Uh`3FEmva%G>gC0;&@i{z{OKoVBhiC1U< zwx!oUD*cz9sPez15r6`}$|5d=YLuTSv`x{sOrw@wx6s4mD)UrPkI=g2=&;&=RqG8EQpsyJk}S3{lsgeit%tjv(a*Be0$7opS~u^|SbH-!13RU- zI#pjj7lMwMH;FvU?T-ILNNs4j(05xtrIL)Z?>}fyRqMCeA zONeyXqoWO%(tGSbLi6uJ>rO{&?)h>^IK>{*@p&lX(9vOR%WB@rXdGk}6D7&cyn1mQ zCHO>T&NdBT%CLPgARb2UN`=-a-ev3PtYoMaIXX0^wY2k8QzB={3Kg>sseKb|JSXAR z;I~TmLg7c(OJ?q{2CE1x{qJ)|n=guiJt&u!G%)1EhmAxhnxQ#}^X1Kuu99zE^j1q(p07TkBia{&wjoStMxN&= z`I~h!ZB^uez-T&oZfJyu%dsdb&&CGDN(O*leD!MtAR|0-$D~oni&Wp|-&9lP_6Q-Y zcl#jE>nAU5Mmv7gAy)f40{RC>6cua;bN#e_(ojK@3`+6i8u;GJ{OiN?;_nM<*OjMf zbn{E?EnzAphw($3otaWs#=Vu~q{ua0U17!i} z<)21P=@}4oNqwSyH}hgpv#2FMddb&QV&vpYB8M{~ujSi1cY4UA0w000&&iojP>$ag zdZOIGJG9~42C6g4a_L9uJYrsl@}umDumARW5ffAtFo|DPMreY(NVa&5!dqMETds%K zw{7h-3I1K_IJYNQ!l?ssmfu)wq_sRXN!xYEv9sacXv|wHB5JXf9ab>iSQOxF_N21q zoEA#=J^B{(U$f0HFM5>cB9dzRsLL+PgzC)ZKQXyW1kMNtQzs&m#p^rZk=B1mQb()D z!M{(1s9%Xrt)E~Y(IM#m%P@O(^juXiCjUPnY5%+I#70x5{ZA+V?(4scYhsxwxqo-_ ze+mX>RMLR|x;6i2yDEptM#%Xel;wYw{*n3JO#g```Cnxi5lJ$z_}>@!e;3D0A*2!i zp(_9HQu2+)+L+jr&<>o?4*pTGr6PVRvsXWeGq~Ry=IDRB{D3+sppbW4g|_!di)UC= z!QAe!tljd}GSxpPe_ge9e{O#h_-o1wE4tzJy@tb&{Nn=IVdQ~h&Dpc~B*VzYYwt!Q zIooeP6|@5!wvTY51~@Vd&>~pSB=d;q4$6Un#mWy11b2lwT7u81^+EiGWzK~7`CBtg z?w++_OO}Ee8U~UW-#_li%A)T3+RXS%%y10NIBLS8fiEc4T~ODACfo<@x5G=VNZgAF z|J__)P*g6wyUd&rpA1=>_&E3bHo(@&LE&Ot?ihJLx2#^x!>lP_DPmu*x4-b9uw9qo zM!_h-V)qv*?kH+9f8P0R+C5tp`PJHLu&(aaR8jq{?ceoSteDB@?|*CtC1~C$py=DH zGX)q_FJ2_K8J3GAr>ZMzDgQV(H&qvf(o24pLA0m$8M#dPrjKlCR@RA#uC_1H0a~W% zDdXgabSx3|qGR)aJUW+I5)w1RA^YOA3k`OE_nsD^v6Vc3s|5c%@**ZUSaRpjVFi>7 zDnS1%TG^_sySqET<~M;N;UQiRRzhZKaN)%Fs`>Tt>G{yE4}Jo#wjsXU7J>n<#%Cbi z1Kkq?^-e(cPceFav|n{>VO=8MZ2arvx)I*rvKw&v;%K|2gG~Rn#ZAH!sQ4s(7P+Q# zMYq7BZRh*NedllPB{Kq{-EZz6Jm(s|HR999VZS3NT7P?3R7~sQ;^O=oTSGU1{J?<+ z;2$Wi>TX;*K|wY$aM=h1*;rTzF3^k4WzuMNSw8K3;LZ33ckfOgNpEnbu5WGK`O?PO z;>HN#wq!1qzV+h(vS-rl$_A-hWhZ}N zQ1C+ZUxoBoq#PkPlHcPKVb==xov`3Q$P8cG!#p$M3^RAfH4J&q#`kYjtW*^{4?K%L zEsts<=<$pmi;KSw(GG(a4m_2H&uAaagSL;tv1A?U)PZQt+k~^*VWavy=&jMlrvvQD^HV#C12vo^R$RW!Z-(IHL|bAn&tKM z0P?>TFXm}Q7-^E0hMrM$W25)=UEqzsr7?jxeZ!puC+Nf6 zOSLI`lMJbAuX@8*#U)^i+cF>io^;vDrtokLQ$HgMUfMf)b0hZB;??bfdn$M6eSkXu zQ>{NjY=N0Hbu-WT1Id!&bu}Pgidh+-xYyRb^@B9zMt@;pBJsh}-pKWNjJ*(^px@^T z3CPOHiW@GY&7k)o7>T_&_Lb0#>pL3xdycVXNM8sxZ$H^;>^hQ^5yQ&+EXJCC-Cx7&F0wZ4 zfo{+xG0XHz*@Az%s=)RqRN(vA^zC^2kw&j1!kAu>!Rxkh?VenXQ{F%!)qmO!HiJ_+ zEw&}#!LQ&$C(gdEhYe3LeIHU>Ob$}lmZLK;d|}<1$9;X*bFHE@6z`!Sx9Cz^_bOsb zVKlGhaJbCq=689Wk_EVBZ;OiKC2vZTEs$7RSCQrul!36v&c2LJTX~3$eU{?DVdPu6 z2MQ&*QbgG)NjH~?_!5!hBok;8?pTLp9futkg=#e1r zuATKAG;Bb;*^LK&#s3x5H%TuQlKM3q_58fqsM(zNS$a)&#Q=rnhN_4EWMu#IH7VdQ zvil7@6yW#Rgr&{zF$WywCmV0qMcs2GaTCop=K0gj^oUde**oCqw^?z*OCNz-te1)=I(I^xOOntJqlN z(!kPtS=Xmev3>h|`Z8oRPu_Ez{w7od=ZVL6_kc7&;9z%qC-nyWYY$2z!kEeMsZ{ox z>TD59k+OO{{d7gSp5dnS8Z+G228OG&+lo4BykFJw!oug)W#6C4*!N_*Z5Bl0IiX12 zf4C5xM`uGACvVb)|1kvgm@Tld(N^E8CiX~#8a=`cEW>6%RbYV8y79t24Ytd)I^fT zq*juIlzyOad-{IVn3c#k>G_)(fZenyGtsI4wJQ-pL2;SvVL|2w75YSkfBpu>OF(g9 z;`e^kMC@WbRFgsPhH?1P?xQ10ZaQt7+&ns0%Zxg`8nmdkpTj3v?j?QZek>C-5MB57 z-McitVrOo>7OmR^*yQv)7tz|Eukh3X4X4T|>4^k~x!I+cuvc{!sl*1X$~cJvi0mM0 zar(4)7N482iHAOD70)XRc2cs7@i&%2FW6~R0Q0S@>EC#$#1JOlO~gSCBk!D9SZp@jAMy~lVZ9S2=avXK;QCn7{J@!Go z3cyM#vu?=jS;;}4fxX&SI1Xt}jSXCJv!!RI?tbK3pKn_yecqSW!vP-mt`o;H?M9Q8 z8RK84_DoM_z^ubI??=hde^oXZQdHlyGH0l+y%Q06KTPTpv8Kw}n4T+X$7`cAn&t!9 z5WrO;^^U!$lYM(yqoSL`!^+o(*@kG6253%9jt6!Mbam{dOE@KnKM}0IF&hAhF0B#= z%jP*g8TXGWsqPOJdXu`76lfFJN z4I>?1B76I+ll-I;H%i{;oplvk=`Hkdn79*@ze(IeT;ToPQlyBdQseNq?6^WsYgdA< zz&zrM>Rj6?91F?qV{vj7Z~O@2GVAJD>-?ZKDodkc@Rr&5#H4Jir)T|;ct-?ZBJ16# zirPC$7tztxc(-Ho>~20};$CJyHXYQb!UgpvaPDDMRGy;*m;SX;{~6c@VZNG}!YnS} zxUfFlwjb0>wGL%Ax%Lul^V;Ipdr9;T;&e$DCM>BcBngnaO$6qdfJ~=E?{f2{6VgmE zE8#5YVUsp;H|P4*(DeWpe3S?zA#E)x65f?FRWp&a8PR9Vvo-}=Z87pls7%>sHz?8S zj2d%X$y>wwrY+Utz%etcJckVW` z4l9T435bb_NofnGo~tt9z8`5Or8}I-V)e9#@tFiU=R5Wqaa{PG6JKeK>6d}KP79JBpy?fSgXHDiNni+z;QPJu5F5Lc^ zLV7HbUu_BXGh@}z3!o1Tg)+<&~Z%*mBn{` zS7$NuTK~x1vz}??YUo#{TgWhyopgOqet-3DcyHtE1+H5LO_7l(qIVKu#Ax~XU`SxZ zr>V8VH<_5-Mch)v25t!X@w=Iw?V&Bt`Q=`+mu0LgyTJIFoq~Z$6c#NvE7kBvF?h zyaUkn0f&A3zx70^>m!PX%P3kY-M@GIVC(cz(!5-N!bzB-*Ec+GTe7$F&4EY;Vl+u& z>mPmL{B_Q~(2h;*vdN%N&;K*@Pfr8?gL+Dl1lg68CU>r~>JD&`cF3w?(|!3dW8(um zo%Rt~_VA0%fKA%9E+0D;jQTd1{!?BC`|EsTP!Nn514~WnU=r>wgf+9yVFU43qAc`vLe_LA=5>5ET&w!b`cEvo`$Mt3> z(E?#vZ{y#u$lbAA-BSkyEV|0Pi@j)En^R{L1E{l;NbXDAYzGwjpA99Eb^gNRqm&IN z8cA63a1rRGd+m=i*^%R!z^K{Dmo!jJ2D&QbdIQ_qp#J_h3CPlf?B(KH>o2&+!6#b3 z9)D@%G6L8LPCpSK@h0#m-m(B+L3t+N#yNDrt4=Pf1}`V|IAT`EE8->p3u_Y4_kGiQ zm^w;7(V~ohsnzr(RF;Nbn^&qLscK&Xx&$)n8A%2rj<6pu6>pd~2=^~2O``PAD9q2o z!G$QWov|`1Mvzp~KJG1K8lJ#74%GBlIP{JqAkCyfMg9~=KD8(e%u|(8L={pgm+w@S zJELnxDH9k0YTLh#?tT-XFl<2Bzd)2k$u2(_j)~-fawdjgUmYk+J{X^RkvdTtKWH+6 ziRfmyES@qr=K7xp`t;YO^EeZY$SdeL{sl?g&4@q6%5v64dkkuf-b+rnnp>wm)u{7B zObO&rtX3J6GmmP&_YF9E%Jl4p4w1K+ge>?OH9g|i(z+whRMB*t*X*2eNnG!QNZ%t5 z6#XMF?Dt^N$o%F9l{O@P)jrJ`=1^9LiTm+TmBoBuF_m2G^mQrDcv40ZK7j3Xk-`pJUYv6L(gxnXb~o-(-qA z8`;5&rBuW za~z>C8qC_Vwtq4MJMeJP<=^tLk4~YUZL02aHlo5OO=BG+I#_0D4nH_KNf!neM4F46 zelqyl$h6JB{yOXaT_;OJz6MTwf2=;aUel6|)P5i;nH7U5Gwhz@$w^0&SZY&O>Zo}y zJtd8NfCNXzYq|^&`FAxVznbN2jDT-*Ll+^JUYDqfH|l zxh3(A#W zs9u}`F`TQzC1`5z(~oH%zP+a7b_H-K1%?(I|R3*AtO+j)%c(zsa-K-`zms!)(KAdo&m(MhaGL{ zADeYl7?orMIBo|2iBZQs8v2X|mZIR*XR}N|pr#t#DsV{>)>H!KK(~<|5BIjKH%y`M z#$fX{@w}>gr9va$iwkC;E8&(GBNmgSqBi*!Eh%-(v!fu@ts9TIVbJ!@PG|jeZJpRO z!}l~}mO{6K6S6i@_A~o#AIRP#WuA|0E5;2S@^N1548R$8J_`PWy=X*=&f3lBk|+Ba zh>7#jJ$CEV+rDo`$a6->4>C;{5yv2M8FB9>V(}Yh?q_4k>Qm+>O-gzdfWzcS{5Epy zQ}^t2FT#f_rmW%T%bMsP3Hfb#k^l5fs z4VmMQ@yedgJgv{(CVY%u)P$x>=&3)Bla1zrgg0_Gyn5cB8E!_+9>W6PGqX3OFY3dE zEj_`KOqiKW+L3j2^v$ogh(0SI4RgwO&cN(*Es~r^ef!aR$2PrQ#R!q;^siD&nCVx;$zZEL5jUP;Io`As@{j z)tVGxO}GXA+Pzwr^C_D&Eb!5BFDj7gS?k}g7A{pi1%+uY|xT8|`+9(4we z!rnK-iA{v8{loXLD#5My)gV^o!TUFni&x3WY|}l9D+&458@r{(z^;R(`tfqVGhw&F ztq$son^|~tTa+wB8$1VT8h|$GHEUTV3*bJ1om^o!Uf6H|1DO@%z+ zD}PVA9M_RYGwEceQVWKI@4vXM{mkX64==hiX>#uv6umZn0wJT#PKAjA^>D~}JRUcX z9-6VFXPSc7)7!&>c+JXg5FF2d>z8Lj`db`uKXmrydZ$`xjQE>at%sP$OZECJbUjn5 zAub&$@WH^5s&&iFML4%7`V(<7m#I>q@5vKa0_B$D-}bqSuM}Ug+2EQbl&eWYu6*h* zFPB_JD}KTV-5pRo{lRNDI*qN$G7v!V-Ei+sqMP5zNO)4Z)|*gm2fkA26G(8HQ}wCGX}5ezNc8dztt%FL(Zf4feayVDIN>14G|#c zrL_`J@PxcVV2;{x)l5#?i2)nqjEj2H3&4bxQ#x0hP4I(TOtgJTBCRd+L}P6IyK(iP zLmkPp7Hf_*pj@6b)F=s}*!yI5sXxlI#xHiiPnE%J$yi8AnC-LKc;fRz^}x%#{fsxJ z37mvpl0$l*sK-C8ze?sq18hBo@59xKQ?O6nJv{Y;DouV{IIMfwO2G-Nq;dJ(sREPQ zgo5I+Er}wC^-rZmZt@;B`4;cwRrVj1tdJY+CT{O_URHJ=!bTWc!Dks40HS)h`r~e8 zxzn(G_JcS}0<~KdDQzv~`U{`2UJb<#v@R-uUb+?mns@!xxe*MaQQ<&cZkjDXeTHvM zF9^j=b#`is+edFenM?0#K_}(k|EN+x>_Wo#@e4PY&>XhtPAyBxe8F~xSy|#YhMq@N z)Kp0=dU(BY;03GokO_Y&WY6-(9!btQ_hcD8G7frgkYr9&kHNYxkx?k1-Wxgi#wg>z7F^*7O?x}z_z4vTht$$ zZV+8&R6lBTgl4KKpt&9l2e~XKShN+otxK76*Vyp$rL+7F#%R`=k9@+tJFg#dffe#m#f+~#~)yS$vUHQK;B)50s6#fIqXxs($TX?v6ZNg?RlIMFTk z)BU*uLW2<}th%}X7XG+54}BHXHDXN5j|%;@w6OZGGm~|<7>rv^xRn`#4}1St2XNlQ zkL)+{zAj}H`FiMe_&rCi@?r0h_kpH_47;Oj_G}Txk~g=^VZ8E89||O;vZq2V z&|_qJM{l?j^0GK#TXUsck-=*E z={@+WbA3~}TV&G+qyAU;ys>~)u3mL#JwEA45Ao>%qo3DWmp1pDzw;wK*Z9h8n^#0i zF-9-NyW5kz`vnL)eR3LawpW%&8JA;F_cw6!!EM9iZ+jbglhzfMvE)-T68zO)`xJl-nhGgNNm%eeX46k;B#HyNu5HY49&h%jkoL?2FFV9p0}! zPQO_z!_GohuoM^v1;a%xaV*1qdLDyrHudLa285d^*sy>^3GHO_CtNaO`c53nwTB1O zi5KMAeOZ#%#wU*s0aQ{Qf`bFOUDMw;^giyP{xONPgrccg9D1Kp++tdWNpCk$9$wkGiME4<` zXUsA6Cvzl%K}`D2M?K?2s@Id^el_o*q&#BQ2TzfkvoFMwj>RGE^7@2qSU**YEc_%| zmCCZ!=viA&%+(AVd5HGi`fsmMf&M`v$rbvX(?-y$B^!RQr=g+K@=K6P6rGKgAsI)} z;$_1E5@PvCUk!9Oi$euHqFEMg_%`6YToeS0EXzRYw^Z2?eKj=^&tB8n3FDe2-|y}3 z*18P*K1S>l7q8F0lg9FKIYRIichB;;jS-8lEPa*D|7_NkGDOMEzgxgC^AuC#1_GK0 zdBYyU3$bfeLGrohH6Yi9Y*)C&gU-U6T}%FqWH;w;h)N8jnOOG4*3i-Uq&s0>9@hFP z9|y0z${$h>JQv978^)M|-svk0uPV~#tf8F4rrGNP+*R|lAB@h##o@SrPmEB@=!$gw zlIAm`pCIfd$lGc1ogJN%rogPgi4SY*sL2tGti_9XaW$5Wx$3mkXtVd+4t+gZ*!D4( zuHKTMluHe#_ZfF%tNq)m>?f2R%DERtV)HxKkq6u?8L^LX2iJ!$B<9XHkKHg&R8dI5 zSD}SQpPFu5$bNdm9q&3i0uRipaTx0oySQ6mDiWBdXW@pEm2lEd_&ygf6YL*O_epZh zim-NR-Vv@gW$AfDkQvZD_k6HDqviY1zk z=`KASJaPqAQp`vCbPL4`Cr*6Aw1zRLdr$2g@6L*UobRI1&VN(KF0vS_#mQi(VC67LB%4Lt7fhh(E z^6uWh_oEWeY0(X5=&78Z(vZCXbd8qF4LKxy?|8yU1EJYValy}VeY)QdRdY+d?q|VB zDI?ldEu`9Cs2{*5gd}aMS9-t$5ZH&Y%XcaQG~=dZmLaaJR8GwePSmGjOdLl*u=9(A zO+6y~!1|K}fym&yj-w^oLVw*vEW9s#AHt0|D`mnLvML{cm7&9@y0igMy{7~j=69%R z>(9lQ%Devi+s6k!H`g!I_}^Jg$vK8d36H5;joK)jpTysYfFERk9;r{cFMSLxdV(NP zAVgUk}felw@GkD1;jIpR7KtzA^Q_5>Ue}a&u0Dnm&A-e+dG-W`^H(g2_-w( zbDOX9ZqTr><2`2FA*sgCu>h%95$O7_qLru1L?nYFxRvMY-_b-jA8Ea9I^w))TDa9e zXf5q;n8-AW`Yk3p4`Q>Y>Fwys+#<4~A5Yv$kE!F5r%2iIDiNkeuy&k)O}u+P$()JB zsRazT%GH}bWTt-4<=5>ICCY{ajX-@NWr5^2ArehQ85krvYoFk3T;IdhK8Ml*tWarX zEzts4#qc~yvvbv(7T{fTdsKy zN|ftiH3~B+WwqNlA=FnE)zlg2z1|{HafeKil-qm}0O%q6ZQg`qMmTpKEjCR`DjGIE zB`k6m_9<-vw<`T^*enOH;Ibmd@l`d>)vqYlosy8w`Wg ziAitPSwW{e!4?YM9C#rn`#%`Hz4!Z>Xwr0E`krXlvy(A)x zzBn+f_=k{Wl+9JwC&}!r)(kWBKC_Hw1UH6OknnpA&a$1SfqBJV9O!>BnVr+|HxZ0* zxg#NF?9bur(8aAzFW)dMaH4mY+35B)bBn!FUV=-)g=lhic2l_OzlQNUrh8%D><`zZ zQn-C_fv1CKQ5WClvW%ftT9tzKmp$6;dnoOk6Bv+C9A1x7!zNJrAkJ@uD&OTI0PBCkHGA+k0 zlAK{k*Zm{nN}}MoppVURv*zu;Wc9~|;lHimHl8@$cRCtz1bGv2@vJn>3Vw|gG*y{= z{QFWz!m?j|5^wX&bfh#75@ZR{+HIGrb>VeR=NG=x^bE#2j0n0D@;X26V8UK1I6l=VWIsoM)#rbf z|LzKSRFs0f7}#6#9l>|>eBo^kwClsU3T1fwON(2eNkj&aw5V$xD6=5O=q1NKN%-Ks zz0zv&l~M+v?9+1VZs!+N3eKl?shVqEcA40}cmJ1S?O(`s@2W0Oa5!QwT)pjWG$x5FCSheJbXpmRL zdiFdicV(uO;izUh-G!)8>SmU2p~(##{ud8hkln|J|CkCx>TX?1RPhC&6fM4$l~oCQ zwR{-2aUh>?dQwEIHZ;^P45lyw*9OFufbA#;z4%4$^^PCo#9cUUxrd1TmX*8k**58W zwrp}|^wxp{8ct&eIwjri4RoM@&#kkUlSZI^8_l8XY?M!vdgt%k@~~uNu4~>|_o1KA zMvRsU=n1WdPGTvddcbz%EGha*T%pYPq96T7`M_P8D`kJQ7IA;TRsG1}$;TSy-crD3 zZj=)}@ND3RC?ZHDCdXQ?Dg@N=xbCh%(!*F#i}e&|kf#|H*uWR$EcWawU0_{R2^=HF zhc6=u2XTwD^nV73Mc3Sab~O_Z(mbX{nCk=1DbGnpKex+7=Z1u)xyX-&bW;$D_kM5B zW?v1VQ<=nHR~~!1wFAj;vnK#c^r0zjQBT`|<5DJ@14oo4#&k#jdY^uOet7=fl5IA( zANqs}EL2dcA)4DH6D7FC^OUz2ZT7pQ+;g02m&+a@cu=2vg;2-c2)bj=R1j^CxGvBz zl<`?ONpl92_O+Pau z5Qk$>mQt2f$l^dYV!sKMLYq&B-cAYsUj0_w*Dinfr?qX z;QT~*gEd&)<>;F5>P60!U5#)RniZD+We=*{@|dIdz~fcIu=!X0I5)`=zCB#h&b$l{U<&X()6 zElDGFol|ayX$s83&tjM2v#2Mz6ZML z8`kZ}CX59Q%(|XHnU+ zKPt{S{pFJ3iCX)vgGc^6DqmEKRvi zqSPY}baaIi!ceC|UP5YF)pb`kb$!iP}w&=5J1iX6Mp7IxKI_)5!~lgIdkMTCpn zH=0dX{)zqLt{3>+^xplv!^B_X`e&+v*w~9IrFa=p0{7|HT2~#V+-2Cya`EeZSl(jmzIuAUo>B>`4Y67oiVXa3EyA!haHqKoy ziKWhNATXig_<@E_vD##%OFc1iHJz&J1J<)N zSP>j(StCz$h1i&)NUHcQCC(kdPUn}$Q$se;)y-bjCuT+v`})KnsZ1h2=Q{d6{3 z|5EtAzsT{yYxrHC72MF>Brc%kz$tt$4Eqjmt1IT+1G8JdjlIvNbgI z4~QY>wlM|X;gdAd>>zUFKR-q`UemHJ+`NV9b^p3eX+`ii5&aZZI^NviU2XIIlDSmjdBb->r*lEPrwG#8pr{c$D>f3c@!I;Bs;WI_5NEt-Yq2g>Ou^t#bIU*z zQwRM{zIy@do|}L?3{_6h9%kgoQK56`NZ2LqFCf6fZ?D2%L(#MCsIY%dwc08R<;hW- zR@tI*tp~bVZp731?RLYsvQ=;Y@{C-lSld(%$poEbL(A~KWxfB<;hVrE?2#N|A zQSkZAQ3QRwGI3EiP&(ewUyk&i7gX6j_!-dHTdA zWO97$rmcNfAW^d;D347(k8$f9VW!$5)@PQ;;V2KMHb*r+d=L2KPYHHBisqgnU45P7 ze45%7Oa*`M8LH`y?KR0F@6~?upBx!LZd>5pj7N`?#&jP3Golh(=lv?@8zDAX{gwPE z0rNFh?Q>82`?146jqTLwyI(r>ozMTM+k+|YZN`mH|GsqF4{%|h{~j6&Ew3(Br)$t+ zwGTopIA|D^$H0;skRDeS;>ItNAaZ(tz6cni*h;H zo{1N5zmWGH`FnCR#!6nNbhs#L!s9nW1w+!KAf5^j4s94Rmw7o+7-T-W&RS%TdG@mz znLR+bzFl;6l+8oTGF3sYzk%?wf4Ai{q*Ec#w;Z(|F+x(~zWIVneWS+abQ-cKbTSh1 zWT-j?KEt$&Xam}fel(FbLocP%El(n_bH1XM`VUpFxz|N$_*| z^Sdu_1vOqng(7dUQIddqcG6n^>SF)JF752c4m=31=ZmVd4mj@IuuyqJC49uVv7c1I z`IH|pg9hP_e_n0Yn$)D0D%NZ*E77wxb~+hWBV0dUV>{UJHA5zN)NS=HZA+XjCLIBE z8^m7t<`s0(nRaRH{xo~pieY`QKzu&kW!*jk%@@y(RzIab4PVzvK7%7D$YKoJGlIna zbq(K7>jOFo%;3gSD)l&cx^)LSgx`GxMGMO4I8+ITc{=y+PUg}JZGQwHN@>Nb=Fu6; ziu%)$P*~W*dQN?RxU1_Ks%)B+{?<^mwwyFV|HW6?A)Hz#c;dmzo2vo^|uq?EM!WRoHR3*JpLUC|Un+3euY zxV}?^$h^B2?a2HCh%=b~CI_|G>qYL+f!>gdxa4kDIw_dBPGi!B1g(9t#yoI?sHKh z+yuG%v(RKfDA`zr^>cxk2zoEpRoEHCduz4bl9DFa65X8)AYtzoDcr^IY1n^5-}iO{ zwypOP8}Y7sRAAbbKRn$w%J)njbpN05cXJJ?UV3+&>5 zVBI@83CTHyt4EVFl;lpFcpSqHspOq{ByUqLcT--F>tMnMK}4$m;_!cQ&Yujr+cI5< zDvIF2_(N!5_-o=5oyp@a7neZzDmK*x#jB<<2ZgO$uZKm6I@dbC+YHM6W~%yR8<|xs z=3FZc&BRb{T?(~o)~o$FgJE&5rma}Mh4QSDXD&MQg$>v*^u zlA=9bvTJ*gQmSTj`|RnqLaQ9OG!T3u{m$;u3qPm)fFWH*%IhJiqVlL74lzdrck3A8Gt@h|EjQ)0h_7ZawJ`F^2OLRm@j7Ol%}~pvGNd0m#iC%NPJXdmCOC-PtI@hC^R8b_)|3^IcHg(Q= zB==+rfLlQi&bMFYXU_ghG+PYMV2{9tfbB9n9ZzJ&1NJUVo7NSjgiF$0`YK+~`VFTM z>@8KJl2udRf3CJ!0QYu89Q{6^kgA&)bXxhMkRVy*6%osDju#^F53Jmgj-}2G;PP<^$Z*jyXQ_GF)?oScxX6Y_c*w3(CG45qsf!OgF z>%3yNmjXg7wsOM-aDy*G(HDU?y9nj#nx;}r3hhrwGI4AF<2zGir`HH4d^R-snB}^| zNJeoZU`no&4|38KaR{7Fz(SJbvab~JTJth5Y;sq=Ex zhxrYFj4>H;WKWHHZ+h?ZrIIN&q%A_2F;Cu$k^BBx}6yxd@H}H)D;FeFn=sZnpcHftE zFI}4NpqnYo{0PC?JV*u3Mv@KH@?x)}7X{9hfwmy|kK#!#^tWA`<0A+u*xz#{T8!aI z3_ni_m1~l1D`N@&?ayL}(U|~1e@`Pp=1|;5%92(lv~FddFJSrN@@iJEp!nH( zj7ydEy5Hdd8`pl*XOm7Z;CgpM7Px2Px!uCRx+0HWcv1vw@CSJ7D=TnMv(rQGIdjN9 z(3?I>WT;n6%xo5Sb$-N%c{Q%-?bPEAnIboinBNlFR`J$s%|X%CJFm&nf>pWb`)H_a zRRSfPv{wM3c#W}EfP&6_=9VNUc#Nut2WgfLe_O*3)d>$#Milux-KT%hn8)P$UjOGe zc3=Bz>6=-4h~iNam7X=~OFikijTb%*=?oj>pjRmGp$FxrjQPh+soedKVa?=3^e^~) zoT2mK3}X95b%?~yF51}mrxasYqQGQM?DzDx5_$%QB4kkIjh=;r(oWDehKy;~z^zODmT690Wt6XQPuj zzqed4zP2atj1FpP-oKiJ_+t2%Wfj8Hto&oT7T3wmHaH1tAwq1+A033jI2e-+2IcqC6#bm9JO_=1*iEGaYbh(0Oj-r*f*Uva zS$O&7K<8 z!f4M6QuA>JWp#>j4>s@RT8FMJJ4&4TKTm+nC0yndAG^5wl&?5E9Je-UXjGUsH?ovf zDOT@zMJ0y@I$L$^wQqcgHV767?N;u%c5~gW-uUbpkeNIAf2;; z{+KRk+ZCi=LTpF*J_&_g1Ihj8x!-jjgN;l zmimG+JpSwF$-%*E3nPb|@KiB7hO2q0Ed5G78hz15M7<>cQv*dS%cWj5rvYysef0yM zN!>NQm7Bzz6T$Ogyoxx$V@AB&UN&6W6EXG}*&We2 zlfScLmai2kMjJQ+9(Lt`ms-lv499o_PB`TLi5jxZ;OathTVIE6p1 zm5d?I;B}3!>`hp8m0bqCbLkshl2K<^O_1+Vf8iKKE7+IuUY>}%ztDdr0_^R1@622J z8Q-1|B26KG`q%Ht6RazxH!|<#5T5KYB6fHhfiNqhDih~cEj2}+(YO%*p}MFe=7zgQ zSQ8xdHpNP_k$o9=z5|(JO%CpF$+`o8i)NHQTfC zIQk;ae`xih@)(p@o+H;s4hQzWNtMrKW0;Xx7yEE7urW02aJBC9=3<-EGy*!ot#zQ= z)~ltku=Q?KR4dNmeId)VS(gmkd^O5>r+-iM{V&Q=TdS}(=s7$tzD2XRhtG%J)Vo&D z-fEQ~>_BZFctrd^NxdXVG8{hS1{3Kv+e9x?H$PtV*OI~>&jD}p-W3C%CWvN+@RpT;!@ONnt&RsQ+w-_*D?H0O$(}mmH7X6KpduSe%TJD|2}|!jTveFB}DFj z8vW~ASLCk{xzJFIijtC&LBPKT+QpW@2q>a2F8!xbVaDH|rEe8UP?g^ZegB~NoeTJP zc^Y9bKljuBUmX+%jtPy4?#lQ^|1*B3(r@2rck+M6eU)V3k&Np6cZ1Wf|8LvA^=Zuq zyKja37oIP@RTJhZ0hM&51+AZ_B9IaM=ZY6c-)63y&O~R~G|caq;nf z`F^{ zVizSYII|#rLHZqDsD=!wg|IMMyfWNsy!%3MGwk(=b2C*;iM3LishxHEoGgzQD za!9R(*{HlDT6SsbogeM&tM|w+UkY3(U9z+Av3!}XuI{^MUstc&{yl7}J~;kU3KACG z|K~HUThq9?eEF;?&`>SC6asU@t0jR}r|z=1b8XqSZRf?uaz((kiaQ<6U*z52_3l?y z{_cPK;-~$8wYj<1DxOz>(tnbLv4zEhU%(0d_itx!yDgELwqXDN z?`lGjytU%U^_4K6s?J)v*u(}sJ1Fkk*|Xx)JaFnl5IfFOGm~%(*4&HDVgKb%P5Z)B TXMb`p0}yz+`njxgN@xNAudRas diff --git a/static/js/app.js b/static/js/app.js index 1b0c940..a67f215 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -36,15 +36,6 @@ return d.toLocaleTimeString([], { hour12: false }); } - function downsampleIdx(len, max) { - if (len <= max) return null; - const step = Math.ceil(len / max); - const idx = []; - for (let i = 0; i < len; i += step) idx.push(i); - if (idx[idx.length - 1] !== len - 1) idx.push(len - 1); - return idx; - } - // ---------- charts ---------- const charts = {}; @@ -64,14 +55,15 @@ return o; } - function newDs(label, color, extra) { - return Object.assign( - { label, data: [], borderColor: color, backgroundColor: color, borderWidth: 1.5, pointRadius: 0, tension: 0.25, fill: false }, - extra || {} - ); + function envelopeDs(name, color, s) { + s = s || {}; + return [ + { label: name, data: s.max || [], borderColor: color, backgroundColor: color + "2e", borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 0 }, + { label: name + " min", band: true, data: s.min || [], borderColor: color, backgroundColor: color, borderWidth: 1, pointRadius: 0, tension: 0, fill: false, order: 1 }, + ]; } - const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c" } }; + const LEGEND = { display: true, labels: { boxWidth: 10, color: "#7d8a9c", filter: (item, data) => !data.datasets[item.datasetIndex]?.band } }; function initCharts() { if (typeof Chart === "undefined") return; @@ -79,7 +71,7 @@ type: "line", data: { labels: [], - datasets: [newDs("CPU", "#4fc3f7"), newDs("GPU", "#ba68c8")], + datasets: [].concat(envelopeDs("CPU", "#4fc3f7"), envelopeDs("GPU", "#ba68c8")), }, options: baseOpts({ max: 100 }), }); @@ -88,7 +80,7 @@ type: "line", data: { labels: [], - datasets: [newDs("Memory", "#81c784"), newDs("VRAM", "#ffb74d")], + datasets: [].concat(envelopeDs("Memory", "#81c784"), envelopeDs("VRAM", "#ffb74d")), }, options: baseOpts({ max: 100 }), }); @@ -97,7 +89,7 @@ type: "line", data: { labels: [], - datasets: [newDs("read", "#e57373"), newDs("write", "#fff176")], + datasets: [].concat(envelopeDs("read", "#e57373"), envelopeDs("write", "#fff176")), }, options: baseOpts({ ticks: { color: "#7d8a9c", callback: (v) => fmtBytes(v, 0) }, @@ -109,25 +101,21 @@ function updateCharts(hist) { if (!hist || !hist.ts || !hist.ts.length) return; - const ts = hist.ts; - const idx = downsampleIdx(ts.length, 400); - const pick = (arr) => (arr && idx ? idx.map((i) => (i < arr.length ? arr[i] : null)) : arr); - const labels = (idx ? idx.map((i) => ts[i]) : ts).map(fmtTime); - - const set2 = (chart, keys) => { + const labels = hist.ts.map(fmtTime); + const series = hist.series || {}; + const build = (chart, pairs) => { chart.data.labels = labels; - keys.forEach((k, i) => { - chart.data.datasets[i].data = pick(hist.series[k]) || []; + chart.data.datasets = []; + pairs.forEach(([name, color, key], i) => { + const ds = envelopeDs(name, color, series[key]); + ds[0].fill = i * 2 + 1; + chart.data.datasets.push(...ds); }); chart.update("none"); }; - set2(charts.cpu, ["cpu", "gpu"]); - set2(charts.mem, ["mem_pct", "vram_pct"]); - - charts.io.data.labels = labels; - charts.io.data.datasets[0].data = pick(hist.series.io_read) || []; - charts.io.data.datasets[1].data = pick(hist.series.io_write) || []; - charts.io.update("none"); + build(charts.cpu, [["CPU", "#4fc3f7", "cpu"], ["GPU", "#ba68c8", "gpu"]]); + build(charts.mem, [["Memory", "#81c784", "mem_pct"], ["VRAM", "#ffb74d", "vram_pct"]]); + build(charts.io, [["read", "#e57373", "io_read"], ["write", "#fff176", "io_write"]]); } function pollHistory() {

&wF_JwITWTj{3c9VhN`|h2ad{HlIB!_J^f`BlmP>~fA;%;tfx(Vw( z7*@oQ|AiWPC4Q7%Fa#pfGD81Z!mb9vE`^B|X=n&?$L%8F=nhNO!(sZ4@=WyfTOeOj zPF?4=pOEVuOq78M*RZuz6+cC3tQckl9XTXg`XusOh&vdrfUZDXd_19Gbv`1HJs=|} zF|AA*nQ<7=EgQ!wt2_58ShFH4yUH9fNvLD>KEOD>}_ucoGE zh2K;#$;8auK<�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 literal 141147 zcmd43cT`hb_cj_t!3J24AYG}_5khYw(yLVID7^%b-U28p3QF&xN$(&f^d=%D^iUIe z4-k+RN+8_rIj`sU-uvDA&o}P4aLK|MB~eD4vU)_V?W$@-IA`Tx{(vUpl=2 zi3;)xB+D$AAkrf0uB#bhEa023bGT;(2^e)hG)DdH_;>CaddX zwlzcQ6FYHuyu0)X(&l7o32S~lY0aZN;ST=jF z2T||HUhdfA1ZewyPTp^txDcCYs`%#W(|(g8F6 zIof!98aj(F|2Ykob-nuMbK93&beH~|xH4Y``r|*x^wsu1%+N{jjqWYWvso{w{lj!BdCa8l_KgzSS zqXW1>kVT3|eQ)pTi|I!U4C#w2SRSD>@clJD4ucpL39gP#sXq^YMBCf@^#vRMUoXrn ztK3