Files
sejeteralo/backend/app/schemas/schemas.py
Yvv b30e54a8f7 Initial commit: SejeteralO water tarification platform
Full-stack app for participatory water pricing using Bezier curves.
- Backend: FastAPI + SQLAlchemy + SQLite with JWT auth
- Frontend: Nuxt 4 + TypeScript with interactive SVG editor
- Math engine: cubic Bezier tarification with Cardano solver
- Admin: commune management, household import, vote monitoring, CMS
- Citizen: interactive curve editor, vote submission
- Docker-compose deployment ready

Includes fixes for:
- Impact table snake_case/camelCase property mismatch
- CMS content backend API + frontend editor (was stub)
- Admin route protection middleware
- Public content display on commune page
- Vote confirmation page link fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:26:02 +01:00

206 lines
3.6 KiB
Python

"""Pydantic schemas for API request/response validation."""
from datetime import datetime
from pydantic import BaseModel, Field
# ── Auth ──
class AdminLogin(BaseModel):
email: str
password: str
class CitizenVerify(BaseModel):
commune_slug: str
auth_code: str
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
role: str
commune_slug: str | None = None
# ── Commune ──
class CommuneCreate(BaseModel):
name: str
slug: str
description: str = ""
class CommuneUpdate(BaseModel):
name: str | None = None
description: str | None = None
is_active: bool | None = None
class CommuneOut(BaseModel):
id: int
name: str
slug: str
description: str
is_active: bool
created_at: datetime
model_config = {"from_attributes": True}
# ── TariffParams ──
class TariffParamsUpdate(BaseModel):
abop: float | None = None
abos: float | None = None
recettes: float | None = None
pmax: float | None = None
vmax: float | None = None
class TariffParamsOut(BaseModel):
abop: float
abos: float
recettes: float
pmax: float
vmax: float
model_config = {"from_attributes": True}
# ── Household ──
class HouseholdOut(BaseModel):
id: int
identifier: str
status: str
volume_m3: float
price_paid_eur: float
auth_code: str
has_voted: bool
model_config = {"from_attributes": True}
class HouseholdStats(BaseModel):
total: int
rs_count: int
rp_count: int
pro_count: int
total_volume: float
avg_volume: float
median_volume: float
voted_count: int
class ImportPreview(BaseModel):
valid_rows: int
errors: list[str]
sample: list[dict]
class ImportResult(BaseModel):
created: int
errors: list[str]
# ── Tariff Compute ──
class TariffComputeRequest(BaseModel):
commune_slug: str
vinf: float = Field(ge=0)
a: float = Field(ge=0, le=1)
b: float = Field(ge=0, le=1)
c: float = Field(ge=0, le=1)
d: float = Field(ge=0, le=1)
e: float = Field(ge=0, le=1)
class ImpactRowOut(BaseModel):
volume: float
old_price: float
new_price_rp: float
new_price_rs: float
class TariffComputeResponse(BaseModel):
p0: float
curve_volumes: list[float]
curve_prices_m3: list[float]
curve_bills_rp: list[float]
curve_bills_rs: list[float]
impacts: list[ImpactRowOut]
# ── Vote ──
class VoteCreate(BaseModel):
vinf: float = Field(ge=0)
a: float = Field(ge=0, le=1)
b: float = Field(ge=0, le=1)
c: float = Field(ge=0, le=1)
d: float = Field(ge=0, le=1)
e: float = Field(ge=0, le=1)
class VoteOut(BaseModel):
id: int
household_id: int
vinf: float
a: float
b: float
c: float
d: float
e: float
computed_p0: float | None
submitted_at: datetime
is_active: bool
model_config = {"from_attributes": True}
class MedianOut(BaseModel):
vinf: float
a: float
b: float
c: float
d: float
e: float
computed_p0: float
vote_count: int
# ── Admin User ──
class AdminUserCreate(BaseModel):
email: str
password: str
full_name: str = ""
role: str = "commune_admin"
commune_slugs: list[str] = []
class AdminUserOut(BaseModel):
id: int
email: str
full_name: str
role: str
model_config = {"from_attributes": True}
# ── Content ──
class ContentUpdate(BaseModel):
title: str
body_markdown: str
class ContentOut(BaseModel):
slug: str
title: str
body_markdown: str
updated_at: datetime
model_config = {"from_attributes": True}