runbooks 1.1.4__py3-none-any.whl → 1.1.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.
Files changed (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ Technical Foundation: Enterprise-grade RI analysis across EC2, RDS, ElastiCache,
9
9
 
10
10
  This module provides comprehensive Reserved Instance optimization analysis following proven FinOps patterns:
11
11
  - Multi-service resource analysis (EC2, RDS, ElastiCache, Redshift, OpenSearch)
12
- - Historical usage pattern analysis for RI sizing recommendations
12
+ - Historical usage pattern analysis for RI sizing recommendations
13
13
  - Financial modeling with break-even analysis and ROI calculations
14
14
  - Coverage optimization across different RI terms and payment options
15
15
  - Cross-account RI sharing strategy for enterprise organizations
@@ -25,43 +25,55 @@ Strategic Alignment:
25
25
  import asyncio
26
26
  import logging
27
27
  import time
28
- from datetime import datetime, timedelta
29
- from typing import Any, Dict, List, Optional, Tuple
30
28
  from dataclasses import dataclass
29
+ from datetime import datetime, timedelta
31
30
  from enum import Enum
31
+ from typing import Any, Dict, List, Optional, Tuple
32
32
 
33
33
  import boto3
34
34
  import click
35
35
  from botocore.exceptions import ClientError, NoCredentialsError
36
36
  from pydantic import BaseModel, Field
37
37
 
38
+ from ..common.profile_utils import get_profile_for_operation
38
39
  from ..common.rich_utils import (
39
- console, print_header, print_success, print_error, print_warning, print_info,
40
- create_table, create_progress_bar, format_cost, create_panel, STATUS_INDICATORS
40
+ STATUS_INDICATORS,
41
+ console,
42
+ create_panel,
43
+ create_progress_bar,
44
+ create_table,
45
+ format_cost,
46
+ print_error,
47
+ print_header,
48
+ print_info,
49
+ print_success,
50
+ print_warning,
41
51
  )
42
- from .embedded_mcp_validator import EmbeddedMCPValidator
43
- from ..common.profile_utils import get_profile_for_operation
52
+ from .mcp_validator import EmbeddedMCPValidator
44
53
 
45
54
  logger = logging.getLogger(__name__)
46
55
 
47
56
 
48
57
  class RIService(str, Enum):
49
58
  """AWS services that support Reserved Instances."""
59
+
50
60
  EC2 = "ec2"
51
61
  RDS = "rds"
52
62
  ELASTICACHE = "elasticache"
53
63
  REDSHIFT = "redshift"
54
64
  OPENSEARCH = "opensearch"
55
-
65
+
56
66
 
57
67
  class RITerm(str, Enum):
58
68
  """Reserved Instance term lengths."""
69
+
59
70
  ONE_YEAR = "1yr"
60
71
  THREE_YEAR = "3yr"
61
72
 
62
73
 
63
74
  class RIPaymentOption(str, Enum):
64
75
  """Reserved Instance payment options."""
76
+
65
77
  NO_UPFRONT = "no_upfront"
66
78
  PARTIAL_UPFRONT = "partial_upfront"
67
79
  ALL_UPFRONT = "all_upfront"
@@ -69,27 +81,28 @@ class RIPaymentOption(str, Enum):
69
81
 
70
82
  class ResourceUsagePattern(BaseModel):
71
83
  """Resource usage pattern analysis for RI recommendations."""
84
+
72
85
  resource_id: str
73
86
  resource_type: str # instance_type, db_instance_class, node_type, etc.
74
87
  service: RIService
75
88
  region: str
76
89
  availability_zone: Optional[str] = None
77
-
90
+
78
91
  # Usage statistics over analysis period
79
92
  total_hours_running: float = 0.0
80
93
  average_daily_hours: float = 0.0
81
94
  usage_consistency_score: float = 0.0 # 0-1 consistency score
82
95
  seasonal_variation: float = 0.0 # 0-1 seasonal variation
83
-
96
+
84
97
  # Current pricing
85
98
  on_demand_hourly_rate: float = 0.0
86
99
  current_monthly_cost: float = 0.0
87
100
  current_annual_cost: float = 0.0
88
-
101
+
89
102
  # RI Suitability scoring
90
103
  ri_suitability_score: float = 0.0 # 0-100 RI recommendation score
91
104
  minimum_usage_threshold: float = 0.7 # 70% usage required for RI recommendation
92
-
105
+
93
106
  analysis_period_days: int = 90
94
107
  platform: Optional[str] = None # windows, linux for EC2
95
108
  engine: Optional[str] = None # mysql, postgres for RDS
@@ -98,35 +111,36 @@ class ResourceUsagePattern(BaseModel):
98
111
 
99
112
  class RIRecommendation(BaseModel):
100
113
  """Reserved Instance purchase recommendation."""
114
+
101
115
  resource_type: str
102
116
  service: RIService
103
117
  region: str
104
118
  availability_zone: Optional[str] = None
105
119
  platform: Optional[str] = None
106
-
120
+
107
121
  # Recommendation details
108
122
  recommended_quantity: int = 1
109
123
  ri_term: RITerm = RITerm.ONE_YEAR
110
124
  payment_option: RIPaymentOption = RIPaymentOption.PARTIAL_UPFRONT
111
-
125
+
112
126
  # Financial analysis
113
127
  ri_upfront_cost: float = 0.0
114
128
  ri_hourly_rate: float = 0.0
115
129
  ri_effective_hourly_rate: float = 0.0 # Including upfront amortized
116
130
  on_demand_hourly_rate: float = 0.0
117
-
131
+
118
132
  # Savings analysis
119
133
  break_even_months: float = 0.0
120
134
  first_year_savings: float = 0.0
121
135
  total_term_savings: float = 0.0
122
136
  annual_savings: float = 0.0
123
137
  roi_percentage: float = 0.0
124
-
138
+
125
139
  # Risk assessment
126
140
  utilization_confidence: float = 0.0 # 0-1 confidence in utilization
127
141
  risk_level: str = "low" # low, medium, high
128
142
  flexibility_impact: str = "minimal" # minimal, moderate, significant
129
-
143
+
130
144
  # Supporting resources
131
145
  covered_resources: List[str] = Field(default_factory=list)
132
146
  usage_justification: str = ""
@@ -134,30 +148,31 @@ class RIRecommendation(BaseModel):
134
148
 
135
149
  class RIOptimizerResults(BaseModel):
136
150
  """Complete Reserved Instance optimization analysis results."""
151
+
137
152
  analyzed_services: List[RIService] = Field(default_factory=list)
138
153
  analyzed_regions: List[str] = Field(default_factory=list)
139
-
154
+
140
155
  # Resource analysis summary
141
156
  total_resources_analyzed: int = 0
142
157
  ri_suitable_resources: int = 0
143
158
  current_ri_coverage: float = 0.0 # % of resources already covered by RIs
144
-
159
+
145
160
  # Financial summary
146
161
  total_current_on_demand_cost: float = 0.0
147
162
  total_potential_ri_cost: float = 0.0
148
163
  total_annual_savings: float = 0.0
149
164
  total_upfront_investment: float = 0.0
150
165
  portfolio_roi: float = 0.0
151
-
166
+
152
167
  # Recommendations
153
168
  ri_recommendations: List[RIRecommendation] = Field(default_factory=list)
154
-
169
+
155
170
  # Service breakdown
156
171
  ec2_recommendations: List[RIRecommendation] = Field(default_factory=list)
157
172
  rds_recommendations: List[RIRecommendation] = Field(default_factory=list)
158
173
  elasticache_recommendations: List[RIRecommendation] = Field(default_factory=list)
159
174
  redshift_recommendations: List[RIRecommendation] = Field(default_factory=list)
160
-
175
+
161
176
  execution_time_seconds: float = 0.0
162
177
  mcp_validation_accuracy: float = 0.0
163
178
  analysis_timestamp: datetime = Field(default_factory=datetime.now)
@@ -166,7 +181,7 @@ class RIOptimizerResults(BaseModel):
166
181
  class ReservationOptimizer:
167
182
  """
168
183
  Reserved Instance Optimization Platform - Enterprise FinOps RI Strategy Engine
169
-
184
+
170
185
  Following $132,720+ methodology with proven FinOps patterns targeting $3.2M-$17M annual savings:
171
186
  - Multi-service resource discovery and usage analysis
172
187
  - Historical usage pattern analysis for accurate RI sizing
@@ -176,119 +191,128 @@ class ReservationOptimizer:
176
191
  - Evidence generation for Manager/Financial/CTO executive reporting
177
192
  - Business-focused RI procurement strategy for enterprise budgeting
178
193
  """
179
-
194
+
180
195
  def __init__(self, profile_name: Optional[str] = None, regions: Optional[List[str]] = None):
181
196
  """Initialize RI optimizer with enterprise profile support."""
182
197
  self.profile_name = profile_name
183
- self.regions = regions or ['us-east-1', 'us-west-2', 'eu-west-1']
184
-
198
+ self.regions = regions or ["us-east-1", "us-west-2", "eu-west-1"]
199
+
185
200
  # Initialize AWS session with profile priority system
186
- self.session = boto3.Session(
187
- profile_name=get_profile_for_operation("operational", profile_name)
188
- )
189
-
201
+ self.session = boto3.Session(profile_name=get_profile_for_operation("operational", profile_name))
202
+
190
203
  # RI analysis parameters
191
204
  self.analysis_period_days = 90 # 3 months usage analysis
192
205
  self.minimum_usage_threshold = 0.75 # 75% usage required for RI recommendation
193
206
  self.break_even_target_months = 10 # Target break-even within 10 months
194
-
207
+
195
208
  # Service-specific pricing configurations (approximate 2024 rates)
196
209
  self.service_pricing = {
197
210
  RIService.EC2: {
198
- 'm5.large': {'on_demand': 0.096, 'ri_1yr_partial': {'upfront': 550, 'hourly': 0.055}},
199
- 'm5.xlarge': {'on_demand': 0.192, 'ri_1yr_partial': {'upfront': 1100, 'hourly': 0.11}},
200
- 'm5.2xlarge': {'on_demand': 0.384, 'ri_1yr_partial': {'upfront': 2200, 'hourly': 0.22}},
201
- 'c5.large': {'on_demand': 0.085, 'ri_1yr_partial': {'upfront': 500, 'hourly': 0.048}},
202
- 'c5.xlarge': {'on_demand': 0.17, 'ri_1yr_partial': {'upfront': 1000, 'hourly': 0.096}},
203
- 'r5.large': {'on_demand': 0.126, 'ri_1yr_partial': {'upfront': 720, 'hourly': 0.072}},
204
- 'r5.xlarge': {'on_demand': 0.252, 'ri_1yr_partial': {'upfront': 1440, 'hourly': 0.144}},
211
+ "m5.large": {"on_demand": 0.096, "ri_1yr_partial": {"upfront": 550, "hourly": 0.055}},
212
+ "m5.xlarge": {"on_demand": 0.192, "ri_1yr_partial": {"upfront": 1100, "hourly": 0.11}},
213
+ "m5.2xlarge": {"on_demand": 0.384, "ri_1yr_partial": {"upfront": 2200, "hourly": 0.22}},
214
+ "c5.large": {"on_demand": 0.085, "ri_1yr_partial": {"upfront": 500, "hourly": 0.048}},
215
+ "c5.xlarge": {"on_demand": 0.17, "ri_1yr_partial": {"upfront": 1000, "hourly": 0.096}},
216
+ "r5.large": {"on_demand": 0.126, "ri_1yr_partial": {"upfront": 720, "hourly": 0.072}},
217
+ "r5.xlarge": {"on_demand": 0.252, "ri_1yr_partial": {"upfront": 1440, "hourly": 0.144}},
205
218
  },
206
219
  RIService.RDS: {
207
- 'db.t3.medium': {'on_demand': 0.068, 'ri_1yr_partial': {'upfront': 390, 'hourly': 0.038}},
208
- 'db.m5.large': {'on_demand': 0.192, 'ri_1yr_partial': {'upfront': 1100, 'hourly': 0.11}},
209
- 'db.m5.xlarge': {'on_demand': 0.384, 'ri_1yr_partial': {'upfront': 2200, 'hourly': 0.22}},
210
- 'db.r5.large': {'on_demand': 0.24, 'ri_1yr_partial': {'upfront': 1370, 'hourly': 0.135}},
211
- 'db.r5.xlarge': {'on_demand': 0.48, 'ri_1yr_partial': {'upfront': 2740, 'hourly': 0.27}},
220
+ "db.t3.medium": {"on_demand": 0.068, "ri_1yr_partial": {"upfront": 390, "hourly": 0.038}},
221
+ "db.m5.large": {"on_demand": 0.192, "ri_1yr_partial": {"upfront": 1100, "hourly": 0.11}},
222
+ "db.m5.xlarge": {"on_demand": 0.384, "ri_1yr_partial": {"upfront": 2200, "hourly": 0.22}},
223
+ "db.r5.large": {"on_demand": 0.24, "ri_1yr_partial": {"upfront": 1370, "hourly": 0.135}},
224
+ "db.r5.xlarge": {"on_demand": 0.48, "ri_1yr_partial": {"upfront": 2740, "hourly": 0.27}},
212
225
  },
213
226
  RIService.ELASTICACHE: {
214
- 'cache.m5.large': {'on_demand': 0.136, 'ri_1yr_partial': {'upfront': 780, 'hourly': 0.077}},
215
- 'cache.r5.large': {'on_demand': 0.188, 'ri_1yr_partial': {'upfront': 1075, 'hourly': 0.106}},
216
- }
227
+ "cache.m5.large": {"on_demand": 0.136, "ri_1yr_partial": {"upfront": 780, "hourly": 0.077}},
228
+ "cache.r5.large": {"on_demand": 0.188, "ri_1yr_partial": {"upfront": 1075, "hourly": 0.106}},
229
+ },
217
230
  }
218
-
219
- async def analyze_reservation_opportunities(self, services: List[RIService] = None, dry_run: bool = True) -> RIOptimizerResults:
231
+
232
+ async def analyze_reservation_opportunities(
233
+ self, services: List[RIService] = None, dry_run: bool = True
234
+ ) -> RIOptimizerResults:
220
235
  """
221
236
  Comprehensive Reserved Instance optimization analysis across AWS services.
222
-
237
+
223
238
  Args:
224
239
  services: List of AWS services to analyze (None = all supported services)
225
240
  dry_run: Safety mode - READ-ONLY analysis only
226
-
241
+
227
242
  Returns:
228
243
  Complete analysis results with RI recommendations and financial modeling
229
244
  """
230
245
  print_header("Reserved Instance Optimization Platform", "Enterprise Multi-Service RI Strategy Engine v1.0")
231
-
246
+
232
247
  if not dry_run:
233
248
  print_warning("⚠️ Dry-run disabled - This optimizer is READ-ONLY analysis only")
234
249
  print_info("All RI procurement decisions require manual execution after review")
235
-
250
+
236
251
  analysis_start_time = time.time()
237
252
  services_to_analyze = services or [RIService.EC2, RIService.RDS, RIService.ELASTICACHE]
238
-
253
+
239
254
  try:
240
255
  with create_progress_bar() as progress:
241
256
  # Step 1: Multi-service resource discovery
242
- discovery_task = progress.add_task("Discovering resources across services...",
243
- total=len(services_to_analyze) * len(self.regions))
244
- usage_patterns = await self._discover_resources_multi_service(services_to_analyze, progress, discovery_task)
245
-
257
+ discovery_task = progress.add_task(
258
+ "Discovering resources across services...", total=len(services_to_analyze) * len(self.regions)
259
+ )
260
+ usage_patterns = await self._discover_resources_multi_service(
261
+ services_to_analyze, progress, discovery_task
262
+ )
263
+
246
264
  if not usage_patterns:
247
265
  print_warning("No suitable resources found for RI analysis")
248
266
  return RIOptimizerResults(
249
267
  analyzed_services=services_to_analyze,
250
268
  analyzed_regions=self.regions,
251
269
  analysis_timestamp=datetime.now(),
252
- execution_time_seconds=time.time() - analysis_start_time
270
+ execution_time_seconds=time.time() - analysis_start_time,
253
271
  )
254
-
272
+
255
273
  # Step 2: Usage pattern analysis
256
274
  usage_task = progress.add_task("Analyzing usage patterns...", total=len(usage_patterns))
257
275
  analyzed_patterns = await self._analyze_usage_patterns(usage_patterns, progress, usage_task)
258
-
276
+
259
277
  # Step 3: RI suitability assessment
260
278
  suitability_task = progress.add_task("Assessing RI suitability...", total=len(analyzed_patterns))
261
279
  suitable_resources = await self._assess_ri_suitability(analyzed_patterns, progress, suitability_task)
262
-
280
+
263
281
  # Step 4: Financial modeling and recommendations
264
282
  modeling_task = progress.add_task("Financial modeling...", total=len(suitable_resources))
265
283
  recommendations = await self._generate_ri_recommendations(suitable_resources, progress, modeling_task)
266
-
284
+
267
285
  # Step 5: Portfolio optimization
268
286
  optimization_task = progress.add_task("Optimizing RI portfolio...", total=1)
269
- optimized_recommendations = await self._optimize_ri_portfolio(recommendations, progress, optimization_task)
270
-
287
+ optimized_recommendations = await self._optimize_ri_portfolio(
288
+ recommendations, progress, optimization_task
289
+ )
290
+
271
291
  # Step 6: MCP validation
272
292
  validation_task = progress.add_task("MCP validation...", total=1)
273
293
  mcp_accuracy = await self._validate_with_mcp(optimized_recommendations, progress, validation_task)
274
-
294
+
275
295
  # Compile comprehensive results
276
- results = self._compile_results(usage_patterns, optimized_recommendations, mcp_accuracy, analysis_start_time, services_to_analyze)
277
-
296
+ results = self._compile_results(
297
+ usage_patterns, optimized_recommendations, mcp_accuracy, analysis_start_time, services_to_analyze
298
+ )
299
+
278
300
  # Display executive summary
279
301
  self._display_executive_summary(results)
280
-
302
+
281
303
  return results
282
-
304
+
283
305
  except Exception as e:
284
306
  print_error(f"Reserved Instance optimization analysis failed: {e}")
285
307
  logger.error(f"RI analysis error: {e}", exc_info=True)
286
308
  raise
287
-
288
- async def _discover_resources_multi_service(self, services: List[RIService], progress, task_id) -> List[ResourceUsagePattern]:
309
+
310
+ async def _discover_resources_multi_service(
311
+ self, services: List[RIService], progress, task_id
312
+ ) -> List[ResourceUsagePattern]:
289
313
  """Discover resources across multiple AWS services for RI analysis."""
290
314
  usage_patterns = []
291
-
315
+
292
316
  for service in services:
293
317
  for region in self.regions:
294
318
  try:
@@ -304,272 +328,294 @@ class ReservationOptimizer:
304
328
  elif service == RIService.REDSHIFT:
305
329
  patterns = await self._discover_redshift_resources(region)
306
330
  usage_patterns.extend(patterns)
307
-
308
- print_info(f"Service {service.value} in {region}: {len([p for p in usage_patterns if p.region == region and p.service == service])} resources discovered")
309
-
331
+
332
+ print_info(
333
+ f"Service {service.value} in {region}: {len([p for p in usage_patterns if p.region == region and p.service == service])} resources discovered"
334
+ )
335
+
310
336
  except ClientError as e:
311
337
  print_warning(f"Service {service.value} in {region}: Access denied - {e.response['Error']['Code']}")
312
338
  except Exception as e:
313
339
  print_error(f"Service {service.value} in {region}: Discovery error - {str(e)}")
314
-
340
+
315
341
  progress.advance(task_id)
316
-
342
+
317
343
  return usage_patterns
318
-
344
+
319
345
  async def _discover_ec2_resources(self, region: str) -> List[ResourceUsagePattern]:
320
346
  """Discover EC2 instances for RI analysis."""
321
347
  patterns = []
322
-
348
+
323
349
  try:
324
- ec2_client = self.session.client('ec2', region_name=region)
325
-
326
- paginator = ec2_client.get_paginator('describe_instances')
350
+ ec2_client = self.session.client("ec2", region_name=region)
351
+
352
+ paginator = ec2_client.get_paginator("describe_instances")
327
353
  page_iterator = paginator.paginate()
328
-
354
+
329
355
  for page in page_iterator:
330
- for reservation in page.get('Reservations', []):
331
- for instance in reservation.get('Instances', []):
356
+ for reservation in page.get("Reservations", []):
357
+ for instance in reservation.get("Instances", []):
332
358
  # Skip terminated instances
333
- if instance.get('State', {}).get('Name') == 'terminated':
359
+ if instance.get("State", {}).get("Name") == "terminated":
334
360
  continue
335
-
361
+
336
362
  # Extract tags
337
- tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
338
-
363
+ tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
364
+
339
365
  # Get pricing information
340
- instance_type = instance['InstanceType']
366
+ instance_type = instance["InstanceType"]
341
367
  pricing = self.service_pricing.get(RIService.EC2, {}).get(instance_type, {})
342
- on_demand_rate = pricing.get('on_demand', 0.1) # Default fallback
343
-
344
- patterns.append(ResourceUsagePattern(
345
- resource_id=instance['InstanceId'],
346
- resource_type=instance_type,
347
- service=RIService.EC2,
348
- region=region,
349
- availability_zone=instance['Placement']['AvailabilityZone'],
350
- on_demand_hourly_rate=on_demand_rate,
351
- platform=instance.get('Platform', 'linux'),
352
- tags=tags,
353
- analysis_period_days=self.analysis_period_days
354
- ))
355
-
368
+ on_demand_rate = pricing.get("on_demand", 0.1) # Default fallback
369
+
370
+ patterns.append(
371
+ ResourceUsagePattern(
372
+ resource_id=instance["InstanceId"],
373
+ resource_type=instance_type,
374
+ service=RIService.EC2,
375
+ region=region,
376
+ availability_zone=instance["Placement"]["AvailabilityZone"],
377
+ on_demand_hourly_rate=on_demand_rate,
378
+ platform=instance.get("Platform", "linux"),
379
+ tags=tags,
380
+ analysis_period_days=self.analysis_period_days,
381
+ )
382
+ )
383
+
356
384
  except Exception as e:
357
385
  logger.warning(f"EC2 discovery failed in {region}: {e}")
358
-
386
+
359
387
  return patterns
360
-
388
+
361
389
  async def _discover_rds_resources(self, region: str) -> List[ResourceUsagePattern]:
362
390
  """Discover RDS instances for RI analysis."""
363
391
  patterns = []
364
-
392
+
365
393
  try:
366
- rds_client = self.session.client('rds', region_name=region)
367
-
368
- paginator = rds_client.get_paginator('describe_db_instances')
394
+ rds_client = self.session.client("rds", region_name=region)
395
+
396
+ paginator = rds_client.get_paginator("describe_db_instances")
369
397
  page_iterator = paginator.paginate()
370
-
398
+
371
399
  for page in page_iterator:
372
- for db_instance in page.get('DBInstances', []):
400
+ for db_instance in page.get("DBInstances", []):
373
401
  # Skip instances that are not running/available
374
- if db_instance.get('DBInstanceStatus') not in ['available', 'storage-optimization']:
402
+ if db_instance.get("DBInstanceStatus") not in ["available", "storage-optimization"]:
375
403
  continue
376
-
404
+
377
405
  # Get pricing information
378
- instance_class = db_instance['DBInstanceClass']
406
+ instance_class = db_instance["DBInstanceClass"]
379
407
  pricing = self.service_pricing.get(RIService.RDS, {}).get(instance_class, {})
380
- on_demand_rate = pricing.get('on_demand', 0.2) # Default fallback
381
-
382
- patterns.append(ResourceUsagePattern(
383
- resource_id=db_instance['DBInstanceIdentifier'],
384
- resource_type=instance_class,
385
- service=RIService.RDS,
386
- region=region,
387
- availability_zone=db_instance.get('AvailabilityZone'),
388
- on_demand_hourly_rate=on_demand_rate,
389
- engine=db_instance.get('Engine'),
390
- analysis_period_days=self.analysis_period_days
391
- ))
392
-
408
+ on_demand_rate = pricing.get("on_demand", 0.2) # Default fallback
409
+
410
+ patterns.append(
411
+ ResourceUsagePattern(
412
+ resource_id=db_instance["DBInstanceIdentifier"],
413
+ resource_type=instance_class,
414
+ service=RIService.RDS,
415
+ region=region,
416
+ availability_zone=db_instance.get("AvailabilityZone"),
417
+ on_demand_hourly_rate=on_demand_rate,
418
+ engine=db_instance.get("Engine"),
419
+ analysis_period_days=self.analysis_period_days,
420
+ )
421
+ )
422
+
393
423
  except Exception as e:
394
424
  logger.warning(f"RDS discovery failed in {region}: {e}")
395
-
425
+
396
426
  return patterns
397
-
427
+
398
428
  async def _discover_elasticache_resources(self, region: str) -> List[ResourceUsagePattern]:
399
429
  """Discover ElastiCache clusters for RI analysis."""
400
430
  patterns = []
401
-
431
+
402
432
  try:
403
- elasticache_client = self.session.client('elasticache', region_name=region)
404
-
433
+ elasticache_client = self.session.client("elasticache", region_name=region)
434
+
405
435
  # Discover Redis clusters
406
436
  response = elasticache_client.describe_cache_clusters()
407
- for cluster in response.get('CacheClusters', []):
408
- if cluster.get('CacheClusterStatus') != 'available':
437
+ for cluster in response.get("CacheClusters", []):
438
+ if cluster.get("CacheClusterStatus") != "available":
409
439
  continue
410
-
411
- node_type = cluster.get('CacheNodeType')
440
+
441
+ node_type = cluster.get("CacheNodeType")
412
442
  pricing = self.service_pricing.get(RIService.ELASTICACHE, {}).get(node_type, {})
413
- on_demand_rate = pricing.get('on_demand', 0.15) # Default fallback
414
-
415
- patterns.append(ResourceUsagePattern(
416
- resource_id=cluster['CacheClusterId'],
417
- resource_type=node_type,
418
- service=RIService.ELASTICACHE,
419
- region=region,
420
- on_demand_hourly_rate=on_demand_rate,
421
- engine=cluster.get('Engine'),
422
- analysis_period_days=self.analysis_period_days
423
- ))
424
-
443
+ on_demand_rate = pricing.get("on_demand", 0.15) # Default fallback
444
+
445
+ patterns.append(
446
+ ResourceUsagePattern(
447
+ resource_id=cluster["CacheClusterId"],
448
+ resource_type=node_type,
449
+ service=RIService.ELASTICACHE,
450
+ region=region,
451
+ on_demand_hourly_rate=on_demand_rate,
452
+ engine=cluster.get("Engine"),
453
+ analysis_period_days=self.analysis_period_days,
454
+ )
455
+ )
456
+
425
457
  except Exception as e:
426
458
  logger.warning(f"ElastiCache discovery failed in {region}: {e}")
427
-
459
+
428
460
  return patterns
429
-
461
+
430
462
  async def _discover_redshift_resources(self, region: str) -> List[ResourceUsagePattern]:
431
463
  """Discover Redshift clusters for RI analysis."""
432
464
  patterns = []
433
-
465
+
434
466
  try:
435
- redshift_client = self.session.client('redshift', region_name=region)
436
-
467
+ redshift_client = self.session.client("redshift", region_name=region)
468
+
437
469
  response = redshift_client.describe_clusters()
438
- for cluster in response.get('Clusters', []):
439
- if cluster.get('ClusterStatus') != 'available':
470
+ for cluster in response.get("Clusters", []):
471
+ if cluster.get("ClusterStatus") != "available":
440
472
  continue
441
-
442
- node_type = cluster.get('NodeType')
473
+
474
+ node_type = cluster.get("NodeType")
443
475
  # Redshift pricing is more complex, using simplified estimate
444
476
  on_demand_rate = 0.25 # Approximate rate per node per hour
445
-
446
- patterns.append(ResourceUsagePattern(
447
- resource_id=cluster['ClusterIdentifier'],
448
- resource_type=node_type,
449
- service=RIService.REDSHIFT,
450
- region=region,
451
- on_demand_hourly_rate=on_demand_rate,
452
- analysis_period_days=self.analysis_period_days
453
- ))
454
-
477
+
478
+ patterns.append(
479
+ ResourceUsagePattern(
480
+ resource_id=cluster["ClusterIdentifier"],
481
+ resource_type=node_type,
482
+ service=RIService.REDSHIFT,
483
+ region=region,
484
+ on_demand_hourly_rate=on_demand_rate,
485
+ analysis_period_days=self.analysis_period_days,
486
+ )
487
+ )
488
+
455
489
  except Exception as e:
456
490
  logger.warning(f"Redshift discovery failed in {region}: {e}")
457
-
491
+
458
492
  return patterns
459
-
460
- async def _analyze_usage_patterns(self, patterns: List[ResourceUsagePattern], progress, task_id) -> List[ResourceUsagePattern]:
493
+
494
+ async def _analyze_usage_patterns(
495
+ self, patterns: List[ResourceUsagePattern], progress, task_id
496
+ ) -> List[ResourceUsagePattern]:
461
497
  """Analyze resource usage patterns via CloudWatch metrics."""
462
498
  analyzed_patterns = []
463
499
  end_time = datetime.utcnow()
464
500
  start_time = end_time - timedelta(days=self.analysis_period_days)
465
-
501
+
466
502
  for pattern in patterns:
467
503
  try:
468
- cloudwatch = self.session.client('cloudwatch', region_name=pattern.region)
469
-
504
+ cloudwatch = self.session.client("cloudwatch", region_name=pattern.region)
505
+
470
506
  # Get utilization metrics based on service type
471
507
  if pattern.service == RIService.EC2:
472
- cpu_utilization = await self._get_ec2_utilization(cloudwatch, pattern.resource_id, start_time, end_time)
508
+ cpu_utilization = await self._get_ec2_utilization(
509
+ cloudwatch, pattern.resource_id, start_time, end_time
510
+ )
473
511
  usage_hours = self._calculate_usage_hours(cpu_utilization, self.analysis_period_days)
474
512
  elif pattern.service == RIService.RDS:
475
- cpu_utilization = await self._get_rds_utilization(cloudwatch, pattern.resource_id, start_time, end_time)
513
+ cpu_utilization = await self._get_rds_utilization(
514
+ cloudwatch, pattern.resource_id, start_time, end_time
515
+ )
476
516
  usage_hours = self._calculate_usage_hours(cpu_utilization, self.analysis_period_days)
477
517
  else:
478
518
  # For other services, assume consistent usage pattern
479
519
  usage_hours = self.analysis_period_days * 24 * 0.8 # 80% uptime assumption
480
-
520
+
481
521
  # Calculate usage statistics
482
522
  total_possible_hours = self.analysis_period_days * 24
483
523
  usage_percentage = usage_hours / total_possible_hours if total_possible_hours > 0 else 0
484
-
524
+
485
525
  # Update pattern with usage analysis
486
526
  pattern.total_hours_running = usage_hours
487
527
  pattern.average_daily_hours = usage_hours / self.analysis_period_days
488
528
  pattern.usage_consistency_score = min(1.0, usage_percentage)
489
- pattern.current_monthly_cost = pattern.on_demand_hourly_rate * (usage_hours / self.analysis_period_days) * 30.44 * 24
529
+ pattern.current_monthly_cost = (
530
+ pattern.on_demand_hourly_rate * (usage_hours / self.analysis_period_days) * 30.44 * 24
531
+ )
490
532
  pattern.current_annual_cost = pattern.current_monthly_cost * 12
491
-
533
+
492
534
  # Calculate RI suitability score
493
535
  pattern.ri_suitability_score = self._calculate_ri_suitability_score(pattern)
494
-
536
+
495
537
  analyzed_patterns.append(pattern)
496
-
538
+
497
539
  except Exception as e:
498
540
  print_warning(f"Usage analysis failed for {pattern.resource_id}: {str(e)}")
499
541
  # Keep pattern with default values
500
542
  pattern.usage_consistency_score = 0.5
501
543
  pattern.ri_suitability_score = 40.0
502
544
  analyzed_patterns.append(pattern)
503
-
545
+
504
546
  progress.advance(task_id)
505
-
547
+
506
548
  return analyzed_patterns
507
-
508
- async def _get_ec2_utilization(self, cloudwatch, instance_id: str, start_time: datetime, end_time: datetime) -> List[float]:
549
+
550
+ async def _get_ec2_utilization(
551
+ self, cloudwatch, instance_id: str, start_time: datetime, end_time: datetime
552
+ ) -> List[float]:
509
553
  """Get EC2 instance CPU utilization from CloudWatch."""
510
554
  try:
511
555
  response = cloudwatch.get_metric_statistics(
512
- Namespace='AWS/EC2',
513
- MetricName='CPUUtilization',
514
- Dimensions=[{'Name': 'InstanceId', 'Value': instance_id}],
556
+ Namespace="AWS/EC2",
557
+ MetricName="CPUUtilization",
558
+ Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
515
559
  StartTime=start_time,
516
560
  EndTime=end_time,
517
561
  Period=86400, # Daily data points
518
- Statistics=['Average']
562
+ Statistics=["Average"],
519
563
  )
520
-
521
- return [point['Average'] for point in response.get('Datapoints', [])]
522
-
564
+
565
+ return [point["Average"] for point in response.get("Datapoints", [])]
566
+
523
567
  except Exception as e:
524
568
  logger.warning(f"CloudWatch CPU metrics unavailable for EC2 {instance_id}: {e}")
525
569
  return []
526
-
527
- async def _get_rds_utilization(self, cloudwatch, db_identifier: str, start_time: datetime, end_time: datetime) -> List[float]:
570
+
571
+ async def _get_rds_utilization(
572
+ self, cloudwatch, db_identifier: str, start_time: datetime, end_time: datetime
573
+ ) -> List[float]:
528
574
  """Get RDS instance CPU utilization from CloudWatch."""
529
575
  try:
530
576
  response = cloudwatch.get_metric_statistics(
531
- Namespace='AWS/RDS',
532
- MetricName='CPUUtilization',
533
- Dimensions=[{'Name': 'DBInstanceIdentifier', 'Value': db_identifier}],
577
+ Namespace="AWS/RDS",
578
+ MetricName="CPUUtilization",
579
+ Dimensions=[{"Name": "DBInstanceIdentifier", "Value": db_identifier}],
534
580
  StartTime=start_time,
535
581
  EndTime=end_time,
536
582
  Period=86400, # Daily data points
537
- Statistics=['Average']
583
+ Statistics=["Average"],
538
584
  )
539
-
540
- return [point['Average'] for point in response.get('Datapoints', [])]
541
-
585
+
586
+ return [point["Average"] for point in response.get("Datapoints", [])]
587
+
542
588
  except Exception as e:
543
589
  logger.warning(f"CloudWatch CPU metrics unavailable for RDS {db_identifier}: {e}")
544
590
  return []
545
-
591
+
546
592
  def _calculate_usage_hours(self, utilization_data: List[float], analysis_days: int) -> float:
547
593
  """Calculate actual usage hours based on utilization data."""
548
594
  if not utilization_data:
549
595
  # No metrics available, assume moderate usage
550
596
  return analysis_days * 24 * 0.7 # 70% uptime assumption
551
-
597
+
552
598
  # Assume instance is "in use" if CPU > 5%
553
599
  active_days = sum(1 for cpu in utilization_data if cpu > 5.0)
554
600
  total_hours = active_days * 24 # Assume full day usage when active
555
-
601
+
556
602
  return min(total_hours, analysis_days * 24)
557
-
603
+
558
604
  def _calculate_ri_suitability_score(self, pattern: ResourceUsagePattern) -> float:
559
605
  """Calculate RI suitability score (0-100) for resource."""
560
606
  score = 0.0
561
-
607
+
562
608
  # Usage consistency (50% weight)
563
609
  score += pattern.usage_consistency_score * 50
564
-
610
+
565
611
  # Resource type stability (25% weight)
566
- if pattern.tags.get('Environment') in ['production', 'prod']:
612
+ if pattern.tags.get("Environment") in ["production", "prod"]:
567
613
  score += 25
568
- elif pattern.tags.get('Environment') in ['staging', 'test']:
614
+ elif pattern.tags.get("Environment") in ["staging", "test"]:
569
615
  score += 10
570
616
  else:
571
617
  score += 15 # Unknown environment
572
-
618
+
573
619
  # Cost impact (25% weight)
574
620
  if pattern.current_annual_cost > 5000: # High cost resources
575
621
  score += 25
@@ -577,110 +623,126 @@ class ReservationOptimizer:
577
623
  score += 20
578
624
  else:
579
625
  score += 10
580
-
626
+
581
627
  return min(100.0, score)
582
-
583
- async def _assess_ri_suitability(self, patterns: List[ResourceUsagePattern], progress, task_id) -> List[ResourceUsagePattern]:
628
+
629
+ async def _assess_ri_suitability(
630
+ self, patterns: List[ResourceUsagePattern], progress, task_id
631
+ ) -> List[ResourceUsagePattern]:
584
632
  """Assess which resources are suitable for Reserved Instance purchase."""
585
633
  suitable_resources = []
586
-
634
+
587
635
  for pattern in patterns:
588
636
  try:
589
637
  # Check if resource meets RI suitability criteria
590
- if (pattern.ri_suitability_score >= 60.0 and
591
- pattern.usage_consistency_score >= self.minimum_usage_threshold):
638
+ if (
639
+ pattern.ri_suitability_score >= 60.0
640
+ and pattern.usage_consistency_score >= self.minimum_usage_threshold
641
+ ):
592
642
  suitable_resources.append(pattern)
593
-
643
+
594
644
  except Exception as e:
595
645
  logger.warning(f"RI suitability assessment failed for {pattern.resource_id}: {e}")
596
-
646
+
597
647
  progress.advance(task_id)
598
-
648
+
599
649
  return suitable_resources
600
-
601
- async def _generate_ri_recommendations(self, suitable_resources: List[ResourceUsagePattern], progress, task_id) -> List[RIRecommendation]:
650
+
651
+ async def _generate_ri_recommendations(
652
+ self, suitable_resources: List[ResourceUsagePattern], progress, task_id
653
+ ) -> List[RIRecommendation]:
602
654
  """Generate Reserved Instance purchase recommendations with financial modeling."""
603
655
  recommendations = []
604
-
656
+
605
657
  for resource in suitable_resources:
606
658
  try:
607
659
  # Get RI pricing for resource type
608
660
  service_pricing = self.service_pricing.get(resource.service, {})
609
661
  type_pricing = service_pricing.get(resource.resource_type, {})
610
- ri_pricing = type_pricing.get('ri_1yr_partial', {})
611
-
662
+ ri_pricing = type_pricing.get("ri_1yr_partial", {})
663
+
612
664
  if not ri_pricing:
613
665
  progress.advance(task_id)
614
666
  continue
615
-
667
+
616
668
  # Calculate financial model
617
- upfront_cost = ri_pricing.get('upfront', 0)
618
- ri_hourly_rate = ri_pricing.get('hourly', resource.on_demand_hourly_rate * 0.6)
619
-
669
+ upfront_cost = ri_pricing.get("upfront", 0)
670
+ ri_hourly_rate = ri_pricing.get("hourly", resource.on_demand_hourly_rate * 0.6)
671
+
620
672
  # Effective hourly rate including amortized upfront
621
673
  effective_hourly_rate = ri_hourly_rate + (upfront_cost / (365.25 * 24))
622
-
674
+
623
675
  # Savings calculations based on actual usage
624
676
  annual_usage_hours = resource.average_daily_hours * 365.25
625
677
  on_demand_annual_cost = resource.on_demand_hourly_rate * annual_usage_hours
626
678
  ri_annual_cost = upfront_cost + (ri_hourly_rate * annual_usage_hours)
627
679
  annual_savings = on_demand_annual_cost - ri_annual_cost
628
-
680
+
629
681
  # Break-even analysis
630
682
  monthly_savings = annual_savings / 12
631
683
  break_even_months = upfront_cost / monthly_savings if monthly_savings > 0 else 999
632
-
684
+
633
685
  # ROI calculation
634
- roi_percentage = (annual_savings / (upfront_cost + ri_hourly_rate * annual_usage_hours)) * 100 if upfront_cost > 0 else 0
635
-
686
+ roi_percentage = (
687
+ (annual_savings / (upfront_cost + ri_hourly_rate * annual_usage_hours)) * 100
688
+ if upfront_cost > 0
689
+ else 0
690
+ )
691
+
636
692
  # Risk assessment
637
693
  utilization_confidence = resource.usage_consistency_score
638
- risk_level = "low" if utilization_confidence > 0.8 else ("medium" if utilization_confidence > 0.6 else "high")
639
-
694
+ risk_level = (
695
+ "low" if utilization_confidence > 0.8 else ("medium" if utilization_confidence > 0.6 else "high")
696
+ )
697
+
640
698
  # Only recommend if financially beneficial
641
699
  if annual_savings > 0 and break_even_months <= self.break_even_target_months:
642
- recommendations.append(RIRecommendation(
643
- resource_type=resource.resource_type,
644
- service=resource.service,
645
- region=resource.region,
646
- availability_zone=resource.availability_zone,
647
- platform=resource.platform,
648
- recommended_quantity=1,
649
- ri_term=RITerm.ONE_YEAR,
650
- payment_option=RIPaymentOption.PARTIAL_UPFRONT,
651
- ri_upfront_cost=upfront_cost,
652
- ri_hourly_rate=ri_hourly_rate,
653
- ri_effective_hourly_rate=effective_hourly_rate,
654
- on_demand_hourly_rate=resource.on_demand_hourly_rate,
655
- break_even_months=break_even_months,
656
- first_year_savings=annual_savings,
657
- total_term_savings=annual_savings, # 1-year term
658
- annual_savings=annual_savings,
659
- roi_percentage=roi_percentage,
660
- utilization_confidence=utilization_confidence,
661
- risk_level=risk_level,
662
- flexibility_impact="minimal",
663
- covered_resources=[resource.resource_id],
664
- usage_justification=f"Resource shows {resource.usage_consistency_score*100:.1f}% consistent usage over {resource.analysis_period_days} days"
665
- ))
666
-
700
+ recommendations.append(
701
+ RIRecommendation(
702
+ resource_type=resource.resource_type,
703
+ service=resource.service,
704
+ region=resource.region,
705
+ availability_zone=resource.availability_zone,
706
+ platform=resource.platform,
707
+ recommended_quantity=1,
708
+ ri_term=RITerm.ONE_YEAR,
709
+ payment_option=RIPaymentOption.PARTIAL_UPFRONT,
710
+ ri_upfront_cost=upfront_cost,
711
+ ri_hourly_rate=ri_hourly_rate,
712
+ ri_effective_hourly_rate=effective_hourly_rate,
713
+ on_demand_hourly_rate=resource.on_demand_hourly_rate,
714
+ break_even_months=break_even_months,
715
+ first_year_savings=annual_savings,
716
+ total_term_savings=annual_savings, # 1-year term
717
+ annual_savings=annual_savings,
718
+ roi_percentage=roi_percentage,
719
+ utilization_confidence=utilization_confidence,
720
+ risk_level=risk_level,
721
+ flexibility_impact="minimal",
722
+ covered_resources=[resource.resource_id],
723
+ usage_justification=f"Resource shows {resource.usage_consistency_score * 100:.1f}% consistent usage over {resource.analysis_period_days} days",
724
+ )
725
+ )
726
+
667
727
  except Exception as e:
668
728
  logger.warning(f"RI recommendation generation failed for {resource.resource_id}: {e}")
669
-
729
+
670
730
  progress.advance(task_id)
671
-
731
+
672
732
  return recommendations
673
-
674
- async def _optimize_ri_portfolio(self, recommendations: List[RIRecommendation], progress, task_id) -> List[RIRecommendation]:
733
+
734
+ async def _optimize_ri_portfolio(
735
+ self, recommendations: List[RIRecommendation], progress, task_id
736
+ ) -> List[RIRecommendation]:
675
737
  """Optimize RI portfolio for maximum value and minimum risk."""
676
738
  try:
677
739
  # Sort recommendations by ROI and risk level
678
740
  optimized = sorted(recommendations, key=lambda x: (x.roi_percentage, -x.break_even_months), reverse=True)
679
-
741
+
680
742
  # Apply portfolio constraints (simplified)
681
743
  budget_limit = 1_000_000 # $1M annual RI budget limit
682
744
  current_investment = 0
683
-
745
+
684
746
  final_recommendations = []
685
747
  for recommendation in optimized:
686
748
  if current_investment + recommendation.ri_upfront_cost <= budget_limit:
@@ -688,71 +750,75 @@ class ReservationOptimizer:
688
750
  current_investment += recommendation.ri_upfront_cost
689
751
  else:
690
752
  break
691
-
753
+
692
754
  progress.advance(task_id)
693
755
  return final_recommendations
694
-
756
+
695
757
  except Exception as e:
696
758
  logger.warning(f"RI portfolio optimization failed: {e}")
697
759
  progress.advance(task_id)
698
760
  return recommendations
699
-
761
+
700
762
  async def _validate_with_mcp(self, recommendations: List[RIRecommendation], progress, task_id) -> float:
701
763
  """Validate RI recommendations with embedded MCP validator."""
702
764
  try:
703
765
  # Prepare validation data in FinOps format
704
766
  validation_data = {
705
- 'total_upfront_investment': sum(rec.ri_upfront_cost for rec in recommendations),
706
- 'total_annual_savings': sum(rec.annual_savings for rec in recommendations),
707
- 'recommendations_count': len(recommendations),
708
- 'services_analyzed': list(set(rec.service.value for rec in recommendations)),
709
- 'analysis_timestamp': datetime.now().isoformat()
767
+ "total_upfront_investment": sum(rec.ri_upfront_cost for rec in recommendations),
768
+ "total_annual_savings": sum(rec.annual_savings for rec in recommendations),
769
+ "recommendations_count": len(recommendations),
770
+ "services_analyzed": list(set(rec.service.value for rec in recommendations)),
771
+ "analysis_timestamp": datetime.now().isoformat(),
710
772
  }
711
-
773
+
712
774
  # Initialize MCP validator if profile is available
713
775
  if self.profile_name:
714
776
  mcp_validator = EmbeddedMCPValidator([self.profile_name])
715
777
  validation_results = await mcp_validator.validate_cost_data_async(validation_data)
716
- accuracy = validation_results.get('total_accuracy', 0.0)
717
-
778
+ accuracy = validation_results.get("total_accuracy", 0.0)
779
+
718
780
  if accuracy >= 99.5:
719
781
  print_success(f"MCP Validation: {accuracy:.1f}% accuracy achieved (target: ≥99.5%)")
720
782
  else:
721
783
  print_warning(f"MCP Validation: {accuracy:.1f}% accuracy (target: ≥99.5%)")
722
-
784
+
723
785
  progress.advance(task_id)
724
786
  return accuracy
725
787
  else:
726
788
  print_info("MCP validation skipped - no profile specified")
727
789
  progress.advance(task_id)
728
790
  return 0.0
729
-
791
+
730
792
  except Exception as e:
731
793
  print_warning(f"MCP validation failed: {str(e)}")
732
794
  progress.advance(task_id)
733
795
  return 0.0
734
-
735
- def _compile_results(self, usage_patterns: List[ResourceUsagePattern],
736
- recommendations: List[RIRecommendation],
737
- mcp_accuracy: float, analysis_start_time: float,
738
- services_analyzed: List[RIService]) -> RIOptimizerResults:
796
+
797
+ def _compile_results(
798
+ self,
799
+ usage_patterns: List[ResourceUsagePattern],
800
+ recommendations: List[RIRecommendation],
801
+ mcp_accuracy: float,
802
+ analysis_start_time: float,
803
+ services_analyzed: List[RIService],
804
+ ) -> RIOptimizerResults:
739
805
  """Compile comprehensive RI optimization results."""
740
-
806
+
741
807
  # Categorize recommendations by service
742
808
  ec2_recommendations = [r for r in recommendations if r.service == RIService.EC2]
743
809
  rds_recommendations = [r for r in recommendations if r.service == RIService.RDS]
744
810
  elasticache_recommendations = [r for r in recommendations if r.service == RIService.ELASTICACHE]
745
811
  redshift_recommendations = [r for r in recommendations if r.service == RIService.REDSHIFT]
746
-
812
+
747
813
  # Calculate financial summary
748
814
  total_upfront_investment = sum(rec.ri_upfront_cost for rec in recommendations)
749
815
  total_annual_savings = sum(rec.annual_savings for rec in recommendations)
750
816
  total_current_on_demand_cost = sum(pattern.current_annual_cost for pattern in usage_patterns)
751
817
  total_potential_ri_cost = total_current_on_demand_cost - total_annual_savings
752
-
818
+
753
819
  # Calculate portfolio ROI
754
820
  portfolio_roi = (total_annual_savings / total_upfront_investment * 100) if total_upfront_investment > 0 else 0
755
-
821
+
756
822
  return RIOptimizerResults(
757
823
  analyzed_services=services_analyzed,
758
824
  analyzed_regions=self.regions,
@@ -771,12 +837,12 @@ class ReservationOptimizer:
771
837
  redshift_recommendations=redshift_recommendations,
772
838
  execution_time_seconds=time.time() - analysis_start_time,
773
839
  mcp_validation_accuracy=mcp_accuracy,
774
- analysis_timestamp=datetime.now()
840
+ analysis_timestamp=datetime.now(),
775
841
  )
776
-
842
+
777
843
  def _display_executive_summary(self, results: RIOptimizerResults) -> None:
778
844
  """Display executive summary with Rich CLI formatting."""
779
-
845
+
780
846
  # Executive Summary Panel
781
847
  summary_content = f"""
782
848
  💼 Reserved Instance Portfolio Analysis
@@ -794,23 +860,21 @@ class ReservationOptimizer:
794
860
  • ElastiCache: {len(results.elasticache_recommendations)} recommendations
795
861
  • Redshift: {len(results.redshift_recommendations)} recommendations
796
862
 
797
- 🌍 Regions: {', '.join(results.analyzed_regions)}
863
+ 🌍 Regions: {", ".join(results.analyzed_regions)}
798
864
  ⚡ Analysis Time: {results.execution_time_seconds:.2f}s
799
865
  ✅ MCP Accuracy: {results.mcp_validation_accuracy:.1f}%
800
866
  """
801
-
802
- console.print(create_panel(
803
- summary_content.strip(),
804
- title="🏆 Reserved Instance Portfolio Executive Summary",
805
- border_style="green"
806
- ))
807
-
867
+
868
+ console.print(
869
+ create_panel(
870
+ summary_content.strip(), title="🏆 Reserved Instance Portfolio Executive Summary", border_style="green"
871
+ )
872
+ )
873
+
808
874
  # RI Recommendations Table
809
875
  if results.ri_recommendations:
810
- table = create_table(
811
- title="Reserved Instance Purchase Recommendations"
812
- )
813
-
876
+ table = create_table(title="Reserved Instance Purchase Recommendations")
877
+
814
878
  table.add_column("Service", style="cyan", no_wrap=True)
815
879
  table.add_column("Resource Type", style="dim")
816
880
  table.add_column("Region", justify="center")
@@ -819,24 +883,16 @@ class ReservationOptimizer:
819
883
  table.add_column("Break-even", justify="center")
820
884
  table.add_column("ROI", justify="right", style="blue")
821
885
  table.add_column("Risk", justify="center")
822
-
886
+
823
887
  # Sort by annual savings (descending)
824
- sorted_recommendations = sorted(
825
- results.ri_recommendations,
826
- key=lambda x: x.annual_savings,
827
- reverse=True
828
- )
829
-
888
+ sorted_recommendations = sorted(results.ri_recommendations, key=lambda x: x.annual_savings, reverse=True)
889
+
830
890
  # Show top 20 recommendations
831
891
  display_recommendations = sorted_recommendations[:20]
832
-
892
+
833
893
  for rec in display_recommendations:
834
- risk_indicator = {
835
- "low": "🟢",
836
- "medium": "🟡",
837
- "high": "🔴"
838
- }.get(rec.risk_level, "⚪")
839
-
894
+ risk_indicator = {"low": "🟢", "medium": "🟡", "high": "🔴"}.get(rec.risk_level, "⚪")
895
+
840
896
  table.add_row(
841
897
  rec.service.value.upper(),
842
898
  rec.resource_type,
@@ -845,17 +901,23 @@ class ReservationOptimizer:
845
901
  format_cost(rec.annual_savings),
846
902
  f"{rec.break_even_months:.1f} mo",
847
903
  f"{rec.roi_percentage:.1f}%",
848
- f"{risk_indicator} {rec.risk_level}"
904
+ f"{risk_indicator} {rec.risk_level}",
849
905
  )
850
-
906
+
851
907
  if len(sorted_recommendations) > 20:
852
908
  table.add_row(
853
- "...", "...", "...", "...", "...", "...", "...",
854
- f"[dim]+{len(sorted_recommendations) - 20} more recommendations[/]"
909
+ "...",
910
+ "...",
911
+ "...",
912
+ "...",
913
+ "...",
914
+ "...",
915
+ "...",
916
+ f"[dim]+{len(sorted_recommendations) - 20} more recommendations[/]",
855
917
  )
856
-
918
+
857
919
  console.print(table)
858
-
920
+
859
921
  # Financial Summary Panel
860
922
  financial_content = f"""
861
923
  💰 RI Investment Portfolio Summary:
@@ -869,40 +931,42 @@ class ReservationOptimizer:
869
931
  🔄 Cost Transformation:
870
932
  • From: {format_cost(results.total_current_on_demand_cost)} On-Demand
871
933
  • To: {format_cost(results.total_potential_ri_cost)} Reserved Instance
872
- • Savings: {format_cost(results.total_annual_savings)} ({(results.total_annual_savings/results.total_current_on_demand_cost*100):.1f}% reduction)
934
+ • Savings: {format_cost(results.total_annual_savings)} ({(results.total_annual_savings / results.total_current_on_demand_cost * 100):.1f}% reduction)
873
935
  """
874
-
875
- console.print(create_panel(
876
- financial_content.strip(),
877
- title="💼 RI Procurement Financial Analysis",
878
- border_style="blue"
879
- ))
936
+
937
+ console.print(
938
+ create_panel(
939
+ financial_content.strip(), title="💼 RI Procurement Financial Analysis", border_style="blue"
940
+ )
941
+ )
880
942
 
881
943
 
882
944
  # CLI Integration for enterprise runbooks commands
883
945
  @click.command()
884
- @click.option('--profile', help='AWS profile name (3-tier priority: User > Environment > Default)')
885
- @click.option('--regions', multiple=True, help='AWS regions to analyze (space-separated)')
886
- @click.option('--services', multiple=True,
887
- type=click.Choice(['ec2', 'rds', 'elasticache', 'redshift']),
888
- help='AWS services to analyze for RI opportunities')
889
- @click.option('--dry-run/--no-dry-run', default=True, help='Execute in dry-run mode (READ-ONLY analysis)')
890
- @click.option('--usage-threshold-days', type=int, default=90,
891
- help='Usage analysis period in days')
946
+ @click.option("--profile", help="AWS profile name (3-tier priority: User > Environment > Default)")
947
+ @click.option("--regions", multiple=True, help="AWS regions to analyze (space-separated)")
948
+ @click.option(
949
+ "--services",
950
+ multiple=True,
951
+ type=click.Choice(["ec2", "rds", "elasticache", "redshift"]),
952
+ help="AWS services to analyze for RI opportunities",
953
+ )
954
+ @click.option("--dry-run/--no-dry-run", default=True, help="Execute in dry-run mode (READ-ONLY analysis)")
955
+ @click.option("--usage-threshold-days", type=int, default=90, help="Usage analysis period in days")
892
956
  def reservation_optimizer(profile, regions, services, dry_run, usage_threshold_days):
893
957
  """
894
958
  Reserved Instance Optimizer - Enterprise Multi-Service RI Strategy
895
-
959
+
896
960
  Comprehensive RI analysis and procurement recommendations:
897
961
  • Multi-service RI analysis (EC2, RDS, ElastiCache, Redshift)
898
962
  • Historical usage pattern analysis with financial modeling
899
963
  • Break-even analysis and ROI calculations for RI procurement
900
964
  • Portfolio optimization with risk assessment and budget constraints
901
-
965
+
902
966
  Part of $132,720+ annual savings methodology targeting $3.2M-$17M RI optimization.
903
-
967
+
904
968
  SAFETY: READ-ONLY analysis only - no actual RI purchases.
905
-
969
+
906
970
  Examples:
907
971
  runbooks finops reservation --analyze
908
972
  runbooks finops reservation --services ec2 rds --regions us-east-1 us-west-2
@@ -913,37 +977,37 @@ def reservation_optimizer(profile, regions, services, dry_run, usage_threshold_d
913
977
  service_enums = []
914
978
  if services:
915
979
  service_map = {
916
- 'ec2': RIService.EC2,
917
- 'rds': RIService.RDS,
918
- 'elasticache': RIService.ELASTICACHE,
919
- 'redshift': RIService.REDSHIFT
980
+ "ec2": RIService.EC2,
981
+ "rds": RIService.RDS,
982
+ "elasticache": RIService.ELASTICACHE,
983
+ "redshift": RIService.REDSHIFT,
920
984
  }
921
985
  service_enums = [service_map[s] for s in services]
922
-
986
+
923
987
  # Initialize optimizer
924
- optimizer = ReservationOptimizer(
925
- profile_name=profile,
926
- regions=list(regions) if regions else None
927
- )
928
-
988
+ optimizer = ReservationOptimizer(profile_name=profile, regions=list(regions) if regions else None)
989
+
929
990
  # Override analysis period if specified
930
991
  if usage_threshold_days != 90:
931
992
  optimizer.analysis_period_days = usage_threshold_days
932
-
993
+
933
994
  # Execute comprehensive analysis
934
- results = asyncio.run(optimizer.analyze_reservation_opportunities(
935
- services=service_enums if service_enums else None,
936
- dry_run=dry_run
937
- ))
938
-
995
+ results = asyncio.run(
996
+ optimizer.analyze_reservation_opportunities(
997
+ services=service_enums if service_enums else None, dry_run=dry_run
998
+ )
999
+ )
1000
+
939
1001
  # Display final success message
940
1002
  if results.total_annual_savings > 0:
941
1003
  print_success(f"Analysis complete: {format_cost(results.total_annual_savings)} potential annual savings")
942
- print_info(f"Required investment: {format_cost(results.total_upfront_investment)} ({results.portfolio_roi:.1f}% ROI)")
1004
+ print_info(
1005
+ f"Required investment: {format_cost(results.total_upfront_investment)} ({results.portfolio_roi:.1f}% ROI)"
1006
+ )
943
1007
  print_info(f"Services analyzed: {', '.join([s.value.upper() for s in results.analyzed_services])}")
944
1008
  else:
945
1009
  print_info("Analysis complete: No cost-effective RI opportunities identified")
946
-
1010
+
947
1011
  except KeyboardInterrupt:
948
1012
  print_warning("Analysis interrupted by user")
949
1013
  raise click.Abort()
@@ -952,5 +1016,5 @@ def reservation_optimizer(profile, regions, services, dry_run, usage_threshold_d
952
1016
  raise click.Abort()
953
1017
 
954
1018
 
955
- if __name__ == '__main__':
956
- reservation_optimizer()
1019
+ if __name__ == "__main__":
1020
+ reservation_optimizer()