Files
InsanityLab-web/apps/platforms/tests/stress-index-analytics.test.ts
T
AdrianoDev 6301f521fd Separazione: le aree riservate diventano un'app propria (apps/platforms)
Campus, Stress Index e Longevity escono dal sito e diventano
apps.insanitylab.it, con utenti, sessioni, login e database propri.
Deciso da Adriano il 03/09/2026: una repo sola, due applicazioni
autosufficienti, cosi' che ognuna resti estraibile.

Misura che ha guidato il taglio: le tre aree condividevano col sito
SOLO l'identita' - ~170 righe fra auth.ts, middleware.ts e safe-next.ts.
Layout, librerie e dati erano gia' disgiunti.

Lato piattaforme:
- ruoli piattaforme/cliente/trainer/admin, senza quelli redazionali
- canAccessPath con default CHIUSO (nel sito era aperto: li' le rotte non
  elencate erano di chiunque fosse loggato, qui ogni sezione e' di qualcuno)
- tutta l'app protetta, si elencano le eccezioni invece dei prefissi protetti
- /api risponde 401/403, non un redirect verso una pagina di login
- tre database distinti: utenti, dati clinici senza nomi, mappatura coi nomi

Lato sito:
- ROLES tornano i tre redazionali; via le regole e il login pubblico
- il login del pannello RESPINGE un account emigrato invece di aprirgli una
  sessione: senza, landingFor lo mandava su /admin, il middleware glielo
  negava, e si formava un ciclo di redirect che sembra un guasto del sito
- l'header non mostra piu' un menu delle piattaforme: un link che esce

Verificato: sito 20 file / 146 test, piattaforme 26 file / 197 test, entrambi
i build passano, e l'app girata davvero in locale - il ruolo piattaforme entra
in Campus e viene rimbalzato da Longevity, il cliente il contrario.

Non ancora in produzione: manca il record DNS di apps.insanitylab.it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 11:21:12 +00:00

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 },
]);
});
});