feat(schedule): complete schedule follow-ups #228
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!228
Loading…
Reference in a new issue
No description provided.
Delete branch "chore/schedule-p3-followups-186"
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?
Completes the schedule follow-ups in #186. Completed days now keep their schedule through outages and later weekly edits, and calibration reads an explicitly stored Original Schedule that corrections and overwrites preserve.
Architectural impact:
ScheduleAuditActiondefines the action vocabulary for schemas, repository writers and generated CHECKs. A validated CHECK helper handles sources and actions; the generic action migration rebuilds outdated constraints while retaining rows, indexes, triggers and the audit ID sequence.original_valueJSON. The atomic upgrade backfills the oldest audit old/new value, or the record when unaudited. The upgrade runs once so future imported drafts can remain unfixed until approval. Decoding preserves the storedrecordedflag.Coordination: shared write-path invariants posted on PR #178; origin/master refreshed during implementation.
Verification:
python scripts/check_docs.pypassed (42 Markdown files, 80 HTTP operations).Checklist:
Closes #186
WIP: chore(schedule): complete schedule follow-upsto chore(schedule): complete schedule follow-upsRequesting code review pass 1 for 016a012...87e3bb4, spec #186. All eight triage decisions are implemented, including ADR 0008 explicit Original Schedule storage and upgrade backfill. Ruff lint/format, scripts/check_docs.py, the full pytest suite (544 tests through the commit hook), and frontend tests (244) pass. This is a follow-up PR, so the repository three-pass review path applies. Coordination notes were posted on overlapping PR #178; it remains open, and origin/master is current.
Merged
origin/master(b64e805) intochore/schedule-p3-followups-186. All merge conflicts were resolved preserving both #178 holiday/event context and #228 schedule follow-up behaviors:Conflict Resolutions
app/facility_time.py:BusinessDaySchedule.from_record: Merged bothis_holiday=bool(row.get("is_holiday", 0))(#178) andrecorded=bool(row.get("recorded", True))(#228), ensuring stored recorded flags and holiday classifications are accurately decoded.app/db/database.py:init_schema: Preserved dynamic check andALTER TABLEaddition ofis_holidaycolumn onoccupancy_business_day_schedules.sql_check_choices("action", ScheduleAuditAction)foroccupancy_business_day_schedule_audit._migrate_schedule_audit_actionsand_backfill_original_schedulesbefore the versioned holiday migration.20261002_holiday_backfill: Includedoriginal_valuewithjson.dumps(stamp_dict)when inserting unstamped past holidays intooccupancy_business_day_schedules, so stamped historical records always have an explicit Original Schedule.app/db/occupancy_repository.py:_BUSINESS_DAY_SCHEDULE_UPSERT_SQL&ensure_business_day_schedule_record_async: Aligned columns to include all 11 fields (day_date, is_open, open_time, close_time, source, is_exception, exception_name, is_holiday, created_at, updated_at, original_value) matching_business_day_schedule_params.add_holiday_async: Preserved nameless exception empty string handling (str(holiday.get("name") or "").strip()) while adoptingholiday_datevariable and holiday identity tracking.get_original_business_day_schedule_async: Read explicitly storedoriginal_valueJSON directly fromoccupancy_business_day_schedules(ADR 0008) while querying and overridingis_holidaydynamically from the current business-day schedule record (SELECT original_value, is_holiday FROM occupancy_business_day_schedules WHERE day_date = ?), maintaining the business-day record as the single source of holiday truth without a second holiday source.app/services/occupancy_service.py:add_schedule_exception_async&delete_schedule_exception_async: Retained freeze-before-mutation docstrings while keeping completed-day validation andactor/reasonaudit parameter propagation.app/static/js/app.js:renderHolidaysList: Combined localized fallback for nameless schedule exceptions (h.name || t('occupancy.scheduleExceptionSingular')) with theHOLIDAY/NON-HOLIDAYbadge display.tests/test_schedule_migrations.py: Added explicit column list onoccupancy_business_day_schedulesinsert intest_original_backfill_uses_oldest_audit_or_record_and_runs_onceto account foris_holiday.tests/test_occupancy.py: Alignedtest_holiday_schedule_evaluationto checkunnamed_info["is_exception"] and not unnamed_info["is_open"]for closed unnamed schedule exceptions.Verification
ruff check .: passed (clean)ruff format --check .: passed (clean, 152 files)python3 scripts/check_docs.py: passed (42 Markdown files, 89 HTTP operations)pytest: passed full suite through pre-commit hook (564 passed)node --test tests/frontend/*.test.js: passed (245 tests passed)Requesting code review pass 1 for b64e805...f4f2264, spec #186.
Merged origin/master (
9a72ff4) intochore/schedule-p3-followups-186. All merge conflicts were resolved preserving #177 next-day settings activation and audit flow, #234BEGIN IMMEDIATEschema transaction wrapping, #178 single source of holiday identity, and #228 schedule follow-up behaviors:Conflict Resolutions
docs/api/README.md:occupancy_effective_resets), contiguous business cycles (end_D + \epsilon == start_{D+1}), prospective changes at tomorrow's effective reset boundary (facility_now().date() + timedelta(days=1)), and pending activation notices returned by mutation endpoints.app/db/database.py:BEGIN IMMEDIATEtransaction wrapping around_run_schemaand inlineALTER TABLE counting_cameras DROP COLUMN zone_name.occupancy_effective_resets,occupancy_pending_changes,occupancy_settings_audit) and itsCANCELLATIONaudit action migration._migrate_schedule_audit_actions(c)and_backfill_original_schedules(c).app/services/occupancy_service.py:stamp_business_day_schedules_async: Adopted #177's effective reset schedule lookup (reset = await self.get_reset_schedule_async()) while preserving #228's earliest flow data fallback logic and docstrings.add_schedule_exception_asyncanddelete_schedule_exception_async: Retained #177's pending change staging, completed-day validation, and audit flow.update_weekly_schedule_async: Maintained #177's prospective staging behavior and removed extraneousensure_started_day_schedule_async()call which was firing pending changes with wall-clock time during test simulations of past dates.Verification
ruff check .: passed (clean)ruff format --check .: passed (clean, 155 files)python3 scripts/check_docs.py: passed (Checked 43 Markdown files and 90 HTTP operations)pytest: passed full test suite through commit pre-commit hook (and targeted 31/31 intest_next_day_settings_activation.pyand 38/38 in schedule/holiday tests)node --test tests/frontend/*.test.js: passed (250 tests passed, 0 failures)Requesting code review pass 1 for 9a72ff4...f2a4078, spec #186.
Code review, pass 1 (
origin/master...f2a4078, spec #186 + its triage decisions)Result: 2 P2s and 10 P3s. Not mergeable yet. This is pass 1, so fix every finding and request a second pass. Both P2s come from the two rounds of conflict resolution with #178 and #177, not from the original #186 work.
is_holidayororiginal_value, plus a custom index and trigger.init_schemarebuilt the CHECK and accepted a FREEZE insert._oldtable was left behind.old_value, or from the record when a day had no audit row.BEGIN IMMEDIATE.original_valueis written once and never touched byON CONFLICTor a correction.is_holidayoverlays the Original Schedule.Spec
P2
app.js~1870 and ~1888). The spec says "A nameless exception … replaced by a frontend-localized default".live.is_holiday. Since #178,is_holidaymeans holiday identity only, andOccupancyLiveResponsedoesn't exposeis_exception. So a nameless, open, non-holiday exception (10:00–18:00) shows the backend's weekday label ("Lunes (10:00 - 18:00)") with the working-hours title, where master showed "Excepción de horario".f4f2264switched the test assertion tois_exception, but the frontend wasn't switched to match. The frontend test only coversis_holiday: true.is_exception(or key on the exception name being present) in the live response, use it in the card, and add a frontend test for a nameless non-holiday exception.renderHolidaysList,app.js~2481). The<span>${escapeHtml(h.holiday_date)}</span>date label disappeared in mergef4f2264. Neither87e3bb4nor master removed it. With several nameless exceptions, every row reads "Schedule exception", and an admin can't tell which date each row is before pressing DEL or toggling HOLIDAY. Restore it.P3
Item 1 is only partly done. The spec says "the repository uses it instead of literals". Bare
'CORRECTION'remains atoccupancy_repository.py:876(_sync_record_holiday_async, from #178). UseScheduleAuditActionthere. The one-time migration literals atdatabase.py:698and:767('CORRECTION','STAMP') should use it too.Decision 8.2 is not applied consistently:
"Feriado"(exc_name = … or "Feriado").week_view.js:710andmonth_view.js:705now render "Holiday: " with a blank name.Use the same localized default everywhere, or leave a note pointing to #73.
The frontend rebuilds the backend's
schedule_labelformat (app.js:1870). Two places now own the formatname (open - close). Duplicated Code.Standards
P3
monitor_service.py:116). Each business-day boundary restamps from the first flow day, with one SELECT per day underBEGIN IMMEDIATE, and skips the ten-year cap for the default range. It's correct, but the write lock grows with history. Start from the latest recorded day. Plausible, not measured.original_value(_business_day_schedule_params(original=…)).originalis used only on the INSERT path, and the upsert'sDO UPDATEcorrectly leavesoriginal_valuealone. Add a one-line comment on the upsert SQL so nobody adds it later.tests/test_schedule_migrations.py): nothing tests the rollback path or trigger replay. Index replay and the id sequence are covered.chore/is for dependencies, CI and tool config. A new schema column, a data backfill and new monitor behavior arefix/orfeat/work. Use the right type in new commits and the PR title.Co-Authored-Bytrailer.chore(schedule): complete schedule follow-upsto feat(schedule): complete schedule follow-upsPass 1 fixes (
026e4ae)All P2 and P3 findings from review pass 1 (r48) have been resolved. The branch has also merged
origin/master(026e4ae, including fix commitcffb435).Findings and Resolutions
is_exception: boolandexception_name: str | NoneinOccupancyLiveResponse. Keyed live card default label onlive.is_exceptioninstead oflive.is_holiday. Added frontend test for nameless non-holiday exceptions.app/schemas/occupancy_models.py,app/services/occupancy_service.py,app/static/js/app.js,tests/frontend/test_schedule_exception.test.js<span>${escapeHtml(h.holiday_date)}</span>inrenderHolidaysList.app/static/js/app.js'CORRECTION'and'STAMP'literalsScheduleAuditAction.CORRECTIONandScheduleAuditAction.STAMP.app/db/occupancy_repository.py,app/db/database.pystr(h["name"] or "")without injecting"Feriado". Cleanedweek_view.jsandmonth_view.jsto avoid dangling colons when holiday name is empty. Preserved existing rows per ADR 0006/0007 / ADR #34 until #73 sweep.app/db/database.py,app/static/js/src/ui/statistics_deck/views/week_view.js,month_view.jsschedule_labelformat in frontend(open - close)formatting for nameless exceptions; frontend now prepends the localized default rather than rebuilding the full label string.app/services/occupancy_service.py,app/static/js/app.jsget_latest_recorded_schedule_day_async(before=...). Restamp now starts from the latest recorded day before the current schedule day instead of the earliest flow day.app/db/occupancy_repository.py,app/services/occupancy_service.py,app/services/monitor_service.pyoriginal_valueuntouched_BUSINESS_DAY_SCHEDULE_UPSERT_SQLdocumenting thatoriginal_valueis only set on initial INSERT and never modified byDO UPDATE.app/db/occupancy_repository.pytest_action_check_upgrade_preserves_history_indexes_and_sequence, and addedtest_action_check_upgrade_rollback_on_failure_preserves_statefor SAVEPOINT rollback on CHECK violation.tests/test_schedule_migrations.pyfix(schedule): ...commit type and updated PR #228 title tofeat(schedule): complete schedule follow-ups.cffb435Co-Authored-Bytrailer on commitsCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>trailer to fix commitcffb435and master merge commit026e4ae.All automated checks (
ruff,pytest,npm test) pass cleanly.Ready for review pass 2.
Code review, pass 2 (
origin/master...026e4ae, spec #186 + its triage decisions)Summary: 1 P2 (a merge conflict), plus P3s. #228 is a follow-up PR (from #186), so pass 2 fixes everything; request pass 3 afterwards.
Both r48 P2s are fixed (the live card keys on
is_exception; the date span is restored). The spec reviewer found no new P1 or P2.Must fix:
9a608c1in theapp/db/database.pyimport block. Keep master's import list and addScheduleAuditAction. Do not keepDEFAULT_WEEKDAY_HOURS: it is unused after #233, so ruff fails with F401.Standards axis
Result: 0 P1, 1 P2 (merge conflict), 6 P3. All ten pass-1 findings are fixed or acceptably closed. This PR addresses follow-up issue #186, so git-and-workflow.md "Review passes" step 3 applies: fix every P1, P2 and P3 here, then request a third pass.
Checks run on the merged tree (origin/master
9a608c1+026e4ae, with the conflict resolved as described in N-P2-1):ruff checkandruff format --checkare clean.test_business_day_schedules.pyandtest_schedule_migrations.py(20), plustest_occupancy.pyandtest_holiday_state_reset.py(22).node --testpasses fortest_schedule_exceptionandtest_command_deck_adapter(27).Previous findings (r48)
OccupancyLiveResponse.is_exception/exception_nameadded (occupancy_models.py ~1009).get_active_schedule_info_asyncreturns them (occupancy_service.py:1450-1451). The card keys onisException(app.js:1870-1884). A frontend test covers the nameless non-holiday case in en and es.<span …>${escapeHtml(h.holiday_date)}</span>is present at app.js:2493 and is now identical to master. No test covers it (see N-P3-5).FREEZE/STAMP/OVERWRITE/CORRECTIONstrings left inapp/**/*.pyare the enum definitions (occupancy_models.py:384-387).""(database.py:743). week_view.js:710 and month_view.js:705 no longer leave a dangling colon. Normalizing legacy rows is already covered by #73 (maintainer comment of 2026-10-03: "legacy rows stay as they are", fold into area (c)).schedule_labelformatname (open - close). It still parses the backend label withindexOf('(')(app.js:1875-1878), and that code path can never do anything useful (see N-P3-1).get_latest_recorded_schedule_day_async(before=schedule_day). This still meets the #186 decision ("On startup the monitor backfills days it missed while down"). Older history goes through the admin default stamp.original_value_BUSINESS_DAY_SCHEDULE_UPSERT_SQL(occupancy_repository.py:73).test_action_check_upgrade_preserves_history_indexes_and_sequencenow checks thatcustom_audit_triggerfires once per new action.test_action_check_upgrade_rollback_on_failure_preserves_statecovers the SAVEPOINT rollback.feat(schedule): complete schedule follow-ups. That title becomes the merge commit<title> (#228), and the fix commit usesfix(schedule). The branch name stayschore/…, which can't change without a new PR.Co-Authored-Bycffb435and026e4aehave the trailer.87e3bb4,f4f2264andf2a4078still don't, and fixing them would need a history rewrite. Accept this; don't force-push.New findings
P2
N-P2-1. Merge conflict with master in
app/db/database.py:15-24(import block). CONFIRMED.git merge-tree --write-tree origin/master 026e4ae32breports a single conflict, in the import block. Master (#233) swappedDEFAULT_HOLIDAY_HOURSandDEFAULT_WEEKDAY_HOURSforDEFAULT_DAILY_SCHEDULEandseed_default_occupancy_config. The PR keepsDEFAULT_WEEKDAY_HOURSand addsScheduleAuditAction.DEFAULT_WEEKDAY_HOURSis left unused in the merged file, because master's only use (default_days) is gone. Ruff then fails with F401, and so do pre-commit and CI.ScheduleAuditActionfrom the PR side.reset_test_occupancy_statedeletesoccupancy_business_day_schedulesand its audit rows, so the neworiginal_valuecolumn is reset along with them.OccupancyManager.reset()doesn't interact with the new manager method, which holds no state.seed_default_configdoesn't touch the schedule tables.P3
N-P3-1. The live-card label code has a branch that can never run (app.js:1870-1878). CONFIRMED by reading the code.
hours(label = f"{name} {hours}".strip() if name else hours, occupancy_service.py:1440). SoparenIdxis always 0, and both the slice and theparenIdx === -1fallback do nothing. The frontend still knows the backend's(format, which is what P3-3 asked to remove.`${t('occupancy.scheduleExceptionSingular')} ${live.schedule_label}`.isException = live.is_exception || (live.is_holiday && live.is_exception !== false)is a compatibility fallback for a payload withoutis_exception. That can't happen, because the backend always sends the field with a default ofFalse(Speculative Generality).excName = live.exception_name || live.holiday_nameis also redundant, becauseholiday_nameisexception_namewhenever the day is an exception.N-P3-2. The manager method is a pure Middle Man, and the monitor stitches the backfill together itself (occupancy_service.py:1332-1336, monitor_service.py:116-128). PLAUSIBLE (smell).
OccupancyManager.get_latest_recorded_schedule_day_asynconly forwards to the repo.stamp_business_day_schedules_asyncwith five arguments. "Backfill missed days" is domain policy that now lives in the polling loop.occupancy_manager.backfill_missed_schedule_days_async(), that finds the latest recorded day and stamps from there. Call it from the monitor and drop the pass-through.N-P3-3. Duplicated normalization in
add_holiday_async(occupancy_repository.py:919 and :927). CONFIRMED.str(holiday.get("name") or "").strip()is computed once for the INSERT and again forholiday_nameinnew_dict. Those are the same two sites #73's maintainer note calls out.holiday_nameonce, before the INSERT, and use it in both places.N-P3-4. The test name doesn't match what the test does (
tests/test_business_day_schedules.py,test_get_latest_recorded_schedule_day_and_monitor_restamp). CONFIRMED.get_latest_recorded_schedule_day_async. It never runs the monitor or a restamp.test_latest_recorded_schedule_day_excludes_the_given_day, or add the monitor assertion. A useful one is that a gap older than the latest recorded day is deliberately not refilled.N-P3-5. No frontend tests for the other pass-1 UI fixes. CONFIRMED. Nothing in
tests/frontendcovers:renderHolidaysList, the P2-2 regression;:;formatBusinessDayScheduleAuditValuenameless default.P2-2 was a silent merge regression, so a small vm-based test like
test_schedule_exception.test.jswould guard it against the next merge.N-P3-6. The monitor stamps with wall-clock time instead of the
nowit already took (monitor_service.py:116-128, occupancy_service.py ~1356). PLAUSIBLE, unmeasured.ensure_started_day_schedule_async(now)receives the loop'snow.stamp_business_day_schedules_asyncinstead callsactive_business_day(reset_time=reset)with the current time.schedule_day, and the code is easier to reason about with one clock.nowthrough, or fold this into the N-P3-2 method.Verdict
Not mergeable yet.
Nothing in the fix commit regresses the pass-1 behavior.
Spec axis
Verdict: both pass-1 P2s are fixed. No new P1 or P2. The only merge blocker is a trivial import conflict with master
9a608c1.Previous findings (r48)
OccupancyLiveResponsenow hasis_exceptionandexception_name(occupancy_models.py:1009-1010), filled fromget_active_schedule_info_async(occupancy_service.py:1447-1448, 3059-3060). The card keys onisException(app.js:1870-1900). For a nameless exception the backend label is(10:00 - 18:00)and the frontend adds the localized default in front of it. tests/frontend/test_schedule_exception.test.js covers a nameless non-holiday exception, a nameless holiday and a named exception in en and es: 4/4 pass.<span …>${escapeHtml(h.holiday_date)}</span>is back inrenderHolidaysList(app.js ~2492) and matches master. A nameless row also shows the localized default.ScheduleAuditActionis used in occupancy_repository.py (878, 1466, 1507, 1551) and in the database.py migrations."", and the week and month views drop the dangling colon. Existing "Excepción de horario" rows are deferred to #73, as the fix reply says.(…)part.get_latest_recorded_schedule_day_async(before=schedule_day)(monitor_service.py:116-127).feat(schedule). The branch name is unchanged, which is acceptable.cffb435and026e4aecarry the trailer.f2a4078does not, and history won't be rewritten.Acceptance re-check (spec #186 + triage)
sql_check_choices, which validates its input.from_recordkeeps the storedrecordedflag.original_valueis written once by every writer: the freeze, the stamp upsert, the correction (withprevious_fallback) and the #178 holiday stamp. NeitherON CONFLICTnor a correction touches it.original_value, not the audit order.MIN(people_counting_events.timestamp_epoch)up toactive_day − 1.flow_history_camera_hoursrows are correctly not counted as flow data, because they are "never read by statistics" until the import lands (#170).Master clash (
9a608c1: #233, #227)app/db/database.py. Master moved the default seeding toseed_default_occupancy_config, soDEFAULT_WEEKDAY_HOURSis no longer used in database.py. Resolution: keep master's import list and addScheduleAuditAction. Keeping the PR side'sDEFAULT_WEEKDAY_HOURSwould leave an unused import.tests/occupancy_reset.pyalready clears both schedule tables.OccupancyManager.reset()(#233) adds no state that the PR depends on.New findings
P1
None.
P2
None.
P3
test_get_latest_recorded_schedule_day_and_monitor_restamp). It tests only the repository helper and never runs the monitor restamp. The boundary path, where the latest record is yesterday and only that day is preserved, is covered only indirectly bytest_monitor_backfills_missed_days_and_preserves_existing. Rename the test or add a boundary case. CONFIRMED.d.holiday_name or ""). Before, the cell read "Excepción de horario" (the repository default) or the ISO date. The CSV is not localized by the frontend, so these cells are now blank. This is consistent with decision 8.2 being frontend-only. Fold it into the #73 sweep. CONFIRMED by reading the code.(Cerrado)is pre-existing backend text, now more visible. Belongs to #73. CONFIRMED by reading the code.Checks run
Pass 2 fixes (
7334922)All P2 and P3 findings from review pass 2 (r56) have been resolved. The branch has merged
origin/master(commit893189c), and pass 2 fixes are committed in7334922. Items Spec P3-3 and Spec P3-4 have been deferred and logged on issue #73.Findings and Resolutions
app/db/database.pyimport blockScheduleAuditActionwithoutDEFAULT_WEEKDAY_HOURS(avoiding F401).app/db/database.py, commit893189cupdateOccupancyLiveCardinapp.js: removed dead string slice/fallback, dead compatibility fallback forlive.is_exception, and redundant fallback forexcName. Simplified working hours text to prependingt('occupancy.scheduleExceptionSingular')tolive.schedule_label.app/static/js/app.js, commit7334922nowclock skewOccupancyManager.backfill_missed_schedule_days_async(now). Dropped the middle-man methodget_latest_recorded_schedule_day_asyncfrom manager. Passednowthrough toactive_business_day(epoch=now, ...)instamp_business_day_schedules_asyncso monitor and stamping use the identical clock reference.app/services/occupancy_service.py,app/services/monitor_service.py, commit7334922add_holiday_asyncholiday_name = str(holiday.get("name") or "").strip()once before the INSERT and reused for both the SQL parameter andnew_dict.app/db/occupancy_repository.py, commit7334922test_latest_recorded_schedule_day_excludes_the_given_daycallingoccupancy_repodirectly, and addedtest_backfill_missed_schedule_days_preserves_older_gapsto verify that monitor forward backfill deliberately preserves older gaps unrefilled.tests/test_business_day_schedules.py, commit7334922<span class="font-mono text-cyan-300...">), nameless row localized fallback inrenderHolidaysList, nameless default informatBusinessDayScheduleAuditValue, and week/month context entry strings without dangling colons in EN and ES. Also added view integration tests intest_statistics_deck_week.test.jsandtest_statistics_deck_month.test.js.tests/frontend/test_schedule_exception.test.js,tests/frontend/test_statistics_deck_week.test.js,tests/frontend/test_statistics_deck_month.test.js, commit7334922OccupancyManager.backfill_missed_schedule_days_asyncdocstring and monitor loop comments.app/services/occupancy_service.py,app/services/monitor_service.py, commit7334922(Cerrado)backend suffixAll automated checks (
ruff,pytest,node --test) pass cleanly.Ready for review pass 3.
Coordinating #171 pass-1 approval fixes: if #228 merges before publication fixes finish, approval will write original_value explicitly via a shared occupancy repository helper, replacing STAMP audit inference. Please notify #171 when original_value lands.
Code review, pass 3 (
origin/master...7334922, spec #186)Summary: 2 P2s, a few P3s. Every r56 finding is fixed. Pass 3 of a follow-up PR: fix the P2s, request pass 4. P3s are filed as #275.
Must fix:
P2-1 (Standards): merge master
5a02cd9, which now contains #222. A trial merge conflicts inapp/db/occupancy_repository.py(add_holiday_async), inapp/services/occupancy_service.py(the two calendarexception_namessites, around 1251 and 1290) and intests/test_occupancy.py(test_holiday_schedule_evaluation). Resolve it this way, which was tested on a scratch merge (ruff clean, 58/58 targeted backend tests, 115/115 Node tests):Noneand stay out ofexception_names. #184 item 7 says an unnamed exception has a null name in the API and in records.holiday_namevariable in the repository.schedule_label == "(Cerrado)", keep theexception_name/holiday_name is Noneassertions, and delete theweekday_row/expected_day_namelines that become unused (F841). Change #228'sholiday_name == ""assertions tois None.app/db/database.pyaround 717-719,exc_name = str(h["name"] or "")) write NULL with.strip() or None. Migrated and live rows must agree. Check thattest_schedule_migrationsdoesn't assert"".P2-2 (Spec): the monitor's "missed days" backfill stamps history it never missed.
backfill_missed_schedule_days_async(occupancy_service.py around 1332-1350) passesstart_date=Nonewhen no record exists before the active day.stamp_business_day_schedules_asyncthen uses the full admin default range, from the earliest flow data to yesterday. Two probes confirm it:system.This breaks CONTEXT.md (a historical day without a record follows the current schedule until an administrator stamps or corrects it) and the 2026-10-02 decision (the automatic path covers only days the monitor missed while down). It also freezes today's weekly plan as each day's Original Schedule for good (ADR 0008). Fix:
FREEZEaudit row or a record from an automatic source), not on any record. Admin or migration stamps must never open an automatic range.Standards axis
Verdict on r56
All six standards findings (N-P2-1 and N-P3-1 to N-P3-6) are FIXED. Evidence:
ScheduleAuditAction.Boolean(live.is_exception)and has no slice parsing.OccupancyManager.backfill_missed_schedule_days_async, and the monitor makes one call to it.holiday_nameis normalized once.nowis passed through toactive_business_day.Checks at the head:
New findings
tests/frontend/test_schedule_exception.test.jshas a tautological test, "week and month context entries render without dangling colon for nameless holidays". It rebuilds the template literal and asserts on constants, so it runs no production code. Thetest_statistics_deck_week/_monthcases already guard this. Delete it.monitor_service.py:118and the docstring atoccupancy_service.py:1338cite "(#186, Spec P3-2)". A review-finding ID means nothing to later readers. Keep#186only.Spec axis
Verdict on r56
Every finding is FIXED. Spec P3-3 and P3-4 are deferred to #73 by decision.
Acceptance criteria (#186 plus the 2026-10-02 decisions)
Probes on a git-archive export:
recordedflag, the validated CHECK helper, the docstrings) are met.original_valueis written on every insert path.Interaction with #222: see P2-1. The frontend treats null and
""the same everywhere, so the UI doesn't change.New findings
get_first_flow_epoch_async(occupancy_repository.py around 2297) takes an unfilteredMIN(timestamp_epoch)across all cameras. The default range skips the 10-year guard, so one event with a bad camera clock (from 1970, say) makes the admin default stamp write tens of thousands of days in one transaction. Keep a sanity bound, or ignore implausible epochs. PLAUSIBLE.#171 pass-1 fixes are pushed at
e0d10d2and merged into #172 atd8f3c48. Approval now calls OccupancyRepository.stamp_business_day_schedule_conn_async, extracted from the normal stamp writer, inside its atomic publication transaction. Only purely imported days without lived records are written; the helper protects non-IMPORTED existing records. When original_value lands, this shared connection-level helper is the approval seam that must explicitly fix original_value. #228 is still open at this handoff; the coordination remains recorded in #171 body/reply.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.