Forecast snapshots lose their lineage, so a retro-logged period teaches the app nothing #46
Labels
No Label
P0
P1
P2
release-blocker
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: null/Privacy-Period-Tracker#46
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
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?
Found by tracing the confirm-score-feed-back loop end to end at
93ec5b7.What is true now.
CycleRepository.snapshotForecaststampsclock.instant()on every replacement snapshot. EveryrecordNotYet,editPeriodanddeletePeriodtherefore moves the standing forecast'sgeneratedAtforward.scoreOutstandingmeasures its backfill guard from that date, so a period logged even one day retroactively after any of those actions trips the guard, is not scored, and is then deleted unscored by thesnapshotForecastcall that follows — the app collects no error sample from precisely the cycle it got wrong. Two related faults in the same method: theengine.predict(...) ?: returnearly-return skipsdeleteUnscored(), so deleting the last period leaves a stale unscored snapshot that a later confirm scores against an unrelated fresh history; andscoreOutstanding's KDoc claims a backfill-skipped forecast 'survives untouched', which the next statement contradicts.What it costs. Silent, plausible-looking accuracy figures with missing samples — the failure mode §16 is least able to notice, because nothing looks broken.
What to do. Treat the standing unscored snapshot as a lineage: read
unscored().firstOrNull()?.generatedAtbefore deleting, movedeleteUnscored()above the engine call, and insert the replacement with the inherited origin (?: clock.instant()). Rewrite the two comments to match.Traps. Simpler predicates ('score if this is the new latest start') cannot be told apart from onboarding's ascending backfill at a fixed clock and break the existing
backfilled history does not fabricate accuracy figurestest — that test is the tripwire; if a change reddens it, the change is wrong, not the test. Accepted trade-off to document: a retro-logged start scores the final revision of the forecast, so a user's own contradicting 'Not yet' inflates the error by at most that contradiction.Verify:
a period logged retroactively still scores the forecast the user was shownanddeleting the last period retires the standing forecastin core/data/src/test/kotlin/dev/privacyllc/period/core/data/CycleRepositoryTest.kt — both red before the fix, green after.