feat(matching): prevent auto suggestions when manual match exists + comprehensive test scenarios
Matching Service: - Fetch manual matches at start of runMatching() (suggestionId: null) - Build manualBlockedPairs set with both directions (A:B and B:A) - Skip recorder candidates if manual match exists between dancer and recorder - Ensures no duplicate matches on /matches page - Manual match = user-controlled, algorithm respects user decisions Documentation (docs/TODO.md): - Add comprehensive matching system test scenarios (S1-S16) - Document 4 critical gaps (P0): ratings/stats, admin middleware, participant validation - Document 3 high priority items (P1): cleanup conflicts, rate limiting, notifications - Document 6 medium priority items (P2): audit endpoints, zombie cleanup, reminders - List 11 edge cases for team discussion (E1-E11) - Clear priority ranking and questions for team Critical Findings: - recordingsDone/recordingsReceived fields exist but NEVER updated (fairness broken!) - Admin endpoints lack security middleware - Inconsistent event participant validation across endpoints Test Coverage: - S1-S7: Implemented (basic flow, collisions, limits, manual vs auto) - S10: NOT IMPLEMENTED - ratings/stats system (CRITICAL!) - S11: Partially implemented - audit trail exists, API endpoints missing - S14: Partially implemented - recorder-only auth works, admin middleware missing - S15-S16: NOT IMPLEMENTED - security, notifications
This commit is contained in:
@@ -273,6 +273,23 @@ async function runMatching(eventId) {
|
||||
// Build division-to-slot map from schedule config
|
||||
const divisionSlotMap = buildDivisionSlotMap(event?.scheduleConfig);
|
||||
|
||||
// 0a. Get manual matches - we won't create auto suggestions for these pairs
|
||||
const manualMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
eventId,
|
||||
suggestionId: null, // Manual matches don't have a suggestionId
|
||||
status: { in: ['pending', 'accepted', 'completed'] },
|
||||
},
|
||||
select: { user1Id: true, user2Id: true },
|
||||
});
|
||||
|
||||
// Build set of blocked pairs (both directions)
|
||||
const manualBlockedPairs = new Set();
|
||||
for (const m of manualMatches) {
|
||||
manualBlockedPairs.add(`${m.user1Id}:${m.user2Id}`);
|
||||
manualBlockedPairs.add(`${m.user2Id}:${m.user1Id}`); // both directions
|
||||
}
|
||||
|
||||
// 1. Get all participants with their heats and user info
|
||||
const participants = await prisma.eventParticipant.findMany({
|
||||
where: { eventId },
|
||||
@@ -416,6 +433,9 @@ async function runMatching(eventId) {
|
||||
// Skip self
|
||||
if (recorder.userId === dancer.userId) continue;
|
||||
|
||||
// Skip if manual match exists between dancer and recorder
|
||||
if (manualBlockedPairs.has(`${dancer.userId}:${recorder.userId}`)) continue;
|
||||
|
||||
// Check assignment limit
|
||||
const currentCount = recorderAssignmentCount.get(recorder.userId) || 0;
|
||||
if (currentCount >= MAX_RECORDINGS_PER_PERSON) continue;
|
||||
|
||||
Reference in New Issue
Block a user