Skip to content

docs: document DaytimeStress, StressIndex, HRVFreqDomain, StressOnsetDetector - #411

Open
vishk23 wants to merge 3 commits into
ryanbr:mainfrom
vishk23:docs-daytime-stress
Open

docs: document DaytimeStress, StressIndex, HRVFreqDomain, StressOnsetDetector#411
vishk23 wants to merge 3 commits into
ryanbr:mainfrom
vishk23:docs-daytime-stress

Conversation

@vishk23

@vishk23 vishk23 commented Jul 14, 2026

Copy link
Copy Markdown

Summary

docs/ANALYTICS.md's "What is actually wired into the app" section omitted the Stress screen's live analytics entirely: DaytimeStress, StressIndex, HRVFreqDomain, and StressOnsetDetector had zero mention despite all being wired and live.

  • DaytimeStress (Packages/StrandAnalytics/Sources/StrandAnalytics/DaytimeStress.swift) — buckets waking hours (06:00-22:00) from the day's hrSample + rrInterval streams, z-scores hourly HR-elevation / RMSSD-suppression against the day's own calm hours (not a personal baseline), and squashes onto the same 0-3 logistic the daily Stress score uses. Day-relative today; documented as such without promising a future baseline-relative mode.
  • StressIndex (Baevsky Stress Index) and HRVFreqDomain (LF/HF) — two additive, on-demand HRV lenses, purely informational (don't feed the 0-3 score).
  • StressOnsetDetector — the L3 edge-triggered, exercise-gated stress check-in, opt-in and default-off.

Wired call sites confirmed by reading source

  • DaytimeStress.analyze(hr:rr:tzOffsetSeconds:) <- Strand/Screens/StressView.swift loadDaytime(), rendering the "Today's Timeline" card (DaytimeLoadLine, StressTotalsBar) and the sustained-high Breathe-suggestion card. Reachable on iOS via More -> Stress (StrandiOS/App/RootTabView.swift, .stress -> StressView()).
  • StressIndex.components(rr:) / StressIndex.stressIndex(rr:) <- StressView.loadDaytime() (Advanced HRV card) and Strand/AI/AICoach.swift's stressIndexLine() (folded into the AI Coach context, consent-gated).
  • HRVFreqDomain.freqDomain(rr:) <- the same StressView.loadDaytime() call as StressIndex — found wired-but-undocumented alongside it during this pass (not explicitly asked for, flagging it since it shares the exact call site).
  • StressOnsetDetector.evaluate(...) <- Strand/App/AppModel.swift's evaluateStress(), gated behind a default-off "stress check-ins" toggle (BiofeedbackPrefs.stressConfig()), state persisted via BiofeedbackPrefs, surfaced via StressCheckInCard/StressNudgeCenter.

Test plan

  • Read docs/ANALYTICS.md, DaytimeStress.swift, StressView.swift, StressIndex.swift, StressOnsetDetector.swift, RootTabView.swift, AppModel.swift, AICoach.swift, BiofeedbackPrefs.swift, StressCheckInCard.swift in full.
  • Grepped every call site of DaytimeStress., StressIndex., HRVFreqDomain., StressOnsetDetector. across Strand, StrandiOS, Packages to confirm nothing else was missed and nothing claimed here is speculative.
  • Docs-only change; no source touched.

vishk23 added 3 commits July 13, 2026 19:49
…Detector

The "What is actually wired into the app" inventory omitted the Stress
screen's live analytics entirely. DaytimeStress (intraday hourly stress
timeline), StressIndex + HRVFreqDomain (additive HRV lenses), and
StressOnsetDetector (opt-in L3 stress check-in) are all wired and live,
but had zero mention in the doc.

Confirmed each call site by reading the source:
- DaytimeStress.analyze(...) is called from StressView.loadDaytime(),
  rendering the "Today's Timeline" card and sustained-high Breathe
  suggestion. Reachable on iOS via More -> Stress (RootTabView.swift).
- StressIndex.components(...) / stressIndex(...) is called from both
  StressView.loadDaytime() (Advanced HRV card) and AICoach.swift's
  stressIndexLine() (AI Coach context).
- HRVFreqDomain.freqDomain(...) is called from the same
  StressView.loadDaytime() as StressIndex, found undocumented during
  this pass.
- StressOnsetDetector.evaluate(...) is called from AppModel's
  evaluateStress(), gated behind a default-off "stress check-ins"
  toggle, surfaced via StressCheckInCard/StressNudgeCenter.

Documents DaytimeStress as day-relative (scored against the day's own
waking-hour calm reference), not yet baseline-relative like the daily
Stress score.
…alidated

Clean merge — the 3-way correctly kept upstream's later edits to the two
regions this branch originally rewrote, so the stale `HRVAnalyzer` table row
and the "RMSSD for the stress nudge" section (documenting an `AppModel.rmssd`
and a `behavior.stressNudge` toggle upstream has since deleted) dropped out on
their own. What survives is the still-missing part: the four engine table rows
and the Stress-analytics section.

One correction to this branch's own text, which the same upstream work
invalidated: `StressOnsetDetector` no longer runs "alongside (not replacing)
the legacy inline nudge" — `AppModel.evaluateStress()` is now the detector's
only caller and the legacy inline nudge is gone. Re-verified every other claim
in the section against current `main`: `StressView.loadDaytime()` still calls
`DaytimeStress.analyze` / `StressIndex.components` / `HRVFreqDomain.freqDomain`,
`AICoach.stressIndexLine()` still exists, and `BiofeedbackPrefs.stressConfig()`
still carries the `enabled` + `autoNudge` gates.

Docs-only; no code touched.
@vishk23

vishk23 commented Jul 27, 2026

Copy link
Copy Markdown
Author

Merged current main (297 commits) and re-verified every claim against the code as it stands today — worth doing, because part of this PR had gone stale.

What dropped out on its own. The 3-way merge kept upstream's later edits to the two regions this branch originally rewrote, so two now-wrong pieces disappeared without my touching them:

  • the HRVAnalyzer table-row edit (it claimed the app computes RMSSD inline via AppModel.rmssd(_:); upstream's row — "Live stress check-ins use the dedicated StressOnsetDetector" — is the correct one and survives);
  • the whole "RMSSD for the stress nudge" section, which documented an AppModel.rmssd and a behavior.stressNudge toggle that no longer exist.

One correction I made by hand. This branch's own text said StressOnsetDetector runs "alongside (not replacing) the legacy inline nudge". That is no longer true — AppModel.evaluateStress() is now the detector's only caller and the legacy inline nudge is gone. Reworded, and the gate list corrected to cfg.enabled + autoNudge + live.bonded + live.worn.

Re-verified as still accurate on current main: StressView.loadDaytime() still calls DaytimeStress.analyze(hr:rr:tzOffsetSeconds:), StressIndex.components(rr:) and HRVFreqDomain.freqDomain(rr:); AICoach.stressIndexLine() still exists; BiofeedbackPrefs.stressConfig() still carries the two default-off toggles. All four engine files are still present and still undocumented in ANALYTICS.md, so the gap this PR fills is real.

Docs-only, 1 file. Two notes for whoever lands it:

  • The ### 1. / ### 2. / ### 4. numbering gap in "Live analytics in AppModel" is pre-existing on main (left by the removal of the old section 3) — deliberately not touched here, one concern per PR.
  • If Daytime stress: score against a personal cross-day baseline, not the day's own calm hours #463 lands first, the DaytimeStress row's "day-relative — not the personal baseline" wording becomes stale, since that PR makes exactly that change. Cheapest order is docs last, or I can fold the update in.

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.

1 participant