@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.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/index.cjs +573 -0
- package/dist/index.d.cts +608 -0
- package/dist/index.d.ts +608 -0
- package/dist/index.js +500 -0
- package/package.json +85 -0
- package/prisma/migrations/20251231075738_init/migration.sql +112 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +123 -0
- package/prisma/seed.ts +349 -0
|
@@ -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
|
+
});
|