// Water Ball – Detailed Data Model capturing all fields the user requested // Tech target: Prisma (PostgreSQL). TypeScript types included for clarity. // ----------------------------- // ENUMS // ----------------------------- enum Position { WING CENTER } enum SlotKind { START_W1 START_C START_W2 BENCH_W1 BENCH_C BENCH_W2 BENCH_FLEX1 // W or C BENCH_FLEX2 // W or C BENCH_FLEX3 // W or C } // ----------------------------- // CORE ENTITIES // ----------------------------- model Coach { id String @id @default(cuid()) name String age Int ratingPoints Int @default(0) // "Coaches Rating Points" // Titles that follow the coach regardless of team leagueTitles Int @default(0) conferenceTitles Int @default(0) divisionTitles Int @default(0) // All-time coach record that follows the coach allTimeWins Int @default(0) allTimeLosses Int @default(0) // Current employment teamId String? team Team? @relation(fields: [teamId], references: [id]) // History histories CoachHistory[] } model CoachHistory { id String @id @default(cuid()) coachId String coach Coach @relation(fields: [coachId], references: [id]) teamId String? team Team? @relation(fields: [teamId], references: [id]) seasonId String? season Season? @relation(fields: [seasonId], references: [id]) note String? createdAt DateTime @default(now()) } model Player { id String @id @default(cuid()) name String number Int? position Position cost Int @default(0) // "Player Cost" in VD or salary units age Int ratingPoints Int @default(0) // "Player Rating Points" (ORP or PRP) // Career stats careerGoals Int @default(0) careerPlayoffGoals Int @default(0) // Current season currentSeasonGoals Int @default(0) // Team association (nullable for free agents/retired) teamId String? team Team? @relation(fields: [teamId], references: [id]) // Retirement flag retired Boolean @default(false) retirements Retirement[] // Season histories seasons PlayerSeason[] } model Retirement { id String @id @default(cuid()) playerId String player Player @relation(fields: [playerId], references: [id]) retiredAt DateTime @default(now()) // snapshot indicators at retirement lastRatingPoints Int totalGoals Int teamsPlayedFor String // comma-separated list or migrate to join table if you want details } model Team { id String @id @default(cuid()) name String @unique city String? mascot String? // Ownership & identity ownerId String? @unique owner User? @relation(fields: [ownerId], references: [id]) logoUrl String? // Team Logo Slot (thumbnail) // Hierarchy leagueId String league League @relation(fields: [leagueId], references: [id]) conferenceId String conference Conference @relation(fields: [conferenceId], references: [id]) divisionId String division Division @relation(fields: [divisionId], references: [id]) // Records allTimeWins Int @default(0) allTimeLosses Int @default(0) allTimeLeagueTitles Int @default(0) allTimeConferenceTitles Int @default(0) allTimeDivisionTitles Int @default(0) // Current season linkage for convenience (also tracked per Season) currentSeasonId String? currentSeason Season? @relation(fields: [currentSeasonId], references: [id]) // Coach coachId String? coach Coach? @relation(fields: [coachId], references: [id]) // Relations players Player[] lineups Lineup[] seasons TeamSeason[] // each regular season record gamesHome Game[] @relation("HomeTeam") gamesAway Game[] @relation("AwayTeam") retiredPlayers Retirement[] } model TeamSeason { id String @id @default(cuid()) teamId String team Team @relation(fields: [teamId], references: [id]) seasonId String season Season @relation(fields: [seasonId], references: [id]) wins Int @default(0) losses Int @default(0) } model Season { id String @id @default(cuid()) year Int leagueId String league League @relation(fields: [leagueId], references: [id]) status SeasonStatus @default(DRAFT) weeks Int @default(18) teams TeamSeason[] games Game[] // schedule is implied by Game entries } enum SeasonStatus { DRAFT REGULAR PLAYOFFS COMPLETE } model Game { id String @id @default(cuid()) seasonId String season Season @relation(fields: [seasonId], references: [id]) week Int homeTeamId String homeTeam Team @relation("HomeTeam", fields: [homeTeamId], references: [id]) awayTeamId String awayTeam Team @relation("AwayTeam", fields: [awayTeamId], references: [id]) homeScore Int? awayScore Int? boxJson Json? } // Per-season snapshot for a player model PlayerSeason { id String @id @default(cuid()) playerId String player Player @relation(fields: [playerId], references: [id]) seasonId String season Season @relation(fields: [seasonId], references: [id]) teamId String? team Team? @relation(fields: [teamId], references: [id]) goals Int @default(0) // regular season goals for that season } // Lineup with 9 player slots and strict constraints model Lineup { id String @id @default(cuid()) teamId String team Team @relation(fields: [teamId], references: [id]) seasonId String season Season @relation(fields: [seasonId], references: [id]) week Int slots LineupSlot[] @@unique([teamId, seasonId, week]) } model LineupSlot { id String @id @default(cuid()) lineupId String lineup Lineup @relation(fields: [lineupId], references: [id]) slotKind SlotKind playerId String player Player @relation(fields: [playerId], references: [id]) } // ----------------------------- // VALIDATION & BUSINESS RULES (pseudocode) // ----------------------------- /* function validateLineup(lineup: Lineup, players: Record<string, Player>): void { // There must be exactly 9 slots with unique players assert(lineup.slots.length === 9, 'Lineup must have 9 players'); const ids = new Set(lineup.slots.map(s => s.playerId)); assert(ids.size === 9, 'Duplicate player in lineup'); const byKind = (k: SlotKind) => lineup.slots.find(s => s.slotKind === k); const pos = (k: SlotKind) => players[byKind(k)!.playerId].position; // Starter constraints: W, C, W in order assert(pos('START_W1' as any) === 'WING'); assert(pos('START_C' as any) === 'CENTER'); assert(pos('START_W2' as any) === 'WING'); // First three bench: W, C, W assert(pos('BENCH_W1' as any) === 'WING'); assert(pos('BENCH_C' as any) === 'CENTER'); assert(pos('BENCH_W2' as any) === 'WING'); // Final three bench: FLEX and must be W or C (model only has those two anyway) } */ // ----------------------------- // FILE UPLOAD (team logo) // ----------------------------- // Use signed uploads to S3/Supabase Storage. Store the returned URL in Team.logoUrl. // Accept PNG/JPG/WebP, max 1MB, recommended size 256x256 for thumbnail. // ----------------------------- // OWNER / USER RELATION & DISPLAY // ----------------------------- model User { id String @id @default(cuid()) email String @unique name String? role Role @default(USER) teams Team[] } enum Role { USER ADMIN } // On the Team page, surface: owner name, logo, records, coach info, lineup, schedule, retired players, seasons list. // ----------------------------- // EXAMPLE TEAM JSON SHAPE // ----------------------------- /* { "team": { "name": "Denver Stallions", "logoUrl": "/logos/den-stallions.png", "ownerName": "Jane Doe", "records": { "allTime": { "wins": 29, "losses": 38 }, "currentSeason": { "wins": 16, "losses": 14 } }, "titles": { "league": 0, "conference": 0, "division": 0 }, "coach": { "name": "Derrick Rogers", "age": 47, "ratingPoints": 12, "allTimeRecord": { "wins": 210, "losses": 180 }, "leagueTitles": 1, "conferenceTitles": 2, "divisionTitles": 4 }, "lineup": { "week": 3, "slots": [ { "slotKind": "START_W1", "player": { "name": "Christopher Redd", "position": "WING", "number": 5 } }, { "slotKind": "START_C", "player": { "name": "Michael Mann", "position": "CENTER", "number": 8 } }, { "slotKind": "START_W2", "player": { "name": "Ticky Smith", "position": "WING", "number": 6 } }, { "slotKind": "BENCH_W1", "player": { "name": "Rich Barber", "position": "WING" } }, { "slotKind": "BENCH_C", "player": { "name": "Richy McBeth", "position": "CENTER" } }, { "slotKind": "BENCH_W2", "player": { "name": "Mc Jagger", "position": "WING" } }, { "slotKind": "BENCH_FLEX1", "player": { "name": "Travis Loveless", "position": "WING" } }, { "slotKind": "BENCH_FLEX2", "player": { "name": "Richard McMahon", "position": "WING" } }, { "slotKind": "BENCH_FLEX3", "player": { "name": "Lendon Trees", "position": "WING" } } ] }, "retiredPlayers": [ { "name": "Victor Hale", "totalGoals": 410, "lastRatingPoints": 8, "teamsPlayedFor": "Denver Stallions, Seattle Stars" } ], "seasons": [ { "year": 1, "wins": 14, "losses": 6 }, { "year": 2, "wins": 16, "losses": 4 } ], "schedule": [ { "week": 1, "opponent": "Seattle Stars", "home": true }, { "week": 2, "opponent": "Los Angeles Sting", "home": false } ] } } */