Compare commits

...

2 commits

Author SHA1 Message Date
Chris Parsons
97a92aba7d Debug sync-and-simulate cron job: surface curl output and log auth failures
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m57s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m29s
🚀 Deploy / 🧪 Test (push) Successful in 2m52s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m28s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🐳 Build (push) Successful in 1m16s
🚀 Deploy / 🚀 Deploy (push) Successful in 11s
Remove -s (silent) from curl so CI logs show the HTTP response on failure.
Add logger.error in requireCronSecret so 401s from bad/missing secrets appear in Sentry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:50:30 +00:00
Chris Parsons
ea85a21c83 Fix IndyCar standings: correct ESPN stat key, update title, restore finalized badge
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m0s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m31s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🧪 Test (push) Successful in 3m1s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m33s
🚀 Deploy / 🐳 Build (push) Successful in 1m20s
🚀 Deploy / 🚀 Deploy (push) Successful in 11s
- IndyCar points were showing as 0 because ESPN uses 'championshipPts' not 'points'
  as the stat name; add it as primary key in the fallback chain
- Rename season_standings card title from sportSeasonName to "${name} Standings"
- Remove non-finalized subtext from SeasonStandings CardDescription
- Restore finalized-season badge (season complete / top-8 points locked) which
  was dropped when removing the subtext; derive from sportsSeason.status at the
  component level instead of the loader so the dead seasonIsFinalized field is
  also removed from the loader return

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 09:17:23 -07:00
8 changed files with 17 additions and 19 deletions

View file

@ -9,6 +9,6 @@ jobs:
steps: steps:
- name: Sync standings and run simulations where changed - name: Sync standings and run simulations where changed
run: | run: |
curl -sf -X POST https://brackt.com/admin/jobs/sync-and-simulate \ curl -f -X POST https://brackt.com/admin/jobs/sync-and-simulate \
-H "X-Cron-Secret: ${{ secrets.CRON_SECRET }}" \ -H "X-Cron-Secret: ${{ secrets.CRON_SECRET }}" \
-H "Content-Type: application/json" -H "Content-Type: application/json"

View file

@ -99,18 +99,14 @@ export function SeasonStandings({
<Flag className="inline mr-2 h-5 w-5" /> <Flag className="inline mr-2 h-5 w-5" />
{title} {title}
</CardTitle> </CardTitle>
{isFinalized && (
<CardDescription> <CardDescription>
{isFinalized ? (
<span className="text-emerald-400 font-semibold"> <span className="text-emerald-400 font-semibold">
<CheckCircle2 className="inline h-4 w-4 mr-1" /> <CheckCircle2 className="inline h-4 w-4 mr-1" />
Season complete fantasy points assigned to top 8 finishers Season complete fantasy points assigned to top 8 finishers
</span> </span>
) : (
<>
Current championship standings. Positions calculated from points (highest = 1st).
</>
)}
</CardDescription> </CardDescription>
)}
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{standings.length === 0 ? ( {standings.length === 0 ? (

View file

@ -173,7 +173,7 @@ export function SportSeasonDisplay({
userParticipantIds={userParticipantIds} userParticipantIds={userParticipantIds}
showOwnership={showOwnership} showOwnership={showOwnership}
isFinalized={seasonIsFinalized} isFinalized={seasonIsFinalized}
title={sportSeasonName} title={`${sportSeasonName} Standings`}
/> />
); );

View file

@ -1,7 +1,10 @@
import { logger } from "~/lib/logger";
export function requireCronSecret(request: Request): void { export function requireCronSecret(request: Request): void {
const secret = process.env.CRON_SECRET; const secret = process.env.CRON_SECRET;
if (!secret) throw new Response("CRON_SECRET not configured", { status: 500 }); if (!secret) throw new Response("CRON_SECRET not configured", { status: 500 });
if (request.headers.get("x-cron-secret") !== secret) { if (request.headers.get("x-cron-secret") !== secret) {
logger.error("[cron-auth] Invalid or missing X-Cron-Secret header");
throw new Response("Unauthorized", { status: 401 }); throw new Response("Unauthorized", { status: 401 });
} }
} }

View file

@ -333,9 +333,6 @@ export async function loader(args: Route.LoaderArgs) {
]) ])
: [[], []]; : [[], []];
// Derive seasonIsFinalized from status
const seasonIsFinalized = sportsSeason.status === "completed";
// Build participantId → expectedValue map for display // Build participantId → expectedValue map for display
const participantEvs = Object.fromEntries( const participantEvs = Object.fromEntries(
regularSeasonEvs.map((ev) => [ev.participantId, ev.expectedValue]) regularSeasonEvs.map((ev) => [ev.participantId, ev.expectedValue])
@ -357,7 +354,6 @@ export async function loader(args: Route.LoaderArgs) {
userParticipantIds, userParticipantIds,
upcomingEvents, upcomingEvents,
recentEvents, recentEvents,
seasonIsFinalized,
regularSeasonStandings, regularSeasonStandings,
participantEvs, participantEvs,
groupStandings, groupStandings,

View file

@ -38,7 +38,6 @@ export default function SportSeasonDetail({
userParticipantIds, userParticipantIds,
upcomingEvents, upcomingEvents,
recentEvents, recentEvents,
seasonIsFinalized,
regularSeasonStandings, regularSeasonStandings,
groupStandings, groupStandings,
@ -109,7 +108,7 @@ export default function SportSeasonDetail({
participantPoints={participantPoints} participantPoints={participantPoints}
partialScoreParticipantIds={partialScoreParticipantIds} partialScoreParticipantIds={partialScoreParticipantIds}
seasonStandings={seasonStandings} seasonStandings={seasonStandings}
seasonIsFinalized={seasonIsFinalized} seasonIsFinalized={sportsSeason.status === "completed"}
qpStandings={qpStandings} qpStandings={qpStandings}
qpIsFinalized={sportsSeason.qualifyingPointsFinalized || false} qpIsFinalized={sportsSeason.qualifyingPointsFinalized || false}
totalMajors={sportsSeason.totalMajors} totalMajors={sportsSeason.totalMajors}

View file

@ -42,7 +42,7 @@ const SAMPLE_ESPN_INDYCAR = {
athlete: { id: "3097", displayName: "Scott Dixon" }, athlete: { id: "3097", displayName: "Scott Dixon" },
stats: [ stats: [
{ name: "rank", value: 1 }, { name: "rank", value: 1 },
{ name: "points", value: 437 }, { name: "championshipPts", value: 437 },
{ name: "wins", value: 3 }, { name: "wins", value: 3 },
{ name: "starts", value: 10 }, { name: "starts", value: 10 },
], ],
@ -51,7 +51,7 @@ const SAMPLE_ESPN_INDYCAR = {
athlete: { id: "3098", displayName: "Alex Palou" }, athlete: { id: "3098", displayName: "Alex Palou" },
stats: [ stats: [
{ name: "rank", value: 2 }, { name: "rank", value: 2 },
{ name: "points", value: 398 }, { name: "championshipPts", value: 398 },
{ name: "wins", value: 2 }, { name: "wins", value: 2 },
{ name: "starts", value: 10 }, { name: "starts", value: 10 },
], ],

View file

@ -114,7 +114,11 @@ export class IndyCarStandingsAdapter implements StandingsSyncAdapter {
return entries.map((entry, idx): FetchedStandingsRecord => { return entries.map((entry, idx): FetchedStandingsRecord => {
const sm = statsMap(entry.stats); const sm = statsMap(entry.stats);
const points = sm.get("points")?.value ?? sm.get("pts")?.value ?? 0; const points =
sm.get("championshipPts")?.value ??
sm.get("points")?.value ??
sm.get("pts")?.value ??
0;
const wins = sm.get("wins")?.value ?? 0; const wins = sm.get("wins")?.value ?? 0;
const starts = sm.get("starts")?.value ?? sm.get("racesStarted")?.value ?? 0; const starts = sm.get("starts")?.value ?? sm.get("racesStarted")?.value ?? 0;
const rank = sm.get("rank")?.value ?? sm.get("position")?.value ?? idx + 1; const rank = sm.get("rank")?.value ?? sm.get("position")?.value ?? idx + 1;