feat(telemetry): Client-Side Deep Module TelemetryEngine & Frontend Test Harness (Phase 2) #16
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#16
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Parent
PR #13: docs(ui): industrial brutalist frontend redesign and tactical telemetry guidelines
What to build
Deliver the unified client-side telemetry ingestion engine (
TelemetryEngine) that absorbs server clock-skew smoothing, open-door duration timers, active alarm priority hoisting, and Little's Law dwell ratio calculations, emitting immutableTelemetrySnapshotinstances over a 1 Hz temporal loop viasubscribe(listener).Encapsulates both live network transport and fixture playback for offline testing, backed by a Node 22 native test runner integrated directly into
pytest.Acceptance criteria
TelemetryEnginemodule created inapp/static/js/src/telemetry/telemetry_engine.jswith a clean deep module consumption interface (subscribe,start,stop,getSnapshot).LiveNetworkTransport(initial HTTP bootstrap + WebSocket reconnection) andFixtureTransport(in-memory snapshot injection).\Delta t), open-door elapsed timers, active alarm priority hoisting, and dwell envelope ratio ({\max}$).requestAnimationFrametemporal loop emitting immutable snapshots to all registered subscribers.tests/frontend/test_telemetry_engine.test.jstesting clock-skew smoothing, snapshot immutability, alarm ordering, and timer cadence via Node 22 native runner (node:test,node:assert).tests/test_frontend_modules.pyexecuting the frontend test suite so that runningpytestremains the single 100% green repository gate.Blocked by
📋 Tech Debt Carried Forward from Issue #15 Code Review
The following items were identified during the #15 code review and should be addressed as prefactoring at the start of this ticket:
1. Extract
TacticalStaticFilesout ofmain.py(~15 min)app/main.pyis the app bootstrap + exception handlers + router mounts. TheTacticalStaticFilescache-policy subclass (L112-143) causes Divergent Change:main.pynow changes for two unrelated reasons. Extract toapp/middleware/static.pyor similar.2. Harden air-gap test with
xfailassertions (~5 min)test_index_html_air_gapped_vendor_referencesassertscdn.jsdelivr.net/npm/hls.jsis absent but does not assert thatcdn.tailwindcss.comandcdnjs.cloudflare.comare absent. Add assertions for both withpytest.mark.xfail(reason="CDN removal deferred to Phase 5 / Issue #19")so the known gap is visible and tracked, not silently passing.3. Scope the default cache fallback (~5 min)
TacticalStaticFiles.file_responseincludes a blanketCache-Control: public, max-age=86400catch-all for all non-CSS/JS/font/vendor files (L141). The #15 spec only asked for cache directives for fonts, scripts, and tactical styles. Either remove the catch-all or explicitly document it as intentional policy for images/HTML.4. Consider middleware over private method override (~30 min if refactored)
TacticalStaticFiles.file_responseoverrides Starlette's private/undocumentedfile_responsemethod. This works today but is fragile across Starlette upgrades. A middleware-based approach (app.middleware("http")) would be more upgrade-resilient. Can be deferred if the team pins Starlette versions.Resolution Report: Client-Side Deep Module TelemetryEngine & Frontend Test Harness (Phase 2)
Commits:
35bb192,f4203d8(PR #13)1. Acceptance Criteria Verification
TelemetryEnginemodule created: Deep module established inapp/static/js/src/telemetry/telemetry_engine.jsimplementingsubscribe(listener),start(),stop(), andgetSnapshot().LiveNetworkTransportmanages initial HTTP bootstrap (/api/auth/me,/api/doors/status,/api/occupancy/overview) and WebSocket reconnection over/ws/realtime.FixtureTransportenables offline in-memory streaming and direct snapshot injection (injectSnapshot).\Delta t), open-door duration timers derived via normalized epoch timestamps, priority hoisting ofALARM_FORCED_OPENandALARM_TIMEOUTdoors to the front ofsnapshot.doors, and Little's Law dwell envelope ratio ((t) / W_{\max}$) with operational status badges.TelemetrySnapshotinstances usingrequestAnimationFramein browser environments with gracefulsetIntervalfallback in Node.js.tests/frontend/test_telemetry_engine.test.jsvalidating snapshot immutability, alarm ordering, skew smoothing, partitioned door ingestion, midnight rollover, and timer cadence.tests/test_frontend_modules.pyexecutes the Node test suite withinpytest, keeping all 110 tests 100% green.2. Code Review (Standards & Spec)
DoorOverviewResponsepartitions (tracked_doors,untracked_doors,open_longest, etc.); extracted magic integers intoDoorStateCodeandDoorEventCodeenums; factored outnormalizeEpochMs; deferred network I/O untilengine.start().requestAnimationFramefor high-precision browser cadence; mappedOccupancyLiveResponsefields (estimated_occupancy,today_total_in,today_total_out,active_exit_multiplier); implemented business cycle rollover across midnight anchored to dynamicdaily_reset_time(04:00 AM); addedinjectSnapshottoFixtureTransport.3. Automated Verification Evidence
node --test.pytest): 110 passed in 15.69s (100% green).ruff): 0 errors, all files formatted.🔍 Code Review: Issue #16 — Client-Side Deep Module TelemetryEngine & Frontend Test Harness
Fixed point:
b872be3→ HEAD (f4203d8)Diff: 4 files changed, +1086 / −7
Commits reviewed:
35bb192feat(telemetry): implement client-side TelemetryEngine and Node 22 test harness (#16)f4203d8docs(ui): mark local ticket 02 as resolved (#16)Standards
Hard Violations
app/static/js/src/telemetry/telemetry_engine.js:326-332TelemetryEngineconstructor explicitly type-checksthis._transport instanceof FixtureTransportto eagerly attach listeners and seed messages without callingstart(). This violates the transport polymorphism contract by coupling the core engine directly to a test fixture class.Judgement Calls (Baseline Smells)
app/main.py:112-143TacticalStaticFilesremains inline inmain.py. The bootstrap module still changes for both static caching policies and API mounting.telemetry_engine.js:421-427vs553-559for (const listener of this._subscribers)loop with try/catch is duplicated verbatim between_injectSnapshotDirectand_emitSnapshot. Extract to_notifySubscribers().telemetry_engine.js:209-217, 312-320_bootstrapHttp()fetches/api/auth/meand emitsauth_bootstrap, but_handleMessage()drops it andTelemetrySnapshotexposes no auth state. 2)_businessCycleConfigholdsopenTime,closeTime,quietWindowStart, but_calculateBusinessCyclehardcodes minutes (600,1320,(3 * 60) + 30).telemetry_engine.js:491, 502, 516-521Spec
(a) Missing or Partial Requirements
TacticalStaticFilesnot extracted frommain.py; 2)test_index_html_air_gapped_vendor_referencesnot hardened withxfailCDN checks; 3) blanketmax-age=86400default cache fallback uncurated; 4) Starlettefile_responseoverride unmodified.ratiois computed strictly asliveCount / dwellMaxCapacity(default 1000 fallback) in lines 710–718. The dynamic Little's Law bound derived from ingress flux (W_{\max} \cdot \lambda) is not dynamically modeled from passage history.(b) Scope Creep (Unrequested Behaviour)
FixtureTransportin the constructor. The documented lifecycle specifies that transports activate uponengine.start().(c) Implemented-but-Wrong Requirements
_calculateBusinessCycle(lines 595–603),NOCTURNAL_QUIETis bounded bytotalMinutes < resetTotalMinutes(04:00). At04:00, it flips immediately toSETUP, truncating the standard03:30–04:30quiet window by 30 minutes._calculateBusinessCycle(lines 570–573) uses browser-localnew Date(serverTimeMs).getHours(). When client and server are in different timezones, cycle phases and reset countdowns shift relative to local client midnight rather than facility server time.Summary
instanceof FixtureTransportcoupling breaks transport polymorphism.📋 Tech Debt to Carry Forward into Issue #17
The following items should be addressed in Issue #17 or as a prefactoring pass before the operator UI deck:
TacticalStaticFilestoapp/middleware/static.py(resolving Divergent Change inapp/main.py).pytest.mark.xfailassertions intest_static_assets.pyforcdn.tailwindcss.comandcdnjs.cloudflare.com.TelemetryEnginefromFixtureTransport:instanceof FixtureTransportconstructor check intelemetry_engine.js:326. Standardize on explicitengine.start()or generic duck-typing.getHours().NOCTURNAL_QUIETphase pastdaily_reset_timeup to04:30(orquietWindowEnd)._notifySubscribers().