Consolidated round management, AI filtering enhancements, MinIO storage restructure
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
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:
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user