"""Audit and usage-ledger ports. ``AuditPort`` writes the immutable trail; ``UsageLedgerPort`` is the billing source of truth. Both are append-only by contract. """ from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime from typing import Protocol, runtime_checkable from app.domain.ai_control.models import RateUnit from app.domain.audit.models import AuditEvent @dataclass(slots=True, kw_only=True) class UsageRecord: organization_id: str unit: RateUnit quantity: float unit_price: float = 0.0 internal_cost_estimate: float = 0.0 total_price: float = 0.0 project_id: str | None = None user_id: str | None = None employee_id: str | None = None run_id: str | None = None metadata_json: dict = field(default_factory=dict) @dataclass(slots=True, kw_only=True) class UsageAggregate: unit: RateUnit quantity: float = 0.0 total_price: float = 0.0 internal_cost_estimate: float = 0.0 @dataclass(slots=True, kw_only=True) class UsageBreakdown: """Grouped totals for the billing dashboard.""" by_unit: list[UsageAggregate] = field(default_factory=list) by_employee: dict[str, float] = field(default_factory=dict) by_project: dict[str, float] = field(default_factory=dict) total_price: float = 0.0 total_cost: float = 0.0 @runtime_checkable class AuditPort(Protocol): async def record(self, *, event: AuditEvent) -> AuditEvent: ... async def list_for_organization( self, *, organization_id: str, limit: int = 100, offset: int = 0 ) -> list[AuditEvent]: ... @runtime_checkable class UsageLedgerPort(Protocol): async def record(self, *, usage: UsageRecord) -> None: """Append one usage event. Never raises for business reasons — a failed ledger write is an infrastructure error and must surface loudly.""" ... async def breakdown( self, *, organization_id: str, since: datetime | None = None, until: datetime | None = None, ) -> UsageBreakdown: ...