diff --git a/.changeset/grade-matches-shown-score.md b/.changeset/grade-matches-shown-score.md new file mode 100644 index 0000000..6ac9893 --- /dev/null +++ b/.changeset/grade-matches-shown-score.md @@ -0,0 +1,5 @@ +--- +'@advance-labs/scoring': patch +--- + +Grade the rounded overall score, so the letter grade always matches the number shown. A raw 59.7 displayed as 60 but graded F (seen on amazon.com and adidas.com), and 79.6 displayed as 80 but graded C. diff --git a/apps/chrome-extension/package.json b/apps/chrome-extension/package.json index a128842..4c6666c 100644 --- a/apps/chrome-extension/package.json +++ b/apps/chrome-extension/package.json @@ -1,6 +1,6 @@ { "name": "@advance-labs/chrome-extension", - "version": "0.3.1", + "version": "0.3.2", "private": true, "type": "module", "description": "AEO/GEO Chrome MV3 extension — runs a client-side AI-readiness audit on the active tab and exports a PDF.", diff --git a/packages/scoring/src/engine.test.ts b/packages/scoring/src/engine.test.ts index 3d88a21..78c1bdd 100644 --- a/packages/scoring/src/engine.test.ts +++ b/packages/scoring/src/engine.test.ts @@ -67,6 +67,16 @@ describe('runRules', () => { expect(score.criticalCount).toBe(1); }); + it('grades the score it shows: 59.7 displays as 60 and grades D, not F', async () => { + // Seen live on amazon.com and adidas.com ("60, grade F") and microsoft.com ("80, grade C"). + const { score } = await runRules(goodContext(), [ + { ...passRule, weight: 597 }, + { ...failRule, weight: 403 }, + ]); + expect(score.overall).toBe(60); + expect(score.grade).toBe('D'); + }); + it('runs async rules and folds a thrown rule into a failed finding', async () => { const ctx = goodContext(); const { categories, score } = await runRules(ctx, [asyncRule, throwingRule]); diff --git a/packages/scoring/src/engine.ts b/packages/scoring/src/engine.ts index 743d2cb..e8a0187 100644 --- a/packages/scoring/src/engine.ts +++ b/packages/scoring/src/engine.ts @@ -162,10 +162,12 @@ export function aggregateScore(categories: ScoreCategory[]): Score { } } - const overall = weightTotal > 0 ? clampScore(weightedSum / weightTotal) : 100; + // Round once and grade the rounded number, so the grade always matches the score shown + // (unrounded 79.6 used to display "80" and grade C). + const overall = Math.round(weightTotal > 0 ? clampScore(weightedSum / weightTotal) : 100); return { - overall: Math.round(overall), + overall, grade: scoreToGrade(overall), categories, passedCount,