99 lines
2.7 KiB
Python
99 lines
2.7 KiB
Python
"""Typed contracts for bounded historical telemetry trends."""
|
|
|
|
from datetime import datetime
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
|
|
|
|
TrendScopeType = Literal["fleet", "site", "room", "miner"]
|
|
TrendRange = Literal["6h", "24h", "7d"]
|
|
TrendMetric = Literal["hashrate", "power"]
|
|
TrendAttribution = Literal["current_placement_cohort", "miner_identity", "fleet"]
|
|
|
|
|
|
class MonitoringTrendQuery(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
scope: TrendScopeType = "fleet"
|
|
scope_id: UUID | None = None
|
|
time_range: TrendRange = Field(default="24h", alias="range")
|
|
|
|
@model_validator(mode="after")
|
|
def validate_scope_identity(self) -> "MonitoringTrendQuery":
|
|
if self.scope == "fleet" and self.scope_id is not None:
|
|
raise ValueError("scope_id is not allowed for fleet scope")
|
|
if self.scope != "fleet" and self.scope_id is None:
|
|
raise ValueError("scope_id is required for this scope")
|
|
return self
|
|
|
|
|
|
class MonitoringScope(BaseModel):
|
|
type: TrendScopeType
|
|
id: UUID | None = None
|
|
label: str
|
|
|
|
|
|
class MonitoringSiteOption(BaseModel):
|
|
id: UUID
|
|
name: str
|
|
|
|
|
|
class MonitoringRoomOption(BaseModel):
|
|
id: UUID
|
|
site_id: UUID
|
|
name: str
|
|
|
|
|
|
class MonitoringAvailableScopes(BaseModel):
|
|
fleet: bool = True
|
|
sites: list[MonitoringSiteOption] = Field(default_factory=list)
|
|
rooms: list[MonitoringRoomOption] = Field(default_factory=list)
|
|
|
|
|
|
class MonitoringTrendPoint(BaseModel):
|
|
bucket_start: datetime
|
|
bucket_end: datetime
|
|
value: float | None = None
|
|
observation_count: int
|
|
metric_sample_count: int
|
|
observed_miner_count: int
|
|
contributing_miner_count: int
|
|
scoped_miner_count: int
|
|
incomplete: bool = False
|
|
|
|
|
|
class MonitoringTrendSeries(BaseModel):
|
|
metric: TrendMetric
|
|
unit: Literal["TH/s", "W"]
|
|
label: str
|
|
points: list[MonitoringTrendPoint]
|
|
|
|
|
|
class MonitoringTrendData(BaseModel):
|
|
scope: MonitoringScope
|
|
series: list[MonitoringTrendSeries]
|
|
available_scopes: MonitoringAvailableScopes
|
|
|
|
|
|
class MonitoringTrendMeta(BaseModel):
|
|
generated_at: datetime
|
|
latest_observed_at: datetime | None = None
|
|
source: Literal["raw_snapshots"] = "raw_snapshots"
|
|
attribution: TrendAttribution
|
|
time_range: TrendRange = Field(alias="range")
|
|
bucket_seconds: int
|
|
maximum_points: int = 200
|
|
completeness: Literal["not_guaranteed"] = "not_guaranteed"
|
|
current_bucket_incomplete: bool
|
|
database_query_count: int = Field(ge=1, le=2)
|
|
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class MonitoringTrendResponse(BaseModel):
|
|
data: MonitoringTrendData
|
|
meta: MonitoringTrendMeta
|
|
error: None = None
|