@tremor/react@3.18.7 requires react@^18 but project uses react@19.
Adding .npmrc with legacy-peer-deps=true and copying it in Dockerfiles
so npm ci resolves correctly. Also fix implicit any in seed file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These packages are imported by new chart and toggle components but were
never added to package.json, causing the observer reports page to crash
client-side on load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New page at /observer/projects/[projectId] showing project info,
documents grouped by round requirements, and jury evaluations with
click-through to full review details. Dashboard table rows now link
to project detail. Also cleans up redundant programName prefixes
and fixes chart edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
notifyAdmins was using BATCH_ASSIGNED notification type, which triggers
the juror assignment email template ('X Projects Assigned'). Admins
received confusing emails that looked like they were assigned projects.
Changed to EVALUATION_MILESTONE type for admin-facing reshuffle/COI
notifications. Also included top receivers in admin notification message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audit log now renders JUROR_DROPOUT_RESHUFFLE and COI_REASSIGNMENT
entries as formatted tables with resolved juror names instead of raw
JSON with opaque IDs. Uses new user.resolveNames endpoint to batch-
lookup user IDs. Also adds missing action types to the filter dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug fix: reassignDroppedJuror, reassignAfterCOI, and getSuggestions all
fell back to querying ALL JURY_MEMBER users globally when the round had
no juryGroupId. This caused projects to be assigned to jurors who are no
longer active in the jury pool. Now scopes to jury group members when
available, otherwise to jurors already assigned to the round.
Also adds getSuggestions jury group scoping (matching runAIAssignmentJob).
New feature: Reassignment History panel on admin round page (collapsible)
shows per-project detail of where dropped/COI-reassigned projects went.
Reconstructs retroactive data from audit log timestamps + MANUAL
assignments for pre-fix entries. Future entries log full move details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Disable enableSlices on ResponsiveLine with single data point (causes
null reference in Nivo internal slice computation)
- Add null check for slice.points[0] in timeline tooltip
- Guard ResponsivePie from empty data array in diversity metrics
- Add fallback for scoreDistribution.distribution on both
observer and admin reports pages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reassignDroppedJuror flow was missing a key step — after
reshuffling unsubmitted projects to other jurors, the dropped juror
was not removed from the jury group. This meant they could be
re-assigned in future assignment runs. Now deletes the JuryGroupMember
record after reshuffle, logs removal in audit, and updates the
confirmation dialog to reflect the full action.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 9 chart components now have early-return null/empty checks before
calling .map() on data props. The diversity-metrics chart guards all
nested array fields (byCountry, byCategory, byOceanIssue, byTag).
Analytics backend guards p.tags in getDiversityMetrics. This prevents
any "Cannot read properties of null (reading 'map')" crashes even if
upstream data shapes are unexpected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Evaluation submit: The requireAllCriteriaScored validation was
querying findFirst({ roundId, isActive: true }) to get the form
criteria, instead of using the evaluation's stored formId. If an
admin ever re-saved the evaluation form (creating a new version
with new criterion IDs), jurors who started evaluating before the
re-save had scores keyed to old IDs that didn't match the new
form. Now uses evaluation.form (the form assigned at start time).
2. Observer reports page: Two .map() calls on p.stages lacked null
guards, causing "Cannot read properties of null (reading 'map')"
crash. Added (p.stages || []) guards matching the pattern already
used in CrossStageTab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. COI audit log: The declareCOI mutation always logged action
'COI_DECLARED' regardless of whether the user clicked "No Conflict"
or "Yes, I Have a Conflict". Now uses 'COI_NO_CONFLICT' when
hasConflict is false, showing "confirmed no conflict of interest"
in the audit trail.
2. Evaluation submission: The requireAllCriteriaScored validation
only accepted numeric values (typeof === 'number'), but boolean
criteria (yes/no questions) store true/false. This caused jurors
to get "Missing scores for criteria: criterion-xxx" errors even
after completing all fields. Now correctly validates boolean
criteria with typeof === 'boolean'. Also improved the error
message to show criterion labels instead of cryptic IDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a juror declares COI, the system now automatically:
- Finds an eligible replacement juror (not at capacity, no COI, not already assigned)
- Deletes the conflicted assignment and creates a new one
- Notifies the replacement juror and admins
- Load-balances by picking the juror with fewest current assignments
Also adds:
- "Reassign (COI)" action in assignment table dropdown with COI badge indicator
- Admin "Reassign to another juror" in COI review now triggers actual reassignment
- Per-juror notify button is now always visible (not just on hover)
- reassignCOI admin procedure for retroactive manual reassignment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a mail icon on hover for each juror row in the Jury Progress
table, allowing admins to send assignment notifications to individual
jurors instead of only bulk-notifying all at once.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Relative linkUrl paths (e.g. /jury/competitions) were passed as-is to
email templates, causing email clients to interpret them as local file
protocols (x-webdoc:// on macOS). Now prepends NEXTAUTH_URL to any
relative path before sending.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Send Reminders button now works: added sendManualReminders() that bypasses
cron-specific window/deadline/dedup guards so admin can send immediately
- Added Notify Jurors button that sends direct BATCH_ASSIGNED emails to all
jurors with assignments (not dependent on NotificationEmailSetting config)
- Fixed checkbox component: default border is now neutral grey (border-input),
red border (border-primary) only applied when checked
- Widened Add Assignment dialog from max-w-2xl to max-w-3xl to prevent overflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "537 projects" count was summing projectRoundStates across all
rounds, so a project in 3 rounds was counted 3 times. Now queries
distinct projectIds across all competition rounds to show the actual
unique project count (214).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The status summary badges (Eligible, Rejected, Assigned, etc.) were
computed from only the current page's projects. Now uses a groupBy
query on the same filters to return statusCounts for all matching
projects across all pages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- specialAward.setEligibility: add ensureUserExists() guard and use Prisma
connect syntax to prevent FK violation on stale session user IDs
- specialAward.confirmShortlist: same ensureUserExists() guard for confirmedBy
- Round projects table: add Country column showing project origin
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix bug: AI assignment router read non-existent `(m as any).maxAssignments`
instead of the actual schema field `m.maxAssignmentsOverride`
- Wire `jurorLimits` record into AI assignment constraints so per-juror
caps are respected during both AI scoring and algorithmic assignment
- Add inline editable cap in jury members table (click to edit, blur/enter
to save, empty = no cap / use group default)
- Add inline editable cap badges on round page member list so admins can
set caps right from the assignment workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of 10 sequential GPT calls (which timeout with GPT-5.1 on 99
projects), use a two-phase approach:
Phase 1 - AI Scoring: ONE API call asks GPT to score each juror's
affinity for all projects, returning a compact preference matrix with
expertise match scores and reasoning.
Phase 2 - Algorithm: Uses AI scores as the preference input to a
balanced assignment algorithm that assigns N reviewers per project,
enforcing even workload distribution, respecting per-juror caps, and
filling coverage gaps.
Benefits:
- Single API call eliminates timeout issues
- AI provides expertise-aware scoring, algorithm ensures balance
- Truncated response handling (JSON repair) for resilience
- Falls back to tag-based algorithm if AI fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cap maxTokens at 12000 (was unlimited dynamic calc that could exceed model limits)
- Replace massive EXISTING array with compact CURRENT_JUROR_LOAD counts and
ALREADY_ASSIGNED per-project map (keeps prompt small across batches)
- Add coverage gap-filler: algorithmically fills projects below required reviews
- Show error state inline on page when AI fails (red banner with message)
- Add server-side logging for debugging assignment flow
- Reduce batch size to 10 projects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problems:
- GPT only generated 1 reviewer per project despite N being required
- maxTokens (4000) too small for N×projects assignment objects
- No fallback when GPT under-assigned
Fixes:
- System prompt now explicitly explains multiple reviewers per project
with concrete example showing 3 different juror_ids per project
- User prompt includes REVIEWS_PER_PROJECT, EXPECTED_OUTPUT_SIZE
- maxTokens dynamically calculated: expectedAssignments × 200 + 500
- Reduced batch size from 15 to 10 (fewer projects per GPT call)
- Added fillCoverageGaps() post-processor: algorithmically assigns
least-loaded jurors to any project below required coverage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: batches of 15 projects were processed independently -
GPT didn't see assignments from previous batches, so expert jurors
got assigned 18-22 projects while others got 4-5.
Fixes:
- Track cumulative assignments across batches (feed to each batch)
- Calculate ideal target per juror and communicate to GPT
- Add post-processing rebalancer that enforces hard caps and
redistributes excess assignments to least-loaded jurors
- Calculate sensible default max cap when not configured
- Reweight prompt: workload balance 50%, expertise 35%, diversity 15%
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows warning with juror names when they have no expertise tags or bio,
so admin can ask them to onboard before committing assignments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite AIRecommendationsDisplay: show project titles, per-project
checkboxes, Apply and Mark as Passed button with batch transition
- Show AI jury assignment reasoning directly in rows (not tooltip)
- Fix unassigned projects badge using requiredReviews instead of hardcoded 3
- Add aiParseFiles to EvaluationConfigSchema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add general settings fields (startupAdvanceCount, conceptAdvanceCount,
notifyOnEntry, notifyOnAdvance) to ALL round config schemas, not just
FilteringConfig. Zod was stripping them on save for other round types.
- Replace floating save bar with error-only bar since autosave handles
all config persistence (800ms debounce)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Server: presigned GET URLs now include Content-Disposition: attachment header
when forDownload=true, triggering native browser downloads on all platforms
- Download button uses window.location.href with attachment URL (works on iOS Safari)
- Bulk download uses hidden iframes instead of fetch+blob
- Fix COI gate: getCOIStatus returns null (not undefined) when undeclared,
so `!== undefined` was always true — changed to `!= null`
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Integrate COI declaration dialog into jury evaluate page (blocks evaluation until declared)
- Add COI review section to admin round page with clear/reassign/note actions
- Fix mobile: remove inline preview (viewport too small), add labeled buttons
- Fix iOS: open-in-new-tab uses synchronous window.open to avoid popup blocker
- Fix iOS: download falls back to direct link if fetch+blob fails (CORS/Safari)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace MultiWindowDocViewer with FileViewer for inline previews (PDF/image/video/Office)
- Fix cross-origin download using fetch+blob instead of <a download>
- Show Startup/Business Concept badge on jury project detail + evaluate pages
- Add admin resetEvaluation procedure with audit logging
- Add dropdown menu on admin assignment rows with Reset Evaluation + Delete
- Make file action buttons responsive on mobile (separate row below file info)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix project documents not displaying on jury project page (rewrote MultiWindowDocViewer to use file.listByProject)
- Add working download/preview for project files via presigned URLs
- Display project tags on jury project detail page
- Add autosave for evaluation drafts (debounced 3s + save on unmount/beforeunload)
- Support mixed criterion types: numeric scores, yes/no booleans, text responses, section headers
- Replace inline criteria editor with rich EvaluationFormBuilder on admin round page
- Remove COI dialog from evaluation page
- Update AI summary service to handle boolean/text criteria (yes/no counts, text synthesis)
- Update EvaluationSummaryCard to show boolean criteria bars and text responses
- Add evaluation detail sheet on admin project page (click juror row to view full scores + feedback)
- Add Recent Evaluations dashboard widget showing latest jury reviews
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Round open/close scheduling now runs as a 60s setInterval inside the
app process (via instrumentation.ts register hook) instead of needing
an external crontab. Removed the /api/cron/round-scheduler endpoint.
- DRAFT rounds auto-activate when windowOpenAt arrives
- ACTIVE rounds auto-close when windowCloseAt passes
- Uses existing activateRound/closeRound from round-engine
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New /api/cron/round-scheduler endpoint that:
- Activates DRAFT rounds whose windowOpenAt has arrived
- Closes ACTIVE rounds whose windowCloseAt has passed
- Uses existing activateRound/closeRound from round-engine
- Protected by CRON_SECRET header like other cron endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
finalizeResults was finding nextRound but never creating
ProjectRoundState entries — projects got set to ELIGIBLE but
were not placed into the next round. Now creates entries with
skipDuplicates so it's safe to re-run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- finalizeResults now queries confirmed SEPARATE_POOL shortlist and
excludes those projects from the main pool ELIGIBLE set
- Finalize confirmation dialog shows breakdown: main pool vs award-routed
- Finalize toast includes award-routed count
- Audit log records routedToAwards count
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Stats cards show new 'Award Track' card with count of confirmed
SEPARATE_POOL shortlisted projects
- Passed card shows breakdown (main + award) when awards are routed
- Results table shows award badge on projects routed to award tracks
- getResults query includes confirmed award eligibility data per project
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auth layout verifies user exists in DB before redirecting to dashboard,
breaking infinite loop for deleted accounts with stale sessions
- Jury/Mentor layouts handle null user (deleted) by redirecting to login
- Filtering stats cards and result list now use effective outcome
(finalOutcome ?? outcome) instead of raw AI outcome
- Award eligibility evaluation includes admin-overridden PASSED projects
- Award shortlist reasoning column shows full text instead of truncating
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduced badge size (text-[11px], h-5, tighter padding/gaps) and capped
the selected tags container to max-h-20 with overflow scroll so they
no longer push the rest of the form off-screen on mobile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>