MOPC-App/CLAUDE.md

8.6 KiB

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

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