Search

Search components, tokens, patterns, architecture

Architecture · Part 07

Decision Engine

How one daily record becomes a recommendation: every weight and threshold on this page is read from shipped code. Three properties define the design — additive legible scoring, strain as the spine, and floors (never sums) for advice.

The pipeline

Additive, legible scoring — every contribution is a named integer, so drivers[] is literally the score decomposition; that is why every card can explain itself. Strain is the spine — computed once, propagated into capacity (−0.25×) and crash risk (+0.4×). Floors for advice — fueling uses Math.max everywhere: ten reasons to raise protein still yield “3”, never “30”.

Interpretation layer — derived signals

SignalDefinition (shipped)
isHardSessionactivity ∈ hard set (HIIT, Gym, Climbing, Hyrox…) OR any non-rest activity felt Hard / Harder than expected / ⚡ Peak
Effective Sleep Qualitysleepq ± tag deltas, capped [+1.5, −2]: 2-4am wake / Fragmented / Night sweats / Woke up tired −1 · Overheating −0.5 · refreshed/energized +1
symptomBurden0–3, Σ tag weights (Crash day 3 · Afternoon crash 2.5 · Fatigue 2 · Sore body / Low energy / Hot flash 1.5 …)
periodBurden0–5: bleed load (Spotting .25 → Heavy 2) + period-symptom loads
isCrashDay'Crash day' logged, OR energy = 1 AND ≥2 of six corroborating indicators
overnightTrendwearable HR/HRV · ≥3 nights · MAD outlier filtering · baseline-half vs recent-half · HRV weighted 2× · no single-night conclusions · raw numbers never leave the module
patternConfidencecycle-length consistency (≥4 valid lengths = high), downgraded by long-running cycle, <5 entries → low, <15 caps at medium, missing sleep caps at medium

The three scores

ScoreComposition (headline weights)
Recovery Strain (0–100)hard session 1/2/3 days ago +35/+25/+15 · ≥2 sessions in 48h +15 (incl. wearable-detected workout days when no manual log) · 'Had to stop' +20 · logged sleep <6h +20 · sleep-tag disruptors ≤+24 · period symptoms ≤+30 · heat +10 · symptom tags ≤+25 (boosters negative) · wearable +6…+8 only below strain 60
Recovery Capacity (base 70)sleep ±15 · ESQ ±12 · strain × −0.25 · logged energy +10/−20 (highest single weight) · fog ±15 · fatigue −15 · Crash day −30 · period syms capped −15 · symptom tags capped −35/+15 · 7-day energy trend ±15 · wearable ±5–8 with manual override
Crash Risk (0–100)strain × 0.4 · sleep <6h +25 · energy ≤2 +20 · crash logged +25 · cycle capped +10 (secondary only) · personal history: same-phase crashes +20, same training+sleep pattern before a crash +35 — the largest weight in the engine

What missing data does — and doesn't do

Unlogged sleep adds zero risk and zero capacity penalty — it reduces confidence and becomes an actionable assumption. And the two personal-history weights (+20/+35) outweighing every generic weight is the codebase literally enforcing “personal history outweighs generic cycle assumptions.”

Recommendation mapping

A logged crash bypasses both scores — symptoms always win. Below 5 entries or 2 period starts the engine returns the learning state instead of scores. The basedOnText paragraph assembles Observation → Interpretation → Recommendation, with cycle phrasing hedged by confidence.

Fueling — floors by priority

PrioritySignalFloors (C/P/E, 1–3)
1Training load high / veryHigh (correct 0–2-day windows)C3 P2 / C3 P3 · E2–3 with sweat or night-sweats
2Logged sleep <6h (<5h)C2 P2 E2 (C3)
3Capacity <30 / <50C3 P3 E2 / C3 P2 E2
4Crash risk ≥70 / ≥45C3 P3 E2–3 / C2–3 P2
5Fatigue · heavy bleedingC2–3 · P2 E2
6Symptom flagsprotein: Sore body, Heavy legs, flu-like · electrolytes: Hot flash, Night sweats, cramps family, Diarrhea→E3 · carbs: Low energy, fatigue family
7Appetite signalscravings → C2–3 · low appetite → P2 (protein-first when eating is hard)
8 (lowest)Cycle phaseskipped entirely when patternConfidence = low

Comparative engines

EngineMechanics
What Changedrecomputes capacity for today and yesterday · headline 'Capacity ↓N' or 'No significant changes' when |Δ|<5 and no threshold crossed · changes capped at 3 · one primary driver
What Nextsimilarity score vs every past day: capacity state 35 · strain 20 · sleep 15 · training 10 · energy 10 · risk 8 · cycle booster 7 — threshold 45 · day-after outcomes aggregate into chips · population fallback with hedged copy at low confidence

Override hierarchy

Enforcement pointMechanism in code
Crash dominanceCrash day → capacity −30, recommendation bypass, other reducer tags dominated to 20% of their strain
Manual beats wearableenergy ≥4 or 'Woke up refreshed/energized' blocks negative wearable adjustments; manual fatigue blocks positive ones; wearable never a primary driver, never adds above strain 60
Cycle as context≤ +10 risk, secondary tier only · hedged language below high confidence · removed entirely in irregular / late_peri modes
Dedup guardsBrain-fog tag ≈ 0 when fog scale set · Low energy halves when Fatigue present

Worked example — the brief's case

5.5h logged sleep · Sore body · CD 25 (late luteal) · hard gym session yesterday.

StageComputationResult
Strainhard recent +35 · sleep <6h +20 · Sore body +863
Capacity70 −15 (sleep) −16 (strain×.25) +5 (no fatigue) −6 (Sore body)38 → Limited
Crash riskstrain×.4 = 25 (primary: Training load) · sleep +25 (primary) · CD 25 +10 (secondary) · Sore body +565
Recommendationrisk ≥45 ∧ capacity <50“Recover first”
Fuelingload high → C3 · sleep + capacity floors → P2 E2C●●● P●● E●●

Personal history flips tiers

If her history contains a crash after the same hard-training + short-sleep pattern, +35 risk lands → 100 → “Rest guilt-free”. One personal pattern outweighs every generic signal — by design. Try this live in the playground simulator.

Engine observation register

#1 · Units mismatch in dailyFocus recency windows — resolved 2026-06-12

daysBetween() returns days but was compared against 24/48/72 under “within last 24h/48h/72h” labels — so a hard session within ~24 days added +35 strain and the +8 rest bonus required 72 hard-session-free days. Fixed to day units (dAgo ≤ 1/2/3) in the strain, capacity, and crash-risk windows; signal labels unchanged; dailyFocus and fuelingEngine now agree on recency. One playbook ordering test re-pinned (a tie-order artefact of the inflated windows); suite green at 281/281. Practical effect: “Train normally” is reachable again for regularly-training users, and the drawer no longer cites weeks-old sessions as “within last 24h”.
#ObservationStatus
2HARD_TRAINING set includes personal activity names (Volley class, Acroyoga, Hyrox) — personal-vocabulary-aware by listing, not learningas designed; scaling note
3Primary/secondary driver split is the UI contract for driver ordering — new contributions must declare a tierconvention