1"""
2Technical Debt Tracker
3
4Demonstrates technical debt management concepts from software engineering:
5- Debt categorization (code, architecture, test, documentation, security)
6- Interest rate modelling: ongoing cost that grows each sprint
7- ROI-based prioritization: benefit of paying off vs. effort required
8- Debt report generation with actionable recommendations
9- Sprint-by-sprint simulation of debt accumulation vs. payoff
10"""
11
12from dataclasses import dataclass
13from enum import Enum
14from typing import Optional
15
16
17class DebtType(Enum):
18 CODE = "Code Quality"
19 ARCHITECTURE = "Architecture"
20 TEST = "Test Coverage"
21 DOCUMENTATION = "Documentation"
22 SECURITY = "Security"
23 DEPENDENCY = "Dependency"
24
25
26class Severity(Enum):
27 CRITICAL = 4
28 HIGH = 3
29 MEDIUM = 2
30 LOW = 1
31
32
33@dataclass
34class DebtItem:
35 """A single technical debt item."""
36 id: str
37 description: str
38 debt_type: DebtType
39 severity: Severity
40 effort_days: float # estimated days to fix
41 interest_rate: float # extra cost per sprint (in hours) if not fixed
42 added_sprint: int # sprint number when debt was introduced
43 paid_off_sprint: Optional[int] = None
44
45 @property
46 def is_active(self) -> bool:
47 return self.paid_off_sprint is None
48
49 def accumulated_interest(self, current_sprint: int) -> float:
50 """Total hours lost to this debt up to current_sprint."""
51 if not self.is_active:
52 end = self.paid_off_sprint
53 else:
54 end = current_sprint
55 sprints_active = max(0, end - self.added_sprint)
56 return sprints_active * self.interest_rate
57
58 def roi_score(self, current_sprint: int) -> float:
59 """
60 ROI = (interest saved over next 10 sprints) / effort_days
61 Higher score → pay off sooner.
62 """
63 if self.effort_days == 0:
64 return float("inf")
65 future_savings = self.interest_rate * 10 # hours saved
66 return future_savings / self.effort_days
67
68
69def generate_report(items: list[DebtItem], current_sprint: int) -> None:
70 """Print a structured technical debt report."""
71 active = [i for i in items if i.is_active]
72 paid = [i for i in items if not i.is_active]
73
74 total_interest = sum(i.accumulated_interest(current_sprint) for i in active)
75 total_effort = sum(i.effort_days for i in active)
76
77 print("=" * 68)
78 print(" TECHNICAL DEBT REPORT")
79 print(f" Sprint {current_sprint} | Active items: {len(active)} | Resolved: {len(paid)}")
80 print("=" * 68)
81
82 print(f"\nDebt Summary:")
83 print(f" Total accumulated interest (active debt) : {total_interest:.1f} hours")
84 print(f" Estimated payoff effort : {total_effort:.1f} dev-days")
85 print(f" Interest / sprint (ongoing burn) : "
86 f"{sum(i.interest_rate for i in active):.1f} hours/sprint")
87
88 # Breakdown by type
89 print("\nDebt by Category:")
90 by_type: dict[str, list[DebtItem]] = {}
91 for item in active:
92 by_type.setdefault(item.debt_type.value, []).append(item)
93 for dtype, group in sorted(by_type.items()):
94 hrs = sum(i.accumulated_interest(current_sprint) for i in group)
95 print(f" {dtype:<22} {len(group):>2} items {hrs:>6.1f} h accumulated")
96
97 # Prioritized list
98 print("\nPrioritized Payoff List (by ROI):")
99 print(f" {'ID':<8} {'Description':<32} {'Sev':<9} {'Effort':>7} {'ROI':>6} Recommendation")
100 print(" " + "-" * 76)
101 ranked = sorted(active, key=lambda i: i.roi_score(current_sprint), reverse=True)
102 for rank, item in enumerate(ranked, 1):
103 roi = item.roi_score(current_sprint)
104 roi_str = f"{roi:.2f}" if roi != float("inf") else "∞"
105 if rank <= 2:
106 rec = ">>> FIX NOW"
107 elif item.severity == Severity.CRITICAL:
108 rec = ">> Urgent"
109 elif roi > 2.0:
110 rec = "> This sprint"
111 else:
112 rec = " Backlog"
113 print(f" {item.id:<8} {item.description:<32} {item.severity.name:<9} "
114 f"{item.effort_days:>5.1f}d {roi_str:>6} {rec}")
115
116 # Resolved items
117 if paid:
118 print(f"\nResolved Debt ({len(paid)} items):")
119 for item in paid:
120 sprint_count = item.paid_off_sprint - item.added_sprint
121 total = item.accumulated_interest(item.paid_off_sprint)
122 print(f" [{item.id}] {item.description[:40]} "
123 f"(paid sprint {item.paid_off_sprint}, cost {total:.0f}h over {sprint_count} sprints)")
124
125 print("=" * 68)
126
127
128def simulate_sprints(items: list[DebtItem], total_sprints: int,
129 payoff_capacity_hours: float = 8.0) -> None:
130 """
131 Simulate debt accumulation over sprints.
132 Each sprint: pay off highest-ROI items if capacity allows.
133 """
134 print("\nSPRINT SIMULATION (auto-payoff with 8h capacity/sprint)")
135 print(f"{'Sprint':>7} {'Active':>7} {'Interest/sp':>11} {'Accumulated':>12} {'Action'}")
136 print("-" * 70)
137
138 payoff_sprint = {}
139 paid_ids: set[str] = set()
140
141 for sprint in range(1, total_sprints + 1):
142 active = [i for i in items if i.added_sprint <= sprint and i.id not in paid_ids]
143 interest_this_sprint = sum(i.interest_rate for i in active)
144 accumulated = sum(i.accumulated_interest(sprint) for i in active)
145
146 # Attempt payoff
147 budget = payoff_capacity_hours
148 action_parts = []
149 for item in sorted(active, key=lambda i: i.roi_score(sprint), reverse=True):
150 cost_hours = item.effort_days * 8
151 if cost_hours <= budget:
152 budget -= cost_hours
153 paid_ids.add(item.id)
154 payoff_sprint[item.id] = sprint
155 action_parts.append(item.id)
156
157 action = "Paid: " + ", ".join(action_parts) if action_parts else "—"
158 print(f"{sprint:>7} {len(active):>7} {interest_this_sprint:>11.1f} "
159 f"{accumulated:>12.1f} {action}")
160
161
162if __name__ == "__main__":
163 CURRENT_SPRINT = 8
164
165 debt_items = [
166 DebtItem("TD-001", "No unit tests for payment module",
167 DebtType.TEST, Severity.CRITICAL,
168 effort_days=3.0, interest_rate=4.0, added_sprint=2),
169
170 DebtItem("TD-002", "Hardcoded API keys in config.py",
171 DebtType.SECURITY, Severity.CRITICAL,
172 effort_days=0.5, interest_rate=6.0, added_sprint=1),
173
174 DebtItem("TD-003", "Monolithic UserService (God Object)",
175 DebtType.ARCHITECTURE, Severity.HIGH,
176 effort_days=5.0, interest_rate=3.0, added_sprint=3),
177
178 DebtItem("TD-004", "SQLAlchemy 1.3 → 2.0 migration pending",
179 DebtType.DEPENDENCY, Severity.HIGH,
180 effort_days=2.0, interest_rate=1.5, added_sprint=4),
181
182 DebtItem("TD-005", "Copy-paste logic in 6 report generators",
183 DebtType.CODE, Severity.MEDIUM,
184 effort_days=1.5, interest_rate=2.0, added_sprint=3),
185
186 DebtItem("TD-006", "No API documentation (OpenAPI spec missing)",
187 DebtType.DOCUMENTATION, Severity.MEDIUM,
188 effort_days=2.0, interest_rate=1.0, added_sprint=5),
189
190 DebtItem("TD-007", "N+1 query in product listing endpoint",
191 DebtType.CODE, Severity.HIGH,
192 effort_days=0.5, interest_rate=2.5, added_sprint=6),
193
194 DebtItem("TD-008", "No retry/circuit-breaker on payment API calls",
195 DebtType.ARCHITECTURE, Severity.MEDIUM,
196 effort_days=1.5, interest_rate=1.0, added_sprint=7,
197 paid_off_sprint=8), # already resolved
198 ]
199
200 generate_report(debt_items, CURRENT_SPRINT)
201 simulate_sprints(debt_items, total_sprints=12, payoff_capacity_hours=8.0)