Leidend
FreshnessCalculator.swift, AppleHealthService.swift, FreshnessTrainingAdvisor.swift, FreshnessModels.swift, AppModel.swift en de Freshness-views bepalen dit systeemgedrag.
Deze pagina is geen oude gap-audit meer. Dit is de actuele systeembeschrijving van Freshness v3 in de WattFlow iOS-codebase. De focus ligt op de echte runtime-logica: data-ingest, slaapankers, HRV-contexten, scoring, confidence, advisor-gedrag, UI-uitlegbaarheid en validatie.
Dit document is opnieuw opgebouwd op basis van de huidige implementatie in de app. Oude audits en specs zijn gebruikt als historische context, maar niet als leidende bron. De code is leidend.
FreshnessCalculator.swift, AppleHealthService.swift, FreshnessTrainingAdvisor.swift, FreshnessModels.swift, AppModel.swift en de Freshness-views bepalen dit systeemgedrag.
Oudere auditteksten die nog risico’s of open punten beschreven, maar inmiddels door implementatie zijn ingehaald.
Nieuwe velden zijn achterwaarts compatibel toegevoegd via optionals en defaults. Oude snapshots en opgeslagen states blijven decodeerbaar.
Freshness draait nu als een vaste keten van Health-ingest naar analyse, naar UI, naar workout-advice en daarna naar validatie. Elke stap draagt expliciete metadata mee over datakwaliteit, lock-context en aanpassingsmodus.
| Laag | Verantwoordelijkheid | Belangrijkste output |
|---|---|---|
AppleHealthService |
HealthKit samples, slaapblokken, workouts, naps en anchors selecteren | FreshnessAnalysisInput met anchor quality, HRV-context, validation score |
FreshnessCalculator |
Scoreberekening, confidence, trainability, morning lock, data quality mode | DailyState |
FreshnessTrainingAdvisor |
Workout-aanpassingen op basis van state, signature en runtime mode | AdvisorResult met action en reason |
AppModel |
Refresh orchestration, throttling, preferences, snapshots, prompt, validation | Gepubliceerde app-state en snapshots |
| Freshness UI | Summary card, detail pagina, banners, explainability en settings | Constrained state, stale profile, adjustment mode, trace |
FreshnessValidationStore + analytics |
Opslaan en classificeren van analyse- en workout-events | Meetlaag voor tuning, niet voor auto-learning |
Apple Health data
-> anchor selection
-> HRV context resolution
-> DailyState scoring
-> UI / prompt / advisor
-> workout execution context
-> validation event + analytics
Freshness gebruikt Apple Health als primaire bron. De HRV-bron is bewust niet aangepast: de app blijft werken op Apple Health SDNN, die intern via een log-transformatie als readiness-proxy wordt gebruikt.
De oude absolute ochtendlock is vervangen door een anker-gedreven systeem. Eerst wordt de meest waarschijnlijke hoofdslaap gekozen, daarna bepaalt de anchor wanneer de app de ochtendscore veilig mag locken.
.anchoredSleep: voorkeursanker op basis van hoofdslaap..fallbackSleep: slaapblok bruikbaar, maar minder overtuigend..inferredMorning: geen bruikbare slaap; ochtendanker wordt afgeleid..uncertain: anker is te zwak en wordt als onzeker behandeld.De anchor-selector gebruikt een historisch slaapprofiel over 21 dagen. Late slapers of ploegendienst worden daardoor niet meteen afgekeurd zolang het slaapvenster past bij hun recente patroon.
+40 als een bruikbare HRV-sample in het recovery-venster valt.+30 als een RHR-meting in datzelfde venster valt.+30 als de anchor quality .anchoredSleep is.Bij anchorValidationScore < 40 krijgt confidence een extra penalty van 0.08 en degradeert de effectieve anchor quality naar .uncertain.
| Anchor quality | Core window | Fallback window | Morning lock reasons |
|---|---|---|---|
.anchoredSleep |
120 min na anchor-end | 240 min na anchor-end | expected-hrv-ready, anchor-relative-core-signals, anchor-relative-fallback |
.fallbackSleep |
150 min | 300 min | Zelfde patroon, maar voorzichtiger timing |
.inferredMorning / .uncertain |
180 min | 360 min | Altijd meer terughoudend en vaak gekoppeld aan .autoReduceOnly of .informOnly |
| Geen anchor | Absolute fallback | 09:00 / 11:00 rules | time-threshold-09-with-core-signals, hard-fallback-11 |
collectingMorningSignals met reason waiting-for-anchor-end.
De engine splitst HRV nu expliciet op in contextfamilies. Daardoor wordt een slaap/ochtend-HRV niet meer zonder meer vergeleken met een rustige dagmeting als daar voldoende context-specifieke historiek voor beschikbaar is.
.nightRecovery voor slaap- of ochtendgerelateerde herstelmetingen..calm voor rustige, niet-slaapgebonden metingen..unavailable als er geen betrouwbare context is.hrvContextMatch wordt opgeslagen in state en detail snapshot. Als dagwaarde en baseline uit verschillende contextfamilies komen, krijgt confidence een extra penalty van 0.06.
| Baseline | Venster | Opmerking |
|---|---|---|
| HRV | 21 dagen | Voorkeur voor dezelfde HRV-contextfamilie, anders expliciete mismatch |
| RHR | 21 dagen | Geharmoniseerd naar hetzelfde venster als HRV |
| Slaapduur, fragmentatie, midpoint, diepe slaap | 21 dagen | Geen 14-daagse uitzonderingen meer |
| Ademhaling, temperatuur, energie, stappen, dutjes | 21 dagen | Zelfde baseline-horizon voor coherentie |
| Carry-over load | 21 dagen | Ongewijzigd gebleven |
baselineCoherence-safety valve niet verwijderd. Als recente en langere patronen uiteenlopen, zakt confidence mee.
Freshness gebruikt nog steeds een neutrale trekwaarde van 82, maar die is nu duidelijker ingekaderd. Zodra confidence laag is en de confidence-adjusted score wordt toegepast, markeert de engine de dag expliciet als constrained in plaats van die uitkomst als een normale groene herstelscore te presenteren.
neutralFreshness = 82. Lage confidence trekt de score gecontroleerd richting neutraal, niet richting “goed herstel”.
high >= 0.82, moderate >= 0.62, low < 0.62.
isConfidenceConstrained = true als de adjusted score actief is en confidence .low is.
| Component | Gewicht | Opmerking |
|---|---|---|
| Duur | 0.40 | Belangrijk, maar iets minder dominant dan eerder |
| Fragmentatie | 0.22 | Relevant, maar minder cliff-achtig meegewogen |
| Consistentie | 0.20 | Midpoint/ritme blijft wezenlijk |
| Diepe slaap | 0.18 | Bewust zwaarder dan voorheen |
hrvContextMatch == false geeft extra penalty van 0.06.anchorValidationScore < 40 geeft extra penalty van 0.08..uncertain.| Mode | Wanneer | Betekenis in product |
|---|---|---|
.reliable |
Goede confidence, degelijke anchor, normale lock-context | Normale scoreweergave en volwaardig advisor-gedrag |
.cautious |
Matige confidence of voorzichtige fallback-context | Waarschuwt expliciet en duwt sneller richting .autoReduceOnly |
.uncertain |
collectingMorningSignals, lage confidence, fallback lock, inferred/uncertain anchor |
Constrained UI, geen groen vertrouwen en vaak .informOnly |
+2+3+4+1+0< 55 worden alle dutjesbonussen gehalveerd met integer floor.illness == .possible of .likely is de dutjesbonus altijd 0.Trainingsaanpassingen zijn nu niet meer één impliciet gedrag. De app kent drie expliciete runtime-modi, die zowel uit gebruikersvoorkeur als uit datakwaliteit kunnen voortkomen.
| Mode | Betekenis | Runtime-trigger |
|---|---|---|
.autoAdjust |
Reduce én replace mogen automatisch worden toegepast | Goede datakwaliteit, betrouwbare lock, geen extra blokkades |
.informOnly |
Advisor geeft advies, maar wijzigt geen training | Gebruikersvoorkeur of scorefase collectingMorningSignals |
.autoReduceOnly |
Reduce mag, replace is geblokkeerd | Lage confidence, onzekere data, fallback lock of zwakke anchor |
De gebruiker kan een voorkeursmodus kiezen. Deze wordt opgeslagen onder freshness.adjustment-mode in FreshnessUserSignalsStore en bij elke refresh opnieuw toegepast.
In .informOnly worden acties expliciet gelogd als .informedReduce of .informedReplace, zodat validatie onderscheid kan maken tussen advies en echte planwijziging.
De advisor werkt niet meer alleen met grove workout-categorieën. Elke training krijgt een signature en risicolaag, waardoor zware bloktrainingen, benchmarks en steady-hard sessies anders behandeld kunnen worden.
.endurance, .steadyHard, .highAerobic, .highNeuromuscular, .benchmark.
Freshness-score, trainability, caution, illness, carry-over load, data quality mode, runtime adjustment mode en workout-signature.
Onzekere dagen verschuiven sneller naar .informOnly of .autoReduceOnly, waardoor automatische replacement agressief wordt afgeremd.
if adjustmentMode == .informOnly:
return .informed(action: suggestedAction)
if lowConfidence or uncertainAnchor:
prefer reduce over replace
if likelyIllness or very high risk signature:
suppress harder
De subjectieve laag leeft nu echt in het product, maar wordt ook actief terug naar neutraal geduwd als die te lang niet meer gevoed wordt. Dat voorkomt schijnpersonalisatie.
lastFedAt houdt het laatste subjectieve event bij.| State | Betekenis | UI-effect |
|---|---|---|
.fresh |
Profiel recent gevoed | Geen extra banner |
.aging |
Profiel veroudert, maar telt nog mee | Geen harde waarschuwing, wel minder subjectieve kracht |
.stale |
Profiel is inhoudelijk terug naar neutraal gedecayed | Banner: “Vul een check-in in om je profiel te vernieuwen.” |
De UI is nu afgestemd op datakwaliteit en runtime-beperkingen. De app laat niet alleen een score zien, maar ook waarom die score voorzichtig, gelockt, of nog voorlopig is.
De Freshness prompt voor AI-output bevat nu niet alleen de score, maar ook de omstandigheden waaronder die score tot stand kwam. Daardoor ziet externe uitleg het verschil tussen “sterke ochtendscore” en “voorzichtige schatting”.
De prompt maakt context rijker, maar schrijft geen modelregels terug. De AI-laag blijft uitleg en interpretatie; de Freshness-engine blijft de primaire beslislaag.
Freshness heeft nu een echte meetlaag. Die bewaart analyse-context, workout-uitvoering en gebruikersfeedback zodat het team false positives en false negatives kan beoordelen. Die meetlaag mag echter niets automatisch terugschrijven naar calculator of advisor.
// CLASSIFICATIECRITERIA (v3 definitief)
// aligned: scoreFeedback == .correct && |TSS-afwijking| < 15%
// falsePositiveCandidate: score >= 65 && trainingFeel <= 2 && advisorAction == .none
// falseNegativeCandidate: score < 50 && trainingFeel >= 4
// && (advisorAction == .reduce || advisorAction == .replace)
// inconclusive: alle overige gevallen
// NOTE: classificatieresultaten vloeien NIET automatisch terug in model of advisor.
// Dit is uitsluitend een manueel tuning-instrument.
De hoofdverbeteringen uit het audit- en spectraject zijn nu als concrete flags en codepaden in de app verankerd. In de huidige configuratie staan deze features aan.
freshnessAdaptiveAnchoringEnabledfreshnessHRVContextSplitEnabledfreshnessSmoothScoringEnabledfreshnessWorkoutSignatureAdvisorEnabledfreshnessBaselineCoherenceEnabledfreshnessValidationLoggingEnabledWattFlow/Services/FreshnessCalculator.swiftWattFlow/Services/AppleHealthService.swiftWattFlow/Services/FreshnessTrainingAdvisor.swiftWattFlow/Services/FreshnessValidationAnalytics.swiftFreshnessViews.swift voor summary card en bannersFreshnessDetailView.swift voor detailtraceMoreTabView.swift voor validatie-ingangAppModel.swift voor refresh, prompt en snapshot-flowHet systeem is nadrukkelijk veiliger en beter uitlegbaar geworden, maar behoudt ook een paar bewuste productkeuzes. Die zijn geen vergeten gaten; ze zijn expliciet zo gelaten.
82 blijft bestaan, maar wordt zichtbaar gemaakt via constrained mode.