diff --git a/backend/prisma/migrations/20251130122800_add_origin_run_id_to_suggestions/migration.sql b/backend/prisma/migrations/20251130122800_add_origin_run_id_to_suggestions/migration.sql new file mode 100644 index 0000000..093a6ac --- /dev/null +++ b/backend/prisma/migrations/20251130122800_add_origin_run_id_to_suggestions/migration.sql @@ -0,0 +1,13 @@ +-- Add origin_run_id column to recording_suggestions +ALTER TABLE "recording_suggestions" ADD COLUMN "origin_run_id" INTEGER; + +-- Add foreign key to matching_runs +ALTER TABLE "recording_suggestions" + ADD CONSTRAINT "recording_suggestions_origin_run_id_fkey" + FOREIGN KEY ("origin_run_id") REFERENCES "matching_runs"("id") + ON DELETE SET NULL ON UPDATE CASCADE; + +-- Simple index to support joins by origin_run_id +CREATE INDEX IF NOT EXISTS "recording_suggestions_origin_run_id_idx" + ON "recording_suggestions"("origin_run_id"); + diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 8731f46..b7ccefc 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -104,6 +104,30 @@ router.get('/events/:slug/matching-runs', authenticate, async (req, res, next) = }, }); + // Aggregate fresh stats per run from recording_suggestions (origin_run_id) + // Cheap and valuable: shows actual created pairs in this run. + if (runs.length > 0) { + const runIds = runs.map(r => r.id); + // Single SQL query for all listed runs + const placeholders = runIds.join(','); + const aggRows = await prisma.$queryRawUnsafe( + `SELECT origin_run_id AS "originRunId", + COUNT(*)::int AS "totalSuggestions", + COUNT(*) FILTER (WHERE recorder_id IS NOT NULL)::int AS "assignedCount", + COUNT(*) FILTER (WHERE status = 'not_found')::int AS "notFoundCount" + FROM recording_suggestions + WHERE event_id = ${event.id} AND origin_run_id IN (${placeholders}) + GROUP BY origin_run_id` + ); + const aggByRun = new Map(aggRows.map(r => [r.originRunId, r])); + for (const r of runs) { + const agg = aggByRun.get(r.id) || { totalSuggestions: 0, assignedCount: 0, notFoundCount: 0 }; + r.totalSuggestions = agg.totalSuggestions; + r.assignedCount = agg.assignedCount; + r.aggregatedNotFoundCount = agg.notFoundCount; // keep original notFoundCount for backward compat + } + } + res.json({ success: true, count: runs.length, data: runs }); } catch (error) { next(error); diff --git a/frontend/src/components/events/MatchingRunsSection.jsx b/frontend/src/components/events/MatchingRunsSection.jsx index df1f213..6c6e6bb 100644 --- a/frontend/src/components/events/MatchingRunsSection.jsx +++ b/frontend/src/components/events/MatchingRunsSection.jsx @@ -101,6 +101,7 @@ export default function MatchingRunsSection({ slug }) {