fix(statistics): one ranked cycle verdict; offset resets are not verdicts #137
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!137
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/cycle-verdict-ranking"
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?
Part of #128: the verdict decision (its first Spec item). The other #128 items are mechanical P3s, which PR #132 already claims, so this PR leaves them alone.
Summary
A day's
excludedandtrustedflags now come from one ranked cycle verdict, as decided on 2026-09-26 (#80 grilling, Q2/Q3):MANUAL_ADMIN,MANUAL_OVERRIDE) are not verdicts. A live "calibrate now" during a bad day no longer makes it trusted, and a reset on its own leaves a day unverified.Before this PR:
excludedcame from the latest nocturnal audit, whiletrustedcame from a ranking that also counted resets.excluded=true, trusted=true, and the baseline would use it.MANUAL_ADMINreset made an automatically excluded day trusted.Architectural impact
app/db/occupancy_repository.py: new_CYCLE_VERDICT_TYPES_SQL,_CYCLE_VERDICT_PRIORITY_SQL(retroactive > nocturnal) and_EXCLUDED_TRUST_STATUSES_SQL.get_counted_cycle_quality_range_async, theverdictsCTE now keeps only verdict types and also returnstrust_status.is_excludedis derived from the same winning row asis_trusted.auditsCTE now supplies onlydata_trust_scoreandcycle_completeness_score.Every consumer reads that one query:
DailyStatistics.excluded/trusted;PeriodQuality.excluded_days(the picker and the summary);So all of them now agree.
k learning is unchanged. Manual calibrations (
calibrate_baseline_offset_asyncwithMANUAL_ADMIN) do measure k, so the trusted-history query keeps its old ranking, now named_K_MEASUREMENT_PRIORITY_SQL. The issue assumed resets carry no multiplier; the code shows they can, so the k path is pinned by a test instead of changed.docs/api/README.mddescribes the verdict andexcluded_dayswith the new rule. No schema change and no migration.Not touched:
get_calibration_log_for_cycle_async, the per-cycle multiplier lookup, which still ranks resets first. That lookup is #127's to rework.Verification
New
tests/test_cycle_verdict.py(10 tests, real in-memory SQLite, no mocks). Six of them failed before the change:AUTO_EXCLUDEDplus a later retroactive audit givesexcluded=false, trusted=true, the day counts in the baseline, andexcluded_daysis 0;MANUAL_ADMINorMANUAL_OVERRIDEreset changes nothing on a trusted day, and doesn't launder an excluded one: it stays excluded, is counted inexcluded_daysand stays out of the baseline;false, false);tests/test_statistics_baselines.py::test_usual_weekdays_follow_the_ranked_cycle_verdict: the reset-on-an-untrusted-day case now expects that day to be excluded from the baseline (the old test encoded the reset as a verdict).pytest: 394 passed, 1 skipped. Ruff, pre-commit andscripts/check_docs.pypassed.Conflict note: PR #132 also edits
get_counted_cycle_quality_range_async(a typedCountedCycleQualityRecord). Whichever of the two lands second rebases.Checklist
excludedandtrustedfrom one ranked verdict (retroactive > nocturnal, as toggled).excluded_daysand the baseline agree.🤖 Generated with Claude Code
Standards
(a) Documented Standards Violations (Hard Violations)
CONTEXT.md(§ Closed Day) &AGENTS.md(§ 5 Authoritative References)tests/test_cycle_verdict.pyCONTEXT.mdstrictly defines a Closed Day as "A calendar day on which the facility's schedule declares it closed to the public... passages counted on it do not enter the period's figures". The test helper returns yesterday's date for an open, completed business cycle seeded with customer flow (ENTRIES=100,EXITS=80). Conflating an open completed cycle with a scheduled facility closure breaches the repo's ubiquitous domain language.(b) Baseline Smells (Judgement Calls)
tests/test_cycle_verdict.pyclosed_day()contradicts what it actually returns (a past/completed cycle, not a facility closure). Recommended rename:yesterday(),past_cycle(), orcompleted_cycle().tests/test_cycle_verdict.pyverdict(day) -> tuple[bool, bool]returns an untyped tuple making assertions likeassert await verdict(day) == (False, True)ambiguous regarding positional order. ANamedTupleor assertingrow.excluded, row.trusteddirectly would clarify intent.Spec
(a) Missing or partial requirements
"Document the null label. docs/api/README.md ("Each usual daypart carries its clock label… the label is the same for all of them") should say that label is null when source days' clock spans differ."docs/api/README.md. (PR description states non-verdict items were left for PR #132).analytics_service.py"Stale comment at analytics_service.py (~230, usual-weekday candidate selection): 'oldest first, as the range query expects; the trust verdict is then read only until enough days pass'...""(resets carry no computed_exit_multiplier, so the k history should be unaffected; pin this with a test)"computed_exit_multiplierleaves k history unaffected.(b) Behaviour in the diff not asked for (scope creep)
_K_MEASUREMENT_PRIORITY_SQL"Remove them from _CYCLE_VERDICT_PRIORITY_SQL (or filter them out of every verdict query). That fragment also drives the trusted-history query used for k learning, so check that query still returns what calibration expects (resets carry no computed_exit_multiplier, so the k history should be unaffected; pin this with a test)."_K_MEASUREMENT_PRIORITY_SQLprioritizingMANUAL_ADMIN/MANUAL_OVERRIDEabove automatic audits for k-learning.(c) Requirements implemented but implementation looks wrong
"(resets carry no computed_exit_multiplier, so the k history should be unaffected; pin this with a test)"test_k_learning_history_still_learns_from_manual_calibrations,offset_resetis supplied withcomputed_exit_multiplier=RESET_Kand asserts that it overrides the automatic audit, diverging from the issue's stated assumption.Summary: Standards: 3 findings (worst: domain term collision with
Closed Dayin test helper); Spec: 5 findings (worst:_K_MEASUREMENT_PRIORITY_SQLdivergence from issue #128 specification on resets and k-learning).e991c3240c4c4432ae1dReview fixes:
4c4432aAll findings from the review are addressed or clarified below.
Standards
CONTEXT.md(§ Closed Day) collision:closed_day()helper returned open completed cycle dateclosed_day()→yesterday()acrosstests/test_cycle_verdict.pyto preserve domain model semantics.closed_day()contradicts returned cycleyesterday().verdict(day) -> tuple[bool, bool]untyped positional returnDayVerdict(NamedTuple)with explicitexcluded: boolandtrusted: boolfields.Spec
docs/api/README.mddocs/api/README.md(labelis null when source days' clock spans differ).analytics_service.py(~230)master; merged and verified during rebase.test_offset_reset_without_multiplier_leaves_k_history_unaffected._K_MEASUREMENT_PRIORITY_SQL(scope creep)Integration & Rebase
origin/master, resolving conflicts inapp/db/occupancy_repository.py,docs/api/README.md, andtests/test_statistics_baselines.py.ScheduleCalendarrequirement from PR #132 intotests/test_cycle_verdict.py.Code Review — Second Pass (PR #137)
Diff reviewed:
master...fix/cycle-verdict-ranking(commits:148d2c7,4c4432a)Standards
(a) Documented Standards Compliance
docs/standards/code-standards.md§1.1): SQL changes are strictly encapsulated inapp/db/occupancy_repository.py.AGENTS.md§2,code-standards.md§2): Explicit Python 3.11+ type annotations across all new functions/returns; all I/O is cleanly awaited.CONTEXT.md§3): Conforms strictly to single ranked cycle verdicts (RETROACTIVE_GUARD_AUDIT>AUTOMATIC_NOCTURNAL) while ensuring offset resets (MANUAL_ADMIN,MANUAL_OVERRIDE) teachkwithout laundering cycle data quality.4c4432a(DayVerdict(NamedTuple),yesterday()helper,ScheduleCalendarinstantiation).(b) Baseline Smells (Judgement Calls)
tests/test_statistics_baselines.py:245-247):add_verdict(reset_on_untrusted, "MANUAL_ADMIN", True)when asserting that an offset reset is not a verdict creates slight semantic dissonance with the accompanying comment.tests/test_cycle_verdict.py:128):async def period_quality(granularity: str, day: date)uses rawstrinstead of domainGranularity/ literal typing.Spec
(a) Requirements Missing or Partial
None. All requirements from originating Issue #128 are implemented:
RETROACTIVE_GUARD_AUDIT>AUTOMATIC_NOCTURNAL) are active across daily stats, period quality, and baseline selection.docs/api/README.mdupdated.4c4432aaddedtest_offset_reset_without_multiplier_leaves_k_history_unaffected, satisfying the pinning requirement for k learning.(b) Behaviour Not Asked For (Scope Creep)
_K_MEASUREMENT_PRIORITY_SQL(app/db/occupancy_repository.py):(c) Requirements Implemented That Look Wrong
None. Verdict queries in
app/db/occupancy_repository.pyevaluateis_excludedandis_trustedfrom the ranked audit verdict (_CYCLE_VERDICT_TYPES_SQLand_CYCLE_VERDICT_PRIORITY_SQL). All 408 tests pass (100% green).One-line summary
Follow-up
Per directive, all non-blocking P3 cleanup suggestions have been consolidated into follow-up issue #142 (follow-up(statistics): P3 cleanups from PR #137 review).
fix(statistics): one ranked cycle verdict; offset resets are not verdicts (#128)to fix(statistics): one ranked cycle verdict; offset resets are not verdicts