"""Pydantic schemas for the EnergyContract CRUD + versioning API (M6-T04). Schema hierarchy ---------------- ContractVersionResponse — single version row (id, dates, values, created_at) ContractResponse — contract head (id, name, kind, active, currency, timestamps) ContractDetailResponse — contract head + embedded versions list (for GET{id}) ContractListResponse — paginated list of ContractResponse items ContractCreate — POST /api/energy/contracts body ContractPatch — PATCH /api/energy/contracts/{id} body (all fields optional) VersionCreate — POST /api/energy/contracts/{id}/versions body ProfilesResponse — GET /api/energy/profiles response """ from __future__ import annotations from datetime import datetime from typing import Any from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # Version schemas # --------------------------------------------------------------------------- class ContractVersionResponse(BaseModel): """Response schema for a single EnergyContractVersion row.""" id: int effective_from: datetime effective_to: datetime | None values: dict[str, Any] created_at: datetime model_config = {"from_attributes": True} # --------------------------------------------------------------------------- # Contract schemas # --------------------------------------------------------------------------- class ContractResponse(BaseModel): """Response schema for a single EnergyContract (without embedded versions). Used for list responses where embedding all versions would be expensive. """ id: int name: str kind: str active: bool currency: str created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class ContractDetailResponse(BaseModel): """Response schema for a single EnergyContract with full version history. Returned by GET /api/energy/contracts/{id} and by successful POST / PATCH operations where the caller needs to see all version data. """ id: int name: str kind: str active: bool currency: str created_at: datetime updated_at: datetime versions: list[ContractVersionResponse] model_config = {"from_attributes": True} class ContractListResponse(BaseModel): """Response schema for GET /api/energy/contracts.""" items: list[ContractResponse] total: int # --------------------------------------------------------------------------- # Request schemas # --------------------------------------------------------------------------- class ContractCreate(BaseModel): """Request body for POST /api/energy/contracts. ``effective_from`` defaults to the current UTC time if not provided, giving the first version an open-ended start from "now". ``kind`` is validated at the application layer against the profile registry; clients should send ``"manual"`` or ``"tibber"``. """ name: str = Field(..., min_length=1, max_length=255) kind: str = Field(..., min_length=1, max_length=32) currency: str = Field(default="EUR", min_length=1, max_length=8) values: dict[str, Any] effective_from: datetime | None = Field( default=None, description=( "UTC datetime from which the first pricing version is effective. " "Defaults to the current UTC time when omitted." ), ) class ContractPatch(BaseModel): """Request body for PATCH /api/energy/contracts/{id}. All fields are optional. Sending ``active=true`` activates this contract (deactivating all others); ``active=false`` deactivates it without affecting other contracts. """ name: str | None = Field(default=None, min_length=1, max_length=255) active: bool | None = None class VersionCreate(BaseModel): """Request body for POST /api/energy/contracts/{id}/versions.""" effective_from: datetime values: dict[str, Any] # --------------------------------------------------------------------------- # Profile response schemas # --------------------------------------------------------------------------- class ProfilesResponse(BaseModel): """Response schema for GET /api/energy/profiles. ``profiles`` is a list of raw profile dicts as produced by ``list_profiles()`` (Pydantic model dumps). Each entry contains at minimum ``kind`` and ``label``; the full nested structure allows the front-end to render a type-appropriate form for each pricing profile. """ profiles: list[dict[str, Any]]