@opprs/db-prisma 0.5.2-canary.16741f5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ // Prisma schema for OPPR Database
2
+ // Documentation: https://pris.ly/d/prisma-schema
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "postgresql"
10
+ url = env("DATABASE_URL")
11
+ }
12
+
13
+ // Player model - represents a pinball player
14
+ model Player {
15
+ id String @id @default(cuid())
16
+ createdAt DateTime @default(now())
17
+ updatedAt DateTime @updatedAt
18
+
19
+ // Player identification
20
+ externalId String? @unique // External ID from OPPR or other systems
21
+ name String?
22
+ email String? @unique
23
+
24
+ // OPPR Rating fields
25
+ rating Float @default(1500) // Glicko rating
26
+ ratingDeviation Float @default(200) // Rating uncertainty (RD)
27
+ ranking Int? // World ranking position
28
+ isRated Boolean @default(false) // Has 5+ events
29
+ eventCount Int @default(0) // Number of events participated
30
+
31
+ // Timestamps for rating calculations
32
+ lastRatingUpdate DateTime @default(now())
33
+ lastEventDate DateTime?
34
+
35
+ // Relations
36
+ tournamentResults TournamentResult[]
37
+
38
+ @@index([email])
39
+ @@index([externalId])
40
+ @@index([rating])
41
+ @@index([ranking])
42
+ }
43
+
44
+ // Tournament model - represents a pinball tournament event
45
+ model Tournament {
46
+ id String @id @default(cuid())
47
+ createdAt DateTime @default(now())
48
+ updatedAt DateTime @updatedAt
49
+
50
+ // Tournament identification
51
+ externalId String? @unique // External ID from OPPR or other systems
52
+ name String
53
+ location String?
54
+ date DateTime
55
+
56
+ // Tournament configuration (stored as JSON)
57
+ // Contains TGPConfig structure from OPPR
58
+ tgpConfig Json?
59
+
60
+ // Event classification
61
+ eventBooster EventBoosterType @default(NONE)
62
+ allowsOptOut Boolean @default(false)
63
+
64
+ // Tournament value calculations (can be calculated or cached)
65
+ baseValue Float?
66
+ tvaRating Float?
67
+ tvaRanking Float?
68
+ totalTVA Float?
69
+ tgp Float?
70
+ eventBoosterMultiplier Float?
71
+ firstPlaceValue Float?
72
+
73
+ // Relations
74
+ results TournamentResult[]
75
+
76
+ @@index([date])
77
+ @@index([eventBooster])
78
+ @@index([externalId])
79
+ }
80
+
81
+ // Tournament Result - junction table linking players to tournaments
82
+ model TournamentResult {
83
+ id String @id @default(cuid())
84
+ createdAt DateTime @default(now())
85
+ updatedAt DateTime @updatedAt
86
+
87
+ // Relations
88
+ playerId String
89
+ player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
90
+ tournamentId String
91
+ tournament Tournament @relation(fields: [tournamentId], references: [id], onDelete: Cascade)
92
+
93
+ // Result data
94
+ position Int // Finishing position (1 = first place)
95
+ optedOut Boolean @default(false)
96
+
97
+ // Points awarded
98
+ linearPoints Float? @default(0) // Linear distribution points
99
+ dynamicPoints Float? @default(0) // Dynamic distribution points
100
+ totalPoints Float? // Total points (linear + dynamic)
101
+
102
+ // Time decay tracking
103
+ ageInDays Int? @default(0) // Age of event in days at calculation time
104
+ decayMultiplier Float? @default(1.0) // Time decay multiplier (1.0, 0.75, 0.5, or 0.0)
105
+ decayedPoints Float? // Points after applying decay (defaults to totalPoints via application logic)
106
+
107
+ // Efficiency tracking
108
+ efficiency Float? // Performance efficiency percentage
109
+
110
+ @@unique([playerId, tournamentId])
111
+ @@index([playerId])
112
+ @@index([tournamentId])
113
+ @@index([position])
114
+ }
115
+
116
+ // Enum for event booster types
117
+ enum EventBoosterType {
118
+ NONE
119
+ CERTIFIED
120
+ CERTIFIED_PLUS
121
+ CHAMPIONSHIP_SERIES
122
+ MAJOR
123
+ }
package/prisma/seed.ts ADDED
@@ -0,0 +1,349 @@
1
+ import { PrismaClient, EventBoosterType } from '@prisma/client';
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ async function main() {
6
+ console.log('🌱 Seeding database...');
7
+
8
+ // Create sample players
9
+ console.log('Creating players...');
10
+ const player1 = await prisma.player.create({
11
+ data: {
12
+ externalId: 'player-1',
13
+ name: 'Alice Champion',
14
+ email: 'alice@example.com',
15
+ rating: 1850,
16
+ ratingDeviation: 50,
17
+ ranking: 5,
18
+ isRated: true,
19
+ eventCount: 25,
20
+ },
21
+ });
22
+
23
+ const player2 = await prisma.player.create({
24
+ data: {
25
+ externalId: 'player-2',
26
+ name: 'Bob Wizard',
27
+ email: 'bob@example.com',
28
+ rating: 1750,
29
+ ratingDeviation: 60,
30
+ ranking: 12,
31
+ isRated: true,
32
+ eventCount: 18,
33
+ },
34
+ });
35
+
36
+ const player3 = await prisma.player.create({
37
+ data: {
38
+ externalId: 'player-3',
39
+ name: 'Charlie Flipper',
40
+ email: 'charlie@example.com',
41
+ rating: 1650,
42
+ ratingDeviation: 75,
43
+ ranking: 28,
44
+ isRated: true,
45
+ eventCount: 12,
46
+ },
47
+ });
48
+
49
+ const player4 = await prisma.player.create({
50
+ data: {
51
+ externalId: 'player-4',
52
+ name: 'Diana Tilt',
53
+ email: 'diana@example.com',
54
+ rating: 1550,
55
+ ratingDeviation: 100,
56
+ ranking: 45,
57
+ isRated: true,
58
+ eventCount: 8,
59
+ },
60
+ });
61
+
62
+ const player5 = await prisma.player.create({
63
+ data: {
64
+ externalId: 'player-5',
65
+ name: 'Eve Plunger',
66
+ email: 'eve@example.com',
67
+ rating: 1300,
68
+ ratingDeviation: 150,
69
+ ranking: null,
70
+ isRated: false,
71
+ eventCount: 3,
72
+ },
73
+ });
74
+
75
+ console.log(`✓ Created ${await prisma.player.count()} players`);
76
+
77
+ // Create sample tournaments
78
+ console.log('Creating tournaments...');
79
+
80
+ // Major Championship Tournament
81
+ const tournament1 = await prisma.tournament.create({
82
+ data: {
83
+ externalId: 'tournament-1',
84
+ name: 'World Pinball Championship 2024',
85
+ location: 'Las Vegas, NV',
86
+ date: new Date('2024-03-15'),
87
+ eventBooster: EventBoosterType.MAJOR,
88
+ allowsOptOut: false,
89
+ tgpConfig: {
90
+ qualifying: {
91
+ type: 'limited',
92
+ meaningfulGames: 12,
93
+ fourPlayerGroups: true,
94
+ },
95
+ finals: {
96
+ formatType: 'match-play',
97
+ meaningfulGames: 20,
98
+ fourPlayerGroups: true,
99
+ finalistCount: 16,
100
+ },
101
+ ballCountAdjustment: 1.0,
102
+ },
103
+ baseValue: 32.0,
104
+ tvaRating: 25.0,
105
+ tvaRanking: 50.0,
106
+ totalTVA: 75.0,
107
+ tgp: 1.92,
108
+ eventBoosterMultiplier: 2.0,
109
+ firstPlaceValue: 411.84,
110
+ },
111
+ });
112
+
113
+ // Certified Tournament
114
+ const tournament2 = await prisma.tournament.create({
115
+ data: {
116
+ externalId: 'tournament-2',
117
+ name: 'Spring Classics 2024',
118
+ location: 'Portland, OR',
119
+ date: new Date('2024-04-20'),
120
+ eventBooster: EventBoosterType.CERTIFIED,
121
+ allowsOptOut: true,
122
+ tgpConfig: {
123
+ qualifying: {
124
+ type: 'limited',
125
+ meaningfulGames: 7,
126
+ },
127
+ finals: {
128
+ formatType: 'double-elimination',
129
+ meaningfulGames: 15,
130
+ fourPlayerGroups: false,
131
+ finalistCount: 8,
132
+ },
133
+ },
134
+ baseValue: 28.0,
135
+ tvaRating: 18.5,
136
+ tvaRanking: 32.0,
137
+ totalTVA: 50.5,
138
+ tgp: 0.88,
139
+ eventBoosterMultiplier: 1.25,
140
+ firstPlaceValue: 87.28,
141
+ },
142
+ });
143
+
144
+ // Local Tournament
145
+ const tournament3 = await prisma.tournament.create({
146
+ data: {
147
+ externalId: 'tournament-3',
148
+ name: 'Monthly League Finals',
149
+ location: 'Seattle, WA',
150
+ date: new Date('2024-05-10'),
151
+ eventBooster: EventBoosterType.NONE,
152
+ allowsOptOut: false,
153
+ tgpConfig: {
154
+ qualifying: {
155
+ type: 'none',
156
+ meaningfulGames: 0,
157
+ },
158
+ finals: {
159
+ formatType: 'match-play',
160
+ meaningfulGames: 10,
161
+ fourPlayerGroups: true,
162
+ finalistCount: 8,
163
+ },
164
+ },
165
+ baseValue: 15.0,
166
+ tvaRating: 8.5,
167
+ tvaRanking: 12.0,
168
+ totalTVA: 20.5,
169
+ tgp: 0.80,
170
+ eventBoosterMultiplier: 1.0,
171
+ firstPlaceValue: 28.4,
172
+ },
173
+ });
174
+
175
+ console.log(`✓ Created ${await prisma.tournament.count()} tournaments`);
176
+
177
+ // Create tournament results
178
+ console.log('Creating tournament results...');
179
+
180
+ // World Championship results
181
+ await prisma.tournamentResult.createMany({
182
+ data: [
183
+ {
184
+ playerId: player1.id,
185
+ tournamentId: tournament1.id,
186
+ position: 1,
187
+ totalPoints: 411.84,
188
+ linearPoints: 41.18,
189
+ dynamicPoints: 370.66,
190
+ ageInDays: 0,
191
+ decayMultiplier: 1.0,
192
+ decayedPoints: 411.84,
193
+ efficiency: 85.5,
194
+ },
195
+ {
196
+ playerId: player2.id,
197
+ tournamentId: tournament1.id,
198
+ position: 2,
199
+ totalPoints: 298.45,
200
+ linearPoints: 41.18,
201
+ dynamicPoints: 257.27,
202
+ ageInDays: 0,
203
+ decayMultiplier: 1.0,
204
+ decayedPoints: 298.45,
205
+ efficiency: 72.5,
206
+ },
207
+ {
208
+ playerId: player3.id,
209
+ tournamentId: tournament1.id,
210
+ position: 3,
211
+ totalPoints: 215.32,
212
+ linearPoints: 41.18,
213
+ dynamicPoints: 174.14,
214
+ ageInDays: 0,
215
+ decayMultiplier: 1.0,
216
+ decayedPoints: 215.32,
217
+ efficiency: 65.2,
218
+ },
219
+ {
220
+ playerId: player4.id,
221
+ tournamentId: tournament1.id,
222
+ position: 5,
223
+ totalPoints: 125.18,
224
+ linearPoints: 41.18,
225
+ dynamicPoints: 84.00,
226
+ ageInDays: 0,
227
+ decayMultiplier: 1.0,
228
+ decayedPoints: 125.18,
229
+ efficiency: 48.3,
230
+ },
231
+ ],
232
+ });
233
+
234
+ // Spring Classics results
235
+ await prisma.tournamentResult.createMany({
236
+ data: [
237
+ {
238
+ playerId: player2.id,
239
+ tournamentId: tournament2.id,
240
+ position: 1,
241
+ totalPoints: 87.28,
242
+ linearPoints: 8.73,
243
+ dynamicPoints: 78.55,
244
+ ageInDays: 0,
245
+ decayMultiplier: 1.0,
246
+ decayedPoints: 87.28,
247
+ efficiency: 92.0,
248
+ },
249
+ {
250
+ playerId: player1.id,
251
+ tournamentId: tournament2.id,
252
+ position: 2,
253
+ totalPoints: 63.25,
254
+ linearPoints: 8.73,
255
+ dynamicPoints: 54.52,
256
+ ageInDays: 0,
257
+ decayMultiplier: 1.0,
258
+ decayedPoints: 63.25,
259
+ efficiency: 78.5,
260
+ },
261
+ {
262
+ playerId: player4.id,
263
+ tournamentId: tournament2.id,
264
+ position: 3,
265
+ totalPoints: 45.67,
266
+ linearPoints: 8.73,
267
+ dynamicPoints: 36.94,
268
+ ageInDays: 0,
269
+ decayMultiplier: 1.0,
270
+ decayedPoints: 45.67,
271
+ efficiency: 68.2,
272
+ },
273
+ {
274
+ playerId: player5.id,
275
+ tournamentId: tournament2.id,
276
+ position: 6,
277
+ totalPoints: 18.52,
278
+ linearPoints: 8.73,
279
+ dynamicPoints: 9.79,
280
+ ageInDays: 0,
281
+ decayMultiplier: 1.0,
282
+ decayedPoints: 18.52,
283
+ efficiency: 35.8,
284
+ },
285
+ ],
286
+ });
287
+
288
+ // Monthly League results
289
+ await prisma.tournamentResult.createMany({
290
+ data: [
291
+ {
292
+ playerId: player3.id,
293
+ tournamentId: tournament3.id,
294
+ position: 1,
295
+ totalPoints: 28.4,
296
+ linearPoints: 2.84,
297
+ dynamicPoints: 25.56,
298
+ ageInDays: 0,
299
+ decayMultiplier: 1.0,
300
+ decayedPoints: 28.4,
301
+ efficiency: 88.5,
302
+ },
303
+ {
304
+ playerId: player4.id,
305
+ tournamentId: tournament3.id,
306
+ position: 2,
307
+ totalPoints: 20.58,
308
+ linearPoints: 2.84,
309
+ dynamicPoints: 17.74,
310
+ ageInDays: 0,
311
+ decayMultiplier: 1.0,
312
+ decayedPoints: 20.58,
313
+ efficiency: 75.2,
314
+ },
315
+ {
316
+ playerId: player5.id,
317
+ tournamentId: tournament3.id,
318
+ position: 3,
319
+ totalPoints: 14.85,
320
+ linearPoints: 2.84,
321
+ dynamicPoints: 12.01,
322
+ ageInDays: 0,
323
+ decayMultiplier: 1.0,
324
+ decayedPoints: 14.85,
325
+ efficiency: 62.5,
326
+ },
327
+ ],
328
+ });
329
+
330
+ console.log(`✓ Created ${await prisma.tournamentResult.count()} tournament results`);
331
+
332
+ console.log('');
333
+ console.log('✅ Database seeded successfully!');
334
+ console.log('');
335
+ console.log('Summary:');
336
+ console.log(` - ${await prisma.player.count()} players`);
337
+ console.log(` - ${await prisma.tournament.count()} tournaments`);
338
+ console.log(` - ${await prisma.tournamentResult.count()} tournament results`);
339
+ }
340
+
341
+ main()
342
+ .catch((e) => {
343
+ console.error('Error seeding database:');
344
+ console.error(e);
345
+ process.exit(1);
346
+ })
347
+ .finally(async () => {
348
+ await prisma.$disconnect();
349
+ });