# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**MOPC (Monaco Ocean Protection Challenge)** — a secure jury voting platform for managing multi-round project selection. Jury members evaluate ocean conservation projects through configurable competitions with rounds (intake, filtering, evaluation, submission, mentoring, live finals, deliberation). Domain: `monaco-opc.com`.

## Common Commands

```bash
# Development
npm run dev           # Next.js dev (Turbopack)
npm run build         # Production build (always test before push)
npm run typecheck     # tsc --noEmit

# Database
npx prisma generate   # Regenerate client after schema changes
npx prisma migrate dev  # Create + apply migration
npx prisma studio     # GUI database browser
npm run db:seed       # Run seed (tsx prisma/seed.ts)

# Testing (vitest, not jest)
npx vitest                        # Run all tests (watch mode)
npx vitest run                    # Run all tests once
npx vitest run tests/unit/round-engine.test.ts  # Single file
npx vitest run -t 'test name'     # Single test by name

# Code Quality
npm run lint          # ESLint
npm run format        # Prettier

# Docker (production)
docker compose -f docker/docker-compose.yml up -d
docker compose -f docker/docker-compose.dev.yml up -d  # Dev stack
```

## Tech Stack

Next.js 15 (App Router) | TypeScript 5 (strict) | Tailwind CSS 4 | shadcn/ui | tRPC 11 (superjson) | Prisma 6 | PostgreSQL 16 | NextAuth 5 (Auth.js) | Vitest 4 | OpenAI SDK 6 | MinIO (S3) | Nodemailer + Poste.io

## Architecture

### Data Flow: Prisma → tRPC → React

End-to-end type safety: `prisma/schema.prisma` defines models → `src/server/routers/*.ts` expose tRPC procedures with Zod validation → `src/lib/trpc/client.ts` provides typed React hooks → components call `trpc.domain.procedure.useQuery()`.

### tRPC Middleware Hierarchy (`src/server/trpc.ts`)

All role-based access is enforced via procedure types:

| Procedure | Roles Allowed |
|-----------|---------------|
| `publicProcedure` | Anyone (no auth) |
| `protectedProcedure` | Any authenticated user |
| `adminProcedure` | SUPER_ADMIN, PROGRAM_ADMIN |
| `superAdminProcedure` | SUPER_ADMIN only |
| `juryProcedure` | JURY_MEMBER only |
| `mentorProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, MENTOR |
| `observerProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, OBSERVER |
| `awardMasterProcedure` | SUPER_ADMIN, PROGRAM_ADMIN, AWARD_MASTER |
| `audienceProcedure` | Any authenticated user |

### Route Groups (Next.js App Router)

- `src/app/(auth)/` — Public auth pages (login, verify, accept-invite, onboarding)
- `src/app/(admin)/` — Admin dashboard, competition management, round config
- `src/app/(jury)/` — Jury evaluation interface, round assignments, live voting
- `src/app/(applicant)/` — Applicant dashboard, competition progress, document uploads
- `src/app/(mentor)/` — Mentor workspace

### Competition System

The core domain model. A **Competition** represents a complete evaluation cycle:

```
Competition → has many Rounds (ordered)
```

- **Competition** (`src/server/routers/competition.ts`): Top-level competition config with program link, status, and settings
- **Round** (`RoundType: INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | DELIBERATION`): Each competition has ordered rounds with type-specific config in `configJson`

Key models:
- `JuryGroup` — Named jury panel with chair/member/observer roles
- `AdvancementRule` — Auto-advance, score threshold, top-N, admin selection between rounds
- `SubmissionWindow` — File submission deadlines per round
- `AssignmentPolicy` / `AssignmentIntent` — Governance layer for jury assignment
- `ProjectRoundState` — Per-project state within each round
- `DeliberationSession` / `DeliberationVote` / `ResultLock` — Structured deliberation and result finalization

Key services:
- `src/server/services/round-engine.ts` — State machine for round transitions
- `src/server/services/round-assignment.ts` — Jury assignment generation with policy enforcement
- `src/server/services/submission-manager.ts` — File submission + filtering with duplicate detection
- `src/server/services/deliberation.ts` — Deliberation session management and vote tallying
- `src/server/services/result-lock.ts` — Result finalization and unlock governance
- `src/server/services/live-control.ts` — Live ceremony cursor management
- `src/server/services/competition-context.ts` — Cross-cutting competition context resolver

Competition types: `src/types/competition.ts`, `src/types/competition-configs.ts`

### AI Services (`src/server/services/ai-*.ts`)

All AI calls anonymize data before sending to OpenAI. Services:
- `ai-filtering.ts` — AI-powered project screening with rubric
- `ai-assignment.ts` — GPT-suggested jury-project matching
- `ai-evaluation-summary.ts` — Strengths/weaknesses synthesis from evaluations
- `ai-tagging.ts` — Auto-tagging projects
- `ai-award-eligibility.ts` — Award eligibility assessment
- `ai-shortlist.ts` — AI-powered shortlist recommendations
- `anonymization.ts` — Strips PII before AI calls

### Auth System

NextAuth v5 with two providers: **Email** (magic links) and **Credentials** (password + invite token). Failed login tracking with 5-attempt lockout (15 min). Session includes `user.role` for RBAC.

### Docker Entrypoint (`docker/docker-entrypoint.sh`)

On container start: retry `prisma migrate deploy` → `prisma generate` → auto-seed if User table is empty → `node server.js`. A `docker compose down -v && docker compose up -d` will run all migrations from scratch and seed.

## Testing Infrastructure

- **Framework**: Vitest 4 with `fileParallelism: false` and `pool: 'forks'` (tests run sequentially)
- **Setup**: `tests/setup.ts` provides `prisma` client (uses `DATABASE_URL_TEST` or `DATABASE_URL`), `createTestContext(user)`, `createCaller(router, user)`
- **Factories**: `tests/helpers.ts` has `createTestUser()`, `createTestProgram()`, `createTestCompetition()`, `createTestRound()`, `uid()` helper, and `cleanupTestData()`
- **Pattern**: Create data with factories → build caller with `createCaller(routerModule, user)` → call procedures → assert → cleanup in `afterAll`

## User Roles

`SUPER_ADMIN` | `PROGRAM_ADMIN` | `JURY_MEMBER` | `OBSERVER` | `MENTOR` | `APPLICANT` | `AWARD_MASTER` | `AUDIENCE`

## Coding Standards

- **TypeScript**: Strict mode, `type` over `interface`, prefer `unknown` over `any`
- **Files**: kebab-case. **Components**: PascalCase. **DB models**: PascalCase in Prisma
- **React**: Server Components by default, `'use client'` only when needed
- **Styling**: Tailwind utility classes, mobile-first (`md:`, `lg:` breakpoints), shadcn/ui as base
- **tRPC**: Group by domain (`trpc.competition.create()`), Zod input validation, `TRPCError` for errors
- **Brand colors**: Primary Red `#de0f1e`, Dark Blue `#053d57`, White `#fefefe`, Teal `#557f8c`
- **Typography**: Montserrat (600/700 headings, 300/400 body)

## Key Constraints

1. Jury can only see assigned projects (enforced at query level)
2. Voting windows are strict — submissions blocked outside active window
3. All admin actions are audited via `DecisionAuditLog`
4. Files accessed via MinIO pre-signed URLs only (no public bucket)
5. COI declaration required before evaluation (blocking dialog)
6. Smart assignment skips COI-declared jurors, applies geo-diversity penalty and familiarity bonus
7. Cron endpoints protected by `CRON_SECRET` header
8. Round notifications never throw — all errors caught and logged

## Security

- CSRF: tRPC uses `application/json` (triggers CORS preflight). Do NOT add permissive CORS headers.
- Rate limiting: 100 req/min tRPC, 10 req/min auth, 5-attempt lockout
- AI privacy: All data anonymized before OpenAI calls

## Seed Data

`prisma/seed.ts` imports from `docs/Candidatures 2026 *.csv` with special handling for non-breaking spaces (U+00A0) around French guillemets. Includes ALL CSV rows (no filtering/dedup) — duplicate detection happens in `submission-manager.ts` at runtime.

## Environment Variables

Required: `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_BUCKET`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `EMAIL_FROM`, `OPENAI_API_KEY`, `CRON_SECRET`

External services (pre-existing on VPS): MinIO `:9000`, Poste.io `:587`, Nginx reverse proxy with SSL.

## Git

Remote: `code.monaco-opc.com/MOPC/MOPC-Portal`. Branch: `main`. Always `npm run build` before pushing.
