import type { Audit, UserAnswer, ScoreResult, CategoryScore, RiskBand, } from '../types'; /** * Calculate the overall score and per-category breakdown. * Works with any audit — completely data-driven. */ export function calculateScore(audit: Audit, answers: UserAnswer[]): ScoreResult { const { categories, band_headlines } = audit; // Overall raw score (sum of all answer points). const rawScore = answers.reduce((sum, a) => sum + a.points, 0); // Max possible score (sum of max points per question). const maxPossible = audit.questions.reduce((sum, q) => { const maxPts = Math.max(...q.answers.map((a) => a.points)); return sum + maxPts; }, 0); const percentage = maxPossible > 0 ? Math.round((rawScore / maxPossible) * 100) : 0; // Per-category scores. const categoryScores: CategoryScore[] = categories.map((cat) => { const catQuestions = audit.questions.filter((q) => q.categoryId === cat.id); const catAnswers = answers.filter((a) => a.categoryId === cat.id); const catRaw = catAnswers.reduce((s, a) => s + a.points, 0); const catMax = catQuestions.reduce( (s, q) => s + Math.max(...q.answers.map((a) => a.points)), 0 ); return { id: cat.id, name: cat.name, color: cat.color, score: catMax > 0 ? Math.round((catRaw / catMax) * 100) : 0, rawScore: catRaw, maxScore: catMax, }; }); // Risk band. const riskBand = getRiskBand(percentage); // Weakest two categories. const weakestCategories = [...categoryScores] .sort((a, b) => a.score - b.score) .slice(0, 2); return { rawScore, percentage, riskBand, headline: band_headlines[riskBand], categoryScores, weakestCategories, }; } export function getRiskBand(percentage: number): RiskBand { if (percentage <= 30) return 'critical'; if (percentage <= 55) return 'high'; if (percentage <= 75) return 'moderate'; return 'low'; } export function getRiskEmoji(band: RiskBand): string { switch (band) { case 'critical': return '🔴'; case 'high': return '🟠'; case 'moderate': return '🟡'; case 'low': return '🟢'; } }