Herstel eerst. Daarna pas de training.
Freshness is in de app nog steeds de zichtbare naam, maar onder water werkt WattFlow nu met een slaap-geankerde readiness-architectuur. De engine bouwt eerst een morning readiness op uit slaap, herstel en carry-over van gisteren, laat daarna alleen begrensde intraday bijsturing toe en gebruikt die ene uitkomst vervolgens in UI, widget, planadvies en AI-context. Daar bovenop zit nu ook een lichte subjectieve laag: check-ins en feedback helpen de score menselijker en persoonlijker te maken, zonder dat het systeem een black box wordt.
1. Kort samengevat
De huidige Freshness-engine is geen generieke wellnessscore en ook geen medisch model. Het is een trainingsgerichte readinesslaag: eerst bepalen hoe hersteld je ochtend eruitziet, daarna voorzichtig volgen wat er overdag nog verandert. Alles loopt via één centrale flow: Apple Health lezen, anker-slaap kiezen, morning readiness berekenen, day score bijstellen, snapshot opslaan en diezelfde uitkomst gebruiken in UI, widget, training advisor en promptgenerator.
WattFlow wil niet alleen een training starten, maar ook context geven voor vandaag. Daarom leeft Freshness nu niet verstopt in een experimenteel hoekje, maar zichtbaar in Vandaag, Schema en Meer. De score moet helpen om beter te doseren, niet om meer dashboards te bouwen.
Slaap, HRV en rusthartslag zien veel, maar niet alles. Spierpijn, mentale stress, zware benen of een verrassend slechte nachtbeleving zijn precies het soort context dat sensoren missen. Daarom laat WattFlow je check-in meetellen, maar bewust licht en ondersteunend.
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 uit Apple Health | Autonomic recovery | In de huidige overgangslaag gebruikt WattFlow een log-getransformeerde HRV proxy als opstap naar readiness-logica rond RMSSD / lnRMSSD. |
| Rusthartslag | Autonomic recovery en caution | Vergeleken met je persoonlijke baseline, niet met populatiegrenzen. |
| Slaapduur, fragmentatie, consistentie, diepe slaap | Sleep component | De slaapcomponent is breder dan alleen duur en is altijd gekoppeld aan de laatste hoofd-slaap. |
| Ademhaling tijdens slaap | Caution-signaal | Wordt aanvullend gebruikt, niet als hoofdscore. |
| Polstemperatuur tijdens slaap | Caution-signaal | Alleen met voldoende nachten eigen historie; tot die tijd neutraal. |
| Workouts, active energy en stappen | Load context | Workouts zijn leidend; gewone dagelijkse activiteit is vooral ondersteunende context. |
| Subjectieve check-in | Lichte modifier | Vermoeidheid, spierpijn, stress en slaapkwaliteit sturen de score beperkt bij. |
| Trainingsfeedback | Persoonlijke calibratie | “Te hoog / klopt / te laag” en “lichter / zoals verwacht / zwaarder” helpen later bij het afstemmen van bias en betrouwbaarheid. |
| Dutjes | Day score-context | Een geldig dutje kan een beperkte bonus geven, maar nooit boven de morning readiness uitkomen. |
| Feedback en historie | Personalisatie | Feedback stuurt geen black-box model, maar wel begrensde persoonlijke calibratie binnen vaste regels. |
2.2 Slaap als anker
De engine bouwt geen analyse meer uit “nu minus 24 uur”, maar vanuit een anker. Dat anker is bij voorkeur de laatste hoofd-slaap, zodat de ochtendscore echt voelt als herstel voor vandaag in plaats van een willekeurig tijdvenster.
- 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 hersteldata uit slaap en rustige ochtend.
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. In de huidige implementatie is dit nog een readiness-proxy boven op Apple Health HRV-data, met als expliciete richting een RMSSD / lnRMSSD-gedachte in plaats van absolute SDNN-drempels.
| 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 als overtuigend recovery-signaal mee te tellen.
2.4 Check-ins, feedback en opslag
Freshness leest dus niet alleen Apple Health, maar ook een kleine lokale gebruikerslaag. Die laag is bewust simpel: niet bedoeld als dagboek, maar als corrigerende trainingscontext. Alles wordt lokaal in de gedeelde appgroep opgeslagen, zodat dezelfde state ook voor widgets en andere appdelen beschikbaar blijft.
| Onderdeel | Hoe het werkt | Waarom zo |
|---|---|---|
| Check-in | Één check-in per analysedag. Een nieuwe save overschrijft de vorige voor dezelfde dag. | Freshness heeft dagcontext nodig, geen stapel losse meningen op dezelfde ochtend. |
| Feedback | Feedbackrecords worden op analysedag samengevoegd, zodat scorefeedback en trainingsgevoel samen één dagbeeld vormen. | De app wil leren van de dag, niet van elk los knopje als aparte waarheid. |
| Bewaarlimieten | Check-ins: 30 recente dagen. Feedback: 60 recente dagen. | Genoeg voor trends en personalisatie, zonder eindeloze opslag of oude ruis. |
| Opslag | FreshnessUserSignalsStore in app group group.app.ergtrainer.shared. |
Widgets en app gebruiken dan dezelfde persoonlijke Freshness-context. |
| Refreshgedrag | Na een check-in of feedback laat AppModel de Freshness-engine direct opnieuw lopen. |
De gebruiker moet de bijgestelde context meteen terugzien. |
3. Zo wordt de score opgebouwd
De uiteindelijke Freshness-score komt uit een vaste keten: persoonlijke baselines opbouwen, componenten scoren, caution en confidence toepassen, morning readiness vastzetten en daarna alleen begrensd intraday bijsturen.
3.1 Baselines en ratios
| Metric | Baselinevenster | Aggregator | Extra context |
|---|---|---|---|
| HRV proxy | Laatste 28 geldige dagen | Mediaan + rolling mean | WattFlow bewaart ook spreiding en HRV CV om stabiliteit en ruis mee te nemen. |
| Rusthartslag | Laatste 28 dagen | Mediaan | Wordt als recovery-indicator altijd tegen je eigen normaal afgezet. |
| Slaap | Laatste 14 dagen | Gemiddelde / mediaan per onderdeel | Duur, fragmentatie, midpoint-consistentie en diepe slaap hebben ieder hun eigen referentie. |
| Ademhaling | Laatste 14 dagen | Mediaan | Alleen als caution-context. |
| Load context | Laatste 3 en 14 dagen | Gemiddelde load-units | WattFlow onderscheidt acute en chronische load plus hun verhouding. |
3.2 Componenten en score-opbouw
| Component | Hoofdinput | Rol in morning readiness | Opmerking |
|---|---|---|---|
| Sleep component | Slaapduur, fragmentatie, consistentie, diepe slaap | Vormt een expliciet deelscoreblok | Diepe slaap telt bewust licht mee en domineert de score niet. |
| Recovery component | HRV proxy, rusthartslag, HRV-stabiliteit | Vormt het autonomic recovery-blok | HRV-stabiliteit gebruikt rolling spreiding en CV als extra context. |
| Load context | Carry-over workouts, energy, stappen, acute/chronic verhouding | Remt de ochtendscore als er nog belasting doorwerkt | Workouts zijn leidend; gewone dagelijkse activiteit is veel minder zwaar. |
| Caution | Ademhaling, temperatuur, rusthartslag, subjectieve signalen | Kan de score cap-en en trainability verlagen | Geen medische diagnose; wel een expliciete voorzichtigheidslaag. |
| Confidence | Compleetheid, historie, conflictsignalen | Dempt extreme uitkomsten richting neutraal | Confidence is dus niet alleen decoratie, maar een echte modifier. |
- Duur: zwaarste gewicht
- Fragmentatie: middengewicht
- Slaapconsistentie: middengewicht
- Diepe slaap: lichte nuance
- HRV proxy versus persoonlijke baseline
- Rusthartslag versus persoonlijke baseline
- HRV stabiliteit of instabiliteit
- Trendcontext als extra nuance
Load context
- Acute versus chronische load wordt apart gevolgd.
- Carry-over van gisteren telt mee in de morning readiness.
- Day score gebruikt daarna current-day load sinds ontwaken.
Subjectieve check-in
- Vermoeidheid, spierpijn, stress en slaapkwaliteit.
- Werkt bewust licht bij en is nooit dominant.
- Wordt ook gebruikt als caution- en trendcontext.
3.3 Confidence en illness cap
Confidence en caution zijn volwaardige lagen in de engine. Confidence zegt hoe stevig de score staat. Caution zegt of meerdere signalen samen om extra voorzichtigheid vragen. Beide lagen zijn uitlegbaar.
| Confidence-zakker | Effect |
|---|---|
| Geen HRV, RHR of slaap | Confidence daalt zichtbaar en de score wordt teruggetrokken richting neutraal. |
| Onvoldoende historie | Persoonlijke baselines en trends krijgen minder gewicht. |
| Geen check-in | Geen blokkade, maar wel iets minder vertrouwen in de totale context. |
| Conflicterende signalen | Confidence daalt en de uitleg meldt expliciet dat signalen niet helemaal dezelfde kant op wijzen. |
| Nap-onzekerheid | Een dutje kan helpen, maar nooit de score boven je ochtendbasis tillen. |
morningReadiness =
weighted(sleep, recovery, load, subjective)
- cautionAdjustment
+ trendAdjustment
freshnessMorning =
confidenceAdjusted(morningReadiness)
|> applyCautionCap()
Caution / illness
- Ademhaling, temperatuur, rusthartslag en subjectieve signalen worden samen bekeken.
- De laag blijft deterministisch en geeft altijd redenen terug.
- De app gebruikt dit als trainingscontext, niet als diagnose.
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 wordt geprobeerd de morning readiness stevig vast te zetten. Pas daarna mag de day score nog gecontroleerd bewegen door workouts of een geldig dutje.
- 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.
- Workouts en duidelijke huidige dagbelasting kunnen de day score verlagen.
- Een geldig dutje kan een beperkte bonus geven, maar nooit boven de morning readiness.
- Maximaal 2 echte scorewijzigingen per dag.
- Elke intraday stap blijft begrensd en uitlegbaar.
morningReadinessScore = locked ? adjustedMorningReadiness : provisionalMorningReadiness
dayScore = min(
morningReadinessScore,
morningReadinessScore + intradayAdjustment + napBonus
)
3.5 Personalisatie en neutraal profiel
Onder de score zit nu ook een expliciet personalisatieprofiel. Dat profiel start neutraal en schuift pas op als er genoeg historie, check-ins en feedback zijn. WattFlow gebruikt hier geen vrij lerend model, maar een set begrensde gewichten en betrouwbaarheden.
| Neutraal profiel | Startwaarde | Betekenis |
|---|---|---|
| HRV-gewicht | 0.34 | Hoe zwaar autonome hersteldata meeweegt in de totale score. |
| RHR-gewicht | 0.22 | Rusthartslag als tweede herstelanker naast HRV. |
| Slaapgewicht | 0.28 | Slaap blijft een hoofdcomponent en niet alleen een randvoorwaarde. |
| Subjectief gewicht | 0.08 | De check-in is ondersteunend, niet leidend. |
| Load-gewicht | 0.18 | Recente belasting remt de score waar nodig af. |
| Caution-gewicht | 0.18 | Voorzichtigheidssignalen krijgen een echte remfunctie. |
| Recovery half-life | 1.2 dagen | Hoe lang eerdere belasting ongeveer doorwerkt. |
| Score bias | 0 | Neutraal startpunt totdat feedback laat zien dat de score systematisch te hoog of te laag zit. |
Als jij vaker “te laag” teruggeeft dan “te hoog”, dan mag het profiel de scorebias heel beperkt omhoog trekken, en omgekeerd. Dat gebeurt binnen vaste grenzen van -6 tot +6. Het systeem leert dus voorzichtig, niet onbeperkt.
De calculator houdt nu ook lateTrainingSensitivity bij. Wie vaker laat traint en daarna slechter slaapt of lagere HRV laat zien, krijgt een profiel waarin carry-over en herstelverval iets zwaarder meetellen.
4. Wat de score in de app doet
De score blijft niet in een debuglaag hangen. Ze komt direct terug in Vandaag, Schema, Meer, de Freshness-detailview, de widgetsnapshot, de training advisor en de AI-context. De app gebruikt nog steeds de naam Freshness, maar de inhoud is readiness-gedreven.
4.1 DailyState en UI
DailyState bevat onder meer
freshnesstrainabilityScoreentrainabilityLevelillnessgeneralLoadscorePhaseconfidenceconfidenceLevelencautionLevelshortReasonendetailedReasondominantReasonenchangedReasonmorningReadinessScoreenmorningLockedAt
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”.
- Vandaag toont de dagcontext, de plankaart en snelle route naar training of schema.
- Schema toont dezelfde score als plancontext boven de catalogus.
- Meer bevat Freshness instellingen: databronnen, privacy en compacte score-uitleg.
- De huidige detailview toont vooral score, signaalkaarten en achtergrondcontext; subjectieve input en feedback leven nog steeds mee in engine, opslag en advisorlogica.
AppModel en FreshnessUserSignalsStore, ook nu de
zichtbare UI vooral rond signaalkaarten en dagcontext draait.
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. De advisor kijkt daarbij niet meer alleen
naar de ruwe score, maar ook naar caution, confidence, load-context en trainability.
| 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 |
trainability == low |
Replace of reduce | 0.74 tot 0.90 | 0.72 tot 0.78 |
freshness < 40 + intensieve workout |
Replace | 0.72 | 0.60 tot 0.80, afhankelijk van score |
freshness < 40 + rustige workout |
Reduce | 0.90 | 0.60 tot 0.80 |
freshness 60-69 |
Reduce | 0.95 tot 0.97 | 0.92 tot 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 readiness, confidence, caution, trends, trainability en aankomende sessies. De prompt is dus geen vervanging van de score, maar een contextlaag voor planbijsturing of evaluatie.
User freshness: 64
Morning readiness: 68
Trainability: 61 (controlled)
Illness: possible
General load: medium
Confidence: 0.70 (moderate)
Caution: moderate
Main reason: slaap bleef onder je normale patroon
Score changed: vandaag zie je meer load dan gisteren
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: readiness-componenten, confidence, caution, morning lock, day score, trends, trainability en personalisatie. |
WattFlow/Domain/FreshnessModels.swift |
Publieke modellen zoals DailyState, component snapshots, trend/output types en compatibiliteitslagen voor de app. |
WattFlow/Services/FreshnessUserSignalsStore.swift |
Bewaart subjectieve check-ins, feedback en het persoonlijke calibratieprofiel. |
WattFlow/Services/FreshnessTrainingAdvisor.swift |
Past planworkouts aan op basis van Freshness, confidence, caution, load-context en trainability. |
WattFlow/Services/FreshnessPromptGenerator.swift |
Bouwt een compacte herstelprompt met readiness-context, explanation en trainability. |
WattFlow/Views/FreshnessDetailView.swift |
Detailweergave met score-ring, signaalkaarten en herstelcontext; de subjectieve laag wordt in de huidige code vooral via engine- en opslagpaden gevoed. |
WattFlow/AppModel.swift |
Refresh-orchestratie, snapshot-opslag, subjectieve signalen en koppeling naar planlaag en widgets. |
Deze freshness-gids is afgestemd op de huidige Freshness-engine met readiness-architectuur, planadvisor, trends, trainability, check-ins, feedback en personalisatie zoals die in de code staan op 13 april 2026.