Data seeding, filter overhaul, UI scaling fixes, sanitized AlarmAnalysis copy

Data & simulation:
- simulation_tags.json: re-prioritize 10 tags to Medium so all four
  priorities appear in live data (backfilled journal history to match)
- SimHarness probe P2: ack-user/includeData ground truth + filtered-bundle
  verification against the live journal

Data layer (PrimeControls.calc/alarms):
- filters now drive every metric: queryStatus rows (Active Now/Unacked/
  Standing) filtered via calc.apply_status_filters; event_count counts only
  filtered instances (per-instance n_events); dropdown options enumerate
  unfiltered data so choices never collapse
- build_bundle refactored into _assemble(); when filters are active an
  unfiltered mirror is attached as bundle.overview - the Overview tab stays
  a plant-wide summary while all other consumers bind filtered sections
- _fetch_journal now honors includeData (required for ackUserName; without
  it ack attribution is always None); enabled for journal rows + source
  detail so Top Ack Users and the Ack By column populate

FilterBar/Dashboard:
- root-cause fix: bidirectional bindings never wrote dropdown selections
  back to session props, so filters silently did nothing; Apply now pulls
  values directly from the sibling controls, Clear resets both
- multi-select dropdowns cap at 280px and wrap chips; filter bar row is
  auto-height so wrapped chips push tabs down instead of overlapping
- filter bar hidden on the Overview tab (not applicable there)

UI scaling:
- Header: Prime logo added; KpiCard compacted to fit the 71px KPI row
  (basis-95% overflow fix, chip hugs text, embeds clipped) - no scrollbars
- Overview timeline: thinned x labels (minDistance 140), labels anchored
  below the axis, smaller legend markers
- Bad Actors focus chip shows the full source string with the clear X
- popup repeaters (Daily Activity, ScoreBars) no longer overflow
  (useDefaultViewWidth/Height off, taller strip); 5px table cell padding

Tooling/tests:
- lint_project.py: accept propConfig params.* as param declarations
  (Designer saves strip null params entries)
- tests: 31 -> 34 (status-filter semantics, overview mirror, filtered
  event counts)
- .gitignore: runtime churn (valueStore.idb, local-system-properties,
  migration logs, thumbnails, .schemas)

New project: AlarmAnalysis - submission copy of PrimeBAT with client-visible
Prime Controls branding removed (logo, subtitle, project title, shift report
heading); PrimeControls organizational namespaces retained per spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:27:48 -05:00
parent b70b4d6991
commit d353c5001e
165 changed files with 13988 additions and 525 deletions

View File

@@ -301,6 +301,56 @@ def test_build_bundle_filters():
start, end = T0, T0 + 4 * H
b = calc.build_bundle(synth_rows(), [], None, start, end, end,
{"filters": {"areas": ["Area2"]}})
assert all(a == "Area2" for a in b["meta"]["areas"])
# Area2 = 5 baseline rows (i % 4 == 2) + 18 flood rows
assert b["meta"]["activation_count"] == 23
# dropdown options enumerate the UNfiltered data (never collapse)
assert len(b["meta"]["areas"]) >= 4
# event_count reflects only events of filtered instances
b_all = calc.build_bundle(synth_rows(), [], None, start, end, end)
assert 0 < b["meta"]["event_count"] < b_all["meta"]["event_count"]
def test_build_bundle_overview_stays_unfiltered():
start, end = T0, T0 + 4 * H
b_all = calc.build_bundle(synth_rows(), [], None, start, end, end)
assert b_all["overview"] is None # no filters -> Overview uses main bundle
b = calc.build_bundle(synth_rows(), [], None, start, end, end,
{"filters": {"areas": ["Area2"]}})
ov = b["overview"]
assert ov is not None
# main sections are filtered; overview mirrors the unfiltered plant view
assert b["meta"]["activation_count"] == 23
assert ov["meta"]["activation_count"] == b_all["meta"]["activation_count"]
assert ov["meta"]["event_count"] == b_all["meta"]["event_count"]
assert len(ov["pareto"]["rows"]) == len(b_all["pareto"]["rows"])
assert ov.get("overview", None) is None # no recursion
def test_build_bundle_filters_apply_to_status_kpis():
start, end = T0, T0 + 4 * H
status = [
{"source": "prov:default:/tag:Plant/Area1/S1:/alm:S1", "display_path": "",
"priority": 3, "priority_name": "High", "active_ms": T0, "unacked": True},
{"source": "prov:default:/tag:Plant/Area2/S2:/alm:S2", "display_path": "",
"priority": 1, "priority_name": "Low", "active_ms": T0, "unacked": False},
]
b = calc.build_bundle(synth_rows(), status, 0, start, end, end,
{"filters": {"areas": ["Area2"]}})
assert b["kpis"]["active_now"]["value"] == 1
assert b["kpis"]["unacked_now"]["value"] == 0
assert all(r["area"] == "Area2" for r in b["active_now"])
def test_apply_status_filters_semantics():
rows = [
{"source": "prov:default:/tag:Plant/Area1/Pump:/alm:Pump", "display_path": "",
"priority": 4, "unacked": True},
{"source": "prov:default:/tag:Plant/Area2/Valve:/alm:Valve", "display_path": "",
"priority": 1, "unacked": False},
]
assert calc.apply_status_filters(rows, None) == rows
assert calc.apply_status_filters(rows, {"priorities": [4]}) == [rows[0]]
assert calc.apply_status_filters(rows, {"states": ["unacked"]}) == [rows[0]]
assert calc.apply_status_filters(rows, {"states": ["acked"]}) == [rows[1]]
assert calc.apply_status_filters(rows, {"states": ["active"]}) == rows
assert calc.apply_status_filters(rows, {"search": "valve"}) == [rows[1]]