From 28f26ac4c45c3a1fce468e776de8a6e9de80f869 Mon Sep 17 00:00:00 2001 From: johnmeshulam <55348702+johnmeshulam@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:56:51 +0200 Subject: [PATCH 1/3] Draft --- apps/frontend/locale/en.json | 13 +- apps/frontend/locale/he.json | 11 ++ apps/frontend/locale/pl.json | 13 +- .../final/components/anomaly-alert.tsx | 155 ++++++++++++++++++ .../components/final-deliberation-grid.tsx | 9 +- .../final/final-deliberation-computation.ts | 64 ++++++++ .../final/final-deliberation-context.tsx | 7 +- .../(volunteer)/deliberation/final/types.ts | 3 + 8 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx diff --git a/apps/frontend/locale/en.json b/apps/frontend/locale/en.json index 88aa9933e..4c48e7d07 100644 --- a/apps/frontend/locale/en.json +++ b/apps/frontend/locale/en.json @@ -223,7 +223,7 @@ "pit-map": "Pit Map" } }, - "judging-head-queuer": { + "judging-head-queuer": { "page-title": "Head Judging Queuer", "no-data": "No data loaded yet. Check in a minute.", "current-sessions": { @@ -1493,6 +1493,17 @@ "success-message": "Awards locked successfully", "error-message": "Failed to lock awards" } + }, + "anomalies": { + "alert-title": "Ranking Anomalies Detected", + "alert-description": "Some teams have significant differences between their picklist position and calculated rank.", + "higher-single": "Team {teamNumber} was ranked {difference} place higher in the {category} category", + "higher-plural": "Team {teamNumber} was ranked {difference} places higher in the {category} category", + "lower-single": "Team {teamNumber} was ranked {difference} place lower in the {category} category", + "lower-plural": "Team {teamNumber} was ranked {difference} places lower in the {category} category", + "core-values": "Core Values", + "innovation-project": "Innovation Project", + "robot-design": "Robot Design" } } } diff --git a/apps/frontend/locale/he.json b/apps/frontend/locale/he.json index aebace233..47510af08 100644 --- a/apps/frontend/locale/he.json +++ b/apps/frontend/locale/he.json @@ -1490,6 +1490,17 @@ "success-message": "הפרסים ננעלו בהצלחה", "error-message": "נעילה נכשלה." } + }, + "anomalies": { + "alert-title": "אנומליות בדירוג זוהו", + "alert-description": "לכמה קבוצות יש הבדלים משמעותיים בין מיקום הרשימה שלהן לדירוג המחושב.", + "higher-single": "קבוצה {teamNumber} דורגה מקום {difference} גבוה יותר בקטגוריית {category}", + "higher-plural": "קבוצה {teamNumber} דורגה {difference} מקומות גבוהים יותר בקטגוריית {category}", + "lower-single": "קבוצה {teamNumber} דורגה מקום {difference} נמוך יותר בקטגוריית {category}", + "lower-plural": "קבוצה {teamNumber} דורגה {difference} מקומות נמוכים יותר בקטגוריית {category}", + "core-values": "ערכים ליבה", + "innovation-project": "פרויקט חדשנות", + "robot-design": "עיצוב רובוט" } } } diff --git a/apps/frontend/locale/pl.json b/apps/frontend/locale/pl.json index 5def63ffb..6ce2e33fc 100644 --- a/apps/frontend/locale/pl.json +++ b/apps/frontend/locale/pl.json @@ -223,7 +223,7 @@ "pit-map": "Mapa pitów" } }, - "judging-head-queuer": { + "judging-head-queuer": { "page-title": "Główny queuer sędziowania", "no-data": "Brak załadowanych danych. Sprawdź za minutę.", "current-sessions": { @@ -1492,6 +1492,17 @@ "success-message": "Nagrody zostały zablokowane", "error-message": "Nie udało się zablokować nagród" } + }, + "anomalies": { + "alert-title": "Wykryto anomalie w rankingu", + "alert-description": "Niektóre zespoły mają znaczące różnice między pozycją na liście a obliczonym rankingiem.", + "higher-single": "Zespół {teamNumber} został umieszczony o {difference} miejsce wyżej w kategorii {category}", + "higher-plural": "Zespół {teamNumber} został umieszczony o {difference} miejsca wyżej w kategorii {category}", + "lower-single": "Zespół {teamNumber} został umieszczony o {difference} miejsce niżej w kategorii {category}", + "lower-plural": "Zespół {teamNumber} został umieszczony o {difference} miejsca niżej w kategorii {category}", + "core-values": "Wartości Podstawowe", + "innovation-project": "Projekt Innowacyjny", + "robot-design": "Projekt Robota" } } } diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx new file mode 100644 index 000000000..7468857bd --- /dev/null +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx @@ -0,0 +1,155 @@ +'use client'; + +import { useState } from 'react'; +import { Alert, Box, Popover, Stack, Typography, useTheme, alpha } from '@mui/material'; +import { Warning } from '@mui/icons-material'; +import { useTranslations } from 'next-intl'; +import { Anomaly } from '../final-deliberation-computation'; + +const CATEGORY_COLORS: Record = { + 'core-values': '#d32f2f', + 'innovation-project': '#1976d2', + 'robot-design': '#388e3c' +}; + +const CATEGORY_BG_COLORS: Record = { + 'core-values': '#ffebee', + 'innovation-project': '#e3f2fd', + 'robot-design': '#e8f5e8' +}; + +interface AnomalyAlertProps { + anomalies: Anomaly[]; +} + +export const AnomalyAlert: React.FC = ({ anomalies }) => { + const t = useTranslations('pages.deliberations.final.anomalies'); + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + + if (!anomalies || anomalies.length === 0) { + return null; + } + + const handleAlertClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const open = Boolean(anchorEl); + const popoverId = open ? 'anomaly-popover' : undefined; + + const getAnomalyMessage = (anomaly: Anomaly): string => { + const categoryKey = anomaly.category as 'core-values' | 'innovation-project' | 'robot-design'; + const categoryLabel = t(categoryKey); + const isHigher = anomaly.isHigher; + const isSingular = anomaly.difference === 1; + + if (isHigher) { + const messageKey = isSingular ? 'higher-single' : 'higher-plural'; + return t(messageKey, { + teamNumber: anomaly.teamNumber, + difference: anomaly.difference, + category: categoryLabel + }); + } else { + const messageKey = isSingular ? 'lower-single' : 'lower-plural'; + return t(messageKey, { + teamNumber: anomaly.teamNumber, + difference: anomaly.difference, + category: categoryLabel + }); + } + }; + + return ( + <> + } + onClick={handleAlertClick} + sx={{ + cursor: 'pointer', + mb: 2, + '&:hover': { + backgroundColor: alpha(theme.palette.warning.main, 0.15) + } + }} + > + + + {t('alert-title')} + + {t('alert-description')} + + + + + + + {t('alert-title')} + + + + {anomalies.map((anomaly, index) => { + const categoryColor = CATEGORY_COLORS[anomaly.category]; + const categoryBgColor = CATEGORY_BG_COLORS[anomaly.category]; + + return ( + + + + {getAnomalyMessage(anomaly)} + + + ); + })} + + + + + ); +}; diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/final-deliberation-grid.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/final-deliberation-grid.tsx index 6e3ec1d1f..5ec543598 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/final-deliberation-grid.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/final-deliberation-grid.tsx @@ -23,6 +23,7 @@ import { ChampionsStage } from './champions/champions-stage'; import { CoreAwardsStage } from './core-awards/core-awards-stage'; import { OptionalAwardsStage } from './optional-awards/optional-awards-stage'; import { ReviewStage } from './review/review-stage'; +import { AnomalyAlert } from './anomaly-alert'; const STAGES: FinalDeliberationStage[] = ['champions', 'core-awards', 'optional-awards', 'review']; @@ -30,7 +31,7 @@ export const FinalDeliberationGrid: React.FC = () => { const t = useTranslations('pages.deliberations.final'); const theme = useTheme(); const router = useRouter(); - const { awardCounts, deliberation } = useFinalDeliberation(); + const { awardCounts, deliberation, anomalies } = useFinalDeliberation(); // Determine visible stages based on whether optional awards exist const visibleStages = useMemo(() => { @@ -87,7 +88,10 @@ export const FinalDeliberationGrid: React.FC = () => { backgroundColor: alpha(theme.palette.primary.light, 0.1), borderBottom: `1px solid ${theme.palette.divider}`, py: 2, - px: 4 + px: 4, + display: 'flex', + flexDirection: 'column', + gap: 2 }} > { ))} + {/* Main Content - Flex grow to fill remaining space */} diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts index 98872d44a..1337c208d 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts @@ -3,6 +3,20 @@ import { compareScoreArrays } from '@lems/shared/utils/arrays'; import { CategorizedRubrics, MetricPerCategory, Team } from '../types'; import { EnrichedTeam, OptionalAwardNominations, RanksPerCategory } from './types'; +/** + * Represents an anomaly where a team's picklist position differs significantly + * from their calculated rank based on rubric scores. + */ +export interface Anomaly { + teamId: string; + teamNumber: string; + category: JudgingCategory; + calculatedRank: number; + picklistPosition: number; + difference: number; + isHigher: boolean; // true if team ranked higher (more favorable) in picklist than calculated +} + /** * Extracts optional award nominations from a rubric */ @@ -152,3 +166,53 @@ export const computeOptionalAwardsEligibility = ( return Object.keys(team.awardNominations).length > 0 || manualNominations.includes(team.id); }; + +/** + * Computes anomalies for the final deliberation. + * + * An anomaly occurs when a team's picklist position differs by 3 or more places + * from their calculated rank based on rubric scores (excluding ties). + * + * @param enrichedTeams - Teams with computed scores and ranks + * @param categoryPicklists - Picklist rankings for each category + * @returns Array of detected anomalies + */ +export function computeAnomalies( + enrichedTeams: EnrichedTeam[], + categoryPicklists: Record +): Anomaly[] { + const anomalies: Anomaly[] = []; + + const categories: JudgingCategory[] = ['core-values', 'innovation-project', 'robot-design']; + + categories.forEach(category => { + const picklist = categoryPicklists[category]; + + enrichedTeams.forEach(team => { + const picklistPosition = picklist.indexOf(team.id) + 1; + + // Only check teams that are in the picklist + if (picklistPosition > 0) { + const calculatedRank = team.ranks[category]; + const difference = Math.abs(picklistPosition - calculatedRank); + + // Anomaly threshold is 3 or more places difference + if (difference >= 3) { + const isHigher = picklistPosition < calculatedRank; + + anomalies.push({ + teamId: team.id, + teamNumber: team.number, + category, + calculatedRank, + picklistPosition, + difference, + isHigher + }); + } + } + }); + }); + + return anomalies; +} diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx index 5816ea3b7..cf83abb90 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx @@ -30,7 +30,8 @@ import { computeCoreAwardsEligibility, computeOptionalAwardsEligibility, computeRank, - extractOptionalAwards + extractOptionalAwards, + computeAnomalies } from './final-deliberation-computation'; const FinalDeliberationContext = createContext(null); @@ -237,6 +238,9 @@ export const FinalDeliberationProvider = ({ .filter(t => (eligibleTeams[deliberation.stage as StagesWithNomination] ?? []).includes(t.id)) .map(t => t.id); + // Compute anomalies based on picklist positions vs calculated ranks + const anomalies = computeAnomalies(enrichedTeams, categoryPicklists); + return { division, deliberation, @@ -247,6 +251,7 @@ export const FinalDeliberationProvider = ({ awards, awardCounts, roomMetrics, + anomalies, startDeliberation: handleStartFinalDeliberation, updateAward: handleUpdateFinalDeliberationAwards, advanceStage: handleAdvanceStage, diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts index 4072da7f5..c4897fbdf 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts @@ -2,6 +2,7 @@ import { Award, OptionalAwards } from '@lems/shared'; import { JudgingCategory } from '@lems/database'; import { Room, MetricPerCategory, RoomMetricsMap } from '../types'; import { Division, FinalJudgingDeliberation } from './graphql'; +import { Anomaly } from './final-deliberation-computation'; export type FinalDeliberationStage = 'champions' | 'core-awards' | 'optional-awards' | 'review'; export type StagesWithNomination = 'champions' | 'core-awards' | 'optional-awards'; @@ -60,6 +61,8 @@ export interface FinalDeliberationContextValue { roomMetrics: RoomMetricsMap; + anomalies: Anomaly[]; + startDeliberation(): Promise; updateAward(awardName: Award, updatedAward: string[] | Record): Promise; advanceStage(): Promise; From 6a4813446746ef03554496604020877afe74821a Mon Sep 17 00:00:00 2001 From: johnmeshulam <55348702+johnmeshulam@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:57:12 +0200 Subject: [PATCH 2/3] Fix locale --- apps/frontend/locale/en.json | 6 ++--- apps/frontend/locale/he.json | 6 ++--- apps/frontend/locale/pl.json | 6 ++--- .../final/components/anomaly-alert.tsx | 23 +++++-------------- 4 files changed, 12 insertions(+), 29 deletions(-) diff --git a/apps/frontend/locale/en.json b/apps/frontend/locale/en.json index 4c48e7d07..c61f26b81 100644 --- a/apps/frontend/locale/en.json +++ b/apps/frontend/locale/en.json @@ -1497,10 +1497,8 @@ "anomalies": { "alert-title": "Ranking Anomalies Detected", "alert-description": "Some teams have significant differences between their picklist position and calculated rank.", - "higher-single": "Team {teamNumber} was ranked {difference} place higher in the {category} category", - "higher-plural": "Team {teamNumber} was ranked {difference} places higher in the {category} category", - "lower-single": "Team {teamNumber} was ranked {difference} place lower in the {category} category", - "lower-plural": "Team {teamNumber} was ranked {difference} places lower in the {category} category", + "higher": "Team {teamNumber} was ranked {difference, plural, =1 {one place} other {# places}} higher in the {category} category", + "lower": "Team {teamNumber} was ranked {difference, plural, =1 {one place} other {# places}} lower in the {category} category", "core-values": "Core Values", "innovation-project": "Innovation Project", "robot-design": "Robot Design" diff --git a/apps/frontend/locale/he.json b/apps/frontend/locale/he.json index 47510af08..8ae1d0b7c 100644 --- a/apps/frontend/locale/he.json +++ b/apps/frontend/locale/he.json @@ -1494,10 +1494,8 @@ "anomalies": { "alert-title": "אנומליות בדירוג זוהו", "alert-description": "לכמה קבוצות יש הבדלים משמעותיים בין מיקום הרשימה שלהן לדירוג המחושב.", - "higher-single": "קבוצה {teamNumber} דורגה מקום {difference} גבוה יותר בקטגוריית {category}", - "higher-plural": "קבוצה {teamNumber} דורגה {difference} מקומות גבוהים יותר בקטגוריית {category}", - "lower-single": "קבוצה {teamNumber} דורגה מקום {difference} נמוך יותר בקטגוריית {category}", - "lower-plural": "קבוצה {teamNumber} דורגה {difference} מקומות נמוכים יותר בקטגוריית {category}", + "higher": "קבוצה {teamNumber} דורגה {difference, plural, =1 {מקום אחד} other {# מקומות}} גבוה יותר בתחום {category}", + "lower": "קבוצה {teamNumber} דורגה {difference, plural, =1 {מקום אחד} other {# מקומות}} נמוך יותר בתחום {category}", "core-values": "ערכים ליבה", "innovation-project": "פרויקט חדשנות", "robot-design": "עיצוב רובוט" diff --git a/apps/frontend/locale/pl.json b/apps/frontend/locale/pl.json index 6ce2e33fc..74c7bae9a 100644 --- a/apps/frontend/locale/pl.json +++ b/apps/frontend/locale/pl.json @@ -1496,10 +1496,8 @@ "anomalies": { "alert-title": "Wykryto anomalie w rankingu", "alert-description": "Niektóre zespoły mają znaczące różnice między pozycją na liście a obliczonym rankingiem.", - "higher-single": "Zespół {teamNumber} został umieszczony o {difference} miejsce wyżej w kategorii {category}", - "higher-plural": "Zespół {teamNumber} został umieszczony o {difference} miejsca wyżej w kategorii {category}", - "lower-single": "Zespół {teamNumber} został umieszczony o {difference} miejsce niżej w kategorii {category}", - "lower-plural": "Zespół {teamNumber} został umieszczony o {difference} miejsca niżej w kategorii {category}", + "higher": "Zespół {teamNumber} został umieszczony o {difference, plural, =1 {jedno miejsce} other {# miejsca}} wyżej w kategorii {category}", + "lower": "Zespół {teamNumber} został umieszczony o {difference, plural, =1 {jedno miejsce} other {# miejsca}} niżej w kategorii {category}", "core-values": "Wartości Podstawowe", "innovation-project": "Projekt Innowacyjny", "robot-design": "Projekt Robota" diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx index 7468857bd..f655c6389 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/anomaly-alert.tsx @@ -45,24 +45,13 @@ export const AnomalyAlert: React.FC = ({ anomalies }) => { const getAnomalyMessage = (anomaly: Anomaly): string => { const categoryKey = anomaly.category as 'core-values' | 'innovation-project' | 'robot-design'; const categoryLabel = t(categoryKey); - const isHigher = anomaly.isHigher; - const isSingular = anomaly.difference === 1; + const messageKey = anomaly.isHigher ? 'higher' : 'lower'; - if (isHigher) { - const messageKey = isSingular ? 'higher-single' : 'higher-plural'; - return t(messageKey, { - teamNumber: anomaly.teamNumber, - difference: anomaly.difference, - category: categoryLabel - }); - } else { - const messageKey = isSingular ? 'lower-single' : 'lower-plural'; - return t(messageKey, { - teamNumber: anomaly.teamNumber, - difference: anomaly.difference, - category: categoryLabel - }); - } + return t(messageKey, { + teamNumber: anomaly.teamNumber, + difference: anomaly.difference, + category: categoryLabel + }); }; return ( From 66cd9e4d46281b79c8e04864c2d66ec45f33a7da Mon Sep 17 00:00:00 2001 From: johnmeshulam <55348702+johnmeshulam@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:02:05 +0200 Subject: [PATCH 3/3] Fix calculation --- .../final/final-deliberation-computation.ts | 41 ++++++++++++++++++- .../final/final-deliberation-context.tsx | 6 ++- .../(volunteer)/deliberation/final/types.ts | 1 + 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts index 1337c208d..c30a4df1f 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-computation.ts @@ -1,5 +1,6 @@ import { FinalDeliberationAwards, JudgingCategory } from '@lems/database'; import { compareScoreArrays } from '@lems/shared/utils/arrays'; +import { JUDGING_CATEGORIES } from '@lems/types/judging'; import { CategorizedRubrics, MetricPerCategory, Team } from '../types'; import { EnrichedTeam, OptionalAwardNominations, RanksPerCategory } from './types'; @@ -45,6 +46,42 @@ export function extractOptionalAwards(rubrics: CategorizedRubrics): OptionalAwar return nominations; } +/** + * Computes raw ranking for teams based purely on rubric scores (no picklist consideration). + * + * This is used for anomaly detection to compare against picklist positions. + * + * @param team - The team to compute raw rank for + * @param allTeams - All teams (used for relative ranking) + * @returns RanksPerCategory object with score-based ranks only + */ +export function computeRawRank( + team: Team & { scores: MetricPerCategory; robotGameScores: number[] }, + allTeams: (Team & { scores: MetricPerCategory; robotGameScores: number[] })[] +): RanksPerCategory { + const rawRanks: RanksPerCategory = { + 'innovation-project': 0, + 'robot-design': 0, + 'core-values': 0, + 'robot-game': 0, + total: 0 + }; + + const computeRankByScore = (category: JudgingCategory, teamScore: number): number => { + const higherScoreCount = allTeams.filter(t => { + const score = t.scores[category]; + return score > teamScore; + }).length; + return higherScoreCount + 1; + }; + + JUDGING_CATEGORIES.forEach(category => { + rawRanks[category] = computeRankByScore(category, team.scores[category]); + }); + + return rawRanks; +} + /** * Computes ranking for teams based on scores and picklists. * @@ -171,7 +208,7 @@ export const computeOptionalAwardsEligibility = ( * Computes anomalies for the final deliberation. * * An anomaly occurs when a team's picklist position differs by 3 or more places - * from their calculated rank based on rubric scores (excluding ties). + * from their raw rubric score rank (score-based ranking without picklist consideration). * * @param enrichedTeams - Teams with computed scores and ranks * @param categoryPicklists - Picklist rankings for each category @@ -193,7 +230,7 @@ export function computeAnomalies( // Only check teams that are in the picklist if (picklistPosition > 0) { - const calculatedRank = team.ranks[category]; + const calculatedRank = team.rawRanks[category]; const difference = Math.abs(picklistPosition - calculatedRank); // Anomaly threshold is 3 or more places difference diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx index cf83abb90..16d020cbd 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx @@ -30,6 +30,7 @@ import { computeCoreAwardsEligibility, computeOptionalAwardsEligibility, computeRank, + computeRawRank, extractOptionalAwards, computeAnomalies } from './final-deliberation-computation'; @@ -162,8 +163,10 @@ export const FinalDeliberationProvider = ({ team.judgingSession?.room.id ); const awardNominations = extractOptionalAwards(team.rubrics); - // Compute ranks + // Compute ranks (with picklist consideration) const ranks = computeRank(teamsWithScores[index], teamsWithScores, categoryPicklists); + // Compute raw ranks (score-based only, for anomaly detection) + const rawRanks = computeRawRank(teamsWithScores[index], teamsWithScores); const eligibilites: Partial = { 'core-awards': computeCoreAwardsEligibility( @@ -199,6 +202,7 @@ export const FinalDeliberationProvider = ({ scores, normalizedScores, ranks, + rawRanks, eligibility: eligibilites, robotGameScores, rubricsFields: { diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts index c4897fbdf..a06050838 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts @@ -28,6 +28,7 @@ export type EnrichedTeam = { scores: MetricPerCategory; normalizedScores: Omit; ranks: RanksPerCategory; + rawRanks: RanksPerCategory; eligibility: EligiblityPerStage;