import type { ZScoreResult, Classification, ClassificationSeverity, Indicator, I18nStrings, } from './types.js'; /** * SISVAN/WHO classification cutpoints by indicator. * * Weight-for-age (0-10 years): * < -3: Muito baixo peso para a idade * -3 to < -2: Baixo peso para a idade * -2 to +2: Peso adequado para a idade * > +2: Peso elevado para a idade * * Length/Height-for-age (0-19 years): * < -3: Muito baixa estatura para a idade * -3 to < -2: Baixa estatura para a idade * >= -2: Estatura adequada para a idade * * BMI-for-age: * 0-5 years: * < -3: Magreza acentuada * -3 to < -2: Magreza * -2 to +1: Eutrofia * +1 to +2: Risco de sobrepeso * +2 to +3: Sobrepeso * > +3: Obesidade * 5-19 years: * < -3: Magreza acentuada * -3 to < -2: Magreza * -2 to +1: Eutrofia * +1 to +2: Sobrepeso * +2 to +3: Obesidade * > +3: Obesidade grave * * Weight-for-length/height (0-5 years): * Same as BMI 0-5 years * * Head circumference-for-age: * < -2: Abaixo do esperado * -2 to +2: Adequado * > +2: Acima do esperado */ interface CutpointDef { maxZ: number; severity: ClassificationSeverity; } const WFA_CUTPOINTS: CutpointDef[] = [ { maxZ: -3, severity: 'very-low' }, { maxZ: -2, severity: 'low' }, { maxZ: 2, severity: 'adequate' }, { maxZ: Infinity, severity: 'high' }, ]; const LHFA_CUTPOINTS: CutpointDef[] = [ { maxZ: -3, severity: 'very-low' }, { maxZ: -2, severity: 'low' }, { maxZ: Infinity, severity: 'adequate' }, ]; const BMI_0_5_CUTPOINTS: CutpointDef[] = [ { maxZ: -3, severity: 'very-low' }, { maxZ: -2, severity: 'low' }, { maxZ: 1, severity: 'adequate' }, { maxZ: 2, severity: 'risk' }, { maxZ: 3, severity: 'high' }, { maxZ: Infinity, severity: 'very-high' }, ]; const BMI_5_19_CUTPOINTS: CutpointDef[] = [ { maxZ: -3, severity: 'very-low' }, { maxZ: -2, severity: 'low' }, { maxZ: 1, severity: 'adequate' }, { maxZ: 2, severity: 'high' }, { maxZ: 3, severity: 'very-high' }, { maxZ: Infinity, severity: 'very-high' }, // Obesidade grave ]; const HC_CUTPOINTS: CutpointDef[] = [ { maxZ: -2, severity: 'low' }, { maxZ: 2, severity: 'adequate' }, { maxZ: Infinity, severity: 'high' }, ]; function classifyByZ(z: number, cutpoints: CutpointDef[]): ClassificationSeverity { for (const cp of cutpoints) { if (z < cp.maxZ) return cp.severity; } return cutpoints[cutpoints.length - 1].severity; } function getCutpoints(indicator: Indicator, ageInDays: number): CutpointDef[] { switch (indicator) { case 'weight-for-age': return WFA_CUTPOINTS; case 'length-height-for-age': return LHFA_CUTPOINTS; case 'bmi-for-age': return ageInDays < 1857 ? BMI_0_5_CUTPOINTS : BMI_5_19_CUTPOINTS; case 'weight-for-length': case 'weight-for-height': return BMI_0_5_CUTPOINTS; case 'head-circumference-for-age': return HC_CUTPOINTS; } } /** Labels SISVAN por indicador e severidade */ const SISVAN_LABELS: Record>> = { 'weight-for-age': { 'very-low': 'Muito baixo peso para a idade', low: 'Baixo peso para a idade', adequate: 'Peso adequado para a idade', high: 'Peso elevado para a idade', }, 'length-height-for-age': { 'very-low': 'Muito baixa estatura para a idade', low: 'Baixa estatura para a idade', adequate: 'Estatura adequada para a idade', }, 'bmi-for-age': { 'very-low': 'Magreza acentuada', low: 'Magreza', adequate: 'Eutrofia', risk: 'Risco de sobrepeso', high: 'Sobrepeso', 'very-high': 'Obesidade', }, 'weight-for-length': { 'very-low': 'Magreza acentuada', low: 'Magreza', adequate: 'Eutrofia', risk: 'Risco de sobrepeso', high: 'Sobrepeso', 'very-high': 'Obesidade', }, 'weight-for-height': { 'very-low': 'Magreza acentuada', low: 'Magreza', adequate: 'Eutrofia', risk: 'Risco de sobrepeso', high: 'Sobrepeso', 'very-high': 'Obesidade', }, 'head-circumference-for-age': { low: 'Abaixo do esperado', adequate: 'Adequado', high: 'Acima do esperado', }, }; /** 5-19 BMI specific labels */ const BMI_5_19_LABELS: Partial> = { 'very-low': 'Magreza acentuada', low: 'Magreza', adequate: 'Eutrofia', high: 'Sobrepeso', 'very-high': 'Obesidade grave', }; /** * Classify a single z-score result. */ export function classify( result: ZScoreResult, ageInDays: number, _strings?: I18nStrings ): Classification { const cutpoints = getCutpoints(result.indicator, ageInDays); const severity = classifyByZ(result.zScore, cutpoints); // Use specific labels for BMI 5-19 let label: string; if (result.indicator === 'bmi-for-age' && ageInDays >= 1857) { label = BMI_5_19_LABELS[severity] ?? severity; } else { label = SISVAN_LABELS[result.indicator][severity] ?? severity; } return { indicator: result.indicator, severity, label, }; } /** * Classify all z-score results. */ export function classifyAll( results: ZScoreResult[], ageInDays: number, strings?: I18nStrings ): Classification[] { return results.map((r) => classify(r, ageInDays, strings)); }