Skip to content

fix: derive race card location from race name when manifest lacks it#232

Open
rootulp wants to merge 1 commit into
mainfrom
fix/219-race-location-fallback
Open

fix: derive race card location from race name when manifest lacks it#232
rootulp wants to merge 1 commit into
mainfrom
fix/219-race-location-fallback

Conversation

@rootulp

@rootulp rootulp commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Closes #219

Summary

Only 690 of 1447 races in data/races.json have a location, so most cards on /races rendered without a location line and the grid looked inconsistent. This adds a display-time fallback instead of mass-scraping or hand-editing the manifest:

  • New pure helper app/src/lib/raceLocation.ts:
    • deriveLocationFromRaceName(name) strips the leading year and the IRONMAN / IRONMAN 70.3 prefix ("2026 IRONMAN 70.3 Subic Bay Philippines""Subic Bay Philippines", "2013 IRONMAN Austria""Austria").
    • Conservative: returns null for names that don't match the pattern (5150 Mont-Tremblant, Victoria Olympic, Timberman Triathlon, June IRONMAN 70.3 Eagleman) and for championship designations that aren't locations (World Championship - Men), so the card renders without a location rather than garbage.
    • getRaceLocation(race) applies precedence: the real manifest location always wins; the derived value is only a fallback.
  • Wired into the race cards on /races (app/src/app/races/race-list.tsx, including the country-flag lookup) and the /race/[slug] header (which also no longer renders a dangling · when there's no location).

With this fallback, 741 of the 757 location-less races now display a derived location; the remaining 16 (non-IRONMAN names and championship designations) intentionally render without one.

Scraper backfill was skipped: manifest locations come from scripts/race-registry.json entries, and capturing them for future scrapes would require fetching per-event metadata — not a trivial change.

Test notes

  • Red/green TDD: app/src/lib/__tests__/race-location.test.ts (13 tests) written first and watched fail, then the helper implemented. Covers prefix variants, year placement, non-matching names, empty remainders, championship names, and the manifest-precedence rule.
  • cd app && npx vitest run: 10 files, 98 tests passed.
  • cd app && npm run lint: clean.
  • npx tsc --noEmit: no errors in changed files (pre-existing errors in athlete-shards.test.ts only).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved race location data handling with automatic fallback mechanism that derives location information from race names when explicit data is not available.

Only 690 of 1447 races in data/races.json have a location, so most
cards on /races rendered without a location line and the grid looked
inconsistent. Add a conservative display-time fallback that derives a
location from the race name by stripping the leading year and the
IRONMAN / IRONMAN 70.3 prefix (e.g. "2026 IRONMAN 70.3 Subic Bay
Philippines" -> "Subic Bay Philippines"). The real manifest location
always takes precedence; names that don't match the pattern (5150,
Timberman, championship designations, etc.) fall back to no location
rather than garbage. Applied on the /races cards and the /race/[slug]
header.

Closes #219

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rootulp rootulp self-assigned this Jun 12, 2026
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tritimes Ready Ready Preview, Comment Jun 12, 2026 3:28am

@rootulp rootulp enabled auto-merge (squash) June 12, 2026 02:54
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fb7443a-4779-471f-8a0d-60d54ec7a642

📥 Commits

Reviewing files that changed from the base of the PR and between c55c53f and e54c3c1.

📒 Files selected for processing (4)
  • app/src/app/race/[slug]/page.tsx
  • app/src/app/races/race-list.tsx
  • app/src/lib/__tests__/race-location.test.ts
  • app/src/lib/raceLocation.ts

📝 Walkthrough

Walkthrough

This PR adds location derivation for races missing manifest data. A new utility module extracts place names from IRONMAN-format race names via regex, with manifest locations taking precedence. The race page header and race list cards now use this fallback to ensure consistent location display across all races.

Changes

Location Fallback for Race Cards

Layer / File(s) Summary
Location derivation functions
app/src/lib/raceLocation.ts
deriveLocationFromRaceName() parses IRONMAN-format names to extract the place portion after year and prefix; getRaceLocation() prefers manifest location and falls back to derived location, returning null when neither is available.
Location derivation test coverage
app/src/lib/__tests__/race-location.test.ts
Comprehensive test suites for both functions validating multi-word extraction, malformed input rejection, championship pattern exclusion, manifest precedence, and whitespace handling.
Race page header integration
app/src/app/race/[slug]/page.tsx
Page imports getRaceLocation() and updates the header metadata line to display race.date followed by the derived or manifest location (with dot separator) only when available.
Race list card integration
app/src/app/races/race-list.tsx
Race card imports getRaceLocation(), derives location for flag country code calculation, and renders the derived location in the flag text area instead of manifest-only location.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A race without a place? No more!
The names now whisper what they store—
Extract the magic from the text,
Hamburg, Boston, what comes next?
Cards now dance in harmony's grace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a fallback to derive race location from the race name when the manifest lacks it.
Linked Issues check ✅ Passed The PR fully addresses issue #219 by implementing a display-time fallback to derive race locations from race names, resulting in 741 of 757 previously missing locations now displaying.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the location fallback feature: new helper functions, integration into race cards and headers, and comprehensive tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/219-race-location-fallback

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a display-time fallback so race cards show a location for IRONMAN races whose manifest entry lacks one, covering 741 of 757 previously blank cards. A new pure helper module (raceLocation.ts) derives a location by stripping the leading year and IRONMAN [70.3] prefix from the race name, with conservative null returns for non-matching names and championship designations.

  • deriveLocationFromRaceName uses a module-level compiled regex and guards against back-tracking ambiguity ("70.3" sentinel) and championship remainder strings; getRaceLocation wraps it with manifest-first precedence.
  • race-list.tsx now gates the getCountryFlag call on a truthy location, which also fixes a latent crash path if race.location were ever undefined at runtime.
  • race/[slug]/page.tsx fixes a dangling · separator that appeared when race.location was empty.

Confidence Score: 5/5

Safe to merge — changes are additive and display-only, with no effect on data persistence or routing.

All four changed files are narrow and well-tested. The regex has dedicated tests for every null-return branch, including the "70.3" back-track edge case. The UI integration correctly guards the flag lookup and the separator character. No existing behavior is removed; the manifest location always takes precedence.

No files require special attention.

Important Files Changed

Filename Overview
app/src/lib/raceLocation.ts New pure helper — module-level compiled regex, correct championship guard, and defensive optional-chain on location even though RaceInfo.location is typed as string; all logic is well-covered by tests.
app/src/lib/tests/race-location.test.ts 13 tests covering positive derivation, all null-return branches (no year, non-IRONMAN prefix, extra words, empty remainder, "70.3" back-track sentinel, championship guard), whitespace-only manifest location, and manifest-precedence rule.
app/src/app/races/race-list.tsx Flag lookup now guarded with location ? … : "" before calling getCountryFlag, preventing a crash that would have occurred if race.location were ever undefined at runtime; {location} (null) renders as nothing in React.
app/src/app/race/[slug]/page.tsx Fixes dangling · when location is absent by wrapping the separator+location in a conditional fragment; minimal, correct change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[race object] --> B{race.location trimmed non-empty?}
    B -- yes --> C[return manifest location]
    B -- no --> D[deriveLocationFromRaceName]
    D --> E{matches year + IRONMAN pattern?}
    E -- no --> F[return null]
    E -- yes --> G[extract remainder]
    G --> H{remainder empty or equals 70.3?}
    H -- yes --> F
    H -- no --> I{contains 'championship'?}
    I -- yes --> F
    I -- no --> J[return remainder as location]
    C --> K[render in UI with flag lookup]
    J --> K
    F --> L[render nothing / no flag]
Loading

Reviews (1): Last reviewed commit: "fix: derive race card location from race..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

data: only 690 of 1447 races have a location — race cards inconsistently show the location line

1 participant