Skip to content

fix(readiness): freeze the morning headline once the overnight is complete#132

Merged
abdulsaheel merged 1 commit into
OpenStrap:mainfrom
dannymcc:fix/readiness-daytime-freeze
Jul 22, 2026
Merged

fix(readiness): freeze the morning headline once the overnight is complete#132
abdulsaheel merged 1 commit into
OpenStrap:mainfrom
dannymcc:fix/readiness-daytime-freeze

Conversation

@dannymcc

@dannymcc dannymcc commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem (#128, user RR)

Readiness DRIFTS during the day — "morning it was 49, now 45." It's meant to be a stable morning score.

A day stays recomputable for ~48h (_finalizationSec) and selectLightDeriveDays re-targets today on every BLE drain; each derive OVERWRITES the persisted readiness scalar. As the night's overnight flash finishes draining and the trailing 28-day baseline shifts, the value legitimately moves.

PR #121's TodayData.settledReadinessScore only withheld the number while the overnight was still building. Once overnight_state == 'ready' it passed the latest re-derived value straight through, so it could not stop a ready→ready drift.

Fix (Option A — freeze the headline)

Once today's overnight is genuinely COMPLETE, pin the first settled readiness and surface that pinned value for the two headline surfaces — the orbit hero (today_screen) and the once-a-morning recovery story. Later re-derives keep updating the full day_result, baselines, trends and finalisation exactly as before; only the headline number is pinned.

"Complete" ≠ overnight_state == 'ready'

ready flips as soon as the first sleep-bearing row lands (mid-drain), so pinning on it could freeze a partial-night value. Instead the freeze trigger requires the drained data edge to have moved a margin past the sleep offset (wake):

overnightComplete = hasSleep && dataNowSec >= sleepOffsetSec + _headlineFreezeMarginSec  // 60 min

This is the same "edge past the window" model the engine already uses for finalisation (day.endSec + 48h < dataNowSec), anchored at wake+margin instead of wake+48h: the whole sleep window is then decoded and the segmentation-placed wake is settled, so the overnight inputs are final. Below that margin we might still be looking at a partial night, so we do not pin.

Persistence + reset

The pin is a day-tagged cursor (frozen_headline = {day, value}) via the existing sync_cursor store — survives restarts; a new day's first complete overnight overwrites it, and getToday only surfaces a pin whose day matches today.

Same-day improvement → does NOT move the headline

Default and shipped behaviour: once pinned, a later same-day re-derive that would RAISE the score is ignored, same as one that lowers it. Stability is the whole point. Flagged as a product call for RR.

Files

  • lib/compute/derivation_engine.dart_headlineFreezeMarginSec, the pure nextFrozenHeadline decision (@visibleForTesting), and _maybeFreezeHeadlineReadiness called at the end of _derivePreparedDay (writes the pin on the first complete-overnight settle for today; rescan/baseline re-derives hit the "already pinned" guard and hold).
  • lib/data/db.dartfrozenHeadline() / setFrozenHeadline() cursor accessors.
  • lib/data/local_repository_impl.dartgetToday surfaces the pin as the readiness/recovery headline metric when it belongs to today's own overnight (overnight_state == 'ready', matching day). Both the hero and the story read it via settledReadinessScore.
  • test/readiness_freeze_test.dart — ready→ready with a changing value → headline holds; a new day resets; a partial night settles to the complete-night value.

Unverified without a device

No Flutter/Dart SDK or hardware in this environment — verified by inspection + the pure-function unit test only. On-device confirmation needed that: the freeze fires in the morning (the data edge reaches wake+60min on a normal post-wake sync), the pinned hero/story stay put across the day while trends/baselines keep moving, and the pin resets cleanly the next morning. The 60-minute margin is the tunable conservatism knob if the freeze proves too early/late in the field.

Summary by CodeRabbit

  • New Features

    • Today’s morning readiness headline now stays consistent after the overnight sleep is fully complete.
    • Underlying readiness calculations continue updating without changing the displayed headline.
    • Headlines reset appropriately for each new day and update once the new overnight is complete.
  • Bug Fixes

    • Prevented readiness headlines from drifting during the day or carrying over from a previous day.
  • Tests

    • Added coverage for incomplete nights, missing readiness values, day transitions, and recalculations.

Closes #128

…plete

Readiness is meant to be a stable morning score, but a day stays
recomputable for ~48h and every re-derive overwrites the persisted
readiness scalar. As the night's flash finishes draining and the
trailing 28-day baseline shifts, the surfaced value legitimately drifts
through the day (OpenStrap#128: "morning it was 49, now 45").

PR OpenStrap#121's `settledReadinessScore` only WITHHELD readiness while the
overnight was still building; once `overnight_state == 'ready'` it passed
the latest re-derived value straight through, so it could not stop a
ready→ready drift.

Freeze the headline instead: once today's overnight is genuinely COMPLETE
— the drained data edge has moved a margin PAST the sleep offset (wake),
distinct from `ready` which flips on the first sleep-bearing row mid-drain
— pin the first such readiness and surface it for the Today hero and the
once-a-morning recovery story. Later re-derives keep updating the full
day_result, baselines, trends and finalisation; only the headline number
is pinned. Day-tagged so it survives restarts and resets on a new day.

The freeze decision (`nextFrozenHeadline`) is a pure function covered by
a ready→ready + new-day unit test.

Closes OpenStrap#128
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change freezes Today’s morning readiness headline after genuine overnight completion, stores the pinned day and value in LocalDb, applies matching pins in getToday(), and tests completion, day-transition, null-value, and same-day stability behavior.

Changes

Readiness headline freezing

Layer / File(s) Summary
Frozen headline storage
lib/data/db.dart
Adds JSON-backed LocalDb cursor APIs for reading, validating, and writing the pinned headline.
Completion-time headline pinning
lib/compute/derivation_engine.dart, test/readiness_freeze_test.dart
Adds completion-margin checks and nextFrozenHeadline, persists only necessary same-day pins, and tests completion and re-derivation semantics.
Today headline consumption
lib/data/local_repository_impl.dart
Uses a matching frozen headline for Today when the overnight state is ready, otherwise retaining derived readiness.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DerivationEngine
  participant LocalDb
  participant LocalRepository
  DerivationEngine->>LocalDb: Read frozen headline after derived-day persistence
  DerivationEngine->>DerivationEngine: Pin completed current-day readiness
  DerivationEngine->>LocalDb: Write changed day/value pin
  LocalRepository->>LocalDb: Read frozen headline for Today
  LocalDb-->>LocalRepository: Return matching pinned value
Loading

Possibly related issues

  • Issue 128 — Directly concerns freezing Today’s morning readiness headline after genuine overnight completion.

Suggested reviewers: abdulsaheel

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: freezing the morning readiness headline once overnight data is complete.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@abdulsaheel
abdulsaheel merged commit bae8f23 into OpenStrap:main Jul 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Readiness score drifts during the day (ready→ready recompute)

2 participants