WattFlow Freshness audit

Exacte codeflow van Freshness.

Dit document beschrijft Freshness zoals het nu in de code staat, niet zoals we het marketingmatig zouden samenvatten. We lopen letterlijk langs AppModel, AppleHealthService, FreshnessCalculator, FreshnessSnapshotStore, FreshnessUserSignalsStore, FreshnessPromptGenerator en FreshnessTrainingAdvisor.

1 AppModel start de refresh, bewaart snapshots en koppelt de uitkomst aan UI en planning.
2 AppleHealthService bouwt ankers, selecteert HRV en splitst carry-over versus huidige dag.
3 FreshnessCalculator berekent baselines, componenten, confidence, trainability en day score.
4 FreshnessTrainingAdvisor gebruikt die ene uitkomst direct om plansessies te houden, reduceren of vervangen.

1. Scope en bronbestanden

Deze audit legt uit wat de huidige code exact doet op 13 april 2026. Het is dus geen wensdocument en ook geen vereenvoudigde supporttekst. Waar een detail belangrijk is voor de werking, staat het hier erin: opslagkeys, lookbackvensters, ankerregels, selectievolgorde, drempels, caps en latere trainingsimpact.

Hoofdbronbestanden
WattFlow/AppModel.swift WattFlow/Services/AppleHealthService.swift WattFlow/Services/FreshnessCalculator.swift WattFlow/Services/FreshnessTrainingAdvisor.swift WattFlow/Services/FreshnessUserSignalsStore.swift WattFlow/Services/FreshnessSnapshotStore.swift WattFlow/Domain/FreshnessModels.swift
Belangrijke nuance

De engine ondersteunt nog steeds dagelijkse check-ins en feedback, maar de huidige viewlaag laat vooral score- en signaalkaarten zien. In deze audit volgen we daarom de code als bron van waarheid, ook waar de zichtbare UI compacter is dan oudere documentatie suggereerde.

Wat Freshness in één zin is. Een slaap-geankerde readiness-engine die eerst een morning readiness vastzet en daarna slechts beperkt intraday mag bijsturen, waarna die uitkomst direct wordt gebruikt in dagcontext, snapshots, widgets, AI-context en planbijsturing.

2. Orchestratie in de app

Alle echte Freshness-berekening loopt via AppModel.refreshFreshnessIfAvailable(...). Daar worden de randvoorwaarden gecontroleerd, Apple Health-inputs opgehaald, subjectieve signalen geladen, de calculator gestart en de uitkomst opgeslagen voor de rest van de app.

2.1 Refreshvoorwaarden

Stap Exact gedrag Codepad
Sync uit latestFreshnessComputation, freshnessState en freshnessDetailSnapshot worden leeg gemaakt en FreshnessSnapshotStore.clear() draait. AppModel.refreshFreshnessIfAvailable
Geen Health-autorisatie Zelfde clearpad als hierboven. Er blijft dus geen verouderde Freshness zichtbaar als Health niet echt aan staat. AppModel.refreshFreshnessIfAvailable
Inputs laden AppleHealthService.shared.loadFreshnessAnalysisInputs() levert de volledige analyseketen voor vandaag plus historie. AppModel -> AppleHealthService
Persoonlijke laag laden De huidige dag-check-in, recente check-ins, recente feedback en het laatst opgeslagen personalisatieprofiel worden meegegeven aan de calculator. FreshnessUserSignalsStore
Output opslaan Bij succes worden dailyState, detailSnapshot en het nieuwe personalisatieprofiel meteen opgeslagen. FreshnessSnapshotStore + FreshnessUserSignalsStore
let inputs = try await AppleHealthService.shared.loadFreshnessAnalysisInputs()
let currentCheckIn = inputs.first.map { FreshnessUserSignalsStore.loadCheckIn(for: $0.analysisDate) }
let recentCheckIns = FreshnessUserSignalsStore.recentCheckIns()
let recentFeedback = FreshnessUserSignalsStore.recentFeedback()
let personalizationProfile = FreshnessUserSignalsStore.loadPersonalizationProfile()

let computed = FreshnessCalculator.compute(
    inputs: inputs,
    previousState: previousState,
    subjectiveCheckIn: currentCheckIn ?? nil,
    recentCheckIns: recentCheckIns,
    recentFeedback: recentFeedback,
    personalizationSeed: personalizationProfile
)

2.2 Snapshots en opslag

Snapshot store

FreshnessSnapshotStore bewaart de modelversie en drie snapshots: freshness.daily-state, freshness.detail-snapshot en freshness.widget-snapshot. De huidige modelversie is 4.

Widgets en watch

Bij opslaan wordt ook een widgetsnapshot gemaakt, complication-data geüpdatet, phone connectivity gesynct en worden widget timelines gerefresht. Freshness is dus niet alleen een schermscore, maar ook een gedeelde outputlaag.

2.3 Subjectieve laag

De subjectieve laag bestaat uit twee losse sporen: een check-in voor vandaag en feedback achteraf. Beide leven in FreshnessUserSignalsStore in de gedeelde suite group.app.ergtrainer.shared.

Onderdeel Opslagkey Exact gedrag
Dag-check-in freshness.subjective-checkins Wordt per dag op startOfDay gezet en overschrijft dezelfde dag. Er kunnen dus niet meerdere check-ins voor één analyse-dag opstapelen.
Scorefeedback freshness.feedback-entries Te laag, Klopt en Te hoog worden per dag gemerged met optionele training feel.
Personalisatieprofiel freshness.personalization-profile Wordt na elke succesvolle berekening opnieuw opgeslagen en valt terug op .neutral als er nog niets bestaat.
Auditobservatie. In de huidige viewcode zie ik geen duidelijke actieve invoerflow die saveFreshnessCheckIn(...) of recordFreshnessFeedback(...) rechtstreeks aanroept. De engine en opslaglaag ondersteunen ze wel volledig.

3. Inputlaag uit Apple Health

AppleHealthService.loadFreshnessAnalysisInputs(dayCount: 35) bouwt standaard 35 analysedagen op, maar kijkt daar ruimer voor terug zodat baselines en anchorselectie voldoende geschiedenis hebben.

3.1 Queries en lookback

Lookback

De lookback is max(21, dayCount + 21). Met de standaard dayCount = 35 betekent dat een queryvenster van 56 dagen terug vanaf de start van vandaag.

Exact opgevraagd

HRV SDNN, rusthartslag, ademhaling, Apple sleeping wrist temperature, slaapcategorieën en workouts worden parallel uit HealthKit geladen. Daarna begint pas de Freshness-specifieke selectie.

3.2 Slaapepisodes en ankers

Slaap is het anker van de hele engine. Health-samples worden eerst samengevoegd tot slaapepisodes, daarna kiest de code per analysedag precies één anker. Dat anker bepaalt analyse-datum, recovery-window, HRV-context en de scheiding tussen gisteren en vandaag.

Stap Exacte regel Code
Slaapepisodes mergen Alle relevante slaap- en awake-samples worden op episode-niveau samengevoegd met een merge gap van 60 minuten. mergedSleepEpisodes(from:)
Asleep-intervals Binnen een episode worden alleen asleep-intervals voor de echte slaapduur gebruikt, met een merge gap van 5 minuten. sleepEpisode(from:)
Main sleep kandidaat Minimaal 3 uur asleep en eindtijd tussen 03:00 en 12:00. Dan krijgt het anker kwaliteit .anchoredSleep. selectAnchor(before:)
Fallback sleep Als geen main-sleep kandidaat bestaat maar wel een block van minimaal 2 uur, dan kwaliteit .fallbackSleep. selectAnchor(before:)
Inferred morning Als er geen bruikbaar slaapblock is, gebruikt de code 07:00 op dezelfde of vorige dag als synthetisch anker. inferredMorningAnchor(before:)
if let mainSleep = eligibleEpisodes.first(where: {
    $0.asleepDurationSeconds >= 3 * 60 * 60
        && anchorHour(for: $0.interval.end, calendar: calendar).map { 3...12 ~= $0 } == true
}) {
    quality = .anchoredSleep
} else if let fallbackSleep = eligibleEpisodes.first(where: { $0.asleepDurationSeconds >= 2 * 60 * 60 }) {
    quality = .fallbackSleep
} else {
    quality = .inferredMorning
}

3.3 Per-dag intervallen

Analyse-datum

analysisDate = startOfDay(anchor.anchorDate)

Carry-over

previousAnchor.anchorDate tot anchorSleep.startDate of, zonder slaap, tot anchor.anchorDate.

Current day

anchor.anchorDate tot de volgende slaapstart of de refresh-tijd.

Recovery interval

Van slaapstart of 3 uur voor het anker, tot maximaal 4 uur na het anker.

De activity periods bevatten niet alleen Health-energie en stappen, maar ook alle overlappende workouts. In FreshnessActivityPeriod worden die workouts eerst genormaliseerd zodat dubbels of zwaar overlappende sessies van hetzelfde type samenvallen in één cluster. Daarna gebruikt de code effectiveActiveEnergy als max(activeEnergy, workoutEnergyFloorKilocalories). Daardoor kan een workout nooit “verdwijnen” als Health toevallig minder actieve energie teruggeeft dan de workout zelf al bevat.

3.4 HRV filtering en selectie

De code gebruikt Apple Health SDNN, maar maakt daar in de calculator een log-getransformeerde readiness-proxy van. Niet elke HRV-sample mag mee. De selectie is bewust streng om workoutvervuiling uit de ochtendcontext te houden.

Regel Exact gedrag
Validatievenster hrvValidationInterval start 90 minuten voor de recovery-intervalstart en eindigt op de recovery-intervaleindtijd.
Workoutfilter Een HRV-sample is ongeldig als hij tijdens een workout valt of binnen 90 minuten na het workout-einde start.
Categorieën Geldige samples worden opgesplitst in sleep, morning en calm.
Selectievolgorde sleep (niet low) -> calm (niet low) -> morning -> sleep -> calm -> any valid -> none.
Kwaliteit 0 samples = none, 1 = low, 2 = medium, 3 of meer = high.

3.5 Overige signalen

Signaal Exact extractiegebied Aggregatie
Rusthartslag Binnen de recovery interval Mediaan
Ademhaling tijdens slaap Alleen over de anchor sleep intervals Mediaan
Sleeping wrist temperature Alleen over de anchor sleep intervals Mediaan
Dutjes Tussen anker en current day end Totaal minuten, maar alleen als elk interval 15-120 min is en start tussen 09:00 en 19:00

4. Score-engine in FreshnessCalculator

FreshnessCalculator.compute(...) werkt in vaste lagen: historie afleiden, baselines bouwen, statuses scoren, componenten combineren, confidence en illness toepassen, morning readiness locken en daarna eventueel intraday begrensd bijsturen.

4.1 Historievensters en baselines

Historie Aantal dagen Gebruik
HRV28HRV-baseline, log-proxy, rolling mean/std dev, CV
Rusthartslag28Persoonlijke baseline
Slaapduur14Slaapbaseline
Ademhaling14Nachtbaseline
Wrist temperature14Temperatuurbaseline, maar pas bruikbaar vanaf 5 nachten
Actieve energie14Belastingsbaseline
Stappen14Belastingsbaseline
Fragmentatie14Slaapkwaliteit
Slaapmidpoint14Slaapconsistentie
Diepe slaap14Slaapcomponent
Dutjes14Historiedekking en detailuitleg
Carry-over load units21Acute load, chronic load en ACWR

4.2 Sleep component

De sleep component bestaat uit vier deelscores en wordt daarna gewogen opgeteld. De gewichten zijn hard in code: duur 0.45, fragmentatie 0.25, consistentie 0.20 en diepe slaap 0.10.

Subscore Thresholds Score
Duur Ratio vs baseline: >= 0.96, >= 0.86, >= 0.76, lager 95 / 80 / 62 / 42
Fragmentatie Ratio lager is beter: <= 1.05, <= 1.25, <= 1.50, hoger 92 / 76 / 60 / 42
Slaapmidpoint Afwijking: < 30, < 60, < 90, < 120, hoger 92 / 80 / 66 / 54 / 40
Diepe slaap Ratio vs baseline: >= 0.90, >= 0.75, >= 0.60, lager 95 / 80 / 62 / 42

Daarna krijgt de hele sleep component status normal bij >= 80, deviated bij >= 65 en anders strongDeviation.

4.3 Recovery component

Recovery is een combinatie van HRV-score, rusthartslagscore en HRV-stabiliteit. De HRV-score gebruikt niet de ruwe milliseconden, maar een log-proxy op de gekozen representatieve HRV. De stabiliteit kijkt naar de coefficient of variation van de geselecteerde HRV-samples.

Onderdeel Exacte thresholds Score
HRV z-score >= 0, -0.5..<0, -1.0..<-0.5, -1.5..<-1.0, lager 96 / 84 / 68 / 54 / 40
Rusthartslag ratio <= 1.0, <= 1.03, <= 1.08, hoger 94 / 84 / 64 / 40
HRV stabiliteit Geen kwaliteit = 65, geen current CV = 64/72, geen baseline CV = 84 of 68, anders ratio <= 1.10, <= 1.35, hoger 65 / 64-72 / 84-68 / 88 / 74 / 56

De onderlinge gewichten worden niet hard als vaste percentages gebruikt, maar afgeleid uit het personalisatieprofiel: HRV-gewicht wordt geclamped naar 0.18...0.38, rusthartslag naar 0.14...0.30 en stabiliteit staat vast op 0.16. Daarna wordt genormaliseerd.

4.4 Load context

Load is in Freshness niet simpelweg “hoeveel heb je bewogen”, maar een combinatie van duidelijke workoutbelasting, actieve energie, stappen en een straf voor late trainingen.

loadUnits =
    (clearWorkoutMinutes / 8.0)
    + (clearWorkoutEnergy / 70.0)
    + movementSupport
    + lateWorkoutBonus
Onderdeel Exacte regel
Duidelijke workout Een workout telt als clear load bij minstens 30 minuten of minstens 250 kcal.
Movement support met workouts min(6, max(0, (energyRatio - 1) * 4) + max(0, (stepsRatio - 1) * 2))
Movement support zonder workouts 4 punten bij ratio 2.7 / 2.4, 2 punten bij 2.2 / 1.9, anders 0
Late training Elke clear workout die eindigt op of na 20:00 zet lateClearWorkoutCount omhoog.
Late training straf 6 + lateTrainingSensitivity * 8, afgerond, alleen als er een late trainingflag is.
Load state spikeRisk bij ACWR >= 1.35 en carry-over >= 12; elevated bij ACWR >= 1.10 of carry-over >= 10; underloaded bij chronic load < 4 en carry-over < 4; anders optimal.

Het uiteindelijke loadScore start op 100 en gaat naar beneden via carry-over load (0 / -10 / -22), load state (-2 / 0 / -6 / -12) en eventueel late training. Het resultaat wordt geclamped naar 35...100.

4.5 Caution, illness en confidence

Laag Exacte regel
Caution flags Afwijkende ademhaling, afwijkende temperatuur, sterk verhoogde rusthartslag, of subjectieve check-in met averageLoad >= 4 of sleepQuality <= 2.
Caution level high bij sterke ademhaling + temperatuur of 3+ flags; moderate bij 2 flags; mild bij 1 flag.
Caution penalty 0 / 4 / 10 / 18 voor none / mild / moderate / high.
Illness status none voor none/mild, possible voor moderate, likely voor high.
Illness cap possible cap op 60, likely cap op 35.
Confidence penalties HRV 0.18, RHR 0.15, slaap 0.15, nacht-signalen 0.08, korte historie 0.15, geen check-in 0.05, geen dutje-signaal 0.02, conflicten tot 0.12.
Confidence levels high bij >= 0.82, moderate bij >= 0.62, daaronder low.

De confidence wordt niet alleen als label getoond, maar trekt de score echt terug richting de neutrale waarde 82 via confidenceAdjustedScore.

4.6 Subjective laag, trends en personalisatie

De check-in wordt vertaald naar een subjectieve score via vermoeidheid, spierpijn, stress en slaapkwaliteit. De gewichten zijn hard: fatigue 0.32, soreness 0.22, stress 0.20, sleep quality 0.26. Het resultaat wordt geclamped naar 25...95.

Subjective modifier

De modifier is (subjectiveScore - 75) * clamp(profile.subjectiveWeight * profile.subjectiveReliability, 0.04...0.12), afgerond naar een integer. De subjectieve laag is dus bewust begrensd en kan niet de hele score kapen.

Trend adjustment

HRV improving geeft +2, slaap improving +1, load-recovery declining -3, fatigue declining -2 en fatigue improving +1.

let tooHighCount = feedback.filter { $0.scoreFeedback == .higherThanExpected }.count
let tooLowCount = feedback.filter { $0.scoreFeedback == .lowerThanExpected }.count
let scoreBias = clampInt(tooLowCount - tooHighCount, lower: -6, upper: 6)

let lateTrainingSensitivity = lateTrainingSensitivity(today: today, history: history)
let recoveryHalfLifeDays = clamp(
    1.0 + lateTrainingSensitivity * 0.8 + max(0, hrvNoise - 0.12) * 2.5,
    lower: 0.8,
    upper: 2.5
)

Het neutral profiel start op: HRV 0.34, rusthartslag 0.22, slaap 0.28, subjective 0.08, load 0.18, caution 0.18, met een neutrale recoveryHalfLifeDays van 1.2 en lateTrainingSensitivity = 0.0.

lateTrainingSensitivity zelf wordt bepaald uit de laatste 10 analysedagen. Alleen dagen met late clear workouts tellen mee. Vervolgens kijkt de code hoeveel van die late dagen minder dan 6,75 uur slaap hadden en hoeveel een representatieve HRV onder 55 ms lieten zien. De ratio van die twee tellers bepaalt de sensitiviteit, geclamped naar 0.0...0.7.

5. Morning readiness en intraday

Hier zit de kern van het model. De score begint niet als een dagscore die continu zweeft, maar als een morning readiness die eerst moet “locken”. Pas daarna mag Freshness overdag nog beperkt omlaag of licht omhoog door dutjes.

5.1 Raw morning readiness

weightedMorningReadiness = weightedAverage([
    (sleepComponent.totalScore, componentWeights.sleep),
    (recoveryComponent.totalScore, componentWeights.recovery),
    (loadScore, componentWeights.load),
    (subjectiveScore ?? 75, componentWeights.subjective)
])

rawMorningReadiness =
    weightedMorningReadiness
    - cautionPenalty
    + subjectiveModifier
    + trendAdjustment
    + profile.scoreBias

adjustedMorningReadiness =
    applyIllnessCap(
        confidenceAdjustedScore(rawMorningReadiness, confidence, neutralScore: 82),
        illness
    )

Belangrijk: voor de huidige dag kan de subjectieve component volledig uit de morning readiness-berekening vallen als er geen check-in is. In dat geval krijgt subjective gewicht 0 in de genormaliseerde componentgewichten.

5.2 Lockvoorwaarden

Voorwaarde Exacte regel Reason string
HRV niet verwacht Als minder dan 5 van de laatste 14 dagen HRV-samples hebben, maar ankercontext en core signals er wel zijn, mag de score meteen locken. hrv-not-expected-core-signals-ready
HRV verwacht en klaar Als er wel ochtend-HRV verwacht wordt en de selectie-kwaliteit vandaag medium of high is. expected-hrv-ready
Tijdsfallback 09:00 Vanaf 09:00 met ankercontext en core signals mag de score locken, ook als HRV nog niet ideaal is. time-threshold-09-with-core-signals
Harde fallback 11:00 Vanaf 11:00 lockt de score altijd. hard-fallback-11
if expectsMorningHRV == false, hasAnchorContext, hasCoreSignals { lock }
else if expectsMorningHRV, hasAnchorContext, hrvReady { lock }
else if hour >= 9, hasAnchorContext, hasCoreSignals { lock }
else if hour >= 11 { lock }
else { keep collectingMorningSignals }

5.3 Provisional en reuse

Herbruik bestaande ochtendlock

Als dezelfde analysedag opnieuw berekend wordt en de vorige state had al een morningLockedAt, dan wordt die oude morning readiness hergebruikt. De ochtendscore beweegt dan niet opnieuw.

Voorlopige ochtendscore

Zonder lock gebruikt de code niet de volle adjusted morning readiness, maar trekt die halverwege terug naar neutral 82 via provisionalMorningReadiness.

5.4 Intraday begrenzing

Zodra de ochtend gelockt is, kan de score nog verschuiven door huidige dagbelasting en dutjes. Maar dat mag alleen binnen strakke grenzen:

  • Base intraday adjustment: low 0, medium -4, high -8.
  • Extra state correctie: spike risk -2, underloaded +1.
  • Dutjesbonus: 20-39 min +2, 40-59 min +3, 60-89 min +4, 90-120 min +3.
  • Cap: de candidate freshness mag nooit boven de morning readiness uitkomen.
  • Wijzigingslimiet: geen wijziging bij delta van 2 punten of kleiner, maximaal 2 intraday wijzigingen per dag, maximaal 10 punten per wijziging.

Daardoor wordt Freshness expres niet een springerig live dashboard. Overdag mag hij wel reageren, maar alleen als het verschil materieel genoeg is en binnen de begrenzingen blijft.

6. Outputs van de engine

De output is niet alleen een integer score. DailyState bevat ook illness, general load, confidence, morning lock metadata, trainability en verschillende explanationvelden. Daarnaast wordt een detail snapshot gebouwd voor de signaalkaarten en een widgetsnapshot voor homescreen en complicaties.

6.1 DailyState en snapshots

Output Belangrijkste velden
DailyState freshness, illness, generalLoad, scorePhase, confidence, morningReadinessScore, morningLockedAt, cautionLevel, trainabilityScore, trainabilityLevel, subjectiveModifierApplied, napAdjustmentApplied, lastUpdated.
FreshnessDetailSnapshot Baselines, ratios, penalties, trends, explanation, recent days en de uitgewerkte component-snapshots voor de detailview.
FreshnessWidgetSnapshot Compacte score-output voor widget- en complicationgebruik.

6.2 Huidige UI-oppervlakken

Vandaag en Schema

Daar leeft Freshness als dagcontext via FreshnessSummaryCard. Die krijgt de score, detailsnapshot en de status of de engine nog ochtenddata verzamelt.

Meer en detailview

In MoreTabView staat Freshness als instellingenroute met Apple Health-toggle en compacte copy. FreshnessDetailView toont nu vooral HRV, rusthartslag, slaap, belasting, illness, ademhaling en temperatuur.

Auditnotitie over de UI. Oudere documentatie noemde expliciete check-in- en feedbackacties in de detailview. In de huidige viewcode zie ik die niet terug. De engine gebruikt subjectieve data nog wel als die al in de store staat.

6.3 Promptgenerator

Naast de deterministische advisor bouwt WattFlow ook een compacte contextprompt. Die probeert niet zelf de score opnieuw te berekenen, maar geeft een externe AI alleen de huidige herstelcontext mee.

User freshness: 64
Morning readiness: 68
Trainability: 61 (controlled)
Illness: none
General load: medium
Confidence: 0.78 (moderate)
Caution: mild
Main reason: ...
Score changed: ...

Upcoming workouts:
...

Request:
Adjust plan, keep structure, reduce overload.

7. Effect op training en planning

De Freshness-score is niet alleen informatielaag. Elke plansessie loopt langs FreshnessTrainingAdvisor. Die kijkt niet alleen naar de ruwe score, maar ook naar illness, caution, general load, confidence en trainability.

7.1 Advisor thresholds

Voorwaarde Actie Intensity Duration
illness == likelyReplace0.650.60
illness == possible of caution moderate/high + intensief blokReplace0.720.70
generalLoad == high + intensief blokReplace0.750.75
trainability == low + intensief blokReplace0.740.72
trainability == low + niet-intensiefReduce0.900.78
confidence == low + intensief blokReduce0.900.85
freshness < 40 + intensiefReplace0.720.60-0.80
freshness < 40 + niet-intensiefReduce0.900.60-0.80
freshness 60-69Reduce0.95 of 0.970.92 of 0.95
freshness 50-59Reduce0.950.90
freshness 40-49Reduce0.920.85
trainability == controlled + intensief blokReduce0.960.94

Intensieve sessies worden in deze advisor gedefinieerd als categorie .vo2max, .omslagpunt of .test. Dat is dus een inhoudelijke workout-categoriecheck, niet alleen een drempel op IF of TSS.

7.2 Reduce versus replace

Reduce

scaledWorkout(...) schaalt steady- en rampblokken in duur en intensiteit. FreeRide-blokken houden hun freeride-karakter, maar krijgen wel een kortere duur.

Replace

recoveryReplacement(...) bouwt een vervangende hersteltraining met warm-up, rustige steady load en cooldown. Doelduur wordt begrensd tussen 20 en 45 minuten.

8. Auditnotities en grenzen

  • Geen medische engine: illness is een trainingscontextlaag, geen diagnose.
  • Geen Health, geen Freshness: sync uit of niet geautoriseerd ruimt de snapshots op.
  • Slaap blijft de baas: zonder bruikbare slaap valt de code terug op inferred morning anchors, maar dat is expliciet een lagere kwaliteit.
  • Confidence doet echt mee: het is geen cosmetisch label; de score wordt echt teruggetrokken richting 82.
  • Late training telt dubbel door: niet alleen via carry-over load, maar ook via lateTrainingSensitivity en een extra loadstraf.
  • Subjectieve laag is echt, maar UI-route is nu minder zichtbaar: engine, AppModel en opslagpad zijn aanwezig; de huidige viewcode toont vooral de objectieve signaalkaarten.
Verdieping vanuit hier. Voor de leesbare uitleg ga naar WattFlow_Freshness.html. Voor de concrete impact op planschema’s en AI-plannen ga naar WattFlow_Trainingen.html.

Deze audit volgt de actuele Freshness-implementatie in de codebasis van WattFlow op 13 april 2026. Als code en UI opnieuw verschuiven, hoort juist deze pagina als eerste mee te veranderen.