Fix filtering config save, auto-save, streamed results, improved AI prompt
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m7s

- 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>
This commit is contained in:
Matt
2026-02-17 17:18:04 +01:00
parent bed444e5f4
commit cf1508f856
4 changed files with 107 additions and 11 deletions

View File

@@ -149,7 +149,7 @@ const MAX_PARALLEL_BATCHES = 10
const AI_SCREENING_SYSTEM_PROMPT = `You are an expert project screening assistant for an ocean conservation competition.
## Your Role
Evaluate each project against the provided screening criteria. Be objective and base evaluation only on provided data.
Evaluate each project against the provided screening criteria. Be objective and base evaluation only on provided data. Your goal is to identify projects that clearly FAIL the criteria — not to nitpick minor issues.
## Output Format
Return a JSON object with this exact structure:
@@ -179,12 +179,43 @@ Return a JSON object with this exact structure:
- 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), size_kb, detected_lang (ISO 639-3 language code like 'eng', 'fra'), lang_confidence (0-1), round_name (which round the file was submitted for), and is_current_round flag
- files[]: per-file details (see File Type Reference below) with file_type, page_count (if known), size_kb, detected_lang (ISO 639-3 language code like 'eng', 'fra'), lang_confidence (0-1), round_name (which round the file was submitted for), and is_current_round flag
- description: project summary text
- tags: topic tags
- If document content is provided (text_content field in files), use it for deeper analysis. Pay SPECIAL ATTENTION to files from the current round (is_current_round=true) as they are the most recent and relevant submissions.
- If detected_lang is provided, use it to evaluate language requirements (e.g. 'eng' = English, 'fra' = French). lang_confidence indicates detection reliability.
## File Type Reference
The file_type field uses these codes. When criteria mention document names, match them to the correct code:
- EXEC_SUMMARY = Executive Summary
- PRESENTATION = Pitch Deck / Presentation
- BUSINESS_PLAN = Business Plan
- VIDEO = Video
- VIDEO_PITCH = Video Pitch
- SUPPORTING_DOC = Supporting Document
- OTHER = Other / miscellaneous document
IMPORTANT: Only evaluate page limits against the CORRECT document type. For example, a page limit on "executive summary" applies ONLY to files with file_type=EXEC_SUMMARY, not to pitch decks or other documents.
## How to Interpret Criteria
- Treat criteria as reasonable guidelines written by a human, not as rigid legal rules.
- When criteria say "soft cap", "approximately", "around", "about", or "reasonable amount", be LENIENT. A document that is 1-3 pages over a soft cap still meets criteria. Only flag if it is egregiously over (e.g. double the stated limit).
- When criteria have NO softening language, treat them as firm but still use judgment — a 1-page overage on a hard limit is borderline, not an automatic fail.
- Page count data may be unavailable (null) for some files. If page_count is null, do NOT penalize — only evaluate page limits when page_count is actually provided for that file.
- Focus on the INTENT behind each criterion, not a hyper-literal reading.
## Missing Documents
- If criteria mention document requirements (executive summary, pitch deck, etc.) and the project has ZERO files (file_count=0), this is a CLEAR FAIL. Every competition applicant is expected to upload documents.
- "Where applicable" in criteria refers to edge cases within specific document types — it does NOT mean documents are optional.
- A project with no uploaded documents at all should always have meets_criteria=FALSE unless the criteria explicitly say documents are optional.
## Decision Framework
- Set meets_criteria=TRUE if the project satisfies the core intent of the criteria, even with minor imperfections (e.g. a few pages over a soft cap).
- Set meets_criteria=FALSE when there is a clear, material violation: missing required documents entirely, startup far exceeding age limit, documents entirely in the wrong language, no visible ocean impact, etc.
- When in doubt on a SOFT criterion (page lengths, formatting), set meets_criteria=TRUE with a lower confidence score and note the concern in reasoning.
- When in doubt on a HARD criterion (age limits, language, having documents at all), set meets_criteria=FALSE.
- Mention specific concerns in the reasoning field so the admin can review.
## Guidelines
- Evaluate ONLY against the provided criteria, not your own standards
- A confidence of 1.0 means absolute certainty; 0.5 means borderline
@@ -192,7 +223,7 @@ Return a JSON object with this exact structure:
- 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`
- If project data is insufficient to evaluate, set confidence below 0.3 and default meets_criteria to TRUE`
// ─── Field-Based Rule Evaluation ────────────────────────────────────────────
@@ -371,10 +402,16 @@ async function processAIBatch(
// Sanitize user-supplied criteria
const { sanitized: safeCriteria } = sanitizeUserInput(criteriaText)
// Build optimized prompt
const userPrompt = `CRITERIA: ${safeCriteria}
PROJECTS: ${JSON.stringify(anonymized)}
Evaluate and return JSON.`
// Build user prompt with clear structure
const userPrompt = `## Screening Criteria
The admin has defined the following requirements. Evaluate each project against ALL of these criteria:
${safeCriteria}
## Projects to Evaluate (${anonymized.length} total)
${JSON.stringify(anonymized)}
Evaluate each project and return JSON with your assessment.`
const MAX_PARSE_RETRIES = 2
let parseAttempts = 0