# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. `fitseg` legge un CSV `t_ms,T_C,dT_C` (temperatura di un sensore IR), lo divide in **domini** (riposo / riscaldamento = la salita rapida di un gradino, 2–4 campioni / raffreddamento = il decadimento dopo ogni gradino) e trova per ogni (dominio, serie) l'equazione che meglio lo approssima. Documentazione, commenti e messaggi sono in italiano: mantieni la lingua. Fonti di verità (non duplicarne il contenuto): la documentazione in [docs/](docs/README.md) — [assunzioni](docs/assunzioni.md) (A1–A25, richiamate nel codice), [algoritmi](docs/algoritmi.md), [moduli](docs/moduli.md), [configurazione](docs/configurazione.md), [output](docs/output.md), [test](docs/test.md), [limiti](docs/limiti.md) — e [config.toml](config.toml), unica fonte dei valori: nessun default nel codice, ogni chiave nuova va in `CHIAVI` di [src/fitseg/config.py](src/fitseg/config.py) e in [docs/configurazione.md](docs/configurazione.md). Il [README](README.md) è solo un indice con l'avvio rapido. **Quando cambi comportamento aggiorna il file di docs corrispondente** (e le assunzioni se ne cambia una). ## Comandi ```bash uv venv .venv && uv pip install -e ".[dev]" .venv/bin/fitseg domini input/gen_7.csv # passo 1: output/gen_7/01_domini/ (domini.png, domini.toml), nessun fit .venv/bin/fitseg analizza input/gen_7.csv [--domini output/gen_7/01_domini/domini.toml] # tutti i passi → output/gen_7/{01_domini,02_sovrapposizione,03_report} .venv/bin/pytest # tutti; singolo: -k raffreddamento o tests/test_fit.py .venv/bin/ruff check src tests ./scarica_gen_data.sh [-u] # clona/aggiorna gen_data in extern/ (serve a test_validazione) ./pulisci.sh [-n] [-y] [--solo-output] # svuota output/ e cancella input/*.csv (con conferma); -n dry-run, --solo-output lascia input/ ``` `--config`/`--output` vanno **prima** del sottocomando. `input/` contiene i CSV da analizzare; `output/`, `input/*.csv` e `extern/` (clone di `gen_data`) sono ignorati da git. Ogni analisi scrive in `output//` (`01_domini`, `02_sovrapposizione`, `03_report`, vedi `uscita.PASSI`). ## Architettura (`src/fitseg/`, un modulo per responsabilità) `cli.py` (due sottocomandi, unico punto d'ingresso) → `config.py`, `dati.py` (lettura) → `domini.py` (gradini ⇒ domini, I/O di `domini.toml`) → `analisi.py` (ordina i domini, sceglie il modello) → `fit.py` (`adatta`) con `modelli.py` (registro `MODELLI`) → `uscita.py` (cartelle dei passi, grafici, report; il report incorpora le immagini con percorsi relativi `../01_domini/…`, quindi i nomi dei file/cartelle vanno cambiati in modo coerente). Nel report **ogni grandezza matematica è in LaTeX** (`$…$`, `$$…$$`; l'equazione in `MODELLI` è LaTeX, i valori si formattano con `uscita._num`): usa `\\` solo in stringhe raw e mantieni i `$` bilanciati per riga (c'è un test). - I domini derivano **solo dai gradini positivi** di `serie_riferimento` (`diff > soglia_gradino_c`): la salita (estesa ai campioni adiacenti ancora in salita) è un `riscaldamento`, il tratto dopo è un `raffreddamento`; con N gradini i domini sono 1 + 2N. `domini.toml` è modificabile e deve essere contiguo. - `analisi.analizza_domini`: il raffreddamento **finale** (ultimo) si fitta per primo, libero; i raffreddamenti intermedi fissano `c`/`tau` ai suoi valori (`raffreddamento_intermedio_fissa_da_finale`, `adatta(..., fissi=)`). Il `riscaldamento` non si fitta: `_gradino` ne riporta il salto (`vs["gradino"]`, `sigma` None). Modelli per tipo in `[fit.modelli_per_tipo]` (riposo, raffreddamento). - `fit.adatta`: `least_squares` con perdita robusta; l'esponenziale parte da una griglia su τ. "Non identificabile" = condizione > soglia **o** IC 95 % > |valore| per un parametro diverso da `a`/`c`. σ del rumore **per dominio** da `MAD(d²y)`; tempo traslato a `t' = t − t0`. - Nuovo modello = voce in `modelli.py` + riga in [docs/algoritmi.md](docs/algoritmi.md) §5.1 e in [docs/moduli.md](docs/moduli.md) + nome in `[fit.modelli_per_tipo]`. Non ci sono partenze casuali: il programma è deterministico. ## Test `.venv/bin/pytest` (96 test, ≈ 40 s). Senza `extern/gen_data` i 20 di validazione sono saltati, gli altri non richiedono file esterni. Unitari su un segnale sintetico in [tests/conftest.py](tests/conftest.py) (τ = 16 s, 3 gradini, nessun disturbo a salto); [tests/test_validazione.py](tests/test_validazione.py) genera 20 seed con `extern/gen_data` (clone gitignorato creato da `./scarica_gen_data.sh`; saltato se manca) e confronta gradini, τ, ampiezze e salti con i **parametri veri** sorteggiati (`genera.sorteggia`): è il test che protegge da regressioni sul comportamento reale. [tests/test_report.py](tests/test_report.py) e [tests/test_cli.py](tests/test_cli.py) controllano report, grafici e CLI su CSV sintetici. `gen_data` sorteggia a ogni seed numero di gradini (1–3), k (τ = 11–20 s), rumore e scarti (≤ 4 °C, residui autocorrelati: limite noto, non un bug). ## Gotcha - Il gradino si rileva sommando i campioni consecutivi in salita (`soglia_bordo_sigma`): la soglia `soglia_gradino_c` è sulla **salita complessiva**, non sul singolo salto (un gradino su 2 campioni può avere ogni salto sotto soglia). La salita appartiene al `riscaldamento`; se `soglia_bordo_sigma` è troppo alta un campione parziale può finire nel raffreddamento (`scarta_dopo_salto_n` lo esclude dal fit). - Raffreddamenti intermedi corti: con gli scarti casuali τ non è identificabile da solo (su dati puliti sì), quindi c e τ sono fissi ai valori del finale e basta `n_min_punti_parametri_fissi` (4) campioni invece di `n_min_punti` (6); gli IC di `A` sono condizionati. Senza un finale valido tornano liberi e possono risultare indeterminati. - Il salto Δ del riscaldamento è stimato dai modelli adiacenti (`analisi._affina_salti`, `stima` = `"modelli"`); se manca un modello vicino, dalla misura riportata all'istante del gradino con c e τ del finale (`"misura corretta"`); altrimenti dalla misura sui campioni, che è per difetto (fino a ≈ 10–20 %). - **σ si stima con almeno 8 campioni** (`domini.N_MIN_SIGMA`): con meno la MAD di pochi d²y quantizzati può annullarsi e la perdita robusta degenera (fit scartato come «non identificabile»). Sotto 8, `sigma` è `null` e l'adeguatezza `null`. `risultati.json` non deve mai contenere NaN (`uscita._senza_nan`, `allow_nan=False`). - «Adeguato» = scala **robusta** dei residui (1.4826·MAD) ≤ `scala_residui_vs_rumore_max`·σ, non la RMSE: gli scarti casuali gonfiano la RMSE ma non la scala robusta (vedi docs/algoritmi.md §6). `scarta_dopo_salto_n` vale 0 (la salita è già nel riscaldamento). - Gli intervalli di confidenza coprono il τ vero solo nel 73 % dei casi (disturbi non stazionari, concentrati a inizio raffreddamento): correzione a più lag e sandwich HAC provate e scartate (docs/limiti.md). Non cambiare lo stimatore senza rimisurare la copertura. - Limiti aperti (docs/limiti.md): falso «non adeguato» su ~metà dei raffreddamenti intermedi corti; `soglia_gradino_c` fissa (20 °C) non derivata dai dati; modello del riposo non unico (`T_C` e `dT_C` possono differire). - `extern/gen_data` non è fissato a un commit: `./scarica_gen_data.sh -u` porta l'ultima versione, che può cambiare i seed. CSV di prova: `(cd extern/gen_data && ../../.venv/bin/python genera.py config.toml --seed 7 -o ../../input/gen_7.csv)`. - Cambiando la config di `gen_data` (gradini, rumore) ricontrolla `soglia_gradino_c` e le tolleranze di `tests/test_validazione.py`. - Exit code: 0 ok · 2 uso · 3 config · 4 CSV · 6 domini non validi.