from __future__ import annotations from datetime import datetime from typing import Any, List, Optional from pydantic import BaseModel, Field class MetricCreate(BaseModel): """Create a metric definition with business and technical metadata.""" metric_code: str = Field(..., description="Internal metric code, unique.") metric_name: str = Field(..., description="Display name.") metric_aliases: Optional[List[str]] = Field(None, description="Optional alias list.") biz_domain: str = Field(..., description="Business domain identifier.") biz_desc: Optional[str] = Field(None, description="Business definition.") chat_turn_id: Optional[int] = Field(None, description="Source chat turn ID.") tech_desc: Optional[str] = Field(None, description="Technical definition.") formula_expr: Optional[str] = Field(None, description="Formula expression text.") base_sql: str = Field(..., description="Canonical SQL used to compute the metric.") time_grain: str = Field(..., description="DAY/HOUR/WEEK/MONTH etc.") dim_binding: List[str] = Field(..., description="Dimension columns bound to the metric.") update_strategy: str = Field(..., description="FULL/INCR/REALTIME.") schedule_id: Optional[int] = Field(None, description="Linked schedule id if any.") schedule_type: Optional[int] = Field(None, description="Scheduler type identifier.") is_active: bool = Field(True, description="Whether the metric is enabled.") created_by: Optional[int] = Field(None, description="Creator user id.") updated_by: Optional[int] = Field(None, description="Updater user id.") class MetricUpdate(BaseModel): """Partial update for an existing metric definition.""" metric_name: Optional[str] = None metric_aliases: Optional[List[str]] = None biz_domain: Optional[str] = None biz_desc: Optional[str] = None tech_desc: Optional[str] = None formula_expr: Optional[str] = None base_sql: Optional[str] = None time_grain: Optional[str] = None dim_binding: Optional[List[str]] = None update_strategy: Optional[str] = None schedule_id: Optional[int] = None schedule_type: Optional[int] = None is_active: Optional[bool] = None updated_by: Optional[int] = None class MetricScheduleCreate(BaseModel): """Create a cron-based schedule for a metric.""" metric_id: int cron_expr: str enabled: bool = True priority: int = 10 backfill_allowed: bool = True max_runtime_sec: Optional[int] = None retry_times: int = 0 owner_team: Optional[str] = None owner_user_id: Optional[int] = None class MetricScheduleUpdate(BaseModel): """Update fields of an existing metric schedule.""" cron_expr: Optional[str] = None enabled: Optional[bool] = None priority: Optional[int] = None backfill_allowed: Optional[bool] = None max_runtime_sec: Optional[int] = None retry_times: Optional[int] = None owner_team: Optional[str] = None owner_user_id: Optional[int] = None class MetricRunTrigger(BaseModel): """Trigger a metric run, optionally linking to a chat turn or schedule.""" metric_id: int schedule_id: Optional[int] = None source_turn_id: Optional[int] = None data_time_from: Optional[datetime] = None data_time_to: Optional[datetime] = None metric_version: Optional[int] = None base_sql_snapshot: Optional[str] = None triggered_by: str = Field("API", description="SCHEDULER/MANUAL/API/QA_TURN") triggered_at: Optional[datetime] = None class MetricResultItem(BaseModel): """Single metric result row to be persisted.""" stat_time: datetime metric_value: float metric_version: Optional[int] = None extra_dims: Optional[dict[str, Any]] = None load_time: Optional[datetime] = None data_version: Optional[int] = None class MetricResultsWriteRequest(BaseModel): """Batch write request for metric results.""" metric_id: int results: List[MetricResultItem]