Herstel eerst. Daarna pas de training.
Freshness in WattFlow is een uitlegbare herstelscore op basis van Apple Health. De score kijkt naar slaap, rustige HRV, rusthartslag, ademhaling, temperatuur en recente belasting, zet die vast op een logisch ochtendmoment en gebruikt het resultaat daarna om actieve plantrainingen gecontroleerd lichter of rustiger te maken.
1. Kort samengevat
De huidige Freshness-engine is geen generieke “wellness score”, maar een trainingsgerichte readinesslaag. Alles loopt via één centrale flow: Apple Health lezen, ankerdag bepalen, score berekenen, snapshot opslaan en diezelfde score gebruiken in UI, widget en planadvies.
2. Welke data Freshness leest
Freshness wordt alleen berekend als Apple Health-sync aan staat en de Health-autorisatie echt verleend is. Zo niet, dan maakt de app de opgeslagen snapshots leeg en laat de planlaag geen herstelgestuurde aanpassingen zien.
2.1 Apple Health inputs
| Input | Waarvoor gebruikt | Belangrijk detail |
|---|---|---|
| HRV (SDNN) | Herstel en readiness | Niet elk HRV-sample telt mee; selectie gebeurt op rust, slaap en ochtendcontext. |
| Rusthartslag | Herstel en illness-signalen | Mediaan binnen het recovery-observatievenster. |
| Slaapduur | Morning readiness | Komt uit een gemergde hoofd-slaapperiode, niet uit een rollend 24-uurs totaal. |
| Ademhaling tijdens slaap | Illness- en herstelcontext | Wordt alleen gelezen op slaapintervallen. |
| Polstemperatuur-afwijking | Illness-signalen | Alleen positieve afwijkingen duwen de score omlaag. |
| Active Energy en stappen | Carry-over load en intraday load | Worden in twee periodes opgesplitst: voor slaap en sinds ontwaken. |
| Workouts | Loadclassificatie | Een workout geldt als duidelijke belasting bij >= 30 min of >= 250 kcal. |
2.2 Slaap als anker
De engine bouwt geen analyse uit “nu minus 24 uur”, maar vanuit een anker. Dat anker is bij voorkeur de laatste hoofd-slaap, zodat de ochtendscore voelt als een echte herstelscore voor vandaag.
- Alle
asleep-categorieën uit Apple Health worden eerst samengevoegd. - Blokken met een tussenruimte tot 30 minuten worden samengevoegd.
- Een hoofd-slaapblok wint als het minstens 3 uur duurt en eindigt tussen 03:00 en 12:00.
- Als dat niet lukt, gebruikt WattFlow een fallback-slaapblok van minstens 2 uur.
- Als ook dat niet lukt, valt de engine terug op een inferrede ochtend-anker rond 07:00.
- Carry-over period: vorige wakkere periode tot aan de anker-slaap of ankerdatum.
- Current-day period: vanaf ankerdatum tot nu of tot de volgende slaapstart.
- Recovery interval: vanaf slaapstart of 3 uur voor ankerdatum tot maximaal 4 uur na ankerdatum.
- Daardoor kan belasting van gisteren wel meetellen, maar niet op dezelfde manier als frisse ochtenddata.
2.3 HRV-selectie
HRV is de gevoeligste input in de hele engine. Daarom worden workouts rondom HRV uitgefilterd en kiest WattFlow niet blind voor “de laatste sample”, maar voor de meest bruikbare rustige subset.
| Stap | Regel |
|---|---|
| Validatie | Een HRV-sample is ongeldig als het tijdens een workout valt, of binnen 90 minuten na het workout-einde. |
| Subset 1 | sleepHRVSamples: HRV die overlap heeft met de anker-slaap. |
| Subset 2 | morningHRVSamples: HRV tussen ankerdatum en maximaal 3 uur later. |
| Subset 3 | calmHRVSamples: rustige samples buiten slaap en buiten morning-subset. |
| Keuzevolgorde | Slaap met niet-low quality, anders calm met niet-low quality, anders morning, anders slaap, anders calm, anders alle geldige HRV. |
| Aggregatie | De representatieve HRV van de dag is de mediaan van de geselecteerde HRV-samples. |
HRV quality
- 0 samples =
none - 1 sample =
low - 2 samples =
medium - 3+ samples =
high
Wat dit praktisch betekent
Eén losse ochtendwaarde mag meedoen, maar telt lichter. Pas bij meerdere rustige samples wordt HRV stevig genoeg om echt een lock te dragen en de volledige penalty of steun mee te nemen.
3. Zo wordt de score opgebouwd
De uiteindelijke Freshness-score komt uit een vaste keten: baselines bouwen, statussen bepalen, penalties optellen, confidence toepassen, illness-cap toepassen, morning lock beslissen en pas daarna eventueel intraday bijsturen.
3.1 Baselines en ratios
| Metric | Baselinevenster | Aggregator | Ratio |
|---|---|---|---|
| HRV | Laatste 28 geldige dagen | Mediaan | today / baseline |
| Rusthartslag | Laatste 28 dagen | Mediaan | today / baseline |
| Slaapduur | Laatste 14 dagen | Gemiddelde | today / baseline |
| Ademhaling | Laatste 14 dagen | Mediaan | today / baseline |
| Active energy | Laatste 7 dagen | Gemiddelde | today / baseline |
| Stappen | Laatste 7 dagen | Gemiddelde | today / baseline |
3.2 Statusregels en penalties
| Metric | Normal | Deviated | Strong deviation | Penalty |
|---|---|---|---|---|
| HRV | ratio >= 0.95 | 0.85 t/m 0.94 | < 0.85 | 12 / 24, daarna vermenigvuldigd met sample-factor |
| Rusthartslag | ratio <= 1.03 | 1.03 t/m 1.08 | > 1.08 | 10 / 20 |
| Slaap | ratio >= 0.90 | 0.75 t/m 0.89 | < 0.75 | 10 / 20 |
| Ademhaling | ratio <= 1.05 | 1.05 t/m 1.12 | > 1.12 | Geen directe scorestraf, wel illness-signaal |
| Temperatuur | < 0.3 °C afwijking | 0.3 t/m 0.49 °C | >= 0.5 °C | Geen directe scorestraf, wel illness-signaal |
- 0 samples -> factor
0.0 - 1 sample -> factor
0.5 - 2 samples -> factor
0.75 - 3+ samples -> factor
1.0
- Carry-over high bij clear workout, energy-ratio > 1.35 of steps-ratio > 1.40
- Carry-over medium bij energy-ratio > 1.10 of steps-ratio > 1.10
- Current-day gebruikt een tijdgeschaalde baseline; voor 09:00 wordt high nog gematigd naar medium
Combo-penalties
- HRV
strongDeviation+ rusthartslagstrongDeviation-> +10 - Slaap
strongDeviation+ carry-over loadhigh-> +8
Carry-over recovery factor
- HRV normaal én slaap normaal -> factor 0.5
- Eén van beide normaal -> factor 0.75
- Beide niet normaal -> factor 1.0
3.3 Confidence en illness cap
Freshness straft niet onbeperkt door als data ontbreekt. In plaats daarvan schuift de ochtendscore bij lage zekerheid terug richting een neutrale referentie van 85.
| Confidence-penalty | Waarde |
|---|---|
| Geen HRV | 0.2 |
| Geen rusthartslag | 0.2 |
| Geen slaap | 0.2 |
| Geen ademhaling of temperatuur | 0.1 |
| Onvoldoende historie | 0.2 |
confidence = clamp(1.0 - totalConfidencePenalty, 0.0, 1.0)
rawBeforeIllnessCap = clamp(100 - totalMorningPenaltyBeforeIllnessCap)
rawFreshness = applyIllnessCap(rawBeforeIllnessCap, illness)
adjustedMorningReadiness =
applyIllnessCap(
clamp(85 + (rawFreshness - 85) * confidence),
illness
)
Illness detectie
- Likely als ademhaling sterk afwijkend is en temperatuur afwijkt.
- Likely ook bij ademhaling afwijkend + temperatuur afwijkend + rusthartslag sterk afwijkend.
- Possible als minstens twee flags tegelijk aan staan.
Illness caps
- none -> geen cap
- possible -> score max 60
- likely -> score max 35
3.4 Morning lock en intraday
WattFlow laat de score niet de hele ochtend wild bewegen. Eerst probeert de engine te bepalen of er al genoeg signalen zijn om de ochtendscore vast te zetten. Pas daarna mag de dagbelasting nog gecontroleerd doorwerken.
- Als morning-HRV niet echt verwacht wordt, lockt de score zodra ankercontext en kernsignalen aanwezig zijn.
- Als morning-HRV wel verwacht wordt, lockt de score zodra HRV-quality minstens
mediumis. - Vanaf 09:00 lockt de score ook met ankercontext + kernsignalen.
- Vanaf 11:00 volgt een harde fallback-lock, ook als het ochtendbeeld nog niet perfect compleet is.
- Current-day load geeft na lock een intraday-aanpassing van 0, -4 of -9.
- Verschillen tot en met 2 punten worden genegeerd.
- Maximaal 2 echte scorewijzigingen per dag.
- Elke intraday sprong wordt begrensd op 10 punten per stap.
if locked == false:
morningReadinessScore = clamp(85 + (adjustedMorningReadiness - 85) * 0.5)
else:
morningReadinessScore = adjustedMorningReadiness
candidateFreshness = applyIllnessCap(
clamp(morningReadinessScore + intradayAdjustment(currentDayLoad)),
illness
)
4. Wat de score in de app doet
De score blijft niet in een debuglaag hangen. Ze komt direct terug in de plan-tab, de Freshness-detailview, de widgetsnapshot en vooral in het voorbereiden van de volgende plansessie.
4.1 DailyState en UI
DailyState bevat onder meer
freshnessillnessgeneralLoadscorePhaseconfidenceshortReasonendetailedReasonmorningReadinessScoreenmorningLockedAt
Publieke afleiding
- RecoveryStatus good bij 70 tot 100
- moderate bij 40 tot 69
- low onder 40
- Widgettekst wordt overruled door illness-signalen: “Mogelijk ziek” of “Rust of herstel”.
4.2 Plan-aanpassingen
Elke geplande workout loopt via FreshnessTrainingAdvisor.prepareWorkout(...). Daardoor kan een sessie
ongewijzigd blijven, lichter worden of vervangen worden door een herstelrit.
| Voorwaarde | Actie | Intensity factor | Duration factor |
|---|---|---|---|
illness == likely |
Replace | 0.65 | 0.60 |
illness == possible + intensieve workout |
Replace | 0.72 | 0.70 |
generalLoad == high + intensieve workout |
Replace | 0.75 | 0.75 |
freshness < 40 + intensieve workout |
Replace | 0.72 | 0.65 tot 0.85, afhankelijk van score |
freshness < 40 + rustige workout |
Reduce | 0.90 | 0.65 tot 0.85 |
freshness 60-69 |
Reduce | 0.97 | 0.95 |
freshness 50-59 |
Reduce | 0.95 | 0.90 |
freshness 40-49 |
Reduce | 0.92 | 0.85 |
vo2max, omslagpunt
en test als blokken die de engine liever vervangt dan slechts licht terugschaaft.
4.3 Freshness prompt
Naast de deterministische advisor kan WattFlow ook een compacte freshness-prompt opbouwen. Die prompt is veel lichter dan het volledige AI-plancontract en is bedoeld om een externe AI kort context te geven over herstel, belasting en aankomende sessies.
User freshness: 64
Illness: possible
General load: medium
Confidence: 0.70
Upcoming workouts:
tuesday: Basis duur
thursday: Omslag bouwen
saturday: Lange duur
Goal:
FTP verhogen
Event days:
20 jun 2026
Request:
Adjust plan, keep structure, reduce overload.
5. Relevante bronbestanden
| Bestand | Rol |
|---|---|
WattFlow/Services/AppleHealthService.swift |
Leest Health-data, bouwt anchors, splitst carry-over en current-day periodes, selecteert HRV. |
WattFlow/Services/FreshnessCalculator.swift |
De hoofdengine: baselines, statusregels, penalties, confidence, illness cap, morning lock en intraday clamp. |
WattFlow/Domain/FreshnessModels.swift |
Publieke modellen zoals DailyState, snapshots, breakdowns en load/illness enums. |
WattFlow/Services/FreshnessTrainingAdvisor.swift |
Past planworkouts aan op basis van Freshness en beslist keep / reduce / replace. |
WattFlow/Services/FreshnessPromptGenerator.swift |
Bouwt een compacte herstelprompt voor externe AI. |
WattFlow/Views/FreshnessDetailView.swift |
Detailweergave met score-ring, metric cards, charts en penalty-uitleg. |
WattFlow/AppModel.swift |
Refresh-orchestratie, snapshot-opslag en koppeling naar de planlaag. |
Deze freshness-gids is afgestemd op de huidige Freshness-engine, planadvisor en Health-flow zoals die in de code staan op 3 april 2026.