truthound-dashboard 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -18
  162. truthound_dashboard-1.4.0.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BCA8H1hO.js +0 -574
  164. truthound_dashboard/static/assets/index-BNsSQ2fN.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CsJWCRx9.js +0 -1
  166. truthound_dashboard-1.3.0.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.0.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,374 @@
1
+ """Pydantic schemas for ML Model Monitoring.
2
+
3
+ Provides schemas for:
4
+ - Model registration and management
5
+ - Performance metrics (latency, throughput, error rates)
6
+ - Data quality metrics (null rates, type violations)
7
+ - Drift detection integration
8
+ - Alert rules and handlers
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from datetime import datetime
14
+ from enum import Enum
15
+ from typing import Any, Literal
16
+
17
+ from pydantic import BaseModel, Field
18
+
19
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
20
+
21
+
22
+ # =============================================================================
23
+ # Enums
24
+ # =============================================================================
25
+
26
+
27
+ class ModelStatus(str, Enum):
28
+ """Model monitoring status."""
29
+
30
+ ACTIVE = "active"
31
+ PAUSED = "paused"
32
+ DEGRADED = "degraded"
33
+ ERROR = "error"
34
+
35
+
36
+ class AlertSeverity(str, Enum):
37
+ """Alert severity levels."""
38
+
39
+ CRITICAL = "critical"
40
+ WARNING = "warning"
41
+ INFO = "info"
42
+
43
+
44
+ class MetricType(str, Enum):
45
+ """Types of metrics collected."""
46
+
47
+ LATENCY = "latency"
48
+ THROUGHPUT = "throughput"
49
+ ERROR_RATE = "error_rate"
50
+ NULL_RATE = "null_rate"
51
+ TYPE_VIOLATION = "type_violation"
52
+ DRIFT_SCORE = "drift_score"
53
+ CUSTOM = "custom"
54
+
55
+
56
+ class AlertRuleType(str, Enum):
57
+ """Types of alert rules."""
58
+
59
+ THRESHOLD = "threshold"
60
+ STATISTICAL = "statistical"
61
+ TREND = "trend"
62
+
63
+
64
+ class AlertHandlerType(str, Enum):
65
+ """Types of alert handlers."""
66
+
67
+ SLACK = "slack"
68
+ WEBHOOK = "webhook"
69
+ EMAIL = "email"
70
+
71
+
72
+ # =============================================================================
73
+ # Model Registration Schemas
74
+ # =============================================================================
75
+
76
+
77
+ class ModelConfigBase(BaseSchema):
78
+ """Configuration for model monitoring."""
79
+
80
+ enable_drift_detection: bool = Field(default=True, description="Enable drift detection")
81
+ enable_quality_metrics: bool = Field(default=True, description="Enable quality metrics")
82
+ enable_performance_metrics: bool = Field(default=True, description="Enable performance metrics")
83
+ sample_rate: float = Field(default=1.0, description="Sampling rate for metrics", ge=0.0, le=1.0)
84
+ drift_threshold: float = Field(default=0.1, description="Threshold for drift detection", ge=0.0, le=1.0)
85
+ drift_window_size: int = Field(default=1000, description="Window size for drift detection", ge=100)
86
+
87
+
88
+ class RegisteredModelBase(BaseSchema):
89
+ """Base schema for registered model."""
90
+
91
+ name: str = Field(..., description="Model name/identifier", min_length=1, max_length=255)
92
+ version: str = Field(default="1.0.0", description="Model version")
93
+ description: str = Field(default="", description="Model description")
94
+ status: ModelStatus = Field(default=ModelStatus.ACTIVE, description="Monitoring status")
95
+ config: ModelConfigBase = Field(default_factory=ModelConfigBase, description="Monitoring config")
96
+ metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
97
+
98
+
99
+ class RegisterModelRequest(RegisteredModelBase):
100
+ """Request to register a model for monitoring."""
101
+
102
+ pass
103
+
104
+
105
+ class UpdateModelRequest(BaseSchema):
106
+ """Request to update model registration."""
107
+
108
+ name: str | None = Field(None, description="Model name")
109
+ version: str | None = Field(None, description="Model version")
110
+ description: str | None = Field(None, description="Model description")
111
+ status: ModelStatus | None = Field(None, description="Monitoring status")
112
+ config: ModelConfigBase | None = Field(None, description="Monitoring config")
113
+ metadata: dict[str, Any] | None = Field(None, description="Additional metadata")
114
+
115
+
116
+ class RegisteredModelResponse(RegisteredModelBase, IDMixin, TimestampMixin):
117
+ """Response for registered model."""
118
+
119
+ prediction_count: int = Field(default=0, description="Total predictions")
120
+ last_prediction_at: datetime | None = Field(None, description="Last prediction timestamp")
121
+ current_drift_score: float | None = Field(None, description="Current drift score")
122
+ health_score: float = Field(default=100.0, description="Model health score (0-100)")
123
+
124
+
125
+ class RegisteredModelListResponse(ListResponseWrapper):
126
+ """List response for registered models."""
127
+
128
+ items: list[RegisteredModelResponse]
129
+
130
+
131
+ # =============================================================================
132
+ # Metrics Schemas
133
+ # =============================================================================
134
+
135
+
136
+ class MetricDataPoint(BaseModel):
137
+ """Single metric data point."""
138
+
139
+ timestamp: datetime = Field(..., description="Measurement timestamp")
140
+ value: float = Field(..., description="Metric value")
141
+ labels: dict[str, str] = Field(default_factory=dict, description="Metric labels")
142
+
143
+
144
+ class MetricSummary(BaseModel):
145
+ """Summary statistics for a metric."""
146
+
147
+ name: str = Field(..., description="Metric name")
148
+ type: MetricType = Field(..., description="Metric type")
149
+ count: int = Field(default=0, description="Number of observations")
150
+ min_value: float | None = Field(None, description="Minimum value")
151
+ max_value: float | None = Field(None, description="Maximum value")
152
+ avg_value: float | None = Field(None, description="Average value")
153
+ p50_value: float | None = Field(None, description="50th percentile")
154
+ p95_value: float | None = Field(None, description="95th percentile")
155
+ p99_value: float | None = Field(None, description="99th percentile")
156
+ last_value: float | None = Field(None, description="Most recent value")
157
+
158
+
159
+ class RecordPredictionRequest(BaseModel):
160
+ """Request to record a model prediction."""
161
+
162
+ features: dict[str, Any] = Field(..., description="Input features")
163
+ prediction: Any = Field(..., description="Model prediction output")
164
+ actual: Any | None = Field(None, description="Actual value (for accuracy tracking)")
165
+ latency_ms: float | None = Field(None, description="Prediction latency in ms", ge=0)
166
+ metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
167
+
168
+
169
+ class RecordPredictionResponse(BaseModel):
170
+ """Response for recorded prediction."""
171
+
172
+ id: str = Field(..., description="Prediction ID")
173
+ model_id: str = Field(..., description="Model ID")
174
+ recorded_at: datetime = Field(..., description="Recording timestamp")
175
+
176
+
177
+ class MetricsResponse(BaseModel):
178
+ """Response containing model metrics."""
179
+
180
+ model_id: str = Field(..., description="Model ID")
181
+ model_name: str = Field(..., description="Model name")
182
+ time_range_hours: int = Field(default=24, description="Time range for metrics")
183
+ metrics: list[MetricSummary] = Field(default_factory=list, description="Metric summaries")
184
+ data_points: dict[str, list[MetricDataPoint]] = Field(
185
+ default_factory=dict,
186
+ description="Time series data points by metric name",
187
+ )
188
+
189
+
190
+ # =============================================================================
191
+ # Alert Rule Schemas
192
+ # =============================================================================
193
+
194
+
195
+ class ThresholdRuleConfig(BaseModel):
196
+ """Configuration for threshold-based alert rule."""
197
+
198
+ metric_name: str = Field(..., description="Metric to monitor")
199
+ threshold: float = Field(..., description="Threshold value")
200
+ comparison: Literal["gt", "lt", "gte", "lte", "eq"] = Field(
201
+ default="gt",
202
+ description="Comparison operator",
203
+ )
204
+ duration_seconds: int = Field(
205
+ default=0,
206
+ description="Condition must persist for this duration",
207
+ ge=0,
208
+ )
209
+
210
+
211
+ class StatisticalRuleConfig(BaseModel):
212
+ """Configuration for statistical alert rule."""
213
+
214
+ metric_name: str = Field(..., description="Metric to monitor")
215
+ std_devs: float = Field(default=3.0, description="Standard deviations for anomaly", ge=1.0)
216
+ window_size: int = Field(default=100, description="Window size for baseline", ge=10)
217
+
218
+
219
+ class AlertRuleBase(BaseSchema):
220
+ """Base schema for alert rules."""
221
+
222
+ name: str = Field(..., description="Rule name", min_length=1, max_length=255)
223
+ model_id: str = Field(..., description="Model ID this rule applies to")
224
+ rule_type: AlertRuleType = Field(..., description="Type of alert rule")
225
+ severity: AlertSeverity = Field(default=AlertSeverity.WARNING, description="Alert severity")
226
+ config: dict[str, Any] = Field(..., description="Rule-specific configuration")
227
+ is_active: bool = Field(default=True, description="Whether rule is active")
228
+
229
+
230
+ class CreateAlertRuleRequest(AlertRuleBase):
231
+ """Request to create an alert rule."""
232
+
233
+ pass
234
+
235
+
236
+ class UpdateAlertRuleRequest(BaseSchema):
237
+ """Request to update an alert rule."""
238
+
239
+ name: str | None = Field(None, description="Rule name")
240
+ severity: AlertSeverity | None = Field(None, description="Alert severity")
241
+ config: dict[str, Any] | None = Field(None, description="Rule configuration")
242
+ is_active: bool | None = Field(None, description="Whether rule is active")
243
+
244
+
245
+ class AlertRuleResponse(AlertRuleBase, IDMixin, TimestampMixin):
246
+ """Response for alert rule."""
247
+
248
+ last_triggered_at: datetime | None = Field(None, description="Last trigger time")
249
+ trigger_count: int = Field(default=0, description="Total triggers")
250
+
251
+
252
+ class AlertRuleListResponse(ListResponseWrapper):
253
+ """List response for alert rules."""
254
+
255
+ items: list[AlertRuleResponse]
256
+
257
+
258
+ # =============================================================================
259
+ # Alert Handler Schemas
260
+ # =============================================================================
261
+
262
+
263
+ class SlackHandlerConfig(BaseModel):
264
+ """Configuration for Slack alert handler."""
265
+
266
+ webhook_url: str = Field(..., description="Slack webhook URL")
267
+ channel: str | None = Field(None, description="Override channel")
268
+ username: str = Field(default="Truthound", description="Bot username")
269
+
270
+
271
+ class WebhookHandlerConfig(BaseModel):
272
+ """Configuration for webhook alert handler."""
273
+
274
+ url: str = Field(..., description="Webhook URL")
275
+ method: Literal["POST", "PUT"] = Field(default="POST", description="HTTP method")
276
+ headers: dict[str, str] = Field(default_factory=dict, description="Custom headers")
277
+
278
+
279
+ class AlertHandlerBase(BaseSchema):
280
+ """Base schema for alert handlers."""
281
+
282
+ name: str = Field(..., description="Handler name", min_length=1, max_length=255)
283
+ handler_type: AlertHandlerType = Field(..., description="Type of handler")
284
+ config: dict[str, Any] = Field(..., description="Handler-specific configuration")
285
+ is_active: bool = Field(default=True, description="Whether handler is active")
286
+
287
+
288
+ class CreateAlertHandlerRequest(AlertHandlerBase):
289
+ """Request to create an alert handler."""
290
+
291
+ pass
292
+
293
+
294
+ class UpdateAlertHandlerRequest(BaseSchema):
295
+ """Request to update an alert handler."""
296
+
297
+ name: str | None = Field(None, description="Handler name")
298
+ config: dict[str, Any] | None = Field(None, description="Handler configuration")
299
+ is_active: bool | None = Field(None, description="Whether handler is active")
300
+
301
+
302
+ class AlertHandlerResponse(AlertHandlerBase, IDMixin, TimestampMixin):
303
+ """Response for alert handler."""
304
+
305
+ last_sent_at: datetime | None = Field(None, description="Last alert sent time")
306
+ send_count: int = Field(default=0, description="Total alerts sent")
307
+ failure_count: int = Field(default=0, description="Total failures")
308
+
309
+
310
+ class AlertHandlerListResponse(ListResponseWrapper):
311
+ """List response for alert handlers."""
312
+
313
+ items: list[AlertHandlerResponse]
314
+
315
+
316
+ # =============================================================================
317
+ # Alert Instance Schemas
318
+ # =============================================================================
319
+
320
+
321
+ class AlertInstance(BaseModel, TimestampMixin):
322
+ """An instance of a triggered alert."""
323
+
324
+ id: str = Field(..., description="Alert instance ID")
325
+ rule_id: str = Field(..., description="Rule that triggered")
326
+ model_id: str = Field(..., description="Model ID")
327
+ severity: AlertSeverity = Field(..., description="Alert severity")
328
+ message: str = Field(..., description="Alert message")
329
+ metric_value: float | None = Field(None, description="Metric value that triggered")
330
+ threshold_value: float | None = Field(None, description="Threshold value")
331
+ acknowledged: bool = Field(default=False, description="Whether acknowledged")
332
+ acknowledged_by: str | None = Field(None, description="Who acknowledged")
333
+ acknowledged_at: datetime | None = Field(None, description="When acknowledged")
334
+ resolved: bool = Field(default=False, description="Whether resolved")
335
+ resolved_at: datetime | None = Field(None, description="When resolved")
336
+
337
+
338
+ class AlertListResponse(ListResponseWrapper):
339
+ """List response for alerts."""
340
+
341
+ items: list[AlertInstance]
342
+
343
+
344
+ class AcknowledgeAlertRequest(BaseModel):
345
+ """Request to acknowledge an alert."""
346
+
347
+ actor: str = Field(..., description="Who is acknowledging")
348
+
349
+
350
+ # =============================================================================
351
+ # Dashboard Schemas
352
+ # =============================================================================
353
+
354
+
355
+ class ModelDashboardData(BaseModel):
356
+ """Dashboard data for a single model."""
357
+
358
+ model: RegisteredModelResponse = Field(..., description="Model information")
359
+ metrics: MetricsResponse = Field(..., description="Current metrics")
360
+ active_alerts: list[AlertInstance] = Field(default_factory=list, description="Active alerts")
361
+ recent_predictions: int = Field(default=0, description="Predictions in last hour")
362
+ health_status: str = Field(default="healthy", description="Overall health status")
363
+
364
+
365
+ class MonitoringOverview(BaseModel):
366
+ """Overview of all monitored models."""
367
+
368
+ total_models: int = Field(default=0, description="Total registered models")
369
+ active_models: int = Field(default=0, description="Models actively receiving predictions")
370
+ degraded_models: int = Field(default=0, description="Models in degraded state")
371
+ total_predictions_24h: int = Field(default=0, description="Total predictions in 24h")
372
+ active_alerts: int = Field(default=0, description="Currently active alerts")
373
+ models_with_drift: int = Field(default=0, description="Models with detected drift")
374
+ avg_latency_ms: float | None = Field(None, description="Average latency across all models")