WIP: feat(research): executive statistics deck (universal zero-scroll, horizon tabs, density scale) #20
No reviewers
Labels
No labels
blocked
bug
enhancement
high-priority
low-priority
needs-info
needs-triage
ready-for-agent
ready-for-human
referenced
research
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gabogg/hikcentral!20
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/responsive-statistics-deck-research"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📌 Problem Statement & The Physical Display Paradox
The HikCentral integration platform currently provides:
DUAL_OPS_DECK): A mission-critical 60/40 split screen designed for physical door control and live real-time ingress/egress. In operator mode (body.role-operator), it delivers a distraction-free, 24/7 wall-display experience.content-analytics): An administrative diagnostic interface requiring manual date picking, multi-step dropdowns, and table scrolling across disjointed cards.However, executive stakeholders, facility directors, and SOC operators lack a dedicated single view for statistics and graphics.
💡 The Conference Room & Projector Reality Check
The initial draft proposition assumed screen resolution dictated physical display size (1080p = small laptop with vertical scroll; 4K = large wall screen with zero scroll).
In practice, enterprise presentation spaces introduce a critical paradox:
1920 \times 1080).\approx 166DPI), making ultra-compact text hard to read. On a 72" projector (\approx 30DPI), pixels are physically large; high information density is crisp, legible, and highly advantageous.🏛️ Reconciled Architectural Approach
1. Universal Zero-Scroll Invariant (Locked Viewport)
height: 100dvh; overflow: hidden !important;).2. Horizon Tab Separation (
DAY/WEEK/MONTH)Rather than stacking Day (
D-1), Week (W-1), and Month (M-1) in a vertical scroll cascade, each temporal horizon is decoupled into a dedicated, high-density analytical cockpit:[ D-1: COMPLETE DAY ]: 24h Diurnal Flux Curve (Ingress vs Egress vs Net Flux), Continuous Riemann Occupancy Envelope with 95% Confidence Band, Hourly Kinetics Velocity Matrix, and Day Portal Attribution.[ W-1: COMPLETE WEEK ]: 7-day Day-of-Week Diurnal Intensity Matrix (Heatmap), Day-by-Day Volume vs Peak Headcount Bars, 7-day Performance Ledger, and Weekly Portal Balance.[ M-1: COMPLETE MONTH ]: 30-day Footfall Trajectory & Peak Envelope, Multiplier Stability & Drift Trend (\hat{k}EWMA), Nocturnal Calibration Ledger, and Monthly Gate Attribution.3. Operator-Controlled Tri-State Density Scale System
Density is explicitly selected by the user to match physical room ergonomics, independent of screen resolution:
[ 0.85x DENSE ](Projector / Wallboard Mode): Tight padding (6px 8px), compact typography. Ideal for 70"–120" 1080p conference projectors and 4K command walls.[ 1.00x BALANCED ](Standard Desktop Mode): Default tactical layout for 22"–27" 1080p desktop monitors at normal desk distance.[ 1.15x COMFORT ](Laptop / High-DPI Mode): Enlarged typography (13.5px body, 32px KPIs) and relaxed spacing for 13"–14" high-DPI laptops to prevent eye strain.localStorage(hikcentral_deck_density) and deep-linkable (#density=dense).4. Unified Tactical Top Bar & Fullscreen Integration
[ ⛶ FULLSCREEN ]), Refresh, and Deck Switch into a single 38px tactical top bar.📦 Concrete Deliverables in this PR (Phase 1 Foundation)
docs/architecture/rfc-executive-statistics-deck.md: Comprehensive revised architectural specification, mathematical RFC, ASCII blueprints, height budgeting formulas, and phased delivery roadmap.docs/README.md: Master documentation index updated.app/schemas/occupancy_models.py: Pydantic v2 domain contracts (SummaryPeriod,DeckDensityScale,ExecutiveSummaryResponse,CompletePeriodMetrics,DiurnalTimeseriesBucket,DayOfWeekProfile,MonthlyDayBucket,PortalAttribution).tests/test_analytics.py: Unit and contract serialization verification tests.🔍 Verification Evidence
pytestcompleted with 0 errors).ruff checkandruff format.test_executive_summary_schemas().📋 Automated Code Review (Standards & Spec Axes)
Target PR: #20 (
feat/responsive-statistics-deck-research)Commit:
a87a3f0Base:
master(b07490e)📐 Standards
1. Hard Documented Violations
docs/architecture/rfc-executive-statistics-deck.md:312, 363— Forbidden Color Tokensdocs/standards/ui-design-guidelines.md§ 2.1 & § 7 (Color Discipline & Palette).INGRESS (CYAN) vs EGRESS (ROSE) vs NET (EMR). Soft pastel hues (Rose, Emerald) violate the strict Tactical Telemetry palette; telemetry charts must map strictly to established tokens (--color-accent-hazard,--color-telemetry-ack,--color-telemetry-cyan).docs/architecture/rfc-executive-statistics-deck.md:203, 217, 226, 248— Blueprint Grid Invariant Breachdocs/standards/ui-design-guidelines.md§ 4.2 & § 7 (Tactical Blueprint Grid).gap: 8px;for.executive-stats-deck. Tactical Brutalism mandates structural 1px grid separations viadisplay: grid; gap: 1px;over--color-border-grid.tests/test_analytics.py:402— Missing Return Type Annotationdocs/standards/code-standards.md§ 2.2 (Type Annotations).def test_executive_summary_schemas():lacks the explicit-> Nonereturn type annotation required for all test and helper functions.2. Baseline Smells (Judgement Calls)
Primitive Obsession —
app/schemas/occupancy_models.py:396, 464direction_type: strinPortalAttributionandperiod_type: str = Field(description="'DAY', 'WEEK', or 'MONTH'")inCompletePeriodMetrics.DirectionTypeenum (DirectionType.ENTRANCE, etc.).period_typeshould also be a typedStrEnumrather than an unvalidated free-form string.Mysterious / Inconsistent Naming —
app/schemas/occupancy_models.pyCompletePeriodMetrics.net_flow: int(L403)DiurnalTimeseriesBucket.net_count: int(L427)PortalAttribution.net_balance: int(L468)(I - E)is given three divergent field names across sibling response models. These should be standardized tonet_flow.Speculative Generality —
app/schemas/occupancy_models.py:393-489🎯 Spec
(a) Missing or Partial Requirements
Context: PR is explicitly scoped as an RFC and schema foundation (
WIP: feat(research)...), not complete feature execution.GET /api/analytics/executive-summary?reference_epoch={optional_epoch}, but no route handler, repository queries, or background service aggregation logic exist yet inapp/controllers/orapp/services/.app/static/js/src/ui/executive_stats_adapter.jsand §5.3 CSS rules; neither the view adapter nor styles are included in this PR.(b) Scope Creep (Unrequested Behaviour / Claims)
[F8]toggles directly toSTATS_DECK... URL hash#deck=statsactivates the view directly on boot". The PR brief focused strictly on layout research and data modeling, not application-level keyboard intercepts or router mutations.idx_counting_events_range ON people_counting_events(timestamp_epoch, direction, count). This composite index already exists inapp/db/database.py(L268).(c) Conflicts with Current Capabilities & Existing Code
MEAN DWELL: 88 min (σ = 12 min)and schema L411 definesdwell_standard_deviation: float = 0.0. Under queueing theory, Little's Law (L = \lambda W, RFC L113) only estimates expected mean dwell duration (W). Tripwire counters cannot produce a standard deviation of dwell times (\sigma_W) without tracking individual visitor entry/exit pairings.OccupancyRepositoryqueries joincounting_camerasfiltering byis_excluded = 0 AND is_active = 1. Becausecamera_index_codeis not the index prefix inidx_counting_events_range, SQLite must perform table lookups.2084\text{px} = 18\% + 43\% + 39\%, but CSS L248–251 introducesgap: 8px(2 row gaps =16\text{px}) andpadding: 12px(24\text{px}vertical). Allocating100\%height to row tracks without subtracting gap/padding dimensions will cause viewport overflow on strict 4K displays.average_occupancyandriemann_average_occupancy. The service layer only computes continuous Riemann integration (get_cycle_average_occupancy_async).Summary: 6 findings in Standards (worst: non-compliant color tokens and blueprint grid gaps in the RFC layout specification); 8 findings in Spec (worst: mathematically impossible claim of calculating dwell standard deviation from aggregate tripwire counts).
⚖️ Draft & Review Reconciliation Receipt
Target PR: #20 (
feat/responsive-statistics-deck-research)Reconciliation Commit:
c3c5ac7Draft PRs exist precisely to stress-test architectural ideas, contracts, and layout math before sinking hours into deep implementation. Rather than treating the initial draft or the automated review as absolute binary verdicts, this reconciliation synthesizes the valid strengths of both sides:
📐 Standards Axis Reconciliation
Rose,Emerald) violateui-design-guidelines.md.--color-telemetry-cyan, Egress to--color-accent-hazard(Amber), and Net Flux to--color-telemetry-ack(Ack Green). Preserves instant curve differentiation while adhering strictly to Tactical Telemetry tokens.gap: 8px;for card separation.gap: 1px).display: grid; gap: 1px; background-color: var(--color-border-grid);with cards havingbackground-color: var(--color-bg-surface); padding: 12px; box-sizing: border-box;.-> Nonein test.code-standards.md§2.2.-> Nonetotest_executive_summary_schemas().strfor periods and door directions.DirectionType; lacked enum for period type.SummaryPeriod(str, Enum)(DAY,WEEK,MONTH) and boundPortalAttribution.direction_typetoDirectionType.net_flow,net_count,net_balanceused across models.I - E).net_flow.dwell_standard_deviation, redundantaverage_occupancy) were pruned to ensure schemas stay lean.🎯 Spec Axis Reconciliation
WIP: feat(research)...).#deck=statsURL hash and[F8]hotkey.#deck=statsis defended as a non-negotiable architectural invariant for unattended SOC wallboards/kiosks to boot zero-click into wall mode.[F8]keyboard hotkey is softened to an optional ergonomics enhancement.idx_counting_events_range.app/db/database.py:268.\sigma = 12\,\text{min}dwell std dev.L = \lambda W) only yields expectation; aggregate tripwires cannot compute individual\sigma_W.dwell_standard_deviationfrom schemas and wireframes. Documented queueing boundary in RFC §3.3: aggregate tripwires measure cycle-to-cycle mean dwell volatility, not individual visitor variance.18\% + 43\% + 39\% = 100\%with extra gaps/padding.16\text{px}) and padding (24\text{px}) cause overflow.minmax(0, 18fr) minmax(0, 43fr) minmax(0, 39fr)with 1px blueprint gaps andbox-sizing: border-box. Mathematically guarantees 100% viewport fill (2100\text{px}) with zero overflow.average_occupancyandriemann_average_occupancy.average_occupancy, standardizing onriemann_average_occupancy.🔍 Verification Status
100% green).ruff checkandruff formatpassed with 0 errors.feat/responsive-statistics-deck-researchsynchronized to remote.WIP: feat(research): responsive executive statistics deck (1080p to 4k zero-scroll)to WIP: feat(research): executive statistics deck (universal zero-scroll, horizon tabs, density scale)⚖️ Architecture Pivot & Stakeholder Feedback Reconciliation Receipt
Target PR: #20 (
feat/responsive-statistics-deck-research)Reconciliation Commit:
91c7505Following review and operational feedback regarding real-world presentation environments (specifically conference room displays and projectors), the architectural proposition has pivoted from a resolution-dependent scrolling engine to a Universal Zero-Scroll Architecture with Horizon Tabs and Tri-State Density Scaling.
🔄 Architectural Pivot Comparison
a87a3f0,c3c5ac7)91c7505)overflow-y: auto); stacked Day, Week, and Month in 3 tiers.height: 100dvh; overflow: hidden !important;).[ D-1: COMPLETE DAY ],[ W-1: COMPLETE WEEK ],[ M-1: COMPLETE MONTH ]).@media (min-width: 2560px)).0.85x DENSE,1.00x BALANCED,1.15x COMFORT) persisted inlocalStorage.[ ⛶ FULLSCREEN ]/[ 🗖 EXIT ]wired to HTML5 Fullscreen API directly in top bar.📐 Height Budget Proof (1080p Fullscreen Presentation)
🔍 Verification Status
100% green).ruff checkandruff format.feat/responsive-statistics-deck-researchsynchronized to origin.🔬 Design & Contract Review — Phase 1 Foundation
Reviewed at:
482f827· Base:origin/master(b07490e)Axes: domain correctness, schema hardening, display architecture, chart integrity.
An interactive prototype of all nine deck compositions was built alongside this review to pressure-test the layout math with real (synthetic but self-consistent) data — the occupancy curve is integrated from hourly counts with
kapplied, and dwell is Little's Law over that same curve, so no KPI can disagree with the chart beside it. Link shared out-of-band; it is access-gated, not public.✅ Verified in this pass
pytestorigin/mastermergeable: false— almost certainly a stale computation on a draft PR, worth forcing a re-check before anyone reads that as a blocker.ruffExecutiveSummaryResponse.model_dump()succeeds and enum coercion holds.🔴 A. Domain correctness
A1 — The Day KPI strip mislabels raw net as calibrated net
docs/architecture/rfc-executive-statistics-deck.md:359The wireframe reads
CALIBRATED NET FLUX │ +1,910 [k = 1.1162]. But+1,910isI − E— the uncalibrated difference. The calibrated figure is:These are two different quantities with two different meanings, and the near-zero one is the interesting one: it is the evidence that the business cycle actually closed. Publishing
+1,910under a "calibrated" label puts a number on a boardroom wall that says the mall gained 1,910 permanent residents yesterday.The same conflation is baked into the test fixture —
tests/test_analytics.pysetsnet_flow=1910next toasymmetry_ratio=1.1157, which are mutually consistent only ifnet_flowmeans raw.Fix: keep
net_flowas raw, label it as such in the wireframe, and add a second tile for the calibrated residual. Both are worth screen space; they answer different questions.A2 — No calibrated-net field exists in the contract at all
app/schemas/occupancy_models.pyCompletePeriodMetricsexposesnet_flow(raw), andExecutiveSummaryResponseexposesactive_exit_multiplierseparately. To show a calibrated figure the frontend must computeI − k·Eitself.That directly contradicts
docs/standards/ui-design-guidelines.md§3.4:Fix: add
calibrated_net_flow: inttoCompletePeriodMetrics. The aggregation service already haskin hand; the view should never be doing arithmetic on domain quantities.A3 — §4.2 specifies a dual-axis chart
docs/architecture/rfc-executive-statistics-deck.md:183Two y-scales sharing one frame is the single most reliable way to make a chart imply a correlation that isn't there — the relationship between the bars and the line is set by whoever picks the two axis ranges, not by the data.
Fix: stacked small multiples on a shared day axis — volume bars above, peak-headcount line below, same categories, independent scales, no implied crossing point. The prototype does this and loses nothing; the comparison is still immediate.
🟠 B. Schema hardening
All in
app/schemas/occupancy_models.py.AGENTS.md§2 mandates "Validate input strictly." — right now these models validate types and nothing else.B1 — No range constraints anywhere
Every numeric field is unbounded. A few that matter:
trusted_cycles_ratiofloat = 1.0Field(ge=0.0, le=1.0)calibration_trust_indexfloat = 100.0Field(ge=0.0, le=100.0)flow_share_pctfloat = 0.0Field(ge=0.0, le=100.0)bucket_indexintField(ge=0, le=23)day_of_weekintField(ge=0, le=6)day_numberintField(ge=1, le=31)total_in/total_out/peak_occupancyintField(ge=0)These are frozen contracts about to be consumed by two downstream phases. Constraints are cheapest to add now.
B2 — Silent-zero defaults are worse than missing
occupancy_ci_lower: int = 0andoccupancy_ci_upper: int = 0mean an un-populated bucket renders a zero-width confidence band — visually indistinguishable from "we are perfectly certain". Same problem withflow_share_pct: float = 0.0, which renders a portal as contributing nothing.Fix:
int | None = None. The adapter can then draw nothing, which is honest, instead of drawing certainty it doesn't have.B3 —
DeckDensityScaleis a dead enumIt is declared, exported, and asserted in the test — and referenced by zero fields, zero endpoints, zero services. Density is client-side presentation state persisted in
localStorageand deep-linked via the URL hash; it has no business in the backend domain contracts.Either wire it (e.g.
default_density: DeckDensityScaleon the response, so a kiosk can be provisioned server-side) or drop it from this module. Right now it's exactly the speculative generality the earlier review flagged, just relocated.B4 — Three change fields where the period type already picks one
dod_change_pct/wow_change_pct/mom_change_pctall live onCompletePeriodMetrics, butperiod_typealready determines which one is meaningful. Two of the three are permanentlyNoneon every instance.Fix: collapse to
change_pct: float | None— the period type names the comparison.B5 — The test is a smoke test, not a contract test
test_executive_summary_schemasconstructs the models and dumps once. It never round-trips and never asserts a rejection.Add:
ExecutiveSummaryResponse.model_validate(dumped)for the actual round-trip, pluspytest.raises(ValidationError)on an invalidperiod_type, a negative count, and an out-of-rangeflow_share_pctonce B1 lands. Contract tests that only test the happy path don't protect a contract.🟡 C. Display architecture
C1 — Density is specified as a token swap, but it needs to be a layout system
docs/architecture/rfc-executive-statistics-deck.md:229–256§5.2 redefines only
--stats-padand the--stats-font-*scale. The grid staysminmax(0,12fr) minmax(0,58fr) minmax(0,30fr)in all three modes.That breaks in both directions:
The density modes are different compositions, not different type scales. What the prototype implements:
10/42/30/18)12/58/30)16/52/32)Nine compositions total. This needs a new §5.x in the RFC defining the row/column map per density, otherwise Phase 3 will implement a font multiplier and we'll rediscover the problem in review.
(Open question for the room: dropping panels at Comfort means a laptop user never sees the hourly kinetics matrix without switching density. The alternative — same panels everywhere, just smaller — is what §5.2 currently implies and what breaks above. A third option is paging secondary panels. Worth a decision before Phase 3.)
C2 — Chart colour rules are missing from the design system entirely
grep -i chart docs/standards/ui-design-guidelines.mdreturns zero matches. The RFC is the first document to assign palette tokens to data series, and it's doing so without a rule to follow.Two measurable problems with the mandated palette used as a categorical scale:
--color-telemetry-ack(#4AF626) and--color-warning-amber(#FFB000) separate by ΔE 3.4 under deuteranopia (OKLab ×100). That is indistinguishable for roughly 1 in 12 men. They are safe as status chips, because a chip carries text — they must never be two series on one chart. The trio actually specified for the diurnal curve (cyan / hazard / ack) is fine at ΔE ≥ 19.6.Fix: add a charting section to
ui-design-guidelines.mdwith two rules — (a) ack and amber are never adjacent series; (b) every series carries a secondary encoding (direct label, dash pattern, or texture), never colour alone. The prototype direct-labels every series and dashes the net-flux line.C3 — The 38px bar is now the entire navigation surface
§5.4's 90px reclamation is real and worth taking. But collapsing Header + HUD + deck selector means the door-alarm state that lived in the Master HUD has nowhere to surface while someone is presenting the stats deck. An
ALARM_FORCED_OPENduring a board meeting would be invisible.Proposal: the sync dot flips to
--color-accent-hazardand the bar picks up an alarm count badge. Cheap, stays inside 38px, and keeps the deck honest about the fact that it is running on a live security system.C4 — Closed periods are immutable; §7 recomputes them on every request
GET /api/analytics/executive-summaryreturns D-1, W-1 and M-1 together, aggregated live offpeople_counting_events. The covering index makes each scan cheap, but M-1 is 31 days × 24 buckets that can never change again, recomputed on every poll of a wallboard that is by design refreshing continuously.Fix: cache or materialise closed-period aggregates keyed by
period_code(2026-08,2026-W37,2026-09-14). Only D-1 needs recomputation, and only once, after the nocturnal reset converges. This also makes the<35mstarget in §7.1 something the architecture actually guarantees rather than something the index happens to deliver today.C5 — The height budget assumes fullscreen
§5.3 sums to exactly 1080px, which is only true under the Fullscreen API. A maximised 1080p browser window has ≈960px usable. The
fr-based grid handles both correctly — the arithmetic just needs a sentence saying so, otherwise it reads as though F11 is a hard requirement for the zero-scroll invariant to hold.🟢 Endorsed as written
min-widthmedia queries genuinely does misclassify a 72″ 1080p projector as a 13″ laptop. Operator-selected density is the correct fix, and the#deck=stats&period=day&density=densekiosk hash makes unattended wallboards deterministic. This is the strongest idea in the revision.Bias(t)framing in §2.1 justifies it properly.❓ Open questions before Phase 2 starts
DeckDensityScalestay in the backend contract (§B3) — i.e. is server-provisioned kiosk density a requirement, or is this purely client state?📋 Suggested Phase 1 completion checklist
calibrated_net_flowtoCompletePeriodMetrics(A2)Field(...)range constraints across the six new models (B1)occupancy_ci_*andflow_share_pct→ nullable, not zero-defaulted (B2)DeckDensityScale: wire it or drop it (B3)dod/wow/momtochange_pct(B4)test_executive_summary_schemas(B5)ui-design-guidelines.md(C2)gabogg referenced this pull request2026-09-22 16:31:07 +00:00
gabogg referenced this pull request2026-09-23 22:49:04 +00:00
Closing — superseded by a redesign (2026-09-24)
The maintainer is redesigning the statistics deck from scratch; the layout proposed here (horizon decks, zero-scroll density scale, RFC-ARCH-2026-004) will not be used. The issues that grew out of this draft were worth solving and remain valid on their own.
The branch
feat/responsive-statistics-deck-researchis kept for reference (it was 111 commits behindmasterand no longer merged cleanly).Backend contracts now on
masterthat any future deck should consumeThese landed through the
[data-veracity]work, and several items were explicitly left to "the deck":data_trust_score,cycle_completeness_score,trust_scores_cycle_date);CalibrationAnomalyFlagseparates data-quality, low-activity and ingestion flags (#33, #34).UNCALIBRATEDstate until 14 trusted cycles (sample_maturity.is_uncalibrated), and a wide default variance, so the confidence band is widest when the sample is empty (#36, ADR 0006).GET /api/analytics/dwell/dayparts), self-labelling window metadata, and theoccupancy_definition_cutover_atmarker (#32, ADR 0006).GET /api/analytics/ingestion/anomalies). Widening the confidence band across reconstructed spans was deferred to the deck (#31).peak_timestamp_epochfrom the tie-safe peak walk (#30).🤖 Generated with Claude Code
Follow-up: the projector paradox from §1.2 of this RFC is carried forward as the basis of a new display-model RFC in #80. The rest of this RFC (metric catalogue, schemas, per-period layouts) is not carried over.
Pull request closed