runbooks 0.9.0__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +1 -1
- runbooks/cfat/assessment/compliance.py +4 -1
- runbooks/cloudops/__init__.py +123 -0
- runbooks/cloudops/base.py +385 -0
- runbooks/cloudops/cost_optimizer.py +811 -0
- runbooks/cloudops/infrastructure_optimizer.py +29 -0
- runbooks/cloudops/interfaces.py +828 -0
- runbooks/cloudops/lifecycle_manager.py +29 -0
- runbooks/cloudops/mcp_cost_validation.py +678 -0
- runbooks/cloudops/models.py +251 -0
- runbooks/cloudops/monitoring_automation.py +29 -0
- runbooks/cloudops/notebook_framework.py +676 -0
- runbooks/cloudops/security_enforcer.py +449 -0
- runbooks/common/mcp_cost_explorer_integration.py +900 -0
- runbooks/common/mcp_integration.py +19 -10
- runbooks/common/rich_utils.py +1 -1
- runbooks/finops/README.md +31 -0
- runbooks/finops/cost_optimizer.py +1340 -0
- runbooks/finops/finops_dashboard.py +211 -5
- runbooks/finops/schemas.py +589 -0
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- runbooks/main.py +525 -0
- runbooks/operate/ec2_operations.py +428 -0
- runbooks/operate/iam_operations.py +598 -3
- runbooks/operate/rds_operations.py +508 -0
- runbooks/operate/s3_operations.py +508 -0
- runbooks/remediation/base.py +5 -3
- runbooks/security/__init__.py +101 -0
- runbooks/security/cloudops_automation_security_validator.py +1164 -0
- runbooks/security/compliance_automation_engine.py +4 -4
- runbooks/security/enterprise_security_framework.py +4 -5
- runbooks/security/executive_security_dashboard.py +1247 -0
- runbooks/security/multi_account_security_controls.py +2254 -0
- runbooks/security/real_time_security_monitor.py +1196 -0
- runbooks/security/security_baseline_tester.py +3 -3
- runbooks/sre/production_monitoring_framework.py +584 -0
- runbooks/validation/mcp_validator.py +29 -15
- runbooks/vpc/networking_wrapper.py +6 -3
- runbooks-0.9.1.dist-info/METADATA +308 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.1.dist-info}/RECORD +45 -23
- runbooks-0.9.0.dist-info/METADATA +0 -718
- {runbooks-0.9.0.dist-info → runbooks-0.9.1.dist-info}/WHEEL +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.1.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,589 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Pydantic v2 Schemas for FinOps Cost Optimization Platform
|
4
|
+
|
5
|
+
This module provides comprehensive Pydantic v2 validation schemas for all cost optimization
|
6
|
+
outputs, ensuring strict type safety and data integrity throughout the platform.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
- Comprehensive cost optimization result validation
|
10
|
+
- Business metrics validation with executive reporting
|
11
|
+
- Technical validation schemas for functional testing
|
12
|
+
- Schema evolution support for backward compatibility
|
13
|
+
- Rich CLI integration for formatted output display
|
14
|
+
- Export validation for JSON, CSV, HTML, and PDF formats
|
15
|
+
|
16
|
+
Strategic Alignment:
|
17
|
+
- Supports dual-purpose architecture (business + technical users)
|
18
|
+
- Enables real-time MCP validation with strict tolerances
|
19
|
+
- Provides executive-ready reporting with validated metrics
|
20
|
+
- Maintains enterprise standards for data integrity
|
21
|
+
"""
|
22
|
+
|
23
|
+
from decimal import Decimal, ROUND_HALF_UP
|
24
|
+
from datetime import datetime, date
|
25
|
+
from typing import Dict, List, Optional, Union, Any, Literal
|
26
|
+
from enum import Enum
|
27
|
+
import re
|
28
|
+
|
29
|
+
from pydantic import BaseModel, Field, validator, root_validator, ConfigDict
|
30
|
+
from pydantic.types import UUID4, PositiveFloat, NonNegativeFloat
|
31
|
+
|
32
|
+
|
33
|
+
# Configuration for Pydantic v2
|
34
|
+
class BaseSchema(BaseModel):
|
35
|
+
"""Base schema with common configuration for all models."""
|
36
|
+
|
37
|
+
model_config = ConfigDict(
|
38
|
+
# Enable strict validation
|
39
|
+
str_strip_whitespace=True,
|
40
|
+
validate_assignment=True,
|
41
|
+
use_enum_values=True,
|
42
|
+
# Allow for schema evolution
|
43
|
+
extra='forbid', # Strict validation - no unexpected fields
|
44
|
+
# JSON encoding settings
|
45
|
+
json_encoders={
|
46
|
+
datetime: lambda dt: dt.isoformat(),
|
47
|
+
date: lambda d: d.isoformat(),
|
48
|
+
Decimal: lambda d: float(d),
|
49
|
+
}
|
50
|
+
)
|
51
|
+
|
52
|
+
|
53
|
+
class ComplexityLevel(str, Enum):
|
54
|
+
"""Implementation complexity levels for cost optimization scenarios."""
|
55
|
+
LOW = "Low"
|
56
|
+
MEDIUM = "Medium"
|
57
|
+
HIGH = "High"
|
58
|
+
|
59
|
+
|
60
|
+
class RiskLevel(str, Enum):
|
61
|
+
"""Risk levels for cost optimization implementations."""
|
62
|
+
LOW = "Low"
|
63
|
+
MEDIUM = "Medium"
|
64
|
+
HIGH = "High"
|
65
|
+
CRITICAL = "Critical"
|
66
|
+
|
67
|
+
|
68
|
+
class OptimizationCategory(str, Enum):
|
69
|
+
"""Categories of cost optimization scenarios."""
|
70
|
+
UNUSED_RESOURCES = "Unused Resources"
|
71
|
+
RIGHTSIZING = "Rightsizing"
|
72
|
+
RESERVED_INSTANCES = "Reserved Instances"
|
73
|
+
SPOT_INSTANCES = "Spot Instances"
|
74
|
+
STORAGE_OPTIMIZATION = "Storage Optimization"
|
75
|
+
NETWORK_OPTIMIZATION = "Network Optimization"
|
76
|
+
GOVERNANCE = "Governance"
|
77
|
+
LIFECYCLE_MANAGEMENT = "Lifecycle Management"
|
78
|
+
|
79
|
+
|
80
|
+
class ValidationStatus(str, Enum):
|
81
|
+
"""Validation status for cross-checking with MCP servers."""
|
82
|
+
VALIDATED = "validated"
|
83
|
+
VARIANCE_DETECTED = "variance_detected"
|
84
|
+
MCP_UNAVAILABLE = "mcp_unavailable"
|
85
|
+
ERROR = "error"
|
86
|
+
PENDING = "pending"
|
87
|
+
|
88
|
+
|
89
|
+
class ExportFormat(str, Enum):
|
90
|
+
"""Supported export formats for reports."""
|
91
|
+
JSON = "json"
|
92
|
+
CSV = "csv"
|
93
|
+
HTML = "html"
|
94
|
+
PDF = "pdf"
|
95
|
+
MARKDOWN = "markdown"
|
96
|
+
|
97
|
+
|
98
|
+
# Core Cost Optimization Schemas
|
99
|
+
|
100
|
+
class CostBreakdown(BaseSchema):
|
101
|
+
"""Detailed cost breakdown by service/category."""
|
102
|
+
|
103
|
+
service_name: str = Field(..., min_length=1, max_length=100)
|
104
|
+
monthly_cost: NonNegativeFloat = Field(..., ge=0)
|
105
|
+
annual_cost: NonNegativeFloat = Field(..., ge=0)
|
106
|
+
percentage_of_total: float = Field(..., ge=0, le=100)
|
107
|
+
resource_count: int = Field(..., ge=0)
|
108
|
+
|
109
|
+
@validator('service_name')
|
110
|
+
def validate_service_name(cls, v):
|
111
|
+
"""Validate AWS service names."""
|
112
|
+
# Common AWS service patterns
|
113
|
+
valid_patterns = [
|
114
|
+
r'^EC2-', # EC2 services
|
115
|
+
r'^S3', # S3 services
|
116
|
+
r'^RDS', # RDS services
|
117
|
+
r'^Lambda', # Lambda
|
118
|
+
r'^CloudWatch', # CloudWatch
|
119
|
+
r'^VPC', # VPC services
|
120
|
+
r'^Route 53', # Route 53
|
121
|
+
r'^ElastiCache', # ElastiCache
|
122
|
+
r'^Redshift', # Redshift
|
123
|
+
r'^\w+$' # General service names
|
124
|
+
]
|
125
|
+
|
126
|
+
if not any(re.match(pattern, v) for pattern in valid_patterns):
|
127
|
+
# Allow any string for flexibility, but validate length
|
128
|
+
if len(v.strip()) == 0:
|
129
|
+
raise ValueError('Service name cannot be empty')
|
130
|
+
|
131
|
+
return v.strip()
|
132
|
+
|
133
|
+
@validator('annual_cost')
|
134
|
+
def validate_annual_cost_consistency(cls, v, values):
|
135
|
+
"""Ensure annual cost is approximately 12x monthly cost."""
|
136
|
+
if 'monthly_cost' in values:
|
137
|
+
expected_annual = values['monthly_cost'] * 12
|
138
|
+
# Allow 1% tolerance for rounding differences
|
139
|
+
if abs(v - expected_annual) > (expected_annual * 0.01):
|
140
|
+
raise ValueError(f'Annual cost {v} should be approximately 12x monthly cost {values["monthly_cost"]}')
|
141
|
+
return v
|
142
|
+
|
143
|
+
|
144
|
+
class OptimizationScenario(BaseSchema):
|
145
|
+
"""Individual cost optimization scenario with validation."""
|
146
|
+
|
147
|
+
scenario_name: str = Field(..., min_length=3, max_length=200)
|
148
|
+
category: OptimizationCategory = Field(...)
|
149
|
+
description: str = Field(..., min_length=10, max_length=1000)
|
150
|
+
|
151
|
+
# Financial metrics
|
152
|
+
monthly_savings: NonNegativeFloat = Field(..., ge=0)
|
153
|
+
annual_savings: NonNegativeFloat = Field(..., ge=0)
|
154
|
+
implementation_cost: NonNegativeFloat = Field(0, ge=0)
|
155
|
+
payback_period_months: Optional[PositiveFloat] = Field(None, gt=0, le=120) # Max 10 years
|
156
|
+
|
157
|
+
# Implementation details
|
158
|
+
complexity: ComplexityLevel = Field(...)
|
159
|
+
risk_level: RiskLevel = Field(...)
|
160
|
+
estimated_hours: PositiveFloat = Field(..., gt=0, le=2000) # Reasonable implementation time
|
161
|
+
|
162
|
+
# Resource impact
|
163
|
+
affected_services: List[str] = Field(..., min_items=1)
|
164
|
+
affected_accounts: List[str] = Field(..., min_items=1)
|
165
|
+
resource_count: int = Field(..., ge=1)
|
166
|
+
|
167
|
+
# Validation metadata
|
168
|
+
validation_status: ValidationStatus = Field(ValidationStatus.PENDING)
|
169
|
+
validation_timestamp: Optional[datetime] = Field(None)
|
170
|
+
mcp_variance_percent: Optional[float] = Field(None, ge=0, le=100)
|
171
|
+
|
172
|
+
@validator('scenario_name')
|
173
|
+
def validate_scenario_name(cls, v):
|
174
|
+
"""Validate scenario naming conventions."""
|
175
|
+
# Ensure professional naming
|
176
|
+
if not re.match(r'^[A-Z][A-Za-z0-9\s\-\(\)]{2,199}$', v):
|
177
|
+
raise ValueError('Scenario name must start with capital letter and contain only letters, numbers, spaces, hyphens, and parentheses')
|
178
|
+
return v.strip()
|
179
|
+
|
180
|
+
@validator('annual_savings')
|
181
|
+
def validate_annual_savings_consistency(cls, v, values):
|
182
|
+
"""Ensure annual savings consistency with monthly savings."""
|
183
|
+
if 'monthly_savings' in values:
|
184
|
+
expected_annual = values['monthly_savings'] * 12
|
185
|
+
if abs(v - expected_annual) > (expected_annual * 0.01): # 1% tolerance
|
186
|
+
raise ValueError(f'Annual savings {v} should be approximately 12x monthly savings {values["monthly_savings"]}')
|
187
|
+
return v
|
188
|
+
|
189
|
+
@validator('payback_period_months')
|
190
|
+
def calculate_payback_period(cls, v, values):
|
191
|
+
"""Calculate payback period if not provided."""
|
192
|
+
if v is None and 'implementation_cost' in values and 'monthly_savings' in values:
|
193
|
+
impl_cost = values['implementation_cost']
|
194
|
+
monthly_savings = values['monthly_savings']
|
195
|
+
if monthly_savings > 0:
|
196
|
+
calculated_payback = impl_cost / monthly_savings
|
197
|
+
return round(calculated_payback, 1)
|
198
|
+
return v
|
199
|
+
|
200
|
+
@validator('affected_services')
|
201
|
+
def validate_aws_services(cls, v):
|
202
|
+
"""Validate AWS service names in affected services."""
|
203
|
+
common_services = {
|
204
|
+
'EC2', 'S3', 'RDS', 'Lambda', 'CloudWatch', 'VPC', 'ELB',
|
205
|
+
'Route53', 'CloudFront', 'ElastiCache', 'Redshift', 'DynamoDB',
|
206
|
+
'EBS', 'EFS', 'FSx', 'Backup', 'Config', 'CloudTrail', 'IAM'
|
207
|
+
}
|
208
|
+
|
209
|
+
for service in v:
|
210
|
+
# Allow any service that starts with common patterns or is in common list
|
211
|
+
if not (service in common_services or
|
212
|
+
any(service.startswith(prefix) for prefix in ['AWS', 'Amazon']) or
|
213
|
+
re.match(r'^[A-Z][A-Za-z0-9\-]{1,50}$', service)):
|
214
|
+
raise ValueError(f'Invalid AWS service name: {service}')
|
215
|
+
|
216
|
+
return v
|
217
|
+
|
218
|
+
@validator('affected_accounts')
|
219
|
+
def validate_account_ids(cls, v):
|
220
|
+
"""Validate AWS account ID format."""
|
221
|
+
account_pattern = r'^\d{12}$|^[\w\-\.]{1,50}$' # 12-digit ID or account name
|
222
|
+
|
223
|
+
for account in v:
|
224
|
+
if not re.match(account_pattern, account):
|
225
|
+
raise ValueError(f'Invalid account format: {account}. Must be 12-digit ID or valid account name')
|
226
|
+
|
227
|
+
return v
|
228
|
+
|
229
|
+
|
230
|
+
class CostOptimizationResult(BaseSchema):
|
231
|
+
"""Comprehensive cost optimization analysis result."""
|
232
|
+
|
233
|
+
# Analysis metadata
|
234
|
+
analysis_id: UUID4 = Field(..., description="Unique analysis identifier")
|
235
|
+
analysis_timestamp: datetime = Field(..., description="Analysis execution time")
|
236
|
+
profile_name: str = Field(..., min_length=1, max_length=100)
|
237
|
+
|
238
|
+
# Scope and configuration
|
239
|
+
analysis_scope: Literal["single_account", "multi_account", "organization"] = Field(...)
|
240
|
+
total_accounts: int = Field(..., ge=1, le=1000) # Reasonable limit
|
241
|
+
analysis_period_days: int = Field(..., ge=1, le=365)
|
242
|
+
|
243
|
+
# Financial summary
|
244
|
+
current_monthly_spend: NonNegativeFloat = Field(..., ge=0)
|
245
|
+
total_potential_monthly_savings: NonNegativeFloat = Field(..., ge=0)
|
246
|
+
total_potential_annual_savings: NonNegativeFloat = Field(..., ge=0)
|
247
|
+
savings_percentage: float = Field(..., ge=0, le=100)
|
248
|
+
|
249
|
+
# Scenarios and breakdown
|
250
|
+
optimization_scenarios: List[OptimizationScenario] = Field(..., min_items=1, max_items=100)
|
251
|
+
cost_breakdown: List[CostBreakdown] = Field(..., min_items=1)
|
252
|
+
|
253
|
+
# Implementation summary
|
254
|
+
total_scenarios: int = Field(..., ge=1)
|
255
|
+
low_complexity_scenarios: int = Field(..., ge=0)
|
256
|
+
medium_complexity_scenarios: int = Field(..., ge=0)
|
257
|
+
high_complexity_scenarios: int = Field(..., ge=0)
|
258
|
+
|
259
|
+
# Risk assessment
|
260
|
+
average_risk_score: float = Field(..., ge=1, le=5) # 1-5 scale
|
261
|
+
high_risk_scenarios_count: int = Field(..., ge=0)
|
262
|
+
|
263
|
+
# Validation and quality
|
264
|
+
mcp_validation_status: ValidationStatus = Field(ValidationStatus.PENDING)
|
265
|
+
validation_summary: Optional[Dict[str, Any]] = Field(None)
|
266
|
+
accuracy_confidence: Optional[float] = Field(None, ge=0, le=100)
|
267
|
+
|
268
|
+
# Export metadata
|
269
|
+
supported_export_formats: List[ExportFormat] = Field(
|
270
|
+
default=[ExportFormat.JSON, ExportFormat.CSV, ExportFormat.PDF]
|
271
|
+
)
|
272
|
+
|
273
|
+
@validator('total_potential_annual_savings')
|
274
|
+
def validate_annual_consistency(cls, v, values):
|
275
|
+
"""Validate annual savings consistency."""
|
276
|
+
if 'total_potential_monthly_savings' in values:
|
277
|
+
expected = values['total_potential_monthly_savings'] * 12
|
278
|
+
if abs(v - expected) > (expected * 0.01):
|
279
|
+
raise ValueError('Annual savings must be approximately 12x monthly savings')
|
280
|
+
return v
|
281
|
+
|
282
|
+
@validator('savings_percentage')
|
283
|
+
def calculate_savings_percentage(cls, v, values):
|
284
|
+
"""Validate or calculate savings percentage."""
|
285
|
+
if 'current_monthly_spend' in values and 'total_potential_monthly_savings' in values:
|
286
|
+
current_spend = values['current_monthly_spend']
|
287
|
+
if current_spend > 0:
|
288
|
+
calculated = (values['total_potential_monthly_savings'] / current_spend) * 100
|
289
|
+
if abs(v - calculated) > 0.1: # 0.1% tolerance
|
290
|
+
raise ValueError(f'Savings percentage {v}% inconsistent with calculated {calculated:.1f}%')
|
291
|
+
return v
|
292
|
+
|
293
|
+
@validator('total_scenarios')
|
294
|
+
def validate_scenario_count(cls, v, values):
|
295
|
+
"""Ensure scenario count matches actual scenarios."""
|
296
|
+
if 'optimization_scenarios' in values:
|
297
|
+
actual_count = len(values['optimization_scenarios'])
|
298
|
+
if v != actual_count:
|
299
|
+
raise ValueError(f'Total scenarios {v} does not match actual scenarios count {actual_count}')
|
300
|
+
return v
|
301
|
+
|
302
|
+
@root_validator
|
303
|
+
def validate_complexity_distribution(cls, values):
|
304
|
+
"""Validate complexity scenario counts."""
|
305
|
+
scenarios = values.get('optimization_scenarios', [])
|
306
|
+
if scenarios:
|
307
|
+
low_count = sum(1 for s in scenarios if s.complexity == ComplexityLevel.LOW)
|
308
|
+
medium_count = sum(1 for s in scenarios if s.complexity == ComplexityLevel.MEDIUM)
|
309
|
+
high_count = sum(1 for s in scenarios if s.complexity == ComplexityLevel.HIGH)
|
310
|
+
|
311
|
+
expected_low = values.get('low_complexity_scenarios', 0)
|
312
|
+
expected_medium = values.get('medium_complexity_scenarios', 0)
|
313
|
+
expected_high = values.get('high_complexity_scenarios', 0)
|
314
|
+
|
315
|
+
if (low_count != expected_low or
|
316
|
+
medium_count != expected_medium or
|
317
|
+
high_count != expected_high):
|
318
|
+
raise ValueError(
|
319
|
+
f'Complexity counts mismatch: expected L:{expected_low} M:{expected_medium} H:{expected_high}, '
|
320
|
+
f'actual L:{low_count} M:{medium_count} H:{high_count}'
|
321
|
+
)
|
322
|
+
|
323
|
+
return values
|
324
|
+
|
325
|
+
|
326
|
+
# Business Interface Schemas
|
327
|
+
|
328
|
+
class ExecutiveSummary(BaseSchema):
|
329
|
+
"""Executive-ready summary for business stakeholders."""
|
330
|
+
|
331
|
+
# High-level metrics
|
332
|
+
total_annual_opportunity: NonNegativeFloat = Field(..., ge=0)
|
333
|
+
confidence_level: float = Field(..., ge=70, le=100) # Must be high confidence for exec presentation
|
334
|
+
implementation_timeline_months: PositiveFloat = Field(..., gt=0, le=24) # Reasonable timeline
|
335
|
+
|
336
|
+
# Business impact
|
337
|
+
roi_percentage: PositiveFloat = Field(..., gt=0)
|
338
|
+
payback_period_months: PositiveFloat = Field(..., gt=0, le=60) # Max 5 years
|
339
|
+
risk_assessment: RiskLevel = Field(...)
|
340
|
+
|
341
|
+
# Quick wins
|
342
|
+
quick_wins_count: int = Field(..., ge=0, le=50)
|
343
|
+
quick_wins_annual_value: NonNegativeFloat = Field(..., ge=0)
|
344
|
+
|
345
|
+
# Implementation priority
|
346
|
+
priority_scenarios: List[str] = Field(..., max_items=10) # Top priorities only
|
347
|
+
recommended_next_steps: List[str] = Field(..., min_items=1, max_items=5)
|
348
|
+
|
349
|
+
# Validation status
|
350
|
+
data_validation_status: ValidationStatus = Field(...)
|
351
|
+
last_validated: datetime = Field(...)
|
352
|
+
|
353
|
+
@validator('roi_percentage')
|
354
|
+
def validate_reasonable_roi(cls, v):
|
355
|
+
"""Ensure ROI is reasonable for executive presentation."""
|
356
|
+
if v > 1000: # 1000% ROI
|
357
|
+
raise ValueError('ROI over 1000% requires additional validation')
|
358
|
+
return v
|
359
|
+
|
360
|
+
|
361
|
+
# Technical Validation Schemas
|
362
|
+
|
363
|
+
class MCPValidationResult(BaseSchema):
|
364
|
+
"""MCP cross-validation result with strict tolerance checking."""
|
365
|
+
|
366
|
+
validation_timestamp: datetime = Field(...)
|
367
|
+
notebook_value: NonNegativeFloat = Field(..., ge=0)
|
368
|
+
mcp_value: NonNegativeFloat = Field(..., ge=0)
|
369
|
+
variance_amount: NonNegativeFloat = Field(..., ge=0)
|
370
|
+
variance_percent: float = Field(..., ge=0)
|
371
|
+
tolerance_threshold: PositiveFloat = Field(..., gt=0, le=10) # Max 10% tolerance
|
372
|
+
|
373
|
+
validation_status: ValidationStatus = Field(...)
|
374
|
+
validation_message: str = Field(..., min_length=1)
|
375
|
+
|
376
|
+
# Technical details
|
377
|
+
mcp_source: str = Field(..., min_length=1)
|
378
|
+
response_time_seconds: Optional[PositiveFloat] = Field(None, le=300) # 5 minute timeout
|
379
|
+
|
380
|
+
@validator('variance_percent')
|
381
|
+
def calculate_variance_percent(cls, v, values):
|
382
|
+
"""Calculate and validate variance percentage."""
|
383
|
+
if 'notebook_value' in values and 'mcp_value' in values:
|
384
|
+
notebook_val = values['notebook_value']
|
385
|
+
mcp_val = values['mcp_value']
|
386
|
+
|
387
|
+
if notebook_val > 0:
|
388
|
+
calculated = abs((notebook_val - mcp_val) / notebook_val) * 100
|
389
|
+
if abs(v - calculated) > 0.01: # Very tight tolerance for variance calculation
|
390
|
+
raise ValueError(f'Variance percent {v}% does not match calculated {calculated:.2f}%')
|
391
|
+
return v
|
392
|
+
|
393
|
+
|
394
|
+
class FunctionalTestResult(BaseSchema):
|
395
|
+
"""Functional testing result for technical validation."""
|
396
|
+
|
397
|
+
test_name: str = Field(..., min_length=3, max_length=200)
|
398
|
+
test_category: Literal["cost_analysis", "mcp_validation", "export_validation", "performance"] = Field(...)
|
399
|
+
|
400
|
+
# Test execution
|
401
|
+
execution_timestamp: datetime = Field(...)
|
402
|
+
execution_time_seconds: PositiveFloat = Field(..., le=300) # 5 minute timeout
|
403
|
+
test_passed: bool = Field(...)
|
404
|
+
|
405
|
+
# Test details
|
406
|
+
expected_value: Optional[Union[str, float, int, bool]] = Field(None)
|
407
|
+
actual_value: Optional[Union[str, float, int, bool]] = Field(None)
|
408
|
+
tolerance_applied: Optional[float] = Field(None, ge=0, le=10)
|
409
|
+
|
410
|
+
# Error handling
|
411
|
+
error_message: Optional[str] = Field(None, max_length=1000)
|
412
|
+
stack_trace: Optional[str] = Field(None, max_length=5000)
|
413
|
+
|
414
|
+
# Performance metrics
|
415
|
+
memory_usage_mb: Optional[PositiveFloat] = Field(None, le=4096) # 4GB max
|
416
|
+
cpu_utilization_percent: Optional[float] = Field(None, ge=0, le=100)
|
417
|
+
|
418
|
+
|
419
|
+
class ComprehensiveTestSuite(BaseSchema):
|
420
|
+
"""Complete test suite results for technical validation."""
|
421
|
+
|
422
|
+
# Test suite metadata
|
423
|
+
suite_id: UUID4 = Field(...)
|
424
|
+
execution_timestamp: datetime = Field(...)
|
425
|
+
total_execution_time_seconds: PositiveFloat = Field(...)
|
426
|
+
|
427
|
+
# Test results
|
428
|
+
total_tests: int = Field(..., ge=1)
|
429
|
+
passed_tests: int = Field(..., ge=0)
|
430
|
+
failed_tests: int = Field(..., ge=0)
|
431
|
+
skipped_tests: int = Field(..., ge=0)
|
432
|
+
|
433
|
+
test_results: List[FunctionalTestResult] = Field(..., min_items=1)
|
434
|
+
|
435
|
+
# Summary metrics
|
436
|
+
pass_rate_percent: float = Field(..., ge=0, le=100)
|
437
|
+
performance_target_met: bool = Field(...)
|
438
|
+
mcp_validation_success: bool = Field(...)
|
439
|
+
|
440
|
+
# Quality gates
|
441
|
+
meets_production_criteria: bool = Field(...)
|
442
|
+
quality_score: float = Field(..., ge=0, le=100)
|
443
|
+
|
444
|
+
@validator('passed_tests')
|
445
|
+
def validate_test_counts(cls, v, values):
|
446
|
+
"""Ensure test counts are consistent."""
|
447
|
+
if 'failed_tests' in values and 'skipped_tests' in values and 'total_tests' in values:
|
448
|
+
calculated_total = v + values['failed_tests'] + values['skipped_tests']
|
449
|
+
if calculated_total != values['total_tests']:
|
450
|
+
raise ValueError(f'Test counts inconsistent: {calculated_total} ≠ {values["total_tests"]}')
|
451
|
+
return v
|
452
|
+
|
453
|
+
@validator('pass_rate_percent')
|
454
|
+
def calculate_pass_rate(cls, v, values):
|
455
|
+
"""Calculate and validate pass rate."""
|
456
|
+
if 'passed_tests' in values and 'total_tests' in values:
|
457
|
+
total = values['total_tests']
|
458
|
+
if total > 0:
|
459
|
+
calculated = (values['passed_tests'] / total) * 100
|
460
|
+
if abs(v - calculated) > 0.01:
|
461
|
+
raise ValueError(f'Pass rate {v}% inconsistent with calculated {calculated:.2f}%')
|
462
|
+
return v
|
463
|
+
|
464
|
+
|
465
|
+
# Export and Integration Schemas
|
466
|
+
|
467
|
+
class ExportMetadata(BaseSchema):
|
468
|
+
"""Metadata for exported reports."""
|
469
|
+
|
470
|
+
export_timestamp: datetime = Field(...)
|
471
|
+
export_format: ExportFormat = Field(...)
|
472
|
+
file_path: str = Field(..., min_length=1, max_length=500)
|
473
|
+
file_size_bytes: int = Field(..., ge=0)
|
474
|
+
|
475
|
+
# Content metadata
|
476
|
+
record_count: int = Field(..., ge=0)
|
477
|
+
schema_version: str = Field(..., pattern=r'^\d+\.\d+\.\d+$') # Semantic versioning
|
478
|
+
|
479
|
+
# Validation
|
480
|
+
export_validated: bool = Field(...)
|
481
|
+
validation_errors: List[str] = Field(default_factory=list)
|
482
|
+
|
483
|
+
@validator('file_path')
|
484
|
+
def validate_file_path(cls, v):
|
485
|
+
"""Validate file path format."""
|
486
|
+
# Basic path validation
|
487
|
+
if not re.match(r'^[/\w\-\.\s]+\.(json|csv|html|pdf|md)$', v):
|
488
|
+
raise ValueError('Invalid file path format')
|
489
|
+
return v
|
490
|
+
|
491
|
+
|
492
|
+
# Utility Functions for Schema Validation
|
493
|
+
|
494
|
+
def validate_cost_optimization_result(data: Dict[str, Any]) -> CostOptimizationResult:
|
495
|
+
"""
|
496
|
+
Validate and create CostOptimizationResult from raw data.
|
497
|
+
|
498
|
+
Args:
|
499
|
+
data: Raw data dictionary
|
500
|
+
|
501
|
+
Returns:
|
502
|
+
Validated CostOptimizationResult instance
|
503
|
+
|
504
|
+
Raises:
|
505
|
+
ValidationError: If data doesn't meet schema requirements
|
506
|
+
"""
|
507
|
+
return CostOptimizationResult(**data)
|
508
|
+
|
509
|
+
|
510
|
+
def create_executive_summary(optimization_result: CostOptimizationResult) -> ExecutiveSummary:
|
511
|
+
"""
|
512
|
+
Create executive summary from optimization result.
|
513
|
+
|
514
|
+
Args:
|
515
|
+
optimization_result: Validated optimization result
|
516
|
+
|
517
|
+
Returns:
|
518
|
+
ExecutiveSummary instance
|
519
|
+
"""
|
520
|
+
# Calculate quick wins (Low complexity, Low-Medium risk)
|
521
|
+
quick_wins = [
|
522
|
+
scenario for scenario in optimization_result.optimization_scenarios
|
523
|
+
if (scenario.complexity == ComplexityLevel.LOW and
|
524
|
+
scenario.risk_level in [RiskLevel.LOW, RiskLevel.MEDIUM])
|
525
|
+
]
|
526
|
+
|
527
|
+
quick_wins_value = sum(scenario.annual_savings for scenario in quick_wins)
|
528
|
+
|
529
|
+
# Calculate implementation timeline (weighted by complexity)
|
530
|
+
complexity_weights = {ComplexityLevel.LOW: 1, ComplexityLevel.MEDIUM: 3, ComplexityLevel.HIGH: 6}
|
531
|
+
total_weighted_hours = sum(
|
532
|
+
scenario.estimated_hours * complexity_weights[scenario.complexity]
|
533
|
+
for scenario in optimization_result.optimization_scenarios
|
534
|
+
)
|
535
|
+
timeline_months = max(1, total_weighted_hours / 160) # Assuming 160 hours/month
|
536
|
+
|
537
|
+
# ROI calculation
|
538
|
+
annual_savings = optimization_result.total_potential_annual_savings
|
539
|
+
implementation_cost = sum(scenario.implementation_cost for scenario in optimization_result.optimization_scenarios)
|
540
|
+
roi_percentage = (annual_savings / max(implementation_cost, 1)) * 100
|
541
|
+
|
542
|
+
return ExecutiveSummary(
|
543
|
+
total_annual_opportunity=annual_savings,
|
544
|
+
confidence_level=optimization_result.accuracy_confidence or 85.0,
|
545
|
+
implementation_timeline_months=timeline_months,
|
546
|
+
roi_percentage=roi_percentage,
|
547
|
+
payback_period_months=max(0.1, implementation_cost / (annual_savings / 12)) if annual_savings > 0 else 12.0,
|
548
|
+
risk_assessment=RiskLevel.MEDIUM, # Conservative default
|
549
|
+
quick_wins_count=len(quick_wins),
|
550
|
+
quick_wins_annual_value=quick_wins_value,
|
551
|
+
priority_scenarios=[
|
552
|
+
scenario.scenario_name for scenario in
|
553
|
+
sorted(optimization_result.optimization_scenarios,
|
554
|
+
key=lambda x: x.annual_savings, reverse=True)[:5]
|
555
|
+
],
|
556
|
+
recommended_next_steps=[
|
557
|
+
"Review and approve quick-win scenarios",
|
558
|
+
"Establish implementation team and timeline",
|
559
|
+
"Set up monitoring for cost optimization KPIs",
|
560
|
+
"Schedule quarterly optimization reviews"
|
561
|
+
],
|
562
|
+
data_validation_status=optimization_result.mcp_validation_status,
|
563
|
+
last_validated=datetime.now()
|
564
|
+
)
|
565
|
+
|
566
|
+
|
567
|
+
# Export all schemas and utilities
|
568
|
+
__all__ = [
|
569
|
+
# Enums
|
570
|
+
'ComplexityLevel', 'RiskLevel', 'OptimizationCategory', 'ValidationStatus', 'ExportFormat',
|
571
|
+
|
572
|
+
# Core schemas
|
573
|
+
'CostBreakdown', 'OptimizationScenario', 'CostOptimizationResult',
|
574
|
+
|
575
|
+
# Business schemas
|
576
|
+
'ExecutiveSummary',
|
577
|
+
|
578
|
+
# Technical validation schemas
|
579
|
+
'MCPValidationResult', 'FunctionalTestResult', 'ComprehensiveTestSuite',
|
580
|
+
|
581
|
+
# Export schemas
|
582
|
+
'ExportMetadata',
|
583
|
+
|
584
|
+
# Utility functions
|
585
|
+
'validate_cost_optimization_result', 'create_executive_summary',
|
586
|
+
|
587
|
+
# Base schema
|
588
|
+
'BaseSchema'
|
589
|
+
]
|
File without changes
|
File without changes
|