Fix Monte Carlo project completion simulations that give identical dates for all sub-groups (milestones, priorities, epics). Use when: (1) All sub-group predictions converge to the same date despite different remaining counts, (2) Proportional throughput scaling produces flat/identical results, (3) Applying overall scope rate to individual sub-groups causes simulations to hit max_weeks cap and never converge, (4) Building project forecasting tools that predict completion for sub-groups of a l...
Scanned 5/27/2026
Install via CLI
openskills install divinevideo/divine-mobile---
name: montecarlo-subgroup-prediction
description: |
Fix Monte Carlo project completion simulations that give identical dates for all
sub-groups (milestones, priorities, epics). Use when: (1) All sub-group predictions
converge to the same date despite different remaining counts, (2) Proportional
throughput scaling produces flat/identical results, (3) Applying overall scope rate
to individual sub-groups causes simulations to hit max_weeks cap and never converge,
(4) Building project forecasting tools that predict completion for sub-groups of
a larger backlog. Covers hypergeometric draw model for milestones and cumulative
priority model for sequential priorities.
author: Claude Code
version: 1.0.0
date: 2026-02-16
---
# Monte Carlo Sub-Group Prediction Scaling
## Problem
When running Monte Carlo simulations for project completion, predicting dates for
sub-groups (milestones, priorities, epics) within a larger project produces identical
results for all groups, or simulations diverge and hit the max_weeks cap.
## Context / Trigger Conditions
- Building a project forecasting tool that predicts dates for individual milestones
or priority groups within a larger project backlog
- All sub-group predictions show the same date despite very different remaining counts
(e.g., 5-item milestone shows same date as 46-item milestone)
- Simulations hit max_weeks (104) and produce dates 2+ years in the future
- Scope rate applied per-group causes negative net velocity (items grow each week)
## Root Causes
### 1. Proportional Scaling is Mathematically Constant
If you scale throughput proportionally:
```
share = remaining_i / total_remaining
scaled_throughput = overall_throughput * share
weeks = remaining_i / scaled_throughput
= remaining_i / (overall_throughput * remaining_i / total_remaining)
= total_remaining / overall_throughput # CONSTANT for all groups!
```
Every sub-group predicts the same number of weeks regardless of size.
### 2. Overall Scope Rate Overwhelms Sub-Group Throughput
If the project's overall scope rate is 50 items/week and you apply it to a sub-group
with only 5 remaining items (share = 1.6%), the scaled throughput might be ~0.6/week
while scaled scope is ~0.8/week. Net velocity is negative — the simulation never
converges and hits max_weeks.
### 3. GitHub `created_at` ≠ "Added to Board"
Scope rate calculated from `created_at` dates reflects when GitHub issues were created,
not when they were added to the project board. Bulk triaging or importing old issues
inflates the apparent scope rate dramatically.
## Solution
Use **different simulation models** for different sub-group types:
### For Sequential Priorities (P0, P1, P2...): Cumulative Model
Higher priorities are completed first. Each priority's prediction includes all
higher-priority work that must finish before it:
```python
sorted_priorities = sorted(priorities, key=lambda p: p["name"])
cumulative_before = 0
for p in sorted_priorities:
effective_remaining = cumulative_before + p["remaining"]
result = simulate(
remaining=effective_remaining,
throughput_history=full_project_throughput, # NOT scaled
scope_rate=0.0, # Don't apply scope per-group
)
result.remaining = p["remaining"] # Show actual remaining
cumulative_before += p["remaining"]
```
### For Milestones/Epics: Hypergeometric Draw Model
Items from each milestone are randomly drawn from the overall work pool. Smaller
milestones finish earlier due to higher variance:
```python
for i in range(n_simulations):
subset_left = remaining
pool_left = total_remaining
weeks = 0
while subset_left > 0 and weeks < max_weeks:
throughput = rng.choice(throughput_samples)
draw_size = min(throughput, pool_left)
if draw_size > 0 and pool_left > 0:
other = pool_left - subset_left
# How many completed items come from this milestone?
drawn = rng.hypergeometric(subset_left, max(other, 0), draw_size)
subset_left -= drawn
pool_left -= draw_size
pool_left = max(pool_left, subset_left)
weeks += 1
```
### Key Principles
1. **Don't scale throughput proportionally** — it produces identical results
2. **Don't apply overall scope rate to sub-groups** — it causes divergence
3. **Use the full project throughput** for priority predictions (cumulative model)
4. **Use hypergeometric sampling** for milestone predictions (discrete draws)
5. **Show scope rate as informational** rather than baking it into per-group MC
## Verification
- Sub-groups with fewer remaining items should predict earlier dates
- Smaller sub-groups should have wider confidence intervals (more variance)
- No predictions should hit the max_weeks cap under normal conditions
- Priority predictions should be ordered (P0 < P1 < P2 < P3)
## Example
With throughput of [2, 29, 44, 34, 11, 24, 33, 54, 34, 101, 57, 81, 0] and
303 total remaining items:
**Before fix (proportional scaling):**
```
MVP Rel 1 (5 left): Apr 13 # All identical!
MVP Rel 2 (46 left): Apr 13
Release 3 (33 left): Apr 13
```
**After fix (hypergeometric draws):**
```
MVP Rel 1 (5 left): Apr 6 # Differentiated by size
MVP Rel 2 (46 left): Apr 13
Release 3 (33 left): Apr 13
Zap Store (10 left): Apr 13
```
## Notes
- The continuous proportional model (`subset_throughput = throughput * share`)
is mathematically equivalent to "everything finishes when the project finishes"
because the differential equation d(subset)/dt = -T*(subset/pool) preserves ratios
- The hypergeometric distribution is the correct statistical model for "drawing
without replacement from a mixed pool"
- For very small sub-groups (< 5 items), the hypergeometric model produces high
variance — this is correct and reflects genuine uncertainty
- Consider capping scope_rate at `min(scope_rate, throughput * 0.5)` if using it,
to prevent divergent simulations
No comments yet. Be the first to comment!