149 lines
4.5 KiB
Python
149 lines
4.5 KiB
Python
"""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]]
|