runbooks 1.0.3__py3-none-any.whl → 1.1.0__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 +10 -5
- runbooks/__init__.py.backup +134 -0
- runbooks/__init___optimized.py +110 -0
- runbooks/cloudops/base.py +56 -3
- runbooks/cloudops/cost_optimizer.py +496 -42
- runbooks/common/aws_pricing.py +236 -80
- runbooks/common/business_logic.py +485 -0
- runbooks/common/cli_decorators.py +219 -0
- runbooks/common/error_handling.py +424 -0
- runbooks/common/lazy_loader.py +186 -0
- runbooks/common/module_cli_base.py +378 -0
- runbooks/common/performance_monitoring.py +512 -0
- runbooks/common/profile_utils.py +133 -6
- runbooks/enterprise/logging.py +30 -2
- runbooks/enterprise/validation.py +177 -0
- runbooks/finops/README.md +311 -236
- runbooks/finops/aws_client.py +1 -1
- runbooks/finops/business_case_config.py +723 -19
- runbooks/finops/cli.py +136 -0
- runbooks/finops/commvault_ec2_analysis.py +25 -9
- runbooks/finops/config.py +272 -0
- runbooks/finops/dashboard_runner.py +136 -23
- runbooks/finops/ebs_cost_optimizer.py +39 -40
- runbooks/finops/enhanced_trend_visualization.py +7 -2
- runbooks/finops/enterprise_wrappers.py +45 -18
- runbooks/finops/finops_dashboard.py +50 -25
- runbooks/finops/finops_scenarios.py +22 -7
- runbooks/finops/helpers.py +115 -2
- runbooks/finops/multi_dashboard.py +7 -5
- runbooks/finops/optimizer.py +97 -6
- runbooks/finops/scenario_cli_integration.py +247 -0
- runbooks/finops/scenarios.py +12 -1
- runbooks/finops/unlimited_scenarios.py +393 -0
- runbooks/finops/validation_framework.py +19 -7
- runbooks/finops/workspaces_analyzer.py +1 -5
- runbooks/inventory/mcp_inventory_validator.py +2 -1
- runbooks/main.py +132 -94
- runbooks/main_final.py +358 -0
- runbooks/main_minimal.py +84 -0
- runbooks/main_optimized.py +493 -0
- runbooks/main_ultra_minimal.py +47 -0
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/METADATA +1 -1
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/RECORD +47 -31
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/WHEEL +0 -0
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/entry_points.txt +0 -0
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.0.3.dist-info → runbooks-1.1.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ Strategic Achievement: Replace hardcoded JIRA references with dynamic business c
|
|
5
5
|
- Enterprise naming conventions with configurable business scenarios
|
6
6
|
- Dynamic financial targets and achievement tracking
|
7
7
|
- Reusable template system for unlimited business case scaling
|
8
|
+
- Business Scenario Matrix with intelligent parameter defaults (Phase 1 Priority 2)
|
8
9
|
|
9
10
|
This module provides configurable business case templates following enterprise standards:
|
10
11
|
- "Do one thing and do it well": Centralized configuration management
|
@@ -13,7 +14,7 @@ This module provides configurable business case templates following enterprise s
|
|
13
14
|
|
14
15
|
import os
|
15
16
|
from dataclasses import dataclass
|
16
|
-
from typing import Dict, List, Optional, Any, Union
|
17
|
+
from typing import Dict, List, Optional, Any, Union, NamedTuple
|
17
18
|
from enum import Enum
|
18
19
|
|
19
20
|
|
@@ -148,31 +149,125 @@ class BusinessCaseConfigManager:
|
|
148
149
|
}
|
149
150
|
|
150
151
|
def _load_environment_overrides(self) -> None:
|
151
|
-
"""
|
152
|
+
"""
|
153
|
+
Load configuration overrides and discover new scenarios from environment variables.
|
154
|
+
|
155
|
+
Implements Unlimited Scenario Expansion Framework:
|
156
|
+
- Overrides existing scenarios with environment variables
|
157
|
+
- Auto-discovers new scenarios via RUNBOOKS_BUSINESS_CASE_[SCENARIO]_* pattern
|
158
|
+
- Creates BusinessScenario objects for new scenarios dynamically
|
159
|
+
"""
|
152
160
|
prefix = f"{self.config_source}_"
|
153
|
-
|
161
|
+
|
162
|
+
# Phase 1: Override existing scenarios
|
154
163
|
for scenario_key, scenario in self.scenarios.items():
|
155
164
|
# Check for scenario-specific overrides
|
156
165
|
env_key = f"{prefix}{scenario_key.upper().replace('-', '_')}"
|
157
|
-
|
166
|
+
|
158
167
|
# Override target savings if specified
|
159
168
|
min_savings = os.getenv(f"{env_key}_MIN_SAVINGS")
|
160
169
|
max_savings = os.getenv(f"{env_key}_MAX_SAVINGS")
|
161
|
-
|
170
|
+
|
162
171
|
if min_savings:
|
163
172
|
scenario.target_savings_min = float(min_savings)
|
164
173
|
if max_savings:
|
165
174
|
scenario.target_savings_max = float(max_savings)
|
166
|
-
|
175
|
+
|
167
176
|
# Override display name if specified
|
168
177
|
display_name = os.getenv(f"{env_key}_DISPLAY_NAME")
|
169
178
|
if display_name:
|
170
179
|
scenario.display_name = display_name
|
171
|
-
|
172
|
-
# Override business description if specified
|
180
|
+
|
181
|
+
# Override business description if specified
|
173
182
|
description = os.getenv(f"{env_key}_DESCRIPTION")
|
174
183
|
if description:
|
175
184
|
scenario.business_description = description
|
185
|
+
|
186
|
+
# Phase 2: Auto-discovery of new scenarios from environment variables
|
187
|
+
self._discover_new_scenarios_from_environment(prefix)
|
188
|
+
|
189
|
+
def _discover_new_scenarios_from_environment(self, prefix: str) -> None:
|
190
|
+
"""
|
191
|
+
Discover and create new business scenarios from environment variables.
|
192
|
+
|
193
|
+
Environment Variable Pattern:
|
194
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_DISPLAY_NAME=... (required)
|
195
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_MIN_SAVINGS=... (optional)
|
196
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_MAX_SAVINGS=... (optional)
|
197
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_DESCRIPTION=... (optional)
|
198
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_CLI_SUFFIX=... (optional, defaults to scenario key)
|
199
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_TYPE=... (optional, defaults to cost_optimization)
|
200
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO]_RISK_LEVEL=... (optional, defaults to Medium)
|
201
|
+
"""
|
202
|
+
# Scan all environment variables for new scenario patterns
|
203
|
+
discovered_scenarios = set()
|
204
|
+
|
205
|
+
for env_var in os.environ:
|
206
|
+
if env_var.startswith(prefix) and "_DISPLAY_NAME" in env_var:
|
207
|
+
# Extract scenario key from pattern: RUNBOOKS_BUSINESS_CASE_[SCENARIO]_DISPLAY_NAME
|
208
|
+
scenario_part = env_var.replace(prefix, "").replace("_DISPLAY_NAME", "")
|
209
|
+
scenario_key = scenario_part.lower().replace('_', '-')
|
210
|
+
discovered_scenarios.add((scenario_key, scenario_part))
|
211
|
+
|
212
|
+
# Create new scenarios for discovered patterns
|
213
|
+
for scenario_key, env_scenario_key in discovered_scenarios:
|
214
|
+
if scenario_key not in self.scenarios: # Don't override existing scenarios
|
215
|
+
new_scenario = self._create_scenario_from_environment(scenario_key, env_scenario_key, prefix)
|
216
|
+
if new_scenario:
|
217
|
+
self.scenarios[scenario_key] = new_scenario
|
218
|
+
|
219
|
+
def _create_scenario_from_environment(self, scenario_key: str, env_scenario_key: str, prefix: str) -> Optional[BusinessScenario]:
|
220
|
+
"""
|
221
|
+
Create a new BusinessScenario from environment variables.
|
222
|
+
|
223
|
+
Args:
|
224
|
+
scenario_key: The normalized scenario key (kebab-case)
|
225
|
+
env_scenario_key: The environment variable scenario key (UPPER_CASE)
|
226
|
+
prefix: Environment variable prefix
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
New BusinessScenario object or None if required fields missing
|
230
|
+
"""
|
231
|
+
env_base = f"{prefix}{env_scenario_key}"
|
232
|
+
|
233
|
+
# Required field: display name
|
234
|
+
display_name = os.getenv(f"{env_base}_DISPLAY_NAME")
|
235
|
+
if not display_name:
|
236
|
+
return None
|
237
|
+
|
238
|
+
# Optional fields with smart defaults
|
239
|
+
business_type_str = os.getenv(f"{env_base}_TYPE", "cost_optimization")
|
240
|
+
try:
|
241
|
+
business_case_type = BusinessCaseType(business_type_str)
|
242
|
+
except ValueError:
|
243
|
+
business_case_type = BusinessCaseType.COST_OPTIMIZATION
|
244
|
+
|
245
|
+
# Financial targets
|
246
|
+
min_savings_str = os.getenv(f"{env_base}_MIN_SAVINGS")
|
247
|
+
max_savings_str = os.getenv(f"{env_base}_MAX_SAVINGS")
|
248
|
+
|
249
|
+
min_savings = float(min_savings_str) if min_savings_str else None
|
250
|
+
max_savings = float(max_savings_str) if max_savings_str else None
|
251
|
+
|
252
|
+
# Business context
|
253
|
+
description = os.getenv(f"{env_base}_DESCRIPTION", f"Business optimization scenario for {display_name}")
|
254
|
+
technical_focus = os.getenv(f"{env_base}_TECHNICAL_FOCUS", f"{display_name} analysis and optimization")
|
255
|
+
risk_level = os.getenv(f"{env_base}_RISK_LEVEL", "Medium")
|
256
|
+
implementation_status = os.getenv(f"{env_base}_STATUS", "Analysis")
|
257
|
+
cli_suffix = os.getenv(f"{env_base}_CLI_SUFFIX", scenario_key)
|
258
|
+
|
259
|
+
return BusinessScenario(
|
260
|
+
scenario_id=scenario_key,
|
261
|
+
display_name=display_name,
|
262
|
+
business_case_type=business_case_type,
|
263
|
+
target_savings_min=min_savings,
|
264
|
+
target_savings_max=max_savings,
|
265
|
+
business_description=description,
|
266
|
+
technical_focus=technical_focus,
|
267
|
+
risk_level=risk_level,
|
268
|
+
implementation_status=implementation_status,
|
269
|
+
cli_command_suffix=cli_suffix
|
270
|
+
)
|
176
271
|
|
177
272
|
def get_scenario(self, scenario_key: str) -> Optional[BusinessScenario]:
|
178
273
|
"""Get business scenario by key."""
|
@@ -212,19 +307,155 @@ class BusinessCaseConfigManager:
|
|
212
307
|
|
213
308
|
return base_info
|
214
309
|
|
310
|
+
def add_dynamic_scenario(self, scenario: BusinessScenario) -> None:
|
311
|
+
"""
|
312
|
+
Add a new business scenario dynamically.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
scenario: BusinessScenario object to add to the configuration
|
316
|
+
"""
|
317
|
+
self.scenarios[scenario.scenario_id] = scenario
|
318
|
+
|
319
|
+
def create_scenario_from_template(self, scenario_id: str, template_type: str = "aws_resource_optimization") -> BusinessScenario:
|
320
|
+
"""
|
321
|
+
Create a business scenario from predefined templates.
|
322
|
+
|
323
|
+
Templates available:
|
324
|
+
- aws_resource_optimization: Generic AWS resource optimization
|
325
|
+
- lambda_rightsizing: AWS Lambda function rightsizing
|
326
|
+
- s3_storage_optimization: S3 storage class optimization
|
327
|
+
- healthcare_compliance: Healthcare-specific compliance scenarios
|
328
|
+
- finance_cost_governance: Financial industry cost governance
|
329
|
+
- manufacturing_automation: Manufacturing automation scenarios
|
330
|
+
|
331
|
+
Args:
|
332
|
+
scenario_id: Unique identifier for the scenario
|
333
|
+
template_type: Type of template to use
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
Pre-configured BusinessScenario object
|
337
|
+
"""
|
338
|
+
templates = {
|
339
|
+
"aws_resource_optimization": BusinessScenario(
|
340
|
+
scenario_id=scenario_id,
|
341
|
+
display_name=f"{scenario_id.title().replace('-', ' ')} Resource Optimization",
|
342
|
+
business_case_type=BusinessCaseType.COST_OPTIMIZATION,
|
343
|
+
business_description=f"Optimize {scenario_id.replace('-', ' ')} resources for cost efficiency and performance",
|
344
|
+
technical_focus=f"{scenario_id.title().replace('-', ' ')} resource analysis with automated recommendations",
|
345
|
+
risk_level="Medium",
|
346
|
+
implementation_status="Template Ready",
|
347
|
+
cli_command_suffix=scenario_id
|
348
|
+
),
|
349
|
+
"lambda_rightsizing": BusinessScenario(
|
350
|
+
scenario_id=scenario_id,
|
351
|
+
display_name="Lambda Function Rightsizing",
|
352
|
+
business_case_type=BusinessCaseType.COST_OPTIMIZATION,
|
353
|
+
business_description="Optimize Lambda function memory allocation and timeout settings",
|
354
|
+
technical_focus="CloudWatch metrics analysis for memory and duration optimization",
|
355
|
+
risk_level="Low",
|
356
|
+
cli_command_suffix=scenario_id
|
357
|
+
),
|
358
|
+
"s3_storage_optimization": BusinessScenario(
|
359
|
+
scenario_id=scenario_id,
|
360
|
+
display_name="S3 Storage Class Optimization",
|
361
|
+
business_case_type=BusinessCaseType.COST_OPTIMIZATION,
|
362
|
+
business_description="Optimize S3 storage classes based on access patterns",
|
363
|
+
technical_focus="S3 access pattern analysis with intelligent storage class recommendations",
|
364
|
+
risk_level="Low",
|
365
|
+
cli_command_suffix=scenario_id
|
366
|
+
),
|
367
|
+
"healthcare_compliance": BusinessScenario(
|
368
|
+
scenario_id=scenario_id,
|
369
|
+
display_name="Healthcare Compliance Optimization",
|
370
|
+
business_case_type=BusinessCaseType.COMPLIANCE_FRAMEWORK,
|
371
|
+
business_description="HIPAA compliance optimization with cost considerations",
|
372
|
+
technical_focus="Healthcare data security analysis with cost-effective compliance solutions",
|
373
|
+
risk_level="High",
|
374
|
+
cli_command_suffix=scenario_id
|
375
|
+
),
|
376
|
+
"finance_cost_governance": BusinessScenario(
|
377
|
+
scenario_id=scenario_id,
|
378
|
+
display_name="Financial Cost Governance",
|
379
|
+
business_case_type=BusinessCaseType.COMPLIANCE_FRAMEWORK,
|
380
|
+
business_description="Financial industry cost governance and audit readiness",
|
381
|
+
technical_focus="SOX compliance cost optimization with audit trail requirements",
|
382
|
+
risk_level="High",
|
383
|
+
cli_command_suffix=scenario_id
|
384
|
+
),
|
385
|
+
"manufacturing_automation": BusinessScenario(
|
386
|
+
scenario_id=scenario_id,
|
387
|
+
display_name="Manufacturing Process Automation",
|
388
|
+
business_case_type=BusinessCaseType.AUTOMATION_DEPLOYMENT,
|
389
|
+
business_description="Manufacturing workflow automation with cost optimization",
|
390
|
+
technical_focus="IoT and automation pipeline cost optimization analysis",
|
391
|
+
risk_level="Medium",
|
392
|
+
cli_command_suffix=scenario_id
|
393
|
+
)
|
394
|
+
}
|
395
|
+
|
396
|
+
template = templates.get(template_type, templates["aws_resource_optimization"])
|
397
|
+
# Create a copy and update the scenario_id
|
398
|
+
return BusinessScenario(
|
399
|
+
scenario_id=scenario_id,
|
400
|
+
display_name=template.display_name,
|
401
|
+
business_case_type=template.business_case_type,
|
402
|
+
target_savings_min=template.target_savings_min,
|
403
|
+
target_savings_max=template.target_savings_max,
|
404
|
+
business_description=template.business_description,
|
405
|
+
technical_focus=template.technical_focus,
|
406
|
+
risk_level=template.risk_level,
|
407
|
+
implementation_status=template.implementation_status,
|
408
|
+
cli_command_suffix=template.cli_command_suffix
|
409
|
+
)
|
410
|
+
|
411
|
+
def get_template_types(self) -> List[str]:
|
412
|
+
"""Get list of available template types for scenario creation."""
|
413
|
+
return [
|
414
|
+
"aws_resource_optimization",
|
415
|
+
"lambda_rightsizing",
|
416
|
+
"s3_storage_optimization",
|
417
|
+
"healthcare_compliance",
|
418
|
+
"finance_cost_governance",
|
419
|
+
"manufacturing_automation"
|
420
|
+
]
|
421
|
+
|
422
|
+
def calculate_roi_projection(self, scenario_key: str, current_monthly_cost: float,
|
423
|
+
optimization_percentage: float = 0.20) -> Dict[str, float]:
|
424
|
+
"""
|
425
|
+
Calculate ROI projection for a business scenario.
|
426
|
+
|
427
|
+
Args:
|
428
|
+
scenario_key: Business scenario identifier
|
429
|
+
current_monthly_cost: Current monthly cost for the resource
|
430
|
+
optimization_percentage: Expected optimization percentage (default 20%)
|
431
|
+
|
432
|
+
Returns:
|
433
|
+
Dictionary with ROI calculations
|
434
|
+
"""
|
435
|
+
monthly_savings = current_monthly_cost * optimization_percentage
|
436
|
+
annual_savings = monthly_savings * 12
|
437
|
+
|
438
|
+
return {
|
439
|
+
"current_monthly_cost": current_monthly_cost,
|
440
|
+
"monthly_savings": monthly_savings,
|
441
|
+
"annual_savings": annual_savings,
|
442
|
+
"optimization_percentage": optimization_percentage * 100,
|
443
|
+
"roi_12_month": annual_savings # Assuming minimal implementation cost for analysis
|
444
|
+
}
|
445
|
+
|
215
446
|
def create_business_case_summary(self) -> Dict[str, Any]:
|
216
447
|
"""Create executive summary of all business cases."""
|
217
448
|
total_min_savings = sum(
|
218
|
-
scenario.target_savings_min or 0
|
449
|
+
scenario.target_savings_min or 0
|
219
450
|
for scenario in self.scenarios.values()
|
220
451
|
)
|
221
|
-
|
452
|
+
|
222
453
|
total_max_savings = sum(
|
223
|
-
scenario.target_savings_max or 0
|
224
|
-
for scenario in self.scenarios.values()
|
454
|
+
scenario.target_savings_max or 0
|
455
|
+
for scenario in self.scenarios.values()
|
225
456
|
if scenario.target_savings_max
|
226
457
|
)
|
227
|
-
|
458
|
+
|
228
459
|
return {
|
229
460
|
"total_scenarios": len(self.scenarios),
|
230
461
|
"total_potential_min": total_min_savings,
|
@@ -232,10 +463,16 @@ class BusinessCaseConfigManager:
|
|
232
463
|
"potential_range": f"${total_min_savings:,.0f}-${total_max_savings:,.0f}",
|
233
464
|
"scenarios_by_type": {
|
234
465
|
case_type.value: [
|
235
|
-
s.display_name for s in self.scenarios.values()
|
466
|
+
s.display_name for s in self.scenarios.values()
|
236
467
|
if s.business_case_type == case_type
|
237
468
|
]
|
238
469
|
for case_type in BusinessCaseType
|
470
|
+
},
|
471
|
+
"scenario_discovery": {
|
472
|
+
"default_scenarios": 7,
|
473
|
+
"environment_discovered": len(self.scenarios) - 7,
|
474
|
+
"total_active": len(self.scenarios),
|
475
|
+
"unlimited_expansion": True
|
239
476
|
}
|
240
477
|
}
|
241
478
|
|
@@ -283,20 +520,119 @@ def format_business_achievement(scenario_key: str, achieved_savings: float) -> s
|
|
283
520
|
return achievement_text
|
284
521
|
|
285
522
|
|
523
|
+
# Unlimited Scenario Expansion Framework - Helper Functions
|
524
|
+
def create_scenario_from_environment_variables(scenario_id: str) -> Optional[BusinessScenario]:
|
525
|
+
"""
|
526
|
+
Create a business scenario from environment variables.
|
527
|
+
|
528
|
+
Environment Variable Pattern:
|
529
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO_ID]_DISPLAY_NAME=... (required)
|
530
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO_ID]_MIN_SAVINGS=... (optional)
|
531
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO_ID]_MAX_SAVINGS=... (optional)
|
532
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO_ID]_DESCRIPTION=... (optional)
|
533
|
+
RUNBOOKS_BUSINESS_CASE_[SCENARIO_ID]_TYPE=... (optional)
|
534
|
+
|
535
|
+
Args:
|
536
|
+
scenario_id: Scenario identifier (will be normalized to kebab-case)
|
537
|
+
|
538
|
+
Returns:
|
539
|
+
BusinessScenario object or None if required fields missing
|
540
|
+
"""
|
541
|
+
config_manager = get_business_case_config()
|
542
|
+
env_key = scenario_id.upper().replace('-', '_')
|
543
|
+
|
544
|
+
return config_manager._create_scenario_from_environment(
|
545
|
+
scenario_id.lower().replace('_', '-'),
|
546
|
+
env_key,
|
547
|
+
"RUNBOOKS_BUSINESS_CASE_"
|
548
|
+
)
|
549
|
+
|
550
|
+
|
551
|
+
def add_scenario_from_template(scenario_id: str, template_type: str = "aws_resource_optimization") -> BusinessScenario:
|
552
|
+
"""
|
553
|
+
Add a new business scenario from a template.
|
554
|
+
|
555
|
+
Args:
|
556
|
+
scenario_id: Unique identifier for the new scenario
|
557
|
+
template_type: Template type to use (see get_available_templates())
|
558
|
+
|
559
|
+
Returns:
|
560
|
+
Created BusinessScenario object
|
561
|
+
"""
|
562
|
+
config_manager = get_business_case_config()
|
563
|
+
scenario = config_manager.create_scenario_from_template(scenario_id, template_type)
|
564
|
+
config_manager.add_dynamic_scenario(scenario)
|
565
|
+
return scenario
|
566
|
+
|
567
|
+
|
568
|
+
def get_available_templates() -> List[str]:
|
569
|
+
"""Get list of available business scenario templates."""
|
570
|
+
config_manager = get_business_case_config()
|
571
|
+
return config_manager.get_template_types()
|
572
|
+
|
573
|
+
|
574
|
+
def calculate_scenario_roi(scenario_id: str, current_monthly_cost: float,
|
575
|
+
optimization_percentage: float = 0.20) -> Dict[str, float]:
|
576
|
+
"""
|
577
|
+
Calculate ROI projection for any business scenario.
|
578
|
+
|
579
|
+
Args:
|
580
|
+
scenario_id: Business scenario identifier
|
581
|
+
current_monthly_cost: Current monthly AWS spend
|
582
|
+
optimization_percentage: Expected optimization (default 20%)
|
583
|
+
|
584
|
+
Returns:
|
585
|
+
ROI calculation dictionary
|
586
|
+
"""
|
587
|
+
config_manager = get_business_case_config()
|
588
|
+
return config_manager.calculate_roi_projection(scenario_id, current_monthly_cost, optimization_percentage)
|
589
|
+
|
590
|
+
|
591
|
+
def get_unlimited_scenario_choices() -> List[str]:
|
592
|
+
"""
|
593
|
+
Get unlimited scenario choices including environment-discovered scenarios.
|
594
|
+
|
595
|
+
This function enables unlimited scenario expansion without hardcoded lists.
|
596
|
+
"""
|
597
|
+
config_manager = get_business_case_config()
|
598
|
+
return config_manager.get_scenario_choices()
|
599
|
+
|
600
|
+
|
601
|
+
def get_unlimited_scenario_help() -> str:
|
602
|
+
"""
|
603
|
+
Get unlimited scenario help text including dynamically discovered scenarios.
|
604
|
+
|
605
|
+
Returns comprehensive help covering all available scenarios.
|
606
|
+
"""
|
607
|
+
config_manager = get_business_case_config()
|
608
|
+
return config_manager.get_scenario_help_text()
|
609
|
+
|
610
|
+
|
611
|
+
def discover_scenarios_summary() -> Dict[str, Any]:
|
612
|
+
"""
|
613
|
+
Get summary of scenario discovery including environment-based scenarios.
|
614
|
+
|
615
|
+
Returns:
|
616
|
+
Discovery summary with counts and expansion capabilities
|
617
|
+
"""
|
618
|
+
config_manager = get_business_case_config()
|
619
|
+
return config_manager.create_business_case_summary()
|
620
|
+
|
621
|
+
|
286
622
|
# Migration helper functions for existing hardcoded patterns
|
287
623
|
def migrate_legacy_scenario_reference(legacy_ref: str) -> str:
|
288
624
|
"""
|
289
625
|
Migrate legacy JIRA references to dynamic business case keys.
|
290
|
-
|
626
|
+
|
291
627
|
Args:
|
292
628
|
legacy_ref: Legacy reference like "FinOps-24", "finops-23", etc.
|
293
|
-
|
629
|
+
|
294
630
|
Returns:
|
295
631
|
Dynamic business case key
|
296
632
|
"""
|
297
633
|
legacy_mapping = {
|
298
634
|
"finops-24": "workspaces",
|
299
|
-
"FinOps-24": "workspaces",
|
635
|
+
"FinOps-24": "workspaces",
|
300
636
|
"finops-23": "rds-snapshots",
|
301
637
|
"FinOps-23": "rds-snapshots",
|
302
638
|
"finops-25": "backup-investigation",
|
@@ -310,5 +646,373 @@ def migrate_legacy_scenario_reference(legacy_ref: str) -> str:
|
|
310
646
|
"awso-05": "vpc-cleanup",
|
311
647
|
"AWSO-05": "vpc-cleanup"
|
312
648
|
}
|
313
|
-
|
314
|
-
return legacy_mapping.get(legacy_ref, legacy_ref.lower())
|
649
|
+
|
650
|
+
return legacy_mapping.get(legacy_ref, legacy_ref.lower())
|
651
|
+
|
652
|
+
|
653
|
+
# ============================================================================
|
654
|
+
# Business Scenario Matrix - Phase 1 Priority 2 Implementation
|
655
|
+
# ============================================================================
|
656
|
+
|
657
|
+
class ScenarioParameter(NamedTuple):
|
658
|
+
"""Parameter recommendation for business scenarios."""
|
659
|
+
name: str
|
660
|
+
optimal_value: Union[str, int, float]
|
661
|
+
business_justification: str
|
662
|
+
alternative_values: Optional[List[Any]] = None
|
663
|
+
|
664
|
+
|
665
|
+
@dataclass
|
666
|
+
class ScenarioParameterMatrix:
|
667
|
+
"""Intelligent parameter defaults for business scenarios."""
|
668
|
+
timerange_days: Optional[int] = None
|
669
|
+
regional_scope: Optional[str] = None # single, multi, global
|
670
|
+
cost_focus: Optional[str] = None # unblended, amortized, dual-metrics
|
671
|
+
export_priority: Optional[str] = None # csv, json, pdf, markdown
|
672
|
+
validation_level: Optional[str] = None # basic, enhanced, comprehensive
|
673
|
+
business_justification: str = ""
|
674
|
+
|
675
|
+
@property
|
676
|
+
def parameter_recommendations(self) -> Dict[str, ScenarioParameter]:
|
677
|
+
"""Get structured parameter recommendations."""
|
678
|
+
recommendations = {}
|
679
|
+
|
680
|
+
if self.timerange_days:
|
681
|
+
recommendations['timerange'] = ScenarioParameter(
|
682
|
+
name='--time-range',
|
683
|
+
optimal_value=self.timerange_days,
|
684
|
+
business_justification=f"Optimal analysis period: {self.timerange_days} days. {self.business_justification}",
|
685
|
+
alternative_values=[7, 30, 60, 90, 180] if self.timerange_days not in [7, 30, 60, 90, 180] else None
|
686
|
+
)
|
687
|
+
|
688
|
+
if self.regional_scope:
|
689
|
+
scope_mapping = {
|
690
|
+
'single': ['--region', 'us-east-1'],
|
691
|
+
'multi': ['--all-regions', True],
|
692
|
+
'global': ['--global-scope', True]
|
693
|
+
}
|
694
|
+
if self.regional_scope in scope_mapping:
|
695
|
+
param_name, param_value = scope_mapping[self.regional_scope]
|
696
|
+
recommendations['regional_scope'] = ScenarioParameter(
|
697
|
+
name=param_name,
|
698
|
+
optimal_value=param_value,
|
699
|
+
business_justification=f"Regional scope: {self.regional_scope} - {self.business_justification}"
|
700
|
+
)
|
701
|
+
|
702
|
+
if self.cost_focus:
|
703
|
+
recommendations['cost_focus'] = ScenarioParameter(
|
704
|
+
name=f'--{self.cost_focus}',
|
705
|
+
optimal_value=True,
|
706
|
+
business_justification=f"Cost perspective: {self.cost_focus} - {self.business_justification}"
|
707
|
+
)
|
708
|
+
|
709
|
+
if self.export_priority:
|
710
|
+
recommendations['export_format'] = ScenarioParameter(
|
711
|
+
name='--' + self.export_priority.replace('_', '-'),
|
712
|
+
optimal_value=True,
|
713
|
+
business_justification=f"Export format: {self.export_priority} - {self.business_justification}",
|
714
|
+
alternative_values=['csv', 'json', 'pdf', 'markdown'] if self.export_priority else None
|
715
|
+
)
|
716
|
+
|
717
|
+
return recommendations
|
718
|
+
|
719
|
+
|
720
|
+
class BusinessScenarioMatrix:
|
721
|
+
"""
|
722
|
+
Business Scenario Matrix with intelligent parameter defaults.
|
723
|
+
|
724
|
+
Implements Phase 1 Priority 2: Business scenario intelligence with smart
|
725
|
+
parameter recommendations per business case type.
|
726
|
+
|
727
|
+
Enhanced with Phase 2 Priority 1: Unlimited Scenario Expansion Framework
|
728
|
+
supporting dynamic parameter matrix generation for environment-discovered scenarios.
|
729
|
+
"""
|
730
|
+
|
731
|
+
def __init__(self):
|
732
|
+
"""Initialize scenario matrix with Tier 1, 2, 3 configurations and dynamic expansion."""
|
733
|
+
self.scenario_matrix = self._build_scenario_matrix()
|
734
|
+
self._extend_matrix_with_discovered_scenarios()
|
735
|
+
|
736
|
+
def _build_scenario_matrix(self) -> Dict[str, ScenarioParameterMatrix]:
|
737
|
+
"""Build the complete business scenario parameter matrix."""
|
738
|
+
return {
|
739
|
+
# TIER 1 HIGH-VALUE SCENARIOS
|
740
|
+
'workspaces': ScenarioParameterMatrix(
|
741
|
+
timerange_days=90,
|
742
|
+
regional_scope='single',
|
743
|
+
cost_focus='unblended',
|
744
|
+
export_priority='pdf',
|
745
|
+
validation_level='comprehensive',
|
746
|
+
business_justification="WorkSpaces require quarterly analysis for usage pattern detection. Single-region focus optimizes analysis speed. Unblended costs show true resource utilization. PDF format ideal for management review."
|
747
|
+
),
|
748
|
+
|
749
|
+
'nat-gateway': ScenarioParameterMatrix(
|
750
|
+
timerange_days=30,
|
751
|
+
regional_scope='multi',
|
752
|
+
cost_focus='amortized',
|
753
|
+
export_priority='json',
|
754
|
+
validation_level='enhanced',
|
755
|
+
business_justification="NAT Gateways require monthly analysis for traffic optimization. Multi-region analysis essential for comprehensive network cost optimization. Amortized costs account for data transfer pricing. JSON format enables automation integration."
|
756
|
+
),
|
757
|
+
|
758
|
+
'rds-snapshots': ScenarioParameterMatrix(
|
759
|
+
timerange_days=90,
|
760
|
+
regional_scope='multi',
|
761
|
+
cost_focus='dual-metrics',
|
762
|
+
export_priority='csv',
|
763
|
+
validation_level='comprehensive',
|
764
|
+
business_justification="RDS snapshots require quarterly analysis for retention policy optimization. Multi-region scope captures all backup strategies. Dual metrics provide complete cost visibility. CSV format enables spreadsheet analysis."
|
765
|
+
),
|
766
|
+
|
767
|
+
# TIER 2 STRATEGIC SCENARIOS
|
768
|
+
'ebs-optimization': ScenarioParameterMatrix(
|
769
|
+
timerange_days=180,
|
770
|
+
regional_scope='multi',
|
771
|
+
cost_focus='dual-metrics',
|
772
|
+
export_priority='pdf',
|
773
|
+
validation_level='comprehensive',
|
774
|
+
business_justification="EBS optimization requires extended analysis to identify usage patterns. Multi-region scope captures all storage. Dual metrics show both immediate and amortized costs. PDF format suitable for capacity planning presentations."
|
775
|
+
),
|
776
|
+
|
777
|
+
'vpc-cleanup': ScenarioParameterMatrix(
|
778
|
+
timerange_days=30,
|
779
|
+
regional_scope='multi',
|
780
|
+
cost_focus='unblended',
|
781
|
+
export_priority='csv',
|
782
|
+
validation_level='enhanced',
|
783
|
+
business_justification="VPC cleanup requires recent data for active resource identification. Multi-region analysis captures all network resources. Unblended costs show direct infrastructure impact. CSV enables detailed resource tracking."
|
784
|
+
),
|
785
|
+
|
786
|
+
'elastic-ip': ScenarioParameterMatrix(
|
787
|
+
timerange_days=7,
|
788
|
+
regional_scope='multi',
|
789
|
+
cost_focus='unblended',
|
790
|
+
export_priority='json',
|
791
|
+
validation_level='basic',
|
792
|
+
business_justification="Elastic IP analysis requires recent data for attachment status. Multi-region scope captures all IP allocations. Unblended costs show direct charges. JSON format enables automated cleanup workflows."
|
793
|
+
),
|
794
|
+
|
795
|
+
# TIER 3 FRAMEWORK SCENARIOS
|
796
|
+
'backup-investigation': ScenarioParameterMatrix(
|
797
|
+
timerange_days=None, # Framework-based
|
798
|
+
regional_scope='multi',
|
799
|
+
cost_focus='amortized',
|
800
|
+
export_priority='markdown',
|
801
|
+
validation_level='basic',
|
802
|
+
business_justification="Backup investigation uses framework-based timerange analysis. Multi-region scope for comprehensive backup strategy. Amortized costs for long-term planning. Markdown format for documentation and reporting."
|
803
|
+
)
|
804
|
+
}
|
805
|
+
|
806
|
+
def _extend_matrix_with_discovered_scenarios(self) -> None:
|
807
|
+
"""
|
808
|
+
Extend parameter matrix with environment-discovered scenarios.
|
809
|
+
|
810
|
+
Creates intelligent parameter defaults for newly discovered scenarios
|
811
|
+
based on business case type and heuristics.
|
812
|
+
"""
|
813
|
+
config_manager = get_business_case_config()
|
814
|
+
all_scenarios = config_manager.get_all_scenarios()
|
815
|
+
|
816
|
+
for scenario_key, scenario in all_scenarios.items():
|
817
|
+
if scenario_key not in self.scenario_matrix:
|
818
|
+
# Generate intelligent defaults based on business case type
|
819
|
+
parameter_matrix = self._generate_parameter_matrix_for_scenario(scenario)
|
820
|
+
self.scenario_matrix[scenario_key] = parameter_matrix
|
821
|
+
|
822
|
+
def _generate_parameter_matrix_for_scenario(self, scenario: BusinessScenario) -> ScenarioParameterMatrix:
|
823
|
+
"""
|
824
|
+
Generate intelligent parameter matrix for a discovered scenario.
|
825
|
+
|
826
|
+
Args:
|
827
|
+
scenario: BusinessScenario object to generate parameters for
|
828
|
+
|
829
|
+
Returns:
|
830
|
+
ScenarioParameterMatrix with intelligent defaults
|
831
|
+
"""
|
832
|
+
# Default parameter patterns based on business case type
|
833
|
+
type_defaults = {
|
834
|
+
BusinessCaseType.COST_OPTIMIZATION: ScenarioParameterMatrix(
|
835
|
+
timerange_days=30,
|
836
|
+
regional_scope='multi',
|
837
|
+
cost_focus='dual-metrics',
|
838
|
+
export_priority='pdf',
|
839
|
+
validation_level='enhanced',
|
840
|
+
business_justification=f"Cost optimization requires comprehensive analysis with dual cost perspectives for {scenario.display_name}"
|
841
|
+
),
|
842
|
+
BusinessCaseType.RESOURCE_CLEANUP: ScenarioParameterMatrix(
|
843
|
+
timerange_days=7,
|
844
|
+
regional_scope='multi',
|
845
|
+
cost_focus='unblended',
|
846
|
+
export_priority='csv',
|
847
|
+
validation_level='basic',
|
848
|
+
business_justification=f"Resource cleanup requires recent data for active resource identification in {scenario.display_name}"
|
849
|
+
),
|
850
|
+
BusinessCaseType.COMPLIANCE_FRAMEWORK: ScenarioParameterMatrix(
|
851
|
+
timerange_days=90,
|
852
|
+
regional_scope='global',
|
853
|
+
cost_focus='amortized',
|
854
|
+
export_priority='pdf',
|
855
|
+
validation_level='comprehensive',
|
856
|
+
business_justification=f"Compliance frameworks require extended analysis with comprehensive reporting for {scenario.display_name}"
|
857
|
+
),
|
858
|
+
BusinessCaseType.SECURITY_ENHANCEMENT: ScenarioParameterMatrix(
|
859
|
+
timerange_days=30,
|
860
|
+
regional_scope='global',
|
861
|
+
cost_focus='unblended',
|
862
|
+
export_priority='markdown',
|
863
|
+
validation_level='comprehensive',
|
864
|
+
business_justification=f"Security enhancements require thorough analysis with documentation focus for {scenario.display_name}"
|
865
|
+
),
|
866
|
+
BusinessCaseType.AUTOMATION_DEPLOYMENT: ScenarioParameterMatrix(
|
867
|
+
timerange_days=60,
|
868
|
+
regional_scope='multi',
|
869
|
+
cost_focus='amortized',
|
870
|
+
export_priority='json',
|
871
|
+
validation_level='enhanced',
|
872
|
+
business_justification=f"Automation deployment requires extended analysis with machine-readable output for {scenario.display_name}"
|
873
|
+
)
|
874
|
+
}
|
875
|
+
|
876
|
+
# Use type-based defaults or fallback to generic cost optimization pattern
|
877
|
+
default_matrix = type_defaults.get(scenario.business_case_type,
|
878
|
+
type_defaults[BusinessCaseType.COST_OPTIMIZATION])
|
879
|
+
|
880
|
+
# Override with scenario-specific intelligence where available
|
881
|
+
if scenario.risk_level.lower() == 'high':
|
882
|
+
# High risk scenarios need more comprehensive validation
|
883
|
+
default_matrix.validation_level = 'comprehensive'
|
884
|
+
default_matrix.timerange_days = max(default_matrix.timerange_days or 30, 90)
|
885
|
+
|
886
|
+
if scenario.risk_level.lower() == 'low':
|
887
|
+
# Low risk scenarios can use basic validation
|
888
|
+
default_matrix.validation_level = 'basic'
|
889
|
+
default_matrix.timerange_days = min(default_matrix.timerange_days or 30, 14)
|
890
|
+
|
891
|
+
return default_matrix
|
892
|
+
|
893
|
+
def add_custom_scenario_parameters(self, scenario_key: str, parameters: ScenarioParameterMatrix) -> None:
|
894
|
+
"""
|
895
|
+
Add or override parameter matrix for a specific scenario.
|
896
|
+
|
897
|
+
Args:
|
898
|
+
scenario_key: Scenario identifier
|
899
|
+
parameters: Custom parameter matrix configuration
|
900
|
+
"""
|
901
|
+
self.scenario_matrix[scenario_key] = parameters
|
902
|
+
|
903
|
+
def get_scenario_parameters(self, scenario_key: str) -> Optional[ScenarioParameterMatrix]:
|
904
|
+
"""Get parameter matrix for specific scenario."""
|
905
|
+
return self.scenario_matrix.get(scenario_key)
|
906
|
+
|
907
|
+
def get_parameter_recommendations(self, scenario_key: str) -> Dict[str, ScenarioParameter]:
|
908
|
+
"""Get intelligent parameter recommendations for scenario."""
|
909
|
+
scenario_params = self.get_scenario_parameters(scenario_key)
|
910
|
+
if not scenario_params:
|
911
|
+
return {}
|
912
|
+
return scenario_params.parameter_recommendations
|
913
|
+
|
914
|
+
def generate_scenario_help(self, scenario_key: str) -> str:
|
915
|
+
"""Generate scenario-specific help text with parameter recommendations."""
|
916
|
+
scenario_params = self.get_scenario_parameters(scenario_key)
|
917
|
+
if not scenario_params:
|
918
|
+
return f"No parameter recommendations available for scenario: {scenario_key}"
|
919
|
+
|
920
|
+
recommendations = self.get_parameter_recommendations(scenario_key)
|
921
|
+
if not recommendations:
|
922
|
+
return f"Scenario {scenario_key}: Standard parameters apply"
|
923
|
+
|
924
|
+
help_lines = [f"Scenario '{scenario_key}' - Intelligent Parameter Recommendations:"]
|
925
|
+
help_lines.append("")
|
926
|
+
|
927
|
+
for param_key, param in recommendations.items():
|
928
|
+
if isinstance(param.optimal_value, bool) and param.optimal_value:
|
929
|
+
help_lines.append(f" {param.name}")
|
930
|
+
else:
|
931
|
+
help_lines.append(f" {param.name} {param.optimal_value}")
|
932
|
+
help_lines.append(f" → {param.business_justification}")
|
933
|
+
|
934
|
+
if param.alternative_values:
|
935
|
+
alternatives = ', '.join(str(v) for v in param.alternative_values)
|
936
|
+
help_lines.append(f" Alternatives: {alternatives}")
|
937
|
+
help_lines.append("")
|
938
|
+
|
939
|
+
return "\n".join(help_lines)
|
940
|
+
|
941
|
+
def validate_parameters_for_scenario(self, scenario_key: str, provided_params: Dict[str, Any]) -> Dict[str, str]:
|
942
|
+
"""
|
943
|
+
Validate provided parameters against scenario recommendations.
|
944
|
+
|
945
|
+
Returns dict of warnings/suggestions for parameter optimization.
|
946
|
+
"""
|
947
|
+
recommendations = self.get_parameter_recommendations(scenario_key)
|
948
|
+
if not recommendations:
|
949
|
+
return {}
|
950
|
+
|
951
|
+
suggestions = {}
|
952
|
+
|
953
|
+
# Check timerange optimization
|
954
|
+
if 'timerange' in recommendations:
|
955
|
+
optimal_timerange = recommendations['timerange'].optimal_value
|
956
|
+
provided_timerange = provided_params.get('time_range')
|
957
|
+
|
958
|
+
if provided_timerange and provided_timerange != optimal_timerange:
|
959
|
+
suggestions['timerange'] = f"Consider --time-range {optimal_timerange} for optimal {scenario_key} analysis (current: {provided_timerange})"
|
960
|
+
|
961
|
+
# Check export format optimization
|
962
|
+
if 'export_format' in recommendations:
|
963
|
+
optimal_export = recommendations['export_format'].name.replace('--', '')
|
964
|
+
export_formats = provided_params.get('export_formats', [])
|
965
|
+
|
966
|
+
if export_formats and optimal_export not in export_formats:
|
967
|
+
suggestions['export_format'] = f"Consider {optimal_export} export format for {scenario_key} analysis (optimal for business case)"
|
968
|
+
|
969
|
+
# Check cost focus optimization
|
970
|
+
if 'cost_focus' in recommendations:
|
971
|
+
optimal_focus = recommendations['cost_focus'].name.replace('--', '')
|
972
|
+
cost_focus_params = ['unblended', 'amortized', 'dual_metrics']
|
973
|
+
provided_focus = None
|
974
|
+
|
975
|
+
for focus_type in cost_focus_params:
|
976
|
+
if provided_params.get(focus_type):
|
977
|
+
provided_focus = focus_type
|
978
|
+
break
|
979
|
+
|
980
|
+
if not provided_focus:
|
981
|
+
suggestions['cost_focus'] = f"Consider {optimal_focus} cost perspective for {scenario_key} analysis"
|
982
|
+
|
983
|
+
return suggestions
|
984
|
+
|
985
|
+
def get_all_scenario_summaries(self) -> Dict[str, str]:
|
986
|
+
"""Get summary of all scenarios with their optimal parameters."""
|
987
|
+
summaries = {}
|
988
|
+
|
989
|
+
for scenario_key in self.scenario_matrix.keys():
|
990
|
+
params = self.get_scenario_parameters(scenario_key)
|
991
|
+
if params:
|
992
|
+
summary_parts = []
|
993
|
+
|
994
|
+
if params.timerange_days:
|
995
|
+
summary_parts.append(f"{params.timerange_days}d analysis")
|
996
|
+
if params.regional_scope:
|
997
|
+
summary_parts.append(f"{params.regional_scope}-region")
|
998
|
+
if params.cost_focus:
|
999
|
+
summary_parts.append(f"{params.cost_focus} costs")
|
1000
|
+
if params.export_priority:
|
1001
|
+
summary_parts.append(f"{params.export_priority} export")
|
1002
|
+
|
1003
|
+
summaries[scenario_key] = " | ".join(summary_parts)
|
1004
|
+
else:
|
1005
|
+
summaries[scenario_key] = "Standard analysis"
|
1006
|
+
|
1007
|
+
return summaries
|
1008
|
+
|
1009
|
+
|
1010
|
+
# Global scenario matrix instance
|
1011
|
+
_scenario_matrix = None
|
1012
|
+
|
1013
|
+
def get_business_scenario_matrix() -> BusinessScenarioMatrix:
|
1014
|
+
"""Get global business scenario matrix instance."""
|
1015
|
+
global _scenario_matrix
|
1016
|
+
if _scenario_matrix is None:
|
1017
|
+
_scenario_matrix = BusinessScenarioMatrix()
|
1018
|
+
return _scenario_matrix
|