Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m23s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m23s
Phase 1 — Critical bugs: - Fix deliberation participant selection (wire jury group query) - Fix reports "By Round" tab (inline content instead of 404 route) - Fix messages "Sent History" (add message.sent procedure, wire tab) - Add missing fields to competition award form (criteriaText, maxRankedPicks) - Wire LiveControlPanel buttons (cursor, voting, scores) - Fix ResultLockControls empty snapshot (fetch actual data before lock) - Fix SubmissionWindowManager losing fields on edit Phase 2 — Backend fixes: - Remove write-in-query from specialAward.get - Fix award eligibility job overwriting manual shortlist overrides - Fix filtering startJob deleting all prior results (defer cleanup to post-success) - Tighten access control: protectedProcedure → adminProcedure on 8 procedures - Add audit logging to deliberation mutations - Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete Phase 3 — Auto-refresh: - Add refetchInterval to 15+ admin pages/components (10s–30s) - Fix AI job polling: derive speed from job status for all viewers Phase 4 — Dead code cleanup: - Delete unused command-palette, pdf-report, admin-page-transition - Remove dead subItems sidebar code, unused GripVertical import - Replace redundant isGenerating state with mutation.isPending - Add Role column to jury members table - Remove misleading manual mentor assignment stub Phase 5 — UX improvements: - Fix rounds page single-competition assumption (add selector) - Remove raw UUID fallback in deliberation config - Fix programs page "Stage" → "Round" terminology Phase 6 — Backend hardening: - Complete logAudit calls (add prisma, ipAddress, userAgent) - Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear) - Batch user.bulkCreate writes (assignments, jury memberships, intents) - Remove any casts from deliberation service (typed PrismaClient + TransactionClient) - Fix stale DeliberationStatus enum values blocking build 40 files changed, 1010 insertions(+), 612 deletions(-) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -181,7 +181,7 @@ export async function processEligibilityJob(
|
||||
}
|
||||
})
|
||||
|
||||
// Upsert eligibilities
|
||||
// Upsert eligibilities — preserve manual overrides and shortlist status
|
||||
await prisma.$transaction(
|
||||
eligibilities.map((e) =>
|
||||
prisma.awardEligibility.upsert({
|
||||
@@ -200,26 +200,54 @@ export async function processEligibilityJob(
|
||||
aiReasoningJson: e.aiReasoningJson ?? undefined,
|
||||
},
|
||||
update: {
|
||||
eligible: e.eligible,
|
||||
method: e.method as 'AUTO' | 'MANUAL',
|
||||
// Only update AI-computed fields; DO NOT reset overriddenBy,
|
||||
// overriddenAt, shortlisted, confirmedAt, confirmedBy — those
|
||||
// reflect admin decisions that must survive re-runs.
|
||||
qualityScore: e.qualityScore,
|
||||
aiReasoningJson: e.aiReasoningJson ?? undefined,
|
||||
overriddenBy: null,
|
||||
overriddenAt: null,
|
||||
shortlisted: false,
|
||||
confirmedAt: null,
|
||||
confirmedBy: null,
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
// For records without manual override, sync the eligible/method fields
|
||||
const nonOverridden = await prisma.awardEligibility.findMany({
|
||||
where: { awardId, overriddenBy: null },
|
||||
select: { projectId: true },
|
||||
})
|
||||
const nonOverriddenIds = new Set(nonOverridden.map((r) => r.projectId))
|
||||
|
||||
if (nonOverriddenIds.size > 0) {
|
||||
await prisma.$transaction(
|
||||
eligibilities
|
||||
.filter((e) => nonOverriddenIds.has(e.projectId))
|
||||
.map((e) =>
|
||||
prisma.awardEligibility.update({
|
||||
where: {
|
||||
awardId_projectId: { awardId, projectId: e.projectId },
|
||||
},
|
||||
data: {
|
||||
eligible: e.eligible,
|
||||
method: e.method as 'AUTO' | 'MANUAL',
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Auto-shortlist top N eligible projects by qualityScore
|
||||
// Only auto-shortlist records that aren't already manually shortlisted
|
||||
const shortlistSize = award.shortlistSize ?? 10
|
||||
const alreadyShortlisted = await prisma.awardEligibility.findMany({
|
||||
where: { awardId, shortlisted: true, overriddenBy: { not: null } },
|
||||
select: { projectId: true },
|
||||
})
|
||||
const manuallyShortlistedIds = new Set(alreadyShortlisted.map((r) => r.projectId))
|
||||
|
||||
const topEligible = eligibilities
|
||||
.filter((e) => e.eligible && e.qualityScore != null)
|
||||
.filter((e) => e.eligible && e.qualityScore != null && !manuallyShortlistedIds.has(e.projectId))
|
||||
.sort((a, b) => (b.qualityScore ?? 0) - (a.qualityScore ?? 0))
|
||||
.slice(0, shortlistSize)
|
||||
.slice(0, Math.max(0, shortlistSize - manuallyShortlistedIds.size))
|
||||
|
||||
if (topEligible.length > 0) {
|
||||
await prisma.$transaction(
|
||||
|
||||
Reference in New Issue
Block a user