fix(calibration): background daemon scheduling, historical reconciliation, and business-cycle charts #8

Merged
gabogg merged 3 commits from fix/calibration-daemon-and-business-cycle-charts into master 2026-09-07 17:20:16 +00:00
Owner

Root Cause

check_and_run_auto_calibration_async was only invoked inside get_live_occupancy_async() — an on-demand API handler triggered when a browser client hits /api/occupancy/live. If no user or kiosk was active between 03:30 and 04:30 AM, the calibration never executed. This is why Friday, Saturday, and Sunday had zero calibration logs.

Fixes

1. Background Daemon Scheduling

Wired check_and_run_auto_calibration_async() into background_monitor() loop (every 30 seconds), completely independent of incoming HTTP requests.

2. Historical Calibration Reconciliation

New reconcile_historical_calibrations_async() walks all past business cycles, detects missing calibration logs, and records audit entries. Runs on boot and exposed as POST /api/analytics/calibration/reconcile.

3. Business-Cycle Chart Windowing

Timeseries endpoints now use daily_reset_time (e.g. 04:00 to 04:00) instead of midnight-to-midnight.

4. New Repository Method

get_earliest_event_epoch_async() supports the reconciliation scan.

Tests

85/85 pass including 3 new tests for reconciliation, quiet-window trigger, and API endpoint.

## Root Cause `check_and_run_auto_calibration_async` was **only invoked inside `get_live_occupancy_async()`** — an on-demand API handler triggered when a browser client hits `/api/occupancy/live`. If no user or kiosk was active between 03:30 and 04:30 AM, the calibration never executed. This is why Friday, Saturday, and Sunday had **zero calibration logs**. ## Fixes ### 1. Background Daemon Scheduling Wired `check_and_run_auto_calibration_async()` into `background_monitor()` loop (every 30 seconds), completely independent of incoming HTTP requests. ### 2. Historical Calibration Reconciliation New `reconcile_historical_calibrations_async()` walks all past business cycles, detects missing calibration logs, and records audit entries. Runs on boot and exposed as `POST /api/analytics/calibration/reconcile`. ### 3. Business-Cycle Chart Windowing Timeseries endpoints now use `daily_reset_time` (e.g. 04:00 to 04:00) instead of midnight-to-midnight. ### 4. New Repository Method `get_earliest_event_epoch_async()` supports the reconciliation scan. ## Tests 85/85 pass including 3 new tests for reconciliation, quiet-window trigger, and API endpoint.
Root cause: check_and_run_auto_calibration_async was only invoked inside
get_live_occupancy_async (on-demand API handler). If no browser client
was active during the 03:30-04:30 quiet window, the calibration never
fired. Friday, Saturday, and Sunday had zero calibration logs.

Fixes:
1. Background daemon scheduling — wire auto-calibration check into
   background_monitor() loop (every 30s), independent of HTTP requests.
2. Historical reconciliation — new reconcile_historical_calibrations_async()
   walks past business cycles, detects missing calibration logs, computes
   the nocturnal offset for each, and records audit entries. Runs on boot
   and exposed as POST /api/analytics/calibration/reconcile.
3. Business-cycle chart windowing — timeseries endpoints now use
   daily_reset_time (e.g. 04:00→04:00) instead of midnight-to-midnight,
   so charts align with the actual operating cycle.
4. New repository method get_earliest_event_epoch_async() supports the
   reconciliation scan.

Hypothesis that turned out correct: calibration only triggered via
get_live_occupancy_async → no HTTP request at 3:30 AM → no calibration.
Address code review findings from PR #8:

- Auto-calibration now evaluates the completed business cycle during the
  quiet-window tail (04:00-04:30) instead of the brand-new cycle, and dedups
  persistently against the calibration audit log.
- Historical reconciliation no longer over-matches the next cycle's log when
  checking for an existing calibration entry.
- Hourly timeseries charts now join counting_cameras and filter
  excluded/inactive cameras, matching the calibration aggregation math.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Author
Owner

Code Review — Standards & Spec

Reviewed git diff master...HEAD (commit 6a032b0) against PR #8's stated fixes and the repo's domain docs. No CODING_STANDARDS.md exists, so Standards carried only the Fowler smell baseline.

Standards (5 findings, all judgement calls)

  • Duplicated Code — calibration formula guard_target - raw_net recurs across occupancy_service.py and analytics_service.py.
  • Duplicated Code — quiet-window overlap expression duplicated in two services.
  • Middle Man — AnalyticsService telemetry/history methods merely delegate to repos.
  • Data Clumps — nine calibration-log fields travel together from three call sites.
  • Shotgun Surgery — calibration responsibility split across controller, two services, and repository.

Worst: duplicated calibration formula.

Spec (5 findings)

  • Fixed — quiet-window tail 04:00–04:30 selected the brand-new cycle instead of the completed cycle.
  • Fixed — reconciliation log-search over-matched by one hour into the next cycle.
  • Fixed — hourly timeseries ignored the counting_cameras excluded/inactive filter used by calibration math.
  • Remaining — no test asserts background_monitor() actually invokes the calibration check on its 30s cadence.
  • Remaining — reconcile_historical_calibrations_async records audit logs but never updates occupancy_config.baseline_offset.

Full suite after fixes: 85 passed.

Corrections committed as 9154a89 and pushed to this branch.

## Code Review — Standards & Spec Reviewed `git diff master...HEAD` (commit `6a032b0`) against PR #8's stated fixes and the repo's domain docs. No `CODING_STANDARDS.md` exists, so Standards carried only the Fowler smell baseline. ### Standards (5 findings, all judgement calls) - **Duplicated Code** — calibration formula `guard_target - raw_net` recurs across `occupancy_service.py` and `analytics_service.py`. - **Duplicated Code** — quiet-window overlap expression duplicated in two services. - **Middle Man** — `AnalyticsService` telemetry/history methods merely delegate to repos. - **Data Clumps** — nine calibration-log fields travel together from three call sites. - **Shotgun Surgery** — calibration responsibility split across controller, two services, and repository. Worst: duplicated calibration formula. ### Spec (5 findings) - **Fixed** — quiet-window tail `04:00–04:30` selected the brand-new cycle instead of the completed cycle. - **Fixed** — reconciliation log-search over-matched by one hour into the next cycle. - **Fixed** — hourly timeseries ignored the `counting_cameras` excluded/inactive filter used by calibration math. - **Remaining** — no test asserts `background_monitor()` actually invokes the calibration check on its 30s cadence. - **Remaining** — `reconcile_historical_calibrations_async` records audit logs but never updates `occupancy_config.baseline_offset`. Full suite after fixes: **85 passed**. Corrections committed as `9154a89` and pushed to this branch.
Historical reconciliation now writes the latest corrected offset back to
occupancy_config when the current cycle has not yet been calibrated. This
ensures backfilled logs actually correct the live baseline instead of only
recording audit history.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
gabogg merged commit ec03e892b5 into master 2026-09-07 17:20:16 +00:00
Sign in to join this conversation.
No description provided.