WIP: feat(research): executive statistics deck (universal zero-scroll, horizon tabs, density scale) #20

Closed
gabogg wants to merge 6 commits from feat/responsive-statistics-deck-research into master
Owner

📌 Problem Statement & The Physical Display Paradox

The HikCentral integration platform currently provides:

  1. The Tactical Operations Deck (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.
  2. The Admin Analytics Tab (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:

  • Executive boardrooms, auditoriums, and briefing spaces frequently utilize 70" to 120" projectors or 65" to 85" commercial flat panels.
  • However, presentation PCs, wall-plate HDMI switchers, and laptop docking stations almost universally output at standard 1080p (1920 \times 1080).
  • When an interface uses CSS media queries to enable scrolling on 1080p, a 72" projector gets misclassified as a "small laptop", pushing 50%+ of critical metrics below the fold and forcing presenters to scroll with a mouse during executive meetings.
  • Conversely, on a genuine 13" 1080p laptop screen, physical pixel density is high (\approx 166 DPI), making ultra-compact text hard to read. On a 72" projector (\approx 30 DPI), pixels are physically large; high information density is crisp, legible, and highly advantageous.

🏛️ Reconciled Architectural Approach

1. Universal Zero-Scroll Invariant (Locked Viewport)

  • Zero-Scroll Everywhere: All views are strictly locked to the viewport (height: 100dvh; overflow: hidden !important;).
  • No vertical or horizontal scrolling is permitted under any resolution. 100% of the active horizon's data is visible simultaneously at a glance.

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.
  • Persisted in localStorage (hikcentral_deck_density) and deep-linkable (#density=dense).

4. Unified Tactical Top Bar & Fullscreen Integration

  • Consolidates Header, Horizon Tabs, Density Scale Switcher, Fullscreen Toggle ([ ⛶ FULLSCREEN ]), Refresh, and Deck Switch into a single 38px tactical top bar.
  • Reclaims ~90px of vertical space previously lost to stacked ribbons (Header 44px + HUD 52px + Nav 32px = 128px).
  • In 1080p Fullscreen presentation mode, provides 1042px of net visualization canvas with exact mathematical height budgeting:
Layer / Track Fractional Share Grid Track Allocation Net Usable Space
Unified Top Control Bar — Fixed Height 38px
Grid Separators (2 gaps) — Fixed (2 × 1px) 2px
Row 1: Macro KPI Hero Strip 12% minmax(0, 12fr) 125px
Row 2: Dual Mid-Deck Charts 58% minmax(0, 58fr) 603px
Row 3: Lower Ledgers & Attribution 30% minmax(0, 30fr) 312px
Total Fullscreen Viewport 100% Rows + Gaps + Top Bar 1080px
  1080px (Total Physical Display Height)
-   38px (Unified Top Control Bar)
= 1042px (Net Distributable Viewport Canvas)
-    2px (Blueprint Grid Separator Gaps: 2 x 1px)
= 1040px (Net Distributable Panel Space)
--------------------------------------------------
  Row 1 (12%): Macro KPI Hero Strip   = 125px
+ Row 2 (58%): Dual Mid-Deck Charts   = 603px
+ Row 3 (30%): Lower Attribution      = 312px
+ Internal Gaps (2 x 1px)             =   2px
+ Unified Top Bar                     =  38px
--------------------------------------------------
= 1080px (Strict 100% Zero-Scroll Viewport Fill)
H_{\text{usable}} = 1080\,\text{px} - 38\,\text{px} - 2\,\text{px} = 1040\,\text{px}
H_{\text{total}} = 125\,\text{px} + 603\,\text{px} + 312\,\text{px} + 2\,\text{px} + 38\,\text{px} = 1080\,\text{px}

📦 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

  • Pytest Suite: 100% green across all 122 tests (pytest completed with 0 errors).
  • Linter & Formatter: Clean pass on ruff check and ruff format.
  • Contract Integrity: Verified round-trip serialization and domain enum constraints in test_executive_summary_schemas().
## 📌 Problem Statement & The Physical Display Paradox The HikCentral integration platform currently provides: 1. **The Tactical Operations Deck (`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. 2. **The Admin Analytics Tab (`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**: - Executive boardrooms, auditoriums, and briefing spaces frequently utilize **70" to 120" projectors** or **65" to 85" commercial flat panels**. - However, presentation PCs, wall-plate HDMI switchers, and laptop docking stations almost universally output at standard **1080p ($1920 \times 1080$)**. - When an interface uses CSS media queries to enable scrolling on 1080p, a 72" projector gets misclassified as a "small laptop", pushing 50%+ of critical metrics below the fold and forcing presenters to scroll with a mouse during executive meetings. - Conversely, on a genuine 13" 1080p laptop screen, physical pixel density is high ($\approx 166$ DPI), making ultra-compact text hard to read. On a 72" projector ($\approx 30$ DPI), pixels are physically large; high information density is crisp, legible, and highly advantageous. --- ## 🏛️ Reconciled Architectural Approach ### 1. Universal Zero-Scroll Invariant (Locked Viewport) - **Zero-Scroll Everywhere**: All views are strictly locked to the viewport (`height: 100dvh; overflow: hidden !important;`). - No vertical or horizontal scrolling is permitted under any resolution. 100% of the active horizon's data is visible simultaneously at a glance. ### 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. - Persisted in `localStorage` (`hikcentral_deck_density`) and deep-linkable (`#density=dense`). ### 4. Unified Tactical Top Bar & Fullscreen Integration - Consolidates Header, Horizon Tabs, Density Scale Switcher, Fullscreen Toggle (`[ ⛶ FULLSCREEN ]`), Refresh, and Deck Switch into a single **38px tactical top bar**. - **Reclaims ~90px of vertical space** previously lost to stacked ribbons (Header 44px + HUD 52px + Nav 32px = 128px). - In 1080p Fullscreen presentation mode, provides **1042px of net visualization canvas** with exact mathematical height budgeting: | Layer / Track | Fractional Share | Grid Track Allocation | Net Usable Space | | :--- | :---: | :---: | :---: | | **Unified Top Control Bar** | — | Fixed Height | 38px | | **Grid Separators (2 gaps)** | — | Fixed (2 × 1px) | 2px | | **Row 1: Macro KPI Hero Strip** | 12% | minmax(0, 12fr) | 125px | | **Row 2: Dual Mid-Deck Charts** | 58% | minmax(0, 58fr) | 603px | | **Row 3: Lower Ledgers & Attribution** | 30% | minmax(0, 30fr) | 312px | | **Total Fullscreen Viewport** | 100% | Rows + Gaps + Top Bar | **1080px** | ```text 1080px (Total Physical Display Height) - 38px (Unified Top Control Bar) = 1042px (Net Distributable Viewport Canvas) - 2px (Blueprint Grid Separator Gaps: 2 x 1px) = 1040px (Net Distributable Panel Space) -------------------------------------------------- Row 1 (12%): Macro KPI Hero Strip = 125px + Row 2 (58%): Dual Mid-Deck Charts = 603px + Row 3 (30%): Lower Attribution = 312px + Internal Gaps (2 x 1px) = 2px + Unified Top Bar = 38px -------------------------------------------------- = 1080px (Strict 100% Zero-Scroll Viewport Fill) ``` $$H_{\text{usable}} = 1080\,\text{px} - 38\,\text{px} - 2\,\text{px} = 1040\,\text{px}$$ $$H_{\text{total}} = 125\,\text{px} + 603\,\text{px} + 312\,\text{px} + 2\,\text{px} + 38\,\text{px} = 1080\,\text{px}$$ --- ## 📦 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 - **Pytest Suite**: 100% green across all 122 tests (`pytest` completed with 0 errors). - **Linter & Formatter**: Clean pass on `ruff check` and `ruff format`. - **Contract Integrity**: Verified round-trip serialization and domain enum constraints in `test_executive_summary_schemas()`.
feat(analytics): research responsive executive statistics deck (1080p to 4k)
Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
a87a3f0768
Author
Owner

📋 Automated Code Review (Standards & Spec Axes)

Target PR: #20 (feat/responsive-statistics-deck-research)
Commit: a87a3f0
Base: master (b07490e)


📐 Standards

1. Hard Documented Violations

  • docs/architecture/rfc-executive-statistics-deck.md:312, 363 — Forbidden Color Tokens

    • Standard: docs/standards/ui-design-guidelines.md § 2.1 & § 7 (Color Discipline & Palette).
    • Violation: Proposes 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 Breach

    • Standard: docs/standards/ui-design-guidelines.md § 4.2 & § 7 (Tactical Blueprint Grid).
    • Violation: Outlines CSS with gap: 8px; for .executive-stats-deck. Tactical Brutalism mandates structural 1px grid separations via display: grid; gap: 1px; over --color-border-grid.
  • tests/test_analytics.py:402 — Missing Return Type Annotation

    • Standard: docs/standards/code-standards.md § 2.2 (Type Annotations).
    • Violation: def test_executive_summary_schemas(): lacks the explicit -> None return type annotation required for all test and helper functions.

2. Baseline Smells (Judgement Calls)

  • Primitive Obsession — app/schemas/occupancy_models.py:396, 464

    • Hunk: direction_type: str in PortalAttribution and period_type: str = Field(description="'DAY', 'WEEK', or 'MONTH'") in CompletePeriodMetrics.
    • Smell: Bypasses the existing DirectionType enum (DirectionType.ENTRANCE, etc.). period_type should also be a typed StrEnum rather than an unvalidated free-form string.
  • Mysterious / Inconsistent Naming — app/schemas/occupancy_models.py

    • Hunk:
      • CompletePeriodMetrics.net_flow: int (L403)
      • DiurnalTimeseriesBucket.net_count: int (L427)
      • PortalAttribution.net_balance: int (L468)
    • Smell: The exact same domain calculation (I - E) is given three divergent field names across sibling response models. These should be standardized to net_flow.
  • Speculative Generality — app/schemas/occupancy_models.py:393-489

    • Hunk: 6 new Pydantic schema models committed directly to core domain contracts without active controller routes or service consumers.
    • Smell: Staging schemas ahead of their endpoints risks contract drift and orphaned data structures before implementation begins.

🎯 Spec

(a) Missing or Partial Requirements

Context: PR is explicitly scoped as an RFC and schema foundation (WIP: feat(research)...), not complete feature execution.

  1. Endpoint & Service Implementation: RFC L393 specifies GET /api/analytics/executive-summary?reference_epoch={optional_epoch}, but no route handler, repository queries, or background service aggregation logic exist yet in app/controllers/ or app/services/.
  2. Frontend Adapter & Grid Styles: RFC L505 targets app/static/js/src/ui/executive_stats_adapter.js and §5.3 CSS rules; neither the view adapter nor styles are included in this PR.

(b) Scope Creep (Unrequested Behaviour / Claims)

  1. Kiosk Hotkeys & Route Hashing: RFC L512–513 states "Keyboard shortcut [F8] toggles directly to STATS_DECK... URL hash #deck=stats activates the view directly on boot". The PR brief focused strictly on layout research and data modeling, not application-level keyboard intercepts or router mutations.
  2. Redundant Index Proposal: RFC L494–496 proposes creating idx_counting_events_range ON people_counting_events(timestamp_epoch, direction, count). This composite index already exists in app/db/database.py (L268).

(c) Conflicts with Current Capabilities & Existing Code

  1. Little's Law Dwell Variance Claim: RFC L358 posits MEAN DWELL: 88 min (σ = 12 min) and schema L411 defines dwell_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.
  2. Index-Only Scan Impossibility: RFC L498 asserts time-slice queries will execute as pure "Index-Only Scans, eliminating random row page reads". However, OccupancyRepository queries join counting_cameras filtering by is_excluded = 0 AND is_active = 1. Because camera_index_code is not the index prefix in idx_counting_events_range, SQLite must perform table lookups.
  3. 4K Zero-Scroll Height Deficit: RFC L181–189 budgets 2084\text{px} = 18\% + 43\% + 39\%, but CSS L248–251 introduces gap: 8px (2 row gaps = 16\text{px}) and padding: 12px (24\text{px} vertical). Allocating 100\% height to row tracks without subtracting gap/padding dimensions will cause viewport overflow on strict 4K displays.
  4. Dual Mean Occupancy Redundancy: Schema L408–409 specifies both average_occupancy and riemann_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).

## 📋 Automated Code Review (Standards & Spec Axes) **Target PR**: #20 (`feat/responsive-statistics-deck-research`) **Commit**: `a87a3f0` **Base**: `master` (`b07490e`) --- ## 📐 Standards ### 1. Hard Documented Violations - **`docs/architecture/rfc-executive-statistics-deck.md:312, 363` — Forbidden Color Tokens** - *Standard*: `docs/standards/ui-design-guidelines.md` § 2.1 & § 7 (Color Discipline & Palette). - *Violation*: Proposes `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 Breach** - *Standard*: `docs/standards/ui-design-guidelines.md` § 4.2 & § 7 (Tactical Blueprint Grid). - *Violation*: Outlines CSS with `gap: 8px;` for `.executive-stats-deck`. Tactical Brutalism mandates structural 1px grid separations via `display: grid; gap: 1px;` over `--color-border-grid`. - **`tests/test_analytics.py:402` — Missing Return Type Annotation** - *Standard*: `docs/standards/code-standards.md` § 2.2 (Type Annotations). - *Violation*: `def test_executive_summary_schemas():` lacks the explicit `-> None` return type annotation required for all test and helper functions. --- ### 2. Baseline Smells (Judgement Calls) - **Primitive Obsession — `app/schemas/occupancy_models.py:396, 464`** - *Hunk*: `direction_type: str` in `PortalAttribution` and `period_type: str = Field(description="'DAY', 'WEEK', or 'MONTH'")` in `CompletePeriodMetrics`. - *Smell*: Bypasses the existing `DirectionType` enum (`DirectionType.ENTRANCE`, etc.). `period_type` should also be a typed `StrEnum` rather than an unvalidated free-form string. - **Mysterious / Inconsistent Naming — `app/schemas/occupancy_models.py`** - *Hunk*: - `CompletePeriodMetrics.net_flow: int` (L403) - `DiurnalTimeseriesBucket.net_count: int` (L427) - `PortalAttribution.net_balance: int` (L468) - *Smell*: The exact same domain calculation $(I - E)$ is given three divergent field names across sibling response models. These should be standardized to `net_flow`. - **Speculative Generality — `app/schemas/occupancy_models.py:393-489`** - *Hunk*: 6 new Pydantic schema models committed directly to core domain contracts without active controller routes or service consumers. - *Smell*: Staging schemas ahead of their endpoints risks contract drift and orphaned data structures before implementation begins. --- ## 🎯 Spec ### (a) Missing or Partial Requirements *Context: PR is explicitly scoped as an RFC and schema foundation (`WIP: feat(research)...`), not complete feature execution.* 1. **Endpoint & Service Implementation**: RFC L393 specifies `GET /api/analytics/executive-summary?reference_epoch={optional_epoch}`, but no route handler, repository queries, or background service aggregation logic exist yet in `app/controllers/` or `app/services/`. 2. **Frontend Adapter & Grid Styles**: RFC L505 targets `app/static/js/src/ui/executive_stats_adapter.js` and §5.3 CSS rules; neither the view adapter nor styles are included in this PR. ### (b) Scope Creep (Unrequested Behaviour / Claims) 1. **Kiosk Hotkeys & Route Hashing**: RFC L512–513 states *"Keyboard shortcut `[F8]` toggles directly to `STATS_DECK`... URL hash `#deck=stats` activates the view directly on boot"*. The PR brief focused strictly on layout research and data modeling, not application-level keyboard intercepts or router mutations. 2. **Redundant Index Proposal**: RFC L494–496 proposes creating `idx_counting_events_range ON people_counting_events(timestamp_epoch, direction, count)`. This composite index already exists in `app/db/database.py` (L268). ### (c) Conflicts with Current Capabilities & Existing Code 1. **Little's Law Dwell Variance Claim**: RFC L358 posits `MEAN DWELL: 88 min (σ = 12 min)` and schema L411 defines `dwell_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. 2. **Index-Only Scan Impossibility**: RFC L498 asserts time-slice queries will execute as pure *"Index-Only Scans, eliminating random row page reads"*. However, `OccupancyRepository` queries join `counting_cameras` filtering by `is_excluded = 0 AND is_active = 1`. Because `camera_index_code` is not the index prefix in `idx_counting_events_range`, SQLite must perform table lookups. 3. **4K Zero-Scroll Height Deficit**: RFC L181–189 budgets $2084\text{px} = 18\% + 43\% + 39\%$, but CSS L248–251 introduces `gap: 8px` (2 row gaps = $16\text{px}$) and `padding: 12px` ($24\text{px}$ vertical). Allocating $100\%$ height to row tracks without subtracting gap/padding dimensions will cause viewport overflow on strict 4K displays. 4. **Dual Mean Occupancy Redundancy**: Schema L408–409 specifies both `average_occupancy` and `riemann_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).
fix(analytics): reconcile executive stats deck RFC and schemas with review findings
Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
c3c5ac72c3
- Reconcile Standards axis:
  - Migrate RFC color tokens from soft pastels to tactical telemetry palette (cyan, hazard amber, ack green)
  - Align 4K zero-scroll layout to 1px blueprint grid with minmax(0, Xfr) tracks to prevent height overflow
  - Add explicit -> None return type annotation to test_executive_summary_schemas()
  - Replace primitive strings with SummaryPeriod and DirectionType enums
  - Standardize net flow field nomenclature to net_flow across all sibling models
- Reconcile Spec axis:
  - Remove impossible individual dwell standard deviation from Little's Law formulas and schemas
  - Prune duplicate average_occupancy in favor of continuous riemann_average_occupancy
  - Reference existing idx_counting_events_range database index in database.py
  - Retain #deck=stats URL deep-link as NOC wallboard requirement while framing [F8] hotkey as optional
  - Explicitly document 3-phase tracer-bullet roadmap (RFC -> Backend Aggregation -> UI)
Author
Owner

⚖️ Draft & Review Reconciliation Receipt

Target PR: #20 (feat/responsive-statistics-deck-research)
Reconciliation Commit: c3c5ac7

Draft 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

Item Initial Draft Stance Code Review Critique Reconciled Resolution
Color Tokens Used Cyan, Rose, Emerald for high contrast across 3 curves. Soft pastels (Rose, Emerald) violate ui-design-guidelines.md. Adopted with Palette Mapping: Mapped Ingress to --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.
Blueprint Grid Specified gap: 8px; for card separation. Breaches 1px structural blueprint grid standard (gap: 1px). Adopted: Switched container and rows to display: grid; gap: 1px; background-color: var(--color-border-grid); with cards having background-color: var(--color-bg-surface); padding: 12px; box-sizing: border-box;.
Type Annotations Omitted -> None in test. Violates code-standards.md §2.2. Adopted: Added -> None to test_executive_summary_schemas().
Primitive Obsession Raw str for periods and door directions. Bypassed DirectionType; lacked enum for period type. Adopted: Created SummaryPeriod(str, Enum) (DAY, WEEK, MONTH) and bound PortalAttribution.direction_type to DirectionType.
Field Inconsistency net_flow, net_count, net_balance used across models. 3 divergent field names for identical calculation (I - E). Adopted: Standardized all sibling models to net_flow.
Speculative Generality Included schemas before endpoints/services exist. Staged schemas risk contract drift. Reconciled & Defended: Staging frozen Pydantic contracts in an RFC PR is core contract-first practice. However, speculative/duplicate fields (dwell_standard_deviation, redundant average_occupancy) were pruned to ensure schemas stay lean.

🎯 Spec Axis Reconciliation

Item Initial Draft Stance Code Review Critique Reconciled Resolution
Implementation Scope Scoped as Research/RFC (WIP: feat(research)...). Flagged missing endpoints, services, and frontend adapter. Reconciled & Phased: Clarified in RFC §8 that PR #20 is explicitly Phase 1 (RFC + Schemas + Math). Implementation is formally decomposed into downstream tracer bullets: Phase 2 (Backend Service & API) and Phase 3 (Frontend View Adapter & Chart.js).
Kiosk Boot & Hotkeys Proposed #deck=stats URL hash and [F8] hotkey. Flagged as unrequested scope creep. Reconciled: #deck=stats is 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.
Composite Index Proposed creating idx_counting_events_range. Index already exists in app/db/database.py:268. Adopted: Updated RFC §7.3 to cite and leverage the existing production index rather than proposing a redundant migration.
Little's Law Dwell Variance Claimed \sigma = 12\,\text{min} dwell std dev. Little's Law (L = \lambda W) only yields expectation; aggregate tripwires cannot compute individual \sigma_W. Adopted (Mathematical Invariant): Removed dwell_standard_deviation from schemas and wireframes. Documented queueing boundary in RFC §3.3: aggregate tripwires measure cycle-to-cycle mean dwell volatility, not individual visitor variance.
4K Zero-Scroll Height Deficit Summed 18\% + 43\% + 39\% = 100\% with extra gaps/padding. Extra gaps (16\text{px}) and padding (24\text{px}) cause overflow. Adopted & Fixed: Refactored grid tracks to minmax(0, 18fr) minmax(0, 43fr) minmax(0, 39fr) with 1px blueprint gaps and box-sizing: border-box. Mathematically guarantees 100% viewport fill (2100\text{px}) with zero overflow.
Dual Mean Occupancy Included both average_occupancy and riemann_average_occupancy. Redundant; service computes Riemann continuous integration. Adopted: Pruned average_occupancy, standardizing on riemann_average_occupancy.

🔍 Verification Status

  • Pytest Suite: 122/122 passed (100% green).
  • Linter & Formatter: ruff check and ruff format passed with 0 errors.
  • Commits Pushed: Branch feat/responsive-statistics-deck-research synchronized to remote.
## ⚖️ Draft & Review Reconciliation Receipt **Target PR**: #20 (`feat/responsive-statistics-deck-research`) **Reconciliation Commit**: `c3c5ac7` Draft 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 | Item | Initial Draft Stance | Code Review Critique | Reconciled Resolution | | :--- | :--- | :--- | :--- | | **Color Tokens** | Used Cyan, Rose, Emerald for high contrast across 3 curves. | Soft pastels (`Rose`, `Emerald`) violate `ui-design-guidelines.md`. | **Adopted with Palette Mapping**: Mapped Ingress to `--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. | | **Blueprint Grid** | Specified `gap: 8px;` for card separation. | Breaches 1px structural blueprint grid standard (`gap: 1px`). | **Adopted**: Switched container and rows to `display: grid; gap: 1px; background-color: var(--color-border-grid);` with cards having `background-color: var(--color-bg-surface); padding: 12px; box-sizing: border-box;`. | | **Type Annotations** | Omitted `-> None` in test. | Violates `code-standards.md` §2.2. | **Adopted**: Added `-> None` to `test_executive_summary_schemas()`. | | **Primitive Obsession** | Raw `str` for periods and door directions. | Bypassed `DirectionType`; lacked enum for period type. | **Adopted**: Created `SummaryPeriod(str, Enum)` (`DAY`, `WEEK`, `MONTH`) and bound `PortalAttribution.direction_type` to `DirectionType`. | | **Field Inconsistency** | `net_flow`, `net_count`, `net_balance` used across models. | 3 divergent field names for identical calculation ($I - E$). | **Adopted**: Standardized all sibling models to `net_flow`. | | **Speculative Generality** | Included schemas before endpoints/services exist. | Staged schemas risk contract drift. | **Reconciled & Defended**: Staging frozen Pydantic contracts in an RFC PR is core contract-first practice. However, speculative/duplicate fields (`dwell_standard_deviation`, redundant `average_occupancy`) were pruned to ensure schemas stay lean. | --- ### 🎯 Spec Axis Reconciliation | Item | Initial Draft Stance | Code Review Critique | Reconciled Resolution | | :--- | :--- | :--- | :--- | | **Implementation Scope** | Scoped as Research/RFC (`WIP: feat(research)...`). | Flagged missing endpoints, services, and frontend adapter. | **Reconciled & Phased**: Clarified in RFC §8 that PR #20 is explicitly Phase 1 (RFC + Schemas + Math). Implementation is formally decomposed into downstream tracer bullets: Phase 2 (Backend Service & API) and Phase 3 (Frontend View Adapter & Chart.js). | | **Kiosk Boot & Hotkeys** | Proposed `#deck=stats` URL hash and `[F8]` hotkey. | Flagged as unrequested scope creep. | **Reconciled**: `#deck=stats` is 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. | | **Composite Index** | Proposed creating `idx_counting_events_range`. | Index already exists in `app/db/database.py:268`. | **Adopted**: Updated RFC §7.3 to cite and leverage the existing production index rather than proposing a redundant migration. | | **Little's Law Dwell Variance** | Claimed $\sigma = 12\,\text{min}$ dwell std dev. | Little's Law ($L = \lambda W$) only yields expectation; aggregate tripwires cannot compute individual $\sigma_W$. | **Adopted (Mathematical Invariant)**: Removed `dwell_standard_deviation` from schemas and wireframes. Documented queueing boundary in RFC §3.3: aggregate tripwires measure cycle-to-cycle mean dwell volatility, not individual visitor variance. | | **4K Zero-Scroll Height Deficit** | Summed $18\% + 43\% + 39\% = 100\%$ with extra gaps/padding. | Extra gaps ($16\text{px}$) and padding ($24\text{px}$) cause overflow. | **Adopted & Fixed**: Refactored grid tracks to `minmax(0, 18fr) minmax(0, 43fr) minmax(0, 39fr)` with 1px blueprint gaps and `box-sizing: border-box`. Mathematically guarantees 100% viewport fill ($2100\text{px}$) with zero overflow. | | **Dual Mean Occupancy** | Included both `average_occupancy` and `riemann_average_occupancy`. | Redundant; service computes Riemann continuous integration. | **Adopted**: Pruned `average_occupancy`, standardizing on `riemann_average_occupancy`. | --- ### 🔍 Verification Status - **Pytest Suite**: 122/122 passed (`100% green`). - **Linter & Formatter**: `ruff check` and `ruff format` passed with 0 errors. - **Commits Pushed**: Branch `feat/responsive-statistics-deck-research` synchronized to remote.
- Address conference room projector paradox: 70"-120" displays run at 1080p, making resolution-based scrolling fail in presentation environments
- Establish Universal Zero-Scroll Invariant: 100dvh locked viewport with 0px scrolling across all resolutions
- Replace 3-tier vertical scroll with dedicated Horizon Tabs (Day D-1, Week W-1, Month M-1)
- Introduce Tri-State Density Scale System (0.85x Dense for Projectors/Wallboards, 1.00x Balanced for Desktop, 1.15x Comfort for High-DPI Laptops)
- Consolidate navigation, horizon tabs, scale switch, and fullscreen toggle into 38px unified top bar, saving ~90px of vertical space
- Add DeckDensityScale enum to occupancy models and update test suite
gabogg changed title from 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) 2026-09-21 13:04:41 +00:00
Author
Owner

⚖️ Architecture Pivot & Stakeholder Feedback Reconciliation Receipt

Target PR: #20 (feat/responsive-statistics-deck-research)
Reconciliation Commit: 91c7505

Following 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

Dimension Initial Draft Proposition (Commit a87a3f0, c3c5ac7) Operational Reality & Feedback Reconciled Architecture (Commit 91c7505)
1080p Behavior Enabled vertical scrolling (overflow-y: auto); stacked Day, Week, and Month in 3 tiers. Conference rooms use 70"–120" projectors that output 1080p. Scrolling during executive meetings is clunky and hides data. Universal Zero-Scroll Invariant: Strict 0px scrolling on all resolutions (height: 100dvh; overflow: hidden !important;).
Multi-Horizon Layout Day, Week, and Month were crammed vertically onto a single scrollable page. Information overload; vertical overflow on 1080p; metrics competed for pixels. Horizon Tab Separation: 3 dedicated, self-contained horizon decks ([ D-1: COMPLETE DAY ], [ W-1: COMPLETE WEEK ], [ M-1: COMPLETE MONTH ]).
Density Determination Determined purely by CSS media queries based on pixel resolution (@media (min-width: 2560px)). Decoupled physical screen size from resolution (e.g. 72" projector = 1080p @ 30 DPI vs 13" laptop = 1080p @ 166 DPI). Tri-State Density Scale System: User-selectable switch (0.85x DENSE, 1.00x BALANCED, 1.15x COMFORT) persisted in localStorage.
Chrome & Vertical Space Relied on existing stacked chrome: Main Header (44px) + HUD ribbon (52px) + Nav selector (32px) = 128px. Consumed 13%+ of total vertical budget, exacerbating container height squeeze. Unified 38px Top Control Bar: Integrates horizon tabs, density selector, fullscreen toggle, and deck switch. Reclaims ~90px of vertical canvas.
Presentation Mode Proposed browser window fullscreen without dedicated in-app control. Conference room presentations require a quick, one-click transition to kiosk/presentation mode. Integrated Fullscreen Button: [ ⛶ FULLSCREEN ] / [ 🗖 EXIT ] wired to HTML5 Fullscreen API directly in top bar.

📐 Height Budget Proof (1080p Fullscreen Presentation)

Layer / Track Fractional Share Grid Track Allocation Net Usable Space
Unified Top Control Bar — Fixed Height 38px
Grid Separators (2 gaps) — Fixed (2 × 1px) 2px
Row 1: Macro KPI Hero Strip 12% minmax(0, 12fr) 125px
Row 2: Dual Mid-Deck Charts 58% minmax(0, 58fr) 603px
Row 3: Lower Ledgers & Attribution 30% minmax(0, 30fr) 312px
Total Fullscreen Viewport 100% Rows + Gaps + Top Bar 1080px
  1080px (Total Physical Display Height)
-   38px (Unified Top Control Bar)
= 1042px (Net Distributable Viewport Canvas)
-    2px (Blueprint Grid Separator Gaps: 2 x 1px)
= 1040px (Net Distributable Panel Space)
--------------------------------------------------
  Row 1 (12%): Macro KPI Hero Strip   = 125px
+ Row 2 (58%): Dual Mid-Deck Charts   = 603px
+ Row 3 (30%): Lower Attribution      = 312px
+ Internal Gaps (2 x 1px)             =   2px
+ Unified Top Bar                     =  38px
--------------------------------------------------
= 1080px (Strict 100% Zero-Scroll Viewport Fill)
H_{\text{usable}} = 1080\,\text{px} - 38\,\text{px} - 2\,\text{px} = 1040\,\text{px}
H_{\text{total}} = 125\,\text{px} + 603\,\text{px} + 312\,\text{px} + 2\,\text{px} + 38\,\text{px} = 1080\,\text{px}

🔍 Verification Status

  • Pytest Suite: 122/122 passed (100% green).
  • Linter & Formatter: Clean pass on ruff check and ruff format.
  • Remote Synchronization: Branch feat/responsive-statistics-deck-research synchronized to origin.
## ⚖️ Architecture Pivot & Stakeholder Feedback Reconciliation Receipt **Target PR**: #20 (`feat/responsive-statistics-deck-research`) **Reconciliation Commit**: `91c7505` Following 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 | Dimension | Initial Draft Proposition (Commit `a87a3f0`, `c3c5ac7`) | Operational Reality & Feedback | Reconciled Architecture (Commit `91c7505`) | | :--- | :--- | :--- | :--- | | **1080p Behavior** | Enabled vertical scrolling (`overflow-y: auto`); stacked Day, Week, and Month in 3 tiers. | Conference rooms use 70"–120" projectors that output 1080p. Scrolling during executive meetings is clunky and hides data. | **Universal Zero-Scroll Invariant**: Strict 0px scrolling on all resolutions (`height: 100dvh; overflow: hidden !important;`). | | **Multi-Horizon Layout** | Day, Week, and Month were crammed vertically onto a single scrollable page. | Information overload; vertical overflow on 1080p; metrics competed for pixels. | **Horizon Tab Separation**: 3 dedicated, self-contained horizon decks (`[ D-1: COMPLETE DAY ]`, `[ W-1: COMPLETE WEEK ]`, `[ M-1: COMPLETE MONTH ]`). | | **Density Determination** | Determined purely by CSS media queries based on pixel resolution (`@media (min-width: 2560px)`). | Decoupled physical screen size from resolution (e.g. 72" projector = 1080p @ 30 DPI vs 13" laptop = 1080p @ 166 DPI). | **Tri-State Density Scale System**: User-selectable switch (`0.85x DENSE`, `1.00x BALANCED`, `1.15x COMFORT`) persisted in `localStorage`. | | **Chrome & Vertical Space** | Relied on existing stacked chrome: Main Header (44px) + HUD ribbon (52px) + Nav selector (32px) = 128px. | Consumed 13%+ of total vertical budget, exacerbating container height squeeze. | **Unified 38px Top Control Bar**: Integrates horizon tabs, density selector, fullscreen toggle, and deck switch. Reclaims ~90px of vertical canvas. | | **Presentation Mode** | Proposed browser window fullscreen without dedicated in-app control. | Conference room presentations require a quick, one-click transition to kiosk/presentation mode. | **Integrated Fullscreen Button**: `[ ⛶ FULLSCREEN ]` / `[ 🗖 EXIT ]` wired to HTML5 Fullscreen API directly in top bar. | --- ### 📐 Height Budget Proof (1080p Fullscreen Presentation) | Layer / Track | Fractional Share | Grid Track Allocation | Net Usable Space | | :--- | :---: | :---: | :---: | | **Unified Top Control Bar** | — | Fixed Height | 38px | | **Grid Separators (2 gaps)** | — | Fixed (2 × 1px) | 2px | | **Row 1: Macro KPI Hero Strip** | 12% | minmax(0, 12fr) | 125px | | **Row 2: Dual Mid-Deck Charts** | 58% | minmax(0, 58fr) | 603px | | **Row 3: Lower Ledgers & Attribution** | 30% | minmax(0, 30fr) | 312px | | **Total Fullscreen Viewport** | 100% | Rows + Gaps + Top Bar | **1080px** | ```text 1080px (Total Physical Display Height) - 38px (Unified Top Control Bar) = 1042px (Net Distributable Viewport Canvas) - 2px (Blueprint Grid Separator Gaps: 2 x 1px) = 1040px (Net Distributable Panel Space) -------------------------------------------------- Row 1 (12%): Macro KPI Hero Strip = 125px + Row 2 (58%): Dual Mid-Deck Charts = 603px + Row 3 (30%): Lower Attribution = 312px + Internal Gaps (2 x 1px) = 2px + Unified Top Bar = 38px -------------------------------------------------- = 1080px (Strict 100% Zero-Scroll Viewport Fill) ``` $$H_{\text{usable}} = 1080\,\text{px} - 38\,\text{px} - 2\,\text{px} = 1040\,\text{px}$$ $$H_{\text{total}} = 125\,\text{px} + 603\,\text{px} + 312\,\text{px} + 2\,\text{px} + 38\,\text{px} = 1080\,\text{px}$$ --- ### 🔍 Verification Status - **Pytest Suite**: 122/122 passed (`100% green`). - **Linter & Formatter**: Clean pass on `ruff check` and `ruff format`. - **Remote Synchronization**: Branch `feat/responsive-statistics-deck-research` synchronized to origin.
Author
Owner

🔬 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 k applied, 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

Check Result
pytest 185 passed, 1 warning, 65s — green. (PR body claims 122; the count is stale, not wrong.)
Merge state vs origin/master 0 conflicts, 6 ahead, 0 behind. Forgejo's API reports mergeable: false — almost certainly a stale computation on a draft PR, worth forcing a re-check before anyone reads that as a blocker.
ruff Not installed in the review environment; the lint claim in the PR body is unverified here, not disputed.
Schema round-trip ExecutiveSummaryResponse.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:359

The wireframe reads CALIBRATED NET FLUX │ +1,910 [k = 1.1162]. But +1,910 is I − E — the uncalibrated difference. The calibrated figure is:

I − k·E  =  18,420 − 1.1162 × 16,510  ≈  −8

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,910 under 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.py sets net_flow=1910 next to asymmetry_ratio=1.1157, which are mutually consistent only if net_flow means raw.

Fix: keep net_flow as 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.py

CompletePeriodMetrics exposes net_flow (raw), and ExecutiveSummaryResponse exposes active_exit_multiplier separately. To show a calibrated figure the frontend must compute I − k·E itself.

That directly contradicts docs/standards/ui-design-guidelines.md §3.4:

Declarative View Adapters: Visual decks […] must never parse raw WebSocket frames or compute derived state internally.

Fix: add calibrated_net_flow: int to CompletePeriodMetrics. The aggregation service already has k in 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:183

Dual-axis bar/line chart contrasting daily footfall against peak occupancy per day

Two 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:

Field Current Should be
trusted_cycles_ratio float = 1.0 Field(ge=0.0, le=1.0)
calibration_trust_index float = 100.0 Field(ge=0.0, le=100.0)
flow_share_pct float = 0.0 Field(ge=0.0, le=100.0)
bucket_index int Field(ge=0, le=23)
day_of_week int Field(ge=0, le=6)
day_number int Field(ge=1, le=31)
total_in / total_out / peak_occupancy int Field(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 = 0 and occupancy_ci_upper: int = 0 mean an un-populated bucket renders a zero-width confidence band — visually indistinguishable from "we are perfectly certain". Same problem with flow_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 — DeckDensityScale is a dead enum

It 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 localStorage and deep-linked via the URL hash; it has no business in the backend domain contracts.

Either wire it (e.g. default_density: DeckDensityScale on 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_pct all live on CompletePeriodMetrics, but period_type already determines which one is meaningful. Two of the three are permanently None on 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_schemas constructs the models and dumps once. It never round-trips and never asserts a rejection.

Add: ExecutiveSummaryResponse.model_validate(dumped) for the actual round-trip, plus pytest.raises(ValidationError) on an invalid period_type, a negative count, and an out-of-range flow_share_pct once 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-pad and the --stats-font-* scale. The grid stays minmax(0,12fr) minmax(0,58fr) minmax(0,30fr) in all three modes.

That breaks in both directions:

  • Comfort (1.15×) inflates type inside unchanged row heights, so the lower ledgers clip or scroll internally — which is a zero-scroll violation wearing a disguise, since the data is still hidden, just hidden inside a panel instead of below the fold.
  • Dense (0.85×) shrinks type inside unchanged row heights, so the reclaimed space becomes padding rather than data. The whole argument for Dense is more simultaneous readout on a 72″ projector — shrinking the font without adding panels delivers the opposite.

The density modes are different compositions, not different type scales. What the prototype implements:

Rows KPI tiles Mid row Low row Extra
Dense 4 (10/42/30/18) 8 3-up charts 2-up ledgers full-width trailing-context strip
Balanced 3 (12/58/30) 6 2-up charts 2-up ledgers —
Comfort 3 (16/52/32) 4 full-width hero chart 2-up (or 1-up) secondary panels dropped, not shrunk

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.md returns 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:

  1. --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.
  2. All four accents sit at lightness 0.81–0.85. They separate by hue alone, so they collapse into one grey in projector washout, in print, and in any forced-colors mode. On a 120″ projector with ambient light, that is a realistic failure mode, not a theoretical one.

Fix: add a charting section to ui-design-guidelines.md with 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_OPEN during a board meeting would be invisible.

Proposal: the sync dot flips to --color-accent-hazard and 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-summary returns D-1, W-1 and M-1 together, aggregated live off people_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 <35ms target 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

  • The projector/DPI paradox. Driving density off min-width media 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=dense kiosk hash makes unattended wallboards deterministic. This is the strongest idea in the revision.
  • Complete closed periods only. Excluding the in-flight day is the most defensible decision in the document — an uncalibrated partial day would put a wrong peak on a boardroom wall, and the Bias(t) framing in §2.1 justifies it properly.
  • Horizon tabs over a vertical cascade. Three self-contained cockpits beats one scrolling page, and it is what makes the zero-scroll invariant achievable rather than aspirational.
  • Contract-first staging. Freezing Pydantic models in a research PR is correct practice, and the earlier "speculative generality" objection was rightly pushed back on. The models just need constraints (§B) before they're truly frozen.

❓ Open questions before Phase 2 starts

  1. Comfort behaviour — drop secondary panels, shrink them, or page through them? (§C1)
  2. Does the Dense fourth row earn 18% of the canvas for trailing context (14-day / per-day-profile / 31-day trust strips)?
  3. Default density — ship Balanced and let the kiosk hash pin Dense, or make Dense the product default and let laptops opt up?
  4. Nine layouts is nine things to maintain. Is the right number nine, or two (Presentation / Desk)?
  5. Does DeckDensityScale stay 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

  • §6.2 — relabel raw vs calibrated net; add the residual tile (A1)
  • Add calibrated_net_flow to CompletePeriodMetrics (A2)
  • §4.2 — replace the dual-axis chart with small multiples (A3)
  • Add Field(...) range constraints across the six new models (B1)
  • occupancy_ci_* and flow_share_pct → nullable, not zero-defaulted (B2)
  • Decide DeckDensityScale: wire it or drop it (B3)
  • Collapse dod/wow/mom to change_pct (B4)
  • Add round-trip + rejection cases to test_executive_summary_schemas (B5)
  • New §5.x — the per-density row/column layout map (C1)
  • New charting section in ui-design-guidelines.md (C2)
  • §5.4 — alarm surfacing in the 38px bar (C3)
  • §7 — closed-period aggregate caching (C4)
  • §5.3 — note the windowed-vs-fullscreen height difference (C5)
## 🔬 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 `k` applied, 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 | Check | Result | | :--- | :--- | | `pytest` | **185 passed**, 1 warning, 65s — green. *(PR body claims 122; the count is stale, not wrong.)* | | Merge state vs `origin/master` | **0 conflicts**, 6 ahead, 0 behind. Forgejo's API reports `mergeable: false` — almost certainly a stale computation on a draft PR, worth forcing a re-check before anyone reads that as a blocker. | | `ruff` | Not installed in the review environment; the lint claim in the PR body is **unverified here**, not disputed. | | Schema round-trip | `ExecutiveSummaryResponse.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:359` The wireframe reads `CALIBRATED NET FLUX │ +1,910 [k = 1.1162]`. But `+1,910` is `I − E` — the *uncalibrated* difference. The calibrated figure is: ``` I − k·E = 18,420 − 1.1162 × 16,510 ≈ −8 ``` 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,910` under 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.py` sets `net_flow=1910` next to `asymmetry_ratio=1.1157`, which are mutually consistent only if `net_flow` means *raw*. **Fix**: keep `net_flow` as 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.py` `CompletePeriodMetrics` exposes `net_flow` (raw), and `ExecutiveSummaryResponse` exposes `active_exit_multiplier` separately. To show a calibrated figure the frontend must compute `I − k·E` itself. That directly contradicts `docs/standards/ui-design-guidelines.md` §3.4: > **Declarative View Adapters**: Visual decks […] must never parse raw WebSocket frames or compute derived state internally. **Fix**: add `calibrated_net_flow: int` to `CompletePeriodMetrics`. The aggregation service already has `k` in 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:183` > Dual-axis bar/line chart contrasting daily footfall against peak occupancy per day Two 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: | Field | Current | Should be | | :--- | :--- | :--- | | `trusted_cycles_ratio` | `float = 1.0` | `Field(ge=0.0, le=1.0)` | | `calibration_trust_index` | `float = 100.0` | `Field(ge=0.0, le=100.0)` | | `flow_share_pct` | `float = 0.0` | `Field(ge=0.0, le=100.0)` | | `bucket_index` | `int` | `Field(ge=0, le=23)` | | `day_of_week` | `int` | `Field(ge=0, le=6)` | | `day_number` | `int` | `Field(ge=1, le=31)` | | `total_in` / `total_out` / `peak_occupancy` | `int` | `Field(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 = 0` and `occupancy_ci_upper: int = 0` mean an un-populated bucket renders a **zero-width confidence band** — visually indistinguishable from "we are perfectly certain". Same problem with `flow_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 — `DeckDensityScale` is a dead enum It 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 `localStorage` and deep-linked via the URL hash; it has no business in the backend domain contracts. Either wire it (e.g. `default_density: DeckDensityScale` on 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_pct` all live on `CompletePeriodMetrics`, but `period_type` already determines which one is meaningful. Two of the three are permanently `None` on 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_schemas` constructs the models and dumps once. It never round-trips and never asserts a rejection. **Add**: `ExecutiveSummaryResponse.model_validate(dumped)` for the actual round-trip, plus `pytest.raises(ValidationError)` on an invalid `period_type`, a negative count, and an out-of-range `flow_share_pct` once 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-pad` and the `--stats-font-*` scale. The grid stays `minmax(0,12fr) minmax(0,58fr) minmax(0,30fr)` in all three modes. That breaks in both directions: - **Comfort (1.15×)** inflates type inside unchanged row heights, so the lower ledgers clip or scroll internally — which is a zero-scroll violation wearing a disguise, since the data is still hidden, just hidden inside a panel instead of below the fold. - **Dense (0.85×)** shrinks type inside unchanged row heights, so the reclaimed space becomes padding rather than data. The whole argument for Dense is *more simultaneous readout on a 72″ projector* — shrinking the font without adding panels delivers the opposite. **The density modes are different compositions, not different type scales.** What the prototype implements: | | Rows | KPI tiles | Mid row | Low row | Extra | | :--- | :--- | :--- | :--- | :--- | :--- | | **Dense** | 4 (`10/42/30/18`) | 8 | 3-up charts | 2-up ledgers | full-width trailing-context strip | | **Balanced** | 3 (`12/58/30`) | 6 | 2-up charts | 2-up ledgers | — | | **Comfort** | 3 (`16/52/32`) | 4 | full-width hero chart | 2-up (or 1-up) | secondary panels *dropped*, not shrunk | 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.md` returns **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: 1. **`--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. 2. **All four accents sit at lightness 0.81–0.85.** They separate by *hue alone*, so they collapse into one grey in projector washout, in print, and in any forced-colors mode. On a 120″ projector with ambient light, that is a realistic failure mode, not a theoretical one. **Fix**: add a charting section to `ui-design-guidelines.md` with 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_OPEN` during a board meeting would be invisible. **Proposal**: the sync dot flips to `--color-accent-hazard` and 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-summary` returns D-1, W-1 and M-1 together, aggregated live off `people_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 `<35ms` target 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 - **The projector/DPI paradox.** Driving density off `min-width` media 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=dense` kiosk hash makes unattended wallboards deterministic. This is the strongest idea in the revision. - **Complete closed periods only.** Excluding the in-flight day is the most defensible decision in the document — an uncalibrated partial day would put a wrong peak on a boardroom wall, and the `Bias(t)` framing in §2.1 justifies it properly. - **Horizon tabs over a vertical cascade.** Three self-contained cockpits beats one scrolling page, and it is what makes the zero-scroll invariant achievable rather than aspirational. - **Contract-first staging.** Freezing Pydantic models in a research PR is correct practice, and the earlier "speculative generality" objection was rightly pushed back on. The models just need constraints (§B) before they're truly frozen. --- ## ❓ Open questions before Phase 2 starts 1. **Comfort behaviour** — drop secondary panels, shrink them, or page through them? (§C1) 2. **Does the Dense fourth row earn 18% of the canvas** for trailing context (14-day / per-day-profile / 31-day trust strips)? 3. **Default density** — ship Balanced and let the kiosk hash pin Dense, or make Dense the product default and let laptops opt up? 4. **Nine layouts is nine things to maintain.** Is the right number nine, or two (*Presentation* / *Desk*)? 5. **Does `DeckDensityScale` stay 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 - [ ] §6.2 — relabel raw vs calibrated net; add the residual tile (A1) - [ ] Add `calibrated_net_flow` to `CompletePeriodMetrics` (A2) - [ ] §4.2 — replace the dual-axis chart with small multiples (A3) - [ ] Add `Field(...)` range constraints across the six new models (B1) - [ ] `occupancy_ci_*` and `flow_share_pct` → nullable, not zero-defaulted (B2) - [ ] Decide `DeckDensityScale`: wire it or drop it (B3) - [ ] Collapse `dod`/`wow`/`mom` to `change_pct` (B4) - [ ] Add round-trip + rejection cases to `test_executive_summary_schemas` (B5) - [ ] New §5.x — the per-density row/column layout map (C1) - [ ] New charting section in `ui-design-guidelines.md` (C2) - [ ] §5.4 — alarm surfacing in the 38px bar (C3) - [ ] §7 — closed-period aggregate caching (C4) - [ ] §5.3 — note the windowed-vs-fullscreen height difference (C5)
Author
Owner

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-research is kept for reference (it was 111 commits behind master and no longer merged cleanly).

Backend contracts now on master that any future deck should consume

These landed through the [data-veracity] work, and several items were explicitly left to "the deck":

  • Trust: Data Trust and Cycle Completeness are separate figures, scored on the last completed cycle (data_trust_score, cycle_completeness_score, trust_scores_cycle_date); CalibrationAnomalyFlag separates data-quality, low-activity and ingestion flags (#33, #34).
  • Calibration honesty: the UNCALIBRATED state 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).
  • Dwell: Mean Dwell over the retail open window with dayparts (GET /api/analytics/dwell/dayparts), self-labelling window metadata, and the occupancy_definition_cutover_at marker (#32, ADR 0006).
  • Ingestion gaps: the gap/reset/stall ledger (GET /api/analytics/ingestion/anomalies). Widening the confidence band across reconstructed spans was deferred to the deck (#31).
  • Peaks: peak_timestamp_epoch from the tie-safe peak walk (#30).
  • Attribution: passenger flow is counted per camera group with one camera per group (ADR 0005); any per-camera panel relies on that rule (#62 if it ever breaks).
  • Open decisions that affect what a deck shows: #77 (the live staff-baseline floor, operator communication, dwell edge cases) and #76 (quiet-window placement).

🤖 Generated with Claude Code

## 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-research` is kept for reference (it was 111 commits behind `master` and no longer merged cleanly). ### Backend contracts now on `master` that any future deck should consume These landed through the `[data-veracity]` work, and several items were explicitly left to "the deck": - **Trust:** *Data Trust* and *Cycle Completeness* are separate figures, scored on the **last completed cycle** (`data_trust_score`, `cycle_completeness_score`, `trust_scores_cycle_date`); `CalibrationAnomalyFlag` separates data-quality, low-activity and ingestion flags (#33, #34). - **Calibration honesty:** the `UNCALIBRATED` state 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). - **Dwell:** Mean Dwell over the retail open window with dayparts (`GET /api/analytics/dwell/dayparts`), self-labelling window metadata, and the `occupancy_definition_cutover_at` marker (#32, ADR 0006). - **Ingestion gaps:** the gap/reset/stall ledger (`GET /api/analytics/ingestion/anomalies`). **Widening the confidence band across reconstructed spans was deferred to the deck** (#31). - **Peaks:** `peak_timestamp_epoch` from the tie-safe peak walk (#30). - **Attribution:** passenger flow is counted per camera group with one camera per group (ADR 0005); any per-camera panel relies on that rule (#62 if it ever breaks). - **Open decisions that affect what a deck shows:** #77 (the live staff-baseline floor, operator communication, dwell edge cases) and #76 (quiet-window placement). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gabogg closed this pull request 2026-09-24 22:50:35 +00:00
Author
Owner

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.

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.
Some checks failed
CI / lint-and-test (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No description provided.