# ONBOARDING — PrimeBAT Alarm Analysis Dashboard Getting-started guide for a developer picking up this Ignition 8.3 Build-a-Thon project mid-build. Pairs with two other docs: - **[README.md](README.md)** — the docker stack, gateway-as-files layout, provisioning. - **[CONTRACT.md](CONTRACT.md)** — the *frozen build contract*. This is the source of truth for every prop name, binding pattern, session prop, script signature, style class, and hard rule. **Read it before editing any view.** --- ## 1. What this is A single Perspective dashboard (`PrimeControls/Dashboard`) that answers "is my alarm system healthy, and what do I fix first?" from ISA-18.2 metrics. All data comes from `system.alarm.queryJournal` / `queryStatus` (no SQL, no tag bindings) via one script package, `PrimeControls.{calc, fmt, alarms}`. One journal fetch per refresh produces a "bundle" that every tab/popup binds against. Gateway state is version-controlled **files** under `ignition/gateway/` (see README) — you edit JSON/Python on disk and tell the gateway to reload; there is no separate "deploy." ## 2. Get running (5 min) ```bash docker compose up -d # gateway :8088 (admin/password) + MariaDB curl -s http://localhost:8088/StatusPing # {"state":"RUNNING"} when ready (~1-2 min) ``` The committed gateway files already contain the provisioned DB connection, alarm journal, API token config, and simulator tags — a plain `up -d` restores a working gateway. Only run `python3 tools/provision.py mint-token && … provision` if you rebuild from a *factory-fresh* gateway (README "Reset" section). The alarm simulator timer script is a Designer-side step — see [test-data/README.md](test-data/README.md); without it the journal is empty and every chart shows its empty state. Open the dashboard: ## 3. The edit → reload → verify loop Everything under `ignition/gateway/projects/PrimeBAT/` is a plain file. After editing: ```bash python3 tools/lint_project.py # MUST exit 0 (banned strings, Jython, schema) python3 tools/provision.py scan-projects # hot-reload project resources into the gateway # (use scan-config instead after editing config resources or tags) sleep 5; docker logs buildathon-ignition --since 1m 2>&1 | grep -iE "error|exception|traceback" curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8088/data/perspective/client/PrimeBAT ``` `lint_project.py` is the gate — it enforces the CONTRACT's banned strings (`SELECT`, `[default]`, `BuildathonSim`, f-strings, …), resource.json boilerplate, and style-class references. Keep it at **0 failures / 0 warnings**. ## 4. Unit tests (pure data-layer logic) `PrimeControls.calc` and `PrimeControls.fmt` are written to run under **both** Jython 2.7 (in the gateway) and CPython 3 (for pytest). `tests/conftest.py` imports them straight from the project's `code.py`, so tests exercise the real gateway code. ```bash pip install pytest # not vendored python3 -m pytest -q tests/ # 31 tests (calc + fmt) ``` `PrimeControls.alarms` is the gateway adapter (calls `system.alarm.*`) and is **not** unit-tested — verify it live (next section). ## 5. Browser verification harness (no Designer needed) You can render the live Perspective client headless and screenshot it — how the tabs and popups in this repo were actually verified (the schema lint can't catch render-time bugs). Chromium needs a few system libs. If you have sudo: `npx playwright install --with-deps chromium`. Without sudo (WSL2 dev boxes), fetch and extract them locally: ```bash mkdir -p /tmp/pw && cd /tmp/pw && npm init -y && npm install playwright npx playwright install chromium mkdir libs && cd libs apt-get download libnspr4 libnss3 libasound2t64 libasound2-data for d in *.deb; do dpkg-deb -x "$d" root; done export LD_LIBRARY_PATH="$PWD/root/usr/lib/x86_64-linux-gnu" # then run node from /tmp/pw ``` Minimal probe (`/tmp/pw/probe.js`) — load, wait for the bundle to fetch, screenshot, capture console errors: ```js const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); const p = await b.newPage({ viewport: { width: 1600, height: 1000 } }); p.on('console', m => m.type() === 'error' && console.log('ERR', m.text().slice(0, 200))); p.on('pageerror', e => console.log('PAGEERR', e.message.slice(0, 200))); await p.goto('http://localhost:8088/data/perspective/client/PrimeBAT', { waitUntil: 'networkidle' }); await p.waitForTimeout(11000); // onStartup sets range -> bundle fetch -> render await p.screenshot({ path: 'dash.png' }); // click a tab: await p.getByText('Bad Actors').first().click(); await p.waitForTimeout(4000); await b.close(); })(); ``` `node probe.js` → inspect `dash.png`. A live gateway-side data probe (runs the real `getDashboardBundle` against the journal) lives at `ignition/gateway/projects/SimHarness/ ignition/script-python/probe/` — enable the `ProbeTick` timer, scan, read `ignition/gateway/projects/.probe/out.json`. ## 6. Gotchas that already bit us (all in CONTRACT.md, repeated here) - **Jython 2.7 in all gateway scripts/transforms:** `%` formatting only (no f-strings), no `typing`/`statistics`, every inline-script line starts with a tab. Wrap any `system.*` / `PrimeControls.alarms.*` call in a **bare `except:`** — Java exceptions bypass `except Exception`. Every transform must tolerate `null`/empty (the bundle can be `None`). - **A view cannot nest sub-views inside its own folder.** `Dashboard` is a view, so Header/FilterBar had to move to `PrimeControls/Shell/` — nesting them under `Dashboard/` made them resolve as "view does not exist." Put shared/child views under a *pure folder* (`Tabs/`, `Components/`, `Popups/`, `Shell/`). - **Input params must be `persistent: false`.** With `persistent: true`, Perspective bakes the last-received value into `view.json` on render — the live bundle (with sim tag paths) got serialized into a tab and broke lint. Parents/openers always supply these params, so persistence is never needed. - **No SQL, no tag bindings, no `journalName=` literals, no docked views.** All resources under `PrimeControls/` namespaces. - The gateway writes runtime churn to `ignition/gateway/config/ignition/tags/ valueStore.idb` and the trial-clock system properties — don't commit those. (Worth adding to `.gitignore`.) ## 7. Where things are | Path | What | | --- | --- | | `…/views/PrimeControls/Dashboard/view.json` | Main view: onStartup range init, `custom.bundle` binding, tab embeds | | `…/views/PrimeControls/Shell/{Header,FilterBar}` | Health score + KPI ribbon + date range; filter bar | | `…/views/PrimeControls/Tabs/{Overview,Analysis,BadActors,Journal}` | Tab content (embedded, tab-visibility bound) | | `…/views/PrimeControls/Popups/{HealthScore,ShiftReport,AlarmDetail,EventDetail}` | Popups (IDs are constants — see CONTRACT) | | `…/views/PrimeControls/Components/*` | Shared components (KpiCard, SourceRow, HeatmapCell, …) | | `…/script-python/PrimeControls/{calc,fmt,alarms}/code.py` | Data layer (calc/fmt pure; alarms = gateway adapter) | | `tools/lint_project.py`, `tools/provision.py` | Lint gate; gateway provisioning/reload | | `tests/` | pytest for calc + fmt | ## 8. Current state & what's next **Done (Phase 2, committed):** all 4 tabs + 4 popups built; Overview timeline and the Bad Actors ranked "Top Sources" table are **browser-verified**. Lint clean. **Not yet live-verified:** Analysis, Journal, and the 4 popups were schema-verified and reload-clean but never rendered/clicked — do a browser pass (section 5) before trusting them. **Next phase — the Header.** It renders now (after the Shell relocation) but is slated for rework, and a benign `React.cloneElement … null` console warning still needs chasing (the KPI ribbon / dropdowns are the suspects, since it fires even with the header painting correctly). See [plan.md](plan.md) for the overall design and the remaining "build if time allows" ideas (compare-to-prior-period deltas, native drawing gauge, shift report polish).