brackt/app/utils/sports-data-sync.server.ts

584 lines
19 KiB
TypeScript
Raw Normal View History

import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and } from "drizzle-orm";
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
import { z } from "zod";
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ─── Validation schema ────────────────────────────────────────────────────────
// Used both for runtime validation of imported files and as the canonical shape
// for the exported JSON (fixes: zod validation, calculatedAt, participantResults)
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const ImportDataSchema = z.object({
version: z.string(),
exportedAt: z.string(),
sports: z.array(
z.object({
name: z.string(),
type: z.string(),
slug: z.string(),
description: z.string().nullable(),
})
),
sportsSeasons: z.array(
z.object({
sportSlug: z.string(),
name: z.string(),
year: z.number(),
startDate: z.string().nullable(),
endDate: z.string().nullable(),
status: z.string(),
scoringType: z.string(),
})
),
participants: z.array(
z.object({
sportSlug: z.string(),
seasonName: z.string(),
seasonYear: z.number(),
name: z.string(),
shortName: z.string().nullable(),
externalId: z.string().nullable(),
expectedValue: z.union([z.number(), z.string()]),
})
),
// Optional for backward compatibility with v1.0 exports
participantExpectedValues: z
.array(
z.object({
sportSlug: z.string(),
seasonName: z.string(),
seasonYear: z.number(),
participantName: z.string(),
probFirst: z.string(),
probSecond: z.string(),
probThird: z.string(),
probFourth: z.string(),
probFifth: z.string(),
probSixth: z.string(),
probSeventh: z.string(),
probEighth: z.string(),
expectedValue: z.string(),
source: z.string().nullable(),
sourceOdds: z.number().nullable(),
calculatedAt: z.string().optional(), // optional for backward compat
})
)
.optional(),
participantResults: z
.array(
z.object({
sportSlug: z.string(),
seasonName: z.string(),
seasonYear: z.number(),
participantName: z.string(),
finalPosition: z.number().nullable(),
qualifyingPoints: z.string().nullable(),
notes: z.string().nullable(),
})
)
.optional(),
seasonTemplates: z.array(
z.object({
name: z.string(),
year: z.number(),
description: z.string().nullable(),
isActive: z.boolean(),
sports: z.array(
z.object({
sportSlug: z.string(),
seasonName: z.string(),
seasonYear: z.number(),
})
),
})
),
});
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
type ExportData = z.infer<typeof ImportDataSchema>;
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ─── Export ───────────────────────────────────────────────────────────────────
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
export async function exportSportsDataToJSON(): Promise<ExportData> {
const db = database();
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const [
sports,
sportsSeasons,
participants,
participantEVs,
participantResultsData,
seasonTemplates,
] = await Promise.all([
db.query.sports.findMany({
orderBy: (s, { asc }) => [asc(s.name)],
}),
db.query.sportsSeasons.findMany({
with: { sport: true },
orderBy: (s, { desc, asc }) => [desc(s.year), asc(s.name)],
}),
db.query.participants.findMany({
with: { sportsSeason: { with: { sport: true } } },
orderBy: (p, { asc }) => [asc(p.name)],
}),
db.query.participantExpectedValues.findMany({
with: {
participant: { with: { sportsSeason: { with: { sport: true } } } },
},
}),
db.query.participantResults.findMany({
with: {
participant: { with: { sportsSeason: { with: { sport: true } } } },
},
}),
db.query.seasonTemplates.findMany({
with: {
seasonTemplateSports: {
with: { sportsSeason: { with: { sport: true } } },
},
},
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
orderBy: (t, { desc }) => [desc(t.year)],
}),
]);
return {
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
version: "1.1",
exportedAt: new Date().toISOString(),
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
sports: sports.map((s) => ({
name: s.name,
type: s.type,
slug: s.slug,
description: s.description,
})),
sportsSeasons: sportsSeasons.map((s) => ({
sportSlug: s.sport.slug,
name: s.name,
year: s.year,
startDate: s.startDate,
endDate: s.endDate,
status: s.status,
scoringType: s.scoringType,
})),
participants: participants.map((p) => ({
sportSlug: p.sportsSeason.sport.slug,
seasonName: p.sportsSeason.name,
seasonYear: p.sportsSeason.year,
name: p.name,
shortName: p.shortName,
externalId: p.externalId,
expectedValue: p.expectedValue,
})),
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
participantExpectedValues: participantEVs.map((ev) => ({
sportSlug: ev.participant.sportsSeason.sport.slug,
seasonName: ev.participant.sportsSeason.name,
seasonYear: ev.participant.sportsSeason.year,
participantName: ev.participant.name,
probFirst: ev.probFirst,
probSecond: ev.probSecond,
probThird: ev.probThird,
probFourth: ev.probFourth,
probFifth: ev.probFifth,
probSixth: ev.probSixth,
probSeventh: ev.probSeventh,
probEighth: ev.probEighth,
expectedValue: ev.expectedValue,
source: ev.source,
sourceOdds: ev.sourceOdds,
calculatedAt: ev.calculatedAt.toISOString(),
})),
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
participantResults: participantResultsData.map((r) => ({
sportSlug: r.participant.sportsSeason.sport.slug,
seasonName: r.participant.sportsSeason.name,
seasonYear: r.participant.sportsSeason.year,
participantName: r.participant.name,
finalPosition: r.finalPosition,
qualifyingPoints: r.qualifyingPoints,
notes: r.notes,
})),
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
seasonTemplates: seasonTemplates.map((t) => ({
name: t.name,
year: t.year,
description: t.description,
isActive: t.isActive,
sports: t.seasonTemplateSports.map((ts) => ({
sportSlug: ts.sportsSeason.sport.slug,
seasonName: ts.sportsSeason.name,
seasonYear: ts.sportsSeason.year,
})),
})),
};
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ─── Import ───────────────────────────────────────────────────────────────────
export async function importSportsDataFromJSON(
jsonData: string,
mode: "merge" | "replace" = "merge"
): Promise<{ created: number; updated: number; skipped: number }> {
const db = database();
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// Validate before touching the database — gives a clear error on bad files
let importData: ExportData;
try {
importData = ImportDataSchema.parse(JSON.parse(jsonData));
} catch (err) {
throw new Error(
Add oxlint linting setup with zero errors (#194) * Add oxlint and fix all lint errors - Install oxlint, add .oxlintrc.json with rules for TypeScript/React - Add npm run lint / lint:fix scripts - Add Claude PostToolUse hook to run oxlint on every edited file - Fix 101 errors: unused vars/imports, eqeqeq, prefer-const, no-new-array - Fix no-array-index-key (use stable keys or suppress positional cases) - Fix exhaustive-deps missing dependency in useEffect - Promote exhaustive-deps and no-array-index-key to errors - Fix Map.get() !== null bug in $leagueId.server.ts (should be !== undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix no-explicit-any warnings and upgrade tsconfig to ES2023 - Replace all `any` types with proper types or `unknown` across ~20 files - Add typed socket payload interfaces in draft route and useDraftSocket - Use any[] with eslint-disable for socket.io callbacks (legitimate escape hatch) - Bump all tsconfigs from ES2022 → ES2023 to support toSorted/toReversed - Fix cascading type errors uncovered by removing any: Map.get narrowing, participant relation types, ChartDataPoint, Partial<NewSeason> indexing - Add ParticipantResultWithParticipant type to participant-result model - Fix test fixtures to match updated interfaces (DraftCell, ParticipantResult) - Fix duplicate getQPStandings import in sportsSeasonId.server.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Promote no-explicit-any to error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:05 -07:00
`Invalid import file: ${err instanceof Error ? err.message : "unknown validation error"}`, { cause: err }
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
);
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// Wrap everything in a transaction so a mid-import failure rolls back cleanly
return await db.transaction(async (tx) => {
let created = 0;
let updated = 0;
let skipped = 0;
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
if (mode === "replace") {
// Delete in reverse dependency order.
// participantExpectedValues and participantResults both carry
// onDelete: "cascade" on their participantId FK, so they are
// automatically removed when participants is deleted.
await tx.delete(schema.seasonTemplateSports);
await tx.delete(schema.seasonTemplates);
await tx.delete(schema.participants); // cascades to EVs + results
await tx.delete(schema.seasonSports);
await tx.delete(schema.sportsSeasons);
await tx.delete(schema.sports);
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ID maps used to resolve relationships without further queries
const sportIdMap = new Map<string, string>();
const sportsSeasonIdMap = new Map<string, string>();
// key: `${sportsSeasonId}:${participantName}` — built during participant
// import so EV/result sections avoid a per-record lookup (fixes N+1)
const participantIdMap = new Map<string, string>();
// ── Sports ──────────────────────────────────────────────────────────────
for (const sport of importData.sports) {
const existing = await tx.query.sports.findFirst({
where: eq(schema.sports.slug, sport.slug),
});
if (existing) {
if (mode === "merge") {
await tx
.update(schema.sports)
.set({
name: sport.name,
type: sport.type as "team" | "individual",
description: sport.description,
})
.where(eq(schema.sports.slug, sport.slug));
sportIdMap.set(sport.slug, existing.id);
updated++;
}
} else {
const [created_] = await tx
.insert(schema.sports)
.values({
name: sport.name,
type: sport.type as "team" | "individual",
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
slug: sport.slug,
description: sport.description,
})
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
.returning();
sportIdMap.set(sport.slug, created_.id);
created++;
}
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ── Sports seasons ───────────────────────────────────────────────────────
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
for (const season of importData.sportsSeasons) {
const sportId = sportIdMap.get(season.sportSlug);
if (!sportId) {
skipped++;
continue;
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const seasonKey = `${season.sportSlug}:${season.name}:${season.year}`;
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const existing = await tx.query.sportsSeasons.findFirst({
where: and(
eq(schema.sportsSeasons.sportId, sportId),
eq(schema.sportsSeasons.name, season.name),
eq(schema.sportsSeasons.year, season.year)
),
});
if (existing) {
if (mode === "merge") {
await tx
.update(schema.sportsSeasons)
.set({
startDate: season.startDate,
endDate: season.endDate,
status: season.status as "upcoming" | "active" | "completed",
scoringType: season.scoringType as
| "playoffs"
| "regular_season"
| "majors",
})
.where(eq(schema.sportsSeasons.id, existing.id));
sportsSeasonIdMap.set(seasonKey, existing.id);
updated++;
}
} else {
const [created_] = await tx
.insert(schema.sportsSeasons)
.values({
sportId,
name: season.name,
year: season.year,
startDate: season.startDate,
endDate: season.endDate,
status: season.status as "upcoming" | "active" | "completed",
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
scoringType: season.scoringType as
| "playoffs"
| "regular_season"
| "majors",
// Placeholder: season is not draftable until an admin sets real dates.
// A narrow past window is used so the season never accidentally appears
// in league creation without an intentional update.
draftOn: "2000-01-01",
draftOff: "2000-01-02",
})
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
.returning();
sportsSeasonIdMap.set(seasonKey, created_.id);
created++;
}
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ── Participants ─────────────────────────────────────────────────────────
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
for (const participant of importData.participants) {
const seasonKey = `${participant.sportSlug}:${participant.seasonName}:${participant.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (!sportsSeasonId) {
skipped++;
continue;
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const existing = await tx.query.participants.findFirst({
where: and(
eq(schema.participants.sportsSeasonId, sportsSeasonId),
eq(schema.participants.name, participant.name)
),
});
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
if (existing) {
// Update all mutable fields (fixes: merge mode was silently skipping)
await tx
.update(schema.participants)
.set({
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
shortName: participant.shortName,
externalId: participant.externalId,
expectedValue: String(participant.expectedValue ?? 0),
})
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
.where(eq(schema.participants.id, existing.id));
participantIdMap.set(`${sportsSeasonId}:${participant.name}`, existing.id);
updated++;
} else {
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const [created_] = await tx
.insert(schema.participants)
.values({
sportsSeasonId,
name: participant.name,
shortName: participant.shortName,
externalId: participant.externalId,
expectedValue: String(participant.expectedValue ?? 0),
})
.returning();
participantIdMap.set(`${sportsSeasonId}:${participant.name}`, created_.id);
created++;
}
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ── Participant expected values ──────────────────────────────────────────
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
if (importData.participantExpectedValues) {
for (const ev of importData.participantExpectedValues) {
const seasonKey = `${ev.sportSlug}:${ev.seasonName}:${ev.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (!sportsSeasonId) {
skipped++;
continue;
}
// Use map built during participant import — no extra query needed
const participantId = participantIdMap.get(
`${sportsSeasonId}:${ev.participantName}`
);
if (!participantId) {
skipped++;
continue;
}
const existingEV = await tx.query.participantExpectedValues.findFirst({
where: and(
eq(schema.participantExpectedValues.participantId, participantId),
eq(schema.participantExpectedValues.sportsSeasonId, sportsSeasonId)
),
});
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
const evValues = {
probFirst: ev.probFirst,
probSecond: ev.probSecond,
probThird: ev.probThird,
probFourth: ev.probFourth,
probFifth: ev.probFifth,
probSixth: ev.probSixth,
probSeventh: ev.probSeventh,
probEighth: ev.probEighth,
expectedValue: ev.expectedValue,
source: ev.source as
| "manual"
| "futures_odds"
| "elo_simulation"
| "performance_model"
| null,
sourceOdds: ev.sourceOdds,
// Restore original timestamp when present (fixes: calculatedAt lost)
...(ev.calculatedAt
? { calculatedAt: new Date(ev.calculatedAt) }
: {}),
};
if (existingEV) {
if (mode === "merge") {
await tx
.update(schema.participantExpectedValues)
.set({ ...evValues, updatedAt: new Date() })
.where(eq(schema.participantExpectedValues.id, existingEV.id));
await tx
.update(schema.participants)
.set({ expectedValue: ev.expectedValue })
.where(eq(schema.participants.id, participantId));
updated++;
}
} else {
await tx.insert(schema.participantExpectedValues).values({
participantId,
sportsSeasonId,
...evValues,
});
await tx
.update(schema.participants)
.set({ expectedValue: ev.expectedValue })
.where(eq(schema.participants.id, participantId));
created++;
}
}
}
Add Zod validation and expand export/import for EV and results data (#14) * Include participant EV/futures data in data sync export/import The export now includes the full participantExpectedValues records (placement probabilities, source, sourceOdds) alongside participants. On import, EV records are upserted in merge mode and created fresh in replace mode (cascade handles cleanup). Backward-compatible with older exports that lack the participantExpectedValues field. https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B * Address all data-sync code review findings - Wrap importSportsDataFromJSON in a db.transaction() so any mid-import failure rolls back cleanly instead of leaving partial data - Add zod validation of imported JSON before any DB writes, giving a clear error on malformed/incompatible files - Fix N+1 queries: build participantIdMap during participant import so the EV and results sections resolve IDs from memory, not extra queries - Fix merge mode silently skipping existing participants — now updates shortName, externalId, and expectedValue - Add participantResults to export/import (was deleted in replace mode but never exported, so results were permanently lost) - Restore calculatedAt timestamp on EV import instead of defaulting to now(); stored as ISO string in the export for portability - Document that onDelete:cascade covers participantExpectedValues and participantResults when participants is deleted; remove the now- redundant explicit participantResults delete from replace mode - Bump export version to 1.1; old v1.0 files still import cleanly since participantExpectedValues, participantResults, and calculatedAt are all optional in the zod schema - Collapse all six DB fetches in exportSportsDataToJSON into one Promise.all for parallel execution - Add countAllParticipants() and countAllParticipantEVs() model funcs - Show Participants and EV Records counts on the dashboard stat cards - Replace raw DOM form creation/submit in handleImport with useFetcher, giving proper loading state, no full-page reload, and type-safe data - Remove dead export intent handler from the action function https://claude.ai/code/session_01TVzuuDR7jPtmihaXTrsX7B --------- Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 20:25:25 -08:00
// ── Participant results ──────────────────────────────────────────────────
if (importData.participantResults) {
for (const result of importData.participantResults) {
const seasonKey = `${result.sportSlug}:${result.seasonName}:${result.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (!sportsSeasonId) {
skipped++;
continue;
}
const participantId = participantIdMap.get(
`${sportsSeasonId}:${result.participantName}`
);
if (!participantId) {
skipped++;
continue;
}
const existingResult = await tx.query.participantResults.findFirst({
where: and(
eq(schema.participantResults.participantId, participantId),
eq(schema.participantResults.sportsSeasonId, sportsSeasonId)
),
});
const resultValues = {
finalPosition: result.finalPosition,
qualifyingPoints: result.qualifyingPoints,
notes: result.notes,
};
if (existingResult) {
if (mode === "merge") {
await tx
.update(schema.participantResults)
.set({ ...resultValues, updatedAt: new Date() })
.where(eq(schema.participantResults.id, existingResult.id));
updated++;
}
} else {
await tx.insert(schema.participantResults).values({
participantId,
sportsSeasonId,
...resultValues,
});
created++;
}
}
}
// ── Season templates ─────────────────────────────────────────────────────
for (const template of importData.seasonTemplates) {
const existing = await tx.query.seasonTemplates.findFirst({
where: and(
eq(schema.seasonTemplates.name, template.name),
eq(schema.seasonTemplates.year, template.year)
),
});
let templateId: string;
if (existing) {
if (mode === "merge") {
await tx
.update(schema.seasonTemplates)
.set({
description: template.description,
isActive: template.isActive,
})
.where(eq(schema.seasonTemplates.id, existing.id));
templateId = existing.id;
updated++;
// Remove existing template sports to re-add them cleanly
await tx
.delete(schema.seasonTemplateSports)
.where(eq(schema.seasonTemplateSports.templateId, templateId));
} else {
continue;
}
} else {
const [created_] = await tx
.insert(schema.seasonTemplates)
.values({
name: template.name,
year: template.year,
description: template.description,
isActive: template.isActive,
})
.returning();
templateId = created_.id;
created++;
}
for (const templateSport of template.sports) {
const seasonKey = `${templateSport.sportSlug}:${templateSport.seasonName}:${templateSport.seasonYear}`;
const sportsSeasonId = sportsSeasonIdMap.get(seasonKey);
if (sportsSeasonId) {
await tx.insert(schema.seasonTemplateSports).values({
templateId,
sportsSeasonId,
});
}
}
}
return { created, updated, skipped };
});
}