Source code for unsprawl.core.schemas

"""unsprawl.core.schemas.

This module defines the Universal Modular Design (UMD) schema layer for Unsprawl.

These Pydantic models are the *language* of the deterministic core engine:
- They are global (no country/city-specific assumptions).
- They are stable contracts between messy local datasets (adapters) and the core simulation.

Anti-circular protocol
----------------------
`unsprawl.core` MUST NOT import from adapters/providers/loaders.
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, Field, field_validator

LatLon = tuple[float, float]


[docs] class Entity(BaseModel): """Universal base class for the Unsprawl simulation. Everything in the simulation (static or moving) is an Entity. Notes ----- Coordinate ordering is **strictly (lat, lon)** across the entire platform. Adapters must normalize any source data into this convention at the boundary. """ id: str location: LatLon # (lat, lon)
[docs] @field_validator("location") @classmethod def _validate_location_lat_lon(cls, value: LatLon) -> LatLon: """Validate that location is (lat, lon) in sane numeric ranges. This validator is not meant to be geo-precise; it is a defensive check that: - enforces the ordering contract at runtime - catches common adapter mistakes early (swapped lon/lat) """ lat, lon = value # Range checks (WGS84-ish). Keep permissive but useful. if not (-90.0 <= float(lat) <= 90.0): raise ValueError("location[0] must be latitude in [-90, 90]") if not (-180.0 <= float(lon) <= 180.0): raise ValueError("location[1] must be longitude in [-180, 180]") return float(lat), float(lon)
[docs] class Asset(Entity): """A static economic unit (Building, Park, Transit Station). This replaces the legacy Singapore-specific concept of "HDB Flat" with a generic container that can represent any asset class across any region. The physics engine treats `local_metadata` as an opaque payload. """ asset_type: Literal["residential", "commercial", "transport"] # Physics Properties floor_area_sqm: float lease_remaining_years: float # Keep float (may include month fractions) # Financial Properties valuation_currency: str = "USD" predicted_valuation: float = 0.0 # Waste-bin for local context (UI uses it; physics ignores it) local_metadata: dict[str, Any] = Field(default_factory=dict)
[docs] class Agent(Entity): """A dynamic actor (Commuter, Bus, Car). Agents flow through the city graph / continuous space depending on the simulation backend. """ velocity: tuple[float, float] = (0.0, 0.0) goal: LatLon state: Literal["idle", "moving", "stuck"] = "idle"