# Jury Groups & Assignment Policy ## Overview Jury groups are first-class entities in the redesigned system. Each jury (Jury 1, Jury 2, Jury 3, award juries) is an explicit `JuryGroup` with named members, configurable assignment caps, category ratio preferences, and policy overrides. This document covers the data model, assignment algorithm, policy precedence, and admin controls. --- ## Data Model ### JuryGroup ```prisma model JuryGroup { id String @id @default(cuid()) competitionId String label String // "Jury 1", "Jury 2", "Live Finals Jury", custom per program description String? roundId String? // which round this jury evaluates (nullable for award juries) // Default policies for this group defaultCapMode CapMode @default(SOFT) defaultMaxProjects Int @default(15) softCapBuffer Int @default(10) defaultCategoryBias Json? // { STARTUP: 0.6, BUSINESS_CONCEPT: 0.4 } or null for no preference allowOnboardingSelfService Boolean @default(true) // judges can adjust cap/ratio during onboarding createdAt DateTime @default(now()) updatedAt DateTime @updatedAt competition Competition @relation(fields: [competitionId], references: [id]) round Round? @relation(fields: [roundId], references: [id]) members JuryGroupMember[] @@index([competitionId]) } ``` ### JuryGroupMember ```prisma model JuryGroupMember { id String @id @default(cuid()) juryGroupId String userId String role JuryGroupMemberRole @default(MEMBER) // CHAIR, MEMBER, OBSERVER // Per-member overrides (null = use group default) capMode CapMode? // override group default maxProjects Int? // override group default categoryBias Json? // override group default ratio // Onboarding self-service values (set by judge during onboarding if allowed) selfServiceCap Int? selfServiceBias Json? availabilityNotes String? // Free-text availability info joinedAt DateTime @default(now()) juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id]) user User @relation(fields: [userId], references: [id]) @@unique([juryGroupId, userId]) @@index([userId]) } enum JuryGroupMemberRole { CHAIR // Jury lead — can manage session, see aggregate data, moderate deliberation MEMBER // Regular juror — votes, scores, provides feedback OBSERVER // View-only — can see evaluations/deliberations but cannot vote or score } ``` **Role Permissions:** | Permission | CHAIR | MEMBER | OBSERVER | |-----------|-------|--------|----------| | Submit evaluations | Yes | Yes | No | | Vote in deliberation | Yes | Yes | No | | See aggregate scores (during deliberation) | Yes | No* | No | | Moderate deliberation discussion | Yes | No | No | | View all assigned projects | Yes | Yes | Yes (read-only) | | Be counted toward quorum | Yes | Yes | No | *Members see aggregates only if `showCollectiveRankings` is enabled in DeliberationConfig. ### Supporting Enums ```prisma enum CapMode { HARD // AI cannot assign more than maxProjects SOFT // AI tries to stay under maxProjects, can go up to maxProjects + softCapBuffer NONE // No cap (unlimited assignments) } ``` --- ## Cap Mode Behavior | Cap Mode | AI Assignment Behavior | Manual Assignment | |----------|----------------------|-------------------| | **HARD** | AI will NOT assign more than `maxProjects`. Period. | Admin can still force-assign beyond cap (creates `AssignmentException`). | | **SOFT** | AI tries to stay at `maxProjects`. If excess projects remain after distributing to all judges at their cap, excess is distributed evenly among SOFT judges up to `maxProjects + softCapBuffer`. | Same as HARD — admin can override. | | **NONE** | No limit. AI distributes projects as needed. | No limit. | ### Effective Cap Calculation The effective cap for a judge considers the 5-layer policy precedence: ``` effectiveCap(member) = member.selfServiceCap // Layer 4: onboarding self-service (if allowed and set) ?? member.maxProjects // Layer 4: admin per-member override ?? member.juryGroup.defaultMaxProjects // Layer 3: jury group default ?? program.defaultMaxProjects // Layer 2: program default ?? 15 // Layer 1: system default ``` Admin override (Layer 5) bypasses this entirely via `AssignmentException`. ### Soft Cap Overflow Algorithm 1. Assign projects to all judges up to their effective cap, respecting category bias 2. Count remaining unassigned projects 3. If remaining > 0 and SOFT-cap judges exist: - Distribute remaining projects evenly among SOFT judges - Stop when each SOFT judge reaches `effectiveCap + softCapBuffer` 4. Any projects still unassigned go to the **Unassigned Queue** --- ## Category Quotas & Ratio Bias ### CategoryQuotas (per JuryGroup) ```typescript type CategoryQuotas = { STARTUP: { min: number; max: number } BUSINESS_CONCEPT: { min: number; max: number } } ``` ### Category Bias (per member) Category bias is a **soft preference**, not a hard constraint. It influences the assignment algorithm's project selection but does not guarantee exact ratios. ```typescript type CategoryBias = { STARTUP: number // e.g., 0.6 = prefer 60% startups BUSINESS_CONCEPT: number // e.g., 0.4 = prefer 40% concepts } ``` Special cases: - `{ STARTUP: 1.0, BUSINESS_CONCEPT: 0.0 }` — judge reviews only startups - `{ STARTUP: 0.0, BUSINESS_CONCEPT: 1.0 }` — judge reviews only concepts - `null` — no preference, algorithm decides **Disclosure:** When bias is set, it is disclosed to judges on their dashboard (e.g., "You have been assigned primarily Startup projects based on your preference"). --- ## 5-Layer Policy Precedence | Layer | Scope | Who Sets It | Override Behavior | |-------|-------|-------------|-------------------| | 1 | System default | Platform config | Baseline for all programs | | 2 | Program default | Program admin | Overrides system default for this program | | 3 | Jury group default | Program admin | Overrides program default for this jury | | 4a | Per-member override | Program admin | Overrides jury group default for this judge | | 4b | Self-service override | Judge (during onboarding) | Only if `allowOnboardingSelfService` is true. Cannot exceed admin-set maximum. | | 5 | Admin override | Program admin / Super admin | Always wins. Creates `AssignmentException` with audit trail. | Policy resolution proceeds from Layer 5 → Layer 1. The first non-null value wins. --- ## Judge Onboarding Self-Service When `allowOnboardingSelfService` is enabled on the JuryGroup: 1. Judge receives invitation email with link to accept 2. During onboarding, judge sees their assigned cap and category preference 3. Judge can adjust: - **Max projects**: up or down within admin-defined bounds - **Category preference**: ratio of startups vs concepts 4. Adjusted values stored in `JuryGroupMember.selfServiceCap` and `JuryGroupMember.selfServiceBias` 5. Admin can review and override self-service values at any time --- ## Assignment Algorithm ### Input - List of eligible projects (with category) - List of JuryGroupMembers (with effective caps, biases, COI declarations) - CategoryQuotas for the JuryGroup ### Steps 1. **COI Check**: For each judge-project pair, check COI declarations. Skip if COI exists. 2. **Category Allocation**: Divide projects into STARTUP and BUSINESS_CONCEPT pools. 3. **Primary Assignment** (up to effective cap): - For each judge, select projects matching their category bias - Apply geo-diversity penalty and familiarity bonus (existing algorithm) - Stop when judge reaches effective cap 4. **Soft Cap Overflow** (if unassigned projects remain): - Identify SOFT-cap judges - Distribute remaining projects evenly up to cap + buffer 5. **Unassigned Queue**: Any remaining projects enter the queue with reason codes ### Reason Codes for Unassigned Queue | Code | Meaning | |------|---------| | `ALL_HARD_CAPPED` | All judges at hard cap | | `SOFT_BUFFER_EXHAUSTED` | All soft-cap judges at cap + buffer | | `COI_CONFLICT` | Project has COI with all available judges | | `CATEGORY_IMBALANCE` | No judges available for this category | | `MANUAL_ONLY` | Project flagged for manual assignment | --- ## AssignmentIntent (Pre-Assignment at Invite Time) From the invite/onboarding integration: when a jury member is invited, the admin can optionally pre-assign specific projects. ```prisma model AssignmentIntent { id String @id @default(cuid()) juryGroupMemberId String roundId String projectId String source AssignmentIntentSource // INVITE | ADMIN | SYSTEM status AssignmentIntentStatus @default(PENDING) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt juryGroupMember JuryGroupMember @relation(...) round Round @relation(...) project Project @relation(...) @@unique([juryGroupMemberId, roundId, projectId]) @@index([status]) } enum AssignmentIntentSource { INVITE // set during invitation ADMIN // set by admin after invite SYSTEM // set by AI suggestion } enum AssignmentIntentStatus { PENDING // Created, awaiting assignment algorithm execution HONORED // Algorithm materialized this intent into an Assignment record OVERRIDDEN // Admin changed the assignment, superseding this intent EXPIRED // Round completed without this intent being honored CANCELLED // Explicitly cancelled by admin or system } ``` ### Intent Lifecycle State Machine ``` PENDING ──── [algorithm runs, intent matched] ──── → HONORED │ ├──── [admin changes assignment] ──────────── → OVERRIDDEN ├──── [round completes unmatched] ─────────── → EXPIRED └──── [admin/system cancels] ──────────────── → CANCELLED ``` **Lifecycle rules:** - **PENDING → HONORED**: When the assignment algorithm runs and creates an `Assignment` record matching this intent's judge+project pair. The `AssignmentIntent.id` is stored on the `Assignment` as provenance. - **PENDING → OVERRIDDEN**: When an admin manually assigns the project to a different judge, or removes the intended judge from the JuryGroup. The override is logged to `DecisionAuditLog`. - **PENDING → EXPIRED**: When the round transitions to CLOSED and this intent was never materialized. Automatic batch update on round close. - **PENDING → CANCELLED**: When an admin explicitly removes the intent, or when the judge is removed from the JuryGroup. Source logged. - **Terminal states**: HONORED, OVERRIDDEN, EXPIRED, CANCELLED are all terminal. No transitions out. **Algorithm integration:** 1. Before algorithmic assignment, load all PENDING intents for the round 2. For each intent, attempt to create the assignment (respecting COI, cap checks) 3. If assignment succeeds → mark intent HONORED 4. If assignment fails (COI, cap exceeded) → keep PENDING, add to unassigned queue with reason code `INTENT_BLOCKED` 5. Admin can manually resolve blocked intents --- ## AssignmentException (Audited Over-Cap Assignment) When an admin manually assigns a project to a judge who is at or beyond their cap: ```prisma model AssignmentException { id String @id @default(cuid()) assignmentId String // the actual assignment record reason String // admin must provide justification overCapBy Int // how many over the effective cap approvedById String // admin who approved createdAt DateTime @default(now()) approvedBy User @relation(...) } ``` All exceptions are visible in the assignment audit view and included in compliance reports. --- ## Cross-Jury Membership Judges can belong to multiple JuryGroups simultaneously: - Judge A is on Jury 1 AND Jury 2 - Judge B is on Jury 2 AND the Innovation Award jury - Judge C is on Jury 1, Jury 2, AND Jury 3 Each membership has independent cap/bias configuration. A judge's Jury 1 cap of 20 doesn't affect their Jury 2 cap of 10. --- ## Admin "Juries" UI Section A dedicated admin section for managing JuryGroups: - **Create Jury Group**: name, description, linked round, default policies - **Manage Members**: add/remove members, set per-member overrides, view self-service adjustments - **View Assignments**: see all assignments for the group, identify unassigned projects - **Manual Assignment**: drag-and-drop or bulk-assign unassigned projects - **Assignment Audit**: view all exceptions, overrides, and intent fulfillment This is separate from the round configuration — JuryGroups are entities that exist independently and are linked to rounds. See [08-platform-integration-matrix.md](./08-platform-integration-matrix.md) for full page mapping. --- ## Manual Override Admin can override ANY assignment decision at ANY time: - Reassign a project from one judge to another - Assign a project to a judge beyond their cap (creates `AssignmentException`) - Remove an assignment - Change a judge's cap or bias mid-round - Override the algorithm's category allocation All overrides are logged to `DecisionAuditLog` with the admin's identity, timestamp, and reason.