Build a Roadmap Prioritization Engine
Instructions
Overview
In this lab, you will build a Python Roadmap Prioritization Engine that an Engineering Manager can use to objectively score, rank, and schedule project proposals into a quarterly roadmap. The engine uses the RICE framework (Reach x Impact x Confidence / Effort), handles team capacity constraints, respects inter-project dependencies, and produces a clear roadmap with justifications for every inclusion and exclusion.
Core Concepts
RICE Scoring (created by Sean McBride at Intercom):
- Reach: Number of users or customers affected per quarter
- Impact: How much each user is affected (scale: 0.25 = minimal, 0.5 = low, 1 = medium, 2 = high, 3 = massive)
- Confidence: How certain you are in the estimates (0.0 to 1.0, where 1.0 = 100%)
- Effort: Person-weeks of engineering work required. Intercom's original RICE and the Module 5 lesson both express effort in person-months; this lab uses person-weeks so that effort and team capacity share one unit. The scores are therefore roughly 4x the lesson's for the same project, which is harmless because RICE scores are only ever compared against each other -- but it means a RICE score is meaningless without its unit, and stating the unit is part of presenting a prioritisation.
RICE Score = (Reach x Impact x Confidence) / Effort
Requirements
Part 1: Project Proposals
Create a class PrioritizationEngine with the following methods:
add_project(project) -- Accepts a dictionary with these required keys:
name(str): The project namereach(int or float): Number of users/customers affected per quarter (must be > 0)impact(float): Impact score, must be one of: 0.25, 0.5, 1, 2, 3confidence(float): Confidence percentage as a decimal (0.0 to 1.0)effort(float): Person-weeks required (must be > 0)dependencies(list of str): Names of projects that must be completed first (can be empty)category(str): One of "feature", "tech_debt", "infrastructure", "experiment"
The method must:
- Validate all required fields and value ranges
- Calculate and store the RICE score for the project
- Store the project internally and return the calculated RICE score
- Raise a
ValueErrorfor invalid input (missing fields, out-of-range values, invalid category)
get_project(name) -- Returns the project dictionary by name (including its calculated rice_score), or raises a KeyError if not found.
list_projects(sort_by="rice_score", category=None) -- Returns a list of all projects sorted by the specified field (descending for scores, ascending for effort). If category is provided, filters to only projects of that category.
Part 2: Capacity Allocation
set_capacity(total_weeks, tech_debt_percentage=20) -- Sets the team's total available engineering weeks for the quarter and the percentage reserved for tech debt (default 20%).
The method must:
- Store
total_weeksas the team's total capacity - Calculate
feature_weeksastotal_weeks * (1 - tech_debt_percentage / 100) - Calculate
tech_debt_weeksastotal_weeks * (tech_debt_percentage / 100) - Raise a
ValueErroriftotal_weeks<= 0 ortech_debt_percentageis not between 0 and 100
get_capacity() -- Returns a dictionary with total_weeks, feature_weeks, tech_debt_weeks, and remaining_weeks (initially equal to total_weeks).
Part 3: Dependency Resolution
validate_dependencies() -- Checks all projects for dependency issues and returns a dictionary with:
valid(bool): True if all dependencies can be resolvedmissing(list of str): Project names referenced as dependencies that do not existcircular(list of list of str): Lists of project names involved in circular dependencies
The engine must detect:
- Missing dependencies: A project depends on a project that has not been added
- Circular dependencies: Project A depends on B, and B depends on A (or longer cycles)
Part 4: Roadmap Generation
generate_roadmap() -- Produces a quarterly roadmap. This is the core method. It must:
- Validate dependencies (raise a
ValueErrorif there are missing dependencies) - Validate that capacity has been set (raise a
ValueErrorifset_capacity()has not been called) - Separate projects into two pools:
tech_debtcategory projects usetech_debt_weeks; all other categories usefeature_weeks - Within each pool, sort projects by RICE score (highest first)
- Assign projects to the roadmap in RICE-score order, respecting:
- Capacity: A project is only included if its effort fits within the remaining weeks for its pool
- Dependencies: A project is only included if all its dependencies are already included in the roadmap
- Return a dictionary with:
included(list of dict): Projects included in the roadmap, each withname,rice_score,effort,category, andjustification(a string explaining why it was included, e.g., "RICE score: 5714. Fits within feature capacity.")excluded(list of dict): Projects not included, each withname,rice_score,effort,category, andreason(a string explaining why, e.g., "Insufficient remaining capacity (needs 8 weeks, 3 available)" or "Dependency 'Auth Rewrite' not included in roadmap")capacity_summary(dict):total_weeks,feature_weeks_used,feature_weeks_remaining,tech_debt_weeks_used,tech_debt_weeks_remaining
format_roadmap() -- Returns a human-readable string representation of the roadmap. Must call generate_roadmap() internally. Format:
================================================
QUARTERLY ROADMAP
================================================
--- Capacity ---
Total: 60 weeks | Features: 48 weeks | Tech Debt: 12 weeks
--- Included Projects (by priority) ---
1. Auth Rewrite [infrastructure]
RICE: 6,000 | Effort: 8 weeks
Justification: RICE score: 6000. Fits within feature capacity.
2. Search Improvement [feature]
RICE: 5,714 | Effort: 14 weeks
Justification: RICE score: 5714. Fits within feature capacity.
3. Cache Layer Rebuild [tech_debt]
RICE: 4,500 | Effort: 6 weeks
Justification: RICE score: 4500. Fits within tech debt capacity.
4. Payment V2 [feature]
RICE: 4,200 | Effort: 10 weeks
Justification: RICE score: 4200. Fits within feature capacity.
--- Excluded Projects ---
* Design System Refresh [feature]
RICE: 2,000 | Effort: 4 weeks
Reason: Dependency 'Mobile Redesign' not included in roadmap
* Mobile Redesign [feature]
RICE: 1,800 | Effort: 20 weeks
Reason: Insufficient remaining capacity (needs 20 weeks, 16 available)
--- Summary ---
Included: 4 projects | Excluded: 2 projects
Feature capacity: 32/48 weeks used (67%)
Tech debt capacity: 6/12 weeks used (50%)
================================================
Read the two exclusions together, because they are the reason this engine is worth building. Design System Refresh scores higher than Mobile Redesign and is still excluded -- by the project below it, which it depends on and which does not fit. A ranked list cannot express that; only running the capacity and dependency passes together can. It is also the shape of the argument you will have with a stakeholder: their project was not cut for being unimportant, it was cut for being downstream of something that was.
Example Usage
engine = PrioritizationEngine()
engine.add_project({
"name": "Search Improvement",
"reach": 50000,
"impact": 2,
"confidence": 0.8,
"effort": 14,
"dependencies": [],
"category": "feature",
})
engine.add_project({
"name": "Cache Layer Rebuild",
"reach": 30000,
"impact": 1,
"confidence": 0.9,
"effort": 6,
"dependencies": [],
"category": "tech_debt",
})
engine.add_project({
"name": "Payment V2",
"reach": 20000,
"impact": 3,
"confidence": 0.7,
"effort": 10,
"dependencies": ["Auth Rewrite"],
"category": "feature",
})
engine.add_project({
"name": "Auth Rewrite",
"reach": 40000,
"impact": 2,
"confidence": 0.6,
"effort": 8,
"dependencies": [],
"category": "infrastructure",
})
engine.add_project({
"name": "Mobile Redesign",
"reach": 60000,
"impact": 1,
"confidence": 0.6,
"effort": 20,
"dependencies": [],
"category": "feature",
})
engine.add_project({
"name": "Design System Refresh",
"reach": 20000,
"impact": 0.5,
"confidence": 0.8,
"effort": 4,
"dependencies": ["Mobile Redesign"],
"category": "feature",
})
# Set team capacity: 5 engineers x 12 weeks = 60 person-weeks
engine.set_capacity(total_weeks=60, tech_debt_percentage=20)
# Check dependencies
dep_check = engine.validate_dependencies()
print(f"Dependencies valid: {dep_check['valid']}")
# Generate and display roadmap
roadmap = engine.generate_roadmap()
print(engine.format_roadmap())