truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.1__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.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
  162. truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
  163. truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
  164. truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
  165. truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
  166. truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
  167. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,223 @@
1
+ """Pydantic schemas for Unified Alerts.
2
+
3
+ Provides schemas for aggregating alerts from all sources:
4
+ - Model monitoring alerts
5
+ - Drift monitoring alerts
6
+ - Anomaly detection alerts
7
+ - Validation failures
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from datetime import datetime
13
+ from enum import Enum
14
+ from typing import Any, Literal
15
+
16
+ from pydantic import BaseModel, Field
17
+
18
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
19
+
20
+
21
+ # =============================================================================
22
+ # Enums
23
+ # =============================================================================
24
+
25
+
26
+ class AlertSource(str, Enum):
27
+ """Sources of alerts in the unified system."""
28
+
29
+ MODEL = "model"
30
+ DRIFT = "drift"
31
+ ANOMALY = "anomaly"
32
+ VALIDATION = "validation"
33
+
34
+
35
+ class AlertSeverity(str, Enum):
36
+ """Alert severity levels."""
37
+
38
+ CRITICAL = "critical"
39
+ HIGH = "high"
40
+ MEDIUM = "medium"
41
+ LOW = "low"
42
+ INFO = "info"
43
+
44
+
45
+ class AlertStatus(str, Enum):
46
+ """Alert status values."""
47
+
48
+ OPEN = "open"
49
+ ACKNOWLEDGED = "acknowledged"
50
+ RESOLVED = "resolved"
51
+ IGNORED = "ignored"
52
+
53
+
54
+ # =============================================================================
55
+ # Unified Alert Schemas
56
+ # =============================================================================
57
+
58
+
59
+ class UnifiedAlertBase(BaseSchema):
60
+ """Base unified alert schema."""
61
+
62
+ source: AlertSource = Field(..., description="Alert source type")
63
+ source_id: str = Field(..., description="Source-specific alert ID")
64
+ source_name: str = Field(..., description="Name of the source (model, source, etc.)")
65
+ severity: AlertSeverity = Field(..., description="Alert severity")
66
+ status: AlertStatus = Field(default=AlertStatus.OPEN, description="Alert status")
67
+ title: str = Field(..., description="Alert title", min_length=1, max_length=500)
68
+ message: str = Field(..., description="Alert message/description")
69
+ details: dict[str, Any] = Field(default_factory=dict, description="Source-specific details")
70
+
71
+
72
+ class UnifiedAlertResponse(UnifiedAlertBase, IDMixin, TimestampMixin):
73
+ """Schema for unified alert response."""
74
+
75
+ acknowledged_at: datetime | None = Field(None, description="When acknowledged")
76
+ acknowledged_by: str | None = Field(None, description="Who acknowledged")
77
+ resolved_at: datetime | None = Field(None, description="When resolved")
78
+ resolved_by: str | None = Field(None, description="Who resolved")
79
+ related_alert_ids: list[str] = Field(default_factory=list, description="IDs of related alerts")
80
+
81
+
82
+ class UnifiedAlertListResponse(ListResponseWrapper):
83
+ """List response for unified alerts."""
84
+
85
+ items: list[UnifiedAlertResponse]
86
+
87
+
88
+ # =============================================================================
89
+ # Alert Summary Schemas
90
+ # =============================================================================
91
+
92
+
93
+ class AlertCountBySeverity(BaseModel):
94
+ """Alert counts by severity."""
95
+
96
+ critical: int = Field(default=0, description="Critical alerts")
97
+ high: int = Field(default=0, description="High severity alerts")
98
+ medium: int = Field(default=0, description="Medium severity alerts")
99
+ low: int = Field(default=0, description="Low severity alerts")
100
+ info: int = Field(default=0, description="Info alerts")
101
+
102
+
103
+ class AlertCountBySource(BaseModel):
104
+ """Alert counts by source."""
105
+
106
+ model: int = Field(default=0, description="Model monitoring alerts")
107
+ drift: int = Field(default=0, description="Drift monitoring alerts")
108
+ anomaly: int = Field(default=0, description="Anomaly detection alerts")
109
+ validation: int = Field(default=0, description="Validation failure alerts")
110
+
111
+
112
+ class AlertCountByStatus(BaseModel):
113
+ """Alert counts by status."""
114
+
115
+ open: int = Field(default=0, description="Open alerts")
116
+ acknowledged: int = Field(default=0, description="Acknowledged alerts")
117
+ resolved: int = Field(default=0, description="Resolved alerts")
118
+ ignored: int = Field(default=0, description="Ignored alerts")
119
+
120
+
121
+ class AlertTrendPoint(BaseModel):
122
+ """Single point in alert trend data."""
123
+
124
+ timestamp: datetime = Field(..., description="Time point")
125
+ count: int = Field(default=0, description="Alert count at this time")
126
+
127
+
128
+ class AlertSummary(BaseSchema):
129
+ """Summary statistics for alerts."""
130
+
131
+ total_alerts: int = Field(default=0, description="Total active alerts")
132
+ active_alerts: int = Field(default=0, description="Non-resolved alerts")
133
+ by_severity: AlertCountBySeverity = Field(
134
+ default_factory=AlertCountBySeverity,
135
+ description="Counts by severity",
136
+ )
137
+ by_source: AlertCountBySource = Field(
138
+ default_factory=AlertCountBySource,
139
+ description="Counts by source",
140
+ )
141
+ by_status: AlertCountByStatus = Field(
142
+ default_factory=AlertCountByStatus,
143
+ description="Counts by status",
144
+ )
145
+ trend_24h: list[AlertTrendPoint] = Field(
146
+ default_factory=list,
147
+ description="Alert trend over last 24 hours",
148
+ )
149
+ top_sources: list[dict[str, Any]] = Field(
150
+ default_factory=list,
151
+ description="Top sources with most alerts",
152
+ )
153
+
154
+
155
+ # =============================================================================
156
+ # Alert Correlation Schemas
157
+ # =============================================================================
158
+
159
+
160
+ class AlertCorrelation(BaseSchema):
161
+ """Correlation between alerts."""
162
+
163
+ alert_id: str = Field(..., description="Primary alert ID")
164
+ related_alerts: list[UnifiedAlertResponse] = Field(
165
+ default_factory=list,
166
+ description="Related alerts",
167
+ )
168
+ correlation_type: str = Field(..., description="Type of correlation")
169
+ correlation_score: float = Field(
170
+ default=0.0,
171
+ ge=0.0,
172
+ le=1.0,
173
+ description="Correlation confidence score",
174
+ )
175
+ common_factors: list[str] = Field(
176
+ default_factory=list,
177
+ description="Common factors between alerts",
178
+ )
179
+
180
+
181
+ class AlertCorrelationResponse(BaseSchema):
182
+ """Response for alert correlation query."""
183
+
184
+ correlations: list[AlertCorrelation] = Field(
185
+ default_factory=list,
186
+ description="List of correlations",
187
+ )
188
+ total_correlated: int = Field(default=0, description="Total correlated alerts")
189
+
190
+
191
+ # =============================================================================
192
+ # Request/Action Schemas
193
+ # =============================================================================
194
+
195
+
196
+ class AcknowledgeAlertRequest(BaseModel):
197
+ """Request to acknowledge an alert."""
198
+
199
+ actor: str = Field(..., description="Who is acknowledging", min_length=1)
200
+ message: str = Field(default="", description="Optional acknowledgement message")
201
+
202
+
203
+ class ResolveAlertRequest(BaseModel):
204
+ """Request to resolve an alert."""
205
+
206
+ actor: str = Field(..., description="Who is resolving", min_length=1)
207
+ message: str = Field(default="", description="Resolution message")
208
+
209
+
210
+ class BulkAlertActionRequest(BaseModel):
211
+ """Request for bulk alert actions."""
212
+
213
+ alert_ids: list[str] = Field(..., description="Alert IDs to act on", min_length=1)
214
+ actor: str = Field(..., description="Who is performing the action")
215
+ message: str = Field(default="", description="Optional message")
216
+
217
+
218
+ class BulkAlertActionResponse(BaseModel):
219
+ """Response for bulk alert actions."""
220
+
221
+ success_count: int = Field(default=0, description="Number of successful updates")
222
+ failed_count: int = Field(default=0, description="Number of failed updates")
223
+ failed_ids: list[str] = Field(default_factory=list, description="IDs that failed")
@@ -42,15 +42,30 @@ class ValidationIssue(BaseSchema):
42
42
  )
43
43
 
44
44
 
45
+ class CustomValidatorConfig(BaseSchema):
46
+ """Configuration for running a custom validator."""
47
+
48
+ validator_id: str = Field(
49
+ ..., description="ID of the custom validator to run"
50
+ )
51
+ column: str = Field(
52
+ ..., description="Column to validate"
53
+ )
54
+ params: dict[str, Any] | None = Field(
55
+ default=None, description="Parameter values for the validator"
56
+ )
57
+
58
+
45
59
  class ValidationRunRequest(BaseSchema):
46
60
  """Request to run validation on a source.
47
61
 
48
62
  This schema maps to truthound's th.check() parameters for maximum flexibility.
49
63
  All optional parameters default to None/False to use truthound's defaults.
50
64
 
51
- Supports two modes:
65
+ Supports three modes:
52
66
  1. Simple mode: Use `validators` list with validator names (backward compatible)
53
67
  2. Advanced mode: Use `validator_configs` for per-validator parameter configuration
68
+ 3. Custom validators: Use `custom_validators` to include user-defined validators
54
69
  """
55
70
 
56
71
  # Core validation options - Simple mode (backward compatible)
@@ -69,6 +84,15 @@ class ValidationRunRequest(BaseSchema):
69
84
  ),
70
85
  )
71
86
 
87
+ # Custom validators - User-defined validators
88
+ custom_validators: list[CustomValidatorConfig] | None = Field(
89
+ default=None,
90
+ description=(
91
+ "Custom validators to run alongside built-in validators. "
92
+ "Each config specifies the validator_id, target column, and parameters."
93
+ ),
94
+ )
95
+
72
96
  schema_path: str | None = Field(
73
97
  default=None,
74
98
  description="Path to schema YAML file for schema validation",
@@ -18,12 +18,17 @@ Usage:
18
18
  from __future__ import annotations
19
19
 
20
20
  from .base import (
21
+ CustomValidatorExecuteRequest,
22
+ CustomValidatorExecuteResponse,
21
23
  ParameterDefinition,
22
24
  ParameterType,
25
+ UnifiedValidatorDefinition,
26
+ UnifiedValidatorListResponse,
23
27
  ValidatorCategory,
24
28
  ValidatorConfig,
25
29
  ValidatorConfigList,
26
30
  ValidatorDefinition,
31
+ ValidatorSource,
27
32
  configs_to_truthound_format,
28
33
  has_custom_params,
29
34
  merge_severity_overrides,
@@ -45,6 +50,12 @@ __all__ = [
45
50
  "ValidatorConfig",
46
51
  "ValidatorConfigList",
47
52
  "ValidatorDefinition",
53
+ # Unified validator types (built-in + custom)
54
+ "ValidatorSource",
55
+ "UnifiedValidatorDefinition",
56
+ "UnifiedValidatorListResponse",
57
+ "CustomValidatorExecuteRequest",
58
+ "CustomValidatorExecuteResponse",
48
59
  # Utility functions
49
60
  "configs_to_truthound_format",
50
61
  "has_custom_params",
@@ -261,3 +261,154 @@ def merge_severity_overrides(
261
261
  for config in configs
262
262
  if config.enabled and config.severity_override is not None
263
263
  }
264
+
265
+
266
+ # ============================================================================
267
+ # Unified Validator Types (Built-in + Custom)
268
+ # ============================================================================
269
+
270
+
271
+ class ValidatorSource(str, Enum):
272
+ """Source of a validator (built-in or custom)."""
273
+
274
+ BUILTIN = "builtin"
275
+ CUSTOM = "custom"
276
+
277
+
278
+ class UnifiedValidatorDefinition(BaseModel):
279
+ """Unified validator definition that can represent both built-in and custom validators.
280
+
281
+ This is used for the unified validator list endpoint that combines
282
+ both built-in truthound validators and user-defined custom validators.
283
+ """
284
+
285
+ id: str | None = Field(
286
+ default=None, description="Unique ID (for custom validators only)"
287
+ )
288
+ name: str = Field(..., description="Validator class name")
289
+ display_name: str = Field(..., description="Human-readable name")
290
+ category: str = Field(..., description="Validator category")
291
+ description: str = Field(..., description="What this validator checks")
292
+ parameters: list[ParameterDefinition] = Field(
293
+ default_factory=list, description="Configurable parameters"
294
+ )
295
+ tags: list[str] = Field(default_factory=list, description="Searchable tags")
296
+ severity_default: Literal["low", "medium", "high", "critical"] = Field(
297
+ default="medium", description="Default issue severity"
298
+ )
299
+ source: ValidatorSource = Field(
300
+ default=ValidatorSource.BUILTIN, description="Whether built-in or custom"
301
+ )
302
+ is_enabled: bool = Field(default=True, description="Whether validator is enabled")
303
+ requires_extra: str | None = Field(
304
+ default=None, description="Extra dependency required"
305
+ )
306
+ experimental: bool = Field(default=False, description="Whether experimental")
307
+ deprecated: bool = Field(default=False, description="Whether deprecated")
308
+ usage_count: int = Field(
309
+ default=0, description="Number of times used (custom only)"
310
+ )
311
+ is_verified: bool = Field(
312
+ default=False, description="Whether verified (custom only)"
313
+ )
314
+
315
+ @classmethod
316
+ def from_builtin(cls, validator: ValidatorDefinition) -> "UnifiedValidatorDefinition":
317
+ """Create from a built-in ValidatorDefinition."""
318
+ return cls(
319
+ id=None,
320
+ name=validator.name,
321
+ display_name=validator.display_name,
322
+ category=validator.category.value,
323
+ description=validator.description,
324
+ parameters=validator.parameters,
325
+ tags=validator.tags,
326
+ severity_default=validator.severity_default,
327
+ source=ValidatorSource.BUILTIN,
328
+ is_enabled=True,
329
+ requires_extra=validator.requires_extra,
330
+ experimental=validator.experimental,
331
+ deprecated=validator.deprecated,
332
+ usage_count=0,
333
+ is_verified=True,
334
+ )
335
+
336
+ @classmethod
337
+ def from_custom(cls, custom_validator: Any) -> "UnifiedValidatorDefinition":
338
+ """Create from a CustomValidator database model."""
339
+ # Convert custom validator parameters to ParameterDefinition format
340
+ parameters = []
341
+ for param in custom_validator.parameters or []:
342
+ param_def = ParameterDefinition(
343
+ name=param.get("name", ""),
344
+ label=param.get("name", "").replace("_", " ").title(),
345
+ type=ParameterType(param.get("type", "string")),
346
+ description=param.get("description", ""),
347
+ required=param.get("required", False),
348
+ default=param.get("default"),
349
+ options=param.get("options"),
350
+ min_value=param.get("min_value"),
351
+ max_value=param.get("max_value"),
352
+ )
353
+ parameters.append(param_def)
354
+
355
+ return cls(
356
+ id=str(custom_validator.id),
357
+ name=f"custom:{custom_validator.name}", # Prefix to distinguish
358
+ display_name=custom_validator.display_name,
359
+ category=custom_validator.category,
360
+ description=custom_validator.description,
361
+ parameters=parameters,
362
+ tags=custom_validator.tags or [],
363
+ severity_default=custom_validator.severity or "medium",
364
+ source=ValidatorSource.CUSTOM,
365
+ is_enabled=custom_validator.is_enabled,
366
+ requires_extra=None,
367
+ experimental=False,
368
+ deprecated=False,
369
+ usage_count=custom_validator.usage_count,
370
+ is_verified=custom_validator.is_verified,
371
+ )
372
+
373
+
374
+ class UnifiedValidatorListResponse(BaseModel):
375
+ """Response for unified validator list."""
376
+
377
+ data: list[UnifiedValidatorDefinition] = Field(
378
+ default_factory=list, description="List of validators"
379
+ )
380
+ total: int = Field(..., description="Total number of validators")
381
+ builtin_count: int = Field(..., description="Number of built-in validators")
382
+ custom_count: int = Field(..., description="Number of custom validators")
383
+ categories: list[dict[str, Any]] = Field(
384
+ default_factory=list, description="Category summary with counts"
385
+ )
386
+
387
+
388
+ class CustomValidatorExecuteRequest(BaseModel):
389
+ """Request to execute a custom validator on a data source."""
390
+
391
+ source_id: str = Field(..., description="Data source ID to validate")
392
+ column_name: str = Field(..., description="Column to validate")
393
+ param_values: dict[str, Any] = Field(
394
+ default_factory=dict, description="Parameter values"
395
+ )
396
+ sample_size: int | None = Field(
397
+ default=None, ge=1, le=100000, description="Sample size (optional)"
398
+ )
399
+
400
+
401
+ class CustomValidatorExecuteResponse(BaseModel):
402
+ """Response from custom validator execution."""
403
+
404
+ success: bool = Field(..., description="Whether execution succeeded")
405
+ passed: bool | None = Field(default=None, description="Whether validation passed")
406
+ execution_time_ms: float = Field(default=0, description="Execution time in ms")
407
+ issues: list[dict[str, Any]] = Field(
408
+ default_factory=list, description="Validation issues found"
409
+ )
410
+ message: str = Field(default="", description="Summary message")
411
+ details: dict[str, Any] = Field(
412
+ default_factory=dict, description="Additional details"
413
+ )
414
+ error: str | None = Field(default=None, description="Error message if failed")
@@ -0,0 +1,152 @@
1
+ """Versioning schemas for validation result versioning.
2
+
3
+ Defines request/response models for the versioning API endpoints.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from typing import Any, Literal
10
+
11
+ from pydantic import Field
12
+
13
+ from .base import BaseSchema
14
+
15
+
16
+ # Versioning strategy types
17
+ VersioningStrategyType = Literal["incremental", "semantic", "timestamp", "gitlike"]
18
+
19
+
20
+ class VersionInfoResponse(BaseSchema):
21
+ """Version information response."""
22
+
23
+ version_id: str = Field(..., description="Unique version identifier")
24
+ version_number: str = Field(..., description="Human-readable version number")
25
+ validation_id: str = Field(..., description="Associated validation ID")
26
+ source_id: str = Field(..., description="Associated source ID")
27
+ strategy: VersioningStrategyType = Field(..., description="Versioning strategy used")
28
+ created_at: datetime = Field(..., description="Version creation timestamp")
29
+ parent_version_id: str | None = Field(None, description="Parent version ID")
30
+ metadata: dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
31
+ content_hash: str | None = Field(None, description="Hash of validation content")
32
+
33
+
34
+ class VersionListResponse(BaseSchema):
35
+ """Response for listing versions."""
36
+
37
+ success: bool = Field(default=True)
38
+ data: list[VersionInfoResponse] = Field(..., description="List of versions")
39
+ total: int = Field(..., description="Total number of versions")
40
+ source_id: str = Field(..., description="Source ID for these versions")
41
+
42
+
43
+ class IssueChange(BaseSchema):
44
+ """Represents a changed issue between versions."""
45
+
46
+ key: str = Field(..., description="Issue key (column:issue_type)")
47
+ from_issue: dict[str, Any] = Field(..., alias="from", description="Original issue")
48
+ to_issue: dict[str, Any] = Field(..., alias="to", description="New issue")
49
+
50
+ class Config:
51
+ populate_by_name = True
52
+
53
+
54
+ class VersionDiffResponse(BaseSchema):
55
+ """Version comparison/diff response."""
56
+
57
+ from_version: VersionInfoResponse = Field(..., description="Source version")
58
+ to_version: VersionInfoResponse = Field(..., description="Target version")
59
+ issues_added: list[dict[str, Any]] = Field(
60
+ default_factory=list, description="Issues added in target version"
61
+ )
62
+ issues_removed: list[dict[str, Any]] = Field(
63
+ default_factory=list, description="Issues removed in target version"
64
+ )
65
+ issues_changed: list[dict[str, Any]] = Field(
66
+ default_factory=list, description="Issues that changed between versions"
67
+ )
68
+ summary_changes: dict[str, Any] = Field(
69
+ default_factory=dict, description="Summary of changes"
70
+ )
71
+ has_changes: bool = Field(..., description="Whether there are any changes")
72
+
73
+
74
+ class VersionCompareRequest(BaseSchema):
75
+ """Request to compare two versions."""
76
+
77
+ from_version_id: str = Field(..., description="Source version ID to compare from")
78
+ to_version_id: str = Field(..., description="Target version ID to compare to")
79
+
80
+
81
+ class VersionHistoryResponse(BaseSchema):
82
+ """Response for version history chain."""
83
+
84
+ success: bool = Field(default=True)
85
+ data: list[VersionInfoResponse] = Field(..., description="Version history chain")
86
+ depth: int = Field(..., description="Depth of history returned")
87
+
88
+
89
+ class CreateVersionRequest(BaseSchema):
90
+ """Request to create a new version for a validation."""
91
+
92
+ validation_id: str = Field(..., description="Validation ID to version")
93
+ strategy: VersioningStrategyType | None = Field(
94
+ None, description="Versioning strategy (defaults to incremental)"
95
+ )
96
+ metadata: dict[str, Any] | None = Field(
97
+ None, description="Additional version metadata"
98
+ )
99
+
100
+
101
+ class CreateVersionResponse(BaseSchema):
102
+ """Response after creating a version."""
103
+
104
+ success: bool = Field(default=True)
105
+ data: VersionInfoResponse = Field(..., description="Created version info")
106
+ message: str = Field(..., description="Success message")
107
+
108
+
109
+ # Rollback schemas
110
+ class RollbackRequest(BaseSchema):
111
+ """Request to rollback to a previous version."""
112
+
113
+ target_version_id: str = Field(..., description="Version ID to rollback to")
114
+ create_new_validation: bool = Field(
115
+ default=True,
116
+ description="Whether to create a new validation record for the rollback"
117
+ )
118
+
119
+
120
+ class RollbackResponse(BaseSchema):
121
+ """Response after a rollback operation."""
122
+
123
+ success: bool = Field(default=True)
124
+ source_id: str = Field(..., description="Source ID that was rolled back")
125
+ from_version: VersionInfoResponse | None = Field(
126
+ None, description="Version rolled back from"
127
+ )
128
+ to_version: VersionInfoResponse | None = Field(
129
+ None, description="Version rolled back to"
130
+ )
131
+ new_validation_id: str | None = Field(
132
+ None, description="ID of the new validation created (if any)"
133
+ )
134
+ message: str = Field(..., description="Status message")
135
+ rolled_back_at: datetime = Field(..., description="When rollback was performed")
136
+
137
+
138
+ class RollbackAvailabilityResponse(BaseSchema):
139
+ """Response for checking rollback availability."""
140
+
141
+ success: bool = Field(default=True)
142
+ can_rollback: bool = Field(..., description="Whether rollback is available")
143
+ current_version_id: str | None = Field(
144
+ None, description="Current active version ID"
145
+ )
146
+ available_versions: int = Field(
147
+ ..., description="Number of available versions"
148
+ )
149
+ rollback_targets: list[dict[str, Any]] = Field(
150
+ default_factory=list,
151
+ description="List of versions available for rollback"
152
+ )
@@ -6,8 +6,8 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <meta name="description" content="Truthound Dashboard - Open-source data quality monitoring" />
8
8
  <title>Truthound Dashboard</title>
9
- <script type="module" crossorigin src="/assets/index-BZG20KuF.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-D_HyZ3pb.css">
9
+ <script type="module" crossorigin src="/assets/index-DkU82VsU.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-C6JSrkHo.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>