runbooks 0.7.9__py3-none-any.whl → 0.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/README.md +12 -1
  3. runbooks/cfat/__init__.py +1 -1
  4. runbooks/cfat/assessment/compliance.py +4 -1
  5. runbooks/cfat/assessment/runner.py +42 -34
  6. runbooks/cfat/models.py +1 -1
  7. runbooks/cloudops/__init__.py +123 -0
  8. runbooks/cloudops/base.py +385 -0
  9. runbooks/cloudops/cost_optimizer.py +811 -0
  10. runbooks/cloudops/infrastructure_optimizer.py +29 -0
  11. runbooks/cloudops/interfaces.py +828 -0
  12. runbooks/cloudops/lifecycle_manager.py +29 -0
  13. runbooks/cloudops/mcp_cost_validation.py +678 -0
  14. runbooks/cloudops/models.py +251 -0
  15. runbooks/cloudops/monitoring_automation.py +29 -0
  16. runbooks/cloudops/notebook_framework.py +676 -0
  17. runbooks/cloudops/security_enforcer.py +449 -0
  18. runbooks/common/__init__.py +152 -0
  19. runbooks/common/accuracy_validator.py +1039 -0
  20. runbooks/common/context_logger.py +440 -0
  21. runbooks/common/cross_module_integration.py +594 -0
  22. runbooks/common/enhanced_exception_handler.py +1108 -0
  23. runbooks/common/enterprise_audit_integration.py +634 -0
  24. runbooks/common/mcp_cost_explorer_integration.py +900 -0
  25. runbooks/common/mcp_integration.py +548 -0
  26. runbooks/common/performance_monitor.py +387 -0
  27. runbooks/common/profile_utils.py +216 -0
  28. runbooks/common/rich_utils.py +172 -1
  29. runbooks/feedback/user_feedback_collector.py +440 -0
  30. runbooks/finops/README.md +377 -458
  31. runbooks/finops/__init__.py +4 -21
  32. runbooks/finops/account_resolver.py +279 -0
  33. runbooks/finops/accuracy_cross_validator.py +638 -0
  34. runbooks/finops/aws_client.py +721 -36
  35. runbooks/finops/budget_integration.py +313 -0
  36. runbooks/finops/cli.py +59 -5
  37. runbooks/finops/cost_optimizer.py +1340 -0
  38. runbooks/finops/cost_processor.py +211 -37
  39. runbooks/finops/dashboard_router.py +900 -0
  40. runbooks/finops/dashboard_runner.py +990 -232
  41. runbooks/finops/embedded_mcp_validator.py +288 -0
  42. runbooks/finops/enhanced_dashboard_runner.py +8 -7
  43. runbooks/finops/enhanced_progress.py +327 -0
  44. runbooks/finops/enhanced_trend_visualization.py +423 -0
  45. runbooks/finops/finops_dashboard.py +184 -1829
  46. runbooks/finops/helpers.py +509 -196
  47. runbooks/finops/iam_guidance.py +400 -0
  48. runbooks/finops/markdown_exporter.py +466 -0
  49. runbooks/finops/multi_dashboard.py +1502 -0
  50. runbooks/finops/optimizer.py +15 -15
  51. runbooks/finops/profile_processor.py +2 -2
  52. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  53. runbooks/finops/runbooks.security.report_generator.log +0 -0
  54. runbooks/finops/runbooks.security.run_script.log +0 -0
  55. runbooks/finops/runbooks.security.security_export.log +0 -0
  56. runbooks/finops/schemas.py +589 -0
  57. runbooks/finops/service_mapping.py +195 -0
  58. runbooks/finops/single_dashboard.py +710 -0
  59. runbooks/finops/tests/test_reference_images_validation.py +1 -1
  60. runbooks/inventory/README.md +12 -1
  61. runbooks/inventory/core/collector.py +157 -29
  62. runbooks/inventory/list_ec2_instances.py +9 -6
  63. runbooks/inventory/list_ssm_parameters.py +10 -10
  64. runbooks/inventory/organizations_discovery.py +210 -164
  65. runbooks/inventory/rich_inventory_display.py +74 -107
  66. runbooks/inventory/run_on_multi_accounts.py +13 -13
  67. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  68. runbooks/inventory/runbooks.security.security_export.log +0 -0
  69. runbooks/main.py +1371 -240
  70. runbooks/metrics/dora_metrics_engine.py +711 -17
  71. runbooks/monitoring/performance_monitor.py +433 -0
  72. runbooks/operate/README.md +394 -0
  73. runbooks/operate/base.py +215 -47
  74. runbooks/operate/ec2_operations.py +435 -5
  75. runbooks/operate/iam_operations.py +598 -3
  76. runbooks/operate/privatelink_operations.py +1 -1
  77. runbooks/operate/rds_operations.py +508 -0
  78. runbooks/operate/s3_operations.py +508 -0
  79. runbooks/operate/vpc_endpoints.py +1 -1
  80. runbooks/remediation/README.md +489 -13
  81. runbooks/remediation/base.py +5 -3
  82. runbooks/remediation/commons.py +8 -4
  83. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
  84. runbooks/security/README.md +12 -1
  85. runbooks/security/__init__.py +265 -33
  86. runbooks/security/cloudops_automation_security_validator.py +1164 -0
  87. runbooks/security/compliance_automation.py +12 -10
  88. runbooks/security/compliance_automation_engine.py +1021 -0
  89. runbooks/security/enterprise_security_framework.py +930 -0
  90. runbooks/security/enterprise_security_policies.json +293 -0
  91. runbooks/security/executive_security_dashboard.py +1247 -0
  92. runbooks/security/integration_test_enterprise_security.py +879 -0
  93. runbooks/security/module_security_integrator.py +641 -0
  94. runbooks/security/multi_account_security_controls.py +2254 -0
  95. runbooks/security/real_time_security_monitor.py +1196 -0
  96. runbooks/security/report_generator.py +1 -1
  97. runbooks/security/run_script.py +4 -8
  98. runbooks/security/security_baseline_tester.py +39 -52
  99. runbooks/security/security_export.py +99 -120
  100. runbooks/sre/README.md +472 -0
  101. runbooks/sre/__init__.py +33 -0
  102. runbooks/sre/mcp_reliability_engine.py +1049 -0
  103. runbooks/sre/performance_optimization_engine.py +1032 -0
  104. runbooks/sre/production_monitoring_framework.py +584 -0
  105. runbooks/sre/reliability_monitoring_framework.py +1011 -0
  106. runbooks/validation/__init__.py +2 -2
  107. runbooks/validation/benchmark.py +154 -149
  108. runbooks/validation/cli.py +159 -147
  109. runbooks/validation/mcp_validator.py +291 -248
  110. runbooks/vpc/README.md +478 -0
  111. runbooks/vpc/__init__.py +2 -2
  112. runbooks/vpc/manager_interface.py +366 -351
  113. runbooks/vpc/networking_wrapper.py +68 -36
  114. runbooks/vpc/rich_formatters.py +22 -8
  115. runbooks-0.9.1.dist-info/METADATA +308 -0
  116. {runbooks-0.7.9.dist-info → runbooks-0.9.1.dist-info}/RECORD +120 -59
  117. {runbooks-0.7.9.dist-info → runbooks-0.9.1.dist-info}/entry_points.txt +1 -1
  118. runbooks/finops/cross_validation.py +0 -375
  119. runbooks-0.7.9.dist-info/METADATA +0 -636
  120. {runbooks-0.7.9.dist-info → runbooks-0.9.1.dist-info}/WHEEL +0 -0
  121. {runbooks-0.7.9.dist-info → runbooks-0.9.1.dist-info}/licenses/LICENSE +0 -0
  122. {runbooks-0.7.9.dist-info → runbooks-0.9.1.dist-info}/top_level.txt +0 -0
@@ -8,31 +8,33 @@ optimization dashboard.
8
8
 
9
9
  import json
10
10
  import logging
11
+ from dataclasses import asdict, dataclass
11
12
  from datetime import datetime, timedelta
13
+ from enum import Enum
12
14
  from pathlib import Path
13
15
  from typing import Any, Dict, List, Optional, Tuple
14
- from dataclasses import dataclass, asdict
15
- from enum import Enum
16
16
 
17
17
  import pandas as pd
18
18
  from rich.console import Console
19
- from rich.table import Table
20
19
  from rich.panel import Panel
21
20
  from rich.progress import Progress, SpinnerColumn, TextColumn
21
+ from rich.table import Table
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
25
25
 
26
26
  class BusinessPriority(Enum):
27
27
  """Business priority levels for manager decision making"""
28
+
28
29
  CRITICAL = "Critical"
29
- HIGH = "High"
30
+ HIGH = "High"
30
31
  MEDIUM = "Medium"
31
32
  LOW = "Low"
32
33
 
33
34
 
34
35
  class RiskLevel(Enum):
35
36
  """Risk assessment levels for business decisions"""
37
+
36
38
  MINIMAL = "Minimal"
37
39
  LOW = "Low"
38
40
  MEDIUM = "Medium"
@@ -42,6 +44,7 @@ class RiskLevel(Enum):
42
44
  @dataclass
43
45
  class BusinessRecommendation:
44
46
  """Business-focused recommendation structure"""
47
+
45
48
  title: str
46
49
  executive_summary: str
47
50
  monthly_savings: float
@@ -59,6 +62,7 @@ class BusinessRecommendation:
59
62
  @dataclass
60
63
  class ManagerDashboardConfig:
61
64
  """Configuration for manager dashboard behavior"""
65
+
62
66
  safety_mode: bool = True
63
67
  auto_export: bool = True
64
68
  executive_summaries_only: bool = False
@@ -66,37 +70,34 @@ class ManagerDashboardConfig:
66
70
  target_savings_percentage: float = 30.0
67
71
  max_implementation_weeks: int = 12
68
72
  preferred_export_formats: List[str] = None
69
-
73
+
70
74
  def __post_init__(self):
71
75
  if self.preferred_export_formats is None:
72
- self.preferred_export_formats = ['json', 'csv', 'excel']
76
+ self.preferred_export_formats = ["json", "csv", "excel"]
73
77
 
74
78
 
75
79
  class VPCManagerInterface:
76
80
  """
77
81
  Manager-friendly interface for VPC cost optimization
78
-
82
+
79
83
  This class provides high-level business methods that abstract away
80
84
  technical complexity while providing comprehensive cost optimization
81
85
  insights for executive decision making.
82
86
  """
83
-
87
+
84
88
  def __init__(self, console: Optional[Console] = None):
85
89
  self.console = console or Console(force_jupyter=True, width=100)
86
90
  self.config = ManagerDashboardConfig()
87
91
  self.analysis_results = {}
88
92
  self.business_recommendations = []
89
93
  self.export_directory = Path("./exports/manager_dashboard")
90
-
94
+
91
95
  def configure_for_business_user(
92
- self,
93
- safety_mode: bool = True,
94
- target_savings: float = 30.0,
95
- approval_threshold: float = 1000.0
96
+ self, safety_mode: bool = True, target_savings: float = 30.0, approval_threshold: float = 1000.0
96
97
  ) -> None:
97
98
  """
98
99
  Configure interface for business user preferences
99
-
100
+
100
101
  Args:
101
102
  safety_mode: Enable dry-run only mode for safety
102
103
  target_savings: Target cost reduction percentage
@@ -105,484 +106,501 @@ class VPCManagerInterface:
105
106
  self.config.safety_mode = safety_mode
106
107
  self.config.target_savings_percentage = target_savings
107
108
  self.config.approval_threshold = approval_threshold
108
-
109
- self.console.print(Panel.fit(
110
- f"[bold green]✅ Manager Interface Configured[/bold green]\n\n"
111
- f"Safety Mode: {'[green]ON[/green]' if safety_mode else '[red]OFF[/red]'}\n"
112
- f"Target Savings: [blue]{target_savings}%[/blue]\n"
113
- f"Approval Threshold: [yellow]${approval_threshold:,.2f}[/yellow]",
114
- title="Business Configuration"
115
- ))
116
-
117
- def analyze_cost_optimization_opportunity(
118
- self,
119
- vpc_analysis_results: Dict[str, Any]
120
- ) -> Dict[str, Any]:
109
+
110
+ self.console.print(
111
+ Panel.fit(
112
+ f"[bold green] Manager Interface Configured[/bold green]\n\n"
113
+ f"Safety Mode: {'[green]ON[/green]' if safety_mode else '[red]OFF[/red]'}\n"
114
+ f"Target Savings: [blue]{target_savings}%[/blue]\n"
115
+ f"Approval Threshold: [yellow]${approval_threshold:,.2f}[/yellow]",
116
+ title="Business Configuration",
117
+ )
118
+ )
119
+
120
+ def analyze_cost_optimization_opportunity(self, vpc_analysis_results: Dict[str, Any]) -> Dict[str, Any]:
121
121
  """
122
122
  Convert technical VPC analysis into business-focused insights
123
-
123
+
124
124
  Args:
125
125
  vpc_analysis_results: Raw technical analysis from VPC wrapper
126
-
126
+
127
127
  Returns:
128
128
  Dictionary with business-focused analysis results
129
129
  """
130
130
  self.console.print("[cyan]🔍 Converting technical analysis to business insights...[/cyan]")
131
-
131
+
132
132
  # Extract business metrics
133
133
  business_analysis = {
134
- 'executive_summary': self._create_executive_summary(vpc_analysis_results),
135
- 'financial_impact': self._calculate_financial_impact(vpc_analysis_results),
136
- 'risk_assessment': self._assess_business_risks(vpc_analysis_results),
137
- 'implementation_plan': self._create_implementation_plan(vpc_analysis_results),
138
- 'success_metrics': self._define_success_metrics(vpc_analysis_results),
139
- 'approval_requirements': self._determine_approval_requirements(vpc_analysis_results)
134
+ "executive_summary": self._create_executive_summary(vpc_analysis_results),
135
+ "financial_impact": self._calculate_financial_impact(vpc_analysis_results),
136
+ "risk_assessment": self._assess_business_risks(vpc_analysis_results),
137
+ "implementation_plan": self._create_implementation_plan(vpc_analysis_results),
138
+ "success_metrics": self._define_success_metrics(vpc_analysis_results),
139
+ "approval_requirements": self._determine_approval_requirements(vpc_analysis_results),
140
140
  }
141
-
141
+
142
142
  # Store for later export
143
143
  self.analysis_results = business_analysis
144
-
144
+
145
145
  # Generate business recommendations
146
- self.business_recommendations = self._generate_business_recommendations(
147
- vpc_analysis_results, business_analysis
148
- )
149
-
146
+ self.business_recommendations = self._generate_business_recommendations(vpc_analysis_results, business_analysis)
147
+
150
148
  return business_analysis
151
-
149
+
152
150
  def _create_executive_summary(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
153
151
  """Create executive summary from technical analysis"""
154
-
152
+
155
153
  # Extract key financial metrics
156
154
  total_cost = 0
157
155
  total_savings = 0
158
-
159
- for component in ['nat_gateways', 'vpc_endpoints', 'transit_gateway']:
156
+
157
+ for component in ["nat_gateways", "vpc_endpoints", "transit_gateway"]:
160
158
  if component in analysis:
161
159
  comp_data = analysis[component]
162
- total_cost += comp_data.get('total_cost', 0) or comp_data.get('total_monthly_cost', 0)
163
- total_savings += comp_data.get('optimization_potential', 0) or comp_data.get('potential_savings', 0)
164
-
160
+ total_cost += comp_data.get("total_cost", 0) or comp_data.get("total_monthly_cost", 0)
161
+ total_savings += comp_data.get("optimization_potential", 0) or comp_data.get("potential_savings", 0)
162
+
165
163
  savings_percentage = (total_savings / total_cost * 100) if total_cost > 0 else 0
166
164
  target_achieved = savings_percentage >= self.config.target_savings_percentage
167
-
165
+
168
166
  return {
169
- 'current_monthly_cost': total_cost,
170
- 'potential_monthly_savings': total_savings,
171
- 'savings_percentage': savings_percentage,
172
- 'annual_savings_potential': total_savings * 12,
173
- 'target_achieved': target_achieved,
174
- 'payback_period_months': 1, # Most VPC optimizations have immediate payback
175
- 'confidence_level': 'High',
176
- 'business_case_strength': 'Excellent' if savings_percentage >= 30 else 'Good' if savings_percentage >= 20 else 'Moderate'
167
+ "current_monthly_cost": total_cost,
168
+ "potential_monthly_savings": total_savings,
169
+ "savings_percentage": savings_percentage,
170
+ "annual_savings_potential": total_savings * 12,
171
+ "target_achieved": target_achieved,
172
+ "payback_period_months": 1, # Most VPC optimizations have immediate payback
173
+ "confidence_level": "High",
174
+ "business_case_strength": "Excellent"
175
+ if savings_percentage >= 30
176
+ else "Good"
177
+ if savings_percentage >= 20
178
+ else "Moderate",
177
179
  }
178
-
180
+
179
181
  def _calculate_financial_impact(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
180
182
  """Calculate comprehensive financial impact"""
181
-
183
+
182
184
  exec_summary = self._create_executive_summary(analysis)
183
-
185
+
184
186
  return {
185
- 'immediate_savings': {
186
- 'monthly': exec_summary['potential_monthly_savings'],
187
- 'quarterly': exec_summary['potential_monthly_savings'] * 3,
188
- 'annual': exec_summary['annual_savings_potential']
187
+ "immediate_savings": {
188
+ "monthly": exec_summary["potential_monthly_savings"],
189
+ "quarterly": exec_summary["potential_monthly_savings"] * 3,
190
+ "annual": exec_summary["annual_savings_potential"],
191
+ },
192
+ "cost_avoidance": {
193
+ "description": "Prevents future cost growth from inefficient resources",
194
+ "annual_value": exec_summary["annual_savings_potential"] * 0.1, # 10% growth avoidance
189
195
  },
190
- 'cost_avoidance': {
191
- 'description': 'Prevents future cost growth from inefficient resources',
192
- 'annual_value': exec_summary['annual_savings_potential'] * 0.1 # 10% growth avoidance
196
+ "implementation_cost": {
197
+ "personnel_time": "Existing team allocation",
198
+ "infrastructure_changes": "Minimal - configuration only",
199
+ "estimated_hours": 40, # Conservative estimate
200
+ "estimated_cost": 5000, # Based on average engineer cost
193
201
  },
194
- 'implementation_cost': {
195
- 'personnel_time': 'Existing team allocation',
196
- 'infrastructure_changes': 'Minimal - configuration only',
197
- 'estimated_hours': 40, # Conservative estimate
198
- 'estimated_cost': 5000 # Based on average engineer cost
202
+ "roi_analysis": {
203
+ "investment": 5000,
204
+ "annual_return": exec_summary["annual_savings_potential"],
205
+ "roi_percentage": (exec_summary["annual_savings_potential"] / 5000 * 100)
206
+ if exec_summary["annual_savings_potential"] > 0
207
+ else 0,
208
+ "payback_months": max(1, 5000 / max(exec_summary["potential_monthly_savings"], 1)),
199
209
  },
200
- 'roi_analysis': {
201
- 'investment': 5000,
202
- 'annual_return': exec_summary['annual_savings_potential'],
203
- 'roi_percentage': (exec_summary['annual_savings_potential'] / 5000 * 100) if exec_summary['annual_savings_potential'] > 0 else 0,
204
- 'payback_months': max(1, 5000 / max(exec_summary['potential_monthly_savings'], 1))
205
- }
206
210
  }
207
-
211
+
208
212
  def _assess_business_risks(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
209
213
  """Assess business risks of optimization implementation"""
210
-
211
- total_cost = sum([
212
- analysis.get('nat_gateways', {}).get('total_cost', 0),
213
- analysis.get('vpc_endpoints', {}).get('total_cost', 0),
214
- analysis.get('transit_gateway', {}).get('total_monthly_cost', 0)
215
- ])
216
-
214
+
215
+ total_cost = sum(
216
+ [
217
+ analysis.get("nat_gateways", {}).get("total_cost", 0),
218
+ analysis.get("vpc_endpoints", {}).get("total_cost", 0),
219
+ analysis.get("transit_gateway", {}).get("total_monthly_cost", 0),
220
+ ]
221
+ )
222
+
217
223
  # Risk assessment based on cost and complexity
218
224
  risk_level = RiskLevel.LOW
219
225
  if total_cost > 500:
220
226
  risk_level = RiskLevel.MEDIUM
221
227
  if total_cost > 1000:
222
228
  risk_level = RiskLevel.HIGH
223
-
229
+
224
230
  return {
225
- 'overall_risk': risk_level.value,
226
- 'risk_factors': [
231
+ "overall_risk": risk_level.value,
232
+ "risk_factors": [
227
233
  {
228
- 'factor': 'Service disruption during implementation',
229
- 'probability': 'Low',
230
- 'impact': 'Medium',
231
- 'mitigation': 'Gradual rollout with testing'
234
+ "factor": "Service disruption during implementation",
235
+ "probability": "Low",
236
+ "impact": "Medium",
237
+ "mitigation": "Gradual rollout with testing",
232
238
  },
233
239
  {
234
- 'factor': 'Configuration errors',
235
- 'probability': 'Low',
236
- 'impact': 'High',
237
- 'mitigation': 'Dry-run validation and peer review'
240
+ "factor": "Configuration errors",
241
+ "probability": "Low",
242
+ "impact": "High",
243
+ "mitigation": "Dry-run validation and peer review",
238
244
  },
239
245
  {
240
- 'factor': 'Cost increase if misconfigured',
241
- 'probability': 'Very Low',
242
- 'impact': 'Medium',
243
- 'mitigation': 'Cost monitoring and automated alerts'
244
- }
246
+ "factor": "Cost increase if misconfigured",
247
+ "probability": "Very Low",
248
+ "impact": "Medium",
249
+ "mitigation": "Cost monitoring and automated alerts",
250
+ },
245
251
  ],
246
- 'mitigation_strategies': [
247
- 'Implement changes in non-production first',
248
- 'Use Infrastructure as Code for consistency',
249
- 'Enable comprehensive monitoring and alerting',
250
- 'Maintain rollback procedures',
251
- 'Conduct thorough testing before production'
252
+ "mitigation_strategies": [
253
+ "Implement changes in non-production first",
254
+ "Use Infrastructure as Code for consistency",
255
+ "Enable comprehensive monitoring and alerting",
256
+ "Maintain rollback procedures",
257
+ "Conduct thorough testing before production",
252
258
  ],
253
- 'business_continuity': 'No impact expected with proper implementation'
259
+ "business_continuity": "No impact expected with proper implementation",
254
260
  }
255
-
261
+
256
262
  def _create_implementation_plan(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
257
263
  """Create business-focused implementation plan"""
258
-
264
+
259
265
  exec_summary = self._create_executive_summary(analysis)
260
-
266
+
261
267
  phases = [
262
268
  {
263
- 'phase': 1,
264
- 'name': 'Quick Wins & Foundation',
265
- 'duration': '2-4 weeks',
266
- 'savings': exec_summary['potential_monthly_savings'] * 0.4, # 40% of savings
267
- 'activities': [
268
- 'Deploy free Gateway VPC Endpoints',
269
- 'Implement cost monitoring',
270
- 'Analyze underutilized resources'
269
+ "phase": 1,
270
+ "name": "Quick Wins & Foundation",
271
+ "duration": "2-4 weeks",
272
+ "savings": exec_summary["potential_monthly_savings"] * 0.4, # 40% of savings
273
+ "activities": [
274
+ "Deploy free Gateway VPC Endpoints",
275
+ "Implement cost monitoring",
276
+ "Analyze underutilized resources",
271
277
  ],
272
- 'risk': 'Low',
273
- 'approval_required': exec_summary['potential_monthly_savings'] * 12 > self.config.approval_threshold
278
+ "risk": "Low",
279
+ "approval_required": exec_summary["potential_monthly_savings"] * 12 > self.config.approval_threshold,
274
280
  },
275
281
  {
276
- 'phase': 2,
277
- 'name': 'Strategic Optimization',
278
- 'duration': '4-8 weeks',
279
- 'savings': exec_summary['potential_monthly_savings'] * 0.6, # 60% of savings
280
- 'activities': [
281
- 'Optimize NAT Gateway usage',
282
- 'Deploy strategic Interface endpoints',
283
- 'Consolidate Transit Gateway attachments'
282
+ "phase": 2,
283
+ "name": "Strategic Optimization",
284
+ "duration": "4-8 weeks",
285
+ "savings": exec_summary["potential_monthly_savings"] * 0.6, # 60% of savings
286
+ "activities": [
287
+ "Optimize NAT Gateway usage",
288
+ "Deploy strategic Interface endpoints",
289
+ "Consolidate Transit Gateway attachments",
284
290
  ],
285
- 'risk': 'Medium',
286
- 'approval_required': True
287
- }
291
+ "risk": "Medium",
292
+ "approval_required": True,
293
+ },
288
294
  ]
289
-
295
+
290
296
  return {
291
- 'total_timeline': '6-12 weeks',
292
- 'phases': phases,
293
- 'resource_requirements': {
294
- 'project_manager': '25% allocation for 3 months',
295
- 'cloud_engineer': '75% allocation for 6 weeks',
296
- 'solutions_architect': '50% allocation for 2 weeks',
297
- 'security_engineer': '25% allocation for 1 week'
297
+ "total_timeline": "6-12 weeks",
298
+ "phases": phases,
299
+ "resource_requirements": {
300
+ "project_manager": "25% allocation for 3 months",
301
+ "cloud_engineer": "75% allocation for 6 weeks",
302
+ "solutions_architect": "50% allocation for 2 weeks",
303
+ "security_engineer": "25% allocation for 1 week",
298
304
  },
299
- 'success_criteria': [
300
- f'Achieve {self.config.target_savings_percentage}% cost reduction',
301
- 'No service disruptions during implementation',
302
- 'All changes validated in non-production first',
303
- 'Cost monitoring and alerting operational'
304
- ]
305
+ "success_criteria": [
306
+ f"Achieve {self.config.target_savings_percentage}% cost reduction",
307
+ "No service disruptions during implementation",
308
+ "All changes validated in non-production first",
309
+ "Cost monitoring and alerting operational",
310
+ ],
305
311
  }
306
-
312
+
307
313
  def _define_success_metrics(self, analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
308
314
  """Define business success metrics"""
309
-
315
+
310
316
  exec_summary = self._create_executive_summary(analysis)
311
-
317
+
312
318
  return [
313
319
  {
314
- 'metric': 'Monthly Cost Reduction',
315
- 'target': f'${exec_summary["potential_monthly_savings"]:.2f}',
316
- 'measurement': 'AWS billing comparison',
317
- 'frequency': 'Monthly'
320
+ "metric": "Monthly Cost Reduction",
321
+ "target": f"${exec_summary['potential_monthly_savings']:.2f}",
322
+ "measurement": "AWS billing comparison",
323
+ "frequency": "Monthly",
318
324
  },
319
325
  {
320
- 'metric': 'Savings Percentage',
321
- 'target': f'{self.config.target_savings_percentage}%+',
322
- 'measurement': 'Automated cost analysis',
323
- 'frequency': 'Weekly'
326
+ "metric": "Savings Percentage",
327
+ "target": f"{self.config.target_savings_percentage}%+",
328
+ "measurement": "Automated cost analysis",
329
+ "frequency": "Weekly",
324
330
  },
325
331
  {
326
- 'metric': 'Implementation Timeline',
327
- 'target': '< 12 weeks',
328
- 'measurement': 'Project milestone tracking',
329
- 'frequency': 'Weekly'
332
+ "metric": "Implementation Timeline",
333
+ "target": "< 12 weeks",
334
+ "measurement": "Project milestone tracking",
335
+ "frequency": "Weekly",
330
336
  },
331
337
  {
332
- 'metric': 'Business Continuity',
333
- 'target': 'Zero disruptions',
334
- 'measurement': 'Incident tracking',
335
- 'frequency': 'Continuous'
338
+ "metric": "Business Continuity",
339
+ "target": "Zero disruptions",
340
+ "measurement": "Incident tracking",
341
+ "frequency": "Continuous",
336
342
  },
337
343
  {
338
- 'metric': 'ROI Achievement',
339
- 'target': f'> {((exec_summary["annual_savings_potential"] / 5000 * 100) if exec_summary["annual_savings_potential"] > 0 else 0):.0f}%',
340
- 'measurement': 'Financial analysis',
341
- 'frequency': 'Quarterly'
342
- }
344
+ "metric": "ROI Achievement",
345
+ "target": f"> {((exec_summary['annual_savings_potential'] / 5000 * 100) if exec_summary['annual_savings_potential'] > 0 else 0):.0f}%",
346
+ "measurement": "Financial analysis",
347
+ "frequency": "Quarterly",
348
+ },
343
349
  ]
344
-
350
+
345
351
  def _determine_approval_requirements(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
346
352
  """Determine what approvals are needed"""
347
-
353
+
348
354
  exec_summary = self._create_executive_summary(analysis)
349
- annual_impact = exec_summary['annual_savings_potential']
350
-
355
+ annual_impact = exec_summary["annual_savings_potential"]
356
+
351
357
  approvals_needed = []
352
-
358
+
353
359
  if annual_impact > self.config.approval_threshold:
354
- approvals_needed.append({
355
- 'type': 'Financial Approval',
356
- 'reason': f'Annual impact ${annual_impact:,.2f} exceeds threshold',
357
- 'approver': 'Finance/Management',
358
- 'timeline': '1-2 weeks'
359
- })
360
-
361
- if exec_summary['current_monthly_cost'] > 500:
362
- approvals_needed.append({
363
- 'type': 'Technical Approval',
364
- 'reason': 'High-value infrastructure changes',
365
- 'approver': 'Solutions Architecture',
366
- 'timeline': '1 week'
367
- })
368
-
360
+ approvals_needed.append(
361
+ {
362
+ "type": "Financial Approval",
363
+ "reason": f"Annual impact ${annual_impact:,.2f} exceeds threshold",
364
+ "approver": "Finance/Management",
365
+ "timeline": "1-2 weeks",
366
+ }
367
+ )
368
+
369
+ if exec_summary["current_monthly_cost"] > 500:
370
+ approvals_needed.append(
371
+ {
372
+ "type": "Technical Approval",
373
+ "reason": "High-value infrastructure changes",
374
+ "approver": "Solutions Architecture",
375
+ "timeline": "1 week",
376
+ }
377
+ )
378
+
369
379
  return {
370
- 'approvals_required': len(approvals_needed) > 0,
371
- 'approval_list': approvals_needed,
372
- 'estimated_approval_timeline': '2-3 weeks' if approvals_needed else '0 weeks',
373
- 'bypass_criteria': 'Emergency cost optimization directive'
380
+ "approvals_required": len(approvals_needed) > 0,
381
+ "approval_list": approvals_needed,
382
+ "estimated_approval_timeline": "2-3 weeks" if approvals_needed else "0 weeks",
383
+ "bypass_criteria": "Emergency cost optimization directive",
374
384
  }
375
-
385
+
376
386
  def _generate_business_recommendations(
377
- self,
378
- analysis: Dict[str, Any],
379
- business_analysis: Dict[str, Any]
387
+ self, analysis: Dict[str, Any], business_analysis: Dict[str, Any]
380
388
  ) -> List[BusinessRecommendation]:
381
389
  """Generate business-focused recommendations"""
382
-
390
+
383
391
  recommendations = []
384
- exec_summary = business_analysis['executive_summary']
385
-
392
+ exec_summary = business_analysis["executive_summary"]
393
+
386
394
  # Quick Win: Gateway VPC Endpoints
387
- recommendations.append(BusinessRecommendation(
388
- title="Deploy Free Gateway VPC Endpoints",
389
- executive_summary="Eliminate NAT Gateway charges for S3 and DynamoDB traffic with zero-cost solution",
390
- monthly_savings=25.0,
391
- annual_impact=300.0,
392
- implementation_timeline="1-2 weeks",
393
- business_priority=BusinessPriority.HIGH,
394
- risk_level=RiskLevel.MINIMAL,
395
- resource_requirements=["Cloud Engineer (1 week)"],
396
- success_metrics=["NAT Gateway data transfer reduction", "Zero implementation cost"],
397
- approval_required=False,
398
- quick_win=True,
399
- strategic_value="Foundation for advanced VPC optimization"
400
- ))
401
-
402
- # Strategic: NAT Gateway Optimization
403
- if analysis.get('nat_gateways', {}).get('optimization_potential', 0) > 20:
404
- recommendations.append(BusinessRecommendation(
405
- title="Optimize Underutilized NAT Gateways",
406
- executive_summary="Remove or consolidate NAT Gateways with low utilization to eliminate waste",
407
- monthly_savings=analysis['nat_gateways']['optimization_potential'],
408
- annual_impact=analysis['nat_gateways']['optimization_potential'] * 12,
409
- implementation_timeline="3-4 weeks",
395
+ recommendations.append(
396
+ BusinessRecommendation(
397
+ title="Deploy Free Gateway VPC Endpoints",
398
+ executive_summary="Eliminate NAT Gateway charges for S3 and DynamoDB traffic with zero-cost solution",
399
+ monthly_savings=25.0,
400
+ annual_impact=300.0,
401
+ implementation_timeline="1-2 weeks",
410
402
  business_priority=BusinessPriority.HIGH,
411
- risk_level=RiskLevel.MEDIUM,
412
- resource_requirements=["Cloud Engineer (2 weeks)", "Security review"],
413
- success_metrics=["Resource utilization improvement", "Cost per connection reduction"],
414
- approval_required=analysis['nat_gateways']['optimization_potential'] * 12 > self.config.approval_threshold,
415
- quick_win=False,
416
- strategic_value="Long-term infrastructure efficiency improvement"
417
- ))
418
-
403
+ risk_level=RiskLevel.MINIMAL,
404
+ resource_requirements=["Cloud Engineer (1 week)"],
405
+ success_metrics=["NAT Gateway data transfer reduction", "Zero implementation cost"],
406
+ approval_required=False,
407
+ quick_win=True,
408
+ strategic_value="Foundation for advanced VPC optimization",
409
+ )
410
+ )
411
+
412
+ # Strategic: NAT Gateway Optimization
413
+ if analysis.get("nat_gateways", {}).get("optimization_potential", 0) > 20:
414
+ recommendations.append(
415
+ BusinessRecommendation(
416
+ title="Optimize Underutilized NAT Gateways",
417
+ executive_summary="Remove or consolidate NAT Gateways with low utilization to eliminate waste",
418
+ monthly_savings=analysis["nat_gateways"]["optimization_potential"],
419
+ annual_impact=analysis["nat_gateways"]["optimization_potential"] * 12,
420
+ implementation_timeline="3-4 weeks",
421
+ business_priority=BusinessPriority.HIGH,
422
+ risk_level=RiskLevel.MEDIUM,
423
+ resource_requirements=["Cloud Engineer (2 weeks)", "Security review"],
424
+ success_metrics=["Resource utilization improvement", "Cost per connection reduction"],
425
+ approval_required=analysis["nat_gateways"]["optimization_potential"] * 12
426
+ > self.config.approval_threshold,
427
+ quick_win=False,
428
+ strategic_value="Long-term infrastructure efficiency improvement",
429
+ )
430
+ )
431
+
419
432
  # Advanced: Transit Gateway Optimization
420
- tgw_savings = analysis.get('transit_gateway', {}).get('potential_savings', 0)
433
+ tgw_savings = analysis.get("transit_gateway", {}).get("potential_savings", 0)
421
434
  if tgw_savings > 30:
422
- recommendations.append(BusinessRecommendation(
423
- title="Optimize Multi-Account Network Architecture",
424
- executive_summary="Consolidate Transit Gateway attachments and implement centralized endpoints",
425
- monthly_savings=tgw_savings,
426
- annual_impact=tgw_savings * 12,
427
- implementation_timeline="6-8 weeks",
428
- business_priority=BusinessPriority.MEDIUM,
429
- risk_level=RiskLevel.HIGH,
430
- resource_requirements=["Solutions Architect (3 weeks)", "Network Engineer (2 weeks)"],
431
- success_metrics=["Multi-account cost efficiency", "Network architecture optimization"],
432
- approval_required=True,
433
- quick_win=False,
434
- strategic_value="Enterprise-scale network optimization and future-proofing"
435
- ))
436
-
435
+ recommendations.append(
436
+ BusinessRecommendation(
437
+ title="Optimize Multi-Account Network Architecture",
438
+ executive_summary="Consolidate Transit Gateway attachments and implement centralized endpoints",
439
+ monthly_savings=tgw_savings,
440
+ annual_impact=tgw_savings * 12,
441
+ implementation_timeline="6-8 weeks",
442
+ business_priority=BusinessPriority.MEDIUM,
443
+ risk_level=RiskLevel.HIGH,
444
+ resource_requirements=["Solutions Architect (3 weeks)", "Network Engineer (2 weeks)"],
445
+ success_metrics=["Multi-account cost efficiency", "Network architecture optimization"],
446
+ approval_required=True,
447
+ quick_win=False,
448
+ strategic_value="Enterprise-scale network optimization and future-proofing",
449
+ )
450
+ )
451
+
437
452
  return recommendations
438
-
453
+
439
454
  def generate_executive_presentation(self) -> Dict[str, Any]:
440
455
  """Generate executive presentation content"""
441
-
456
+
442
457
  if not self.analysis_results:
443
458
  raise ValueError("No analysis results available. Run cost analysis first.")
444
-
445
- exec_summary = self.analysis_results['executive_summary']
446
- financial_impact = self.analysis_results['financial_impact']
447
-
459
+
460
+ exec_summary = self.analysis_results["executive_summary"]
461
+ financial_impact = self.analysis_results["financial_impact"]
462
+
448
463
  presentation = {
449
- 'slide_1': {
450
- 'title': 'VPC Cost Optimization Opportunity',
451
- 'content': [
464
+ "slide_1": {
465
+ "title": "VPC Cost Optimization Opportunity",
466
+ "content": [
452
467
  f"Current monthly cost: ${exec_summary['current_monthly_cost']:,.2f}",
453
468
  f"Potential savings: ${exec_summary['potential_monthly_savings']:,.2f} ({exec_summary['savings_percentage']:.1f}%)",
454
469
  f"Annual impact: ${exec_summary['annual_savings_potential']:,.2f}",
455
- f"Business case: {exec_summary['business_case_strength']}"
456
- ]
470
+ f"Business case: {exec_summary['business_case_strength']}",
471
+ ],
457
472
  },
458
- 'slide_2': {
459
- 'title': 'Financial Impact & ROI',
460
- 'content': [
473
+ "slide_2": {
474
+ "title": "Financial Impact & ROI",
475
+ "content": [
461
476
  f"ROI: {financial_impact['roi_analysis']['roi_percentage']:.0f}%",
462
477
  f"Payback period: {financial_impact['roi_analysis']['payback_months']:.0f} months",
463
478
  f"Implementation cost: ${financial_impact['implementation_cost']['estimated_cost']:,.2f}",
464
- f"Net annual benefit: ${exec_summary['annual_savings_potential'] - financial_impact['implementation_cost']['estimated_cost']:,.2f}"
465
- ]
479
+ f"Net annual benefit: ${exec_summary['annual_savings_potential'] - financial_impact['implementation_cost']['estimated_cost']:,.2f}",
480
+ ],
466
481
  },
467
- 'slide_3': {
468
- 'title': 'Implementation Plan',
469
- 'content': [
482
+ "slide_3": {
483
+ "title": "Implementation Plan",
484
+ "content": [
470
485
  "Phase 1: Quick wins (2-4 weeks)",
471
- "Phase 2: Strategic optimization (4-8 weeks)",
486
+ "Phase 2: Strategic optimization (4-8 weeks)",
472
487
  f"Total timeline: {self.analysis_results['implementation_plan']['total_timeline']}",
473
- f"Risk level: {self.analysis_results['risk_assessment']['overall_risk']}"
474
- ]
488
+ f"Risk level: {self.analysis_results['risk_assessment']['overall_risk']}",
489
+ ],
475
490
  },
476
- 'slide_4': {
477
- 'title': 'Business Benefits',
478
- 'content': [
491
+ "slide_4": {
492
+ "title": "Business Benefits",
493
+ "content": [
479
494
  "Immediate cost reduction",
480
495
  "Improved network security through VPC endpoints",
481
496
  "Enhanced operational efficiency",
482
- "Foundation for ongoing optimization"
483
- ]
497
+ "Foundation for ongoing optimization",
498
+ ],
484
499
  },
485
- 'slide_5': {
486
- 'title': 'Next Steps',
487
- 'content': [
500
+ "slide_5": {
501
+ "title": "Next Steps",
502
+ "content": [
488
503
  "Approve Phase 1 implementation",
489
504
  "Assign technical resources",
490
505
  "Schedule weekly progress reviews",
491
- "Plan Phase 2 strategic optimization"
492
- ]
493
- }
506
+ "Plan Phase 2 strategic optimization",
507
+ ],
508
+ },
494
509
  }
495
-
510
+
496
511
  return presentation
497
-
512
+
498
513
  def export_manager_friendly_reports(self, timestamp: Optional[str] = None) -> Dict[str, str]:
499
514
  """Export comprehensive manager-friendly reports"""
500
-
515
+
501
516
  if not self.analysis_results:
502
517
  raise ValueError("No analysis results to export")
503
-
518
+
504
519
  timestamp = timestamp or datetime.now().strftime("%Y%m%d_%H%M%S")
505
520
  self.export_directory.mkdir(parents=True, exist_ok=True)
506
-
521
+
507
522
  exported_files = {}
508
-
523
+
509
524
  # 1. Executive Summary (JSON)
510
525
  executive_report = {
511
- 'metadata': {
512
- 'report_type': 'vpc_cost_optimization_executive',
513
- 'generated_at': datetime.now().isoformat(),
514
- 'version': '2.0'
526
+ "metadata": {
527
+ "report_type": "vpc_cost_optimization_executive",
528
+ "generated_at": datetime.now().isoformat(),
529
+ "version": "2.0",
515
530
  },
516
- 'analysis_results': self.analysis_results,
517
- 'business_recommendations': [asdict(rec) for rec in self.business_recommendations],
518
- 'executive_presentation': self.generate_executive_presentation()
531
+ "analysis_results": self.analysis_results,
532
+ "business_recommendations": [asdict(rec) for rec in self.business_recommendations],
533
+ "executive_presentation": self.generate_executive_presentation(),
519
534
  }
520
-
535
+
521
536
  exec_file = self.export_directory / f"executive_report_{timestamp}.json"
522
- with open(exec_file, 'w') as f:
537
+ with open(exec_file, "w") as f:
523
538
  json.dump(executive_report, f, indent=2, default=str)
524
- exported_files['executive_report'] = str(exec_file)
525
-
539
+ exported_files["executive_report"] = str(exec_file)
540
+
526
541
  # 2. Business Recommendations CSV
527
542
  if self.business_recommendations:
528
543
  recommendations_data = []
529
544
  for rec in self.business_recommendations:
530
- recommendations_data.append({
531
- 'Title': rec.title,
532
- 'Monthly Savings': rec.monthly_savings,
533
- 'Annual Impact': rec.annual_impact,
534
- 'Priority': rec.business_priority.value,
535
- 'Risk Level': rec.risk_level.value,
536
- 'Timeline': rec.implementation_timeline,
537
- 'Quick Win': rec.quick_win,
538
- 'Approval Required': rec.approval_required,
539
- 'Strategic Value': rec.strategic_value
540
- })
541
-
545
+ recommendations_data.append(
546
+ {
547
+ "Title": rec.title,
548
+ "Monthly Savings": rec.monthly_savings,
549
+ "Annual Impact": rec.annual_impact,
550
+ "Priority": rec.business_priority.value,
551
+ "Risk Level": rec.risk_level.value,
552
+ "Timeline": rec.implementation_timeline,
553
+ "Quick Win": rec.quick_win,
554
+ "Approval Required": rec.approval_required,
555
+ "Strategic Value": rec.strategic_value,
556
+ }
557
+ )
558
+
542
559
  rec_df = pd.DataFrame(recommendations_data)
543
560
  rec_file = self.export_directory / f"business_recommendations_{timestamp}.csv"
544
561
  rec_df.to_csv(rec_file, index=False)
545
- exported_files['recommendations'] = str(rec_file)
546
-
562
+ exported_files["recommendations"] = str(rec_file)
563
+
547
564
  # 3. Financial Analysis Spreadsheet
548
565
  financial_data = {
549
- 'Metric': [
550
- 'Current Monthly Cost',
551
- 'Potential Monthly Savings',
552
- 'Annual Savings',
553
- 'Implementation Cost',
554
- 'Net Annual Benefit',
555
- 'ROI Percentage',
556
- 'Payback Period (Months)'
566
+ "Metric": [
567
+ "Current Monthly Cost",
568
+ "Potential Monthly Savings",
569
+ "Annual Savings",
570
+ "Implementation Cost",
571
+ "Net Annual Benefit",
572
+ "ROI Percentage",
573
+ "Payback Period (Months)",
574
+ ],
575
+ "Value": [
576
+ self.analysis_results["executive_summary"]["current_monthly_cost"],
577
+ self.analysis_results["executive_summary"]["potential_monthly_savings"],
578
+ self.analysis_results["executive_summary"]["annual_savings_potential"],
579
+ self.analysis_results["financial_impact"]["implementation_cost"]["estimated_cost"],
580
+ self.analysis_results["executive_summary"]["annual_savings_potential"]
581
+ - self.analysis_results["financial_impact"]["implementation_cost"]["estimated_cost"],
582
+ self.analysis_results["financial_impact"]["roi_analysis"]["roi_percentage"],
583
+ self.analysis_results["financial_impact"]["roi_analysis"]["payback_months"],
557
584
  ],
558
- 'Value': [
559
- self.analysis_results['executive_summary']['current_monthly_cost'],
560
- self.analysis_results['executive_summary']['potential_monthly_savings'],
561
- self.analysis_results['executive_summary']['annual_savings_potential'],
562
- self.analysis_results['financial_impact']['implementation_cost']['estimated_cost'],
563
- self.analysis_results['executive_summary']['annual_savings_potential'] - self.analysis_results['financial_impact']['implementation_cost']['estimated_cost'],
564
- self.analysis_results['financial_impact']['roi_analysis']['roi_percentage'],
565
- self.analysis_results['financial_impact']['roi_analysis']['payback_months']
566
- ]
567
585
  }
568
-
586
+
569
587
  financial_df = pd.DataFrame(financial_data)
570
588
  financial_file = self.export_directory / f"financial_analysis_{timestamp}.csv"
571
589
  financial_df.to_csv(financial_file, index=False)
572
- exported_files['financial_analysis'] = str(financial_file)
573
-
590
+ exported_files["financial_analysis"] = str(financial_file)
591
+
574
592
  return exported_files
575
-
593
+
576
594
  def display_business_dashboard(self) -> None:
577
595
  """Display comprehensive business dashboard"""
578
-
596
+
579
597
  if not self.analysis_results:
580
598
  self.console.print("[red]❌ No analysis results available[/red]")
581
599
  return
582
-
583
- exec_summary = self.analysis_results['executive_summary']
584
- financial = self.analysis_results['financial_impact']
585
-
600
+
601
+ exec_summary = self.analysis_results["executive_summary"]
602
+ financial = self.analysis_results["financial_impact"]
603
+
586
604
  # Main dashboard panel
587
605
  dashboard_content = (
588
606
  f"[bold blue]💰 FINANCIAL IMPACT[/bold blue]\n"
@@ -601,16 +619,13 @@ class VPCManagerInterface:
601
619
  f"Quick Wins: [green]{sum(1 for r in self.business_recommendations if r.quick_win)}[/green]\n"
602
620
  f"Approval Required: [red]{sum(1 for r in self.business_recommendations if r.approval_required)}[/red]"
603
621
  )
604
-
622
+
605
623
  dashboard_panel = Panel(
606
- dashboard_content,
607
- title="VPC Cost Optimization - Business Dashboard",
608
- style="white",
609
- width=80
624
+ dashboard_content, title="VPC Cost Optimization - Business Dashboard", style="white", width=80
610
625
  )
611
-
626
+
612
627
  self.console.print(dashboard_panel)
613
-
628
+
614
629
  # Recommendations table
615
630
  if self.business_recommendations:
616
631
  rec_table = Table(title="Business Recommendations", show_header=True)
@@ -620,7 +635,7 @@ class VPCManagerInterface:
620
635
  rec_table.add_column("Risk", style="red")
621
636
  rec_table.add_column("Timeline", style="blue")
622
637
  rec_table.add_column("Quick Win", style="magenta")
623
-
638
+
624
639
  for rec in self.business_recommendations:
625
640
  rec_table.add_row(
626
641
  rec.title,
@@ -628,7 +643,7 @@ class VPCManagerInterface:
628
643
  rec.business_priority.value,
629
644
  rec.risk_level.value,
630
645
  rec.implementation_timeline,
631
- "✅" if rec.quick_win else "⏳"
646
+ "✅" if rec.quick_win else "⏳",
632
647
  )
633
-
634
- self.console.print(rec_table)
648
+
649
+ self.console.print(rec_table)