diff --git a/app/src/components/tournament/TournamentAnswers.jsx b/app/src/components/tournament/TournamentAnswers.jsx index c85d5d5f..f2ecaffc 100644 --- a/app/src/components/tournament/TournamentAnswers.jsx +++ b/app/src/components/tournament/TournamentAnswers.jsx @@ -13,7 +13,7 @@ import { } from "@mantine/core"; import { IconAlertCircle, IconTarget } from "@tabler/icons-react"; import { TOURNAMENT_CONFIG, getMaskedForecastDate } from "../../config"; -import { getLeaderboard } from "../../utils/tournamentAPI"; +import { getParticipant } from "../../utils/tournamentAPI"; import ForecastleChartCanvas from "../forecastle/ForecastleChartCanvas"; const addWeeksToDate = (dateString, weeks) => { @@ -93,6 +93,8 @@ const getSubmissionByChallenge = (submissions, challenge) => { const TournamentAnswers = ({ tournamentConfig = TOURNAMENT_CONFIG, participantId, + completedCount: completedCountProp = null, + isActive = false, }) => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -107,14 +109,33 @@ const TournamentAnswers = ({ return; } + if (!isActive && completedCountProp !== null) { + setLoading(false); + return; + } + + if ( + Number.isFinite(completedCountProp) && + completedCountProp < tournamentConfig.numChallenges + ) { + setSubmissionsByChallenge({}); + setChallengeData({}); + setParticipantFound(true); + setError(null); + setLoading(false); + return; + } + setLoading(true); setError(null); try { - const leaderboard = await getLeaderboard(tournamentConfig); - const participantEntry = - leaderboard.find((entry) => entry.participantId === participantId) || - null; + const participantData = await getParticipant( + participantId, + tournamentConfig, + ); + const participantEntry = participantData?.participant || null; + const participantSubmissions = participantData?.submissions || []; if (!participantEntry) { setParticipantFound(false); @@ -128,7 +149,7 @@ const TournamentAnswers = ({ const nextSubmissionsByChallenge = {}; tournamentConfig.challenges.forEach((challenge) => { const submission = getSubmissionByChallenge( - participantEntry.submissions || [], + participantSubmissions, challenge, ); const forecasts = getSubmissionForecasts(submission); @@ -210,7 +231,7 @@ const TournamentAnswers = ({ }; loadAnswers(); - }, [participantId, tournamentConfig]); + }, [participantId, tournamentConfig, completedCountProp, isActive]); const completedCount = useMemo( () => Object.keys(submissionsByChallenge).length, diff --git a/app/src/components/tournament/TournamentDashboard.jsx b/app/src/components/tournament/TournamentDashboard.jsx index b8bb9ba4..18d7e322 100644 --- a/app/src/components/tournament/TournamentDashboard.jsx +++ b/app/src/components/tournament/TournamentDashboard.jsx @@ -16,6 +16,7 @@ const TournamentDashboard = ({ tournamentConfig = TOURNAMENT_CONFIG }) => { const [participantName, setParticipantName] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState("challenges"); + const [completedCount, setCompletedCount] = useState(null); // Load participant data on mount useEffect(() => { @@ -32,6 +33,7 @@ const TournamentDashboard = ({ tournamentConfig = TOURNAMENT_CONFIG }) => { const handleRegistration = (id, name) => { setParticipantId(id); setParticipantName(name); + setCompletedCount(0); }; // Navigate to leaderboard @@ -87,6 +89,7 @@ const TournamentDashboard = ({ tournamentConfig = TOURNAMENT_CONFIG }) => { participantId={participantId} participantName={participantName} onAllCompleted={goToLeaderboard} + onProgressChange={setCompletedCount} /> @@ -101,6 +104,8 @@ const TournamentDashboard = ({ tournamentConfig = TOURNAMENT_CONFIG }) => { diff --git a/app/src/components/tournament/TournamentGame.jsx b/app/src/components/tournament/TournamentGame.jsx index ccdfc6f8..85c6162d 100644 --- a/app/src/components/tournament/TournamentGame.jsx +++ b/app/src/components/tournament/TournamentGame.jsx @@ -109,6 +109,7 @@ const TournamentGame = ({ participantId, participantName, onAllCompleted, + onProgressChange, }) => { const [currentChallengeIndex, setCurrentChallengeIndex] = useState(0); const [completedChallenges, setCompletedChallenges] = useState(new Set()); @@ -236,6 +237,10 @@ const TournamentGame = ({ tournamentConfig, ]); + useEffect(() => { + onProgressChange?.(completedChallenges.size); + }, [completedChallenges, onProgressChange]); + useEffect(() => { if (inputMode === "scoring") { return; diff --git a/app/src/config/tournament.js b/app/src/config/tournament.js index c61d68ad..6faaccda 100644 --- a/app/src/config/tournament.js +++ b/app/src/config/tournament.js @@ -114,7 +114,7 @@ export const TOURNAMENT_REGISTRY = [ { id: "ch-3", enabled: true, - number: 2, + number: 3, title: "California Influenza Forecast", description: "Predict California flu hospitalizations for 1, 2, and 3 weeks ahead", @@ -131,24 +131,24 @@ export const TOURNAMENT_REGISTRY = [ { id: "ch-4", enabled: true, - number: 3, - title: "Massachusetts RSV Forecast", + number: 4, + title: "North Carolina COVID-19 Forecast", description: - "Predict Massachusetts RSV hospitalizations for 1, 2, and 3 weeks ahead", - dataset: "rsv", - datasetKey: "rsv", - dataPath: "rsvforecasthub", - fileSuffix: "rsv.json", - location: "MA", - displayName: "Massachusetts", - target: "wk inc rsv hosp", + "Predict North Carolina COVID hospitalizations for 1, 2, and 3 weeks ahead", + dataset: "covid", + datasetKey: "covid19", + dataPath: "covid19forecasthub", + fileSuffix: "covid19.json", + location: "NC", + displayName: "North Carolina", + target: "wk inc covid hosp", horizons: [1, 2, 3], - forecastDate: "2025-12-20", + forecastDate: "2025-09-13", }, { id: "ch-5", enabled: true, - number: 3, + number: 5, title: "Georgia COVID-19 Forecast", description: "Predict Georgia COVID hospitalizations for 1, 2, and 3 weeks ahead",