# Round: Evaluation (Jury 1 & Jury 2) ## 1. Purpose & Position in Flow The EVALUATION round is the core judging mechanism of the competition. It appears **twice** in the standard flow: | Instance | Name | Position | Jury | Purpose | Output | |----------|------|----------|------|---------|--------| | Round 3 | "Jury 1 — Semi-finalist Selection" | After FILTERING | Jury 1 | Score projects, select semi-finalists | Semi-finalists per category | | Round 5 | "Jury 2 — Finalist Selection" | After SUBMISSION Round 2 | Jury 2 | Score semi-finalists, select finalists + awards | Finalists per category | Both instances use the same `RoundType.EVALUATION` but are configured independently with: - Different jury groups (Jury 1 vs Jury 2) - Different evaluation forms/rubrics - Different visible submission windows (Jury 1 sees Window 1 only; Jury 2 sees Windows 1+2) - Different advancement counts --- ## 2. Data Model ### Round Record ``` Round { id: "round-jury-1" competitionId: "comp-2026" name: "Jury 1 — Semi-finalist Selection" roundType: EVALUATION status: ROUND_DRAFT → ROUND_ACTIVE → ROUND_CLOSED sortOrder: 2 windowOpenAt: "2026-04-01" // Evaluation window start windowCloseAt: "2026-04-30" // Evaluation window end juryGroupId: "jury-group-1" // Links to Jury 1 submissionWindowId: null // EVALUATION rounds don't collect submissions configJson: { ...EvaluationConfig } } ``` ### EvaluationConfig ```typescript type EvaluationConfig = { // --- Assignment Settings --- requiredReviewsPerProject: number // How many jurors review each project (default: 3) // --- Scoring Mode --- scoringMode: "criteria" | "global" | "binary" // criteria: Score per criterion + weighted total // global: Single 1-10 score // binary: Yes/No decision (semi-finalist worthy?) requireFeedback: boolean // Must provide text feedback (default: true) // --- COI --- coiRequired: boolean // Must declare COI before evaluating (default: true) // --- Peer Review --- peerReviewEnabled: boolean // Jurors can see anonymized peer evaluations after submission anonymizationLevel: "fully_anonymous" | "show_initials" | "named" // --- AI Features --- aiSummaryEnabled: boolean // Generate AI-powered evaluation summaries aiAssignmentEnabled: boolean // Allow AI-suggested jury-project matching // --- Advancement --- advancementMode: "auto_top_n" | "admin_selection" | "ai_recommended" advancementConfig: { perCategory: boolean // Separate counts per STARTUP / BUSINESS_CONCEPT startupCount: number // How many startups advance (default: 10 for Jury 1, 3 for Jury 2) conceptCount: number // How many concepts advance tieBreaker: "admin_decides" | "highest_individual" | "revote" } } ``` ### Related Models | Model | Role | |-------|------| | `JuryGroup` | Named jury entity linked to this round | | `JuryGroupMember` | Members of the jury with per-juror overrides | | `Assignment` | Juror-project pairing for this round, linked to JuryGroup | | `Evaluation` | Score/feedback submitted by a juror for one project | | `EvaluationForm` | Rubric/criteria definition for this round | | `ConflictOfInterest` | COI declaration per assignment | | `GracePeriod` | Per-juror deadline extension | | `EvaluationSummary` | AI-generated insights per project per round | | `EvaluationDiscussion` | Peer review discussion threads | | `RoundSubmissionVisibility` | Which submission windows' docs jury can see | | `AdvancementRule` | How projects advance after evaluation | | `ProjectRoundState` | Per-project state in this round | --- ## 3. Setup Phase (Before Window Opens) ### 3.1 Admin Creates the Evaluation Round Admin uses the competition wizard or round management UI to: 1. **Create the Round** with type EVALUATION 2. **Link a JuryGroup** — select "Jury 1" (or create a new jury group) 3. **Set the evaluation window** — start and end dates 4. **Configure the evaluation form** — scoring criteria, weights, scales 5. **Set visibility** — which submission windows jury can see (via RoundSubmissionVisibility) 6. **Configure advancement rules** — how many advance per category ### 3.2 Jury Group Configuration The linked JuryGroup has: ``` JuryGroup { name: "Jury 1" defaultMaxAssignments: 20 // Default cap per juror defaultCapMode: SOFT // HARD | SOFT | NONE softCapBuffer: 2 // Can exceed by 2 for load balancing categoryQuotasEnabled: true defaultCategoryQuotas: { "STARTUP": { "min": 3, "max": 15 }, "BUSINESS_CONCEPT": { "min": 3, "max": 15 } } allowJurorCapAdjustment: true // Jurors can adjust their cap during onboarding allowJurorRatioAdjustment: true // Jurors can adjust their category preference } ``` ### 3.3 Per-Juror Overrides Each `JuryGroupMember` can override group defaults: ``` JuryGroupMember { juryGroupId: "jury-group-1" userId: "judge-alice" maxAssignmentsOverride: 25 // Alice wants more projects capModeOverride: HARD // Alice: hard cap, no exceptions categoryQuotasOverride: { "STARTUP": { "min": 5, "max": 20 }, // Alice prefers startups "BUSINESS_CONCEPT": { "min": 0, "max": 5 } } preferredStartupRatio: 0.8 // 80% startups } ``` ### 3.4 Juror Onboarding (Optional) If `allowJurorCapAdjustment` or `allowJurorRatioAdjustment` is true: 1. When a juror first opens their jury dashboard after being added to the group 2. A one-time onboarding dialog appears: - "Your default maximum is 20 projects. Would you like to adjust?" (slider) - "Your default startup/concept ratio is 50/50. Would you like to adjust?" (slider) 3. Juror saves preferences → stored in `JuryGroupMember.maxAssignmentsOverride` and `preferredStartupRatio` 4. Dialog doesn't appear again (tracked via `JuryGroupMember.updatedAt` or a flag) --- ## 4. Assignment System (Enhanced) ### 4.1 Assignment Algorithm — Jury-Group-Aware The current `stage-assignment.ts` algorithm is enhanced to: 1. **Filter jury pool by JuryGroup** — only members of the linked jury group are considered 2. **Apply hard/soft cap logic** per juror 3. **Apply category quotas** per juror 4. **Score candidates** using existing expertise matching + workload balancing + geo-diversity #### Effective Limits Resolution ```typescript function getEffectiveLimits(member: JuryGroupMember, group: JuryGroup): EffectiveLimits { return { maxAssignments: member.maxAssignmentsOverride ?? group.defaultMaxAssignments, capMode: member.capModeOverride ?? group.defaultCapMode, softCapBuffer: group.softCapBuffer, // Group-level only (not per-juror) categoryQuotas: member.categoryQuotasOverride ?? group.defaultCategoryQuotas, categoryQuotasEnabled: group.categoryQuotasEnabled, preferredStartupRatio: member.preferredStartupRatio, } } ``` #### Cap Enforcement Logic ```typescript function canAssignMore( jurorId: string, projectCategory: CompetitionCategory, currentLoad: LoadTracker, limits: EffectiveLimits ): { allowed: boolean; penalty: number; reason?: string } { const total = currentLoad.total(jurorId) const catLoad = currentLoad.byCategory(jurorId, projectCategory) // 1. HARD cap check if (limits.capMode === "HARD" && total >= limits.maxAssignments) { return { allowed: false, penalty: 0, reason: "Hard cap reached" } } // 2. SOFT cap check (can exceed by buffer) let overflowPenalty = 0 if (limits.capMode === "SOFT") { if (total >= limits.maxAssignments + limits.softCapBuffer) { return { allowed: false, penalty: 0, reason: "Soft cap + buffer exceeded" } } if (total >= limits.maxAssignments) { // In buffer zone — apply increasing penalty overflowPenalty = (total - limits.maxAssignments + 1) * 15 } } // 3. Category quota check if (limits.categoryQuotasEnabled && limits.categoryQuotas) { const quota = limits.categoryQuotas[projectCategory] if (quota) { if (catLoad >= quota.max) { return { allowed: false, penalty: 0, reason: `Category ${projectCategory} max reached (${quota.max})` } } // Bonus for under-min if (catLoad < quota.min) { overflowPenalty -= 15 // Negative penalty = bonus } } } // 4. Ratio preference alignment if (limits.preferredStartupRatio != null && total > 0) { const currentStartupRatio = currentLoad.byCategory(jurorId, "STARTUP") / total const isStartup = projectCategory === "STARTUP" const wantMore = isStartup ? currentStartupRatio < limits.preferredStartupRatio : currentStartupRatio > limits.preferredStartupRatio if (wantMore) overflowPenalty -= 10 // Bonus for aligning with preference else overflowPenalty += 10 // Penalty for diverging } return { allowed: true, penalty: overflowPenalty } } ``` ### 4.2 Assignment Flow ``` 1. Admin opens Assignment panel for Round 3 (Jury 1) 2. System loads: - Projects with ProjectRoundState PENDING/IN_PROGRESS in this round - JuryGroup members (with effective limits) - Existing assignments (to avoid duplicates) - COI records (to skip conflicted pairs) 3. Admin clicks "Generate Suggestions" 4. Algorithm runs: a. For each project (sorted by fewest current assignments): - Score each eligible juror (tag matching + workload + geo + cap/quota penalties) - Select top N jurors (N = requiredReviewsPerProject - existing reviews) - Track load in jurorLoadMap b. Report unassigned projects (jurors at capacity) 5. Admin reviews preview: - Assignment matrix (juror × project grid) - Load distribution chart - Unassigned projects list - Category distribution per juror 6. Admin can: - Accept all suggestions - Modify individual assignments (drag-drop or manual add/remove) - Re-run with different parameters 7. Admin clicks "Apply Assignments" 8. System creates Assignment records with juryGroupId set 9. Notifications sent to jurors ``` ### 4.3 AI-Powered Assignment (Optional) If `aiAssignmentEnabled` is true in config: 1. Admin clicks "AI Assignment Suggestions" 2. System calls `ai-assignment.ts`: - Anonymizes juror profiles and project descriptions - Sends to GPT with matching instructions - Returns confidence scores and reasoning 3. AI suggestions shown alongside algorithm suggestions 4. Admin picks which to use or mixes both ### 4.4 Handling Unassigned Projects When all jurors with SOFT cap reach cap+buffer: 1. Remaining projects become "unassigned" 2. Admin dashboard highlights these prominently 3. Admin can: - Manually assign to specific jurors (bypasses cap — manual override) - Increase a juror's cap - Add more jurors to the jury group - Reduce `requiredReviewsPerProject` for remaining projects --- ## 5. Jury Evaluation Experience ### 5.1 Jury Dashboard When a Jury 1 member opens their dashboard: ``` ┌─────────────────────────────────────────────────────┐ │ JURY 1 — Semi-finalist Selection │ │ ─────────────────────────────────────────────────── │ │ Evaluation Window: April 1 – April 30 │ │ ⏱ 12 days remaining │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ │ 15 │ │ 8 │ │ 2 │ │ 5 │ │ │ │ Total │ │ Complete │ │ In Draft │ │ Pending│ │ │ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ │ │ │ [Continue Next Evaluation →] │ │ │ │ Recent Assignments │ │ ┌──────────────────────────────────────────────┐ │ │ │ OceanClean AI │ Startup │ ✅ Done │ View │ │ │ │ Blue Carbon Hub │ Concept │ ⏳ Draft │ Cont │ │ │ │ SeaWatch Monitor │ Startup │ ⬜ Pending│ Start│ │ │ │ ... │ │ │ └──────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` Key elements: - **Deadline countdown** — prominent timer showing days/hours remaining - **Progress stats** — total, completed, in-draft, pending - **Quick action CTA** — jump to next unevaluated project - **Assignment list** — sorted by status (pending first, then drafts, then done) ### 5.2 COI Declaration (Blocking) Before evaluating any project, the juror MUST declare COI: ``` ┌───────────────────────────────────────────┐ │ Conflict of Interest Declaration │ │ │ │ Do you have a conflict of interest with │ │ "OceanClean AI" (Startup)? │ │ │ │ ○ No conflict — I can evaluate fairly │ │ ○ Yes, I have a conflict: │ │ Type: [Financial ▾] │ │ Description: [________________] │ │ │ │ [Submit Declaration] │ └───────────────────────────────────────────┘ ``` - If **No conflict**: Proceed to evaluation form - If **Yes**: Assignment flagged, admin notified, juror may be reassigned - COI declaration is logged in `ConflictOfInterest` model - Admin can review and take action (cleared / reassigned / noted) ### 5.3 Evaluation Form The form adapts to the `scoringMode`: #### Criteria Mode (default for Jury 1 and Jury 2) ``` ┌───────────────────────────────────────────────────┐ │ Evaluating: OceanClean AI (Startup) │ │ ──────────────────────────────────────────────── │ │ │ │ [📄 Documents] [📊 Scoring] [💬 Feedback] │ │ │ │ ── DOCUMENTS TAB ── │ │ ┌─ Round 1 Application Docs ─────────────────┐ │ │ │ 📄 Executive Summary.pdf [Download] │ │ │ │ 📄 Business Plan.pdf [Download] │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ (Jury 2 also sees:) │ │ ┌─ Round 2 Semi-finalist Docs ────────────────┐ │ │ │ 📄 Updated Business Plan.pdf [Download] │ │ │ │ 🎥 Video Pitch.mp4 [Play] │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ── SCORING TAB ── │ │ Innovation & Impact [1] [2] [3] [4] [5] (w:30%)│ │ Feasibility [1] [2] [3] [4] [5] (w:25%)│ │ Team & Execution [1] [2] [3] [4] [5] (w:25%)│ │ Ocean Relevance [1] [2] [3] [4] [5] (w:20%)│ │ │ │ Overall Score: 3.8 / 5.0 (auto-calculated) │ │ │ │ ── FEEDBACK TAB ── │ │ Feedback: [________________________________] │ │ │ │ [💾 Save Draft] [✅ Submit Evaluation] │ │ (Auto-saves every 30s) │ └───────────────────────────────────────────────────┘ ``` #### Binary Mode (optional for quick screening) ``` Should this project advance to the semi-finals? [✅ Yes] [❌ No] Justification (required): [________________] ``` #### Global Score Mode ``` Overall Score: [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] Feedback (required): [________________] ``` ### 5.4 Document Visibility (Cross-Round) Controlled by `RoundSubmissionVisibility`: | Round | Sees Window 1 ("Application Docs") | Sees Window 2 ("Semi-finalist Docs") | |-------|------------------------------------|-----------------------------------------| | Jury 1 (Round 3) | Yes | No (doesn't exist yet) | | Jury 2 (Round 5) | Yes | Yes | | Jury 3 (Round 7) | Yes | Yes | In the evaluation UI: - Documents are grouped by submission window - Each group has a label (from `RoundSubmissionVisibility.displayLabel`) - Clear visual separation (tabs, accordion sections, or side panels) ### 5.5 Auto-Save and Submission - **Auto-save**: Client debounces and calls `evaluation.autosave` every 30 seconds while draft is open - **Draft status**: Evaluation starts as NOT_STARTED → DRAFT on first save → SUBMITTED on explicit submit - **Submission validation**: - All required criteria scored (if criteria mode) - Global score provided (if global mode) - Binary decision selected (if binary mode) - Feedback text provided (if `requireFeedback`) - Window is open (or juror has grace period) - **After submission**: Evaluation becomes read-only for juror (status = SUBMITTED) - **Admin can lock**: Set status to LOCKED to prevent any further changes ### 5.6 Grace Periods ``` GracePeriod { roundId: "round-jury-1" userId: "judge-alice" projectId: null // Applies to ALL Alice's assignments in this round extendedUntil: "2026-05-02" // 2 days after official close reason: "Travel conflict" grantedById: "admin-1" } ``` - Admin can grant per-juror or per-juror-per-project grace periods - Evaluation submission checks grace period before rejecting past-window submissions - Dashboard shows "(Grace period: 2 extra days)" badge for affected jurors --- ## 6. End of Evaluation — Results & Advancement ### 6.1 Results Visualization When the evaluation window closes, the admin sees: ``` ┌──────────────────────────────────────────────────────────────┐ │ Jury 1 Results │ │ ─────────────────────────────────────────────────────────── │ │ │ │ Completion: 142/150 evaluations submitted (94.7%) │ │ Outstanding: 8 (3 jurors have pending evaluations) │ │ │ │ ┌─ STARTUPS (Top 10) ──────────────────────────────────────┐│ │ │ # Project Avg Score Consensus Reviews Status ││ │ │ 1 OceanClean AI 4.6/5 0.92 3/3 ✅ ││ │ │ 2 SeaWatch 4.3/5 0.85 3/3 ✅ ││ │ │ 3 BlueCarbon 4.1/5 0.78 3/3 ✅ ││ │ │ ... ││ │ │ 10 TidalEnergy 3.2/5 0.65 3/3 ✅ ││ │ │ ── cutoff line ────────────────────────────────────────── ││ │ │ 11 WavePower 3.1/5 0.71 3/3 ⬜ ││ │ │ 12 CoralGuard 2.9/5 0.55 2/3 ⚠️ ││ │ └──────────────────────────────────────────────────────────┘│ │ │ │ ┌─ CONCEPTS (Top 10) ──────────────────────────────────────┐│ │ │ (same layout) ││ │ └──────────────────────────────────────────────────────────┘│ │ │ │ [🤖 AI Recommendation] [📊 Score Distribution] [Export] │ │ │ │ [✅ Approve Shortlist] [✏️ Edit Shortlist] │ └──────────────────────────────────────────────────────────────┘ ``` **Metrics shown:** - Average global score (or weighted criteria average) - Consensus score (1 - normalized stddev, where 1.0 = full agreement) - Review count / required - Per-criterion averages (expandable) ### 6.2 AI Recommendation When admin clicks "AI Recommendation": 1. System calls `ai-evaluation-summary.ts` for each project in bulk 2. AI generates: - Ranked shortlist per category based on scores + feedback analysis - Strengths, weaknesses, themes per project - Recommendation: "Advance" / "Borderline" / "Do not advance" 3. Admin sees AI recommendation alongside actual scores 4. AI recommendations are suggestions only — admin has final say ### 6.3 Advancement Decision ``` Advancement Mode: admin_selection (with AI recommendation) 1. System shows ranked list per category 2. AI highlights recommended top N per category 3. Admin can: - Accept AI recommendation - Drag projects to reorder - Add/remove projects from advancement list - Set custom cutoff line 4. Admin clicks "Confirm Advancement" 5. System: a. Sets ProjectRoundState to PASSED for advancing projects b. Sets ProjectRoundState to REJECTED for non-advancing projects c. Updates Project.status to SEMIFINALIST (Jury 1) or FINALIST (Jury 2) d. Logs all decisions in DecisionAuditLog e. Sends notifications to all teams (advanced / not selected) ``` ### 6.4 Advancement Modes | Mode | Behavior | |------|----------| | `auto_top_n` | Top N per category automatically advance when window closes | | `admin_selection` | Admin manually selects who advances (with AI/score guidance) | | `ai_recommended` | AI proposes list, admin must approve/modify | --- ## 7. Special Awards Integration (Jury 2 Only) During the Jury 2 evaluation round, special awards can run alongside: ### 7.1 How It Works ``` Round 5: "Jury 2 — Finalist Selection" ├── Main evaluation (all semi-finalists scored by Jury 2) └── Special Awards (run in parallel): ├── "Innovation Award" — STAY_IN_MAIN mode │ Projects remain in main eval, flagged as eligible │ Award jury (subset of Jury 2 or separate) votes └── "Impact Award" — SEPARATE_POOL mode AI filters eligible projects into award pool Dedicated jury evaluates and votes ``` ### 7.2 SpecialAward.evaluationRoundId Each award links to the evaluation round it runs alongside: ``` SpecialAward { evaluationRoundId: "round-jury-2" // Runs during Jury 2 eligibilityMode: STAY_IN_MAIN juryGroupId: "jury-group-innovation" // Can be same or different jury } ``` ### 7.3 Award Evaluation Flow 1. Before Jury 2 window opens: Admin runs award eligibility (AI or manual) 2. During Jury 2 window: Award jury members see their award assignments alongside regular evaluations 3. Award jury submits award votes (PICK_WINNER, RANKED, or SCORED) 4. After Jury 2 closes: Award results finalized alongside main results --- ## 8. Differences Between Jury 1 and Jury 2 | Aspect | Jury 1 (Round 3) | Jury 2 (Round 5) | |--------|-------------------|-------------------| | Input projects | All eligible (post-filtering) | Semi-finalists only | | Visible docs | Window 1 only | Window 1 + Window 2 | | Output | Semi-finalists | Finalists | | Project.status update | → SEMIFINALIST | → FINALIST | | Special awards | No | Yes (alongside) | | Jury group | Jury 1 | Jury 2 (different members, possible overlap) | | Typical project count | 50-100+ | 10-20 | | Required reviews | 3 (more projects, less depth) | 3-5 (fewer projects, more depth) | --- ## 9. API Changes ### Preserved Procedures (renamed stageId → roundId) | Procedure | Change | |-----------|--------| | `evaluation.get` | roundId via assignment | | `evaluation.start` | No change | | `evaluation.autosave` | No change | | `evaluation.submit` | Window check uses round.windowCloseAt + grace periods | | `evaluation.declareCOI` | No change | | `evaluation.getCOIStatus` | No change | | `evaluation.getProjectStats` | No change | | `evaluation.listByRound` | Renamed from listByStage | | `evaluation.generateSummary` | roundId instead of stageId | | `evaluation.generateBulkSummaries` | roundId instead of stageId | ### New Procedures | Procedure | Purpose | |-----------|---------| | `assignment.previewWithJuryGroup` | Preview assignments filtered by jury group with cap/quota logic | | `assignment.getJuryGroupStats` | Per-member stats: load, category distribution, cap utilization | | `evaluation.getResultsOverview` | Rankings, scores, consensus, AI recommendations per category | | `evaluation.confirmAdvancement` | Admin confirms which projects advance | | `evaluation.getAdvancementPreview` | Preview advancement impact before confirming | ### Modified Procedures | Procedure | Modification | |-----------|-------------| | `assignment.getSuggestions` | Now filters by JuryGroup, applies hard/soft caps, category quotas | | `assignment.create` | Now sets `juryGroupId` on Assignment | | `assignment.bulkCreate` | Now validates against jury group caps | | `file.listByProjectForRound` | Uses RoundSubmissionVisibility to filter docs | --- ## 10. Service Layer Changes ### `stage-assignment.ts` → `round-assignment.ts` Key changes to `previewStageAssignment` → `previewRoundAssignment`: 1. **Load jury pool from JuryGroup** instead of all JURY_MEMBER users: ```typescript const juryGroup = await prisma.juryGroup.findUnique({ where: { id: round.juryGroupId }, include: { members: { include: { user: true } } } }) const jurors = juryGroup.members.map(m => ({ ...m.user, effectiveLimits: getEffectiveLimits(m, juryGroup), })) ``` 2. **Replace simple max check** with cap mode logic (hard/soft/none) 3. **Add category quota tracking** per juror 4. **Add ratio preference scoring** in candidate ranking 5. **Report overflow** — projects that couldn't be assigned because all jurors hit caps ### `stage-engine.ts` → `round-engine.ts` Simplified: - Remove trackId from all transitions - `executeTransition` now takes `fromRoundId` + `toRoundId` (or auto-advance to next sortOrder) - `validateTransition` simplified — no StageTransition lookup, just checks next round exists and is active - Guard evaluation simplified — AdvancementRule.configJson replaces arbitrary guardJson --- ## 11. Edge Cases ### More projects than jurors can handle - Algorithm assigns up to hard/soft cap for all jurors - Remaining projects flagged as "unassigned" in admin dashboard - Admin must: add jurors, increase caps, or manually assign ### Juror doesn't complete by deadline - Dashboard shows overdue assignments prominently - Admin can: extend via GracePeriod, reassign to another juror, or mark as incomplete ### Tie in scores at cutoff - Depending on `tieBreaker` config: - `admin_decides`: Admin manually picks from tied projects - `highest_individual`: Project with highest single-evaluator score wins - `revote`: Tied projects sent back for quick re-evaluation ### Category imbalance - If one category has far more projects, quotas ensure jurors still get a mix - If quotas can't be satisfied (not enough of one category), system relaxes quota for that category ### Juror in multiple jury groups - Juror Alice is in Jury 1 and Jury 2 - Her assignments for each round are independent - Her caps are per-jury-group (20 for Jury 1, 15 for Jury 2) - No cross-round cap — each round manages its own workload