Stress Index: Analytics e Sport rifatti sulle viste reali
Le due sezioni erano ferme al prototipo locale, più povero della piattaforma vera. Analytics ha ora l'intervallo di date, i KPI con la nota sotto il valore, la distribuzione dello score a quattro serie per fascia, le classifiche per recupero medio col numero di misurazioni e il confronto segmenti con selettore di dimensione. Sport diventa Dashboard, Sessioni con filtri atleta/periodo/sport e schede atleta con carico e tendenza. I numeri non sono più costanti scritte a mano: si calcolano dai dati nel periodo scelto. La logica sta in lib/stress-index/analytics.ts e sport.ts, coperta da test — che hanno subito trovato uno slittamento delle settimane dovuto alle date costruite in ora locale invece che in UTC. Le sessioni hanno ora ora di inizio, HR max, RPE e durata al secondo, che il prototipo non prevedeva. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ export const clients: Client[] = [
|
||||
{ id: 'c10', name: 'V. Costa', initials: 'VC', age: 36, sex: 'F', tags: ['ansia', 'lavoro'], email: 'v.costa@esempio.it', since: '2024-07-01', lastMeasurement: '2026-07-10', stress: 58, alerts: 1 },
|
||||
];
|
||||
|
||||
export interface Measurement {
|
||||
export type Measurement = {
|
||||
date: string;
|
||||
stress: number;
|
||||
recovery: number;
|
||||
@@ -120,12 +120,13 @@ export const studioKpis = {
|
||||
retentionRate: 92,
|
||||
};
|
||||
|
||||
// `fill` colora la singola barra: le fasce basse sono "buone", le alte critiche.
|
||||
export const scoreDistribution = [
|
||||
{ range: '0-20', count: 0 },
|
||||
{ range: '21-40', count: 2 },
|
||||
{ range: '41-60', count: 3 },
|
||||
{ range: '61-80', count: 4 },
|
||||
{ range: '81-100', count: 1 },
|
||||
{ range: '0-20', count: 0, fill: '#6f8f6a' },
|
||||
{ range: '21-40', count: 2, fill: '#6f8f6a' },
|
||||
{ range: '41-60', count: 3, fill: '#c08a3e' },
|
||||
{ range: '61-80', count: 4, fill: '#b05a4e' },
|
||||
{ range: '81-100', count: 1, fill: '#8d3f34' },
|
||||
];
|
||||
|
||||
export const segments = [
|
||||
@@ -146,26 +147,66 @@ export const criticalClients = [
|
||||
{ clientId: 'c6', name: 'E. Ricci', stress: 70 },
|
||||
];
|
||||
|
||||
export const sessions = [
|
||||
{ id: 's1', athleteId: 'c2', date: '2026-07-11', type: 'Corsa', durationMin: 45, load: 62, trimp: 58, hrAvg: 142, dfaA1: 0.72 },
|
||||
{ id: 's2', athleteId: 'c5', date: '2026-07-08', type: 'Ciclismo', durationMin: 90, load: 74, trimp: 96, hrAvg: 138, dfaA1: 0.61 },
|
||||
{ id: 's3', athleteId: 'c8', date: '2026-07-12', type: 'Nuoto', durationMin: 40, load: 50, trimp: 45, hrAvg: 128, dfaA1: 0.8 },
|
||||
{ id: 's4', athleteId: 'c2', date: '2026-07-04', type: 'Pesi', durationMin: 60, load: 68, trimp: 70, hrAvg: 145, dfaA1: 0.55 },
|
||||
{ id: 's5', athleteId: 'c5', date: '2026-07-01', type: 'Corsa', durationMin: 50, load: 58, trimp: 52, hrAvg: 135, dfaA1: 0.75 },
|
||||
// Ultimo giorno coperto dai dati dimostrativi dello Sport.
|
||||
const OGGI_SPORT = '2026-07-12';
|
||||
|
||||
export type Session = {
|
||||
id: string;
|
||||
athleteId: string;
|
||||
/** Inizio della sessione, data e ora locali. */
|
||||
startedAt: string;
|
||||
durationSec: number;
|
||||
/** Lo sport può non essere indicato: l'app lo ricava dal sensore solo se dichiarato. */
|
||||
sport: string | null;
|
||||
hrAvg: number;
|
||||
hrMax: number;
|
||||
trimp: number;
|
||||
/** DFA Alpha1 a fine sessione; assente sulle registrazioni troppo brevi. */
|
||||
dfaA1: number | null;
|
||||
/** Sforzo percepito 1-10, inserito a mano dall'atleta. */
|
||||
rpe: number | null;
|
||||
};
|
||||
|
||||
export type Athlete = { clientId: string; name: string; sport: string | null };
|
||||
|
||||
export const athletes: Athlete[] = [
|
||||
{ clientId: 'c2', name: 'A. Neri', sport: 'Corsa' },
|
||||
{ clientId: 'c5', name: 'P. Colombo', sport: 'Ciclismo' },
|
||||
{ clientId: 'c8', name: 'C. Greco', sport: null },
|
||||
];
|
||||
|
||||
export const athletes = [
|
||||
{ clientId: 'c2', name: 'A. Neri', sport: 'Corsa', weeklyLoad: 130 },
|
||||
{ clientId: 'c5', name: 'P. Colombo', sport: 'Ciclismo', weeklyLoad: 164 },
|
||||
{ clientId: 'c8', name: 'C. Greco', sport: 'Nuoto', weeklyLoad: 90 },
|
||||
];
|
||||
// Venti sessioni distribuite sugli ultimi due mesi, generate in modo deterministico dallo
|
||||
// stesso schema seno/coseno usato per le misurazioni: nessun valore casuale, così server e
|
||||
// client disegnano gli stessi grafici.
|
||||
function buildSessions(): Session[] {
|
||||
const out: Session[] = [];
|
||||
const fine = new Date(`${OGGI_SPORT}T00:00:00`);
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const atleta = athletes[i % athletes.length];
|
||||
const giorno = new Date(fine);
|
||||
giorno.setDate(giorno.getDate() - Math.round(i * 2.6));
|
||||
const seed = i * 7 + atleta.clientId.charCodeAt(1);
|
||||
const ora = 7 + (i % 12);
|
||||
const minuti = (seed * 13) % 60;
|
||||
const durata = i % 9 === 0 ? 5 + (seed % 40) : 540 + Math.round(Math.abs(Math.sin(seed)) * 4400);
|
||||
const hrAvg = 95 + Math.round(Math.abs(Math.cos(seed)) * 55);
|
||||
out.push({
|
||||
id: `s${i + 1}`,
|
||||
athleteId: atleta.clientId,
|
||||
startedAt: `${giorno.toISOString().slice(0, 10)}T${String(ora).padStart(2, '0')}:${String(minuti).padStart(2, '0')}`,
|
||||
durationSec: durata,
|
||||
sport: atleta.sport,
|
||||
hrAvg,
|
||||
hrMax: hrAvg + 15 + (seed % 25),
|
||||
trimp: durata < 300 ? 0 : Math.max(4, Math.round((durata / 60) * (hrAvg / 110))),
|
||||
dfaA1: durata < 300 ? null : Math.round((0.85 + Math.abs(Math.sin(seed * 1.7)) * 0.85) * 100) / 100,
|
||||
rpe: i % 7 === 2 ? null : 3 + (seed % 6),
|
||||
});
|
||||
}
|
||||
return out.sort((a, b) => (a.startedAt < b.startedAt ? 1 : -1));
|
||||
}
|
||||
|
||||
export const sessionsPerWeek = [
|
||||
{ week: 'S1', count: 4 }, { week: 'S2', count: 5 }, { week: 'S3', count: 3 },
|
||||
{ week: 'S4', count: 6 }, { week: 'S5', count: 4 }, { week: 'S6', count: 5 },
|
||||
{ week: 'S7', count: 2 }, { week: 'S8', count: 5 }, { week: 'S9', count: 6 },
|
||||
{ week: 'S10', count: 4 }, { week: 'S11', count: 5 }, { week: 'S12', count: 3 },
|
||||
];
|
||||
export const sessions: Session[] = buildSessions();
|
||||
|
||||
// "Oggi" della piattaforma: i dati dimostrativi sono ancorati a questa data, quindi i conteggi
|
||||
// "giorni fa" restano coerenti invece di crescere col passare del tempo reale.
|
||||
|
||||
Reference in New Issue
Block a user