- parseRankingCriteria: parse natural-language criteria, returns ParsedRankingRule[]
- executeRanking: fetch+rank both categories, persist CONFIRMED RankingSnapshot
- quickRank: parse+execute in one step, persist QUICK RankingSnapshot
- listSnapshots: list snapshots for a round ordered by createdAt desc
- getSnapshot: fetch full snapshot by ID with NOT_FOUND guard
- All procedures use adminProcedure (SUPER_ADMIN, PROGRAM_ADMIN only)
- Casts to Prisma.InputJsonValue via unknown to satisfy strict TS
- Update listByStage query to include evaluation form criteriaJson and criterionScoresJson
- Add Advance column to individual assignments table showing YES/NO badge per submitted evaluation
- Create AdvancementSummaryCard component showing yes/no/pending vote counts with stacked bar
- Wire AdvancementSummaryCard into the EVALUATION round overview tab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add getMyProgress juryProcedure query to evaluationRouter: fetches all
assignments for the current juror in a round, tallies completed/total,
advance yes/no counts, per-project numeric scores and averages
- Create JurorProgressDashboard client component with progress bar, advance
badge summary, and collapsible per-submission score table
- Wire dashboard into jury round page, gated by configJson.showJurorProgressDashboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new 'advance' criterion type representing "should this project advance to the next round?". Only one advance criterion is allowed per form (button disabled once added). No weight, no condition fields, always required. Also updates the upsertForm Zod schema to accept the new type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add roles UserRole[] to User model with migration + backfill from existing role column
- Update auth JWT/session to propagate roles array with [role] fallback for stale tokens
- Update tRPC hasRole() middleware and add userHasRole() helper for inline role checks
- Update ~15 router inline checks and ~13 DB queries to use roles array
- Add updateRoles admin mutation with SUPER_ADMIN guard and priority-based primary role
- Add role switcher UI in admin sidebar and role-nav for multi-role users
- Remove redundant stats cards from round detail, add window dates to header banner
- Merge Members section into JuryProgressTable with inline cap editor and remove buttons
- Reorder round detail assignments tab: Progress > Score Dist > Assignments > Coverage > Jury Group
- Make score distribution fill full vertical height, reassignment history always open
- Add per-juror progress bars to admin dashboard ActiveRoundPanel for EVALUATION rounds
- Fix evaluation submit bug: use isSubmitting state instead of startMutation.isPending
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mail/transfer/reshuffle/redistribute icons to each juror row in Members card
- New redistributeJurorAssignments procedure: reassign all pending projects without dropping juror from group
- New DROPOUT_REASSIGNED email template with project names, deadline, and dropped juror context
- Update reassignDroppedJuror to send per-juror DROPOUT_REASSIGNED emails instead of generic BATCH_ASSIGNED
- Transfer dialog now shows all candidates with "Already assigned" / "At cap" labels instead of hiding them
- SQL script for prod DB insertion of new notification setting without seeding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add COI_REASSIGNED and MANUAL_REASSIGNED notification types with distinct
email templates, icons, and priorities
- COI declaration dialog now shows a confirmation step warning that the
project will be reassigned before submitting
- reassignAfterCOI now checks historical assignments (all rounds, audit logs)
to never assign the same project to a juror twice, and prefers jurors with
incomplete evaluations over those who have finished all their work
- Admin transfer (transferAssignments) sends per-juror MANUAL_REASSIGNED
notifications with actual project names instead of generic batch emails
- docker-entrypoint syncs notification settings on every deploy via upsert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite ctaButton to use td-background pattern (works in all clients
including Outlook, Gmail, Yahoo, Apple Mail) instead of VML/conditional
comments that broke link clicking in Outlook desktop
- Add plaintext fallback URL below every CTA button so users always have
a working link even if the button fails
- Add getBaseUrl() and ensureAbsoluteUrl() helpers in email.ts to
guarantee all email links are absolute https:// URLs
- Apply ensureAbsoluteUrl safety net in sendStyledNotificationEmail and
sendNotificationEmail so relative paths can never reach email templates
- Standardize all NEXTAUTH_URL fallbacks to https://portal.monaco-opc.com
(was inconsistently http://localhost:3000 or https://monaco-opc.com)
- Fix legacy notification.ts: wrong argument order in
sendJuryInvitationEmail (URL was passed as name parameter)
- Fix legacy notification.ts: missing NEXTAUTH_URL fallback for
evaluation reminder URL construction
- Change tooltip styling from red bg to white bg with black text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The getUploadUrl procedure generated a presigned PUT URL without first
checking that the mopc-learning bucket exists, causing all uploads to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add getTransferCandidates/transferAssignments procedures for targeted
assignment moves between jurors with TOCTOU guards and audit logging
- Add getOverCapPreview/redistributeOverCap for auto-redistributing
assignments when a juror's cap is lowered below their current load
- Add TransferAssignmentsDialog (2-step: select projects, pick destinations)
- Extend InlineMemberCap with over-cap detection and redistribute banner
- Extend getReassignmentHistory to show ASSIGNMENT_TRANSFER and CAP_REDISTRIBUTE events
- Learning hub: replace ResourceType/CohortLevel enums with accessJson JSONB,
add coverImageKey, resource detail pages for jury/mentor, shared renderer
- Migration: 20260221200000_learning_hub_overhaul
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Anthropic API:
- Add @anthropic-ai/sdk with adapter wrapping OpenAI-shaped interface
- Support Claude models (opus, sonnet, haiku) with extended thinking
- Auto-reset model on provider switch, JSON retry logic
- Add Claude model pricing to ai-usage tracker
- Update AI settings form with Anthropic provider option
- Add provider field to AIUsageLog for cross-provider cost tracking
Locale Settings Removal:
- Strip Localization tab from admin settings (mobile + desktop)
- Remove i18n settings from router and feature flags
- Remove LOCALIZATION from SettingCategory enum
- Keep franc document language detection intact
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rewrite diversity metrics: horizontal bar charts for ocean issues and
geographic distribution (replaces unreadable vertical/donut charts)
- Rewrite juror score heatmap: expandable table with score distribution
- Rewrite juror consistency: horizontal bar visual with juror names
- Merge filtering tabs into single screening view with per-project
AI reasoning and expandable rows
- Add project preview dialog for juror performance table
- Fix status breakdown for evaluation rounds (Fully/Partially/Not Reviewed)
- Show active round name instead of count on observer dashboard
- Move Global tab to last position, default to first round-specific tab
- Add 4-card stats layout for evaluation with reviews/project ratio
- Fix oceanIssue field (singular) and remove non-existent aiSummary
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor the observer reports page from a static 3-tab layout to a
dynamic tab system that adapts to each round type (INTAKE, FILTERING,
EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION). Adds a
persistent Global tab for edition-wide analytics, juror score heatmap,
expandable juror assignment rows, filtering screening bar, and
deliberation results with tie detection.
- Add 5 observer proxy procedures to analytics router
- Create JurorScoreHeatmap, ExpandableJurorTable, FilteringScreeningBar
- Create 8 round-type tab components + GlobalAnalyticsTab
- Reduce reports page from 914 to ~190 lines (thin dispatcher)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The round breakdown was showing 200% for active rounds (assignments/projects)
and 0% for closed rounds. Now correctly computes evaluations/assignments for
active rounds and shows 100% for closed/archived rounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getStatusBreakdown now uses ProjectRoundState when a specific round is selected
(fixes donut showing all "Eligible")
- Filter out boolean/section_header criteria from getCriteriaScores
(removes "Move to the Next Stage?" from bar chart)
- Replace 6 insight tiles with Top Countries horizontal bar chart
- Add round-level state labels/colors to chart-theme
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When scoringMode is not 'binary', binaryDecision is null even though
jurors answer boolean criteria (e.g. "Do you recommend?"). Now falls
back to checking boolean values in criterionScoresJson. Hides the
recommendation line entirely when no boolean data exists.
Fixed in both analytics.ts (observer) and project.ts (admin).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix dashboard default round selection to target active round instead of R1
- Move edition selector from dashboard header to hamburger menu via shared context
- Add observer-friendly status labels (Not Reviewed / Under Review / Reviewed)
- Fix pipeline completion: closed rounds show 100%, cap all rates at 100%
- Round badge on projects list shows furthest round reached
- Hide scores/evals for projects with zero evaluations
- Enhance project detail round history with pass/reject indicators from ProjectRoundState
- Remove irrelevant fields (Org Type, Budget, Duration) from project detail
- Clickable juror workload with expandable project assignments
- Humanize activity feed with icons and readable messages
- Fix jurors table: responsive card layout on mobile
- Fix criteria chart: horizontal bars for readable labels on mobile
- Animate hamburger menu open/close with CSS grid transition
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>
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>
- 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>
- 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>