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>
- 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>
- 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>
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>
Projects with no files are now automatically rejected before AI
screening runs, regardless of whether a DOCUMENT_CHECK rule is
configured. This prevents the AI from incorrectly passing projects
that have no supporting documents.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The overriddenBy FK to User was failing when the session contained a
stale user ID (e.g. after database reseed). Added ensureUserExists()
guard to all override/reinstate mutations and switched single-record
updates to use Prisma connect syntax for safer FK resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Award shortlist:
- Expandable reasoning text (click to toggle, hover hint)
- Bulk select/deselect all checkbox in header
- Top N projects highlighted with amber background
- New bulkToggleShortlisted backend mutation
Invite link expiry:
- New "Invitation Link Expiry (hours)" field in Security Settings
- Reads from systemSettings `invite_link_expiry_hours` (default 72h / 3 days)
- Email template dynamically shows "X hours" or "X days" based on setting
- All 3 invite paths (bulk create, single invite, bulk resend) use setting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Re-evaluate button was producing fewer eligible results because
the standalone job sent minimal project data (title, description, tags)
while the integrated filtering pass sent full data (files, team, institution).
- Fetch rich project data in award-eligibility-job (files, team, institution, etc.)
- Relax AI prompt to be inclusive like the integrated pass — strong primary
criterion fit is sufficient, don't require all dimensions above 0.5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Awards created from /admin/awards/new only sent programId, leaving
competitionId null. The edit page's source round dropdown was empty
because it depended on competitionId to fetch competition rounds.
- create mutation: auto-resolve competitionId from program's latest competition
- update mutation: backfill competitionId on save if missing
- get query: backfill competitionId on read for legacy awards
- edit page: use award.competition.rounds directly instead of separate query
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single AI call now evaluates both screening criteria AND award eligibility.
Awards with useAiEligibility + criteriaText are appended to the system prompt,
AI returns award_matches per project, results auto-populate AwardEligibility
and auto-shortlist top-N. Re-running filtering clears and re-evaluates awards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Rounds tab to award detail page with create/list/delete functionality
- Add "Entry point" badge on first award round (confirmShortlist routes here)
- Fix round detail back-link to navigate to parent award when specialAwardId set
- Filter award rounds out of competition round list
- Add specialAwardId to competition getById round select
- Warn on confirmShortlist when no award rounds exist (SEPARATE_POOL mode)
- Remove auto-tag rules from award config, edit page, router, and AI service
- Fix competitionId not passed when creating awards from competition context
- Add AUTO_FILTER quality threshold to AI filtering dashboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add filtering results summary card on round Overview tab with pass/fail/flag
counts and color-coded progress bar (polls every 5s)
- Auto-delete previous filtering results when re-running so new ones stream in
- Rename BUSINESS_CONCEPT to "Concept" in filtering results to prevent overflow
- Fix config save race condition where toggling switches (aiParseFiles, advance
counts) would revert: pendingSaveRef cleared before refetch completed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Part 1 - Bug Fixes:
- Fix toProjectWithRelations() stripping file fields needed by AI (detectedLang, textContent, etc.)
- Fix parseAIData() reading flat when aiScreeningJson is nested under rule ID
- Fix getAIConfidenceScore() with same nesting issue (always returned 0)
Part 2 - Special Award Track Integration:
- Add shortlistSize to SpecialAward, qualityScore/shortlisted/confirmed fields to AwardEligibility
- Add specialAwardId to Round for award-owned rounds
- Update AI eligibility service to return qualityScore (0-100) for ranking
- Update eligibility job with filteringRoundId scoping and auto-shortlist top N
- Add 8 new specialAward router procedures (listForRound, runEligibilityForRound,
listShortlist, toggleShortlisted, confirmShortlist, listRounds, createRound, deleteRound)
- Create award-shortlist.tsx component with ranked table, shortlist checkboxes, confirm dialog
- Add "Special Award Tracks" section to filtering dashboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add aiPreview mutation with full project/juror data (bios, descriptions,
documents, categories, ocean issues, countries, team sizes)
- Increase AI description limit from 300 to 2000 chars for richer context
- Update GPT system prompt to use all available data fields
- Add mode toggle (AI default / Algorithm fallback) in assignment preview
- Lift AI mutation to parent page for background generation persistence
- Show visual indicator on page while AI generates (spinner + progress card)
- Toast notification with "Review" action when AI completes
- Staggered reveal animation for assignment results (streaming feel)
- Fix assignment balance with dynamic penalty (25pts per existing assignment)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace bare summary stats with full interactive assignment preview:
- Assignments grouped by juror with collapsible cards
- Per-assignment detail: match score, tags, reasoning, policy warnings
- Remove individual assignments with hover X button
- Inline add projects per juror + global juror/project picker
- No cap enforcement on manual adds (admin override)
- Track manual additions and removals with badge indicators
- Include user details in round.getById jury group members query
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When closing round N, any active rounds with lower sortOrder in the
same competition are automatically closed. Each cascade closure is
recorded in DecisionAuditLog with closedBy: 'cascade' reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix assignments page header overflow on mobile (flex-wrap)
- Hide 'Back to Dashboard' button on mobile (logo tap navigates home)
- Make logo/brand text clickable to navigate to role dashboard
- Snap windowOpenAt to now when manually activating a round early
- Gate Compare Projects cards behind jury_compare_enabled setting (defaults off)
- Expose jury_compare_enabled in getFeatureFlags tRPC procedure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch COI dialog from Dialog to AlertDialog (non-dismissible)
- Replace number inputs with sliders + rating buttons for criteria/global scores
- Redesign jury dashboard stat cards: compact strip on mobile, editorial grid on desktop
- Remove ROUND_ACTIVE filter from myAssignments so all assignments show
- Block evaluate page when round is inactive or voting window is closed
- Gate evaluate button on project detail page based on voting window status
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix criteria not showing for jurors: fetch active form independently via
getStageForm query instead of relying on existing evaluation record
- Fix scoringMode default from 'global' to 'criteria' (matching schema)
- Parse scale string format ("1-10") into minScore/maxScore for criteria display
- Fix COI dialog dismissal: prevent outside click on evaluate page Dialog
- Fix requiredReviews hardcoded to 3: read from round configJson in 4 locations
- Add jury preferences banner for unconfirmed caps on jury dashboard
- Add updateJuryPreferences tRPC procedure for self-service cap/ratio
- Simplify onboarding: always show jury step, allow cap up to 50
- Add role/ratio/availability fields to jury member invite dialog
- Simplify jury group settings (keep only defaultMaxAssignments)
- Enforce deliberation showCollectiveRankings flag for non-admin users
- Redesign dashboard stat cards: editorial data strip on mobile,
clean grid layout on desktop (no more generic card pattern)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Some PDFs contain \x00 null bytes in their text which PostgreSQL rejects
with "invalid byte sequence for encoding UTF8: 0x00". Sanitize extracted
text in both document-analyzer and file-content-extractor services.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete rewrite of the admin dashboard replacing the 1056-line monolith with
modular components. Key improvements:
- Competition pipeline: horizontal visualization of all rounds in pipeline order
(by sortOrder, not creation date) with type-specific icons and metrics
- Round-specific stats: stat cards dynamically change based on active round type
(INTAKE shows submissions/docs, FILTERING shows pass/fail/flagged, EVALUATION
shows assignments/completion, fallback shows generic project/jury stats)
- Smart actions: context-aware "Action Required" panel that only flags the next
draft round (not all), only flags unassigned projects for EVALUATION rounds,
includes deadline warnings with severity levels (critical/warning/info)
- Active round panel: detailed view with project state bar, type-specific content,
and deadline countdown
- Extracted components: project list, activity feed, category breakdown, skeleton
Backend enriched with 17 parallel queries building rich PipelineRound data
including per-round project states, eval stats, filtering stats, live session
status, and deliberation counts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pdf-parse v2 requires DOMMatrix (browser API) which fails in Node.js.
Replaced with unpdf (serverless PDF.js build) for PDFs and mammoth for
Word .docx files. Also fixed the same broken pdf-parse usage in
file-content-extractor.ts used by AI filtering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a document analyzer service that extracts page count (via pdf-parse),
text preview, and detected language (via franc) from uploaded files. Analysis runs
automatically on upload (configurable via SystemSettings) and can be triggered
retroactively for existing files. Results are displayed as badges in the FileViewer
and fed to AI screening for language-based filtering criteria.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire batchCheckRequirementsAndTransition into round activation and reopen
so pre-existing projects that already have all required docs get auto-
passed. Also adds checkDocumentCompletion endpoint for manual sweeps on
already-active rounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI assignment path was receiving project tags as flat strings, losing
the confidence scores from AI tagging. Now both the GPT path and the
fallback algorithm weight tag matches by confidence — a 0.9 tag matters
more than a 0.5 one.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
For INTAKE, SUBMISSION, and MENTORING rounds, the Advance Projects dialog
now shows a simplified "Advance All" flow that auto-passes all pending
projects and advances them in one click. Backend accepts autoPassPending
flag to bulk-set PENDING→PASSED before advancing. Jury/evaluation rounds
keep the existing per-project selection workflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add checkRequirementsAndTransition() to round-engine that checks if all
required FileRequirements for a round are satisfied by uploaded files.
When all are met and the project is PENDING/IN_PROGRESS, it auto-
transitions to PASSED. Also adds batchCheckRequirementsAndTransition()
for bulk operations.
Wired into:
- file.adminUploadForRoundRequirement (admin bulk upload)
- applicant.saveFileMetadata (applicant self-upload)
Non-fatal: failures in the check never break the upload itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ai_provider setting: 'openai' (API key) or 'litellm' (ChatGPT subscription proxy)
- Auto-strip max_tokens/max_completion_tokens for chatgpt/ prefix models
(ChatGPT subscription backend rejects token limit fields)
- LiteLLM mode: dummy API key when none configured, base URL required
- isOpenAIConfigured() checks base URL instead of API key for LiteLLM
- listAvailableModels() returns manualEntry flag for LiteLLM (no models.list)
- Settings UI: conditional fields, info banner, manual model input with
chatgpt/ prefix examples when LiteLLM selected
- All 7 AI services work transparently via buildCompletionParams()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change AI tagging to use AI_MODELS.QUICK (gpt-4o-mini) instead of gpt-4o for
10-15x cost reduction on classification tasks
- Add openai_base_url system setting for OpenAI-compatible providers
(OpenRouter, Groq, Together AI, local models)
- Reset OpenAI client singleton when API key, base URL, or model changes
- Add base URL field to AI settings form with provider examples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The project detail page called useQuery inside .map() to fetch file
requirements per round, violating React's rules of hooks. When
competitionRounds changed from [] to [round1, round2], the hook count
changed, causing React to crash with "Cannot read properties of
undefined (reading 'length')".
Fix: Add listRequirementsByRounds endpoint that accepts multiple
roundIds in one query, replacing the dynamic hook pattern with a
single stable useQuery call.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- AI Tagging: batch 10 projects per API call with 3 concurrent batches (~10x faster)
- New `tagProjectsBatch()` with `getAISuggestionsBatch()` for multi-project prompts
- Single DB query for all projects, single anonymization pass
- Compact JSON in prompts (no pretty-print) saves tokens
- AI Shortlist: run STARTUP and BUSINESS_CONCEPT categories in parallel (2x faster)
- AI Filtering: increase default parallel batches from 1 to 3 (3x faster)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>