From cbaed12018ac797fa766212a016824af8a34ad8b Mon Sep 17 00:00:00 2001 From: aamoghS Date: Thu, 19 Feb 2026 13:14:07 -0500 Subject: [PATCH] fix the routes --- .gitignore | 4 +- packages/api/src/routers/judge.ts | 13 +- packages/db/package.json | 2 + packages/db/src/schemas/judge.ts | 11 +- .../app/(portal)/admin-judging/page.tsx | 244 ++++++++++-------- sites/mainweb/app/(portal)/judge/page.tsx | 46 +++- 6 files changed, 193 insertions(+), 127 deletions(-) diff --git a/.gitignore b/.gitignore index cf35c41..d347fad 100644 --- a/.gitignore +++ b/.gitignore @@ -58,4 +58,6 @@ migrations/ .claude/settings.local.json packages/db/scripts/seed.ts packages/db/scripts/seed2.ts -scripts/seed-stripe.ts \ No newline at end of file +scripts/seed-stripe.ts +packages/db/scripts/seed-judges.ts +packages/db/scripts/seed-projects.ts \ No newline at end of file diff --git a/packages/api/src/routers/judge.ts b/packages/api/src/routers/judge.ts index 17750bf..ad23e64 100644 --- a/packages/api/src/routers/judge.ts +++ b/packages/api/src/routers/judge.ts @@ -151,6 +151,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: z.number().min(1).max(10), scoreClarity: z.number().min(1).max(10), scoreSoundness: z.number().min(1).max(10), + scoreImagination: z.number().min(1).max(10).optional(), comment: z.string().max(1000).optional(), }) ) @@ -173,6 +174,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: input.scoreScope, scoreClarity: input.scoreClarity, scoreSoundness: input.scoreSoundness, + scoreImagination: input.scoreImagination, comment: input.comment, updatedAt: new Date(), }) @@ -193,6 +195,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: input.scoreScope, scoreClarity: input.scoreClarity, scoreSoundness: input.scoreSoundness, + scoreImagination: input.scoreImagination, comment: input.comment, }) .returning(); @@ -210,6 +213,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: z.number().min(1).max(10), scoreClarity: z.number().min(1).max(10), scoreSoundness: z.number().min(1).max(10), + scoreImagination: z.number().min(1).max(10).optional(), comment: z.string().max(1000).optional(), }) ) @@ -232,6 +236,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: input.scoreScope, scoreClarity: input.scoreClarity, scoreSoundness: input.scoreSoundness, + scoreImagination: input.scoreImagination, comment: input.comment, updatedAt: new Date(), }) @@ -246,6 +251,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: input.scoreScope, scoreClarity: input.scoreClarity, scoreSoundness: input.scoreSoundness, + scoreImagination: input.scoreImagination, comment: input.comment, }); } @@ -363,13 +369,14 @@ export const judgeRouter = createTRPCRouter({ const avgScore = voteCount > 0 ? totalScore / voteCount : 0; // Per-category averages - const sumCat = { creativity: 0, impact: 0, scope: 0, clarity: 0, soundness: 0 }; + const sumCat = { creativity: 0, impact: 0, scope: 0, clarity: 0, soundness: 0, imagination: 0 }; project.votes.forEach((v) => { sumCat.creativity += v.scoreCreativity ?? 0; sumCat.impact += v.scoreImpact ?? 0; sumCat.scope += v.scoreScope ?? 0; sumCat.clarity += v.scoreClarity ?? 0; sumCat.soundness += v.scoreSoundness ?? 0; + sumCat.imagination += v.scoreImagination ?? 0; }); const categoryAvg = voteCount > 0 @@ -379,8 +386,9 @@ export const judgeRouter = createTRPCRouter({ scope: round2(sumCat.scope / voteCount), clarity: round2(sumCat.clarity / voteCount), soundness: round2(sumCat.soundness / voteCount), + imagination: round2(sumCat.imagination / voteCount), } - : { creativity: 0, impact: 0, scope: 0, clarity: 0, soundness: 0 }; + : { creativity: 0, impact: 0, scope: 0, clarity: 0, soundness: 0, imagination: 0 }; return { project: { @@ -404,6 +412,7 @@ export const judgeRouter = createTRPCRouter({ scoreScope: v.scoreScope, scoreClarity: v.scoreClarity, scoreSoundness: v.scoreSoundness, + scoreImagination: v.scoreImagination, comment: v.comment, judgeName: v.judge.user?.name || v.judge.name || "Unknown", })), diff --git a/packages/db/package.json b/packages/db/package.json index 00f53bf..2badc14 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -12,6 +12,8 @@ "migrate:generate": "drizzle-kit generate", "studio": "drizzle-kit studio", "db:seed": "tsx scripts/seed.ts", + "db:seed:judges": "tsx scripts/seed-judges.ts", + "db:seed:projects": "tsx scripts/seed-projects.ts", "db:seed2": "tsx scripts/seed2.ts", "db:seed:super": "tsx scripts/seed-super.ts", "db:reset": "tsx scripts/reset.ts" diff --git a/packages/db/src/schemas/judge.ts b/packages/db/src/schemas/judge.ts index a6718a9..ec82418 100644 --- a/packages/db/src/schemas/judge.ts +++ b/packages/db/src/schemas/judge.ts @@ -28,7 +28,7 @@ export const judgeAssignments = pgTable("judge_assignment", { .references(() => hackathons.id, { onDelete: "cascade" }), assignedAt: timestamp("assigned_at").defaultNow().notNull(), isLead: boolean("is_lead").notNull().default(false), - track: text("track"), // Optional: if set, judge only sees projects in this track/challenge + track: text("track"), }, (table) => [ index("assignment_judge_id_idx").on(table.judgeId), index("assignment_hackathon_id_idx").on(table.hackathonId), @@ -45,12 +45,12 @@ export const judgingProjects = pgTable("judging_project", { description: text("description"), tableNumber: integer("table_number").notNull(), category: text("category"), // e.g., "AI", "Web3", "Health", "Sustainability" - tracks: text("tracks").array(), - challenges: text("challenges").array(), - isCreateX: boolean("is_create_x").default(false), teamMembers: text("team_members"), // comma-separated or JSON string projectUrl: text("project_url"), repoUrl: text("repo_url"), + tracks: text("tracks").array(), // Enum: GEN-AI, SPORTS, FINANCE, HEALTH, CYBER, NONE + challenges: text("challenges").array(), // Enum: AGG, ASSURANT, AWS, CAPONE, GROWTH, MLH_MONGODB, MLH_STREAMLIT, MLH_TECH, MLH_CLOUDFLARE, MLH_REACH_CAPITAL + isCreateX: boolean("is_create_x").default(false), createdAt: timestamp("created_at").defaultNow().notNull(), }, (table) => [ index("judging_project_hackathon_id_idx").on(table.hackathonId), @@ -68,6 +68,8 @@ export const judgeVotes = pgTable("judge_vote", { .references(() => judgingProjects.id, { onDelete: "cascade" }), score: integer("score").notNull(), // Total score (sum of all criteria, 5-50) // Rubric scores (1-10 each) + + scoreImagination: integer("score_imagination"), // immagination track scoreCreativity: integer("score_creativity"), // Creativity & Originality scoreImpact: integer("score_impact"), // Impact & Relevance scoreScope: integer("score_scope"), // Scope & Technical Depth @@ -168,6 +170,7 @@ export const hackathonMapsRelations = relations(hackathonMaps, ({ one }) => ({ }), })); + export const judgeQueueRelations = relations(judgeQueue, ({ one }) => ({ judge: one(judges, { fields: [judgeQueue.judgeId], diff --git a/sites/mainweb/app/(portal)/admin-judging/page.tsx b/sites/mainweb/app/(portal)/admin-judging/page.tsx index 197bcc2..268ce2b 100644 --- a/sites/mainweb/app/(portal)/admin-judging/page.tsx +++ b/sites/mainweb/app/(portal)/admin-judging/page.tsx @@ -13,6 +13,7 @@ export default function AdminResultsPage() { const [mounted, setMounted] = useState(false); const [selectedHackathon, setSelectedHackathon] = useState(null); const [selectedCategory, setSelectedCategory] = useState('ALL'); + const [selectedTrack, setSelectedTrack] = useState('ALL'); const [expandedProject, setExpandedProject] = useState(null); // Check if admin @@ -54,11 +55,39 @@ export default function AdminResultsPage() { return ['ALL', ...Array.from(cats)]; }, [rankings]); - const filteredRankings = useMemo(() => { + const tracks = useMemo(() => { + if (!rankings?.rankings) return ['ALL']; + const ts = new Set(rankings.rankings.flatMap(r => r.project.tracks || []).filter((t): t is string => !!t)); + return ['ALL', ...Array.from(ts)]; + }, [rankings]); + + const processedRankings = useMemo(() => { if (!rankings?.rankings) return []; - if (selectedCategory === 'ALL') return rankings.rankings; - return rankings.rankings.filter(r => r.project.category === selectedCategory); - }, [rankings, selectedCategory]); + + let filtered = rankings.rankings; + + if (selectedCategory !== 'ALL') { + filtered = filtered.filter(r => r.project.category === selectedCategory); + } + + if (selectedTrack !== 'ALL') { + filtered = filtered.filter(r => r.project.tracks?.includes(selectedTrack)); + } + + // Calculate display score based on track + return filtered.map(r => { + const isPureImagination = selectedTrack === 'Pure Imagination'; + // If Pure Imagination track is selected, include imagination score in total + const imaginationSum = r.votes.reduce((sum, v) => sum + (v.scoreImagination || 0), 0); + const displayScore = isPureImagination ? r.totalScore + imaginationSum : r.totalScore; + + return { + ...r, + displayScore, + isPureImagination + }; + }).sort((a, b) => b.displayScore - a.displayScore); + }, [rankings, selectedCategory, selectedTrack]); if (!mounted || status === 'loading' || adminLoading) { return ( @@ -154,26 +183,49 @@ export default function AdminResultsPage() { )} - {/* Category Filter */} - {categories.length > 1 && ( -
- -
- {categories.map((cat) => ( - - ))} +
+ {/* Category Filter */} + {categories.length > 1 && ( +
+ +
+ {categories.map((cat) => ( + + ))} +
-
- )} + )} + + {/* Track Filter */} + {tracks.length > 1 && ( +
+ +
+ {tracks.map((track) => ( + + ))} +
+
+ )} +
{/* Tie Warning — Overall */} @@ -201,32 +253,6 @@ export default function AdminResultsPage() { )} - {/* Tie Warning — Per-Category */} - {rankings?.hasCategoryTies && ( - -
-
-

Category-Level Ties

-
-
- {rankings.categoryTies.map((ct: { category: string; avgScore: number; projects: string[] }, i: number) => ( -
-

{ct.category}

-

Avg: {ct.avgScore}

-
- {ct.projects.map((p, pi) => ( -

> {p}

- ))} -
-
- ))} -
-

- Projects share identical average in one or more rubric categories -

- - )} - {/* Projected Winners Section */} {rankings && rankings.rankings.length > 0 && (
@@ -245,29 +271,38 @@ export default function AdminResultsPage() { // 2. Identify Track Winners (Top 1 per Track, excluding Overall) // Get all unique tracks const allTracks = Array.from(new Set(rankings.rankings.flatMap(r => r.project.tracks || []))); - const trackWinners: Record = {}; + const trackWinners: Record = {}; const usedWinnerIds = new Set(overallWinnerIds); allTracks.forEach(track => { - // Find highest scoring project in this track that hasn't won yet - const candidate = sortedByScore.find(r => - r.project.tracks?.includes(track) && !usedWinnerIds.has(r.project.id) - ); + // If track is Pure Imagination, sort by Total + Imagination + const isPureImagination = track === 'Pure Imagination'; - if (candidate) { - trackWinners[track] = candidate; - usedWinnerIds.add(candidate.project.id); - } - }); + // Sort projects for this track + const projectsInTrack = rankings.rankings.filter(r => r.project.tracks?.includes(track)); + + const sortedTrackProjects = projectsInTrack.sort((a, b) => { + if (isPureImagination) { + const scoreA = a.totalScore + a.votes.reduce((sum, v) => sum + (v.scoreImagination || 0), 0); + const scoreB = b.totalScore + b.votes.reduce((sum, v) => sum + (v.scoreImagination || 0), 0); + return scoreB - scoreA; + } + return b.totalScore - a.totalScore; + }); - // 3. Identify Challenge (Sponsor) Winners (Top 1 per Challenge, NO exclusions) - const allChallenges = Array.from(new Set(rankings.rankings.flatMap(r => r.project.challenges || []))); - const challengeWinners: Record = {}; + // Find highest scoring project in this track that hasn't won an overall prize + // (Unless Pure Imagination rules are different? Assuming strict exclusion for fairness) + const candidate = sortedTrackProjects.find(r => !usedWinnerIds.has(r.project.id)); - allChallenges.forEach(challenge => { - const candidate = sortedByScore.find(r => r.project.challenges?.includes(challenge)); if (candidate) { - challengeWinners[challenge] = candidate; + // augment candidate with pure imagination score if needed for display + if (isPureImagination) { + const imagScore = candidate.votes.reduce((sum, v) => sum + (v.scoreImagination || 0), 0); + trackWinners[track] = { ...candidate, customScore: candidate.totalScore + imagScore }; + } else { + trackWinners[track] = candidate; + } + usedWinnerIds.add(candidate.project.id); } }); @@ -277,8 +312,8 @@ export default function AdminResultsPage() {
{overallWinners.map((w, i) => (

{i === 0 ? 'Grand Prize' : i === 1 ? '2nd Place' : '3rd Place'} @@ -301,29 +336,9 @@ export default function AdminResultsPage() {

{track}

{w.project.name}

-

{w.totalScore}

-
- ))} -
-
- )} - - {/* Challenge Winners */} - {Object.keys(challengeWinners).length > 0 && ( -
-

- Challenge Winners (Sponsors) -

-
- {Object.entries(challengeWinners).map(([challenge, w]) => ( -
-

{challenge}

-

{w.project.name}

-
-

{w.totalScore}

- {overallWinnerIds.has(w.project.id) && ( - Also Overall - )} +
+

{w.customScore || w.totalScore}

+ {track === 'Pure Imagination' && (w/ IMG)}
))} @@ -336,18 +351,17 @@ export default function AdminResultsPage() {
)} - {/* Rankings Table Logic... */} {/* Rankings Table */}

Metrics Log

- Evaluated Rankings + Evaluated Rankings {selectedTrack !== 'ALL' && :: {selectedTrack}}

- Displaying: {filteredRankings.length} Nodes + Displaying: {processedRankings.length} Nodes
@@ -355,7 +369,7 @@ export default function AdminResultsPage() {

Awaiting Data Packet...

- ) : filteredRankings.length === 0 ? ( + ) : processedRankings.length === 0 ? (

0 Records Found

No submissions detected for the specified search parameters.

@@ -370,21 +384,28 @@ export default function AdminResultsPage() { Node Identifier Sum - Avg + {!selectedTrack.includes('Pure Imagination') && Avg} + + {/* Rubric Headers */} CRE IMP SCP CLR SND + + {/* Show IMG column if Pure Imagination or All */} + IMG + Count - {filteredRankings.map((r, idx) => { + {processedRankings.map((r, idx) => { const isExpanded = expandedProject === r.project.id; const isTied = rankings?.ties.some((t: { projects: string[] }) => t.projects.includes(r.project.name) ); + const isPureImaginationRow = r.isPureImagination; return ( @@ -412,11 +433,11 @@ export default function AdminResultsPage() {

{r.project.name}

- {r.project.category && ( - - {r.project.category} + {r.project.tracks?.map(t => ( + + {t} - )} + ))}
{r.project.teamMembers && (

{r.project.teamMembers}

@@ -424,28 +445,34 @@ export default function AdminResultsPage() {
- {r.totalScore} + + {r.displayScore} + + {isPureImaginationRow &&

INCL. IMG

} - {r.avgScore} + {!selectedTrack.includes('Pure Imagination') && {r.avgScore}} + {r.categoryAvg?.creativity ?? '-'} {r.categoryAvg?.impact ?? '-'} {r.categoryAvg?.scope ?? '-'} {r.categoryAvg?.clarity ?? '-'} {r.categoryAvg?.soundness ?? '-'} + {r.categoryAvg?.imagination ?? '-'} + {r.voteCount} {/* Expanded row with individual votes */} {isExpanded && ( - +

Vote Audit Log

{r.votes.length === 0 ? (

> 0 records found for this node

) : (
- {r.votes.map((v: { judgeName: string; score: number; scoreCreativity: number | null; scoreImpact: number | null; scoreScope: number | null; scoreClarity: number | null; scoreSoundness: number | null; comment: string | null }, vi: number) => ( + {r.votes.map((v: any, vi: number) => (
{/* Per-category breakdown */} -
+
{[ { label: 'CRE', value: v.scoreCreativity }, { label: 'IMP', value: v.scoreImpact }, { label: 'SCP', value: v.scoreScope }, { label: 'CLR', value: v.scoreClarity }, { label: 'SND', value: v.scoreSoundness }, + { label: 'IMG', value: v.scoreImagination, highlight: true }, ].map((cat) => ( -
-

{cat.label}

-

{cat.value ?? '-'}

+
+

{cat.label}

+

{cat.value ?? '-'}

))}
diff --git a/sites/mainweb/app/(portal)/judge/page.tsx b/sites/mainweb/app/(portal)/judge/page.tsx index 446ffa7..b943c1f 100644 --- a/sites/mainweb/app/(portal)/judge/page.tsx +++ b/sites/mainweb/app/(portal)/judge/page.tsx @@ -15,6 +15,7 @@ const RUBRIC_CRITERIA = [ { key: 'scope', label: 'Technical Depth', description: 'Variety of tools & complexity' }, { key: 'clarity', label: 'Clarity', description: 'Clear presentation & engagement' }, { key: 'soundness', label: 'Soundness', description: 'Logical & accurate conclusions' }, + { key: 'imagination', label: 'Pure Imagination', description: 'Wildly creative & out-of-the-box ideas' }, ] as const; const RUBRIC_GUIDE = [ @@ -68,6 +69,16 @@ const RUBRIC_GUIDE = [ { range: '9-10', desc: 'Excellent. Consistent, correct, well-supported conclusions.' }, ] }, + { + name: 'Pure Imagination', + levels: [ + { range: '1-2', desc: 'Standard. Follows conventional thinking.' }, + { range: '3-4', desc: 'Some spark. Slight deviation from the norm.' }, + { range: '5-6', desc: 'Creative. Shows good imagination.' }, + { range: '7-8', desc: 'Imaginative. Very creative and novel ideas.' }, + { range: '9-10', desc: 'Pure Imagination. Mind-blowing, boundary-pushing creativity.' }, + ] + }, ]; type RubricScores = { @@ -76,6 +87,7 @@ type RubricScores = { scope: number; clarity: number; soundness: number; + imagination?: number; }; type JudgingStep = 'viewing' | 'judging'; @@ -88,7 +100,7 @@ export default function JudgePage() { const [showHelp, setShowHelp] = useState(false); const [showMap, setShowMap] = useState(false); const [scores, setScores] = useState({ - creativity: 5, impact: 5, scope: 5, clarity: 5, soundness: 5, + creativity: 5, impact: 5, scope: 5, clarity: 5, soundness: 5, imagination: 5, }); const [comment, setComment] = useState(''); @@ -101,6 +113,8 @@ export default function JudgePage() { }); const hackathonId = assignments?.[0]?.hackathon?.id; + const assignmentTrack = assignments?.[0]?.track; // "Pure Imagination" or others + const isPureImagination = assignmentTrack === 'Pure Imagination'; const { data: nextTable, isLoading: loadingNext, refetch } = trpc.judge.getNextTable.useQuery( { hackathonId: hackathonId! }, @@ -114,7 +128,7 @@ export default function JudgePage() { const submit = trpc.judge.completeAndNext.useMutation({ onSuccess: () => { - setScores({ creativity: 5, impact: 5, scope: 5, clarity: 5, soundness: 5 }); + setScores({ creativity: 5, impact: 5, scope: 5, clarity: 5, soundness: 5, imagination: 5 }); setComment(''); setStep('viewing'); refetch(); @@ -128,7 +142,7 @@ export default function JudgePage() { if (status === 'unauthenticated') router.push('/login'); }, [status, router]); - const totalScore = scores.creativity + scores.impact + scores.scope + scores.clarity + scores.soundness; + const totalScore = scores.creativity + scores.impact + scores.scope + scores.clarity + scores.soundness + (isPureImagination ? (scores.imagination || 0) : 0); const handleSubmit = () => { if (!nextTable?.queueId || !nextTable?.project) return; @@ -140,6 +154,7 @@ export default function JudgePage() { scoreScope: scores.scope, scoreClarity: scores.clarity, scoreSoundness: scores.soundness, + scoreImagination: isPureImagination ? scores.imagination : undefined, comment: comment || undefined, }); }; @@ -149,11 +164,18 @@ export default function JudgePage() { }; const getScoreColor = () => { - if (totalScore <= 15) return 'text-red-400'; - if (totalScore <= 30) return 'text-yellow-400'; + const max = isPureImagination ? 60 : 50; + const pct = totalScore / max; + if (pct <= 0.3) return 'text-red-400'; + if (pct <= 0.6) return 'text-yellow-400'; return 'text-emerald-400'; }; + const visibleCriteria = RUBRIC_CRITERIA.filter(c => { + if (c.key === 'imagination') return isPureImagination; + return true; + }); + // Loading state if (!mounted || status === 'loading' || checkingJudge) { return ( @@ -261,7 +283,7 @@ export default function JudgePage() {
- {RUBRIC_GUIDE.map((criterion, idx) => ( + {RUBRIC_GUIDE.filter(g => isPureImagination || g.name !== 'Pure Imagination').map((criterion, idx) => (
@@ -327,9 +349,9 @@ export default function JudgePage() {
- {assignments?.[0]?.track && ( + {assignmentTrack && (
- Track: {assignments[0].track} + Track: {assignmentTrack}
)}

{project.name}

@@ -413,19 +435,19 @@ export default function JudgePage() {
-

Evaluation Rubric

+

Evaluation Rubric {isPureImagination && (+Imagination)}

- {RUBRIC_CRITERIA.map((c) => ( + {visibleCriteria.map((c) => ( updateScore(c.key, v)} + value={scores[c.key as keyof RubricScores] || 5} + onChange={(v) => updateScore(c.key as keyof RubricScores, v)} /> ))}