48910d84ba
Aggiunge le 24 voci f_* al registro (misure grezze del check-up, senza curva/sotto_dominio: il calcolo non passa da applicaCurva) e motore/fisici.ts (sottoDominiFisici), che chiama le funzioni gia' verificate di test-fisici.ts con eta e sesso per produrre i punteggi di sotto-dominio. Un ingresso mancante lascia il sotto-dominio assente, mai un ripiego. Non ancora collegato a calcolaSessione (prossimo commit). Refs #1 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
140 lines
6.9 KiB
TypeScript
140 lines
6.9 KiB
TypeScript
/**
|
|
* Dalle misure grezze del check-up (registrate coi test_id `f_*` di `registro.ts`) ai
|
|
* punteggi 0-100 di sotto-dominio, con lo stesso raggruppamento che l'oracolo fa in
|
|
* `demo()` (`tests/longevity/riferimento/isl_scoring_engine.py`): ogni sotto-dominio è
|
|
* una chiamata a una funzione di `test-fisici.ts`, con gli argomenti giusti presi dalla
|
|
* mappa delle misure.
|
|
*
|
|
* A differenza del questionario, queste voci NON passano da `applicaCurva`: le sette curve
|
|
* generiche (`bell`, `lin_dec`, ecc.) prendono un solo valore, e quasi tutte le funzioni qui
|
|
* sotto ne vogliono almeno due (il valore grezzo più età e/o sesso, a volte anche il peso
|
|
* corporeo di un'altra misura). Estendere `applicaCurva` per farci passare età e sesso è
|
|
* lavoro del piano degli import (vedi l'intestazione di `test-fisici.ts`), non di questa
|
|
* revisione: qui il calcolo resta una funzione dedicata, non una curva dichiarata nel dato.
|
|
*
|
|
* REGOLA UNICA, la stessa di tutto il motore: un sotto-dominio a cui manca anche un solo
|
|
* ingresso (compresa l'età, dove la funzione la richiede) NON compare nel risultato — mai
|
|
* un valore di ripiego. È lo stesso principio di `applicaCurva`/`paramsValidi`, qui
|
|
* applicato a funzioni con più argomenti invece che a una curva coi parametri incompleti.
|
|
*
|
|
* FUORI DAL PERIMETRO (dichiarato, non dimenticato): spirometria (serve un calcolatore
|
|
* GLI che non esiste ancora), milfit, il 2km walk, il sit-to-stand, la trazione isometrica.
|
|
* L'oracolo li usa nella demo con segnaposto o non li usa affatto nei pesi correnti;
|
|
* restano fuori finché non arriva un test_id e una decisione su come raccoglierli.
|
|
*/
|
|
|
|
import type { Sesso } from './test-fisici';
|
|
import { clamp } from './curve';
|
|
import {
|
|
scoreHandgrip, scorePushup, scoreSollevamentoSuPeso, TIERS_ROW_M, TIERS_ROW_F,
|
|
TIERS_SQUAT_M, TIERS_SQUAT_F, scorePlank, scoreGrassoPercento, scoreMuscoloPercento,
|
|
scoreWhr, scoreVo2max, vo2maxDaMutt, vo2maxDaStepTest, scoreSpo2, scorePressione,
|
|
scoreRecuperoCardiaco, scoreFlamingo, scoreSitAndReach, scoreBackScratch, scoreMobilitaSpalla,
|
|
} from './test-fisici';
|
|
|
|
export type { Sesso };
|
|
|
|
/**
|
|
* `m` è la mappa test_id -> valore grezzo più recente e valido (vedi `misureValideAlla` in
|
|
* `motore/index.ts`), `eta` l'età alla data della sessione (nullable come in tutto il
|
|
* motore: senza età gli score che la richiedono restano assenti, mai calcolati a zero).
|
|
*
|
|
* Ordine dei blocchi: uguale a `demo()` nell'oracolo (forza, composizione, cardio,
|
|
* recupero, stabilità), così un confronto riga per riga fra questo file e quello resta
|
|
* diretto.
|
|
*/
|
|
export function sottoDominiFisici(
|
|
m: Map<string, number>, eta: number | null, sesso: Sesso
|
|
): Record<string, number> {
|
|
const out: Record<string, number> = {};
|
|
|
|
// --- Forza & Struttura ----------------------------------------------------
|
|
const handgrip = m.get('f_handgrip_kg');
|
|
if (handgrip !== undefined && eta !== null) out.handgrip = scoreHandgrip(handgrip, eta, sesso);
|
|
|
|
const pushup = m.get('f_pushup_rip');
|
|
if (pushup !== undefined && eta !== null) out.spinta = scorePushup(pushup, eta, sesso);
|
|
|
|
const pesoKg = m.get('f_peso_kg');
|
|
const rowCarico = m.get('f_row_carico_kg');
|
|
const rowRip = m.get('f_row_rip');
|
|
if (rowCarico !== undefined && pesoKg !== undefined && rowRip !== undefined) {
|
|
out.trazione = scoreSollevamentoSuPeso(rowCarico, pesoKg, rowRip, TIERS_ROW_M, TIERS_ROW_F, sesso);
|
|
}
|
|
|
|
const squatCarico = m.get('f_squat_carico_kg');
|
|
const squatRip = m.get('f_squat_rip');
|
|
if (squatCarico !== undefined && pesoKg !== undefined && squatRip !== undefined) {
|
|
out.arti_inferiori = scoreSollevamentoSuPeso(squatCarico, pesoKg, squatRip, TIERS_SQUAT_M, TIERS_SQUAT_F, sesso);
|
|
}
|
|
|
|
// Il plank pesa su DUE assi (core in Forza & Struttura, plank in Stabilità & Mobilità):
|
|
// confermato da Nicola il 22/08, vedi il commento su PESI_ASSE in registro.ts. Stessa
|
|
// misura, stessa funzione, due chiavi nel risultato — non è una duplicazione per errore.
|
|
const plank = m.get('f_plank_sec');
|
|
if (plank !== undefined) {
|
|
const punteggio = scorePlank(plank, sesso);
|
|
out.core = punteggio;
|
|
out.plank = punteggio;
|
|
}
|
|
|
|
// --- Composizione Corporea -------------------------------------------------
|
|
const grasso = m.get('f_grasso_pct');
|
|
if (grasso !== undefined) out.grasso = scoreGrassoPercento(grasso, sesso);
|
|
|
|
const muscolo = m.get('f_muscolo_pct');
|
|
if (muscolo !== undefined) out.muscolo = scoreMuscoloPercento(muscolo, sesso);
|
|
|
|
const whr = m.get('f_whr');
|
|
if (whr !== undefined) out.whr = scoreWhr(whr, sesso);
|
|
|
|
// --- Cardio-Respiratorio -----------------------------------------------------
|
|
// VO2max: diretto se misurato, altrimenti stimato da MUTT, altrimenti dallo step test —
|
|
// il primo presente vince, come nella cascata di fonti dell'oracolo (che nella demo usa
|
|
// solo MUTT, ma le tre vie coesistono nel resto del file Python).
|
|
const vo2Diretto = m.get('f_vo2max');
|
|
const muttVam = m.get('f_mutt_vam');
|
|
const stepHr = m.get('f_step_hr_recovery');
|
|
let vo2max: number | undefined;
|
|
if (vo2Diretto !== undefined) vo2max = vo2Diretto;
|
|
else if (muttVam !== undefined) vo2max = vo2maxDaMutt(muttVam);
|
|
else if (stepHr !== undefined) vo2max = vo2maxDaStepTest(stepHr, sesso);
|
|
if (vo2max !== undefined && eta !== null) out.vo2max = scoreVo2max(vo2max, eta, sesso);
|
|
|
|
const spo2 = m.get('f_spo2_pct');
|
|
if (spo2 !== undefined) out.wellness_tower_cardio = scoreSpo2(spo2);
|
|
|
|
// --- Recupero & Sistema Nervoso / Energia & Regolazione Stress ---------------
|
|
// hrv è già un punteggio 0-100 (lo calcola lo Stress Index a monte, non una curva di
|
|
// questo motore): qui si limita soltanto, non si ricalcola. Pesa su DUE assi (Recupero
|
|
// e Energia, vedi PESI_ASSE) con la stessa chiave 'hrv' — un solo valore in `out`, letto
|
|
// da entrambi in cascata.ts.
|
|
const hrv = m.get('f_hrv_score');
|
|
if (hrv !== undefined) out.hrv = clamp(hrv);
|
|
|
|
const sistolica = m.get('f_pa_sistolica');
|
|
const diastolica = m.get('f_pa_diastolica');
|
|
if (sistolica !== undefined && diastolica !== undefined) out.pressione = scorePressione(sistolica, diastolica);
|
|
|
|
const hrr = m.get('f_hrr_1min_bpm');
|
|
if (hrr !== undefined) out.hrr = scoreRecuperoCardiaco(hrr);
|
|
|
|
// --- Stabilità & Mobilità Funzionale ------------------------------------------
|
|
const flamingo = m.get('f_flamingo_cadute');
|
|
if (flamingo !== undefined) out.flamingo = scoreFlamingo(flamingo);
|
|
|
|
const sitReach = m.get('f_sit_reach_cm');
|
|
if (sitReach !== undefined) out.sit_and_reach = scoreSitAndReach(sitReach, sesso);
|
|
|
|
const backScratch = m.get('f_back_scratch_cm');
|
|
if (backScratch !== undefined && eta !== null) out.back_scratch = scoreBackScratch(backScratch, eta, sesso);
|
|
|
|
const outreach = m.get('f_spalla_outreach_deg');
|
|
const buckling = m.get('f_spalla_buckling_deg');
|
|
if (outreach !== undefined && buckling !== undefined) {
|
|
out.wellness_tower_shoulder = scoreMobilitaSpalla(outreach, buckling);
|
|
}
|
|
|
|
return out;
|
|
}
|