diff --git a/.gitignore b/.gitignore index 6692393..895b74e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ assets-src/ # Artefatti di lavoro dei subagenti (ledger, brief, review package) .superpowers/ + +# Cache di Python generata rieseguendo l'oracolo +__pycache__/ diff --git a/docs/plans/2026-08-22-longevity-motore.md b/docs/plans/2026-08-22-longevity-motore.md index 1a76d14..657e3fe 100644 --- a/docs/plans/2026-08-22-longevity-motore.md +++ b/docs/plans/2026-08-22-longevity-motore.md @@ -69,141 +69,27 @@ Prima di portare una sola curva, serve il metro di paragone. - [ ] **Step 1: Scrivi il generatore** -```python -#!/usr/bin/env python3 -""" -Esegue il motore del cliente su una griglia fitta di ingressi e scrive i risultati -in riferimento.json. È il metro contro cui si misura il porting in TypeScript: -non serve a provare che l'oracolo è giusto, ma che il nostro porting gli è fedele. +⚠️ **Questo passo è già stato eseguito.** La griglia definitiva vive in +`tests/longevity/riferimento/genera-riferimento.py`, committata e verificata: rieseguirla +riproduce `riferimento.json` byte per byte (4018 casi, 25 funzioni, nessuna eccezione). -Uso: python3 tests/longevity/riferimento/genera-riferimento.py -""" -import json -import pathlib -import sys +Qui **non** ne teniamo una seconda copia, e la ragione è un incidente vero: quando il piano +conteneva anche il codice, piano e script sono divergiti — il file di riferimento è rimasto +per un momento non riproducibile dal proprio generatore, che per un oracolo è il difetto +peggiore possibile. Due copie della stessa cosa si separano sempre; il codice sta nel repo, +il piano dice cosa deve fare e perché. -QUI = pathlib.Path(__file__).parent -sys.path.insert(0, str(QUI)) +**Cosa la griglia copre**, che è ciò che va verificato se un domani si tocca: -import isl_scoring_engine as m - -casi = [] - - -def prova(fn_nome, *args): - """Esegue una funzione dell'oracolo e registra ingressi e uscita.""" - fn = getattr(m, fn_nome) - casi.append({"fn": fn_nome, "args": list(args), "atteso": fn(*args)}) - - -def griglia(inizio, fine, passo): - """Valori da inizio a fine compreso, con arrotondamento pulito.""" - n, v = [], inizio - while v <= fine + 1e-9: - n.append(round(v, 4)) - v += passo - return n - - -# --- curve generiche del questionario: tutto il dominio, non un campione --- -for v in griglia(0, 14, 0.5): - prova("score_bell_curve", v, 4, 7, 9, 12) # q_ore_sonno - prova("score_decreasing", v, 0, 7) # q_caffeina e gemelle - prova("score_decreasing", v, 7, 14) # q_alcol_life: la soglia voluta -for v in griglia(0, 10, 0.5): - prova("score_direct_x10", v) -for v in griglia(0, 7, 0.5): - prova("score_increasing_plateau", v, 0, 4) - -# --- le tre aggiunte che chiudono i buchi di copertura --- -# ⚠️ `worst` va variato: con `worst` sempre a 0 un errore sull'offset resta invisibile -for v in griglia(0, 7, 0.5): - prova("score_increasing_plateau", v, 2, 6) - -# ⚠️ formula a cinque coefficienti: un parametro alla volta, per sapere QUALE diverge -for t in (10, 15.5, 20, 25): - prova("vo2max_from_2km_walk", t, 130, 40, 24) -for hr in (100, 115, 130, 145, 160): - prova("vo2max_from_2km_walk", 15.5, hr, 40, 24) -for eta in (20, 40, 60, 75): - prova("vo2max_from_2km_walk", 15.5, 130, eta, 24) -for bmi in (18, 22, 26, 32): - prova("vo2max_from_2km_walk", 15.5, 130, 40, bmi) - -# i confini di banda esatti del plank: la curva è continua, ma costano nulla -for sesso, confini in (("M", (79, 97, 122, 157, 201)), ("F", (35, 63, 84, 108, 142))): - for v in confini: - prova("score_plank", v, sesso) - -# --- composizione corporea: attorno ai confini delle bande --- -for sesso in ("M", "F"): - for v in griglia(0, 45, 0.5): - prova("score_fat_percent", v, sesso) - for v in griglia(20, 60, 0.5): - prova("score_muscle_percent", v, sesso) - for v in griglia(0.5, 1.3, 0.01): - prova("score_whr", v, sesso) - -# --- cardio --- -for v in griglia(85, 100, 1): - prova("score_spo2", v) -for sbp in griglia(90, 200, 5): - for dbp in griglia(50, 120, 5): - prova("score_blood_pressure", sbp, dbp) -for v in griglia(0, 40, 1): - prova("score_hrr", v) -for sesso in ("M", "F"): - for eta in griglia(20, 75, 5): - for v in griglia(15, 70, 2.5): - prova("score_vo2max", v, eta, sesso) -for v in griglia(120, 200, 5): - prova("vo2max_from_step_test", v, "M") - prova("vo2max_from_step_test", v, "F") -for v in griglia(100, 300, 10): - prova("vo2max_from_mutt", v) -for w in griglia(50, 200, 10): - prova("vo2max_from_milfit", w, 75) -prova("vo2max_from_2km_walk", 15.5, 130, 40, 24) - -# --- forza --- -for sesso in ("M", "F"): - for eta in griglia(25, 75, 5): - for v in griglia(10, 70, 2.5): - prova("score_handgrip", v, eta, sesso) - for v in griglia(0, 60, 2): - prova("score_pushup", v, eta, sesso) - for v in griglia(0, 250, 5): - prova("score_plank", v, sesso) - for v in griglia(0, 120, 5): - prova("score_flexed_arm_hang", v, sesso) - for v in griglia(0, 60, 2): - prova("score_sit_to_stand_1min", v, 40, sesso) - for v in griglia(-30, 30, 1): - prova("score_sit_and_reach", v, sesso) - for eta in griglia(25, 75, 5): - for v in griglia(-30, 20, 2): - prova("score_back_scratch", v, eta, sesso) - -# --- sollevamenti sui rapporti col peso corporeo --- -for sesso in ("M", "F"): - for carico in griglia(20, 200, 10): - prova("score_bw_ratio_lift", carico, 80, 5, m.TIERS_BENCH_M, m.TIERS_BENCH_F, sesso) - prova("score_bw_ratio_lift", carico, 80, 5, m.TIERS_SQUAT_M, m.TIERS_SQUAT_F, sesso) - prova("score_bw_ratio_lift", carico, 80, 5, m.TIERS_ROW_M, m.TIERS_ROW_F, sesso) - -# --- stabilità --- -for v in griglia(0, 30, 1): - prova("score_flamingo", v) -for a in griglia(0, 180, 10): - prova("score_shoulder_mobility_wt", a, a) - -uscita = QUI / "riferimento.json" -uscita.write_text(json.dumps( - {"generato_da": "isl_scoring_engine.py", "casi": casi}, - indent=1, ensure_ascii=False, -)) -print(f"{len(casi)} casi scritti in {uscita}") -``` +- le curve del questionario su **tutto** il dominio, non su un campione, comprese le due + coppie di parametri di `score_decreasing` — quella normale e quella dell'alcol (7→14); +- `score_increasing_plateau` con `worst` **diverso da zero**: zero è il caso degenere in + cui un errore sull'offset resterebbe invisibile; +- `vo2max_from_2km_walk` variando **un parametro alla volta** dal caso base, così una + divergenza dice anche quale dei cinque coefficienti è sbagliato; +- i confini di banda **esatti** delle funzioni a bande (composizione, pressione, plank, + flamingo, SpO2), che una griglia a passo regolare salterebbe; +- le tre tabelle dei sollevamenti sul rapporto col peso corporeo, per entrambi i sessi. - [ ] **Step 2: Eseguilo e guarda l'esito**