3cda76b4c4
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>
103 lines
4.1 KiB
TypeScript
103 lines
4.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
fascia, misurazioniNelPeriodo, kpis, distribuzione, classificaRecupero, confrontoSegmenti,
|
|
} from '../src/lib/stress-index/analytics';
|
|
import type { Alert, Client, Measurement } from '../src/lib/stress-index/data';
|
|
|
|
const PERIODO = { from: '2026-07-01', to: '2026-07-31' };
|
|
|
|
const misura = (date: string, stress: number, recovery: number): Measurement =>
|
|
({ date, stress, recovery, balance: 50, energy: 50, type: 'Riposo', durationMin: 20 });
|
|
|
|
const cliente = (id: string, name: string, sex: 'M' | 'F', age: number): Client =>
|
|
({ id, name, initials: 'XX', age, sex, tags: ['test'], email: `${id}@esempio.it`,
|
|
since: '2024-01-01', lastMeasurement: '2026-07-10', stress: 50, alerts: 0 });
|
|
|
|
const clients = [cliente('a', 'A. Uno', 'M', 30), cliente('b', 'B. Due', 'F', 55), cliente('c', 'C. Tre', 'F', 40)];
|
|
const byClient: Record<string, Measurement[]> = {
|
|
// dentro il periodo
|
|
a: [misura('2026-07-05', 10, 90), misura('2026-07-12', 30, 70)],
|
|
// una dentro, una fuori
|
|
b: [misura('2026-06-20', 80, 20), misura('2026-07-20', 90, 30)],
|
|
// tutte fuori periodo
|
|
c: [misura('2026-05-01', 50, 50)],
|
|
};
|
|
const alerts: Alert[] = [
|
|
{ clientId: 'a', clientName: 'A. Uno', text: 'x', severity: 'warn', when: '2026-07-03' },
|
|
{ clientId: 'b', clientName: 'B. Due', text: 'y', severity: 'error', when: '2026-06-01' },
|
|
];
|
|
|
|
describe('fasce di score', () => {
|
|
it('assegna gli estremi alla fascia giusta', () => {
|
|
expect(fascia(0)).toBe('0-20');
|
|
expect(fascia(20)).toBe('0-20');
|
|
expect(fascia(21)).toBe('21-40');
|
|
expect(fascia(60)).toBe('41-60');
|
|
expect(fascia(81)).toBe('81-100');
|
|
expect(fascia(100)).toBe('81-100');
|
|
});
|
|
});
|
|
|
|
describe('filtro sul periodo', () => {
|
|
it('tiene solo le misurazioni comprese fra le due date, estremi inclusi', () => {
|
|
const dentro = misurazioniNelPeriodo(byClient, PERIODO);
|
|
expect(dentro.a.map((m) => m.date)).toEqual(['2026-07-05', '2026-07-12']);
|
|
expect(dentro.b.map((m) => m.date)).toEqual(['2026-07-20']);
|
|
expect(dentro.c).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('kpi', () => {
|
|
it('conta clienti attivi, misurazioni, alert del periodo e aderenza', () => {
|
|
expect(kpis(clients, byClient, alerts, PERIODO)).toEqual([
|
|
{ label: 'Clienti attivi', value: 2, note: '/ 3 totali' },
|
|
{ label: 'Misurazioni', value: 3, note: 'nel periodo' },
|
|
{ label: 'Alert attivi', value: 1, note: 'nel periodo' },
|
|
{ label: 'Aderenza', value: '67%', note: 'hanno misurato' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('distribuzione per fascia', () => {
|
|
it('conta ogni metrica nella propria fascia', () => {
|
|
const righe = distribuzione(byClient, PERIODO);
|
|
const perFascia = Object.fromEntries(righe.map((r) => [r.range, r]));
|
|
// stress: 10 → 0-20, 30 → 21-40, 90 → 81-100
|
|
expect(perFascia['0-20'].stress).toBe(1);
|
|
expect(perFascia['21-40'].stress).toBe(1);
|
|
expect(perFascia['81-100'].stress).toBe(1);
|
|
// recovery: 90 → 81-100, 70 → 61-80, 30 → 21-40
|
|
expect(perFascia['81-100'].recovery).toBe(1);
|
|
expect(perFascia['61-80'].recovery).toBe(1);
|
|
expect(perFascia['21-40'].recovery).toBe(1);
|
|
// le tre misurazioni hanno tutte balance ed energy a 50
|
|
expect(perFascia['41-60'].balance).toBe(3);
|
|
expect(perFascia['41-60'].energy).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('classifica per recupero', () => {
|
|
it('ordina dal recupero medio più alto e ignora chi non ha misurato', () => {
|
|
expect(classificaRecupero(clients, byClient, PERIODO)).toEqual([
|
|
{ clientId: 'a', name: 'A. Uno', recovery: 80, count: 2 },
|
|
{ clientId: 'b', name: 'B. Due', recovery: 30, count: 1 },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('confronto segmenti', () => {
|
|
it('media stress e recupero per sesso', () => {
|
|
expect(confrontoSegmenti(clients, byClient, PERIODO, 'sesso')).toEqual([
|
|
{ gruppo: 'Uomini', stress: 20, recovery: 80 },
|
|
{ gruppo: 'Donne', stress: 90, recovery: 30 },
|
|
]);
|
|
});
|
|
|
|
it('raggruppa per fascia di età', () => {
|
|
expect(confrontoSegmenti(clients, byClient, PERIODO, 'eta')).toEqual([
|
|
{ gruppo: 'Fino a 34', stress: 20, recovery: 80 },
|
|
{ gruppo: '50 e oltre', stress: 90, recovery: 30 },
|
|
]);
|
|
});
|
|
});
|