From a714c56e81eacc97739b336ba43c19f03fc29b85 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 20 Feb 2026 23:47:48 +0100 Subject: [PATCH] Fix % recommended: derive from boolean criteria when binaryDecision is null When scoringMode is not 'binary', binaryDecision is null even though jurors answer boolean criteria (e.g. "Do you recommend?"). Now falls back to checking boolean values in criterionScoresJson. Hides the recommendation line entirely when no boolean data exists. Fixed in both analytics.ts (observer) and project.ts (admin). Co-Authored-By: Claude Opus 4.6 --- src/server/routers/analytics.ts | 25 +++++++++++++++++++++++-- src/server/routers/project.ts | 22 ++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/server/routers/analytics.ts b/src/server/routers/analytics.ts index 4bbbed1..317a421 100644 --- a/src/server/routers/analytics.ts +++ b/src/server/routers/analytics.ts @@ -1287,7 +1287,26 @@ export const analyticsRouter = router({ const globalScores = submittedEvaluations .map((e) => e.globalScore) .filter((s): s is number => s !== null) - const yesVotes = submittedEvaluations.filter((e) => e.binaryDecision === true).length + + // Count recommendations: first check binaryDecision, then fall back to + // boolean criteria in criterionScoresJson (when scoringMode isn't 'binary') + const yesVotes = submittedEvaluations.filter((e) => { + if (e.binaryDecision != null) return e.binaryDecision === true + // Fall back: check if any boolean criterion is true + const scores = e.criterionScoresJson as Record | null + if (!scores) return false + const boolValues = Object.values(scores).filter((v) => typeof v === 'boolean') + return boolValues.length > 0 && boolValues.every((v) => v === true) + }).length + + // Check if recommendation data exists at all + const hasRecommendationData = submittedEvaluations.some((e) => { + if (e.binaryDecision != null) return true + const scores = e.criterionScoresJson as Record | null + if (!scores) return false + return Object.values(scores).some((v) => typeof v === 'boolean') + }) + stats = { totalEvaluations: submittedEvaluations.length, averageGlobalScore: globalScores.length > 0 @@ -1297,7 +1316,9 @@ export const analyticsRouter = router({ maxScore: globalScores.length > 0 ? Math.max(...globalScores) : null, yesVotes, noVotes: submittedEvaluations.length - yesVotes, - yesPercentage: (yesVotes / submittedEvaluations.length) * 100, + yesPercentage: hasRecommendationData + ? (yesVotes / submittedEvaluations.length) * 100 + : null, } } diff --git a/src/server/routers/project.ts b/src/server/routers/project.ts index ac60da4..e4bfcda 100644 --- a/src/server/routers/project.ts +++ b/src/server/routers/project.ts @@ -1190,7 +1190,23 @@ export const projectRouter = router({ const globalScores = submittedEvaluations .map((e) => e.globalScore) .filter((s): s is number => s !== null) - const yesVotes = submittedEvaluations.filter((e) => e.binaryDecision === true).length + + // Count recommendations: check binaryDecision first, fall back to boolean criteria + const yesVotes = submittedEvaluations.filter((e) => { + if (e.binaryDecision != null) return e.binaryDecision === true + const scores = e.criterionScoresJson as Record | null + if (!scores) return false + const boolValues = Object.values(scores).filter((v) => typeof v === 'boolean') + return boolValues.length > 0 && boolValues.every((v) => v === true) + }).length + + const hasRecommendationData = submittedEvaluations.some((e) => { + if (e.binaryDecision != null) return true + const scores = e.criterionScoresJson as Record | null + if (!scores) return false + return Object.values(scores).some((v) => typeof v === 'boolean') + }) + stats = { totalEvaluations: submittedEvaluations.length, averageGlobalScore: globalScores.length > 0 @@ -1200,7 +1216,9 @@ export const projectRouter = router({ maxScore: globalScores.length > 0 ? Math.max(...globalScores) : null, yesVotes, noVotes: submittedEvaluations.length - yesVotes, - yesPercentage: (yesVotes / submittedEvaluations.length) * 100, + yesPercentage: hasRecommendationData + ? (yesVotes / submittedEvaluations.length) * 100 + : null, } }