runbooks 1.0.1__py3-none-any.whl → 1.0.3__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 (35) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cloudops/models.py +20 -14
  3. runbooks/common/aws_pricing_api.py +276 -44
  4. runbooks/common/dry_run_examples.py +587 -0
  5. runbooks/common/dry_run_framework.py +520 -0
  6. runbooks/common/memory_optimization.py +533 -0
  7. runbooks/common/performance_optimization_engine.py +1153 -0
  8. runbooks/common/profile_utils.py +10 -3
  9. runbooks/common/sre_performance_suite.py +574 -0
  10. runbooks/finops/business_case_config.py +314 -0
  11. runbooks/finops/cost_processor.py +19 -4
  12. runbooks/finops/ebs_cost_optimizer.py +1 -1
  13. runbooks/finops/embedded_mcp_validator.py +642 -36
  14. runbooks/finops/executive_export.py +789 -0
  15. runbooks/finops/finops_scenarios.py +34 -27
  16. runbooks/finops/notebook_utils.py +1 -1
  17. runbooks/finops/schemas.py +73 -58
  18. runbooks/finops/single_dashboard.py +20 -4
  19. runbooks/finops/vpc_cleanup_exporter.py +2 -1
  20. runbooks/inventory/models/account.py +5 -3
  21. runbooks/inventory/models/inventory.py +1 -1
  22. runbooks/inventory/models/resource.py +5 -3
  23. runbooks/inventory/organizations_discovery.py +89 -5
  24. runbooks/main.py +182 -61
  25. runbooks/operate/vpc_operations.py +60 -31
  26. runbooks/remediation/workspaces_list.py +2 -2
  27. runbooks/vpc/config.py +17 -8
  28. runbooks/vpc/heatmap_engine.py +425 -53
  29. runbooks/vpc/performance_optimized_analyzer.py +546 -0
  30. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/METADATA +15 -15
  31. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/RECORD +35 -27
  32. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/WHEEL +0 -0
  33. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/entry_points.txt +0 -0
  34. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/licenses/LICENSE +0 -0
  35. {runbooks-1.0.1.dist-info → runbooks-1.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,314 @@
1
+ """
2
+ Dynamic Business Case Configuration - Enterprise Template System
3
+
4
+ Strategic Achievement: Replace hardcoded JIRA references with dynamic business case templates
5
+ - Enterprise naming conventions with configurable business scenarios
6
+ - Dynamic financial targets and achievement tracking
7
+ - Reusable template system for unlimited business case scaling
8
+
9
+ This module provides configurable business case templates following enterprise standards:
10
+ - "Do one thing and do it well": Centralized configuration management
11
+ - "Move Fast, But Not So Fast We Crash": Proven template patterns with validation
12
+ """
13
+
14
+ import os
15
+ from dataclasses import dataclass
16
+ from typing import Dict, List, Optional, Any, Union
17
+ from enum import Enum
18
+
19
+
20
+ class BusinessCaseType(Enum):
21
+ """Standard business case types for enterprise scenarios."""
22
+ COST_OPTIMIZATION = "cost_optimization"
23
+ RESOURCE_CLEANUP = "resource_cleanup"
24
+ COMPLIANCE_FRAMEWORK = "compliance_framework"
25
+ SECURITY_ENHANCEMENT = "security_enhancement"
26
+ AUTOMATION_DEPLOYMENT = "automation_deployment"
27
+
28
+
29
+ @dataclass
30
+ class BusinessScenario:
31
+ """Dynamic business scenario configuration."""
32
+ scenario_id: str
33
+ display_name: str
34
+ business_case_type: BusinessCaseType
35
+ target_savings_min: Optional[float] = None
36
+ target_savings_max: Optional[float] = None
37
+ business_description: str = ""
38
+ technical_focus: str = ""
39
+ risk_level: str = "Medium"
40
+ implementation_status: str = "Analysis"
41
+ cli_command_suffix: str = ""
42
+
43
+ @property
44
+ def scenario_display_id(self) -> str:
45
+ """Generate enterprise-friendly scenario display ID."""
46
+ return f"{self.business_case_type.value.replace('_', '-').title()}-{self.scenario_id}"
47
+
48
+ @property
49
+ def savings_range_display(self) -> str:
50
+ """Generate savings range display for business presentations."""
51
+ if self.target_savings_min and self.target_savings_max:
52
+ if self.target_savings_min == self.target_savings_max:
53
+ return f"${self.target_savings_min:,.0f}/year"
54
+ else:
55
+ return f"${self.target_savings_min:,.0f}-${self.target_savings_max:,.0f}/year"
56
+ elif self.target_savings_min:
57
+ return f"${self.target_savings_min:,.0f}+/year"
58
+ else:
59
+ return "Analysis pending"
60
+
61
+
62
+ class BusinessCaseConfigManager:
63
+ """Enterprise business case configuration manager."""
64
+
65
+ def __init__(self, config_source: Optional[str] = None):
66
+ """
67
+ Initialize business case configuration manager.
68
+
69
+ Args:
70
+ config_source: Optional path to configuration file or environment variable prefix
71
+ """
72
+ self.config_source = config_source or "RUNBOOKS_BUSINESS_CASE"
73
+ self.scenarios = self._load_default_scenarios()
74
+ self._load_environment_overrides()
75
+
76
+ def _load_default_scenarios(self) -> Dict[str, BusinessScenario]:
77
+ """Load default enterprise business scenarios."""
78
+ return {
79
+ "workspaces": BusinessScenario(
80
+ scenario_id="workspaces",
81
+ display_name="WorkSpaces Resource Optimization",
82
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
83
+ target_savings_min=12000,
84
+ target_savings_max=15000,
85
+ business_description="Identify and optimize unused Amazon WorkSpaces for cost efficiency",
86
+ technical_focus="Zero-usage WorkSpaces detection and cost analysis",
87
+ risk_level="Low",
88
+ cli_command_suffix="workspaces"
89
+ ),
90
+ "rds-snapshots": BusinessScenario(
91
+ scenario_id="rds-snapshots",
92
+ display_name="RDS Storage Optimization",
93
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
94
+ target_savings_min=5000,
95
+ target_savings_max=24000,
96
+ business_description="Optimize manual RDS snapshots to reduce storage costs",
97
+ technical_focus="Manual RDS snapshot lifecycle management",
98
+ risk_level="Medium",
99
+ cli_command_suffix="snapshots"
100
+ ),
101
+ "backup-investigation": BusinessScenario(
102
+ scenario_id="backup-investigation",
103
+ display_name="Backup Infrastructure Analysis",
104
+ business_case_type=BusinessCaseType.COMPLIANCE_FRAMEWORK,
105
+ business_description="Investigate backup account utilization and optimization opportunities",
106
+ technical_focus="Backup infrastructure resource utilization analysis",
107
+ risk_level="Medium",
108
+ implementation_status="Framework",
109
+ cli_command_suffix="commvault"
110
+ ),
111
+ "nat-gateway": BusinessScenario(
112
+ scenario_id="nat-gateway",
113
+ display_name="Network Gateway Optimization",
114
+ business_case_type=BusinessCaseType.COST_OPTIMIZATION,
115
+ target_savings_min=8000,
116
+ target_savings_max=12000,
117
+ business_description="Optimize NAT Gateway configurations for cost efficiency",
118
+ technical_focus="NAT Gateway usage analysis and rightsizing",
119
+ cli_command_suffix="nat-gateway"
120
+ ),
121
+ "elastic-ip": BusinessScenario(
122
+ scenario_id="elastic-ip",
123
+ display_name="IP Address Resource Management",
124
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
125
+ target_savings_min=44, # $3.65 * 12 months
126
+ business_description="Optimize unattached Elastic IP addresses",
127
+ technical_focus="Elastic IP attachment analysis and cleanup recommendations",
128
+ risk_level="Low",
129
+ cli_command_suffix="elastic-ip"
130
+ ),
131
+ "ebs-optimization": BusinessScenario(
132
+ scenario_id="ebs-optimization",
133
+ display_name="Storage Volume Optimization",
134
+ business_case_type=BusinessCaseType.COST_OPTIMIZATION,
135
+ business_description="Optimize EBS volume types and utilization for cost efficiency",
136
+ technical_focus="EBS volume rightsizing and type optimization (15-20% potential)",
137
+ cli_command_suffix="ebs"
138
+ ),
139
+ "vpc-cleanup": BusinessScenario(
140
+ scenario_id="vpc-cleanup",
141
+ display_name="Network Infrastructure Cleanup",
142
+ business_case_type=BusinessCaseType.RESOURCE_CLEANUP,
143
+ target_savings_min=5869,
144
+ business_description="Clean up unused VPC resources and infrastructure",
145
+ technical_focus="VPC resource utilization analysis and cleanup recommendations",
146
+ cli_command_suffix="vpc-cleanup"
147
+ )
148
+ }
149
+
150
+ def _load_environment_overrides(self) -> None:
151
+ """Load configuration overrides from environment variables."""
152
+ prefix = f"{self.config_source}_"
153
+
154
+ for scenario_key, scenario in self.scenarios.items():
155
+ # Check for scenario-specific overrides
156
+ env_key = f"{prefix}{scenario_key.upper().replace('-', '_')}"
157
+
158
+ # Override target savings if specified
159
+ min_savings = os.getenv(f"{env_key}_MIN_SAVINGS")
160
+ max_savings = os.getenv(f"{env_key}_MAX_SAVINGS")
161
+
162
+ if min_savings:
163
+ scenario.target_savings_min = float(min_savings)
164
+ if max_savings:
165
+ scenario.target_savings_max = float(max_savings)
166
+
167
+ # Override display name if specified
168
+ display_name = os.getenv(f"{env_key}_DISPLAY_NAME")
169
+ if display_name:
170
+ scenario.display_name = display_name
171
+
172
+ # Override business description if specified
173
+ description = os.getenv(f"{env_key}_DESCRIPTION")
174
+ if description:
175
+ scenario.business_description = description
176
+
177
+ def get_scenario(self, scenario_key: str) -> Optional[BusinessScenario]:
178
+ """Get business scenario by key."""
179
+ return self.scenarios.get(scenario_key)
180
+
181
+ def get_all_scenarios(self) -> Dict[str, BusinessScenario]:
182
+ """Get all configured business scenarios."""
183
+ return self.scenarios
184
+
185
+ def get_scenario_choices(self) -> List[str]:
186
+ """Get list of valid scenario keys for CLI choice options."""
187
+ return list(self.scenarios.keys())
188
+
189
+ def get_scenario_help_text(self) -> str:
190
+ """Generate help text for CLI scenario option."""
191
+ help_parts = []
192
+ for key, scenario in self.scenarios.items():
193
+ savings_display = scenario.savings_range_display
194
+ help_parts.append(f"{key} ({scenario.display_name}: {savings_display})")
195
+ return "Business scenario analysis: " + ", ".join(help_parts)
196
+
197
+ def format_scenario_for_display(self, scenario_key: str,
198
+ achieved_savings: Optional[float] = None,
199
+ achievement_percentage: Optional[float] = None) -> str:
200
+ """Format scenario for display in tables and reports."""
201
+ scenario = self.get_scenario(scenario_key)
202
+ if not scenario:
203
+ return f"Unknown scenario: {scenario_key}"
204
+
205
+ base_info = f"{scenario.display_name} ({scenario.savings_range_display})"
206
+
207
+ if achieved_savings:
208
+ base_info += f" - Achieved: ${achieved_savings:,.0f}"
209
+
210
+ if achievement_percentage:
211
+ base_info += f" ({achievement_percentage:.0f}% of target)"
212
+
213
+ return base_info
214
+
215
+ def create_business_case_summary(self) -> Dict[str, Any]:
216
+ """Create executive summary of all business cases."""
217
+ total_min_savings = sum(
218
+ scenario.target_savings_min or 0
219
+ for scenario in self.scenarios.values()
220
+ )
221
+
222
+ total_max_savings = sum(
223
+ scenario.target_savings_max or 0
224
+ for scenario in self.scenarios.values()
225
+ if scenario.target_savings_max
226
+ )
227
+
228
+ return {
229
+ "total_scenarios": len(self.scenarios),
230
+ "total_potential_min": total_min_savings,
231
+ "total_potential_max": total_max_savings,
232
+ "potential_range": f"${total_min_savings:,.0f}-${total_max_savings:,.0f}",
233
+ "scenarios_by_type": {
234
+ case_type.value: [
235
+ s.display_name for s in self.scenarios.values()
236
+ if s.business_case_type == case_type
237
+ ]
238
+ for case_type in BusinessCaseType
239
+ }
240
+ }
241
+
242
+
243
+ # Global configuration manager instance
244
+ _config_manager = None
245
+
246
+ def get_business_case_config() -> BusinessCaseConfigManager:
247
+ """Get global business case configuration manager."""
248
+ global _config_manager
249
+ if _config_manager is None:
250
+ _config_manager = BusinessCaseConfigManager()
251
+ return _config_manager
252
+
253
+
254
+ def get_scenario_display_name(scenario_key: str) -> str:
255
+ """Get enterprise-friendly display name for scenario."""
256
+ config = get_business_case_config()
257
+ scenario = config.get_scenario(scenario_key)
258
+ return scenario.display_name if scenario else scenario_key.title()
259
+
260
+
261
+ def get_scenario_savings_range(scenario_key: str) -> str:
262
+ """Get savings range display for scenario."""
263
+ config = get_business_case_config()
264
+ scenario = config.get_scenario(scenario_key)
265
+ return scenario.savings_range_display if scenario else "Analysis pending"
266
+
267
+
268
+ def format_business_achievement(scenario_key: str, achieved_savings: float) -> str:
269
+ """Format business achievement for executive reporting."""
270
+ config = get_business_case_config()
271
+ scenario = config.get_scenario(scenario_key)
272
+
273
+ if not scenario:
274
+ return f"{scenario_key}: ${achieved_savings:,.0f} annual savings"
275
+
276
+ # Calculate achievement percentage if target is available
277
+ achievement_text = f"{scenario.display_name}: ${achieved_savings:,.0f} annual savings"
278
+
279
+ if scenario.target_savings_min:
280
+ percentage = (achieved_savings / scenario.target_savings_min) * 100
281
+ achievement_text += f" ({percentage:.0f}% of target)"
282
+
283
+ return achievement_text
284
+
285
+
286
+ # Migration helper functions for existing hardcoded patterns
287
+ def migrate_legacy_scenario_reference(legacy_ref: str) -> str:
288
+ """
289
+ Migrate legacy JIRA references to dynamic business case keys.
290
+
291
+ Args:
292
+ legacy_ref: Legacy reference like "FinOps-24", "finops-23", etc.
293
+
294
+ Returns:
295
+ Dynamic business case key
296
+ """
297
+ legacy_mapping = {
298
+ "finops-24": "workspaces",
299
+ "FinOps-24": "workspaces",
300
+ "finops-23": "rds-snapshots",
301
+ "FinOps-23": "rds-snapshots",
302
+ "finops-25": "backup-investigation",
303
+ "FinOps-25": "backup-investigation",
304
+ "finops-26": "nat-gateway",
305
+ "FinOps-26": "nat-gateway",
306
+ "finops-eip": "elastic-ip",
307
+ "FinOps-EIP": "elastic-ip",
308
+ "finops-ebs": "ebs-optimization",
309
+ "FinOps-EBS": "ebs-optimization",
310
+ "awso-05": "vpc-cleanup",
311
+ "AWSO-05": "vpc-cleanup"
312
+ }
313
+
314
+ return legacy_mapping.get(legacy_ref, legacy_ref.lower())
@@ -684,18 +684,33 @@ def get_cost_data(
684
684
  if amount > 0.001: # Filter out negligible costs
685
685
  costs_by_service[service] = amount
686
686
 
687
- # Calculate period metadata for trend context
687
+ # Calculate period metadata for trend context with enhanced accuracy assessment
688
688
  current_period_days = (end_date - start_date).days
689
689
  previous_period_days = (previous_period_end - previous_period_start).days
690
- is_partial_comparison = abs(current_period_days - previous_period_days) > 5
690
+ days_difference = abs(current_period_days - previous_period_days)
691
+ is_partial_comparison = days_difference > 5
692
+
693
+ # ENHANCED RELIABILITY ASSESSMENT: Consider MCP validation success in trend reliability
694
+ trend_reliability = "high"
695
+ if is_partial_comparison:
696
+ if days_difference > 15:
697
+ trend_reliability = "low"
698
+ elif days_difference > 10:
699
+ trend_reliability = "medium"
700
+ else:
701
+ # Moderate difference - reliability depends on validation accuracy
702
+ trend_reliability = "medium_with_validation_support"
691
703
 
692
704
  # Enhanced period information for trend analysis
693
705
  period_metadata = {
694
706
  "current_days": current_period_days,
695
707
  "previous_days": previous_period_days,
708
+ "days_difference": days_difference,
696
709
  "is_partial_comparison": is_partial_comparison,
697
- "comparison_type": "partial_vs_partial" if is_partial_comparison else "equal_periods",
698
- "trend_reliability": "low" if is_partial_comparison and abs(current_period_days - previous_period_days) > 10 else "high"
710
+ "comparison_type": "equal_day_comparison" if is_partial_month else "standard_month_comparison",
711
+ "trend_reliability": trend_reliability,
712
+ "period_alignment_strategy": "equal_days" if is_partial_month and days_into_month > 1 else "standard_monthly",
713
+ "supports_mcp_validation": True # This data structure supports MCP cross-validation
699
714
  }
700
715
 
701
716
  return {
@@ -165,7 +165,7 @@ class EBSCostOptimizer:
165
165
  region_task = progress.add_task("Analyzing regions...", total=len(regions))
166
166
 
167
167
  for region in regions:
168
- print(f"🔍 Analyzing EBS volumes in {region}")
168
+ console.print(f"🔍 Analyzing EBS volumes in {region}")
169
169
 
170
170
  # Discover volumes in region
171
171
  volumes_data = self._discover_ebs_volumes(region)