Formule Potenza Ciclismo
Fondamenti Matematici delle Metriche Bike Analytics
Guida all'Implementazione
Questa pagina fornisce formule copia-incolla e metodi di calcolo passo-passo per tutte le metriche Bike Analytics. Usale per implementazioni personalizzate, verifica o una comprensione più profonda dell'allenamento basato sulla potenza.
⚠️ Note di Implementazione
- Tutti i valori di potenza in watt (W), tempo in secondi se non specificato
- FTP e CP sono soglie individuali—nessun valore universale
- Valida sempre gli input per range ragionevoli (0-2000W tipico)
- Gestisci i casi limite (divisione per zero, potenza negativa)
- I dati di potenza richiedono intervalli di registrazione di 1 secondo per accuratezza
Metriche Prestazionali Core
1. Training Stress Score (TSS)
Formula:
Esempio Svolto:
Scenario: Uscita 2 ore, NP = 235W, FTP = 250W
- Calcola IF: IF = 235 / 250 = 0.94
- Durata in secondi: 2 ore × 3600 = 7200 secondi
- TSS = (7200 × 235 × 0.94) / (250 × 3600) × 100
- TSS = 1,590,720 / 900,000 × 100 = 176.7 TSS
Interpretazione: Allenamento duro (>150 TSS), aspettati 2-3 giorni di recupero
Implementazione JavaScript:
function calculateTSS(durationSeconds, normalizedPower, ftp) {
const intensityFactor = normalizedPower / ftp;
const tss = (durationSeconds * normalizedPower * intensityFactor) / (ftp * 3600) * 100;
return Math.round(tss);
}
// Esempio uso:
const tss = calculateTSS(7200, 235, 250);
// Ritorna: 177
2. Normalized Power (NP)
Algoritmo (media mobile 30 secondi):
Perché la 4a Potenza?
La relazione quartica (4a potenza) riflette il costo fisiologico non lineare degli sforzi variabili. Un'uscita con scatti e recuperi costa più energia di una potenza costante alla stessa media.
Esempio:
- Uscita costante: 200W per 1 ora → NP = 200W, Media = 200W
- Uscita variabile: Alternando 300W/100W → Media = 200W, NP = 225W
Stessa potenza media, ma l'uscita variabile ha NP 12% più alta dovuto al costo fisiologico degli scatti
Implementazione JavaScript:
function calculateNormalizedPower(powerData) {
// powerData è array di valori potenza 1-secondo
// Step 1: Calcola medie mobili 30-secondi
const rollingAvgs = [];
for (let i = 29; i < powerData.length; i++) {
const window = powerData.slice(i - 29, i + 1);
const avg = window.reduce((sum, p) => sum + p, 0) / 30;
rollingAvgs.push(avg);
}
// Step 2: Eleva alla 4a potenza
const powered = rollingAvgs.map(p => Math.pow(p, 4));
// Step 3: Media delle 4e potenze
const avgPowered = powered.reduce((sum, p) => sum + p, 0) / powered.length;
// Step 4: Prendi radice 4a
const np = Math.pow(avgPowered, 0.25);
return Math.round(np);
}
// Esempio uso:
const powerData = [/* array potenza 1-secondo */];
const np = calculateNormalizedPower(powerData);
// Ritorna: NP in watt
3. Intensity Factor (IF)
Formula:
Range Interpretazione:
| Range IF | Livello Sforzo | Esempio Allenamento |
|---|---|---|
| < 0.75 | Recupero / Facile | Uscita recupero attivo, Zona 1-2 |
| 0.75 - 0.85 | Endurance | Uscita lunga costante, base aerobica |
| 0.85 - 0.95 | Tempo | Allenamento sweet spot, intervalli tempo |
| 0.95 - 1.05 | Soglia | Intervalli FTP, sforzo cronometro |
| 1.05 - 1.15 | VO₂max | Intervalli 5-minuti, gara criterium |
| > 1.15 | Anaerobico | Sprint brevi, attacchi, scatti MTB |
Esempio Calcolo:
Scenario: NP = 235W, FTP = 250W
IF = 235 / 250 = 0.94
Interpretazione: Alto tempo / sforzo sub-soglia, sostenibile per 2-3 ore
function calculateIF(normalizedPower, ftp) {
return (normalizedPower / ftp).toFixed(2);
}
// Esempio:
const if_value = calculateIF(235, 250);
// Ritorna: 0.94
4. Variability Index (VI)
Formula:
Interpretazione per Disciplina:
| Disciplina | VI Tipico | Significato |
|---|---|---|
| Strada TT / Sforzo Costante | 1.00 - 1.05 | Potenza molto costante, pacing ottimale |
| Gara Strada | 1.05 - 1.10 | Qualche scatto, generalmente costante |
| Criterium | 1.10 - 1.20 | Accelerazioni e attacchi frequenti |
| Mountain Bike XC | 1.15 - 1.30+ | Altamente variabile, scatti costanti |
Esempio Calcolo:
Gara Strada: NP = 240W, Potenza Avg = 230W
VI = 240 / 230 = 1.04 (pacing costante)
Gara MTB: NP = 285W, Potenza Avg = 235W
VI = 285 / 235 = 1.21 (molto variabile, sforzi a scatti)
function calculateVI(normalizedPower, averagePower) {
return (normalizedPower / averagePower).toFixed(2);
}
// Esempio:
const vi_road = calculateVI(240, 230); // Ritorna: 1.04
const vi_mtb = calculateVI(285, 235); // Ritorna: 1.21
Critical Power & W' (Capacità Anaerobica)
5. Critical Power (CP) - Modello Lineare
Formula:
Calcolo da Sforzi Multipli:
Richiede 2-4 sforzi massimali a diverse durate (es. 3, 5, 12, 20 minuti)
Dati Esempio:
| Durata | Potenza (W) | Lavoro Totale (kJ) |
|---|---|---|
| 3 min (180s) | 400W | 72 kJ |
| 5 min (300s) | 365W | 109.5 kJ |
| 12 min (720s) | 310W | 223.2 kJ |
| 20 min (1200s) | 285W | 342 kJ |
Usando regressione lineare (Lavoro = CP × Tempo + W'):
- CP = 270W (pendenza linea regressione)
- W' = 18.5 kJ (intercetta y)
Implementazione JavaScript:
function calculateCP_Linear(efforts) {
// efforts = [{duration: seconds, power: watts}, ...]
const times = efforts.map(e => e.duration);
const work = efforts.map(e => e.power * e.duration / 1000); // kJ
// Regressione lineare: work = CP * time + W'
const n = efforts.length;
const sumT = times.reduce((a, b) => a + b, 0);
const sumW = work.reduce((a, b) => a + b, 0);
const sumTW = times.reduce((sum, t, i) => sum + t * work[i], 0);
const sumTT = times.reduce((sum, t) => sum + t * t, 0);
const CP = (n * sumTW - sumT * sumW) / (n * sumTT - sumT * sumT);
const Wprime = (sumW - CP * sumT) / n;
return {
CP: Math.round(CP * 10) / 10, // watt
Wprime: Math.round(Wprime * 10) / 10 // kJ
};
}
// Esempio uso:
const efforts = [
{duration: 180, power: 400},
{duration: 300, power: 365},
{duration: 720, power: 310},
{duration: 1200, power: 285}
];
const result = calculateCP_Linear(efforts);
// Ritorna: { CP: 270.0, Wprime: 18.5 }
6. W' Balance (W'bal) - Modello Equazione Differenziale
Formule:
W'exp(t) = ∫(P(t) - CP) dt
W'rec(t) = W' × (1 - e^(-t/τ))
e ΔCP = (CP - P(t))
Esempio Mondo Reale:
Specifiche Ciclista: CP = 270W, W' = 18.5 kJ
Scenario 1 - Attacco Duro:
- Ciclista scatta a 400W per 30 secondi
- Spesa W': (400 - 270) × 30 = 3,900 J = 3.9 kJ
- W'bal rimanente: 18.5 - 3.9 = 14.6 kJ
Scenario 2 - Recupero:
- Dopo attacco, scende a 200W (70W sotto CP) per 2 minuti
- ΔCP = 270 - 200 = 70W
- τ = 546 × e^(-0.01 × 70) + 316 = 588 secondi
- Recupero in 120s: 18.5 × (1 - e^(-120/588)) = 3.5 kJ recuperati
- Nuovo W'bal: 14.6 + 3.5 = 18.1 kJ
Implementazione JavaScript:
function calculateWbalance(powerData, CP, Wprime) {
// powerData = array di {time: seconds, power: watts}
let wbal = Wprime * 1000; // Converti in joule
const wbalHistory = [];
for (let i = 1; i < powerData.length; i++) {
const dt = powerData[i].time - powerData[i-1].time;
const power = powerData[i].power;
if (power > CP) {
// Spesa sopra CP
const expenditure = (power - CP) * dt;
wbal -= expenditure;
} else {
// Recupero sotto CP
const deltaCP = CP - power;
const tau = 546 * Math.exp(-0.01 * deltaCP) + 316;
const recovery = (Wprime * 1000 - wbal) * (1 - Math.exp(-dt / tau));
wbal += recovery;
}
// Assicura che W'bal non superi max o vada negativo
wbal = Math.max(0, Math.min(wbal, Wprime * 1000));
wbalHistory.push({
time: powerData[i].time,
wbal: wbal / 1000, // kJ
percent: (wbal / (Wprime * 1000)) * 100
});
}
return wbalHistory;
}
// Esempio uso:
const powerData = [
{time: 0, power: 200},
{time: 1, power: 210},
// ... resto dati uscita
];
const wbalHistory = calculateWbalance(powerData, 270, 18.5);
// Ritorna array valori W'bal nel tempo
Performance Management Chart (PMC)
7. Calcoli CTL, ATL, TSB
Formule (Medie Mobili Ponderate Esponenzialmente):
Definizioni Metriche:
- CTL (Chronic Training Load): Media ponderata esponenziale 42-giorni - rappresenta fitness
- ATL (Acute Training Load): Media ponderata esponenziale 7-giorni - rappresenta fatica
- TSB (Training Stress Balance): Forma = Fitness - Fatica
Esempio Svolto (Blocco Allenamento 7-Giorni):
| Giorno | TSS | CTL | ATL | TSB | Stato |
|---|---|---|---|---|---|
| Lun | 100 | 75.0 | 80.0 | -5.0 | Allenamento |
| Mar | 50 | 74.4 | 75.7 | -1.3 | Recupero |
| Mer | 120 | 75.5 | 82.0 | -6.5 | Allenamento Duro |
| Gio | 0 | 73.7 | 70.3 | +3.4 | Giorno Riposo |
| Ven | 80 | 73.8 | 71.7 | +2.1 | Moderato |
| Sab | 150 | 75.6 | 82.9 | -7.3 | Uscita Lunga |
| Dom | 40 | 74.8 | 76.8 | -2.0 | Recupero |
Interpretazione TSB:
| Range TSB | Stato | Azione |
|---|---|---|
| < -30 | Alto Rischio | Avviso sovrallenamento - ridurre carico |
| -30 a -10 | Allenamento Duro | Costruendo fitness, monitorare recupero |
| -10 a +5 | Ottimale | Zona allenamento normale |
| +5 a +15 | Pronto Gara | Picco forma - gareggia questo weekend |
| > +25 | Detraining | Perdita fitness - aumentare carico |
Implementazione JavaScript:
function calculatePMC(workouts) {
// workouts = [{date: "YYYY-MM-DD", tss: number}, ...]
let ctl = 0, atl = 0;
const results = [];
workouts.forEach(workout => {
// Aggiorna CTL (costante tempo 42-giorni)
ctl = ctl + (workout.tss - ctl) / 42;
// Aggiorna ATL (costante tempo 7-giorni)
atl = atl + (workout.tss - atl) / 7;
// Calcola TSB (CTL ieri - ATL oggi per calcolo tradizionale)
// Per semplicità qui usando valori correnti
const tsb = ctl - atl;
results.push({
date: workout.date,
tss: workout.tss,
ctl: Math.round(ctl * 10) / 10,
atl: Math.round(atl * 10) / 10,
tsb: Math.round(tsb * 10) / 10,
status: getTSBStatus(tsb)
});
});
return results;
}
function getTSBStatus(tsb) {
if (tsb < -30) return "Alto Rischio";
if (tsb < -10) return "Allenamento Duro";
if (tsb < 5) return "Ottimale";
if (tsb < 15) return "Pronto Gara";
return "Detraining";
}
// Esempio uso:
const workouts = [
{date: "2025-01-01", tss: 100},
{date: "2025-01-02", tss: 50},
{date: "2025-01-03", tss: 120},
// ... altri allenamenti
];
const pmc = calculatePMC(workouts);
// Ritorna array con CTL, ATL, TSB per ogni giorno
Metriche Potenza-Peso & Salita
8. Rapporto Potenza-Peso
Formula:
Benchmark FTP W/kg:
| Livello | Uomo W/kg | Donna W/kg | Categoria |
|---|---|---|---|
| Ricreativo | 2.5 - 3.5 | 2.0 - 3.0 | Ciclista fitness |
| Competitivo | 3.5 - 4.5 | 3.0 - 4.0 | Cat 3-4, amatore age group |
| Avanzato | 4.5 - 5.5 | 4.0 - 5.0 | Cat 1-2, amatore forte |
| Amatore Elite | 5.5 - 6.0 | 5.0 - 5.5 | Livello nazionale |
| Professionista | 6.0 - 7.0+ | 5.5 - 6.5+ | World Tour, Grand Tour GC |
Esempio Calcolo:
Scenario: Ciclista con FTP = 275W, massa corporea = 70kg
W/kg = 275 / 70 = 3.93 W/kg
Interpretazione: Livello competitivo, capace in gare collinari
function calculateWattsPerKg(power, bodyMassKg) {
return (power / bodyMassKg).toFixed(2);
}
// Esempio:
const wpkg = calculateWattsPerKg(275, 70);
// Ritorna: 3.93
9. VAM (Velocità Ascensionale Media)
Formula:
Benchmark VAM:
| VAM (m/h) | Livello | Esempio |
|---|---|---|
| 600 - 900 | Ricreativo | Ciclista club su salita locale |
| 900 - 1200 | Competitivo | Buon amatore su Alpe d'Huez |
| 1200 - 1500 | Amatore Elite | Scalatore livello nazionale |
| 1500 - 1800 | Professionista | Gregario World Tour |
| > 1800 | Vincitore Grand Tour | Pogačar, Vingegaard su salite chiave |
Esempio Calcolo:
Scenario: Salita Alpe d'Huez
- Guadagno elevazione: 1100 metri
- Tempo: 55 minuti = 0.917 ore
- VAM = 1100 / 0.917 = 1200 m/h
Interpretazione: Prestazione scalata livello competitivo
function calculateVAM(elevationGainMeters, timeMinutes) {
const hours = timeMinutes / 60;
return Math.round(elevationGainMeters / hours);
}
// Esempio:
const vam = calculateVAM(1100, 55);
// Ritorna: 1200 m/h
10. Stima W/kg da VAM
Formula:
Esempio Calcolo:
Scenario: Salita con 8% pendenza media, VAM = 1200 m/h
W/kg = 1200 / 100 / (8 + 3)
W/kg = 12 / 11 = 4.36 W/kg
Verifica: Con ciclista 70kg → 305W potenza sostenuta su salita
function estimateWkgFromVAM(vam, gradientPercent) {
return (vam / 100 / (gradientPercent + 3)).toFixed(2);
}
// Esempio:
const wkg = estimateWkgFromVAM(1200, 8);
// Ritorna: 4.36