Fix various UI performance or UI minor correctness issues - #415
Open
chondl wants to merge 6 commits into
Open
Conversation
Floor the EPA-percentile Gaussian variance so it is always > 0. When every team at an event shares the same pre-event start EPA (true for all events early in a season, before ratings diverge), epaSd is 0, so the variance (epaSd^2 * 5 / N) is 0 -- or NaN when floating-point error makes the variance argument to Math.sqrt slightly negative. gaussian() throws on variance <= 0, which rejected the un-awaited strengthOfSchedule() promise in the worker and left the SOS table blank; the NaN path instead rendered NaN in the EPA/Composite columns. Both are the same degeneracy. '|| 1e-9' treats 0 and NaN alike, yielding a neutral 0.5 EPA percentile, and leaves every real positive variance untouched.
Read the team EPA from epa.breakdown.total_points, the field the SOS tab and the simulation worker already use. simulation.tsx read epa.total_points.mean, which matches the stale APITeamEvent type but not the runtime shape: the backend serves epa.total_points as a plain number, so .mean is undefined and every row rendered EPA 0.
CockroachDB orders NULLs first under ORDER BY ... DESC, so a match whose clean score is null on both alliances (a fully-DQed / placeholder match, e.g. 2026txmca_sf6m1) sorted to the top of every noteworthy list -- ranking a 0/null result as the highest clean score. greatest()/sum() over the no_foul columns yields null when both alliances lack a clean result. Add .nullslast() to every noteworthy order_by so these matches fall to the bottom (out of the top 30) and real high-scoring matches rank first, while legitimate single-DQ matches keep ranking by their scoring alliance.
On team pages multiple components concurrently request the same event/blob resources. Each caller awaits getWithExpiry(), all miss IndexedDB (nothing is written until a fetch completes), and all issue their own network fetch, so every blob/API resource was downloaded twice per page load. Add a module-level in-flight promise map keyed by storageKey: concurrent callers for the same key now share one fetch, and the entry is cleared once it settles.
|
@chondl is attempting to deploy a commit to the avgupta456's projects Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes six distinct performance or minor correctness UI issues. Verified with a real browser against a local rig (CockroachDB + fake-gcs + full 2026 season, 3724 teams / 215 events / 18372 matches).
1. Match page: infinite media-request loop
Symptom. On every
/match/{key}page the browser fired an endless stream of requests to TBA's/team/frc{N}/media/{year}endpoint — measured ~11,747 requests in 5 seconds on a single open page. Every production visitor has been silently doing this to TBA.Root cause.
frontend/src/pagesContent/match/[match_id]/imageRow.tsx:13-14recomputed theteamsarray with.concat()on every render, producing a new array reference each time. TheuseEffect(..., [teams, year])at line 19 then calledgetMediaUrlsandsetMedia, whose state update re-rendered the component, produced a freshteamsreference, and re-triggered the effect — an unbounded loop.Fix. Wrap
teamsinuseMemo(..., [data])so its reference is stable across renders and the effect runs only when the underlying match data changes.2. Match page: Previous/Next buttons don't update the page
Symptom. Clicking the prev/next arrows changed the URL and document title but left the displayed match unchanged.
Root cause.
frontend/src/pages/match/[match_id].tsx:26guarded the fetch withif (!match_id || data) return;. Because Next.js reuses the same component instance across/match/*route changes,datawas already truthy from the previous match, so the guard short-circuited and never fetched the new match.Fix. Compare the loaded key against the route —
if (!match_id || data?.match?.key == match_id) return;— matching the existing pattern inpages/event/[event_id].tsx:25. The effect already depends on[match_id, data], so it now refetches when the route changes and skips redundant fetches once the correct match is loaded.3. Strength of Schedule renders blank (or NaN) when pre-event EPAs are identical
Symptom. On the Strength of Schedule tab the RP/Rank/EPA/Composite score columns fail to populate. Two manifestations: score columns entirely blank, or
NaNin EPA Score and Composite Score.Root cause.
strengthOfSchedule()(infrontend/src/pagesContent/event/[event_id]/worker.ts) runs a "Before Event" pass that computesepaSd, the standard deviation of every team's pre-event start EPA, and builds a Gaussian to score EPA-based schedule strength:Early in a season — before ratings diverge — every team at an event shares the same cold-start EPA, so
epaSdis0, the variance is0, andgaussianthrowsError('Variance must be > 0'). Because the worker's message handler callsstrengthOfSchedule(...)withoutawait/catch, the throw becomes a silent unhandled promise rejection: no message is posted and the table stays blank. A floating-point variant produces theNaNsymptom — with all EPAs equal,Math.sqrt(sum(x^2)/n - avg^2)occasionally takes the root of a tiny negative rounding residue, soepaSdisNaN,gaussiandoes not throw (NaN <= 0is false), and every EPA percentile isNaN. Both are the same degeneracy: EPA carries no schedule signal when all ratings are identical.Fix. Floor the variance with
|| 1e-9, which treats both0andNaNas falsy and substitutes a negligible positive variance; withdeltaEPA == 0the CDF at the mean is0.5, so every team gets a neutral0.5EPA percentile — the correct answer when EPA is non-informative. Any real positive variance passes through untouched. Matches the|| 0fallback idiom already used throughout this worker.4. Simulation tab shows EPA 0 for every team
Symptom. On the Simulation tab the EPA column reads
0for all teams (the predicted ranks, RP means, and percentiles are correct).Root cause.
frontend/src/pagesContent/event/[event_id]/simulation.tsxreads the team EPA asteamEvent.epa.total_points.mean. That matches theAPITeamEventTypeScript type, but not the runtime shape: the backend servesepa.total_pointsas a plain number, so.meanisundefinedand?? 0renders0.Fix. Read
epa.breakdown.total_points— the same field the SOS tab and the simulation worker already use — which is present at runtime.5. Noteworthy matches rank a null/placeholder match incorrectly
Symptom.
/v3/site/noteworthy_matches/2026returned a 0/null-score, fully-DQed placeholder match (2026txmca_sf6m1) at the top of "Highest Clean Scores" (and the other lists), ahead of real high scores.Root cause.
backend/src/db/functions/noteworthy_matches.py— the listsorder_by(desc(...))without specifying null placement. CockroachDB orders NULLs first underDESC. For 2016+ the sort uses theno_foulcolumns; a match with no clean result on either alliance yieldsgreatest(...) = NULL(andsum = NULLfor combined), so it sorts to the top. (The value only surfaces on a NULLS-FIRST-defaulting DB, which is why it appears on staging.)Fix. Add
.nullslast()to every noteworthyorder_by. Null-result matches fall past the top-30 cutoff; real high-scoring matches rank first; legitimate single-alliance-DQ matches still rank by their scoring alliance.6. Every blob/API resource fetched twice on team pages
Symptom. On
/team/*, each event/team blob was downloaded exactly twice per page load (observed on the production build, so not a StrictMode artifact).Root cause.
frontend/src/api/storage.tsxquery()had no in-flight dedup. Several components request the same resource concurrently; each awaitsgetWithExpiry(), all miss IndexedDB (nothing is written until a fetch resolves), and all issue their own fetch.Fix. Add a module-level in-flight promise map keyed by
storageKey. Concurrent callers for the same key share one fetch; the entry clears once it settles. The fetch body is extracted intofetchAndStoreunchanged.Verification
0(was ~11.7k/5s), and prev/next navigation updates the displayed match (Qual 43 ↔ Qual 44) with the arrows re-pointing correctly.epaSd == 0event — before: Gaussian throws, SOS blank; after: all columns populate (EPA Score0.5).epaSd == NaN(float rounding) event — before: EPA/CompositeNaN; after: populate (EPA Score0.5). Event with a genuine non-zero EPA spread: EPA percentile0.387before and after — unchanged (the fix is inert on the non-degenerate path).0before, real per-team EPA after; predicted-rank distributions unchanged and still vary run-to-run.2026txmca_sf6m1/_sf9m1ranked first or second above the real 964-point match; with the patched query,2026dal_f1m1(964) is first across all six lists and the placeholder is gone.