Consolidated round management, AI filtering enhancements, MinIO storage restructure
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s

- Fix STAGE_ACTIVE bug in assignment router (now ROUND_ACTIVE)
- Add evaluation form CRUD (getForm + upsertForm endpoints)
- Add advanceProjects mutation for manual project advancement
- Rewrite round detail page: 7-tab consolidated interface
- Add filtering rules UI with full CRUD (field-based, document check, AI screening)
- Add pageCount field to ProjectFile for document page limit filtering
- Enhance AI filtering: per-file page limits, category/region-aware guidelines
- Restructure MinIO paths: {ProjectName}/{RoundName}/{timestamp}-{file}
- Update dashboard and pool page links from /admin/competitions to /admin/rounds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 09:20:02 +01:00
parent 845554fdb8
commit 8e5fc18da6
14 changed files with 2606 additions and 303 deletions

View File

@@ -67,6 +67,8 @@ export type FieldRuleConfig = {
export type DocumentCheckConfig = {
requiredFileTypes?: string[] // e.g. ['pdf', 'docx']
minFileCount?: number
maxPages?: number // Max pages for ANY file
maxPagesByFileType?: Record<string, number> // e.g. { "EXECUTIVE_SUMMARY": 2, "PITCH_DECK": 10 }
action: 'PASS' | 'REJECT' | 'FLAG'
}
@@ -110,7 +112,7 @@ interface ProjectForFiltering {
institution?: string | null
submissionSource?: SubmissionSource
submittedAt?: Date | null
files: Array<{ id: string; fileName: string; fileType?: FileType | null }>
files: Array<{ id: string; fileName: string; fileType?: FileType | null; size?: number; pageCount?: number | null }>
_count?: {
teamMembers?: number
files?: number
@@ -162,10 +164,22 @@ Return a JSON object with this exact structure:
- 3-4: Weak — significant shortcomings against criteria
- 1-2: Poor — does not meet criteria or appears low-quality/spam
## Available Data Per Project
- category: STARTUP or BUSINESS_CONCEPT
- country, region: geographic location (use for regional considerations)
- founded_year: when the company/initiative was founded (use for age checks)
- ocean_issue: the ocean conservation area
- file_count, file_types: uploaded documents summary
- files[]: per-file details with file_type, page_count (if known), and size_kb
- description: project summary text
- tags: topic tags
## Guidelines
- Evaluate ONLY against the provided criteria, not your own standards
- A confidence of 1.0 means absolute certainty; 0.5 means borderline
- Flag spam_risk=true for: AI-generated filler text, copied content, or irrelevant submissions
- When criteria differ by category (e.g. stricter for STARTUP vs BUSINESS_CONCEPT), apply the appropriate threshold
- When criteria mention regional considerations (e.g. African projects), use the country/region fields
- Do not include any personal identifiers in reasoning
- If project data is insufficient to evaluate, set confidence below 0.3`
@@ -293,6 +307,25 @@ export function evaluateDocumentRule(
}
}
// Check global max pages (any file exceeding limit fails)
if (config.maxPages !== undefined) {
const overLimit = files.some((f) => f.pageCount != null && f.pageCount > config.maxPages!)
if (overLimit) {
return { passed: false, action: config.action }
}
}
// Check per-fileType max pages (e.g. EXECUTIVE_SUMMARY: 2, PITCH_DECK: 10)
if (config.maxPagesByFileType && Object.keys(config.maxPagesByFileType).length > 0) {
for (const file of files) {
if (!file.fileType || file.pageCount == null) continue
const limit = config.maxPagesByFileType[file.fileType]
if (limit !== undefined && file.pageCount > limit) {
return { passed: false, action: config.action }
}
}
}
return { passed: true, action: config.action }
}

View File

@@ -79,6 +79,12 @@ export interface AnonymizationResult {
* Comprehensive anonymized project data for AI filtering
* Includes all fields needed for flexible filtering criteria
*/
export interface AnonymizedFileInfo {
file_type: string // FileType enum value
page_count: number | null // Number of pages if known
size_kb: number // File size in KB
}
export interface AnonymizedProjectForAI {
project_id: string // P1, P2, etc.
title: string // Sanitized
@@ -94,6 +100,7 @@ export interface AnonymizedProjectForAI {
has_description: boolean
file_count: number
file_types: string[] // FileType values
files: AnonymizedFileInfo[] // Per-file details for document analysis
wants_mentorship: boolean
submission_source: SubmissionSource
submitted_date: string | null // YYYY-MM-DD only
@@ -121,7 +128,7 @@ export interface ProjectWithRelations {
teamMembers?: number
files?: number
}
files?: Array<{ fileType: FileType | null }>
files?: Array<{ fileType: FileType | null; size?: number; pageCount?: number | null }>
}
/**
@@ -153,7 +160,7 @@ export function toProjectWithRelations(project: {
submissionSource?: string
submittedAt?: Date | null
_count?: { teamMembers?: number; files?: number }
files?: Array<{ fileType?: string | null; [key: string]: unknown }>
files?: Array<{ fileType?: string | null; size?: number; pageCount?: number | null; [key: string]: unknown }>
}): ProjectWithRelations {
return {
id: project.id,
@@ -173,7 +180,11 @@ export function toProjectWithRelations(project: {
teamMembers: project._count?.teamMembers ?? 0,
files: project._count?.files ?? project.files?.length ?? 0,
},
files: project.files?.map((f) => ({ fileType: (f.fileType as FileType) ?? null })) ?? [],
files: project.files?.map((f) => ({
fileType: (f.fileType as FileType) ?? null,
size: f.size,
pageCount: f.pageCount ?? null,
})) ?? [],
}
}
@@ -288,6 +299,11 @@ export function anonymizeProjectForAI(
file_types: project.files
?.map((f) => f.fileType)
.filter((ft): ft is FileType => ft !== null) ?? [],
files: project.files?.map((f) => ({
file_type: f.fileType ?? 'OTHER',
page_count: f.pageCount ?? null,
size_kb: Math.round((f.size ?? 0) / 1024),
})) ?? [],
wants_mentorship: project.wantsMentorship ?? false,
submission_source: project.submissionSource,
submitted_date: project.submittedAt?.toISOString().split('T')[0] ?? null,