Files
CleanArchitecture-template/.brain/.agent/skills/engineering-advanced-skills/tech-debt-tracker/scripts/debt_prioritizer.py
2026-03-12 15:17:52 +07:00

857 lines
34 KiB
Python

#!/usr/bin/env python3
"""
Tech Debt Prioritizer
Takes a debt inventory (from scanner or manual JSON) and calculates interest rate,
effort estimates, and produces a prioritized backlog with recommended sprint allocation.
Uses cost-of-delay vs effort scoring and various prioritization frameworks.
Usage:
python debt_prioritizer.py debt_inventory.json
python debt_prioritizer.py debt_inventory.json --output prioritized_backlog.json
python debt_prioritizer.py debt_inventory.json --team-size 6 --sprint-capacity 80
python debt_prioritizer.py debt_inventory.json --framework wsjf --output results.json
"""
import json
import argparse
import sys
import math
from collections import defaultdict, Counter
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
@dataclass
class EffortEstimate:
"""Represents effort estimation for a debt item."""
size_points: int
hours_estimate: float
risk_factor: float # 1.0 = low risk, 1.5 = medium, 2.0+ = high
skill_level_required: str # junior, mid, senior, expert
confidence: float # 0.0-1.0
@dataclass
class BusinessImpact:
"""Represents business impact assessment for a debt item."""
customer_impact: int # 1-10 scale
revenue_impact: int # 1-10 scale
team_velocity_impact: int # 1-10 scale
quality_impact: int # 1-10 scale
security_impact: int # 1-10 scale
@dataclass
class InterestRate:
"""Represents the interest rate calculation for technical debt."""
daily_cost: float # cost per day if left unfixed
frequency_multiplier: float # how often this code is touched
team_impact_multiplier: float # how many developers affected
compound_rate: float # how quickly this debt makes other debt worse
class DebtPrioritizer:
"""Main class for prioritizing technical debt items."""
def __init__(self, team_size: int = 5, sprint_capacity_hours: int = 80):
self.team_size = team_size
self.sprint_capacity_hours = sprint_capacity_hours
self.debt_items = []
self.prioritized_items = []
# Prioritization framework weights
self.framework_weights = {
"cost_of_delay": {
"business_value": 0.3,
"urgency": 0.3,
"risk_reduction": 0.2,
"team_productivity": 0.2
},
"wsjf": {
"business_value": 0.25,
"time_criticality": 0.25,
"risk_reduction": 0.25,
"effort": 0.25
},
"rice": {
"reach": 0.25,
"impact": 0.25,
"confidence": 0.25,
"effort": 0.25
}
}
def load_debt_inventory(self, file_path: str) -> bool:
"""Load debt inventory from JSON file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# Handle different input formats
if isinstance(data, dict) and 'debt_items' in data:
self.debt_items = data['debt_items']
elif isinstance(data, list):
self.debt_items = data
else:
raise ValueError("Invalid debt inventory format")
print(f"Loaded {len(self.debt_items)} debt items from {file_path}")
return True
except Exception as e:
print(f"Error loading debt inventory: {e}")
return False
def analyze_and_prioritize(self, framework: str = "cost_of_delay") -> Dict[str, Any]:
"""
Analyze debt items and create prioritized backlog.
Args:
framework: Prioritization framework to use
Returns:
Dictionary containing prioritized backlog and analysis
"""
print(f"Analyzing {len(self.debt_items)} debt items...")
print(f"Using {framework} prioritization framework")
print("=" * 50)
# Step 1: Enrich debt items with estimates
enriched_items = []
for item in self.debt_items:
enriched_item = self._enrich_debt_item(item)
enriched_items.append(enriched_item)
# Step 2: Calculate prioritization scores
for item in enriched_items:
if framework == "cost_of_delay":
item["priority_score"] = self._calculate_cost_of_delay_score(item)
elif framework == "wsjf":
item["priority_score"] = self._calculate_wsjf_score(item)
elif framework == "rice":
item["priority_score"] = self._calculate_rice_score(item)
else:
raise ValueError(f"Unknown prioritization framework: {framework}")
# Step 3: Sort by priority score
self.prioritized_items = sorted(enriched_items,
key=lambda x: x["priority_score"],
reverse=True)
# Step 4: Generate sprint allocation recommendations
sprint_allocation = self._generate_sprint_allocation()
# Step 5: Generate insights and recommendations
insights = self._generate_insights()
# Step 6: Create visualization data
charts_data = self._generate_charts_data()
return {
"metadata": {
"analysis_date": datetime.now().isoformat(),
"framework_used": framework,
"team_size": self.team_size,
"sprint_capacity_hours": self.sprint_capacity_hours,
"total_items_analyzed": len(self.debt_items)
},
"prioritized_backlog": self.prioritized_items,
"sprint_allocation": sprint_allocation,
"insights": insights,
"charts_data": charts_data,
"recommendations": self._generate_recommendations()
}
def _enrich_debt_item(self, item: Dict[str, Any]) -> Dict[str, Any]:
"""Enrich debt item with detailed estimates and impact analysis."""
enriched = item.copy()
# Generate effort estimate
effort = self._estimate_effort(item)
enriched["effort_estimate"] = asdict(effort)
# Generate business impact assessment
business_impact = self._assess_business_impact(item)
enriched["business_impact"] = asdict(business_impact)
# Calculate interest rate
interest_rate = self._calculate_interest_rate(item, business_impact)
enriched["interest_rate"] = asdict(interest_rate)
# Calculate cost of delay
enriched["cost_of_delay"] = self._calculate_cost_of_delay(interest_rate, effort)
# Assign categories and tags
enriched["category"] = self._categorize_debt_item(item)
enriched["impact_tags"] = self._generate_impact_tags(item, business_impact)
return enriched
def _estimate_effort(self, item: Dict[str, Any]) -> EffortEstimate:
"""Estimate effort required to fix debt item."""
debt_type = item.get("type", "unknown")
severity = item.get("severity", "medium")
# Base effort estimation by debt type
base_efforts = {
"todo_comment": (1, 2),
"missing_docstring": (1, 4),
"long_line": (0.5, 1),
"large_function": (4, 16),
"high_complexity": (8, 32),
"duplicate_code": (6, 24),
"large_file": (16, 64),
"syntax_error": (2, 8),
"security_risk": (4, 40),
"architecture_debt": (40, 160),
"test_debt": (8, 40),
"dependency_debt": (4, 24)
}
min_hours, max_hours = base_efforts.get(debt_type, (4, 16))
# Adjust by severity
severity_multipliers = {
"low": 0.5,
"medium": 1.0,
"high": 1.5,
"critical": 2.0
}
multiplier = severity_multipliers.get(severity, 1.0)
hours_estimate = (min_hours + max_hours) / 2 * multiplier
# Convert to story points (assuming 6 hours per point)
size_points = max(1, round(hours_estimate / 6))
# Determine risk factor
risk_factor = 1.0
if debt_type in ["architecture_debt", "security_risk", "large_file"]:
risk_factor = 1.8
elif debt_type in ["high_complexity", "duplicate_code"]:
risk_factor = 1.4
elif debt_type in ["syntax_error", "dependency_debt"]:
risk_factor = 1.2
# Determine skill level required
skill_requirements = {
"architecture_debt": "expert",
"security_risk": "senior",
"high_complexity": "senior",
"large_function": "mid",
"duplicate_code": "mid",
"dependency_debt": "mid",
"test_debt": "mid",
"todo_comment": "junior",
"missing_docstring": "junior",
"long_line": "junior"
}
skill_level = skill_requirements.get(debt_type, "mid")
# Confidence based on debt type clarity
confidence_levels = {
"todo_comment": 0.9,
"missing_docstring": 0.9,
"long_line": 0.95,
"syntax_error": 0.8,
"large_function": 0.7,
"duplicate_code": 0.6,
"high_complexity": 0.5,
"architecture_debt": 0.3,
"security_risk": 0.4
}
confidence = confidence_levels.get(debt_type, 0.6)
return EffortEstimate(
size_points=size_points,
hours_estimate=hours_estimate,
risk_factor=risk_factor,
skill_level_required=skill_level,
confidence=confidence
)
def _assess_business_impact(self, item: Dict[str, Any]) -> BusinessImpact:
"""Assess business impact of debt item."""
debt_type = item.get("type", "unknown")
severity = item.get("severity", "medium")
# Base impact scores by debt type (1-10 scale)
impact_profiles = {
"security_risk": (9, 8, 7, 9, 10), # customer, revenue, velocity, quality, security
"architecture_debt": (6, 7, 9, 8, 4),
"large_function": (3, 4, 7, 6, 2),
"high_complexity": (4, 5, 8, 7, 3),
"duplicate_code": (3, 4, 6, 6, 2),
"syntax_error": (7, 6, 8, 9, 3),
"test_debt": (5, 5, 7, 8, 3),
"dependency_debt": (6, 5, 6, 7, 7),
"todo_comment": (1, 1, 2, 2, 1),
"missing_docstring": (2, 2, 4, 3, 1)
}
base_impacts = impact_profiles.get(debt_type, (3, 3, 5, 5, 3))
# Adjust by severity
severity_adjustments = {
"low": 0.6,
"medium": 1.0,
"high": 1.4,
"critical": 1.8
}
adjustment = severity_adjustments.get(severity, 1.0)
# Apply adjustment and cap at 10
adjusted_impacts = [min(10, max(1, round(impact * adjustment)))
for impact in base_impacts]
return BusinessImpact(
customer_impact=adjusted_impacts[0],
revenue_impact=adjusted_impacts[1],
team_velocity_impact=adjusted_impacts[2],
quality_impact=adjusted_impacts[3],
security_impact=adjusted_impacts[4]
)
def _calculate_interest_rate(self, item: Dict[str, Any],
business_impact: BusinessImpact) -> InterestRate:
"""Calculate interest rate for technical debt."""
# Base daily cost calculation
velocity_impact = business_impact.team_velocity_impact
quality_impact = business_impact.quality_impact
# Daily cost in "developer hours lost"
daily_cost = (velocity_impact * 0.5) + (quality_impact * 0.3)
# Frequency multiplier based on code location and type
file_path = item.get("file_path", "")
debt_type = item.get("type", "unknown")
# Estimate frequency based on file path patterns
frequency_multiplier = 1.0
if any(pattern in file_path.lower() for pattern in ["main", "core", "auth", "api"]):
frequency_multiplier = 2.0
elif any(pattern in file_path.lower() for pattern in ["util", "helper", "common"]):
frequency_multiplier = 1.5
elif any(pattern in file_path.lower() for pattern in ["test", "spec", "config"]):
frequency_multiplier = 0.5
# Team impact multiplier
team_impact_multiplier = min(self.team_size, 8) / 5.0 # Normalize around team of 5
# Compound rate - how this debt creates more debt
compound_rates = {
"architecture_debt": 0.1, # Creates 10% more debt monthly
"duplicate_code": 0.08,
"high_complexity": 0.05,
"large_function": 0.03,
"test_debt": 0.04,
"security_risk": 0.02, # Doesn't compound much, but high initial impact
"todo_comment": 0.01
}
compound_rate = compound_rates.get(debt_type, 0.02)
return InterestRate(
daily_cost=daily_cost,
frequency_multiplier=frequency_multiplier,
team_impact_multiplier=team_impact_multiplier,
compound_rate=compound_rate
)
def _calculate_cost_of_delay(self, interest_rate: InterestRate,
effort: EffortEstimate) -> float:
"""Calculate total cost of delay if debt is not fixed."""
# Estimate delay in days (assuming debt gets fixed eventually)
estimated_delay_days = effort.hours_estimate / (self.sprint_capacity_hours / 14) # 2-week sprints
# Calculate cumulative cost
daily_cost = (interest_rate.daily_cost *
interest_rate.frequency_multiplier *
interest_rate.team_impact_multiplier)
# Add compound interest effect
compound_effect = (1 + interest_rate.compound_rate) ** (estimated_delay_days / 30)
total_cost = daily_cost * estimated_delay_days * compound_effect
return round(total_cost, 2)
def _categorize_debt_item(self, item: Dict[str, Any]) -> str:
"""Categorize debt item into high-level categories."""
debt_type = item.get("type", "unknown")
categories = {
"code_quality": ["large_function", "high_complexity", "duplicate_code",
"long_line", "missing_docstring"],
"architecture": ["architecture_debt", "large_file"],
"security": ["security_risk", "hardcoded_secrets"],
"testing": ["test_debt", "missing_tests"],
"maintenance": ["todo_comment", "commented_code"],
"dependencies": ["dependency_debt", "outdated_packages"],
"infrastructure": ["deployment_debt", "monitoring_gaps"],
"documentation": ["missing_docstring", "outdated_docs"]
}
for category, types in categories.items():
if debt_type in types:
return category
return "other"
def _generate_impact_tags(self, item: Dict[str, Any],
business_impact: BusinessImpact) -> List[str]:
"""Generate impact tags for debt item."""
tags = []
if business_impact.security_impact >= 7:
tags.append("security-critical")
if business_impact.customer_impact >= 7:
tags.append("customer-facing")
if business_impact.revenue_impact >= 7:
tags.append("revenue-impact")
if business_impact.team_velocity_impact >= 7:
tags.append("velocity-blocker")
if business_impact.quality_impact >= 7:
tags.append("quality-risk")
# Add effort-based tags
effort_hours = item.get("effort_estimate", {}).get("hours_estimate", 0)
if effort_hours <= 4:
tags.append("quick-win")
elif effort_hours >= 40:
tags.append("major-initiative")
return tags
def _calculate_cost_of_delay_score(self, item: Dict[str, Any]) -> float:
"""Calculate priority score using cost-of-delay framework."""
business_impact = item["business_impact"]
effort = item["effort_estimate"]
# Business value (weighted average of impacts)
business_value = (
business_impact["customer_impact"] * 0.3 +
business_impact["revenue_impact"] * 0.3 +
business_impact["quality_impact"] * 0.2 +
business_impact["team_velocity_impact"] * 0.2
)
# Urgency (how quickly value decreases)
urgency = item["interest_rate"]["daily_cost"] * 10 # Scale to 1-10
urgency = min(10, max(1, urgency))
# Risk reduction
risk_reduction = business_impact["security_impact"] * 0.6 + business_impact["quality_impact"] * 0.4
# Team productivity impact
team_productivity = business_impact["team_velocity_impact"]
# Combine with weights
weights = self.framework_weights["cost_of_delay"]
numerator = (
business_value * weights["business_value"] +
urgency * weights["urgency"] +
risk_reduction * weights["risk_reduction"] +
team_productivity * weights["team_productivity"]
)
# Divide by effort (adjusted for risk)
effort_adjusted = effort["hours_estimate"] * effort["risk_factor"]
denominator = max(1, effort_adjusted / 8) # Normalize to story points
return round(numerator / denominator, 2)
def _calculate_wsjf_score(self, item: Dict[str, Any]) -> float:
"""Calculate priority score using Weighted Shortest Job First (WSJF)."""
business_impact = item["business_impact"]
effort = item["effort_estimate"]
# Business value
business_value = (
business_impact["customer_impact"] * 0.4 +
business_impact["revenue_impact"] * 0.6
)
# Time criticality
time_criticality = item["cost_of_delay"] / 10 # Normalize
time_criticality = min(10, max(1, time_criticality))
# Risk reduction
risk_reduction = (
business_impact["security_impact"] * 0.5 +
business_impact["quality_impact"] * 0.5
)
# Job size (effort)
job_size = effort["size_points"]
# WSJF calculation
numerator = business_value + time_criticality + risk_reduction
denominator = max(1, job_size)
return round(numerator / denominator, 2)
def _calculate_rice_score(self, item: Dict[str, Any]) -> float:
"""Calculate priority score using RICE framework."""
business_impact = item["business_impact"]
effort = item["effort_estimate"]
# Reach (how many developers/users affected)
reach = min(10, self.team_size * business_impact["team_velocity_impact"] / 5)
# Impact
impact = (
business_impact["customer_impact"] * 0.3 +
business_impact["revenue_impact"] * 0.3 +
business_impact["quality_impact"] * 0.4
)
# Confidence
confidence = effort["confidence"] * 10
# Effort
effort_score = effort["size_points"]
# RICE calculation
rice_score = (reach * impact * confidence) / max(1, effort_score)
return round(rice_score, 2)
def _generate_sprint_allocation(self) -> Dict[str, Any]:
"""Generate sprint allocation recommendations."""
# Calculate total effort needed
total_effort_hours = sum(item["effort_estimate"]["hours_estimate"]
for item in self.prioritized_items)
# Assume 20% of sprint capacity goes to tech debt
debt_capacity_per_sprint = self.sprint_capacity_hours * 0.2
# Allocate items to sprints
sprints = []
current_sprint = {"sprint_number": 1, "items": [], "total_hours": 0, "capacity_used": 0}
for item in self.prioritized_items:
item_effort = item["effort_estimate"]["hours_estimate"]
if current_sprint["total_hours"] + item_effort <= debt_capacity_per_sprint:
current_sprint["items"].append(item)
current_sprint["total_hours"] += item_effort
current_sprint["capacity_used"] = current_sprint["total_hours"] / debt_capacity_per_sprint
else:
# Start new sprint
sprints.append(current_sprint)
current_sprint = {
"sprint_number": len(sprints) + 1,
"items": [item],
"total_hours": item_effort,
"capacity_used": item_effort / debt_capacity_per_sprint
}
# Add the last sprint
if current_sprint["items"]:
sprints.append(current_sprint)
# Calculate summary statistics
total_sprints_needed = len(sprints)
high_priority_items = len([item for item in self.prioritized_items
if item.get("priority", "medium") in ["high", "critical"]])
return {
"total_debt_hours": round(total_effort_hours, 1),
"debt_capacity_per_sprint": debt_capacity_per_sprint,
"total_sprints_needed": total_sprints_needed,
"high_priority_items": high_priority_items,
"sprint_plan": sprints[:6], # Show first 6 sprints
"recommendations": [
f"Allocate {debt_capacity_per_sprint} hours per sprint to tech debt",
f"Focus on {high_priority_items} high-priority items first",
f"Estimated {total_sprints_needed} sprints to clear current backlog"
]
}
def _generate_insights(self) -> Dict[str, Any]:
"""Generate insights from the prioritized debt analysis."""
# Category distribution
categories = Counter(item["category"] for item in self.prioritized_items)
# Effort distribution
total_effort = sum(item["effort_estimate"]["hours_estimate"]
for item in self.prioritized_items)
effort_by_category = defaultdict(float)
for item in self.prioritized_items:
effort_by_category[item["category"]] += item["effort_estimate"]["hours_estimate"]
# Priority distribution
priorities = Counter()
for item in self.prioritized_items:
score = item["priority_score"]
if score >= 8:
priorities["critical"] += 1
elif score >= 5:
priorities["high"] += 1
elif score >= 2:
priorities["medium"] += 1
else:
priorities["low"] += 1
# Risk analysis
high_risk_items = [item for item in self.prioritized_items
if item["effort_estimate"]["risk_factor"] >= 1.5]
# Quick wins identification
quick_wins = [item for item in self.prioritized_items
if (item["effort_estimate"]["hours_estimate"] <= 8 and
item["priority_score"] >= 3)]
# Cost analysis
total_cost_of_delay = sum(item["cost_of_delay"] for item in self.prioritized_items)
avg_interest_rate = sum(item["interest_rate"]["daily_cost"]
for item in self.prioritized_items) / len(self.prioritized_items)
return {
"category_distribution": dict(categories),
"total_effort_hours": round(total_effort, 1),
"effort_by_category": {k: round(v, 1) for k, v in effort_by_category.items()},
"priority_distribution": dict(priorities),
"high_risk_items_count": len(high_risk_items),
"quick_wins_count": len(quick_wins),
"total_cost_of_delay": round(total_cost_of_delay, 1),
"average_daily_interest_rate": round(avg_interest_rate, 2),
"top_categories_by_effort": sorted(effort_by_category.items(),
key=lambda x: x[1], reverse=True)[:3]
}
def _generate_charts_data(self) -> Dict[str, Any]:
"""Generate data for charts and visualizations."""
# Priority vs Effort scatter plot data
scatter_data = []
for item in self.prioritized_items:
scatter_data.append({
"x": item["effort_estimate"]["hours_estimate"],
"y": item["priority_score"],
"label": item.get("description", "")[:50],
"category": item["category"],
"size": item["cost_of_delay"]
})
# Category effort distribution (pie chart)
effort_by_category = defaultdict(float)
for item in self.prioritized_items:
effort_by_category[item["category"]] += item["effort_estimate"]["hours_estimate"]
pie_data = [{"category": k, "effort": round(v, 1)}
for k, v in effort_by_category.items()]
# Priority timeline (bar chart)
timeline_data = []
cumulative_effort = 0
for i, item in enumerate(self.prioritized_items[:20]): # Top 20 items
cumulative_effort += item["effort_estimate"]["hours_estimate"]
timeline_data.append({
"item_rank": i + 1,
"description": item.get("description", "")[:30],
"effort": item["effort_estimate"]["hours_estimate"],
"cumulative_effort": round(cumulative_effort, 1),
"priority_score": item["priority_score"]
})
# Interest rate trend (line chart data structure)
interest_trend_data = []
for i, item in enumerate(self.prioritized_items):
interest_trend_data.append({
"item_index": i,
"daily_cost": item["interest_rate"]["daily_cost"],
"category": item["category"]
})
return {
"priority_effort_scatter": scatter_data,
"category_effort_distribution": pie_data,
"priority_timeline": timeline_data,
"interest_rate_trend": interest_trend_data[:50] # Limit for performance
}
def _generate_recommendations(self) -> List[str]:
"""Generate actionable recommendations based on analysis."""
recommendations = []
insights = self._generate_insights()
# Quick wins recommendation
if insights["quick_wins_count"] > 0:
recommendations.append(
f"Start with {insights['quick_wins_count']} quick wins to build momentum "
"and demonstrate immediate value from tech debt reduction efforts."
)
# High-risk items
if insights["high_risk_items_count"] > 5:
recommendations.append(
f"Plan careful execution for {insights['high_risk_items_count']} high-risk items. "
"Consider pair programming, extra testing, and incremental approaches."
)
# Category focus
top_category = insights["top_categories_by_effort"][0][0]
recommendations.append(
f"Focus initial efforts on '{top_category}' category debt, which represents "
f"the largest effort investment ({insights['top_categories_by_effort'][0][1]:.1f} hours)."
)
# Cost of delay urgency
if insights["average_daily_interest_rate"] > 5:
recommendations.append(
f"High average daily interest rate ({insights['average_daily_interest_rate']:.1f}) "
"suggests urgent action needed. Consider increasing tech debt budget allocation."
)
# Sprint planning
sprints_needed = len(self.prioritized_items) / 10 # Rough estimate
if sprints_needed > 12:
recommendations.append(
"Large debt backlog detected. Consider dedicating entire sprints to debt reduction "
"rather than trying to fit debt work around features."
)
# Team capacity
total_effort = insights["total_effort_hours"]
weeks_needed = total_effort / (self.sprint_capacity_hours * 0.2)
if weeks_needed > 26: # Half a year
recommendations.append(
f"With current capacity allocation, debt backlog will take {weeks_needed:.0f} weeks. "
"Consider increasing tech debt budget or focusing on highest-impact items only."
)
return recommendations
def format_prioritized_report(analysis_result: Dict[str, Any]) -> str:
"""Format the prioritization analysis in human-readable format."""
output = []
# Header
output.append("=" * 60)
output.append("TECHNICAL DEBT PRIORITIZATION REPORT")
output.append("=" * 60)
metadata = analysis_result["metadata"]
output.append(f"Analysis Date: {metadata['analysis_date']}")
output.append(f"Framework: {metadata['framework_used'].upper()}")
output.append(f"Team Size: {metadata['team_size']}")
output.append(f"Sprint Capacity: {metadata['sprint_capacity_hours']} hours")
output.append("")
# Executive Summary
insights = analysis_result["insights"]
output.append("EXECUTIVE SUMMARY")
output.append("-" * 30)
output.append(f"Total Debt Items: {metadata['total_items_analyzed']}")
output.append(f"Total Effort Required: {insights['total_effort_hours']} hours")
output.append(f"Total Cost of Delay: ${insights['total_cost_of_delay']:,.0f}")
output.append(f"Quick Wins Available: {insights['quick_wins_count']}")
output.append(f"High-Risk Items: {insights['high_risk_items_count']}")
output.append("")
# Sprint Plan
sprint_plan = analysis_result["sprint_allocation"]
output.append("SPRINT ALLOCATION PLAN")
output.append("-" * 30)
output.append(f"Sprints Needed: {sprint_plan['total_sprints_needed']}")
output.append(f"Hours per Sprint: {sprint_plan['debt_capacity_per_sprint']}")
output.append("")
for sprint in sprint_plan["sprint_plan"][:3]: # Show first 3 sprints
output.append(f"Sprint {sprint['sprint_number']} ({sprint['capacity_used']:.0%} capacity):")
for item in sprint["items"][:3]: # Top 3 items per sprint
output.append(f"{item['description'][:50]}...")
output.append(f" Effort: {item['effort_estimate']['hours_estimate']:.1f}h, "
f"Priority: {item['priority_score']}")
output.append("")
# Top Priority Items
output.append("TOP 10 PRIORITY ITEMS")
output.append("-" * 30)
for i, item in enumerate(analysis_result["prioritized_backlog"][:10], 1):
output.append(f"{i}. [{item['priority_score']:.1f}] {item['description']}")
output.append(f" Category: {item['category']}, "
f"Effort: {item['effort_estimate']['hours_estimate']:.1f}h, "
f"Cost of Delay: ${item['cost_of_delay']:.0f}")
if item["impact_tags"]:
output.append(f" Tags: {', '.join(item['impact_tags'])}")
output.append("")
# Recommendations
output.append("RECOMMENDATIONS")
output.append("-" * 30)
for i, rec in enumerate(analysis_result["recommendations"], 1):
output.append(f"{i}. {rec}")
output.append("")
return "\n".join(output)
def main():
"""Main entry point for the debt prioritizer."""
parser = argparse.ArgumentParser(description="Prioritize technical debt backlog")
parser.add_argument("inventory_file", help="Path to debt inventory JSON file")
parser.add_argument("--output", help="Output file path")
parser.add_argument("--format", choices=["json", "text", "both"],
default="both", help="Output format")
parser.add_argument("--framework", choices=["cost_of_delay", "wsjf", "rice"],
default="cost_of_delay", help="Prioritization framework")
parser.add_argument("--team-size", type=int, default=5, help="Team size")
parser.add_argument("--sprint-capacity", type=int, default=80,
help="Sprint capacity in hours")
args = parser.parse_args()
# Initialize prioritizer
prioritizer = DebtPrioritizer(args.team_size, args.sprint_capacity)
# Load inventory
if not prioritizer.load_debt_inventory(args.inventory_file):
sys.exit(1)
# Analyze and prioritize
try:
analysis_result = prioritizer.analyze_and_prioritize(args.framework)
except Exception as e:
print(f"Analysis failed: {e}")
sys.exit(1)
# Output results
if args.format in ["json", "both"]:
json_output = json.dumps(analysis_result, indent=2, default=str)
if args.output:
output_path = args.output if args.output.endswith('.json') else f"{args.output}.json"
with open(output_path, 'w') as f:
f.write(json_output)
print(f"JSON report written to: {output_path}")
else:
print("JSON REPORT:")
print("=" * 50)
print(json_output)
if args.format in ["text", "both"]:
text_output = format_prioritized_report(analysis_result)
if args.output:
output_path = args.output if args.output.endswith('.txt') else f"{args.output}.txt"
with open(output_path, 'w') as f:
f.write(text_output)
print(f"Text report written to: {output_path}")
else:
print("\nTEXT REPORT:")
print("=" * 50)
print(text_output)
if __name__ == "__main__":
main()