from __future__ import annotations from datetime import datetime, timezone from uuid import uuid4 from sqlalchemy import JSON, DateTime, Integer, String from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column def _utcnow() -> datetime: return datetime.now(timezone.utc) class AWBase(DeclarativeBase): pass class AWSalesForecast(AWBase): """Persisted AW sales forecast runs.""" __tablename__ = "aw_sales_forecasts" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, index=True) horizon_days: Mapped[int] = mapped_column(Integer) point_count: Mapped[int] = mapped_column(Integer) trigger_source: Mapped[str] = mapped_column(String(64), index=True) trace_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) span_id: Mapped[str | None] = mapped_column(String(16), nullable=True) payload: Mapped[list[dict]] = mapped_column(JSON, default=list) class AWRepScore(AWBase): """Persisted AW sales rep performance scoring runs.""" __tablename__ = "aw_rep_scores" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) computed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, index=True) rep_count: Mapped[int] = mapped_column(Integer) trigger_source: Mapped[str] = mapped_column(String(64), index=True) trace_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) span_id: Mapped[str | None] = mapped_column(String(16), nullable=True) payload: Mapped[list[dict]] = mapped_column(JSON, default=list) class AWProductDemand(AWBase): """Persisted AW product demand scoring runs.""" __tablename__ = "aw_product_demand" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) computed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, index=True) product_count: Mapped[int] = mapped_column(Integer) top_n: Mapped[int] = mapped_column(Integer) trigger_source: Mapped[str] = mapped_column(String(64), index=True) trace_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) span_id: Mapped[str | None] = mapped_column(String(16), nullable=True) payload: Mapped[list[dict]] = mapped_column(JSON, default=list) class AWAnomalyRun(AWBase): """Persisted AW revenue anomaly detection runs.""" __tablename__ = "aw_anomaly_runs" id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid4())) detected_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, index=True) anomaly_count: Mapped[int] = mapped_column(Integer) series_days: Mapped[int] = mapped_column(Integer) window_days: Mapped[int] = mapped_column(Integer) threshold_sigma: Mapped[float] = mapped_column(default=2.0) trigger_source: Mapped[str] = mapped_column(String(64), index=True) trace_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True) span_id: Mapped[str | None] = mapped_column(String(16), nullable=True) # Full annotated series (date, revenue, rolling_mean, lower_band, upper_band, is_anomaly, z_score) payload: Mapped[list[dict]] = mapped_column(JSON, default=list)