# 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.

```typescript
// 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)

```typescript
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

1. **Access**: Applicant visits `/applicant/pipeline/{slug}` or receives invite link
2. **Auth**: User logs in (email magic link or password) or proceeds as guest
3. **Form**: Fills out project form (title, description, category, ocean issue, team info)
4. **Files**: Uploads required files (exec summary, business plan, video)
5. **Draft**: Can save as draft and return later
6. **Submit**: Final submission creates Project record with status "SUBMITTED"
7. **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

```prisma
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

```typescript
// 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)

```typescript
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

```typescript
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.

```prisma
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)

```typescript
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 = true` in config
- **Mentorship checkbox**: Always shown, default unchecked

---

## 5. Submission Window Integration

### SubmissionWindow Model (from 03-data-model.md)

```prisma
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

```typescript
// 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.

```typescript
// 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:

```typescript
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).

```typescript
// 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

1. User returns to application page
2. System checks for existing draft:
   ```typescript
   const draft = await prisma.project.findFirst({
     where: {
       programId,
       submittedByUserId: userId,
       isDraft: true,
       draftExpiresAt: { gt: new Date() }  // Not expired
     }
   })
   ```
3. If found, pre-populate form with `draft.draftDataJson`
4. User can continue editing or discard draft

### Validation States

```typescript
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:**
```typescript
{
  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 `GracePeriod` record for specific applicant
- This extends their personal deadline (doesn't affect global deadline)

### FLAG Policy Behavior

**Configuration:**
```typescript
{
  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.isLate` set to `true`

**Database Effect:**
```typescript
await prisma.projectFile.create({
  data: {
    projectId,
    submissionWindowId,
    requirementId,
    fileName,
    mimeType,
    size,
    bucket,
    objectKey,
    isLate: true,  // Flagged
    // ...
  }
})
```

### GRACE Policy Behavior

**Configuration:**
```typescript
{
  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:**
```typescript
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:**
```typescript
{
  categoryQuotasEnabled: true,
  categoryQuotas: {
    STARTUP: 100,
    BUSINESS_CONCEPT: 100
  },
  quotaOverflowPolicy: "reject"  // or "waitlist"
}
```

### Quota Enforcement Flow

```typescript
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 accepting
- `checkCategoryQuota()` — Validate category quota
- `validateApplicationData()` — Form validation
- `validateFileUpload()` — File requirement validation
- `checkRequiredFiles()` — 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:

1. Create ProjectRoundState records
2. 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 notification
- `INTAKE_DEADLINE_APPROACHING` — reminder emails (7d, 3d, 1d before)
- `INTAKE_LATE_SUBMISSION` — flag for admin review
- `INTAKE_QUOTA_REACHED` — notify admins

### Audit Logging

All actions logged in `DecisionAuditLog`:
- `intake.draft_saved` — auto-save triggered
- `intake.submission_finalized` — final submit
- `intake.file_uploaded` — file added
- `intake.file_replaced` — file updated
- `intake.deadline_extended` — admin override
- `intake.quota_reached` — category quota hit

---

## Document Complete

This specification defines the **INTAKE round type** for the redesigned MOPC architecture. Key takeaways:

1. **Typed Config**: IntakeConfig replaces generic JSON with validated, documented fields
2. **SubmissionWindow**: Decouples file requirements from round, enables multi-round submissions
3. **Deadline Policies**: HARD, FLAG, GRACE with clear behavior differences
4. **Draft System**: Auto-save + expiry for incomplete applications
5. **Category Quotas**: Limit startups/concepts with overflow handling
6. **Email Automation**: Confirmation + reminders built-in
7. **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
