runbooks 0.9.4__py3-none-any.whl → 0.9.6__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.
runbooks/__init__.py CHANGED
@@ -61,7 +61,7 @@ s3_ops = S3Operations()
61
61
 
62
62
  # Centralized Version Management - Single Source of Truth
63
63
  # All modules MUST import __version__ from this location
64
- __version__ = "0.9.3"
64
+ __version__ = "0.9.6"
65
65
 
66
66
  # Fallback for legacy importlib.metadata usage during transition
67
67
  try:
@@ -614,14 +614,14 @@ def create_columns(items: List[Any], equal: bool = True, expand: bool = True) ->
614
614
  # Manager's Cost Optimization Scenario Formatting Functions
615
615
  def format_workspaces_analysis(workspaces_data: Dict[str, Any], target_savings: int = 12518) -> Panel:
616
616
  """
617
- Format WorkSpaces cost analysis for manager's AWSO-24 scenario.
617
+ Format WorkSpaces cost analysis for manager's FinOps-24 scenario.
618
618
 
619
619
  Based on manager's requirement for $12,518 annual savings through
620
620
  cleanup of unused WorkSpaces with zero usage in last 6 months.
621
621
 
622
622
  Args:
623
623
  workspaces_data: Dictionary containing WorkSpaces cost and utilization data
624
- target_savings: Annual savings target (default: $12,518 from AWSO-24)
624
+ target_savings: Annual savings target (default: $12,518 from FinOps-24)
625
625
 
626
626
  Returns:
627
627
  Rich Panel with formatted WorkSpaces analysis
@@ -657,7 +657,7 @@ def format_workspaces_analysis(workspaces_data: Dict[str, Any], target_savings:
657
657
 
658
658
  [{status_style}]{status}[/]"""
659
659
 
660
- return Panel(content, title="[bright_cyan]AWSO-24: WorkSpaces Cost Optimization[/bright_cyan]",
660
+ return Panel(content, title="[bright_cyan]FinOps-24: WorkSpaces Cost Optimization[/bright_cyan]",
661
661
  border_style="bright_green" if target_achievement >= 90 else "yellow")
662
662
 
663
663
 
@@ -716,7 +716,7 @@ def format_nat_gateway_optimization(nat_data: Dict[str, Any], target_completion:
716
716
 
717
717
  def format_rds_optimization_analysis(rds_data: Dict[str, Any], savings_range: Dict[str, int] = None) -> Panel:
718
718
  """
719
- Format RDS Multi-AZ optimization analysis for manager's AWSO-23 scenario.
719
+ Format RDS Multi-AZ optimization analysis for manager's FinOps-23 scenario.
720
720
 
721
721
  Manager's requirement for $5K-24K annual savings through RDS manual snapshot cleanup
722
722
  and Multi-AZ configuration review.
@@ -765,7 +765,7 @@ def format_rds_optimization_analysis(rds_data: Dict[str, Any], savings_range: Di
765
765
  🎯 Manager's Target Range:
766
766
  • Minimum Target: [bright_cyan]${savings_min:,.0f}[/bright_cyan]
767
767
  • Maximum Target: [bright_cyan]${savings_max:,.0f}[/bright_cyan]
768
- • Business Case: $5K-24K annual opportunity (AWSO-23)
768
+ • Business Case: $5K-24K annual opportunity (FinOps-23)
769
769
 
770
770
  ⏰ Implementation:
771
771
  • Timeline: 10-12 weeks
@@ -774,7 +774,7 @@ def format_rds_optimization_analysis(rds_data: Dict[str, Any], savings_range: Di
774
774
 
775
775
  [{range_style}]{range_status}[/]"""
776
776
 
777
- return Panel(content, title="[bright_cyan]AWSO-23: RDS Multi-AZ & Snapshot Optimization[/bright_cyan]",
777
+ return Panel(content, title="[bright_cyan]FinOps-23: RDS Multi-AZ & Snapshot Optimization[/bright_cyan]",
778
778
  border_style="bright_green" if within_range else "yellow")
779
779
 
780
780
 
@@ -783,9 +783,9 @@ def format_manager_business_summary(all_scenarios_data: Dict[str, Any]) -> Panel
783
783
  Format executive summary panel for manager's complete AWSO business case.
784
784
 
785
785
  Combines all three manager priorities into executive-ready decision package:
786
- - AWSO-24: WorkSpaces cleanup ($12,518)
786
+ - FinOps-24: WorkSpaces cleanup ($12,518)
787
787
  - Manager Priority #2: NAT Gateway optimization (95% completion)
788
- - AWSO-23: RDS optimization ($5K-24K range)
788
+ - FinOps-23: RDS optimization ($5K-24K range)
789
789
 
790
790
  Args:
791
791
  all_scenarios_data: Dictionary containing data from all three scenarios
@@ -43,6 +43,8 @@ from runbooks.finops.dashboard_runner import (
43
43
  # Enterprise FinOps Dashboard Components - Using existing dashboard_runner.py
44
44
  # Backward compatibility module for legacy tests and components
45
45
  from runbooks.finops.finops_dashboard import FinOpsConfig
46
+
47
+ # Enhanced helpers with notebook integration functions
46
48
  from runbooks.finops.helpers import (
47
49
  export_audit_report_to_csv,
48
50
  export_audit_report_to_json,
@@ -50,6 +52,29 @@ from runbooks.finops.helpers import (
50
52
  export_cost_dashboard_to_pdf,
51
53
  export_trend_data_to_json,
52
54
  load_config_file,
55
+ # NOTEBOOK INTEGRATION FUNCTIONS - Added for clean notebook consumption
56
+ format_currency,
57
+ create_business_summary_table,
58
+ export_scenarios_to_notebook_html,
59
+ create_roi_analysis_table,
60
+ )
61
+
62
+ # Business scenarios with notebook integration
63
+ from runbooks.finops.finops_scenarios import (
64
+ create_business_scenarios_validated,
65
+ format_for_business_audience,
66
+ format_for_technical_audience,
67
+ FinOpsBusinessScenarios,
68
+ )
69
+
70
+ # NEW v0.9.5: Clean API wrapper for notebook consumption
71
+ from runbooks.finops.scenarios import (
72
+ finops_24_workspaces_cleanup,
73
+ finops_23_rds_snapshots_optimization,
74
+ finops_25_commvault_investigation,
75
+ get_business_scenarios_summary,
76
+ format_for_audience,
77
+ validate_scenarios_accuracy,
53
78
  )
54
79
  from runbooks.finops.profile_processor import process_combined_profiles, process_single_profile
55
80
 
@@ -70,6 +95,18 @@ __all__ = [
70
95
  "_run_executive_dashboard",
71
96
  # Enterprise Dashboard Classes - backward compatibility
72
97
  "FinOpsConfig",
98
+ # Business scenarios with notebook integration (v0.9.5)
99
+ "create_business_scenarios_validated",
100
+ "format_for_business_audience",
101
+ "format_for_technical_audience",
102
+ "FinOpsBusinessScenarios",
103
+ # NEW v0.9.5: Clean API wrapper functions
104
+ "finops_24_workspaces_cleanup",
105
+ "finops_23_rds_snapshots_optimization",
106
+ "finops_25_commvault_investigation",
107
+ "get_business_scenarios_summary",
108
+ "format_for_audience",
109
+ "validate_scenarios_accuracy",
73
110
  # Processors
74
111
  "get_cost_data",
75
112
  "get_trend",
@@ -95,6 +132,11 @@ __all__ = [
95
132
  "export_audit_report_to_json",
96
133
  "export_trend_data_to_json",
97
134
  "load_config_file",
135
+ # NOTEBOOK INTEGRATION FUNCTIONS (v0.9.5)
136
+ "format_currency",
137
+ "create_business_summary_table",
138
+ "export_scenarios_to_notebook_html",
139
+ "create_roi_analysis_table",
98
140
  # Types
99
141
  "ProfileData",
100
142
  "CostData",
@@ -0,0 +1,552 @@
1
+ """
2
+ FinOps Business Case Analysis Framework
3
+ =====================================
4
+
5
+ Enterprise-grade business case analysis for cost optimization scenarios.
6
+ This module provides reusable business case analyzers that work across
7
+ multiple enterprises and projects.
8
+
9
+ Key Features:
10
+ - Real AWS data integration (no hardcoded values)
11
+ - ROI calculation methodologies
12
+ - Risk assessment frameworks
13
+ - Timeline estimation algorithms
14
+ - Multi-enterprise configuration support
15
+
16
+ Author: Enterprise Agile Team
17
+ Version: 0.9.5
18
+ """
19
+
20
+ import os
21
+ import json
22
+ import subprocess
23
+ from typing import Dict, List, Optional, Any, Union
24
+ from datetime import datetime, timedelta
25
+ from dataclasses import dataclass
26
+ from enum import Enum
27
+
28
+ from ..common.rich_utils import console, format_cost
29
+
30
+
31
+ class RiskLevel(Enum):
32
+ """Business risk levels for cost optimization initiatives"""
33
+ LOW = "Low"
34
+ MEDIUM = "Medium"
35
+ HIGH = "High"
36
+ CRITICAL = "Critical"
37
+
38
+
39
+ class BusinessCaseStatus(Enum):
40
+ """Business case lifecycle status"""
41
+ INVESTIGATION = "Investigation Phase"
42
+ ANALYSIS = "Analysis Complete"
43
+ APPROVED = "Approved for Implementation"
44
+ IN_PROGRESS = "Implementation In Progress"
45
+ COMPLETED = "Implementation Complete"
46
+ CANCELLED = "Cancelled"
47
+
48
+
49
+ @dataclass
50
+ class ROIMetrics:
51
+ """ROI calculation results"""
52
+ annual_savings: float
53
+ implementation_cost: float
54
+ roi_percentage: float
55
+ payback_months: float
56
+ net_first_year: float
57
+ risk_adjusted_savings: float
58
+
59
+
60
+ @dataclass
61
+ class BusinessCase:
62
+ """Complete business case analysis"""
63
+ title: str
64
+ scenario_key: str
65
+ status: BusinessCaseStatus
66
+ risk_level: RiskLevel
67
+ roi_metrics: ROIMetrics
68
+ implementation_time: str
69
+ resource_count: int
70
+ affected_accounts: List[str]
71
+ next_steps: List[str]
72
+ data_source: str
73
+ validation_status: str
74
+ timestamp: str
75
+
76
+
77
+ class BusinessCaseAnalyzer:
78
+ """
79
+ Enterprise business case analyzer for cost optimization scenarios.
80
+
81
+ This class provides reusable business case analysis capabilities that
82
+ can be used across multiple enterprises and projects.
83
+ """
84
+
85
+ def __init__(self, profile: Optional[str] = None, enterprise_config: Optional[Dict] = None):
86
+ """
87
+ Initialize business case analyzer.
88
+
89
+ Args:
90
+ profile: AWS profile for data collection
91
+ enterprise_config: Enterprise-specific configuration
92
+ """
93
+ self.profile = profile or os.getenv('AWS_PROFILE')
94
+ self.enterprise_config = enterprise_config or {}
95
+ self.runbooks_cmd = 'runbooks'
96
+
97
+ # Enterprise cost configuration
98
+ self.hourly_rate = self.enterprise_config.get('technical_hourly_rate', 150)
99
+ self.risk_multipliers = self.enterprise_config.get('risk_multipliers', {
100
+ RiskLevel.LOW: 1.0,
101
+ RiskLevel.MEDIUM: 0.85,
102
+ RiskLevel.HIGH: 0.7,
103
+ RiskLevel.CRITICAL: 0.5
104
+ })
105
+
106
+ def execute_runbooks_command(self, command_args: List[str], json_output: bool = True) -> Dict[str, Any]:
107
+ """
108
+ Execute runbooks CLI command for data collection.
109
+
110
+ Args:
111
+ command_args: CLI command arguments
112
+ json_output: Whether to parse JSON output
113
+
114
+ Returns:
115
+ Command results or error information
116
+ """
117
+ cmd = [self.runbooks_cmd] + command_args
118
+
119
+ if self.profile:
120
+ cmd.extend(['--profile', self.profile])
121
+
122
+ if json_output:
123
+ cmd.append('--json')
124
+
125
+ try:
126
+ result = subprocess.run(
127
+ cmd,
128
+ capture_output=True,
129
+ text=True,
130
+ check=True,
131
+ timeout=60 # 1 minute timeout for CLI operations
132
+ )
133
+
134
+ if json_output:
135
+ return json.loads(result.stdout)
136
+ return {'stdout': result.stdout, 'success': True}
137
+
138
+ except subprocess.CalledProcessError as e:
139
+ return {
140
+ 'error': True,
141
+ 'message': f"CLI command failed: {e}",
142
+ 'stderr': e.stderr,
143
+ 'returncode': e.returncode
144
+ }
145
+ except subprocess.TimeoutExpired:
146
+ return {
147
+ 'error': True,
148
+ 'message': "CLI command timeout after 60 seconds",
149
+ 'timeout': True
150
+ }
151
+ except json.JSONDecodeError as e:
152
+ return {
153
+ 'error': True,
154
+ 'message': f"Failed to parse JSON output: {e}",
155
+ 'raw_output': result.stdout
156
+ }
157
+ except Exception as e:
158
+ return {
159
+ 'error': True,
160
+ 'message': f"Unexpected error: {e}"
161
+ }
162
+
163
+ def calculate_roi_metrics(
164
+ self,
165
+ annual_savings: float,
166
+ implementation_hours: float = 8,
167
+ additional_costs: float = 0,
168
+ risk_level: RiskLevel = RiskLevel.MEDIUM
169
+ ) -> ROIMetrics:
170
+ """
171
+ Calculate comprehensive ROI metrics for business case analysis.
172
+
173
+ Args:
174
+ annual_savings: Projected annual cost savings
175
+ implementation_hours: Estimated implementation time in hours
176
+ additional_costs: Additional implementation costs (tools, training, etc.)
177
+ risk_level: Business risk assessment
178
+
179
+ Returns:
180
+ Complete ROI metrics analysis
181
+ """
182
+ # Calculate total implementation cost
183
+ labor_cost = implementation_hours * self.hourly_rate
184
+ total_implementation_cost = labor_cost + additional_costs
185
+
186
+ # Risk-adjusted savings calculation
187
+ risk_multiplier = self.risk_multipliers.get(risk_level, 0.85)
188
+ risk_adjusted_savings = annual_savings * risk_multiplier
189
+
190
+ # ROI calculations
191
+ if total_implementation_cost > 0:
192
+ roi_percentage = ((risk_adjusted_savings - total_implementation_cost) / total_implementation_cost) * 100
193
+ payback_months = (total_implementation_cost / annual_savings) * 12 if annual_savings > 0 else 0
194
+ else:
195
+ roi_percentage = float('inf')
196
+ payback_months = 0
197
+
198
+ net_first_year = risk_adjusted_savings - total_implementation_cost
199
+
200
+ return ROIMetrics(
201
+ annual_savings=annual_savings,
202
+ implementation_cost=total_implementation_cost,
203
+ roi_percentage=roi_percentage,
204
+ payback_months=payback_months,
205
+ net_first_year=net_first_year,
206
+ risk_adjusted_savings=risk_adjusted_savings
207
+ )
208
+
209
+ def analyze_workspaces_scenario(self) -> BusinessCase:
210
+ """
211
+ Analyze WorkSpaces cleanup business case using real AWS data.
212
+
213
+ Returns:
214
+ Complete WorkSpaces business case analysis
215
+ """
216
+ # Get real data from runbooks CLI
217
+ data = self.execute_runbooks_command(['finops', '--scenario', 'workspaces'])
218
+
219
+ if data.get('error'):
220
+ # Return error case for handling
221
+ return BusinessCase(
222
+ title="WorkSpaces Cleanup Initiative",
223
+ scenario_key="workspaces",
224
+ status=BusinessCaseStatus.INVESTIGATION,
225
+ risk_level=RiskLevel.MEDIUM,
226
+ roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
227
+ implementation_time="Pending data collection",
228
+ resource_count=0,
229
+ affected_accounts=[],
230
+ next_steps=["Connect to AWS environment for data collection"],
231
+ data_source=f"Error: {data.get('message', 'Unknown error')}",
232
+ validation_status="Failed - no data available",
233
+ timestamp=datetime.now().isoformat()
234
+ )
235
+
236
+ # Extract real data from CLI response
237
+ unused_workspaces = data.get('unused_workspaces', [])
238
+
239
+ # Calculate actual savings from real data
240
+ annual_savings = sum(
241
+ ws.get('monthly_cost', 0) * 12
242
+ for ws in unused_workspaces
243
+ )
244
+
245
+ # Get unique accounts
246
+ unique_accounts = list(set(
247
+ ws.get('account_id')
248
+ for ws in unused_workspaces
249
+ if ws.get('account_id')
250
+ ))
251
+
252
+ # Estimate implementation time based on resource count
253
+ resource_count = len(unused_workspaces)
254
+ if resource_count <= 10:
255
+ implementation_time = "4-6 hours"
256
+ implementation_hours = 6
257
+ elif resource_count <= 25:
258
+ implementation_time = "6-8 hours"
259
+ implementation_hours = 8
260
+ else:
261
+ implementation_time = "1-2 days"
262
+ implementation_hours = 16
263
+
264
+ # Calculate ROI metrics
265
+ roi_metrics = self.calculate_roi_metrics(
266
+ annual_savings=annual_savings,
267
+ implementation_hours=implementation_hours,
268
+ risk_level=RiskLevel.LOW # WorkSpaces deletion is low risk
269
+ )
270
+
271
+ return BusinessCase(
272
+ title="WorkSpaces Cleanup Initiative",
273
+ scenario_key="workspaces",
274
+ status=BusinessCaseStatus.ANALYSIS,
275
+ risk_level=RiskLevel.LOW,
276
+ roi_metrics=roi_metrics,
277
+ implementation_time=implementation_time,
278
+ resource_count=resource_count,
279
+ affected_accounts=unique_accounts,
280
+ next_steps=[
281
+ "Review unused WorkSpaces list with business stakeholders",
282
+ "Schedule maintenance window for WorkSpaces deletion",
283
+ "Execute cleanup during planned maintenance",
284
+ "Validate cost reduction in next billing cycle"
285
+ ],
286
+ data_source="Real AWS API via runbooks CLI",
287
+ validation_status=data.get('validation_status', 'CLI validated'),
288
+ timestamp=datetime.now().isoformat()
289
+ )
290
+
291
+ def analyze_rds_snapshots_scenario(self) -> BusinessCase:
292
+ """
293
+ Analyze RDS snapshots cleanup business case using real AWS data.
294
+
295
+ Returns:
296
+ Complete RDS snapshots business case analysis
297
+ """
298
+ # Get real data from runbooks CLI
299
+ data = self.execute_runbooks_command(['finops', '--scenario', 'snapshots'])
300
+
301
+ if data.get('error'):
302
+ return BusinessCase(
303
+ title="RDS Storage Optimization",
304
+ scenario_key="rds_snapshots",
305
+ status=BusinessCaseStatus.INVESTIGATION,
306
+ risk_level=RiskLevel.MEDIUM,
307
+ roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
308
+ implementation_time="Pending data collection",
309
+ resource_count=0,
310
+ affected_accounts=[],
311
+ next_steps=["Connect to AWS environment for data collection"],
312
+ data_source=f"Error: {data.get('message', 'Unknown error')}",
313
+ validation_status="Failed - no data available",
314
+ timestamp=datetime.now().isoformat()
315
+ )
316
+
317
+ # Extract real snapshot data
318
+ snapshots = data.get('manual_snapshots', [])
319
+
320
+ # Calculate storage and costs
321
+ total_storage_gb = sum(
322
+ s.get('size_gb', 0)
323
+ for s in snapshots
324
+ )
325
+
326
+ # AWS snapshot storage pricing (current as of 2024)
327
+ cost_per_gb_month = 0.095
328
+
329
+ # Conservative savings estimate (assume 70% can be safely deleted)
330
+ conservative_savings = total_storage_gb * cost_per_gb_month * 12 * 0.7
331
+
332
+ # Get unique accounts
333
+ unique_accounts = list(set(
334
+ s.get('account_id')
335
+ for s in snapshots
336
+ if s.get('account_id')
337
+ ))
338
+
339
+ # Estimate implementation time based on accounts and snapshots
340
+ account_count = len(unique_accounts)
341
+ resource_count = len(snapshots)
342
+ implementation_hours = max(8, account_count * 4) # Minimum 8 hours, 4 hours per account
343
+ implementation_time = f"{implementation_hours//8}-{(implementation_hours//8)+1} days"
344
+
345
+ # Calculate ROI metrics
346
+ roi_metrics = self.calculate_roi_metrics(
347
+ annual_savings=conservative_savings,
348
+ implementation_hours=implementation_hours,
349
+ risk_level=RiskLevel.MEDIUM # RDS snapshots require careful analysis
350
+ )
351
+
352
+ return BusinessCase(
353
+ title="RDS Storage Optimization",
354
+ scenario_key="rds_snapshots",
355
+ status=BusinessCaseStatus.ANALYSIS,
356
+ risk_level=RiskLevel.MEDIUM,
357
+ roi_metrics=roi_metrics,
358
+ implementation_time=implementation_time,
359
+ resource_count=resource_count,
360
+ affected_accounts=unique_accounts,
361
+ next_steps=[
362
+ "Review snapshot retention policies with database teams",
363
+ "Identify snapshots safe for deletion (>30 days old)",
364
+ "Create automated cleanup policies for ongoing management",
365
+ "Implement lifecycle policies for future snapshots"
366
+ ],
367
+ data_source="Real AWS API via runbooks CLI",
368
+ validation_status=data.get('validation_status', 'CLI validated'),
369
+ timestamp=datetime.now().isoformat()
370
+ )
371
+
372
+ def analyze_commvault_scenario(self) -> BusinessCase:
373
+ """
374
+ Analyze Commvault infrastructure investigation case.
375
+
376
+ Returns:
377
+ Complete Commvault investigation business case
378
+ """
379
+ # Get real data from runbooks CLI
380
+ data = self.execute_runbooks_command(['finops', '--scenario', 'commvault'])
381
+
382
+ if data.get('error'):
383
+ return BusinessCase(
384
+ title="Infrastructure Utilization Investigation",
385
+ scenario_key="commvault",
386
+ status=BusinessCaseStatus.INVESTIGATION,
387
+ risk_level=RiskLevel.MEDIUM,
388
+ roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0),
389
+ implementation_time="Investigation phase",
390
+ resource_count=0,
391
+ affected_accounts=[],
392
+ next_steps=["Connect to AWS environment for data collection"],
393
+ data_source=f"Error: {data.get('message', 'Unknown error')}",
394
+ validation_status="Failed - no data available",
395
+ timestamp=datetime.now().isoformat()
396
+ )
397
+
398
+ # This scenario is in investigation phase - no concrete savings yet
399
+ account_id = data.get('account_id', 'Unknown')
400
+
401
+ return BusinessCase(
402
+ title="Infrastructure Utilization Investigation",
403
+ scenario_key="commvault",
404
+ status=BusinessCaseStatus.INVESTIGATION,
405
+ risk_level=RiskLevel.MEDIUM,
406
+ roi_metrics=ROIMetrics(0, 0, 0, 0, 0, 0), # No concrete savings yet
407
+ implementation_time="Assessment: 1-2 days, Implementation: TBD",
408
+ resource_count=0, # Will be determined during investigation
409
+ affected_accounts=[account_id] if account_id != 'Unknown' else [],
410
+ next_steps=[
411
+ "Analyze EC2 utilization metrics for all instances",
412
+ "Determine if instances are actively used by applications",
413
+ "Calculate potential savings IF decommissioning is viable",
414
+ "Develop implementation plan based on utilization analysis"
415
+ ],
416
+ data_source="Investigation framework via runbooks CLI",
417
+ validation_status=data.get('validation_status', 'Investigation phase'),
418
+ timestamp=datetime.now().isoformat()
419
+ )
420
+
421
+ def get_all_business_cases(self) -> Dict[str, BusinessCase]:
422
+ """
423
+ Analyze all available business cases and return comprehensive results.
424
+
425
+ Returns:
426
+ Dictionary of all business case analyses
427
+ """
428
+ cases = {
429
+ 'workspaces': self.analyze_workspaces_scenario(),
430
+ 'rds_snapshots': self.analyze_rds_snapshots_scenario(),
431
+ 'commvault': self.analyze_commvault_scenario()
432
+ }
433
+
434
+ return cases
435
+
436
+ def calculate_portfolio_roi(self, business_cases: Dict[str, BusinessCase]) -> Dict[str, Any]:
437
+ """
438
+ Calculate portfolio-level ROI across all business cases.
439
+
440
+ Args:
441
+ business_cases: Dictionary of business case analyses
442
+
443
+ Returns:
444
+ Portfolio ROI analysis
445
+ """
446
+ total_annual_savings = 0
447
+ total_implementation_cost = 0
448
+ total_risk_adjusted_savings = 0
449
+
450
+ for case in business_cases.values():
451
+ if case.roi_metrics:
452
+ total_annual_savings += case.roi_metrics.annual_savings
453
+ total_implementation_cost += case.roi_metrics.implementation_cost
454
+ total_risk_adjusted_savings += case.roi_metrics.risk_adjusted_savings
455
+
456
+ if total_implementation_cost > 0:
457
+ portfolio_roi = ((total_risk_adjusted_savings - total_implementation_cost) / total_implementation_cost) * 100
458
+ portfolio_payback = (total_implementation_cost / total_annual_savings) * 12 if total_annual_savings > 0 else 0
459
+ else:
460
+ portfolio_roi = 0
461
+ portfolio_payback = 0
462
+
463
+ return {
464
+ 'total_annual_savings': total_annual_savings,
465
+ 'total_implementation_cost': total_implementation_cost,
466
+ 'total_risk_adjusted_savings': total_risk_adjusted_savings,
467
+ 'portfolio_roi_percentage': portfolio_roi,
468
+ 'portfolio_payback_months': portfolio_payback,
469
+ 'net_first_year_value': total_risk_adjusted_savings - total_implementation_cost,
470
+ 'analysis_timestamp': datetime.now().isoformat()
471
+ }
472
+
473
+
474
+ class BusinessCaseFormatter:
475
+ """Format business cases for different audiences"""
476
+
477
+ @staticmethod
478
+ def format_for_business_audience(business_cases: Dict[str, BusinessCase]) -> str:
479
+ """
480
+ Format business cases for manager/financial audience.
481
+
482
+ Args:
483
+ business_cases: Dictionary of business case analyses
484
+
485
+ Returns:
486
+ Business-friendly formatted summary
487
+ """
488
+ output = []
489
+ output.append("Executive Summary - Cost Optimization Business Cases")
490
+ output.append("=" * 60)
491
+
492
+ for case in business_cases.values():
493
+ output.append(f"\n📋 {case.title}")
494
+ output.append(f" Status: {case.status.value}")
495
+
496
+ if case.roi_metrics.annual_savings > 0:
497
+ output.append(f" 💰 Annual Savings: {format_cost(case.roi_metrics.annual_savings)}")
498
+ output.append(f" 📈 ROI: {case.roi_metrics.roi_percentage:.0f}%")
499
+ output.append(f" ⏱️ Payback: {case.roi_metrics.payback_months:.1f} months")
500
+ else:
501
+ output.append(f" 💰 Annual Savings: Under investigation")
502
+
503
+ output.append(f" 🛡️ Risk Level: {case.risk_level.value}")
504
+ output.append(f" ⏰ Implementation Time: {case.implementation_time}")
505
+
506
+ if case.resource_count > 0:
507
+ output.append(f" 📊 Resources: {case.resource_count} items")
508
+
509
+ return "\n".join(output)
510
+
511
+ @staticmethod
512
+ def format_for_technical_audience(business_cases: Dict[str, BusinessCase]) -> str:
513
+ """
514
+ Format business cases for technical audience.
515
+
516
+ Args:
517
+ business_cases: Dictionary of business case analyses
518
+
519
+ Returns:
520
+ Technical implementation details
521
+ """
522
+ output = []
523
+ output.append("Technical Implementation Guide - FinOps Business Cases")
524
+ output.append("=" * 60)
525
+
526
+ for key, case in business_cases.items():
527
+ output.append(f"\n🔧 {case.title}")
528
+ output.append(f" Scenario Key: {case.scenario_key}")
529
+ output.append(f" Data Source: {case.data_source}")
530
+ output.append(f" Validation: {case.validation_status}")
531
+
532
+ if case.affected_accounts:
533
+ output.append(f" Affected Accounts: {', '.join(case.affected_accounts)}")
534
+
535
+ output.append(f" Resource Count: {case.resource_count}")
536
+
537
+ # CLI commands for implementation
538
+ output.append(f"\n CLI Implementation:")
539
+ output.append(f" runbooks finops --scenario {key} --validate")
540
+
541
+ if key == 'workspaces':
542
+ output.append(f" runbooks finops --scenario workspaces --delete --dry-run")
543
+ elif key == 'rds_snapshots':
544
+ output.append(f" runbooks finops --scenario snapshots --cleanup --dry-run")
545
+ elif key == 'commvault':
546
+ output.append(f" runbooks finops --scenario commvault --investigate")
547
+
548
+ output.append(f"\n Next Steps:")
549
+ for step in case.next_steps:
550
+ output.append(f" • {step}")
551
+
552
+ return "\n".join(output)