From 88255219883efb353950d7a773b6934ac7a1a789 Mon Sep 17 00:00:00 2001 From: reehals Date: Wed, 6 May 2026 16:48:56 -0700 Subject: [PATCH 01/14] Fix 2026-migrations, add panels migration and judgeVisibleTracks to validation data --- app/_data/db_validation_data.json | 13 ++++ migrations/20260506000000-add-2026-tracks.mjs | 32 +--------- .../20260506120000-update-panels-tracks.mjs | 62 +++++++++++++++++++ 3 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 migrations/20260506120000-update-panels-tracks.mjs diff --git a/app/_data/db_validation_data.json b/app/_data/db_validation_data.json index c80c8191..acf61245 100644 --- a/app/_data/db_validation_data.json +++ b/app/_data/db_validation_data.json @@ -25,5 +25,18 @@ "Best Use of MongoDB Atlas", "Best Domain Name from GoDaddy Registry", "Best use of Reconstruct" + ], + "judgeVisibleTracks": [ + "Most Technically Challenging Hack", + "Best Beginner Hack", + "Best Interdisciplinary Hack", + "Most Creative Hack", + "Best Hardware Hack", + "Best Hack for Social Justice", + "Best User Research", + "Best Entrepreneurship Hack", + "Best Statistical Model", + "Best AI/ML Hack", + "Best UI/UX Design" ] } diff --git a/migrations/20260506000000-add-2026-tracks.mjs b/migrations/20260506000000-add-2026-tracks.mjs index 2f0758a5..da40521e 100644 --- a/migrations/20260506000000-add-2026-tracks.mjs +++ b/migrations/20260506000000-add-2026-tracks.mjs @@ -8,36 +8,6 @@ const dataPath = path.resolve( const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); const tracks = [...new Set(data.tracks)]; -// Track list as of commit 52684b55 (2025 tracks) -const previousTracks = [ - 'Best Hack for Social Good', - 'Best Beginner Hack', - 'Best Interdisciplinary Hack', - 'Most Creative Hack', - 'Best Hack for Social Justice', - 'Best Hardware Hack', - 'Most Technically Challenging Hack', - 'Best Open Data Hack', - 'Best AI/ML Hack', - 'Best UI/UX Design', - 'Best User Research', - 'Best Statistical Model', - 'Best Medical Hack', - 'Best Entrepreneurship Hack', - "Hacker's Choice Award", - 'Best Hack for California GovOps Agency', - 'Best Hack for NAMI Yolo', - 'Best Hack for Fourth and Hope', - 'Best Use of Cerebras API', - 'Best Use of Vectara', - 'Best Use of Gemini API', - 'Best Use of MongoDB Atlas', - 'Best .Tech Domain Name', - 'Best Use of Auth0', - 'Best Use of Snowflake API', - 'Best Assistive Technology', -]; - const teamsSchema = (trackList) => ({ $jsonSchema: { bsonType: 'object', @@ -104,6 +74,6 @@ export const up = async (db) => { export const down = async (db) => { await db.command({ collMod: 'teams', - validator: teamsSchema(previousTracks), + validator: teamsSchema(tracks), }); }; diff --git a/migrations/20260506120000-update-panels-tracks.mjs b/migrations/20260506120000-update-panels-tracks.mjs new file mode 100644 index 00000000..5683a39a --- /dev/null +++ b/migrations/20260506120000-update-panels-tracks.mjs @@ -0,0 +1,62 @@ +import fs from 'fs'; +import path from 'path'; + +const dataPath = path.resolve( + process.cwd(), + 'app/_data/db_validation_data.json' +); +const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); +const judgeVisibleTracks = [...new Set(data.judgeVisibleTracks)]; +const domains = [...new Set(data.domains)]; + +const panelsSchema = (trackList) => ({ + $jsonSchema: { + bsonType: 'object', + title: 'Panels Object Validation', + required: ['track', 'user_ids'], + properties: { + _id: { + bsonType: 'objectId', + description: '_id must be an ObjectId', + }, + track: { + enum: trackList, + description: 'track must be a valid track string', + }, + domain: { + anyOf: [{ enum: domains }, { type: 'string', maxLength: 0 }], + description: `domain must be one of: ${domains.join( + ', ' + )} or empty string`, + }, + user_ids: { + bsonType: 'array', + description: 'user_ids must be an array of object IDs', + maxItems: 5, + items: { + bsonType: 'objectId', + description: 'user_ids must be an array of object IDs', + }, + }, + }, + additionalProperties: false, + }, +}); + +export const up = async (db) => { + await db.command({ + collMod: 'panels', + validator: panelsSchema(judgeVisibleTracks), + validationLevel: 'strict', + validationAction: 'error', + }); +}; + +export const down = async (db) => { + await db.command({ + collMod: 'panels', + validator: panelsSchema(judgeVisibleTracks), + validationLevel: 'strict', + validationAction: 'error', + }); +}; From 2d84510a6ac22fd651d0d3578050c746e4c10b46 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 7 May 2026 21:15:20 -0700 Subject: [PATCH 02/14] Make Panels Tracks = judgeVisibleTracks+Social Good --- app/(api)/_datalib/panels/createPanels.ts | 6 +++--- app/_data/db_validation_data.json | 14 ++++++++++++++ app/_data/tracks.ts | 6 ++++++ migrations/20260506120000-update-panels-tracks.mjs | 6 +++--- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/(api)/_datalib/panels/createPanels.ts b/app/(api)/_datalib/panels/createPanels.ts index b36d2992..0e77040e 100644 --- a/app/(api)/_datalib/panels/createPanels.ts +++ b/app/(api)/_datalib/panels/createPanels.ts @@ -7,10 +7,10 @@ import { } from '@utils/response/Errors'; import isBodyEmpty from '@utils/request/isBodyEmpty'; import parseAndReplace from '@utils/request/parseAndReplace'; -import { judgeVisibleTracks } from '@data/tracks'; +import { panelTracks } from '@data/tracks'; import type Panel from '@typeDefs/panel'; -const validTracks = Object.values(judgeVisibleTracks).map( +const validTracks = Object.values(panelTracks).map( (track) => track.name ); @@ -33,7 +33,7 @@ export const CreatePanel = async (trackName: string) => { const result = await db.collection('panels').insertOne({ track: trackName, - domain: judgeVisibleTracks[trackName].domain, + domain: panelTracks[trackName].domain, user_ids: [], }); diff --git a/app/_data/db_validation_data.json b/app/_data/db_validation_data.json index acf61245..52f63851 100644 --- a/app/_data/db_validation_data.json +++ b/app/_data/db_validation_data.json @@ -38,5 +38,19 @@ "Best Statistical Model", "Best AI/ML Hack", "Best UI/UX Design" + ], + "panelTracks": [ + "Best Hack for Social Good", + "Most Technically Challenging Hack", + "Best Beginner Hack", + "Best Interdisciplinary Hack", + "Most Creative Hack", + "Best Hardware Hack", + "Best Hack for Social Justice", + "Best User Research", + "Best Entrepreneurship Hack", + "Best Statistical Model", + "Best AI/ML Hack", + "Best UI/UX Design" ] } diff --git a/app/_data/tracks.ts b/app/_data/tracks.ts index a54893fa..2b6e1383 100644 --- a/app/_data/tracks.ts +++ b/app/_data/tracks.ts @@ -589,6 +589,11 @@ const judgeVisibleTracks: Tracks = { ...sponsoredNotSendingJudges, }; +const panelTracks: Tracks = { + ...judgeVisibleTracks, + 'Best Hack for Social Good': automaticTracks['Best Hack for Social Good'], +}; + const allTracks: Tracks = { ...automaticTracks, ...optedHDTracks, @@ -609,6 +614,7 @@ export { nonHDTracks, sponsoredNotSendingJudges, judgeVisibleTracks, + panelTracks, displayNameToDomainMap, }; diff --git a/migrations/20260506120000-update-panels-tracks.mjs b/migrations/20260506120000-update-panels-tracks.mjs index 5683a39a..6af5f08e 100644 --- a/migrations/20260506120000-update-panels-tracks.mjs +++ b/migrations/20260506120000-update-panels-tracks.mjs @@ -6,7 +6,7 @@ const dataPath = path.resolve( 'app/_data/db_validation_data.json' ); const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); -const judgeVisibleTracks = [...new Set(data.judgeVisibleTracks)]; +const panelTracks = [...new Set(data.panelTracks)]; const domains = [...new Set(data.domains)]; const panelsSchema = (trackList) => ({ @@ -46,7 +46,7 @@ const panelsSchema = (trackList) => ({ export const up = async (db) => { await db.command({ collMod: 'panels', - validator: panelsSchema(judgeVisibleTracks), + validator: panelsSchema(panelTracks), validationLevel: 'strict', validationAction: 'error', }); @@ -55,7 +55,7 @@ export const up = async (db) => { export const down = async (db) => { await db.command({ collMod: 'panels', - validator: panelsSchema(judgeVisibleTracks), + validator: panelsSchema(panelTracks), validationLevel: 'strict', validationAction: 'error', }); From bc904825439271cc5d2cbebfad12d7ce1b1a8f49 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 7 May 2026 21:15:29 -0700 Subject: [PATCH 03/14] Test data for projects --- app/(api)/_data/test_projects_2026.csv | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 app/(api)/_data/test_projects_2026.csv diff --git a/app/(api)/_data/test_projects_2026.csv b/app/(api)/_data/test_projects_2026.csv new file mode 100644 index 00000000..40982f97 --- /dev/null +++ b/app/(api)/_data/test_projects_2026.csv @@ -0,0 +1,11 @@ +Project Title,Table Number,Submission Url,Project Status,Judging Status,Highest Step Completed,Project Created At,About The Project,"""Try it out"" Links",Video Demo Link,Opt-In Prizes,Built With,Submitter First Name,Submitter Last Name,Submitter Email,Notes,Technical Focus,Design Focus,Track #1 (Primary Track),Track #2,Track #3,Team Colleges/Universities,Additional Team Member Count,Team Member 1 First Name,Team Member 1 Last Name,Team Member 1 Email,Team Member 2 First Name,Team Member 2 Last Name,Team Member 2 Email,Team Member 3 First Name,Team Member 3 Last Name,Team Member 3 Email +AgriSense AI,1,https://hackdavis-2026.devpost.com/submissions/001-agrisense-ai,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 09:00:00,"A machine learning pipeline that classifies crop diseases from drone imagery and recommends treatments using a fine-tuned vision model.",https://agrisense.hackdavis.io,,"Best AI/ML Hack, Best Use of Gemini API, Best UI/UX Design, Best Hack for Social Justice",Python; TensorFlow; Next.js,Sandeep,Reehal,sandeep@hackdavis.io,,10,1,Most Technically Challenging Hack,Best Beginner Hack,Best Hardware Hack,University of California - Davis,1,Michelle,Yeoh,michelleyeoh@hackdavis.io,,, +EduBuddy,2,https://hackdavis-2026.devpost.com/submissions/002-edubuddy,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 09:15:00,"An interactive learning companion for first-time coders that gamifies debugging exercises with adaptive feedback.",https://edubuddy.hackdavis.io,,"Best UI/UX Design, Best Use of ElevenLabs, Best Use of Solana",React; Node.js; ElevenLabs API,Haylie,Tan,haylie@hackdavis.io,,1,7,Best Interdisciplinary Hack,Most Creative Hack,Best Hack for Social Justice,University of California - Davis,2,Afifah,Hadi,afifah@hackdavis.io,Jack,Zheng,jack@hackdavis.io +CrossDiscipline Map,3,https://hackdavis-2026.devpost.com/submissions/003-crossdiscipline-map,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 09:30:00,"A geospatial visualization tool built by a team spanning environmental science, sociology, and CS majors to map community resource deserts in Davis.",https://xdmap.hackdavis.io,,"Best Use of DAC Materials, Best use of Reconstruct, Best Use of Backboard",Next.js; MongoDB; D3.js,Alex,Marasigan,alex@hackdavis.io,,10,7,Best User Research,Best Entrepreneurship Hack,Best Statistical Model,University of California - Davis,1,Sandeep,Reehal,sandeep@hackdavis.io,,, +MoodCanvas,4,https://hackdavis-2026.devpost.com/submissions/004-moodcanvas,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 09:45:00,"A generative art web app that transforms your journaled emotions into personalized abstract paintings using color theory and procedural generation.",https://moodcanvas.hackdavis.io,,"Best Hack for Women's Center, Best Use of Vultr, Best Use of MongoDB Atlas, Most Creative Hack",p5.js; Next.js; MongoDB Atlas,Michelle,Yeoh,michelleyeoh@hackdavis.io,,1,7,Most Technically Challenging Hack,Best Beginner Hack,,University of California - Davis,0,,,,,, +BuildBot 3000,5,https://hackdavis-2026.devpost.com/submissions/005-buildbot-3000,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 10:00:00,"An autonomous sorting robot built with an M5Stack kit that uses computer vision to categorize recyclables and routes them to the correct bin via servo actuators.",https://buildbot.hackdavis.io,,"Best Domain Name from GoDaddy Registry, Best AI/ML Hack, Best Use of ElevenLabs, Best Hack for Women's Center, Best Statistical Model",C++; OpenCV; Arduino; Vultr,Haylie,Tan,haylie@hackdavis.io,,10,1,Best Hardware Hack,Best User Research,,University of California - Davis,2,Afifah,Hadi,afifah@hackdavis.io,Alex,Marasigan,alex@hackdavis.io +JusticeNet,6,https://hackdavis-2026.devpost.com/submissions/006-justicenet,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 10:15:00,"A resource hub for low-income renters facing eviction, connecting them to legal aid organizations and auto-generating demand letters using templated forms.",https://justicenet.hackdavis.io,,"Best UI/UX Design, Best Use of Backboard, Best Use of Solana, Best Entrepreneurship Hack",Next.js; Nodemailer; Figma,Jack,Zheng,jack@hackdavis.io,,10,7,Best Interdisciplinary Hack,Best Statistical Model,,University of California - Davis,1,Michelle,Yeoh,michelleyeoh@hackdavis.io,,, +UserVoice Research Tool,7,https://hackdavis-2026.devpost.com/submissions/007-uservoice,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 10:30:00,"A mixed-methods UX research platform that synthesizes interview transcripts, survey results, and heatmap data into a unified insight dashboard backed by statistical modeling.",https://uservoice.hackdavis.io,,"Best AI/ML Hack, Best Use of Gemini API, Best Use of ElevenLabs",React; Python; Pandas; Scikit-learn,Sandeep,Reehal,sandeep@hackdavis.io,,1,7,Best Hardware Hack,Best Hack for Social Justice,,University of California - Davis,3,Haylie,Tan,haylie@hackdavis.io,Jack,Zheng,jack@hackdavis.io,Afifah,Hadi,afifah@hackdavis.io +MarketMind,8,https://hackdavis-2026.devpost.com/submissions/008-marketmind,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 10:45:00,"A no-code SaaS pitch builder that walks founders through lean canvas modeling, market sizing, and auto-generates a slide deck from their inputs.",https://marketmind.hackdavis.io,,"Best Use of Solana, Best Use of Vultr, Best Use of DAC Materials, Best use of Reconstruct, Best Interdisciplinary Hack",Next.js; Tailwind CSS; Figma API,Alex,Marasigan,alex@hackdavis.io,,1,7,Best Entrepreneurship Hack,Best User Research,,University of California - Davis,1,Michelle,Yeoh,michelleyeoh@hackdavis.io,,, +VoiceBot Arena,9,https://hackdavis-2026.devpost.com/submissions/009-voicebot-arena,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 11:00:00,"A real-time multiplayer game where players command robots using voice instructions parsed by an NLP model, with hardware obstacle courses controlled over WebSockets.",https://voicebotarena.hackdavis.io,,"Best use of Reconstruct, Best Use of MongoDB Atlas, Best Use of Gemini API, Best Beginner Hack",ElevenLabs; WebSockets; Raspberry Pi; Next.js,Afifah,Hadi,afifah@hackdavis.io,,10,1,Most Technically Challenging Hack,Most Creative Hack,,University of California - Davis,2,Jack,Zheng,jack@hackdavis.io,Sandeep,Reehal,sandeep@hackdavis.io +DataStory,10,https://hackdavis-2026.devpost.com/submissions/010-datastory,Submitted (Gallery/Visible),Pending,Submit,05/03/2026 11:15:00,"A storytelling platform that helps first-time data explorers build and share visual narratives from public datasets, with AI-assisted chart recommendations.",https://datastory.hackdavis.io,,"Best Use of DAC Materials, Best Domain Name from GoDaddy Registry, Best AI/ML Hack, Best User Research, Best Hardware Hack",Python; Streamlit; OpenAI API,Haylie,Tan,haylie@hackdavis.io,,10,1,Best Beginner Hack,Best Entrepreneurship Hack,,University of California - Davis,1,Alex,Marasigan,alex@hackdavis.io,,, From 68c298da1489354363f7064aeb450fe090068c34 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 7 May 2026 21:15:55 -0700 Subject: [PATCH 04/14] Fix: migration for table number to be string again --- .../20260507000000-fix-table-number-type.mjs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 migrations/20260507000000-fix-table-number-type.mjs diff --git a/migrations/20260507000000-fix-table-number-type.mjs b/migrations/20260507000000-fix-table-number-type.mjs new file mode 100644 index 00000000..d2e8e4af --- /dev/null +++ b/migrations/20260507000000-fix-table-number-type.mjs @@ -0,0 +1,83 @@ +import fs from 'fs'; +import path from 'path'; + +const dataPath = path.resolve( + process.cwd(), + 'app/_data/db_validation_data.json' +); +const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); +const tracks = [...new Set(data.tracks)]; + +const teamsSchema = (tableNumberType) => ({ + $jsonSchema: { + bsonType: 'object', + title: 'Teams Object Validation', + required: ['teamNumber', 'tableNumber', 'name', 'tracks', 'active'], + properties: { + _id: { + bsonType: 'objectId', + description: '_id must be an ObjectId', + }, + teamNumber: { + bsonType: 'int', + description: 'teamNumber must be an integer', + }, + tableNumber: { + bsonType: tableNumberType, + description: `tableNumber must be a ${tableNumberType}`, + }, + name: { + bsonType: 'string', + description: 'name must be a string', + }, + tracks: { + bsonType: 'array', + items: { + enum: tracks, + description: 'track must be one of the valid tracks', + }, + description: 'tracks must be an array of strings', + }, + reports: { + bsonType: 'array', + items: { + bsonType: 'object', + required: ['timestamp', 'judge_id'], + properties: { + timestamp: { + bsonType: 'number', + description: 'Timestamp in milliseconds since epoch', + }, + judge_id: { + bsonType: 'string', + description: 'ID of the judge', + }, + }, + }, + }, + active: { + bsonType: 'bool', + description: 'active must be a boolean', + }, + }, + additionalProperties: false, + }, +}); + +export const up = async (db) => { + await db.command({ + collMod: 'teams', + validator: teamsSchema('string'), + validationLevel: 'strict', + validationAction: 'error', + }); +}; + +export const down = async (db) => { + await db.command({ + collMod: 'teams', + validator: teamsSchema('int'), + validationLevel: 'strict', + validationAction: 'error', + }); +}; From dd7d42d780e50e27ceccba638e4bbe1b54cc31d9 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 7 May 2026 23:48:46 -0700 Subject: [PATCH 05/14] #528: Devpost submission information --- .../DevpostSubmission/SubmissionTips.tsx | 6 +++++- .../FillOutDetails/FillOutDetails.tsx | 8 +++++++- app/_data/hackbot_knowledge_import.json | 12 +++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/DevpostSubmission/SubmissionTips.tsx b/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/DevpostSubmission/SubmissionTips.tsx index 7cdeb2d8..782d1da5 100644 --- a/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/DevpostSubmission/SubmissionTips.tsx +++ b/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/DevpostSubmission/SubmissionTips.tsx @@ -7,9 +7,13 @@ import arrow from 'public/hackers/project-info/arrow.svg'; const questions = [ { - text: 'PICKED 4 RELEVANT PRIZE TRACKS', + text: 'SELECTED UP TO 4 HACKDAVIS TRACKS', color: styles.qboxGreen, }, + { + text: 'ADDED OPT-IN PRIZES IF RELEVANT (SPONSOR, NPO, OR MLH TRACKS - NO LIMIT)', + color: styles.qboxBlue, + }, { text: 'ADDED YOUR GITHUB AND/OR FIGMA LINKS', color: styles.qboxYellow, diff --git a/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/FillOutDetails/FillOutDetails.tsx b/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/FillOutDetails/FillOutDetails.tsx index 7941ed6d..faa0f731 100644 --- a/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/FillOutDetails/FillOutDetails.tsx +++ b/app/(pages)/(hackers)/_components/ProjectInfo/SubmissionInfo/SubmissionSteps/FillOutDetails/FillOutDetails.tsx @@ -10,7 +10,13 @@ export default function FillOutDetails() { return (

- Fill out respective information - project overview, details, etc + Fill out respective information — project overview, details, etc.
+ When selecting prize tracks: pick{' '} + up to 4 HackDavis tracks via{' '} + Tracks #1–#3 on Devpost, and use the{' '} + Opt-in Prizes section for any{' '} + Sponsor, NPO, or MLH tracks your project qualifies for + (no limit, but keep it relevant).

diff --git a/app/_data/hackbot_knowledge_import.json b/app/_data/hackbot_knowledge_import.json index d10703bf..f5447e82 100644 --- a/app/_data/hackbot_knowledge_import.json +++ b/app/_data/hackbot_knowledge_import.json @@ -2,7 +2,7 @@ { "type": "submission", "title": "Submission Process Overview", - "content": "The HackDavis project submission process has 6 steps and is done through Devpost. Step 1: Login to Devpost \u2014 when you click the Devpost link, click 'Join Hackathon' and log in or sign up for a Devpost account if you don't have one already. Step 2: Register for the Event \u2014 register for the HackDavis hackathon on Devpost. Step 3: Create a Project \u2014 click 'Create project'. Only one person per team needs to create a project and complete the remaining steps. Step 4: Invite Teammates \u2014 invite your teammates to join the Devpost project so everyone is correctly associated with the submission. Step 5: Fill Out Details \u2014 fill out required information such as project overview, description, technical details, and any other requested fields. Step 6: Submit Project \u2014 once all information is filled out and teammates are added, submit the project on Devpost before the deadline.", + "content": "The HackDavis project submission process has 6 steps and is done through Devpost. Step 1: Login to Devpost \u2014 when you click the Devpost link, click 'Join Hackathon' and log in or sign up for a Devpost account if you don't have one already. Step 2: Register for the Event \u2014 register for the HackDavis hackathon on Devpost. Step 3: Create a Project \u2014 click 'Create project'. Only one person per team needs to create a project and complete the remaining steps. Step 4: Invite Teammates \u2014 invite your teammates to join the Devpost project so everyone is correctly associated with the submission. Step 5: Fill Out Details \u2014 fill out required information such as project overview, description, technical details, prize tracks, and any other requested fields. Step 6: Submit Project \u2014 once all information is filled out and teammates are added, submit the project on Devpost before the deadline.", "url": "/project-info#submission" }, { @@ -32,7 +32,7 @@ { "type": "submission", "title": "Step 5: Fill Out Project Details on Devpost", - "content": "Fill out all required information for your project on Devpost. This includes your project overview, description, technical details, and any other requested fields. Make sure every section is complete before submitting.", + "content": "Fill out all required information for your project on Devpost. This includes your project overview, description, technical details, prize tracks, and any other requested fields. Make sure every section is complete before submitting.", "url": "/project-info#submission" }, { @@ -44,7 +44,13 @@ { "type": "submission", "title": "Devpost Submission Tips and Checklist", - "content": "Before submitting on Devpost, make sure you have done the following: (1) Picked 4 relevant prize tracks \u2014 choose up to 4 prize tracks that best describe your project. (2) Added your GitHub and/or Figma links \u2014 include links to your code repository and any design files. (3) Inserted a demo video \u2014 upload or link a video demonstrating your project in action. The HackDavis 2026 Devpost page is at https://hackdavis-2026.devpost.com/", + "content": "Before submitting on Devpost, make sure you have done the following: (1) Selected your prize tracks correctly — on Devpost, Tracks 1-3 show only HackDavis prize tracks; select up to 4 HackDavis tracks total. The Opt-in Prizes section shows Sponsor, NPO, and MLH tracks — you may select as many of these as your project genuinely qualifies for. (2) Added your GitHub and/or Figma links — include links to your code repository and any design files. (3) Inserted a demo video — upload or link a video demonstrating your project in action. The HackDavis 2026 Devpost page is at https://hackdavis-2026.devpost.com/", + "url": "/project-info#submission" + }, + { + "type": "submission", + "title": "Prize Track Selection: HackDavis Tracks vs. Opt-in Prizes", + "content": "On Devpost, prize tracks are split into two sections. Tracks 1-3 show only HackDavis prize tracks — these include both automatic tracks (Best Hack for Social Good and Hacker's Choice Award, which every team is considered for automatically) and opted HackDavis tracks (Most Technically Challenging Hack, Best Beginner Hack, Most Creative Hack, Best Hack for Social Justice, Best User Research, Best Entrepreneurship Hack, Best Statistical Model, Best Interdisciplinary Hack, Best Hardware Hack). You are encouraged to select up to 4 HackDavis tracks in total across Tracks 1-3. The Opt-in Prizes section includes Sponsor tracks (e.g. Best AI/ML Hack sponsored by Anthropic, Best UI/UX Design sponsored by Figma, Best Use of DAC Materials, Best use of Reconstruct), NPO tracks (e.g. Best Hack for Women's Center), and MLH tracks (e.g. Best Use of Gemini API, Best Use of ElevenLabs, Best Use of Solana, Best Use of Backboard, Best Use of Vultr, Best Use of MongoDB Atlas, Best Domain Name from GoDaddy Registry). There is no hard limit on Sponsor, NPO, or MLH opt-in tracks — but only select the ones your project genuinely uses or qualifies for.", "url": "/project-info#submission" }, { From 60789a3a9362408c45af83dd3623b49fc77718a5 Mon Sep 17 00:00:00 2001 From: reehals Date: Thu, 7 May 2026 23:50:54 -0700 Subject: [PATCH 06/14] Update HackBot knowledge import with new prizes and opt-in info --- app/_data/hackbot_knowledge_import.json | 90 +++++++++++++++++++------ 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/app/_data/hackbot_knowledge_import.json b/app/_data/hackbot_knowledge_import.json index f5447e82..27a9cdbf 100644 --- a/app/_data/hackbot_knowledge_import.json +++ b/app/_data/hackbot_knowledge_import.json @@ -122,91 +122,139 @@ { "type": "track", "title": "Prize Track: Hacker's Choice Award", - "content": "Track: Hacker's Choice Award. Filter category: General. Prize: HackDavis Swag Bag. Eligibility: Awarded to the project with the most votes from 2026 hackers. All entries are automatically considered for this prize category. Vote for any project but your own! Voting happens during the break period after demos.", + "content": "Track: Hacker's Choice Award. Filter category: General (Automatic — all entries considered, no opt-in required). Prize: HackDavis Swag Bag (Tote, Stickers, Keychains). Eligibility: Awarded to the project with the most votes from 2026 hackers. All entries are automatically considered for this prize category. Vote for any project but your own! Voting happens during the break period after demos.", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Most Technically Challenging Hack", - "content": "Track: Most Technically Challenging Hack. Filter category: Technical. Domain: Software Engineering. Prize: Backlit Keyboard. Eligibility: Projects must showcase breadth and application of technical knowledge. Focuses on use of advanced technical tools and algorithms/data structures, integration of multiple technologies, quality of implementation, technical depth, and performance/scalability. Scoring criteria: (1) Technical Complexity of the Problem \u2014 from basic/well-known problems (score 1) to highly complex or novel problems requiring significant technical insight (score 5). (2) Quality of Engineering \u2014 from incomplete/poorly structured project (score 1) to exceptionally well-engineered, modular, scalable, fault-tolerant and efficient (score 5). (3) Integration of Tools or Techniques \u2014 from minimal external tools (score 1) to skillful integration of multiple advanced technologies like parallelism and optimization (score 5).", + "content": "Track: Most Technically Challenging Hack. Filter category: Technical (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Software Engineering. Prize: AULA F75 75% Wireless Mechanical Keyboard. Eligibility: Projects must showcase breadth and application of technical knowledge. Focuses on use of advanced technical tools and algorithms/data structures, integration of multiple technologies, quality of implementation, technical depth, and performance/scalability. Scoring criteria: (1) Technical Complexity of the Problem — from basic/well-known problems (score 1) to highly complex or novel problems requiring significant technical insight (score 5). (2) Quality of Engineering — from incomplete/poorly structured project (score 1) to exceptionally well-engineered, modular, scalable, fault-tolerant and efficient (score 5). (3) Integration of Tools or Techniques — from minimal external tools (score 1) to skillful integration of multiple advanced technologies like parallelism and optimization (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Beginner Hack", - "content": "Track: Best Beginner Hack. Filter category: General. Domain: Software Engineering. Prize: 24 Inch Monitor. Eligibility: Every team member must be a first-time hacker in order to qualify. Demonstrate a high level of growth through this project, foster creativity and collaboration within the team, and display a commitment to building skills. Scoring criteria: (1) Evidence of Learning and Growth \u2014 from little learning shown and reused known skills (score 1) to a strong grasp of entirely new topics applied effectively (score 5). (2) Team Collaboration \u2014 from disjointed teamwork with unclear roles (score 1) to strong team balance with active support across roles (score 5). (3) Problem-Solving and Persistence \u2014 from giving up easily (score 1) to tackling tough issues with creative persistence (score 5).", + "content": "Track: Best Beginner Hack. Filter category: General (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Software Engineering. Prize: 24 Inch Monitor. Eligibility: Every team member must be a first-time hacker in order to qualify. Demonstrate a high level of growth through this project, foster creativity and collaboration within the team, and display a commitment to building skills. Scoring criteria: (1) Evidence of Learning and Growth — from little learning shown and reused known skills (score 1) to a strong grasp of entirely new topics applied effectively (score 5). (2) Team Collaboration — from disjointed teamwork with unclear roles (score 1) to strong team balance with active support across roles (score 5). (3) Problem-Solving and Persistence — from giving up easily (score 1) to tackling tough issues with creative persistence (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Interdisciplinary Hack", - "content": "Track: Best Interdisciplinary Hack. Filter category: General. Domain: Software Engineering. Prize: $75 STEAM Giftcard. Eligibility: Leverage multiple perspectives across different disciplines to create a more well-rounded project. At least one member of the team must be a non-CS/CSE/otherwise CS-related major in order to qualify. Scoring criteria: (1) Problem Selection \u2014 from a problem solvable within one discipline (score 1) to a highly original problem that requires all members' disciplines (score 5). (2) Disciplinary Balance \u2014 from all CS-related majors or one discipline dominating (score 1) to disciplines being deeply interwoven with equal importance (score 5). (3) Cross-Field Innovation \u2014 from disciplines barely connected (score 1) to a true blend creating something impossible within one field (score 5).", + "content": "Track: Best Interdisciplinary Hack. Filter category: General (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Software Engineering. Prize: $50 Amazon Giftcard. Eligibility: Leverage multiple perspectives across different disciplines to create a more well-rounded project. At least one member of the team must be a non-CS/CSE/otherwise CS-related major in order to qualify. Scoring criteria: (1) Problem Selection — from a problem solvable within one discipline (score 1) to a highly original problem that requires all members' disciplines (score 5). (2) Disciplinary Balance — from all CS-related majors or one discipline dominating (score 1) to disciplines being deeply interwoven with equal importance (score 5). (3) Cross-Field Innovation — from disciplines barely connected (score 1) to a true blend creating something impossible within one field (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Most Creative Hack", - "content": "Track: Most Creative Hack. Filter category: General. Domain: Business. Prize: Mini Projector. Eligibility: Projects should demonstrate originality, showcase out-of-the-box thinking, and captivate their audience. Scoring criteria: (1) Originality of Concept \u2014 from a common idea similar to known projects (score 1) to a fresh, unexpected concept (score 5). (2) Creative Execution \u2014 from a conventional build with little imagination (score 1) to inventive design with imaginative features (score 5). (3) User Engagement \u2014 from uninspiring or hard to connect with (score 1) to a memorable and captivating experience (score 5).", + "content": "Track: Most Creative Hack. Filter category: General (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Business. Prize: Mini Projector. Eligibility: Projects should demonstrate originality, showcase out-of-the-box thinking, and captivate their audience. Scoring criteria: (1) Originality of Concept — from a common idea similar to known projects (score 1) to a fresh, unexpected concept (score 5). (2) Creative Execution — from a conventional build with little imagination (score 1) to inventive design with imaginative features (score 5). (3) User Engagement — from uninspiring or hard to connect with (score 1) to a memorable and captivating experience (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Hardware Hack", - "content": "Track: Best Hardware Hack. Filter category: Technical. Domain: Hardware or Embedded Systems. Prize: Raspberry Pi Kit. Eligibility: Effectively integrate a hardware component into your final project. The final project should be functional, user-friendly, and interactive. Scoring criteria: (1) Hardware Integration \u2014 from disconnected or non-functional hardware (score 1) to seamless integration that is essential to the project (score 5). (2) Feasibility and Technical Soundness \u2014 from an unrealistic approach (score 1) to a well-grounded and executable design that is feasible to reproduce or extend (score 5). (3) User Interaction \u2014 from difficult to operate or requiring technical knowledge (score 1) to intuitive, responsive interaction that feels natural and engaging (score 5).", + "content": "Track: Best Hardware Hack. Filter category: Technical (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Hardware or Embedded Systems. Prize: Logitech G305 Lightspeed Wireless Gaming Mouse. Eligibility: Effectively integrate a hardware component into your final project. The final project should be functional, user-friendly, and interactive. Scoring criteria: (1) Hardware Integration — from disconnected or non-functional hardware (score 1) to seamless integration that is essential to the project (score 5). (2) Feasibility and Technical Soundness — from an unrealistic approach (score 1) to a well-grounded and executable design that is feasible to reproduce or extend (score 5). (3) User Interaction — from difficult to operate or requiring technical knowledge (score 1) to intuitive, responsive interaction that feels natural and engaging (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Hack for Social Justice", - "content": "Track: Best Hack for Social Justice. Filter category: General. Domain: Business. Prize: Kindle. Eligibility: Hack must address a social justice issue such as racial inequality, economic injustice, environmental justice, etc. The project should develop tangible solutions and/or raise awareness on these topics. Scoring criteria: (1) Issue Understanding and Community Consideration \u2014 from surface-level grasp with minimal community thought (score 1) to deep insight centered on the voices and needs of affected groups (score 5). (2) Advocacy Effectiveness \u2014 from a passive presentation with no engagement strategy (score 1) to a compelling call to action with practical pathways for audience involvement (score 5). (3) Implementation Feasibility and Impact \u2014 from a conceptual solution with significant barriers (score 1) to a ready-to-launch solution with demonstrated potential for measurable impact (score 5).", + "content": "Track: Best Hack for Social Justice. Filter category: General (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Business. Prize: Google TV Streamer 4K. Eligibility: Hack must address a social justice issue such as racial inequality, economic injustice, environmental justice, etc. The project should develop tangible solutions and/or raise awareness on these topics. Scoring criteria: (1) Issue Understanding and Community Consideration — from surface-level grasp with minimal community thought (score 1) to deep insight centered on the voices and needs of affected groups (score 5). (2) Advocacy Effectiveness — from a passive presentation with no engagement strategy (score 1) to a compelling call to action with practical pathways for audience involvement (score 5). (3) Implementation Feasibility and Impact — from a conceptual solution with significant barriers (score 1) to a ready-to-launch solution with demonstrated potential for measurable impact (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best UI/UX Design", - "content": "Track: Best UI/UX Design. Filter category: Design. Domain: UI/UX Design. Prize: Figma Full Seat (4-month subscription). Eligibility: Project includes beautiful design and intuitive web experiences that bring joy to users. Shows that the project is not only functional but also delightful; demonstrates wireframing in Figma, responsive design, and promotes intuitive user experiences. Scoring criteria: (1) Visual Design \u2014 from inconsistent style with poor accessibility (score 1) to beautiful, cohesive, polished design with thoughtful inclusivity (score 5). (2) Navigation Flow \u2014 from confusing user journey with hard-to-find key actions (score 1) to effortless, intuitive navigation throughout (score 5). (3) Design Process \u2014 from limited evidence of planning (score 1) to a comprehensive process with wireframes through to a final product (score 5).", + "content": "Track: Best UI/UX Design (Sponsored by Figma). Filter category: Sponsor / Design (Opt-in prize on Devpost — select in the Opt-in Prizes section). Domain: UI/UX Design. Prize: Sony WH-1000XM5 Wireless Noise-Canceling Headphones. Eligibility: Project uses Figma to create beautiful design and intuitive web experiences that bring joy to users. Shows that the project is not only functional but also delightful; demonstrates wireframing in Figma, responsive design, and promotes intuitive user experiences. Sponsored by Figma. Scoring criteria: (1) Visual Design — from inconsistent style with poor accessibility (score 1) to beautiful, cohesive, polished design with thoughtful inclusivity (score 5). (2) Navigation Flow — from confusing user journey with hard-to-find key actions (score 1) to effortless, intuitive navigation throughout (score 5). (3) Design Process — from limited evidence of planning (score 1) to a comprehensive process with wireframes through to a final product (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best User Research", - "content": "Track: Best User Research. Filter category: Design. Domain: UI/UX Design. Prize: ChatGPT+ (4-month subscription). Eligibility: Awarded to a well-researched project that keeps its userbase in mind with an inclusive design aimed to maximize accessibility. Scoring criteria: (1) User Understanding \u2014 from assumptions made with minimal research (score 1) to comprehensive insights into user needs and behaviors (score 5). (2) Depth of Research Methods \u2014 from few or irrelevant data points (score 1) to a thoughtful combination of multiple research methods (score 5). (3) Design Application and Feedback Integration \u2014 from research/feedback ignored or misaligned with design (score 1) to each design element directly tied to research findings (score 5).", + "content": "Track: Best User Research. Filter category: Design (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: UI/UX Design. Prize: ChatGPT+ (4 month subscription). Eligibility: Awarded to a well-researched project that keeps its userbase in mind with an inclusive design aimed to maximize accessibility. Scoring criteria: (1) User Understanding — from assumptions made with minimal research (score 1) to comprehensive insights into user needs and behaviors (score 5). (2) Depth of Research Methods — from few or irrelevant data points (score 1) to a thoughtful combination of multiple research methods (score 5). (3) Design Application and Feedback Integration — from research/feedback ignored or misaligned with design (score 1) to each design element directly tied to research findings (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Entrepreneurship Hack", - "content": "Track: Best Entrepreneurship Hack. Filter category: Business. Domain: Business. Prize: North Face Backpack. Eligibility: No code required. A project that focuses on viability and persuasive power through presentation on the product/service you are trying to sell, relevant customer segments, distribution channels, and associated revenue/profit models. Scoring criteria: (1) Target Customer Clarity \u2014 from vague ideas of potential users (score 1) to detailed customer profiles with validated pain points (score 5). (2) Business Model \u2014 from unclear how the project would make money (score 1) to a well-thought-out pricing and monetization strategy (score 5). (3) Market Differentiation \u2014 from little distinction from existing solutions (score 1) to a clear competitive advantage with strong market positioning (score 5).", + "content": "Track: Best Entrepreneurship Hack. Filter category: Business (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Business. Prize: North Face Backpack. Eligibility: No code required. A project that focuses on viability and persuasive power through presentation on the product/service you are trying to sell, relevant customer segments, distribution channels, and associated revenue/profit models. Scoring criteria: (1) Target Customer Clarity — from vague ideas of potential users (score 1) to detailed customer profiles with validated pain points (score 5). (2) Business Model — from unclear how the project would make money (score 1) to a well-thought-out pricing and monetization strategy (score 5). (3) Market Differentiation — from little distinction from existing solutions (score 1) to a clear competitive advantage with strong market positioning (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Statistical Model", - "content": "Track: Best Statistical Model. Filter category: Business. Domain: Data Science or AI/ML. Prize: Bluetooth Speaker. Eligibility: Projects must use exploratory data analysis (EDA) to guide their modeling decisions and hypotheses. Final models should include significance tests and be evaluated with metrics like MSE, R\u00b2, adjusted R\u00b2, precision, or recall, demonstrating clear statistical reasoning aligned with the project's core question or goal. Scoring criteria: (1) Exploratory Data Analysis \u2014 from minimal data exploration (score 1) to comprehensive EDA with insightful visualizations that directly inform model design (score 5). (2) Use of Statistical Tests \u2014 from inappropriate or missing tests (score 1) to proper tests applied correctly to the data and analyzed thoroughly (score 5). (3) Results Interpretation \u2014 from unclear numbers with little explanation (score 1) to insightful interpretation connecting statistics to the real world (score 5).", + "content": "Track: Best Statistical Model. Filter category: Business (HackDavis track — opt-in, select via Tracks 1-3 on Devpost). Domain: Data Science or AI/ML. Prize: Bluetooth Speaker. Eligibility: Projects must use exploratory data analysis (EDA) to guide their modeling decisions and hypotheses. Final models should include significance tests and be evaluated with metrics like MSE, R², adjusted R², precision, or recall, demonstrating clear statistical reasoning aligned with the project's core question or goal. Scoring criteria: (1) Exploratory Data Analysis — from minimal data exploration (score 1) to comprehensive EDA with insightful visualizations that directly inform model design (score 5). (2) Use of Statistical Tests — from inappropriate or missing tests (score 1) to proper tests applied correctly to the data and analyzed thoroughly (score 5). (3) Results Interpretation — from unclear numbers with little explanation (score 1) to insightful interpretation connecting statistics to the real world (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best AI/ML Hack", - "content": "Track: Best AI/ML Hack. Filter category: Sponsor. Domain: Data Science or AI/ML. Prize: $750 in Claude API credits. Eligibility: Project must have unique/creative AI functionality, clean data, accuracy in metrics, presence of high-quality data, utilizing relevant algorithms and ML libraries and/or cloud platforms for development. Participants should show how they collected their data and explain how their AI imitates the human mind. Models should work accurately on unseen circumstances (displays versatility). Scoring criteria: (1) Necessity of AI/ML for Solving the Problem \u2014 from a problem obviously solvable with deterministic algorithms (score 1) to one where deterministic algorithms are unable to solve the problem and AI/ML is the only solution (score 5). (2) Model Performance and Evaluation \u2014 from poor accuracy with minimal metrics (score 1) to strong results backed by solid metrics tested on unseen data or edge cases (score 5). (3) Technical Execution and Use of Tools \u2014 from surface-level tool use with no customization (score 1) to deep technical execution with custom methods and advanced techniques (score 5).", + "content": "Track: Best AI/ML Hack (Sponsored by Anthropic). Filter category: Sponsor / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section). Domain: Data Science or AI/ML. Prize: $750 in Claude API credits. Eligibility: Project must have unique/creative AI functionality, clean data, accuracy in metrics, presence of high-quality data, utilizing relevant algorithms and ML libraries and/or cloud platforms for development. Participants should show how they collected their data and explain how their AI imitates the human mind. Models should work accurately on unseen circumstances (displays versatility). Sponsored by Anthropic. Scoring criteria: (1) Necessity of AI/ML for Solving the Problem — from a problem obviously solvable with deterministic algorithms (score 1) to one where deterministic algorithms are unable to solve the problem and AI/ML is the only solution (score 5). (2) Model Performance and Evaluation — from poor accuracy with minimal metrics (score 1) to strong results backed by solid metrics tested on unseen data or edge cases (score 5). (3) Technical Execution and Use of Tools — from surface-level tool use with no customization (score 1) to deep technical execution with custom methods and advanced techniques (score 5).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track: Best Hack for Women's Center", - "content": "Track: Best Hack for Women's Center. Filter category: Non-Profit. Prize: Aroma Diffuser. Eligibility criteria will be announced \u2014 check the prize tracks page or Devpost for the latest details.", + "content": "Track: Best Hack for Women's Center. Filter category: Non-Profit (Opt-in prize on Devpost — select in the Opt-in Prizes section). Prize: Anker Nano 3-in-1 Portable iPhone Charger. Eligibility: Projects must create a digital system to track donations as they come in and go out. Wellspring (Women's Center) is looking for a straightforward, easy-to-use digital tool that helps staff and volunteers quickly log donated items, track how they are distributed, and generate basic reports when needed.", "url": "/#prize-tracks" }, { "type": "track", - "title": "Prize Track: Best Hack for ASUCD Pantry", - "content": "Track: Best Hack for ASUCD Pantry. Filter category: Non-Profit. Prize: Pokemon Packs. Eligibility criteria will be announced \u2014 check the prize tracks page or Devpost for the latest details.", + "title": "Prize Track: Best Use of DAC Materials", + "content": "Track: Best Use of DAC Materials (Sponsored by Davis Autonomy Club). Filter category: Sponsor / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section). Prize: $10,000 Daytona infrastructure credits. Eligibility: Project must incorporate one or more of DAC's materials with a vision-based AI pipeline, implementing and/or configuring concepts such as Vision-Language Models (VLMs) or Vision-Language-Action Models (VLAs) to connect real-world visual perception to physical robotic behavior. Sponsored by the Davis Autonomy Club.", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best use of Reconstruct", + "content": "Track: Best use of Reconstruct (Sponsored by Reconstruct). Filter category: Sponsor / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section). Prize: $125 Visa Gift Card per team member. Eligibility: Most creative use of Reconstruct in their project. Project must use Reconstruct in a prominent and efficient way to qualify for this prize track. Sponsored by Reconstruct.", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of Gemini API", + "content": "Track: Best Use of Gemini API. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Google Swag Kits. Eligibility: Use the Google Gemini API to build AI-powered apps. Gemini can understand language like a human, analyze info like a supercomputer, and generate creative content like code, scripts, and music. Build a chatbot that gives personalized advice, an app that summarizes complex research papers, or any other creative AI-powered application. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of ElevenLabs", + "content": "Track: Best Use of ElevenLabs. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Wireless Earbuds. Eligibility: Deploy natural, human-sounding audio with ElevenLabs. Create realistic, dynamic, and emotionally expressive voices for any project — from interactive AI companions to narrated stories and voice-enabled apps. Integrate fully autonomous audio experiences into your hack with ElevenLabs and give your project a voice. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of Solana", + "content": "Track: Best Use of Solana. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Ledger Nano S Plus. Eligibility: Build applications using the Solana blockchain, which is built for high speed and near-zero transaction costs. Create a game, social app, or consumer product that relies on instant high-frequency transactions; design a decentralized exchange (DEX); or build a prototype for supply chain, identity, or payments. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of Backboard", + "content": "Track: Best Use of Backboard. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Tile Essentials Pack. Eligibility: Use Backboard — a unified API for AI state management — in your project. Backboard provides long-term memory, RAG, embeddings, tool calls, model routing across 17,000+ LLMs, and persistent context across sessions. Every AI model API is stateless by default; Backboard solves this. Build seamless, persistent user experiences like an AI travel guide that remembers preferences or a fitness coach that adapts over time. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of Vultr", + "content": "Track: Best Use of Vultr. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Portable Screens. Eligibility: Use Vultr cloud infrastructure in your project. Vultr provides one-click deployment, scalable cloud compute, and specialized Cloud GPUs for AI-driven applications. Sign up for a Vultr account and claim free cloud credits to take your hack to the cloud. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Use of MongoDB Atlas", + "content": "Track: Best Use of MongoDB Atlas. Filter category: MLH / Technical (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: M5Stack IoT Kit. Eligibility: Build a hack using MongoDB Atlas. Get started with a $50 credit for students or the Atlas free forever tier (no credit card required). Atlas offers a full suite of cloud database services and functionality. Free resources are available through MongoDB University. Judged by MLH (Major League Hacking).", + "url": "/#prize-tracks" + }, + { + "type": "track", + "title": "Prize Track: Best Domain Name from GoDaddy Registry", + "content": "Track: Best Domain Name from GoDaddy Registry. Filter category: MLH (Opt-in prize on Devpost — select in the Opt-in Prizes section, judged by MLH). Prize: Digital Gift Card. Eligibility: Register your domain name with GoDaddy Registry for your hackathon project to be eligible for this prize. Judged by MLH (Major League Hacking).", "url": "/#prize-tracks" }, { "type": "track", "title": "Prize Track Categories Overview", - "content": "HackDavis 2026 prize tracks are organized into the following filter categories: General (Best Hack for Social Good, Hacker's Choice Award, Best Beginner Hack, Best Interdisciplinary Hack, Most Creative Hack, Best Hack for Social Justice), Technical (Most Technically Challenging Hack, Best Hardware Hack), Design (Best UI/UX Design, Best User Research), Business (Best Entrepreneurship Hack, Best Statistical Model), Sponsor (Best AI/ML Hack), Non-Profit (Best Hack for Women's Center, Best Hack for ASUCD Pantry). You can select up to 4 relevant prize tracks for your Devpost submission.", + "content": "HackDavis 2026 prize tracks fall into two groups on Devpost. HackDavis Tracks (select via Tracks 1-3 on Devpost, up to 4 total): General — Best Hack for Social Good (automatic), Hacker's Choice Award (automatic), Best Beginner Hack, Best Interdisciplinary Hack, Most Creative Hack, Best Hack for Social Justice; Technical — Most Technically Challenging Hack, Best Hardware Hack; Design — Best UI/UX Design (see also Sponsor), Best User Research; Business — Best Entrepreneurship Hack, Best Statistical Model. Opt-in Prizes (select in the Opt-in Prizes section, no hard limit — choose only tracks your project qualifies for): Sponsor — Best AI/ML Hack (Anthropic), Best UI/UX Design (Figma), Best Use of DAC Materials (Davis Autonomy Club), Best use of Reconstruct; Non-Profit — Best Hack for Women's Center; MLH — Best Use of Gemini API, Best Use of ElevenLabs, Best Use of Solana, Best Use of Backboard, Best Use of Vultr, Best Use of MongoDB Atlas, Best Domain Name from GoDaddy Registry.", "url": "/#prize-tracks" }, { @@ -242,7 +290,7 @@ { "type": "faq", "title": "FAQ: How do I submit my project?", - "content": "To submit your project, follow the 6-step Devpost process: (1) Login or sign up on Devpost and click 'Join Hackathon' on the HackDavis Devpost page. (2) Register for the event. (3) Click 'Create project' \u2014 only one team member needs to do this. (4) Invite your teammates to the project. (5) Fill out all project details including overview, description, and technical information. (6) Click 'Submit Project' before the 11:00 AM deadline on the second day. The Devpost link is https://hackdavis-2026.devpost.com/", + "content": "To submit your project, follow the 6-step Devpost process: (1) Login or sign up on Devpost and click 'Join Hackathon' on the HackDavis Devpost page. (2) Register for the event. (3) Click 'Create project' — only one team member needs to do this. (4) Invite your teammates to the project. (5) Fill out all project details including overview, description, technical information, and select your prize tracks. (6) Click 'Submit Project' before the 11:00 AM deadline on the second day. The Devpost link is https://hackdavis-2026.devpost.com/", "url": "/project-info#submission" }, { @@ -254,7 +302,7 @@ { "type": "faq", "title": "FAQ: What should I include in my Devpost submission?", - "content": "Your Devpost submission should include: a project overview and detailed description, technical details, up to 4 relevant prize tracks you are entering, your GitHub repository link and/or Figma design file link, and a demo video showing your project in action. Be thorough \u2014 judges review your Devpost submission.", + "content": "Your Devpost submission should include: a project overview and detailed description, technical details, your selected prize tracks (see below for how to pick), your GitHub repository link and/or Figma design file link, and a demo video showing your project in action. Be thorough — judges review your Devpost submission. For prize tracks: select up to 4 HackDavis tracks via Tracks 1-3 on Devpost, and use the Opt-in Prizes section for any Sponsor, NPO, or MLH tracks your project qualifies for.", "url": "/project-info#submission" }, { @@ -266,7 +314,7 @@ { "type": "faq", "title": "FAQ: How are projects judged?", - "content": "Projects are judged using a rubric with four components: 60% Track-Specific criteria (based on the prize tracks you chose), 20% Social Good (how well the project benefits society), 10% Creativity (originality), and 10% Presentation. During demo time (12:00-2:00 PM), judges visit your table \u2014 each of the first 3 judges gets 3 minutes of demo time and 3 minutes of Q&A. Panels of judges then select winners from the top 5 shortlisted projects per track during the break.", + "content": "Projects are judged using a rubric with four components: 60% Track-Specific criteria (based on the prize tracks you chose), 20% Social Good (how well the project benefits society), 10% Creativity (originality), and 10% Presentation. During demo time (12:00-2:00 PM), judges visit your table — each of the first 3 judges gets 3 minutes of demo time and 3 minutes of Q&A. Panels of judges then select winners from the top 5 shortlisted projects per track during the break.", "url": "/project-info#judging" }, { @@ -284,7 +332,7 @@ { "type": "faq", "title": "FAQ: What is the Hacker's Choice Award?", - "content": "The Hacker's Choice Award is voted on by fellow hackers \u2014 not judges. After demo time ends (during the 2:00-3:00 PM break), you can visit other teams and vote for the project you think deserves this award. All entries are automatically considered. You can vote for any project but your own. The winner receives a HackDavis Swag Bag. The voting form link will be available on the Hub when voting opens.", + "content": "The Hacker's Choice Award is voted on by fellow hackers — not judges. After demo time ends (during the 2:00-3:00 PM break), you can visit other teams and vote for the project you think deserves this award. All entries are automatically considered. You can vote for any project but your own. The winner receives a HackDavis Swag Bag (Tote, Stickers, Keychains). The voting form link will be available on the Hub when voting opens.", "url": "/project-info#break" }, { @@ -320,7 +368,7 @@ { "type": "faq", "title": "FAQ: How many prize tracks can I enter?", - "content": "You can select up to 4 relevant prize tracks on your Devpost submission. Choose the tracks that best match your project. Some tracks (Best Hack for Social Good and Hacker's Choice Award) automatically include all entries \u2014 no opt-in required.", + "content": "On Devpost, prize tracks are split into two groups. For HackDavis tracks (shown under Tracks 1-3 on Devpost), you can select up to 4 total — this includes both automatic tracks (Best Hack for Social Good and Hacker's Choice Award, which every team is already considered for) and opted HackDavis tracks. For Opt-in Prizes (Sponsor, NPO, and MLH tracks shown in the Opt-in Prizes section), there is no hard limit — select as many as your project genuinely qualifies for. Only opt into tracks that are truly relevant to your project.", "url": "/#prize-tracks" }, { From 63500509728d4b7464e505f8a271405210f61c6c Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 00:02:43 -0700 Subject: [PATCH 07/14] Cleanup table number migration --- .../20260507000000-fix-table-number-type.mjs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/migrations/20260507000000-fix-table-number-type.mjs b/migrations/20260507000000-fix-table-number-type.mjs index d2e8e4af..24f20516 100644 --- a/migrations/20260507000000-fix-table-number-type.mjs +++ b/migrations/20260507000000-fix-table-number-type.mjs @@ -8,7 +8,7 @@ const dataPath = path.resolve( const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); const tracks = [...new Set(data.tracks)]; -const teamsSchema = (tableNumberType) => ({ +const teamsSchema = { $jsonSchema: { bsonType: 'object', title: 'Teams Object Validation', @@ -23,8 +23,8 @@ const teamsSchema = (tableNumberType) => ({ description: 'teamNumber must be an integer', }, tableNumber: { - bsonType: tableNumberType, - description: `tableNumber must be a ${tableNumberType}`, + bsonType: 'string', + description: 'tableNumber must be an string', }, name: { bsonType: 'string', @@ -62,22 +62,15 @@ const teamsSchema = (tableNumberType) => ({ }, additionalProperties: false, }, -}); - -export const up = async (db) => { - await db.command({ - collMod: 'teams', - validator: teamsSchema('string'), - validationLevel: 'strict', - validationAction: 'error', - }); }; -export const down = async (db) => { - await db.command({ +const applySchema = (db) => + db.command({ collMod: 'teams', - validator: teamsSchema('int'), + validator: teamsSchema, validationLevel: 'strict', validationAction: 'error', }); -}; + +export const up = (db) => applySchema(db); +export const down = (db) => applySchema(db); From 0e30e9ef73681bd726c34c107136031e96649fba Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 00:07:10 -0700 Subject: [PATCH 08/14] Fix: Whey lint --- app/(api)/_datalib/panels/createPanels.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/(api)/_datalib/panels/createPanels.ts b/app/(api)/_datalib/panels/createPanels.ts index 0e77040e..4a4819ae 100644 --- a/app/(api)/_datalib/panels/createPanels.ts +++ b/app/(api)/_datalib/panels/createPanels.ts @@ -10,9 +10,7 @@ import parseAndReplace from '@utils/request/parseAndReplace'; import { panelTracks } from '@data/tracks'; import type Panel from '@typeDefs/panel'; -const validTracks = Object.values(panelTracks).map( - (track) => track.name -); +const validTracks = Object.values(panelTracks).map((track) => track.name); export const CreatePanel = async (trackName: string) => { try { From a4955ccba08f00a699af5d34b9cf867af3019f08 Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 00:11:42 -0700 Subject: [PATCH 09/14] Fix: That broken panels test --- __tests__/datalib/panels/createPanels.test.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/__tests__/datalib/panels/createPanels.test.ts b/__tests__/datalib/panels/createPanels.test.ts index 0f9f61f3..ad2ef655 100644 --- a/__tests__/datalib/panels/createPanels.test.ts +++ b/__tests__/datalib/panels/createPanels.test.ts @@ -1,17 +1,20 @@ +const mockTracks = { + 'Best AI/ML Hack': { + name: 'Best AI/ML Hack', + domain: 'aiml', + domainDisplayName: 'Data Science or AI/ML', + }, + 'Most Technically Challenging Hack': { + name: 'Most Technically Challenging Hack', + domain: 'swe', + domainDisplayName: 'Software Engineering', + }, +}; + jest.mock('@data/tracks', () => ({ __esModule: true, - judgeVisibleTracks: { - 'Best AI/ML Hack': { - name: 'Best AI/ML Hack', - domain: 'aiml', - domainDisplayName: 'Data Science or AI/ML', - }, - 'Most Technically Challenging Hack': { - name: 'Most Technically Challenging Hack', - domain: 'swe', - domainDisplayName: 'Software Engineering', - }, - }, + judgeVisibleTracks: mockTracks, + panelTracks: mockTracks, })); jest.mock('@utils/mongodb/mongoClient.mjs', () => ({ From b23427d89a7943285d9bcac3e7423a8df0e42215 Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 00:57:14 -0700 Subject: [PATCH 10/14] Feat: CSV ingestion preview table --- .../_utils/csv-ingestion/csvAlgorithm.ts | 25 ++ .../CsvIngestion/ProcessedTeamsTable.tsx | 382 ++++++++++++++++++ app/(pages)/admin/csv/page.tsx | 11 +- 3 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx diff --git a/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts b/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts index ea18fff1..53d794b0 100644 --- a/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts +++ b/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts @@ -33,6 +33,16 @@ export type CsvRowIssue = { duplicateTeamNumber?: number; }; +export type CsvRowContact = { + teamNumber: number | undefined; + projectTitle: string; + contactEmails: string[]; + contactNames: string[]; + memberEmails: string[]; + memberNames: string[]; + memberColumnsFromTeamMember1: Array<{ header: string; value: string }>; +}; + export type CsvValidationReport = { totalTeamsParsed: number; validTeams: number; @@ -40,6 +50,7 @@ export type CsvValidationReport = { warningRows: number; unknownTracks: string[]; issues: CsvRowIssue[]; + rowContacts: CsvRowContact[]; globalWarnings?: string[]; }; @@ -416,6 +427,7 @@ export async function validateCsvBlob(blob: Blob): Promise<{ error: string | null; }> { const issues: CsvRowIssue[] = []; + const rowContacts: CsvRowContact[] = []; const unknownTrackSet = new Set(); const output: ParsedRecord[] = []; const seenTeamNumbers = new Map(); // teamNumber -> first rowIndex where seen @@ -496,6 +508,18 @@ export async function validateCsvBlob(blob: Blob): Promise<{ } } + rowContacts.push({ + teamNumber: Number.isFinite(parsedTeamNumber) + ? parsedTeamNumber + : undefined, + projectTitle, + contactEmails, + contactNames, + memberEmails, + memberNames, + memberColumnsFromTeamMember1, + }); + if ( invalidTracks.length > 0 || missingFields.length > 0 || @@ -608,6 +632,7 @@ export async function validateCsvBlob(blob: Blob): Promise<{ warningRows, unknownTracks: Array.from(unknownTrackSet).sort(), issues, + rowContacts, globalWarnings, }; diff --git a/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx b/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx new file mode 100644 index 00000000..845a6c6b --- /dev/null +++ b/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx @@ -0,0 +1,382 @@ +'use client'; + +import React, { useMemo, useState } from 'react'; +import ParsedRecord from '@typeDefs/parsedRecord'; +import { CsvRowIssue, CsvRowContact } from '@utils/csv-ingestion/csvAlgorithm'; + +type RowStatus = 'error' | 'warning' | 'valid'; +type SortKey = 'status' | 'teamNumber' | 'tableNumber' | 'name'; + +const STATUS_ORDER: Record = { + error: 0, + warning: 1, + valid: 2, +}; + +const STATUS_PILL: Record = { + error: 'bg-red-100 text-red-800 border-red-200', + warning: 'bg-yellow-100 text-yellow-800 border-yellow-200', + valid: 'bg-green-100 text-green-800 border-green-200', +}; + +const STATUS_ROW_BG: Record = { + error: 'bg-red-50', + warning: 'bg-yellow-50', + valid: '', +}; + +function buildMemberLines(contact: CsvRowContact): string[] { + const cols = contact.memberColumnsFromTeamMember1 ?? []; + const canon = (s: string) => + s + .trim() + .toLowerCase() + .replace(/[^a-z0-9]/g, ''); + + const findByPrefix = (prefix: string) => { + const p = canon(prefix); + for (const c of cols) { + if (canon(c.header ?? '').startsWith(p)) return c.value?.trim() ?? ''; + } + return ''; + }; + + const lines: string[] = []; + for (let n = 1; n <= 4; n++) { + const first = findByPrefix(`Team member ${n} first name`); + const last = findByPrefix(`Team member ${n} last name`); + const email = + findByPrefix(`Team member ${n} email`) || + findByPrefix(`Team member ${n} e-mail`); + const name = `${first} ${last}`.trim(); + if (!name && !email) continue; + lines.push(`${name || '(no name)'}${email ? ` — ${email}` : ''}`); + } + return lines; +} + +export default function ProcessedTeamsTable({ + records, + issues, + rowContacts, +}: { + records: ParsedRecord[]; + issues: CsvRowIssue[]; + rowContacts: CsvRowContact[]; +}) { + const [search, setSearch] = useState(''); + const [expandedKey, setExpandedKey] = useState(null); + const [sortKey, setSortKey] = useState('status'); + const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc'); + + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); + } else { + setSortKey(key); + setSortDir('asc'); + } + }; + + const issueMap = useMemo(() => { + const byNum = new Map(); + const byName = new Map(); + for (const issue of issues) { + if (issue.teamNumber != null && !isNaN(issue.teamNumber)) { + const existing = byNum.get(issue.teamNumber); + if (!existing || issue.severity === 'error') + byNum.set(issue.teamNumber, issue); + } + if (issue.projectTitle) { + const k = issue.projectTitle.toLowerCase(); + const existing = byName.get(k); + if (!existing || issue.severity === 'error') byName.set(k, issue); + } + } + return { byNum, byName }; + }, [issues]); + + const contactMap = useMemo(() => { + const byNum = new Map(); + const byName = new Map(); + for (const c of rowContacts) { + if (c.teamNumber != null && !isNaN(c.teamNumber)) + byNum.set(c.teamNumber, c); + if (c.projectTitle) byName.set(c.projectTitle.toLowerCase(), c); + } + return { byNum, byName }; + }, [rowContacts]); + + const getIssue = (r: ParsedRecord) => + issueMap.byNum.get(r.teamNumber) ?? + issueMap.byName.get(r.name.toLowerCase()); + + const getContact = (r: ParsedRecord) => + contactMap.byNum.get(r.teamNumber) ?? + contactMap.byName.get(r.name.toLowerCase()); + + const getStatus = (r: ParsedRecord): RowStatus => + getIssue(r)?.severity ?? 'valid'; + + const rowKey = (r: ParsedRecord) => `${r.teamNumber}-${r.name}`; + + const displayed = useMemo(() => { + const q = search.toLowerCase(); + const filtered = records.filter( + (r) => !q || JSON.stringify(r).toLowerCase().includes(q) + ); + + filtered.sort((a, b) => { + let cmp = 0; + if (sortKey === 'status') { + cmp = + STATUS_ORDER[ + issueMap.byNum.get(a.teamNumber)?.severity ?? + issueMap.byName.get(a.name.toLowerCase())?.severity ?? + 'valid' + ] - + STATUS_ORDER[ + issueMap.byNum.get(b.teamNumber)?.severity ?? + issueMap.byName.get(b.name.toLowerCase())?.severity ?? + 'valid' + ]; + } else if (sortKey === 'teamNumber') { + cmp = (a.teamNumber ?? 0) - (b.teamNumber ?? 0); + } else if (sortKey === 'tableNumber') { + cmp = (a.tableNumber ?? '').localeCompare( + b.tableNumber ?? '', + undefined, + { + numeric: true, + } + ); + } else if (sortKey === 'name') { + cmp = a.name.localeCompare(b.name); + } + return sortDir === 'asc' ? cmp : -cmp; + }); + + return filtered; + }, [records, search, issueMap, sortKey, sortDir]); + + const SortIcon = ({ col }: { col: SortKey }) => { + if (sortKey !== col) return ; + return {sortDir === 'asc' ? '↑' : '↓'}; + }; + + const thClass = (col: SortKey) => + `px-4 py-3 cursor-pointer select-none hover:text-gray-800 ${ + sortKey === col ? 'text-gray-800' : '' + }`; + + return ( +
+ + Processed Teams ({records.length} total) + + +
+ setSearch(e.target.value)} + className="mb-3 w-full max-w-sm border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-[#005271]" + /> + +
+ + + + + + + + + + + + {displayed.map((r) => { + const status = getStatus(r); + const issue = getIssue(r); + const contact = getContact(r); + const key = rowKey(r); + const isExpanded = expandedKey === key; + const isWait = r.tableNumber?.startsWith('WAIT'); + const memberLines = contact ? buildMemberLines(contact) : []; + + return ( + + setExpandedKey(isExpanded ? null : key)} + > + + + + + + + + {isExpanded && ( + + + + )} + + ); + })} + +
handleSort('status')} + > + Status + + handleSort('teamNumber')} + > + Team # + + handleSort('tableNumber')} + > + Table + + handleSort('name')} + > + Project Name + + Tracks
+ + {status === 'error' + ? 'Error' + : status === 'warning' + ? 'Warning' + : 'Valid'} + + + {isNaN(r.teamNumber) ? '—' : r.teamNumber} + + + {r.tableNumber || '—'} + + + {r.name} + +
+ {r.tracks.map((t) => ( + + {t} + + ))} +
+
+
+ {issue && ( +
+ {issue.missingFields?.length > 0 && ( + + Missing:{' '} + + {issue.missingFields.join(', ')} + + + )} + {issue.invalidTracks?.length > 0 && ( + + Invalid tracks:{' '} + + {issue.invalidTracks.join(', ')} + + + )} + {issue.duplicateTeamNumber !== undefined && ( + + Duplicate team #:{' '} + {issue.duplicateTeamNumber} + + )} + {issue.duplicateTracks?.length > 0 && ( + + Duplicate tracks:{' '} + + {issue.duplicateTracks.join(', ')} + + + )} + {issue.excludedTracks?.length > 0 && ( + + Excluded:{' '} + + {issue.excludedTracks.join(', ')} + + + )} + {issue.autoFixedTracks?.length > 0 && ( + + Auto-fixed:{' '} + {issue.autoFixedTracks + .map((f) => `${f.raw} → ${f.canonical}`) + .join(', ')} + + )} +
+ )} + + {contact && ( +
+ {contact.contactNames?.length > 0 && ( + + + Submitter: + {' '} + {contact.contactNames.join(', ')} + {contact.contactEmails?.length > 0 && + ` (${contact.contactEmails.join(', ')})`} + + )} + {memberLines.map((line, i) => ( + + + Member {i + 1}: + {' '} + {line} + + ))} +
+ )} + + {!issue && !contact && ( + + No details available. + + )} +
+
+
+ {displayed.length} of {records.length} teams shown +
+
+
+
+ ); +} diff --git a/app/(pages)/admin/csv/page.tsx b/app/(pages)/admin/csv/page.tsx index dcd91d33..c2c12349 100644 --- a/app/(pages)/admin/csv/page.tsx +++ b/app/(pages)/admin/csv/page.tsx @@ -8,6 +8,7 @@ import { CsvValidationReport, CsvRowIssue, } from '@utils/csv-ingestion/csvAlgorithm'; +import ProcessedTeamsTable from '../_components/CsvIngestion/ProcessedTeamsTable'; type ValidationResponse = { ok: boolean; @@ -194,7 +195,7 @@ export default function CsvIngestion() { }; return ( -
+
{teamsAlreadyPopulated?.populated ? (
Teams database already populated
@@ -347,6 +348,14 @@ export default function CsvIngestion() {
)} + {validation.body && validation.body.length > 0 && ( + + )} +
Raw report (JSON) From 1d8ce93d36c80710aab0acfcbf98cf2e35cc141d Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 01:00:50 -0700 Subject: [PATCH 11/14] Fix: csv algo error reporter missing rowContacts --- app/(api)/_utils/csv-ingestion/csvAlgorithm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts b/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts index 53d794b0..a1f6c3f1 100644 --- a/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts +++ b/app/(api)/_utils/csv-ingestion/csvAlgorithm.ts @@ -653,6 +653,7 @@ export async function validateCsvBlob(blob: Blob): Promise<{ warningRows: 0, unknownTracks: [], issues: [], + rowContacts: [], globalWarnings: [], }; return { From ee7fe816578ef2d6ad9b05f0057fc7f999f6b2a3 Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 03:23:03 -0700 Subject: [PATCH 12/14] Fix: Address copilot comments --- app/(api)/_datalib/panels/createPanels.ts | 3 ++- .../admin/_components/CsvIngestion/ProcessedTeamsTable.tsx | 4 +++- migrations/20260507000000-fix-table-number-type.mjs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/(api)/_datalib/panels/createPanels.ts b/app/(api)/_datalib/panels/createPanels.ts index 4a4819ae..851dd495 100644 --- a/app/(api)/_datalib/panels/createPanels.ts +++ b/app/(api)/_datalib/panels/createPanels.ts @@ -29,9 +29,10 @@ export const CreatePanel = async (trackName: string) => { ); } + const domain = panelTracks[trackName].domain; const result = await db.collection('panels').insertOne({ track: trackName, - domain: panelTracks[trackName].domain, + ...(domain !== undefined && { domain }), user_ids: [], }); diff --git a/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx b/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx index 845a6c6b..93d87e1c 100644 --- a/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx +++ b/app/(pages)/admin/_components/CsvIngestion/ProcessedTeamsTable.tsx @@ -141,7 +141,9 @@ export default function ProcessedTeamsTable({ 'valid' ]; } else if (sortKey === 'teamNumber') { - cmp = (a.teamNumber ?? 0) - (b.teamNumber ?? 0); + const aNum = Number.isFinite(a.teamNumber) ? a.teamNumber : 0; + const bNum = Number.isFinite(b.teamNumber) ? b.teamNumber : 0; + cmp = aNum - bNum; } else if (sortKey === 'tableNumber') { cmp = (a.tableNumber ?? '').localeCompare( b.tableNumber ?? '', diff --git a/migrations/20260507000000-fix-table-number-type.mjs b/migrations/20260507000000-fix-table-number-type.mjs index 24f20516..04c8e15c 100644 --- a/migrations/20260507000000-fix-table-number-type.mjs +++ b/migrations/20260507000000-fix-table-number-type.mjs @@ -24,7 +24,7 @@ const teamsSchema = { }, tableNumber: { bsonType: 'string', - description: 'tableNumber must be an string', + description: 'tableNumber must be a string', }, name: { bsonType: 'string', From 61322e0fc24eb594c52d4c74c85e967517af4bb5 Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 03:30:14 -0700 Subject: [PATCH 13/14] Fix panel to judge creation --- app/(api)/_actions/logic/assignJudgesToPanels.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(api)/_actions/logic/assignJudgesToPanels.ts b/app/(api)/_actions/logic/assignJudgesToPanels.ts index 05c6aa1d..3f3310f8 100644 --- a/app/(api)/_actions/logic/assignJudgesToPanels.ts +++ b/app/(api)/_actions/logic/assignJudgesToPanels.ts @@ -3,14 +3,14 @@ import { GetManyPanels } from '@datalib/panels/getPanels'; import { GetManyUsers } from '@datalib/users/getUser'; import judgeToPanelAlgorithm from '@utils/matching/judgeToPanelAlgorithm'; -import { judgeVisibleTracks } from '@data/tracks'; +import { panelTracks } from '@data/tracks'; import { CreateManyPanels } from '@datalib/panels/createPanels'; import { UpdatePanel } from '@datalib/panels/updatePanel'; import Panel from '@typeDefs/panel'; import { DeleteManyPanels } from '@datalib/panels/deletePanel'; async function initializeEmptyPanels() { - const panelData: Panel[] = Object.values(judgeVisibleTracks).map((track) => ({ + const panelData: Panel[] = Object.values(panelTracks).map((track) => ({ track: track.name, domain: track.domain ?? '', user_ids: [], From aa57483ccb7d0a1dc01fcd4a2559181ab2ae708b Mon Sep 17 00:00:00 2001 From: reehals Date: Fri, 8 May 2026 03:36:51 -0700 Subject: [PATCH 14/14] fix: panel judge assignment now prioritizes domain specialists over unmatched judges --- app/(api)/_utils/matching/judgeToPanelAlgorithm.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/(api)/_utils/matching/judgeToPanelAlgorithm.ts b/app/(api)/_utils/matching/judgeToPanelAlgorithm.ts index 2c4b5cc5..1ec8f828 100644 --- a/app/(api)/_utils/matching/judgeToPanelAlgorithm.ts +++ b/app/(api)/_utils/matching/judgeToPanelAlgorithm.ts @@ -9,11 +9,12 @@ export default async function judgeToPanelAlgorithm( for (const panel of panels) { if (judges.length < panelSize) return null; - judges = judges.sort( - (a, b) => - (a.specialties?.indexOf(panel.domain ?? '') ?? 0) - - (b.specialties?.indexOf(panel.domain ?? '') ?? 0) - ); + judges = judges.sort((a, b) => { + const domain = panel.domain ?? ''; + const aIdx = a.specialties?.indexOf(domain) ?? -1; + const bIdx = b.specialties?.indexOf(domain) ?? -1; + return (aIdx === -1 ? Infinity : aIdx) - (bIdx === -1 ? Infinity : bIdx); + }); panel.user_ids = judges .splice(0, panelSize)