Repository navigation
Resilience (1/2): per-person recovery-time engine (Experimental) - #15
Merged
Merged
Conversation
Fits A·exp(−k/τ) to the autocorrelation of a signal's daily fluctuations (weekday pattern and linear trend removed jointly) over a trailing 180-day window, and corrects the fit's bias and sets its 90 % interval by inverting a seeded parametric bootstrap. Also finds knocks (excursions from a rolling median baseline) and times their return. Experimental; nothing reads it into another score.
…acle A 480-run -O study over OU series with known tau (7/14/28/56 days, weekly structure, nugget, 20 % missing) plus white-noise, trend and weekly-only controls. The test reads coverage from the pinned file and re-runs a spread of its cases live.
So app-side tests can build a readout without running the engine.
…s its path A flat return fit pinned at the 60-day search ceiling printed a 41.6-day half-life beside a return observed in two days. The half-life now needs the fitted curve to explain at least half of the path and a decay constant off the search bounds; the pinned oracle loses exactly that one ceiling half-life.
…n reads Reads repo.dailyMetrics once, runs ResilienceEngine off the main actor for steps (ending yesterday), resting HR and HRV, and computes the rolling trend on demand with the headline result as its newest point.
…method sheet Hero with the recovery time and its 90 % range on a log band beside the cohort ticks, three signal chips (steps published, RHR and HRV labelled as extensions), and a detail screen with the measured autocorrelation and its fit, the last three knocks, the 12-month trend with its range, and the day-to-day swing. Shown only when the Settings toggle is on.
Resilience (2/2): Experimental section in the Healthspan tab
…coverage docs Knock was the one Resilience result type left without a public init, so the app layer could read knocks but not build them. daysToBaseline counts calendar days since the peak, not observed days. The header's coverage range (85-92 %) excluded the pinned tau = 56 result (93) and named the wrong test file.
The detail sheet loaded its trend once, on appear. A refresh while the sheet was open handed down a new readout, but the trend kept ending on the old headline, so the chart's last point and the hero could disagree. Key the load on the readout, which is now Equatable, and clear the stale trend first.
Day indices come from the local calendar date, but the knock and trend dates were built at noon UTC, which is already the next day from UTC+12 onwards: a knock peaking on 5 Mar read 6 Mar in New Zealand. Build the date at local noon on the day's calendar date, and pin it across zones from UTC-12 to UTC+14.
The line said nothing moved the signal more than 2 SD for two days running, but a knock also needs the next reading after its peak to fall within 3 days and still sit at least 1 SD out. A gap in wear could reject two consecutive days past 2 SD while the screen claimed there were none. Update all locales.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 1 of 2 for the Resilience plan (
docs/superpowers/plans/2026-09-23-resilience.md). This PR adds the engine only. The Experimental UI is stacked on top of it.What changes
ResilienceEngine(Packages/StrandAnalytics, pure) is a per-person version of the recovery time in Pyrkov et al., Nat Commun 2021. It is Experimental, and no score reads it.Verification
This follows the AGENTS.md rule for signals derived from raw data: the engine must recover several injected values, not match one.
oracles/resilience.jsoncomes from the-OtwinTools/oracle-twins/resilience_twin.swift, which runs 480 analyses:ResilienceOracleTestsreads the coverage from the pinned file. It also re-runs the first seed of every group, plus one fully pinned run (the ACF and the knocks, from a series stored in the file), and requires a match to 1e-9. That shows the file came from this engine. A debug build cannot afford all 480 runs: each estimate simulates about 2,000 series.ResilienceEngineTests) cover:swift testinPackages/StrandAnalytics: 2,129 tests, 0 failures. The Resilience tests take about 15 s in a debug build.doc_comment_lintis clean.No app-target, BLE, schema or UI-copy changes.