feat(occupancy): retrieve and stage historical HikCentral flow #166
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!166
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/historical-flow-backfill"
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?
Summary
Local passenger-flow history begins on 2026-09-03, leaving older days unavailable for closed-month and year-over-year statistics. The deployed HikCentral OpenAPI 2.6.3 service returns hourly IN/OUT counts per counting camera from before that date (verified for July and August 2026).
This PR delivers the first step of the historical-flow RFC: retrieval and durable, revisioned staging (#162).
Scope change (2026-09-28): this PR was first planned as the single integration branch for #162–#165. It now closes #162 only; the rest moved to stacked drafts (see the split comment below):
Merging #162 first allows a real read-only fetch on production. That settles what the RFC leaves open (
completenessmeaning, empty-hour behavior, retention depth) before curation is designed on top. #160 stays open as the umbrella until #165 lands.Architectural impact
ArtemisClient.people_counting_by_hour_asyncforstatisticsTotalNumByTime(at most 10 comma-joined cameras, hourly, ISO times with facility offset).flow_history_months,flow_history_fetches,flow_history_requests,flow_history_camera_hours.(month, source, camera, hour, revision).people_counting_events.FlowHistoryServiceruns background fetches, one at a time per month.completenessis kept./api/occupancy/history/…(POST /fetches,GET /fetches,GET /fetches/{id},GET /months/{month}).app/controllers/errors.domain_errorsreplaces the duplicated error mapping in the statistics, analytics and history controllers.app/facility_time:FacilityMonthandconfigured_reset_time.Verification
python3 scripts/check_docs.pyruff check .andruff format --check .93df647). Earlier run: 439 passed. A pre-commit re-run onb07a0b9passed, including the 5 leap-year cases added afterwards.b07a0b9.93df647.Checklist
Issues closed on merge
Closes #162
Part of #160 (umbrella; stays open until #165 lands).
🤖 Generated with Claude Code
WIP: feat(occupancy): reviewed historical passenger-flow backfillto feat(occupancy): reviewed historical passenger-flow backfillReview pass 1 —
fec62f3(#162 retrieval and staging)Two-axis review of
a236666...fec62f3: Standards (AGENTS.md,docs/standards/code-standards.md, and the Fowler smell baseline) and Spec (#162 and the RFC as ofa236666). This is the first pass, so every P1, P2 and P3 finding will be addressed on this branch before a second pass is requested.Spec
MAX(revision)snapshot subquery andUNIQUE(source, camera, hour_epoch, revision)all ignoremonth.ON CONFLICT … DO UPDATEpath and overwrites month M's row in place, leaving stalemonthandfetch_idvalues.expected = request.expected_camera_count or len(codes), so the count check never fires.unregistered_camera_codesdoes not setcamera_mismatch.completenesssignal is only half-captured.int, and only from the last page.runningfetches after a restart.interruptedonly when a new fetch for the same month starts, so the list shows them as running indefinitely.cameras_with_missing_hours == CAMERASassertion is trivially true.GET /months/{month}per-day coverage leans toward #163 day review. It is defensible as coverage evidence.Standards
list_month_requests_async -> list[dict[str, Any]]is consumed by key in the service. Standard: code-standards §2.3, "Avoid passing untyped, raw dictionaries between service layers when structured schemas are available". Alsoby_day: dict[date, list[Any]].RequestEvidence.status: strand the untypedstatuslocal in_run_fetchshould useRequestStatus/FetchStatus.CONFLICTcode was added. AGENTS.md §2 asks for established or specific codes.handle_controller_errorscopies the statistics controller's version.cfg.get("daily_reset_time") or "04:00"appears twice.(enter_num, exit_num)comparison appears in both the repository and the service.RequestEvidencemirrorsHistoricalFlowRequestRecordfield for field._fetch_revisionreads as a verb.parse_month()is called only for its validation side effect._fetch_rangeis about 85 lines with 11 mutable locals.strand is re-parsed at several sites.wait_for_fetch_asyncis only used by tests, which widens the service's surface.assert fetch is not Noneis used as control flow in the repository. It is stripped under-O.Totals: Spec: 1 P1, 5 P2, 1 P3. Standards: 1 P2, about 10 P3. Worst per axis: the Spec P1 revision/snapshot overwrite, and the Standards P2 raw dicts crossing the repository→service seam.
Review pass 1: resolution in
b07a0b9Every pass-1 finding has been addressed; details below. Full suite green: 439 tests in the last full run, plus 5 leap-year cases added afterwards, and the pre-commit pytest re-ran on commit. Ruff and
check_docs.pyare clean.Spec
UNIQUE(month, source, camera, hour, revision), and the latest-value lookup and the snapshot subquery both filter on month.ON CONFLICT DO UPDATEis gone, so staged rows are insert-only.StagedRevisionConflictErrorif a same-revision overwrite is ever attempted.test_straddling_hour_is_staged_by_each_month_without_overwriting,test_straddling_hour_disagreeing_between_days_fails_the_later_range.omitted_camera_codesfield.camera_mismatch_reasonslists count, unregistered, omitted, without-rows and unexpected differences, andcamera_mismatchis derived from it.completeness_valuesandcompleteness_unreported.cameras_failedandcameras_never_requestedfields.recover_interrupted_async()runs at startup.GET /months/{month}. Kept as coverage evidence ("gaps remain reviewable"). The schema and docstring state it is not a Complete/Partial/Missing verdict; #163 owns the verdicts.Standards
HistoricalFlowRequestRecord, andby_daybecame a typed range slice.RequestStatus/FetchStatusare used for the evidence and the run status.HISTORY_FETCH_RUNNING(409).app/controllers/errors.domain_errors, used by the statistics, analytics and history controllers.configured_reset_time()lives infacility_time.CameraHourCount.counts/.keyreplace the repeated tuple comparisons.HistoricalFlowRequestEvidencebase, whichHistoricalFlowRequestRecordextends; the repository dataclass is removed._fetch_revisionis now_revision_of_fetch, and the side-effectparse_monthcall is gone._fetch_rangeis split into_harvest_pages,_page_items,_accept_pageand_accept_row.FacilityMonthvalue type (parse, business days, completion check).wait_for_fetch_async. Kept as the service's completion handle for its background task, and documented as such. Tests use it instead of polling or mocking.require_fetch_asyncraisingLookupError.Also restored the RFC "Storage and monthly review" heading, which
fec62f3dropped.Ready for review pass 2.
gabogg referenced this pull request2026-09-28 09:00:59 +00:00
gabogg referenced this pull request2026-09-28 09:01:01 +00:00
feat(occupancy): reviewed historical passenger-flow backfillto feat(occupancy): retrieve and stage historical HikCentral flowScope split: this PR now closes #162 only
At the maintainer's request (2026-09-28), this PR no longer closes the umbrella #160. It delivers retrieval and staging (#162), which is implemented and passed review pass 1. The remaining children moved to stacked draft PRs, each labelled
high-priority:needs-triage: maintainer decision neededready-for-agentready-for-agentWhy split
completenessmeaning, empty-hour behavior, retention depth) before #163's review model is designed on top of it.Why #163 needs input: it reuses #113's per-day schedule records, #114's effective-date policy and #161's holiday/event identity. All three are open and unimplemented. #170 lists the options: private draft context now, wait for #113/#161, or split #163 so the server-side day review comes first.
What changed on this PR
Closes #162, and #160 is referenced as the umbrella without being auto-closed.Review pass 2 —
b07a0b9(#162 retrieval and staging)Reviewed
a236666...b07a0b9, including the pass-1 fixes infec62f3..b07a0b9, against issue #162, the historical-flow RFC, AGENTS.md, and the code standards. The pass-1 fixes addressed the previously reported collision, camera, completeness, recovery, and test concerns. This pass found the following remaining issues.Standards
app/services/flow_history_service.py(_page_items,_accept_row, around lines 425–487) reads the vendor envelope and fieldscode,list,cameraIndexCode,enterNum, andexitNum.docs/standards/code-standards.md§1.1 says clients must isolate third-party protocol details so services receive domain-oriented inputs and outputs. Move envelope and row decoding intoArtemisClientor a client-side adapter, returning typed page/evidence data.app/facility_time.pyandapp/schemas/flow_history.pyseparately define the sameYYYY-MMvalidation pattern. Sharing the pattern would keep month validation consistent across the domain and API schema.Spec
get_month_summary_asyncbuilds day ranges with the currentdaily_reset_time(app/services/flow_history_service.py, around lines 507–532), while fetches retain their original request ranges. Changing the reset can move staged hours between days and report a recorded request ascameras_never_requested. The RFC requires “Every request's range ... [to be] stored with the fetch” and describes the month summary as per-business-day review evidence. Derive historical coverage from the persisted fetch context or preserve the reset/ranges used for that revision.?revision=limits staged rows, but the camera list andlatest_attemptsinclude all later fetches (app/services/flow_history_service.py, around lines 508–516). Re-reading an earlier revision after a later fetch can change its coverage and show later failures alongside older counts. The RFC says “earlier revisions stay readable” and calls this summary review evidence. Scope cameras and attempts to the requested revision's historical view.Totals: Standards 2 findings (worst P2 client boundary); Spec 2 findings (both P2 coverage evidence). Under the ordinary second-pass gate, the P2 findings need fixes before merge; the P3 can be fixed here or tracked in a linked follow-up issue with its acceptance criterion.
Review pass 2: resolution in
93df647All four findings are fixed on this PR. At the maintainer's request, the single P3 is fixed here instead of being filed as a follow-up issue.
Standards
app/clients/people_counting.pydecodes thestatisticsTotalNumByTimeenvelope (code/msg/data) and its rows (time,cameraIndexCode,enterNum,exitNum) into typedPeopleCountingPageandHourlyPeopleCountvalues. It handles numeric strings, treats naive times as facility time, counts malformed rows and records why a page failed.ArtemisClient.people_counting_by_hour_asyncnow takes datetimes, formats the request itself and returns a typed page.YYYY-MMpattern.MONTH_PATTERNis now defined once inapp/facility_time.pyand reused byapp/schemas/flow_history.py.Spec
reset_timeits day ranges used, and each request records itsbusiness_day.reset_timeand flagsreset_times_differwhen fetches in the view used different resets.before_fetch_id) and the returned fetch list.Tests
Verification
masterinto the branch first (a0202af).check_docs.pypasses; pre-commit passed on commit.Merging next, as the maintainer requested.