"""API schemas (Pydantic) for the tips engine. The route used to return `list[dict]` — no shape guarantee at all. `Tip.data` stays a free-form bag on purpose (§ tips_service.py docstring: it is interpolation context for the frontend, and every rule shapes it differently), but everything around it is now a real, typed contract. """ from datetime import date from pydantic import BaseModel from app.services.tips_service import Tip # Every value any rule currently puts in `Tip.data` is a JSON scalar (str, # int, float, bool) or None — never a nested dict/list. Typing it this way # (rather than `dict[str, Any]`) still catches a rule accidentally stuffing # something un-renderable into `data`. TipDataValue = str | int | float | bool | None class TipResponse(BaseModel): id: str severity: str message_key: str data: dict[str, TipDataValue] anchor_date: date | None anchor_range: tuple[date, date] | None @classmethod def from_domain(cls, tip: Tip) -> "TipResponse": return cls( id=tip.id, severity=tip.severity, message_key=tip.message_key, data=tip.data, anchor_date=tip.anchor_date, anchor_range=tip.anchor_range, ) class TipDismissalCreate(BaseModel): tip_id: str