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
This commit is contained in:
parent
401efcd833
commit
dd5072a2c2
2 changed files with 122 additions and 1 deletions
|
|
@ -221,7 +221,7 @@ export default function DataSync({ loaderData, actionData }: Route.ComponentProp
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
This will export all sports, sports seasons, participants, and season templates.
|
This will export all sports, sports seasons, participants, participant EV/futures data, and season templates.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -313,6 +313,7 @@ export default function DataSync({ loaderData, actionData }: Route.ComponentProp
|
||||||
<li>All sports</li>
|
<li>All sports</li>
|
||||||
<li>All sports seasons</li>
|
<li>All sports seasons</li>
|
||||||
<li>All participants</li>
|
<li>All participants</li>
|
||||||
|
<li>All participant EV/futures data</li>
|
||||||
<li>All season templates</li>
|
<li>All season templates</li>
|
||||||
</ul>
|
</ul>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,23 @@ interface ExportData {
|
||||||
externalId: string | null;
|
externalId: string | null;
|
||||||
expectedValue: number | string;
|
expectedValue: number | string;
|
||||||
}>;
|
}>;
|
||||||
|
participantExpectedValues?: Array<{
|
||||||
|
sportSlug: string;
|
||||||
|
seasonName: string;
|
||||||
|
seasonYear: number;
|
||||||
|
participantName: string;
|
||||||
|
probFirst: string;
|
||||||
|
probSecond: string;
|
||||||
|
probThird: string;
|
||||||
|
probFourth: string;
|
||||||
|
probFifth: string;
|
||||||
|
probSixth: string;
|
||||||
|
probSeventh: string;
|
||||||
|
probEighth: string;
|
||||||
|
expectedValue: string;
|
||||||
|
source: string | null;
|
||||||
|
sourceOdds: number | null;
|
||||||
|
}>;
|
||||||
seasonTemplates: Array<{
|
seasonTemplates: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
year: number;
|
year: number;
|
||||||
|
|
@ -73,6 +90,21 @@ export async function exportSportsDataToJSON(): Promise<ExportData> {
|
||||||
orderBy: (participants, { asc }) => [asc(participants.name)],
|
orderBy: (participants, { asc }) => [asc(participants.name)],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Export participant expected values with relations
|
||||||
|
const participantEVs = await db.query.participantExpectedValues.findMany({
|
||||||
|
with: {
|
||||||
|
participant: {
|
||||||
|
with: {
|
||||||
|
sportsSeason: {
|
||||||
|
with: {
|
||||||
|
sport: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Export season templates with sports
|
// Export season templates with sports
|
||||||
const seasonTemplates = await db.query.seasonTemplates.findMany({
|
const seasonTemplates = await db.query.seasonTemplates.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
|
@ -117,6 +149,23 @@ export async function exportSportsDataToJSON(): Promise<ExportData> {
|
||||||
externalId: participant.externalId,
|
externalId: participant.externalId,
|
||||||
expectedValue: participant.expectedValue,
|
expectedValue: participant.expectedValue,
|
||||||
})),
|
})),
|
||||||
|
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,
|
||||||
|
})),
|
||||||
seasonTemplates: seasonTemplates.map((template) => ({
|
seasonTemplates: seasonTemplates.map((template) => ({
|
||||||
name: template.name,
|
name: template.name,
|
||||||
year: template.year,
|
year: template.year,
|
||||||
|
|
@ -272,6 +321,77 @@ export async function importSportsDataFromJSON(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import participant expected values (optional field for backward compatibility)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const participant = await db.query.participants.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(schema.participants.sportsSeasonId, sportsSeasonId),
|
||||||
|
eq(schema.participants.name, ev.participantName)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!participant) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingEV = await db.query.participantExpectedValues.findFirst({
|
||||||
|
where: and(
|
||||||
|
eq(schema.participantExpectedValues.participantId, participant.id),
|
||||||
|
eq(schema.participantExpectedValues.sportsSeasonId, sportsSeasonId)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existingEV) {
|
||||||
|
if (mode === "merge") {
|
||||||
|
await db
|
||||||
|
.update(schema.participantExpectedValues)
|
||||||
|
.set({ ...evValues, updatedAt: new Date() })
|
||||||
|
.where(eq(schema.participantExpectedValues.id, existingEV.id));
|
||||||
|
await db
|
||||||
|
.update(schema.participants)
|
||||||
|
.set({ expectedValue: ev.expectedValue })
|
||||||
|
.where(eq(schema.participants.id, participant.id));
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await db.insert(schema.participantExpectedValues).values({
|
||||||
|
participantId: participant.id,
|
||||||
|
sportsSeasonId,
|
||||||
|
...evValues,
|
||||||
|
});
|
||||||
|
await db
|
||||||
|
.update(schema.participants)
|
||||||
|
.set({ expectedValue: ev.expectedValue })
|
||||||
|
.where(eq(schema.participants.id, participant.id));
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Import season templates
|
// Import season templates
|
||||||
for (const template of importData.seasonTemplates) {
|
for (const template of importData.seasonTemplates) {
|
||||||
const existing = await db.query.seasonTemplates.findFirst({
|
const existing = await db.query.seasonTemplates.findFirst({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue