feat(matching): add auto-matching system for recording partners
Implement algorithm to match dancers with recorders based on: - Heat collision avoidance (division + competitionType + heatNumber) - Buffer time (1 heat after dancing before can record) - Location preference (same city > same country > anyone) - Max 3 recordings per person - Opt-out support (falls to bottom of queue) New API endpoints: - PUT /events/:slug/registration-deadline - PUT /events/:slug/recorder-opt-out - POST /events/:slug/run-matching - GET /events/:slug/match-suggestions - PUT /events/:slug/match-suggestions/:id/status Database changes: - Event: registrationDeadline, matchingRunAt - EventParticipant: recorderOptOut - RecordingSuggestion: new model for match suggestions
This commit is contained in:
@@ -59,29 +59,35 @@ model User {
|
||||
ratingsReceived Rating[] @relation("RatedRatings")
|
||||
eventParticipants EventParticipant[]
|
||||
heats EventUserHeat[]
|
||||
recordingAssignments RecordingSuggestion[] @relation("RecorderAssignments")
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// Events table (dance events from worldsdc.com)
|
||||
model Event {
|
||||
id Int @id @default(autoincrement())
|
||||
slug String @unique @default(cuid()) @db.VarChar(50)
|
||||
name String @db.VarChar(255)
|
||||
location String @db.VarChar(255)
|
||||
startDate DateTime @map("start_date") @db.Date
|
||||
endDate DateTime @map("end_date") @db.Date
|
||||
worldsdcId String? @unique @map("worldsdc_id") @db.VarChar(100)
|
||||
participantsCount Int @default(0) @map("participants_count")
|
||||
description String? @db.Text
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
id Int @id @default(autoincrement())
|
||||
slug String @unique @default(cuid()) @db.VarChar(50)
|
||||
name String @db.VarChar(255)
|
||||
location String @db.VarChar(255)
|
||||
startDate DateTime @map("start_date") @db.Date
|
||||
endDate DateTime @map("end_date") @db.Date
|
||||
worldsdcId String? @unique @map("worldsdc_id") @db.VarChar(100)
|
||||
participantsCount Int @default(0) @map("participants_count")
|
||||
description String? @db.Text
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Auto-matching configuration
|
||||
registrationDeadline DateTime? @map("registration_deadline") // When registration closes
|
||||
matchingRunAt DateTime? @map("matching_run_at") // When auto-matching was last run
|
||||
|
||||
// Relations
|
||||
chatRooms ChatRoom[]
|
||||
matches Match[]
|
||||
participants EventParticipant[]
|
||||
checkinToken EventCheckinToken?
|
||||
userHeats EventUserHeat[]
|
||||
chatRooms ChatRoom[]
|
||||
matches Match[]
|
||||
participants EventParticipant[]
|
||||
checkinToken EventCheckinToken?
|
||||
userHeats EventUserHeat[]
|
||||
recordingSuggestions RecordingSuggestion[]
|
||||
|
||||
@@map("events")
|
||||
}
|
||||
@@ -186,6 +192,7 @@ model EventParticipant {
|
||||
userId Int @map("user_id")
|
||||
eventId Int @map("event_id")
|
||||
competitorNumber Int? @map("competitor_number") // Bib number - one per user per event
|
||||
recorderOptOut Boolean @default(false) @map("recorder_opt_out") // Opt-out from being a recorder
|
||||
joinedAt DateTime @default(now()) @map("joined_at")
|
||||
|
||||
// Relations
|
||||
@@ -236,10 +243,11 @@ model EventUserHeat {
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
||||
division Division @relation(fields: [divisionId], references: [id])
|
||||
competitionType CompetitionType @relation(fields: [competitionTypeId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
||||
division Division @relation(fields: [divisionId], references: [id])
|
||||
competitionType CompetitionType @relation(fields: [competitionTypeId], references: [id])
|
||||
recordingSuggestion RecordingSuggestion?
|
||||
|
||||
// Constraint: Cannot have same role in same division+competition type
|
||||
@@unique([userId, eventId, divisionId, competitionTypeId, role])
|
||||
@@ -247,3 +255,23 @@ model EventUserHeat {
|
||||
@@index([eventId])
|
||||
@@map("event_user_heats")
|
||||
}
|
||||
|
||||
// Recording suggestions from auto-matching algorithm
|
||||
model RecordingSuggestion {
|
||||
id Int @id @default(autoincrement())
|
||||
eventId Int @map("event_id")
|
||||
heatId Int @unique @map("heat_id") // One suggestion per heat
|
||||
recorderId Int? @map("recorder_id") // NULL if no match found
|
||||
status String @default("pending") @db.VarChar(20) // 'pending', 'accepted', 'rejected', 'not_found'
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
||||
heat EventUserHeat @relation(fields: [heatId], references: [id], onDelete: Cascade)
|
||||
recorder User? @relation("RecorderAssignments", fields: [recorderId], references: [id])
|
||||
|
||||
@@index([eventId])
|
||||
@@index([recorderId])
|
||||
@@map("recording_suggestions")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user