75 KiB
Round Type: INTAKE — Application Window
1. Overview
The INTAKE round is the first phase of any MOPC competition. It represents the application submission window where teams apply to participate by submitting their ocean conservation projects along with required documentation.
Purpose
- Collect project applications from teams worldwide
- Capture essential project information (title, description, team, ocean issue addressed)
- Receive required documentation (executive summaries, business plans, videos)
- Support draft/save-and-continue workflow for incomplete applications
- Enforce submission deadlines with configurable late submission policies
- Enable both authenticated (user login) and public (anonymous) application flows
Position in Competition Flow
Competition 2026
└─ Round 1: "Application Window" ────── [INTAKE]
└─ Round 2: "AI Screening" ───────── [FILTERING]
└─ Round 3: "Jury Evaluation 1" ── [EVALUATION]
└─ Round 4: "Semi-finalist Docs" ── [SUBMISSION]
└─ ...
The intake round creates the initial pool of projects that flow through subsequent rounds. All projects begin here.
2. Current System (Pipeline/Track/Stage)
How Intake Works Today
In the current architecture, intake is implemented as a Stage with StageType: INTAKE inside the MAIN track.
// Current structure
Pipeline: "MOPC 2026"
└─ Track: "Main Competition" (kind: MAIN)
└─ Stage: "Intake" (stageType: INTAKE)
├─ windowOpenAt: 2026-01-15T00:00:00Z
├─ windowCloseAt: 2026-03-01T23:59:59Z
├─ configJson: {
│ submissionWindowEnabled: true,
│ lateSubmissionPolicy: "flag",
│ lateGraceHours: 24,
│ fileRequirements: [...]
│ }
└─ FileRequirement records (linked to stageId)
Current Config Fields (src/lib/pipeline-defaults.ts)
type IntakeConfig = {
submissionWindowEnabled: boolean // Whether submission is open
lateSubmissionPolicy: 'reject' | 'flag' | 'accept'
lateGraceHours: number // Hours after close for late submissions
fileRequirements: FileRequirementConfig[]
}
type FileRequirementConfig = {
name: string // "Executive Summary"
description?: string // Help text
acceptedMimeTypes: string[] // ["application/pdf"]
maxSizeMB?: number // 50
isRequired: boolean // true
}
Current Applicant Flow
- Access: Applicant visits
/applicant/pipeline/{slug}or receives invite link - Auth: User logs in (email magic link or password) or proceeds as guest
- Form: Fills out project form (title, description, category, ocean issue, team info)
- Files: Uploads required files (exec summary, business plan, video)
- Draft: Can save as draft and return later
- Submit: Final submission creates Project record with status "SUBMITTED"
- Email: Confirmation email sent if configured
Current Admin Experience
Admins configure the intake stage via the pipeline wizard:
- Set open/close dates
- Define file requirements (name, mime types, size limits, required/optional)
- Choose late submission policy (reject, flag, accept)
- Set grace period for late submissions
Current Database Schema
model Stage {
id String @id
trackId String
stageType StageType // INTAKE
name String
slug String
status StageStatus
configJson Json? // IntakeConfig stored here
windowOpenAt DateTime?
windowCloseAt DateTime?
...
}
model FileRequirement {
id String @id
stageId String // Links to intake stage
name String
acceptedMimeTypes String[]
maxSizeMB Int?
isRequired Boolean
sortOrder Int
...
}
model ProjectFile {
id String @id
projectId String
roundId String? // Legacy field
requirementId String? // FK to FileRequirement
fileType FileType
fileName String
mimeType String
size Int
bucket String
objectKey String
isLate Boolean @default(false)
version Int @default(1)
...
}
model Project {
id String @id
programId String
roundId String? // Legacy — which round project was submitted for
status ProjectStatus @default(SUBMITTED)
title String
teamName String?
description String?
competitionCategory CompetitionCategory? // STARTUP | BUSINESS_CONCEPT
oceanIssue OceanIssue?
country String?
wantsMentorship Boolean @default(false)
isDraft Boolean @default(false)
draftDataJson Json? // Form data for drafts
draftExpiresAt DateTime?
submissionSource SubmissionSource @default(MANUAL)
submittedByEmail String?
submittedAt DateTime?
submittedByUserId String?
...
}
Current Limitations
| Issue | Impact |
|---|---|
| Single submission window | Can't require new docs from semi-finalists |
| No form builder | All fields hardcoded in application code |
| No category quotas at intake | Can't limit "first 50 startups, first 50 concepts" |
| Generic configJson | Unclear what fields exist for intake stages |
| File requirements per stage | Awkward: "intake stage" is the only stage with file requirements |
| No dynamic forms | Can't add custom questions per competition year |
| No public form branding | External applicants see generic MOPC form |
3. Redesigned Intake Round
New Round Structure
// Redesigned
Competition: "MOPC 2026"
└─ Round 1: "Application Window" (roundType: INTAKE)
├─ competitionId: competition-2026
├─ name: "Application Window"
├─ slug: "application-window"
├─ roundType: INTAKE
├─ status: ROUND_ACTIVE
├─ sortOrder: 0
├─ windowOpenAt: 2026-01-15T00:00:00Z
├─ windowCloseAt: 2026-03-01T23:59:59Z
├─ submissionWindowId: "sw-1" // NEW: Links to SubmissionWindow
├─ configJson: IntakeConfig { // NEW: Typed, validated config
│ applicationFormId: "form-2026",
│ deadlinePolicy: "GRACE",
│ gracePeriodMinutes: 180,
│ allowDraftSubmissions: true,
│ requireTeamProfile: true,
│ maxTeamSize: 5,
│ minTeamSize: 1,
│ autoConfirmReceipt: true,
│ publicFormEnabled: true,
│ categoryQuotas: { STARTUP: 100, BUSINESS_CONCEPT: 100 }
│ }
└─ SubmissionWindow: "Round 1 Docs"
├─ id: "sw-1"
├─ competitionId: competition-2026
├─ name: "Round 1 Application Docs"
├─ slug: "round-1-docs"
├─ roundNumber: 1
├─ windowOpenAt: 2026-01-15T00:00:00Z
├─ windowCloseAt: 2026-03-01T23:59:59Z
├─ deadlinePolicy: GRACE
├─ graceHours: 3
└─ FileRequirements: [
├─ "Executive Summary" (PDF, required)
├─ "Business Plan" (PDF, required)
└─ "Video Pitch" (video/*, optional)
]
IntakeConfig Type (Zod-validated)
type IntakeConfig = {
// Application Form
applicationFormId: string // Links to ApplicationForm template (future)
// Submission Window (linked via Round.submissionWindowId)
submissionWindowId: string // Which SubmissionWindow to use
// Deadline Behavior
deadlinePolicy: DeadlinePolicy // HARD | FLAG | GRACE
gracePeriodMinutes: number // For GRACE policy (e.g., 180 = 3 hours)
// Draft System
allowDraftSubmissions: boolean // Save-and-continue enabled
draftExpiryDays: number // Auto-delete abandoned drafts after N days
// Team Profile
requireTeamProfile: boolean // Require team member info
maxTeamSize: number // Max team members (including lead)
minTeamSize: number // Min team members (default: 1)
// Notifications
autoConfirmReceipt: boolean // Email confirmation on submission
reminderEmailSchedule: number[] // Days before deadline: [7, 3, 1]
// Public Access
publicFormEnabled: boolean // Allow external application link
publicFormSlug?: string // Custom slug for public URL
// Category Quotas (STARTUP vs BUSINESS_CONCEPT)
categoryQuotasEnabled: boolean
categoryQuotas?: {
STARTUP: number // Max startups accepted
BUSINESS_CONCEPT: number // Max concepts accepted
}
quotaOverflowPolicy?: 'reject' | 'waitlist' // What happens when quota full
// Custom Fields (future: dynamic form builder)
customFields?: CustomFieldDef[] // Additional form fields
}
enum DeadlinePolicy {
HARD // Submissions blocked after deadline, no exceptions
FLAG // Submissions accepted but flagged as late
GRACE // Accept for N minutes after deadline with warning
}
type CustomFieldDef = {
id: string
label: string
type: 'text' | 'textarea' | 'select' | 'multiselect' | 'date' | 'number'
required: boolean
options?: string[] // For select/multiselect
validation?: {
min?: number
max?: number
regex?: string
}
}
Zod Schema for Validation
import { z } from 'zod'
export const intakeConfigSchema = z.object({
applicationFormId: z.string().cuid(),
submissionWindowId: z.string().cuid(),
deadlinePolicy: z.enum(['HARD', 'FLAG', 'GRACE']),
gracePeriodMinutes: z.number().int().min(0).max(1440), // Max 24 hours
allowDraftSubmissions: z.boolean().default(true),
draftExpiryDays: z.number().int().min(1).default(30),
requireTeamProfile: z.boolean().default(true),
maxTeamSize: z.number().int().min(1).max(20).default(5),
minTeamSize: z.number().int().min(1).default(1),
autoConfirmReceipt: z.boolean().default(true),
reminderEmailSchedule: z.array(z.number().int()).default([7, 3, 1]),
publicFormEnabled: z.boolean().default(false),
publicFormSlug: z.string().optional(),
categoryQuotasEnabled: z.boolean().default(false),
categoryQuotas: z.object({
STARTUP: z.number().int().min(0),
BUSINESS_CONCEPT: z.number().int().min(0),
}).optional(),
quotaOverflowPolicy: z.enum(['reject', 'waitlist']).optional(),
customFields: z.array(z.object({
id: z.string(),
label: z.string().min(1).max(200),
type: z.enum(['text', 'textarea', 'select', 'multiselect', 'date', 'number']),
required: z.boolean(),
options: z.array(z.string()).optional(),
validation: z.object({
min: z.number().optional(),
max: z.number().optional(),
regex: z.string().optional(),
}).optional(),
})).optional(),
})
export type IntakeConfig = z.infer<typeof intakeConfigSchema>
4. Application Form System
ApplicationForm Model (Future Enhancement)
For now, the application form is hardcoded. In the future, a dynamic form builder will replace this.
model ApplicationForm {
id String @id @default(cuid())
competitionId String
name String // "MOPC 2026 Application"
description String?
fieldsJson Json @db.JsonB // Array of field definitions
version Int @default(1)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
@@index([competitionId])
@@index([isActive])
}
Standard Form Fields (Hardcoded for MVP)
type ApplicationFormData = {
// Project Info
title: string // Required, 1-500 chars
teamName?: string // Optional
description: string // Required, max 5000 chars
competitionCategory: 'STARTUP' | 'BUSINESS_CONCEPT' // Required
oceanIssue: OceanIssue // Required enum
// Location
country: string // Required
geographicZone?: string // "Europe, France"
institution?: string // Required for BUSINESS_CONCEPT
// Founding
foundedAt?: Date // When project/company started
// Mentorship
wantsMentorship: boolean // Default: false
// Referral
referralSource?: string // "LinkedIn", "Email", etc.
// Team Members (if requireTeamProfile: true)
teamMembers?: TeamMemberInput[]
// Custom Fields (future)
customFieldValues?: Record<string, unknown>
}
type TeamMemberInput = {
name: string
email: string
role: 'LEAD' | 'MEMBER' | 'ADVISOR'
title?: string // "CEO", "CTO"
}
Field Validation Rules
| Field | Validation | Error Message |
|---|---|---|
title |
Required, 1-500 chars | "Project title is required" |
description |
Required, max 5000 chars | "Description must be under 5000 characters" |
competitionCategory |
Required enum | "Please select Startup or Business Concept" |
oceanIssue |
Required enum | "Please select an ocean issue" |
country |
Required string | "Country is required" |
institution |
Required if category = BUSINESS_CONCEPT | "Institution is required for student projects" |
teamMembers[].email |
Valid email format | "Invalid email address" |
teamMembers.length |
>= minTeamSize, <= maxTeamSize | "Team must have 1-5 members" |
Conditional Logic
- Institution field: Only shown/required when
competitionCategory = BUSINESS_CONCEPT - Team members section: Only shown if
requireTeamProfile = truein config - Mentorship checkbox: Always shown, default unchecked
5. Submission Window Integration
SubmissionWindow Model (from 03-data-model.md)
model SubmissionWindow {
id String @id @default(cuid())
competitionId String
name String // "Round 1 Application Docs"
slug String // "round-1-docs"
roundNumber Int // 1 (first window), 2 (second window), etc.
sortOrder Int @default(0)
windowOpenAt DateTime?
windowCloseAt DateTime?
deadlinePolicy DeadlinePolicy @default(FLAG)
graceHours Int? // For GRACE policy
lockOnClose Boolean @default(true) // Prevent edits after close
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
fileRequirements SubmissionFileRequirement[]
projectFiles ProjectFile[]
rounds Round[] // INTAKE rounds using this window
visibility RoundSubmissionVisibility[]
@@unique([competitionId, slug])
@@unique([competitionId, roundNumber])
@@index([competitionId])
}
model SubmissionFileRequirement {
id String @id @default(cuid())
submissionWindowId String
name String // "Executive Summary"
description String? @db.Text
acceptedMimeTypes String[] // ["application/pdf"]
maxSizeMB Int?
isRequired Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
submissionWindow SubmissionWindow @relation(fields: [submissionWindowId], references: [id], onDelete: Cascade)
files ProjectFile[]
@@index([submissionWindowId])
}
How Intake Round Links to SubmissionWindow
// Round creation
const round = await prisma.round.create({
data: {
competitionId: competition.id,
name: "Application Window",
slug: "application-window",
roundType: "INTAKE",
sortOrder: 0,
windowOpenAt: new Date("2026-01-15T00:00:00Z"),
windowCloseAt: new Date("2026-03-01T23:59:59Z"),
submissionWindowId: submissionWindow.id, // Link to window
configJson: {
applicationFormId: "form-2026",
submissionWindowId: submissionWindow.id, // Redundant but explicit
deadlinePolicy: "GRACE",
gracePeriodMinutes: 180,
// ... rest of IntakeConfig
}
}
})
File Requirements for Intake
Admin configures file requirements at the SubmissionWindow level, not the round level.
// Example: Create file requirements for Round 1 docs
const requirements = await prisma.submissionFileRequirement.createMany({
data: [
{
submissionWindowId: "sw-1",
name: "Executive Summary",
description: "A PDF executive summary of your project (max 2 pages)",
acceptedMimeTypes: ["application/pdf"],
maxSizeMB: 10,
isRequired: true,
sortOrder: 0,
},
{
submissionWindowId: "sw-1",
name: "Business Plan",
description: "Full business plan or project proposal (PDF)",
acceptedMimeTypes: ["application/pdf"],
maxSizeMB: 50,
isRequired: true,
sortOrder: 1,
},
{
submissionWindowId: "sw-1",
name: "Video Pitch",
description: "Optional video pitch (max 5 minutes, MP4 or MOV)",
acceptedMimeTypes: ["video/mp4", "video/quicktime"],
maxSizeMB: 500,
isRequired: false,
sortOrder: 2,
},
]
})
Deadline Enforcement
When an applicant tries to upload a file or submit the form:
async function canSubmitToWindow(submissionWindow: SubmissionWindow): Promise<{
canSubmit: boolean
reason?: string
isLate?: boolean
}> {
const now = new Date()
// Not yet open
if (submissionWindow.windowOpenAt && now < submissionWindow.windowOpenAt) {
return {
canSubmit: false,
reason: `Window opens on ${submissionWindow.windowOpenAt.toLocaleDateString()}`
}
}
// Window closed
if (submissionWindow.windowCloseAt && now > submissionWindow.windowCloseAt) {
const { deadlinePolicy, graceHours } = submissionWindow
if (deadlinePolicy === 'HARD') {
return {
canSubmit: false,
reason: "Deadline has passed. Submissions are no longer accepted."
}
}
if (deadlinePolicy === 'GRACE' && graceHours) {
const graceDeadline = new Date(submissionWindow.windowCloseAt.getTime() + graceHours * 60 * 60 * 1000)
if (now > graceDeadline) {
return {
canSubmit: false,
reason: `Grace period ended on ${graceDeadline.toLocaleString()}`
}
}
return {
canSubmit: true,
isLate: true
}
}
if (deadlinePolicy === 'FLAG') {
return {
canSubmit: true,
isLate: true
}
}
}
return { canSubmit: true }
}
6. Draft System
Auto-Save Behavior
When allowDraftSubmissions: true, the form auto-saves every 30 seconds (or on field blur).
// Draft auto-save
const saveDraft = async (formData: Partial<ApplicationFormData>) => {
const project = await trpc.applicant.saveDraft.mutate({
programId: programId,
projectId: existingProjectId, // null for new draft
draftData: formData,
})
return project
}
// Draft expiry
const draftExpiresAt = new Date()
draftExpiresAt.setDate(draftExpiresAt.getDate() + config.draftExpiryDays)
await prisma.project.upsert({
where: { id: projectId },
update: {
isDraft: true,
draftDataJson: formData,
draftExpiresAt: draftExpiresAt,
updatedAt: new Date(),
},
create: {
programId,
isDraft: true,
draftDataJson: formData,
draftExpiresAt: draftExpiresAt,
status: 'SUBMITTED', // Will change to DRAFT status in redesign
submittedByUserId: userId,
submissionSource: 'PUBLIC_FORM',
}
})
Resume Draft Flow
- User returns to application page
- System checks for existing draft:
const draft = await prisma.project.findFirst({ where: { programId, submittedByUserId: userId, isDraft: true, draftExpiresAt: { gt: new Date() } // Not expired } }) - If found, pre-populate form with
draft.draftDataJson - User can continue editing or discard draft
Validation States
type FormValidationState = {
isValid: boolean
canSaveDraft: boolean // Always true
canSubmit: boolean // All required fields filled + files uploaded
errors: Record<string, string>
warnings: Record<string, string>
}
// Example states:
// 1. Empty form → isValid: false, canSaveDraft: true, canSubmit: false
// 2. Partial form → isValid: false, canSaveDraft: true, canSubmit: false
// 3. Complete form → isValid: true, canSaveDraft: true, canSubmit: true
7. Applicant Experience
Landing Page Flow
┌─────────────────────────────────────────────────────────────┐
│ MOPC 2026 Application │
│ ══════════════════════════════════════════════════════ │
│ │
│ Application Window: Jan 15 - Mar 1, 2026 │
│ ⏱️ 23 days remaining │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 🌊 Ocean Conservation Innovation Challenge │ │
│ │ │ │
│ │ We're looking for breakthrough ocean projects │ │
│ │ from startups and student teams worldwide. │ │
│ │ │ │
│ │ 📋 Requirements: │ │
│ │ • Executive Summary (PDF) │ │
│ │ • Business Plan (PDF) │ │
│ │ • Video Pitch (optional) │ │
│ │ │ │
│ │ ⏱️ Application takes ~30 minutes │ │
│ │ │ │
│ │ [ Login to Start ] [ Apply as Guest ] │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Multi-Step Application Form
┌─────────────────────────────────────────────────────────────┐
│ Step 1 of 4: Project Information │
│ ●───────○───────○───────○ [Save Draft] [Continue →] │
│ │
│ Project Title * │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Ocean Plastic Recycling Platform │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Team Name │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PlastiClean Solutions │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Competition Category * │
│ ○ Startup (existing company) │
│ ● Business Concept (student/graduate project) │
│ │
│ Institution * (required for Business Concept) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Stanford University │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Ocean Issue Addressed * │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 🔍 Pollution Reduction [Dropdown ▼] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Description * (max 5000 characters) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ PlastiClean is an AI-powered sorting system... │ │
│ │ │ │
│ │ 247 / 5000 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Country * │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 🌍 United States [Dropdown ▼] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Founding Date │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 2024-06-01 [Date Picker] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ☑ I'm interested in mentorship from industry experts │
│ │
│ [ ← Back ] [ Save Draft ] [ Continue → ] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 2 of 4: Team Members │
│ ○───────●───────○───────○ [Save Draft] [Continue →] │
│ │
│ Team Members (1-5 members) * │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 👤 Sarah Johnson (You) │ │
│ │ sarah.johnson@stanford.edu │ │
│ │ Role: Lead | Title: CEO │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 👤 Mark Chen [Remove ✕] │ │
│ │ Name: ┌──────────────────────┐ │ │
│ │ │ Mark Chen │ │ │
│ │ └──────────────────────┘ │ │
│ │ Email: ┌──────────────────────┐ │ │
│ │ │ mark@stanford.edu │ │ │
│ │ └──────────────────────┘ │ │
│ │ Role: ● Member ○ Advisor │ │
│ │ Title: ┌──────────────────────┐ │ │
│ │ │ CTO │ │ │
│ │ └──────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ [ + Add Team Member ] │
│ │
│ ℹ️ Team members will receive an email invite to view │
│ your project status. │
│ │
│ [ ← Back ] [ Save Draft ] [ Continue → ] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 3 of 4: Document Upload │
│ ○───────○───────●───────○ [Save Draft] [Continue →] │
│ │
│ Required Documents │
│ │
│ 📄 Executive Summary (PDF, max 10 MB) * │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ✓ PlastiClean_Executive_Summary.pdf │ │
│ │ Uploaded 2 hours ago | 2.3 MB │ │
│ │ [ View ] [ Replace ] [ Delete ] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 📄 Business Plan (PDF, max 50 MB) * │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 📎 Drag & drop or click to upload │ │
│ │ │ │
│ │ Accepted formats: PDF │ │
│ │ Max size: 50 MB │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Optional Documents │
│ │
│ 🎥 Video Pitch (MP4/MOV, max 500 MB) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ⚡ PlastiClean_Pitch.mp4 │ │
│ │ Uploading... 67% complete │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░ │ │
│ │ [ Cancel ] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Please upload all required documents before submitting. │
│ │
│ [ ← Back ] [ Save Draft ] [ Continue → ] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 4 of 4: Review & Submit │
│ ○───────○───────○───────● [Save Draft] [Submit] │
│ │
│ Review Your Application │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Project Information [Edit Step 1] │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ Title: Ocean Plastic Recycling Platform │ │
│ │ Team: PlastiClean Solutions │ │
│ │ Category: Business Concept │ │
│ │ Institution: Stanford University │ │
│ │ Ocean Issue: Pollution Reduction │ │
│ │ Country: United States │ │
│ │ Founded: June 2024 │ │
│ │ Mentorship: Yes │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Team Members [Edit Step 2] │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ • Sarah Johnson (Lead) - sarah.johnson@stanford.edu│ │
│ │ • Mark Chen (Member) - mark@stanford.edu │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Documents [Edit Step 3] │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ ✓ Executive Summary (2.3 MB) │ │
│ │ ✓ Business Plan (8.7 MB) │ │
│ │ ✓ Video Pitch (124 MB) │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ☑ I confirm that all information is accurate and that I │
│ have the authority to submit this application. │
│ │
│ ☑ I agree to the MOPC Terms & Conditions │
│ │
│ ⏱️ Deadline: March 1, 2026 at 11:59 PM UTC │
│ │
│ [ ← Back ] [ Save Draft ] [ Submit ✓ ] │
└─────────────────────────────────────────────────────────────┘
Post-Submission Confirmation
┌─────────────────────────────────────────────────────────────┐
│ ✓ Application Submitted Successfully │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 🎉 Thank You! │ │
│ │ │ │
│ │ Your application has been submitted successfully. │ │
│ │ │ │
│ │ Confirmation #: MOPC-2026-00123 │ │
│ │ Submitted: Feb 15, 2026 at 3:42 PM UTC │ │
│ │ │ │
│ │ ✉️ A confirmation email has been sent to: │ │
│ │ sarah.johnson@stanford.edu │ │
│ │ │ │
│ │ What's Next? │ │
│ │ ─────────────────────────────────────────── │ │
│ │ 1. AI Screening (March 2-5) │ │
│ │ 2. Jury Evaluation (March 10-31) │ │
│ │ 3. Semi-finalist Notification (April 5) │ │
│ │ │ │
│ │ You'll receive email updates at each stage. │ │
│ │ │ │
│ │ [ View My Dashboard ] [ Download Receipt ] │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Applicant Dashboard (After Submission)
┌─────────────────────────────────────────────────────────────┐
│ My Application sarah@stanford.edu│
│ ══════════════════════════════════════════════════════ │
│ │
│ PlastiClean Solutions │
│ Ocean Plastic Recycling Platform │
│ Status: Under Review [🟡 In Progress] │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Application Progress │ │
│ │ │ │
│ │ ✓ Submitted Feb 15, 2026 │ │
│ │ ─────────────────────────────────────────── │ │
│ │ ⏳ AI Screening Expected: Mar 2-5 │ │
│ │ ─────────────────────────────────────────── │ │
│ │ ○ Jury Review Expected: Mar 10-31 │ │
│ │ ─────────────────────────────────────────── │ │
│ │ ○ Decision Expected: Apr 5 │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Deadline Countdown │ │
│ │ 13 days remaining until March 1, 2026 │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Quick Actions │
│ [ 📄 View Application ] [ 📎 Documents ] [ 👥 Team ] │
│ │
│ Recent Activity │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ✓ Application submitted Feb 15, 3:42 PM │ │
│ │ ✓ Video pitch uploaded Feb 15, 3:38 PM │ │
│ │ ✓ Business plan uploaded Feb 15, 2:15 PM │ │
│ │ ✓ Executive summary uploaded Feb 15, 1:47 PM │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
8. Admin Experience
Intake Round Configuration Wizard
┌─────────────────────────────────────────────────────────────┐
│ Configure Round: Application Window │
│ ══════════════════════════════════════════════════════ │
│ │
│ Basic Settings │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Round Name * │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Application Window │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Submission Window * │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 🔍 Round 1 Docs [Select Window ▼] │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ [ + Create New Window ] │ │
│ │ │ │
│ │ Open Date * │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 2026-01-15 00:00 UTC [Date Picker] │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Close Date * │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 2026-03-01 23:59 UTC [Date Picker] │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ Deadline Policy │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ○ Hard Deadline │ │
│ │ Block submissions after close time │ │
│ │ │ │
│ │ ○ Flag Late Submissions │ │
│ │ Accept but mark as late │ │
│ │ │ │
│ │ ● Grace Period │ │
│ │ Accept for: ┌────┐ minutes after deadline │ │
│ │ │ 180│ │ │
│ │ └────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Draft Settings │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ☑ Allow draft submissions (save & continue) │ │
│ │ │ │
│ │ Auto-delete drafts after: ┌────┐ days │ │
│ │ │ 30 │ │ │
│ │ └────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Team Profile │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ☑ Require team member information │ │
│ │ │ │
│ │ Min team size: ┌───┐ Max team size: ┌───┐ │ │
│ │ │ 1 │ │ 5 │ │ │
│ │ └───┘ └───┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Notifications │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ☑ Send confirmation email on submission │ │
│ │ │ │
│ │ Send deadline reminders: │ │
│ │ ☑ 7 days before ☑ 3 days before ☑ 1 day before│ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Category Quotas (Optional) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ☐ Enable category quotas │ │
│ │ │ │
│ │ Max Startups: ┌─────┐ │ │
│ │ │ 100 │ │ │
│ │ └─────┘ │ │
│ │ │ │
│ │ Max Business Concepts: ┌─────┐ │ │
│ │ │ 100 │ │ │
│ │ └─────┘ │ │
│ │ │ │
│ │ When quota full: ○ Reject ● Add to waitlist │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ [ Cancel ] [ Save Round ] │
└─────────────────────────────────────────────────────────────┘
Submissions Dashboard
┌─────────────────────────────────────────────────────────────┐
│ Round: Application Window Admin Dashboard │
│ ══════════════════════════════════════════════════════ │
│ │
│ Overview │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 123 Total Submissions │ │
│ │ ┌──────────┬──────────┬──────────┬──────────┐ │ │
│ │ │ 68 │ 55 │ 12 │ 11 │ │ │
│ │ │ Startups │ Concepts │ Drafts │ Late │ │ │
│ │ └──────────┴──────────┴──────────┴──────────┘ │ │
│ │ │ │
│ │ Deadline: March 1, 2026 (13 days remaining) │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Filters: [ All ] [ Startups ] [ Concepts ] [ Late ] [ Drafts ]│
│ Search: ┌─────────────────────────────────────┐ [Export ↓] │
│ │ 🔍 Search by project or team... │ │
│ └─────────────────────────────────────┘ │
│ │
│ Recent Submissions │
│ ┌────────────────────────────────────────────────────┐ │
│ │ PlastiClean Solutions │ │
│ │ Ocean Plastic Recycling Platform │ │
│ │ Business Concept | United States | Feb 15, 3:42 PM │ │
│ │ [ View ] [ Edit ] [ Override Deadline ] │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ AquaTech Innovations │ │
│ │ Sustainable Aquaculture Monitoring │ │
│ │ Startup | Norway | Feb 15, 2:18 PM │ │
│ │ [ View ] [ Edit ] [ Override Deadline ] │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ OceanSense Labs 🔴 LATE │ │
│ │ AI-Powered Ocean Pollution Detection │ │
│ │ Startup | Singapore | Mar 2, 1:15 AM (+3 hours) │ │
│ │ [ View ] [ Edit ] [ Override Deadline ] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ [ Previous ] Page 1 of 7 [ Next ] │
│ │
└─────────────────────────────────────────────────────────────┘
Override Deadline Modal
┌─────────────────────────────────────────────────────────────┐
│ Override Deadline: PlastiClean Solutions │
│ ══════════════════════════════════════════════════════ │
│ │
│ Current Status: Submitted on time │
│ Original Deadline: March 1, 2026 11:59 PM UTC │
│ │
│ Extend submission window for this applicant: │
│ │
│ New Deadline │
│ ┌──────────────────────────────────────────────┐ │
│ │ 2026-03-08 23:59 UTC [Date Picker] │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Reason for Override * │
│ ┌──────────────────────────────────────────────┐ │
│ │ Technical issue during original submission │ │
│ │ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ⚠️ This will create a GracePeriod record and allow the │
│ applicant to edit their submission until the new deadline│
│ │
│ [ Cancel ] [ Grant Extension ] │
└─────────────────────────────────────────────────────────────┘
9. Deadline Behavior
Deadline Policy Comparison
| Policy | Before Deadline | After Deadline | Grace Period | Flagged |
|---|---|---|---|---|
| HARD | ✅ Accept | ❌ Block | N/A | N/A |
| FLAG | ✅ Accept | ✅ Accept | N/A | ✅ Yes |
| GRACE | ✅ Accept | ✅ Accept (for N min) | ✅ Yes | ✅ Yes (after grace) |
HARD Policy Behavior
Configuration:
{
deadlinePolicy: "HARD",
gracePeriodMinutes: null // Ignored
}
User Experience:
- Before deadline: Form is fully functional, all uploads allowed
- At deadline: Form locks immediately at
windowCloseAt - After deadline: Form displays:
❌ Deadline Passed The application deadline was March 1, 2026 at 11:59 PM UTC. Submissions are no longer accepted. Contact admin@monaco-opc.com for assistance.
Admin Override:
- Admin can create a
GracePeriodrecord for specific applicant - This extends their personal deadline (doesn't affect global deadline)
FLAG Policy Behavior
Configuration:
{
deadlinePolicy: "FLAG",
gracePeriodMinutes: null // Ignored
}
User Experience:
- Before deadline: Normal submission
- After deadline: Warning banner shown:
⚠️ Late Submission The deadline was March 1, 2026. Your submission will be marked as late. You can still submit, but late submissions may be deprioritized. - Submission still works, but
ProjectFile.isLateset totrue
Database Effect:
await prisma.projectFile.create({
data: {
projectId,
submissionWindowId,
requirementId,
fileName,
mimeType,
size,
bucket,
objectKey,
isLate: true, // Flagged
// ...
}
})
GRACE Policy Behavior
Configuration:
{
deadlinePolicy: "GRACE",
gracePeriodMinutes: 180 // 3 hours
}
User Experience:
- Before deadline: Normal submission
- 0-3 hours after deadline: Warning banner:
⏱️ Grace Period Active Deadline: March 1, 2026 11:59 PM UTC (passed) Grace period ends: March 2, 2026 2:59 AM UTC (1 hour 23 minutes remaining) Your submission will be marked as late. Please submit as soon as possible. - After grace period: Hard block (same as HARD policy)
Grace Period Calculation:
const graceDeadline = new Date(windowCloseAt.getTime() + gracePeriodMinutes * 60 * 1000)
if (now > windowCloseAt && now <= graceDeadline) {
// In grace period
return {
canSubmit: true,
isLate: true,
graceEndsAt: graceDeadline,
remainingMinutes: Math.floor((graceDeadline - now) / 60000)
}
}
10. Category Quotas
How Category Quotas Work
When categoryQuotasEnabled: true, the system tracks submissions per category and enforces limits.
Configuration:
{
categoryQuotasEnabled: true,
categoryQuotas: {
STARTUP: 100,
BUSINESS_CONCEPT: 100
},
quotaOverflowPolicy: "reject" // or "waitlist"
}
Quota Enforcement Flow
async function canSubmitInCategory(
competitionId: string,
category: 'STARTUP' | 'BUSINESS_CONCEPT',
config: IntakeConfig
): Promise<{ canSubmit: boolean; reason?: string }> {
if (!config.categoryQuotasEnabled || !config.categoryQuotas) {
return { canSubmit: true }
}
const quota = config.categoryQuotas[category]
if (!quota) {
return { canSubmit: true }
}
const submittedCount = await prisma.project.count({
where: {
competitionId,
competitionCategory: category,
isDraft: false, // Don't count drafts
}
})
if (submittedCount >= quota) {
if (config.quotaOverflowPolicy === 'waitlist') {
return {
canSubmit: true,
reason: `Quota full (${submittedCount}/${quota}). You're on the waitlist.`
}
} else {
return {
canSubmit: false,
reason: `Quota full (${submittedCount}/${quota}). No more ${category} applications accepted.`
}
}
}
return { canSubmit: true }
}
Quota Dashboard for Admins
┌─────────────────────────────────────────────────────────────┐
│ Category Quota Status │
│ ══════════════════════════════════════════════════════ │
│ │
│ Startups │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 68 / 100 submissions │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░ 68% │ │
│ │ │ │
│ │ 32 slots remaining │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Business Concepts │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 55 / 100 submissions │ │
│ │ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░ 55% │ │
│ │ │ │
│ │ 45 slots remaining │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Waitlist (if quota full) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Startups: 0 on waitlist │ │
│ │ Concepts: 0 on waitlist │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Overflow Handling
Reject Policy:
- Form shows error: "Category quota reached. No more startups/concepts accepted."
- User cannot submit
- Draft is saved but cannot be finalized
Waitlist Policy:
- Submission accepted, but
Project.status = WAITLISTED(new status) - User sees message: "You're on the waitlist (position #12). We'll notify you if a slot opens."
- If someone withdraws, next waitlist entry promoted to SUBMITTED
11. Email Notifications
Receipt Confirmation Email
Trigger: autoConfirmReceipt: true + project submitted
Template:
Subject: Application Received — MOPC 2026
Dear Sarah Johnson,
Thank you for submitting your application to the Monaco Ocean Protection Challenge 2026.
Application Details:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Project: Ocean Plastic Recycling Platform
Team: PlastiClean Solutions
Category: Business Concept
Confirmation #: MOPC-2026-00123
Submitted: February 15, 2026 at 3:42 PM UTC
What's Next?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. AI Screening: March 2-5, 2026
Your application will be automatically screened for eligibility.
2. Jury Evaluation: March 10-31, 2026
Expert judges will review eligible projects.
3. Semi-finalist Notification: April 5, 2026
Selected teams will be invited to the next round.
Track Your Progress:
View your application status anytime at:
https://monaco-opc.com/applicant/dashboard
Questions?
Contact us at admin@monaco-opc.com
Best regards,
MOPC Team
Deadline Reminder Emails
Trigger: Configured days before deadline (e.g., [7, 3, 1])
7-Day Reminder:
Subject: MOPC 2026 Application Deadline — 7 Days Remaining
Dear Applicant,
This is a friendly reminder that the MOPC 2026 application deadline is approaching.
Deadline: March 1, 2026 at 11:59 PM UTC
Time Remaining: 7 days
Have you started your application?
☐ Draft saved
☐ Documents uploaded
☐ Final submission
Complete your application:
https://monaco-opc.com/apply/mopc-2026
Need help? Contact admin@monaco-opc.com
Best regards,
MOPC Team
1-Day Reminder:
Subject: ⏰ MOPC 2026 Application Deadline — Tomorrow!
Dear Applicant,
The MOPC 2026 application deadline is tomorrow!
Deadline: March 1, 2026 at 11:59 PM UTC
Time Remaining: 23 hours 17 minutes
Don't miss out! Complete your application now:
https://monaco-opc.com/apply/mopc-2026
Best regards,
MOPC Team
12. API Changes (tRPC Procedures)
New/Modified Procedures
All procedures are in src/server/routers/ with these key changes:
applicant.getSubmissionBySlug — Get intake round info by slug (for public access) applicant.getMySubmission (enhanced) — Get current user's application (draft or submitted) applicant.saveDraft (new) — Auto-save form data as draft applicant.submitApplication (new) — Finalize draft and mark as submitted file.getUploadUrl (enhanced) — Get pre-signed URL for file upload file.confirmUpload (new) — Mark file upload as complete after successful S3 upload admin.getIntakeSubmissions — Admin dashboard for intake round admin.extendDeadline (new) — Create grace period for specific applicant
13. Service Functions
Key service functions in src/server/services/intake-round.ts:
canSubmitToIntakeRound()— Check if submission window is acceptingcheckCategoryQuota()— Validate category quotavalidateApplicationData()— Form validationvalidateFileUpload()— File requirement validationcheckRequiredFiles()— Verify all required files uploaded
14. Edge Cases
| Edge Case | Behavior | Solution |
|---|---|---|
| User starts draft, deadline passes | Draft is preserved but cannot submit | Show banner: "Deadline passed. Contact admin if you need extension." Admin can grant GracePeriod. |
| User submits at exact deadline second | Accept if server time <= windowCloseAt | Use database server time for consistency |
| Category quota reached mid-submission | Check quota again on final submit | Race condition: if quota hit between form start and submit, show error "Quota just filled" |
| File upload fails mid-submission | ProjectFile record exists but no S3 object | Cleanup orphaned records via cron job; allow re-upload |
| User replaces file after deadline | Check deadline on upload, not just submit | Each file upload checks canSubmitToWindow() |
| Team member email already registered | Invite sent, user can claim | Email contains link: "Join team or login to existing account" |
| Applicant deletes draft | Hard delete or soft delete? | Soft delete: set deletedAt field, hide from UI but keep for audit |
| Admin extends deadline globally | Update Round.windowCloseAt | All applicants benefit; no GracePeriod records needed |
| Duplicate submissions (same email) | One email = one project per competition | Upsert logic: update existing project instead of creating new |
| File version conflict | User uploads same requirement twice | Create new ProjectFile, link to old via replacedById |
| Draft expires while user editing | Auto-save fails with "Draft expired" | Extend expiry on each auto-save (rolling window) |
15. Integration Points
Connects to Filtering Round (Next Round)
When intake round closes:
- Create ProjectRoundState records
- Trigger filtering job
File System (MinIO)
- All uploads go to MinIO bucket:
mopc-submissions - Object keys:
projects/{projectId}/submissions/{submissionWindowId}/{filename}_{timestamp}.{ext} - Pre-signed URLs expire after 1 hour (uploads) or 24 hours (downloads)
Notification System
Events emitted:
INTAKE_SUBMISSION_RECEIVED— confirmation email + in-app notificationINTAKE_DEADLINE_APPROACHING— reminder emails (7d, 3d, 1d before)INTAKE_LATE_SUBMISSION— flag for admin reviewINTAKE_QUOTA_REACHED— notify admins
Audit Logging
All actions logged in DecisionAuditLog:
intake.draft_saved— auto-save triggeredintake.submission_finalized— final submitintake.file_uploaded— file addedintake.file_replaced— file updatedintake.deadline_extended— admin overrideintake.quota_reached— category quota hit
Document Complete
This specification defines the INTAKE round type for the redesigned MOPC architecture. Key takeaways:
- Typed Config: IntakeConfig replaces generic JSON with validated, documented fields
- SubmissionWindow: Decouples file requirements from round, enables multi-round submissions
- Deadline Policies: HARD, FLAG, GRACE with clear behavior differences
- Draft System: Auto-save + expiry for incomplete applications
- Category Quotas: Limit startups/concepts with overflow handling
- Email Automation: Confirmation + reminders built-in
- Admin Controls: Dashboard, deadline extensions, quota monitoring
Next documents:
- 05-round-filtering.md — AI screening and eligibility
- 06-round-evaluation.md — Jury review with multi-jury support
- 07-round-submission.md — Additional docs from advancing teams