Files
mockupAWS/src/services/cost_calculator.py
Luca Sacchi Ricciardi b18728f0f9 feat(api): implement complete API layer with services and endpoints
Complete API implementation (BE-006 to BE-010):

BE-006: API Dependencies & Configuration
- Add core/config.py with Settings and environment variables
- Add core/exceptions.py with AppException hierarchy
- Add api/deps.py with get_db() and get_running_scenario() dependencies
- Add pydantic-settings dependency

BE-007: Services Layer
- Add services/pii_detector.py: PIIDetector with email/SSN/credit card patterns
- Add services/cost_calculator.py: AWS cost calculation (SQS, Lambda, Bedrock)
- Add services/ingest_service.py: Log processing with hash, PII detection, metrics

BE-008: Scenarios API Endpoints
- POST /api/v1/scenarios - Create scenario
- GET /api/v1/scenarios - List with filters and pagination
- GET /api/v1/scenarios/{id} - Get single scenario
- PUT /api/v1/scenarios/{id} - Update scenario
- DELETE /api/v1/scenarios/{id} - Delete scenario
- POST /api/v1/scenarios/{id}/start - Start (draft->running)
- POST /api/v1/scenarios/{id}/stop - Stop (running->completed)
- POST /api/v1/scenarios/{id}/archive - Archive (completed->archived)

BE-009: Ingest API
- POST /ingest with X-Scenario-ID header validation
- Depends on get_running_scenario() for status check
- Returns LogResponse with processed metrics
- POST /flush for backward compatibility

BE-010: Metrics API
- GET /api/v1/scenarios/{id}/metrics - Full metrics endpoint
- Aggregates data from scenario_logs
- Calculates costs using CostCalculator
- Returns cost breakdown (SQS/Lambda/Bedrock)
- Returns timeseries data grouped by hour

Refactored main.py:
- Simplified to use api_router
- Added exception handlers
- Added health check endpoint

All endpoints tested and working.

Tasks: BE-006, BE-007, BE-008, BE-009, BE-010 complete
2026-04-07 14:35:50 +02:00

87 lines
2.9 KiB
Python

"""Cost calculation service."""
from decimal import Decimal
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from datetime import date
from src.repositories.base import BaseRepository
from src.models.aws_pricing import AwsPricing
class CostCalculator:
"""Service for calculating AWS costs."""
def __init__(self):
self.pricing_repo = BaseRepository(AwsPricing)
async def get_pricing(
self, db: AsyncSession, service: str, region: str, tier: str
) -> Optional[Decimal]:
"""Get active pricing for a service/region/tier."""
result = await db.execute(
select(AwsPricing).where(
and_(
AwsPricing.service == service,
AwsPricing.region == region,
AwsPricing.tier == tier,
AwsPricing.is_active == True,
)
)
)
pricing = result.scalar_one_or_none()
return pricing.price_per_unit if pricing else None
async def calculate_sqs_cost(
self, db: AsyncSession, blocks: int, region: str
) -> Decimal:
"""Calculate SQS cost."""
price = await self.get_pricing(db, "sqs", region, "standard")
if price is None:
price = Decimal("0.40")
# Formula: blocks * price_per_million / 1,000,000
return Decimal(blocks) * price / Decimal("1000000")
async def calculate_lambda_cost(
self, db: AsyncSession, invocations: int, gb_seconds: float, region: str
) -> Decimal:
"""Calculate Lambda cost (requests + compute)."""
request_price = await self.get_pricing(db, "lambda", region, "x86_request")
compute_price = await self.get_pricing(db, "lambda", region, "x86_compute")
if request_price is None:
request_price = Decimal("0.20")
if compute_price is None:
compute_price = Decimal("0.0000166667")
request_cost = Decimal(invocations) * request_price / Decimal("1000000")
compute_cost = Decimal(str(gb_seconds)) * compute_price
return request_cost + compute_cost
async def calculate_bedrock_cost(
self,
db: AsyncSession,
input_tokens: int,
output_tokens: int,
region: str,
model: str = "claude_3_sonnet",
) -> Decimal:
"""Calculate Bedrock LLM cost."""
input_price = await self.get_pricing(db, "bedrock", region, f"{model}_input")
output_price = await self.get_pricing(db, "bedrock", region, f"{model}_output")
if input_price is None:
input_price = Decimal("0.003")
if output_price is None:
output_price = Decimal("0.015")
input_cost = Decimal(input_tokens) * input_price / Decimal("1000")
output_cost = Decimal(output_tokens) * output_price / Decimal("1000")
return input_cost + output_cost
# Singleton instance
cost_calculator = CostCalculator()