833 lines
34 KiB
Plaintext
Executable File
833 lines
34 KiB
Plaintext
Executable File
generator client {
|
|
provider = "prisma-client-js"
|
|
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x", "linux-musl-openssl-3.0.x"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Domain Models — Sports Data
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model Country {
|
|
id String @id
|
|
name String @unique
|
|
flagUrl String? @map("flag_url")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
leagues League[]
|
|
|
|
@@map("countries")
|
|
}
|
|
|
|
model League {
|
|
id String @id
|
|
name String
|
|
countryId String? @map("country_id")
|
|
sport Sport
|
|
competitionSlug String? @map("competition_slug")
|
|
code String?
|
|
logoUrl String? @map("logo_url")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
country Country? @relation(fields: [countryId], references: [id])
|
|
liveMatches LiveMatch[]
|
|
matches Match[]
|
|
|
|
@@unique([name, countryId, sport])
|
|
@@index([sport])
|
|
@@index([countryId])
|
|
@@map("leagues")
|
|
}
|
|
|
|
model Team {
|
|
id String @id
|
|
name String
|
|
slug String?
|
|
sport Sport
|
|
logoUrl String? @map("logo_url")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
awayMatchesLive LiveMatch[] @relation("AwayTeamLive")
|
|
homeMatchesLive LiveMatch[] @relation("HomeTeamLive")
|
|
playerEvents MatchPlayerEvents[]
|
|
playerParticipations MatchPlayerParticipation[]
|
|
basketballPlayerStats BasketballPlayerStats[]
|
|
footballTeamStats FootballTeamStats[]
|
|
basketballTeamStats BasketballTeamStats[]
|
|
awayMatches Match[] @relation("AwayTeam")
|
|
homeMatches Match[] @relation("HomeTeam")
|
|
eloRating TeamEloRating?
|
|
|
|
@@index([name])
|
|
@@index([sport])
|
|
@@map("teams")
|
|
}
|
|
|
|
model Player {
|
|
id String @id
|
|
name String
|
|
slug String? @unique
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
assistEvents MatchPlayerEvents[] @relation("AssistPlayer")
|
|
playerEvents MatchPlayerEvents[] @relation("EventPlayer")
|
|
substitutedOutEvents MatchPlayerEvents[] @relation("SubstitutedOut")
|
|
participations MatchPlayerParticipation[]
|
|
playerStats BasketballPlayerStats[]
|
|
|
|
@@index([name])
|
|
@@map("players")
|
|
}
|
|
|
|
model Match {
|
|
id String @id
|
|
leagueId String? @map("league_id")
|
|
homeTeamId String? @map("home_team_id")
|
|
awayTeamId String? @map("away_team_id")
|
|
sport Sport
|
|
matchName String? @map("match_name")
|
|
matchSlug String? @map("match_slug")
|
|
mstUtc BigInt @map("mst_utc")
|
|
status String?
|
|
state String?
|
|
scoreHome Int? @map("score_home")
|
|
scoreAway Int? @map("score_away")
|
|
htScoreHome Int? @map("ht_score_home")
|
|
htScoreAway Int? @map("ht_score_away")
|
|
winner String?
|
|
iddaaCode String? @map("iddaa_code")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
footballAiFeatures FootballAiFeature?
|
|
basketballAiFeatures BasketballAiFeature?
|
|
officials MatchOfficial[]
|
|
playerEvents MatchPlayerEvents[]
|
|
playerParticipations MatchPlayerParticipation[]
|
|
basketballPlayerStats BasketballPlayerStats[]
|
|
footballTeamStats FootballTeamStats[]
|
|
basketballTeamStats BasketballTeamStats[]
|
|
awayTeam Team? @relation("AwayTeam", fields: [awayTeamId], references: [id])
|
|
homeTeam Team? @relation("HomeTeam", fields: [homeTeamId], references: [id])
|
|
league League? @relation(fields: [leagueId], references: [id])
|
|
oddCategories OddCategory[]
|
|
prediction Prediction?
|
|
couponItems UserCouponItem[]
|
|
|
|
@@index([awayTeamId])
|
|
@@index([homeTeamId])
|
|
@@index([iddaaCode])
|
|
@@index([leagueId])
|
|
@@index([mstUtc(sort: Desc)])
|
|
@@index([sport])
|
|
@@index([state])
|
|
@@index([status, mstUtc(sort: Desc)])
|
|
@@map("matches")
|
|
}
|
|
|
|
model LiveMatch {
|
|
id String @id
|
|
leagueId String? @map("league_id")
|
|
homeTeamId String? @map("home_team_id")
|
|
awayTeamId String? @map("away_team_id")
|
|
sport String?
|
|
matchName String? @map("match_name")
|
|
matchSlug String? @map("match_slug")
|
|
mstUtc BigInt? @map("mst_utc")
|
|
status String?
|
|
state String?
|
|
substate String?
|
|
scoreHome Int? @map("score_home")
|
|
scoreAway Int? @map("score_away")
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
|
odds Json?
|
|
oddsUpdatedAt DateTime? @map("odds_updated_at")
|
|
refereeName String? @map("referee_name")
|
|
lineups Json?
|
|
sidelined Json?
|
|
awayTeam Team? @relation("AwayTeamLive", fields: [awayTeamId], references: [id])
|
|
homeTeam Team? @relation("HomeTeamLive", fields: [homeTeamId], references: [id])
|
|
league League? @relation(fields: [leagueId], references: [id])
|
|
|
|
@@index([mstUtc])
|
|
@@index([state])
|
|
@@map("live_matches")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Match Details — Stats, Events, Participation, Officials
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model FootballAiFeature {
|
|
matchId String @id @map("match_id")
|
|
homeElo Float @default(1500.0) @map("home_elo")
|
|
awayElo Float @default(1500.0) @map("away_elo")
|
|
homeHomeElo Float @default(1500.0) @map("home_home_elo")
|
|
awayAwayElo Float @default(1500.0) @map("away_away_elo")
|
|
homeFormElo Float @default(1500.0) @map("home_form_elo")
|
|
awayFormElo Float @default(1500.0) @map("away_form_elo")
|
|
eloDiff Float @default(0.0) @map("elo_diff")
|
|
homeFormScore Float @default(50.0) @map("home_form_score")
|
|
awayFormScore Float @default(50.0) @map("away_form_score")
|
|
homeGoalsAvg5 Float @default(0.0) @map("home_goals_avg_5")
|
|
awayGoalsAvg5 Float @default(0.0) @map("away_goals_avg_5")
|
|
homeConcededAvg5 Float @default(0.0) @map("home_conceded_avg_5")
|
|
awayConcededAvg5 Float @default(0.0) @map("away_conceded_avg_5")
|
|
homeCleanSheetRate Float @default(0.0) @map("home_clean_sheet_rate")
|
|
awayCleanSheetRate Float @default(0.0) @map("away_clean_sheet_rate")
|
|
homeScoringRate Float @default(0.0) @map("home_scoring_rate")
|
|
awayScoringRate Float @default(0.0) @map("away_scoring_rate")
|
|
homeWinStreak Int @default(0) @map("home_win_streak")
|
|
awayWinStreak Int @default(0) @map("away_win_streak")
|
|
impliedHome Float @default(0.33) @map("implied_home")
|
|
impliedDraw Float @default(0.33) @map("implied_draw")
|
|
impliedAway Float @default(0.33) @map("implied_away")
|
|
impliedOver25 Float @default(0.5) @map("implied_over25")
|
|
impliedBttsYes Float @default(0.5) @map("implied_btts_yes")
|
|
oddsOverround Float @default(0.0) @map("odds_overround")
|
|
homeAvgPossession Float @default(50.0) @map("home_avg_possession")
|
|
awayAvgPossession Float @default(50.0) @map("away_avg_possession")
|
|
homeAvgShotsOnTarget Float @default(0.0) @map("home_avg_shots_on_target")
|
|
awayAvgShotsOnTarget Float @default(0.0) @map("away_avg_shots_on_target")
|
|
homeShotConversion Float @default(0.0) @map("home_shot_conversion")
|
|
awayShotConversion Float @default(0.0) @map("away_shot_conversion")
|
|
homeAvgCorners Float @default(0.0) @map("home_avg_corners")
|
|
awayAvgCorners Float @default(0.0) @map("away_avg_corners")
|
|
h2hTotal Int @default(0) @map("h2h_total")
|
|
h2hHomeWinRate Float @default(0.0) @map("h2h_home_win_rate")
|
|
h2hAvgGoals Float @default(0.0) @map("h2h_avg_goals")
|
|
h2hOver25Rate Float @default(0.0) @map("h2h_over25_rate")
|
|
h2hBttsRate Float @default(0.0) @map("h2h_btts_rate")
|
|
refereeAvgCards Float @default(0.0) @map("referee_avg_cards")
|
|
refereeHomeBias Float @default(0.0) @map("referee_home_bias")
|
|
refereeAvgGoals Float @default(0.0) @map("referee_avg_goals")
|
|
leagueAvgGoals Float @default(0.0) @map("league_avg_goals")
|
|
leagueHomeWinPct Float @default(0.0) @map("league_home_win_pct")
|
|
leagueOver25Pct Float @default(0.0) @map("league_over25_pct")
|
|
missingPlayersImpact Float @default(0.0) @map("missing_players_impact")
|
|
calculatorVer String @default("v2.0") @map("calculator_ver")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("football_ai_features")
|
|
}
|
|
|
|
model BasketballAiFeature {
|
|
matchId String @id @map("match_id")
|
|
homeElo Float @default(1500.0) @map("home_elo")
|
|
awayElo Float @default(1500.0) @map("away_elo")
|
|
homeHomeElo Float @default(1500.0) @map("home_home_elo")
|
|
awayAwayElo Float @default(1500.0) @map("away_away_elo")
|
|
homeFormElo Float @default(1500.0) @map("home_form_elo")
|
|
awayFormElo Float @default(1500.0) @map("away_form_elo")
|
|
eloDiff Float @default(0.0) @map("elo_diff")
|
|
homeFormScore Float @default(50.0) @map("home_form_score")
|
|
awayFormScore Float @default(50.0) @map("away_form_score")
|
|
homePtsAvg5 Float @default(0.0) @map("home_pts_avg_5")
|
|
awayPtsAvg5 Float @default(0.0) @map("away_pts_avg_5")
|
|
homeConcededAvg5 Float @default(0.0) @map("home_conceded_avg_5")
|
|
awayConcededAvg5 Float @default(0.0) @map("away_conceded_avg_5")
|
|
homeWinStreak Int @default(0) @map("home_win_streak")
|
|
awayWinStreak Int @default(0) @map("away_win_streak")
|
|
impliedHome Float @default(0.5) @map("implied_home")
|
|
impliedAway Float @default(0.5) @map("implied_away")
|
|
impliedOverTotal Float @default(0.5) @map("implied_over_total")
|
|
impliedSpreadHome Float @default(0.5) @map("implied_spread_home")
|
|
oddsOverround Float @default(0.0) @map("odds_overround")
|
|
homeAvgPts Float @default(0.0) @map("home_avg_pts")
|
|
awayAvgPts Float @default(0.0) @map("away_avg_pts")
|
|
homeAvgRebounds Float @default(0.0) @map("home_avg_rebounds")
|
|
awayAvgRebounds Float @default(0.0) @map("away_avg_rebounds")
|
|
homeFgPct Float @default(0.0) @map("home_fg_pct")
|
|
awayFgPct Float @default(0.0) @map("away_fg_pct")
|
|
homeAvgThreePtMade Float @default(0.0) @map("home_avg_three_pt_made")
|
|
awayAvgThreePtMade Float @default(0.0) @map("away_avg_three_pt_made")
|
|
homeAvgTurnovers Float @default(0.0) @map("home_avg_turnovers")
|
|
awayAvgTurnovers Float @default(0.0) @map("away_avg_turnovers")
|
|
h2hTotal Int @default(0) @map("h2h_total")
|
|
h2hHomeWinRate Float @default(0.0) @map("h2h_home_win_rate")
|
|
h2hAvgPts Float @default(0.0) @map("h2h_avg_pts")
|
|
h2hAvgMargin Float @default(0.0) @map("h2h_avg_margin")
|
|
missingPlayersImpact Float @default(0.0) @map("missing_players_impact")
|
|
calculatorVer String @default("v2.0") @map("calculator_ver")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("basketball_ai_features")
|
|
}
|
|
|
|
model TeamEloRating {
|
|
teamId String @id @map("team_id")
|
|
overallElo Float @default(1500.0) @map("overall_elo")
|
|
homeElo Float @default(1500.0) @map("home_elo")
|
|
awayElo Float @default(1500.0) @map("away_elo")
|
|
formElo Float @default(1500.0) @map("form_elo")
|
|
matchesPlayed Int @default(0) @map("matches_played")
|
|
recentForm String @default("") @map("recent_form")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("team_elo_ratings")
|
|
}
|
|
|
|
model MatchPlayerEvents {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
playerId String @map("player_id")
|
|
teamId String @map("team_id")
|
|
eventType EventType @map("event_type")
|
|
eventSubtype String? @map("event_subtype")
|
|
timeMinute String @map("time_minute")
|
|
timeSeconds Int? @map("time_seconds")
|
|
periodId Int? @map("period_id")
|
|
assistPlayerId String? @map("assist_player_id")
|
|
scoreAfter String? @map("score_after")
|
|
playerOutId String? @map("player_out_id")
|
|
position MatchPosition?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
assistPlayer Player? @relation("AssistPlayer", fields: [assistPlayerId], references: [id])
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
player Player @relation("EventPlayer", fields: [playerId], references: [id], onDelete: Cascade)
|
|
substitutedOut Player? @relation("SubstitutedOut", fields: [playerOutId], references: [id])
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([assistPlayerId])
|
|
@@index([eventType])
|
|
@@index([matchId])
|
|
@@index([playerId])
|
|
@@index([teamId])
|
|
@@map("match_player_events")
|
|
}
|
|
|
|
model MatchPlayerParticipation {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
playerId String @map("player_id")
|
|
teamId String @map("team_id")
|
|
position PlayerPosition?
|
|
shirtNumber Int? @map("shirt_number")
|
|
isStarting Boolean @default(true) @map("is_starting")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([matchId, playerId, teamId])
|
|
@@index([matchId])
|
|
@@index([playerId])
|
|
@@index([teamId])
|
|
@@map("match_player_participation")
|
|
}
|
|
|
|
model BasketballPlayerStats {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
playerId String @map("player_id")
|
|
teamId String @map("team_id")
|
|
minutes String?
|
|
points Int?
|
|
rebounds Int?
|
|
assists Int?
|
|
steals Int?
|
|
blocks Int?
|
|
turnovers Int?
|
|
fgMade Int? @map("fg_made")
|
|
fgAttempted Int? @map("fg_attempted")
|
|
threePtMade Int? @map("three_pt_made")
|
|
threePtAttempted Int? @map("three_pt_attempted")
|
|
ftMade Int? @map("ft_made")
|
|
ftAttempted Int? @map("ft_attempted")
|
|
fouls Int?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([matchId, playerId, teamId])
|
|
@@index([matchId])
|
|
@@map("basketball_player_stats")
|
|
}
|
|
|
|
model FootballTeamStats {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
teamId String @map("team_id")
|
|
possessionPercentage Float? @map("possession_percentage")
|
|
shotsOnTarget Int? @map("shots_on_target")
|
|
shotsOffTarget Int? @map("shots_off_target")
|
|
totalShots Int? @map("total_shots")
|
|
totalPasses Int? @map("total_passes")
|
|
corners Int?
|
|
fouls Int?
|
|
offsides Int?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([matchId, teamId])
|
|
@@index([matchId])
|
|
@@index([teamId])
|
|
@@map("football_team_stats")
|
|
}
|
|
|
|
model BasketballTeamStats {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
teamId String @map("team_id")
|
|
points Int?
|
|
rebounds Int?
|
|
assists Int?
|
|
fgMade Int? @map("fg_made")
|
|
fgAttempted Int? @map("fg_attempted")
|
|
threePtMade Int? @map("three_pt_made")
|
|
threePtAttempted Int? @map("three_pt_attempted")
|
|
ftMade Int? @map("ft_made")
|
|
ftAttempted Int? @map("ft_attempted")
|
|
steals Int?
|
|
blocks Int?
|
|
turnovers Int?
|
|
fouls Int?
|
|
q1Score Int? @map("q1_score")
|
|
q2Score Int? @map("q2_score")
|
|
q3Score Int? @map("q3_score")
|
|
q4Score Int? @map("q4_score")
|
|
otScore Int? @map("ot_score")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([matchId, teamId])
|
|
@@index([matchId])
|
|
@@index([teamId])
|
|
@@map("basketball_team_stats")
|
|
}
|
|
|
|
model MatchOfficial {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
name String
|
|
roleId Int @map("role_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
role OfficialRole @relation(fields: [roleId], references: [id])
|
|
|
|
@@unique([matchId, name, roleId])
|
|
@@index([matchId])
|
|
@@index([roleId])
|
|
@@map("match_officials")
|
|
}
|
|
|
|
model OfficialRole {
|
|
id Int @id @default(autoincrement())
|
|
name String @unique
|
|
officials MatchOfficial[]
|
|
|
|
@@map("official_roles")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Odds & Predictions
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model OddCategory {
|
|
dbId Int @id @default(autoincrement()) @map("db_id")
|
|
matchId String @map("match_id")
|
|
categoryJsonId Int? @map("category_json_id")
|
|
name String?
|
|
sport Sport?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
selections OddSelection[]
|
|
|
|
@@unique([matchId, name])
|
|
@@index([matchId])
|
|
@@index([sport])
|
|
@@map("odd_categories")
|
|
}
|
|
|
|
model OddSelection {
|
|
dbId Int @id @default(autoincrement()) @map("db_id")
|
|
categoryId Int @map("odd_category_db_id")
|
|
name String?
|
|
oddValue String? @map("odd_value")
|
|
position String?
|
|
sov Float?
|
|
state String?
|
|
sport Sport?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @default(now()) @map("updated_at")
|
|
category OddCategory @relation(fields: [categoryId], references: [dbId], onDelete: Cascade)
|
|
history OddsHistory[]
|
|
|
|
@@unique([categoryId, name])
|
|
@@index([categoryId])
|
|
@@index([sport])
|
|
@@map("odd_selections")
|
|
}
|
|
|
|
model OddsHistory {
|
|
id BigInt @id @default(autoincrement())
|
|
selectionId Int @map("selection_id")
|
|
matchId String @map("match_id")
|
|
previousValue Float @map("previous_value")
|
|
newValue Float @map("new_value")
|
|
bookmaker String? @default("MACKOLIK")
|
|
changeTime DateTime @default(now()) @map("change_time")
|
|
selection OddSelection @relation(fields: [selectionId], references: [dbId], onDelete: Cascade)
|
|
|
|
@@index([matchId, changeTime])
|
|
@@index([selectionId])
|
|
@@map("odds_history")
|
|
}
|
|
|
|
model Prediction {
|
|
matchId String @id @map("match_id")
|
|
predictionJson Json @map("prediction_json")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("predictions")
|
|
}
|
|
|
|
model PredictionRun {
|
|
id BigInt @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
engineVersion String @map("engine_version")
|
|
decisionTraceId String? @map("decision_trace_id")
|
|
generatedAt DateTime @default(now()) @map("generated_at")
|
|
oddsSnapshot Json? @map("odds_snapshot")
|
|
payloadSummary Json @map("payload_summary")
|
|
eventualOutcome String? @map("eventual_outcome")
|
|
unitProfit Float? @map("unit_profit")
|
|
|
|
@@index([matchId, generatedAt(sort: Desc)])
|
|
@@index([engineVersion, generatedAt(sort: Desc)])
|
|
@@map("prediction_runs")
|
|
}
|
|
|
|
model AiPredictionsLog {
|
|
id Int @id @default(autoincrement())
|
|
matchId String @map("match_id")
|
|
modelVersion String @map("model_version")
|
|
recommendedBets Json? @map("recommended_bets")
|
|
confidenceScore Float? @map("confidence_score")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
isResolved Boolean @default(false) @map("is_resolved")
|
|
actualResult String? @map("actual_result")
|
|
isCorrect Boolean? @map("is_correct")
|
|
accuracyScore Float? @map("accuracy_score")
|
|
|
|
@@index([matchId])
|
|
@@index([createdAt(sort: Desc)])
|
|
@@map("ai_predictions_log")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// User Domain — Auth, Coupons, Usage
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
passwordHash String @map("password_hash")
|
|
firstName String? @map("first_name")
|
|
lastName String? @map("last_name")
|
|
role UserRole @default(user)
|
|
subscriptionStatus SubscriptionStatus @default(free) @map("subscription_status")
|
|
subscriptionExpiresAt DateTime? @map("subscription_expires_at")
|
|
encryptedApiKey String? @map("encrypted_api_key")
|
|
isActive Boolean @default(true) @map("is_active")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
deletedAt DateTime? @map("deleted_at")
|
|
analyses Analysis[]
|
|
refreshTokens RefreshToken[]
|
|
usageLimit UsageLimit?
|
|
subscription Subscription?
|
|
coupons UserCoupon[]
|
|
totoCoupons TotoCoupon[]
|
|
|
|
@@index([email])
|
|
@@index([subscriptionStatus, subscriptionExpiresAt])
|
|
@@map("users")
|
|
}
|
|
|
|
model Subscription {
|
|
id String @id @default(uuid())
|
|
userId String @unique @map("user_id")
|
|
paddleSubscriptionId String? @unique @map("paddle_subscription_id")
|
|
paddleCustomerId String? @map("paddle_customer_id")
|
|
plan SubscriptionStatus @default(free)
|
|
billingInterval BillingInterval? @map("billing_interval")
|
|
currentPeriodStart DateTime? @map("current_period_start")
|
|
currentPeriodEnd DateTime? @map("current_period_end")
|
|
cancelledAt DateTime? @map("cancelled_at")
|
|
cancelEffectiveDate DateTime? @map("cancel_effective_date")
|
|
paddlePriceId String? @map("paddle_price_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([paddleSubscriptionId])
|
|
@@index([paddleCustomerId])
|
|
@@map("subscriptions")
|
|
}
|
|
|
|
model RefreshToken {
|
|
id String @id @default(uuid())
|
|
token String @unique
|
|
userId String @map("user_id")
|
|
expiresAt DateTime @map("expires_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([token])
|
|
@@index([userId])
|
|
@@map("refresh_tokens")
|
|
}
|
|
|
|
model UsageLimit {
|
|
id Int @id @default(autoincrement())
|
|
userId String @unique @map("user_id")
|
|
analysisCount Int @default(0) @map("analysis_count")
|
|
couponCount Int @default(0) @map("coupon_count")
|
|
maxAnalyses Int @default(3) @map("max_analyses")
|
|
maxCoupons Int @default(1) @map("max_coupons")
|
|
lastResetDate DateTime @map("last_reset_date") @db.Date
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([lastResetDate])
|
|
@@map("usage_limits")
|
|
}
|
|
|
|
model Analysis {
|
|
id Int @id @default(autoincrement())
|
|
userId String @map("user_id")
|
|
matchIds String @map("match_ids")
|
|
analysisResultJson String @map("analysis_result_json")
|
|
isDeleted Boolean @default(false) @map("is_deleted")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([createdAt(sort: Desc)])
|
|
@@map("analyses")
|
|
}
|
|
|
|
model UserCoupon {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
strategy String
|
|
totalOdds Float @map("total_odds")
|
|
status String @default("PENDING")
|
|
isPublic Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
couponItems UserCouponItem[]
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([status])
|
|
@@map("user_coupons")
|
|
}
|
|
|
|
model UserCouponItem {
|
|
id Int @id @default(autoincrement())
|
|
couponId String @map("coupon_id")
|
|
matchId String @map("match_id")
|
|
selection String
|
|
oddAtTime Float @map("odd_at_time")
|
|
isCorrect Boolean? @map("is_correct")
|
|
coupon UserCoupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
|
|
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([couponId])
|
|
@@map("user_coupon_items")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Spor Toto Domain
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model TotoBulletin {
|
|
id String @id @default(uuid())
|
|
gameCycleNo Int @unique @map("game_cycle_no")
|
|
programName String? @map("program_name")
|
|
season String?
|
|
status TotoBulletinStatus @default(UPCOMING)
|
|
payinBeginDate DateTime? @map("payin_begin_date")
|
|
payinEndDate DateTime? @map("payin_end_date")
|
|
poolTotal Float? @map("pool_total")
|
|
rolloverAmount Float? @map("rollover_amount")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
matches TotoBulletinMatch[]
|
|
result TotoResult?
|
|
coupons TotoCoupon[]
|
|
|
|
@@index([status])
|
|
@@map("toto_bulletins")
|
|
}
|
|
|
|
model TotoBulletinMatch {
|
|
id Int @id @default(autoincrement())
|
|
bulletinId String @map("bulletin_id")
|
|
matchOrder Int @map("match_order")
|
|
homeTeamName String @map("home_team_name")
|
|
awayTeamName String @map("away_team_name")
|
|
leagueName String? @map("league_name")
|
|
kickoffTime DateTime? @map("kickoff_time")
|
|
matchId String? @map("match_id")
|
|
result TotoMatchResult?
|
|
isCancelled Boolean @default(false) @map("is_cancelled")
|
|
drawResult TotoMatchResult? @map("draw_result")
|
|
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([bulletinId, matchOrder])
|
|
@@index([bulletinId])
|
|
@@index([matchId])
|
|
@@map("toto_bulletin_matches")
|
|
}
|
|
|
|
model TotoResult {
|
|
id String @id @default(uuid())
|
|
bulletinId String @unique @map("bulletin_id")
|
|
winners15 Int @default(0) @map("winners_15")
|
|
prize15 Float? @map("prize_15")
|
|
winners14 Int @default(0) @map("winners_14")
|
|
prize14 Float? @map("prize_14")
|
|
winners13 Int @default(0) @map("winners_13")
|
|
prize13 Float? @map("prize_13")
|
|
winners12 Int @default(0) @map("winners_12")
|
|
prize12 Float? @map("prize_12")
|
|
rolloverNext Float? @map("rollover_next")
|
|
poolDistributed Float? @map("pool_distributed")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("toto_results")
|
|
}
|
|
|
|
model TotoCoupon {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
bulletinId String @map("bulletin_id")
|
|
strategy String?
|
|
columnCount Int @map("column_count")
|
|
totalCost Float @map("total_cost")
|
|
status String @default("PENDING")
|
|
totalPrize Float? @map("total_prize")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
columns TotoColumn[]
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([bulletinId])
|
|
@@index([status])
|
|
@@map("toto_coupons")
|
|
}
|
|
|
|
model TotoColumn {
|
|
id Int @id @default(autoincrement())
|
|
couponId String @map("coupon_id")
|
|
predictions String @db.VarChar(15)
|
|
correctCount Int? @map("correct_count")
|
|
prizeAmount Float? @map("prize_amount")
|
|
coupon TotoCoupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([couponId])
|
|
@@index([correctCount])
|
|
@@map("toto_columns")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// System & i18n
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
model AppSetting {
|
|
key String @id
|
|
value String?
|
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
|
|
|
@@map("app_settings")
|
|
}
|
|
|
|
model Translation {
|
|
id String @id @default(uuid())
|
|
key String
|
|
locale String
|
|
value String
|
|
namespace String @default("common")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
@@unique([key, locale, namespace])
|
|
@@index([key])
|
|
@@index([locale])
|
|
@@index([namespace])
|
|
@@map("translations")
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Enums
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
enum Sport {
|
|
football
|
|
basketball
|
|
}
|
|
|
|
enum UserRole {
|
|
user
|
|
superadmin
|
|
}
|
|
|
|
enum SubscriptionStatus {
|
|
free
|
|
plus
|
|
premium
|
|
past_due
|
|
cancelled
|
|
}
|
|
|
|
enum BillingInterval {
|
|
monthly
|
|
yearly
|
|
}
|
|
|
|
enum PlayerPosition {
|
|
goalkeeper
|
|
defender
|
|
midfielder
|
|
striker
|
|
}
|
|
|
|
enum EventType {
|
|
goal
|
|
card
|
|
substitute
|
|
}
|
|
|
|
enum MatchPosition {
|
|
home
|
|
away
|
|
}
|
|
|
|
enum TotoBulletinStatus {
|
|
UPCOMING
|
|
ACTIVE
|
|
COMPLETED
|
|
CANCELLED
|
|
}
|
|
|
|
enum TotoMatchResult {
|
|
HOME_WIN
|
|
DRAW
|
|
AWAY_WIN
|
|
}
|