runbooks 0.9.4__py3-none-any.whl → 0.9.5__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 +1 -1
- runbooks/common/rich_utils.py +8 -8
- runbooks/finops/__init__.py +42 -0
- runbooks/finops/business_cases.py +552 -0
- runbooks/finops/commvault_ec2_analysis.py +8 -8
- runbooks/finops/finops_scenarios.py +351 -0
- runbooks/finops/helpers.py +182 -0
- runbooks/finops/scenarios.py +789 -0
- runbooks/remediation/commvault_ec2_analysis.py +8 -8
- runbooks/remediation/rds_snapshot_list.py +16 -16
- runbooks/remediation/workspaces_list.py +7 -7
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/METADATA +1 -1
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/RECORD +17 -15
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.4.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
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.
|
64
|
+
__version__ = "0.9.5"
|
65
65
|
|
66
66
|
# Fallback for legacy importlib.metadata usage during transition
|
67
67
|
try:
|
runbooks/common/rich_utils.py
CHANGED
@@ -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
|
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
|
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]
|
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
|
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 (
|
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]
|
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
|
-
-
|
786
|
+
- FinOps-24: WorkSpaces cleanup ($12,518)
|
787
787
|
- Manager Priority #2: NAT Gateway optimization (95% completion)
|
788
|
-
-
|
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
|
runbooks/finops/__init__.py
CHANGED
@@ -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)
|