truthound-dashboard 1.4.3__py3-none-any.whl → 1.5.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 (205) hide show
  1. truthound_dashboard/api/alerts.py +75 -86
  2. truthound_dashboard/api/anomaly.py +7 -13
  3. truthound_dashboard/api/cross_alerts.py +38 -52
  4. truthound_dashboard/api/drift.py +49 -59
  5. truthound_dashboard/api/drift_monitor.py +234 -79
  6. truthound_dashboard/api/enterprise_sampling.py +498 -0
  7. truthound_dashboard/api/history.py +57 -5
  8. truthound_dashboard/api/lineage.py +3 -48
  9. truthound_dashboard/api/maintenance.py +104 -49
  10. truthound_dashboard/api/mask.py +1 -2
  11. truthound_dashboard/api/middleware.py +2 -1
  12. truthound_dashboard/api/model_monitoring.py +435 -311
  13. truthound_dashboard/api/notifications.py +227 -191
  14. truthound_dashboard/api/notifications_advanced.py +21 -20
  15. truthound_dashboard/api/observability.py +586 -0
  16. truthound_dashboard/api/plugins.py +2 -433
  17. truthound_dashboard/api/profile.py +199 -37
  18. truthound_dashboard/api/quality_reporter.py +701 -0
  19. truthound_dashboard/api/reports.py +7 -16
  20. truthound_dashboard/api/router.py +66 -0
  21. truthound_dashboard/api/rule_suggestions.py +5 -5
  22. truthound_dashboard/api/scan.py +17 -19
  23. truthound_dashboard/api/schedules.py +85 -50
  24. truthound_dashboard/api/schema_evolution.py +6 -6
  25. truthound_dashboard/api/schema_watcher.py +667 -0
  26. truthound_dashboard/api/sources.py +98 -27
  27. truthound_dashboard/api/tiering.py +1323 -0
  28. truthound_dashboard/api/triggers.py +14 -11
  29. truthound_dashboard/api/validations.py +12 -11
  30. truthound_dashboard/api/versioning.py +1 -6
  31. truthound_dashboard/core/__init__.py +129 -3
  32. truthound_dashboard/core/actions/__init__.py +62 -0
  33. truthound_dashboard/core/actions/custom.py +426 -0
  34. truthound_dashboard/core/actions/notifications.py +910 -0
  35. truthound_dashboard/core/actions/storage.py +472 -0
  36. truthound_dashboard/core/actions/webhook.py +281 -0
  37. truthound_dashboard/core/anomaly.py +262 -67
  38. truthound_dashboard/core/anomaly_explainer.py +4 -3
  39. truthound_dashboard/core/backends/__init__.py +67 -0
  40. truthound_dashboard/core/backends/base.py +299 -0
  41. truthound_dashboard/core/backends/errors.py +191 -0
  42. truthound_dashboard/core/backends/factory.py +423 -0
  43. truthound_dashboard/core/backends/mock_backend.py +451 -0
  44. truthound_dashboard/core/backends/truthound_backend.py +718 -0
  45. truthound_dashboard/core/checkpoint/__init__.py +87 -0
  46. truthound_dashboard/core/checkpoint/adapters.py +814 -0
  47. truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
  48. truthound_dashboard/core/checkpoint/runner.py +270 -0
  49. truthound_dashboard/core/connections.py +437 -10
  50. truthound_dashboard/core/converters/__init__.py +14 -0
  51. truthound_dashboard/core/converters/truthound.py +620 -0
  52. truthound_dashboard/core/cross_alerts.py +540 -320
  53. truthound_dashboard/core/datasource_factory.py +1672 -0
  54. truthound_dashboard/core/drift_monitor.py +216 -20
  55. truthound_dashboard/core/enterprise_sampling.py +1291 -0
  56. truthound_dashboard/core/interfaces/__init__.py +225 -0
  57. truthound_dashboard/core/interfaces/actions.py +652 -0
  58. truthound_dashboard/core/interfaces/base.py +247 -0
  59. truthound_dashboard/core/interfaces/checkpoint.py +676 -0
  60. truthound_dashboard/core/interfaces/protocols.py +664 -0
  61. truthound_dashboard/core/interfaces/reporters.py +650 -0
  62. truthound_dashboard/core/interfaces/routing.py +646 -0
  63. truthound_dashboard/core/interfaces/triggers.py +619 -0
  64. truthound_dashboard/core/lineage.py +407 -71
  65. truthound_dashboard/core/model_monitoring.py +431 -3
  66. truthound_dashboard/core/notifications/base.py +4 -0
  67. truthound_dashboard/core/notifications/channels.py +501 -1203
  68. truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
  69. truthound_dashboard/core/notifications/deduplication/service.py +131 -348
  70. truthound_dashboard/core/notifications/dispatcher.py +202 -11
  71. truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
  72. truthound_dashboard/core/notifications/escalation/engine.py +168 -358
  73. truthound_dashboard/core/notifications/routing/__init__.py +88 -128
  74. truthound_dashboard/core/notifications/routing/engine.py +90 -317
  75. truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
  76. truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
  77. truthound_dashboard/core/notifications/throttling/builder.py +117 -255
  78. truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
  79. truthound_dashboard/core/phase5/collaboration.py +1 -1
  80. truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
  81. truthound_dashboard/core/quality_reporter.py +1359 -0
  82. truthound_dashboard/core/report_history.py +0 -6
  83. truthound_dashboard/core/reporters/__init__.py +175 -14
  84. truthound_dashboard/core/reporters/adapters.py +943 -0
  85. truthound_dashboard/core/reporters/base.py +0 -3
  86. truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
  87. truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
  88. truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
  89. truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
  90. truthound_dashboard/core/reporters/compat.py +266 -0
  91. truthound_dashboard/core/reporters/csv_reporter.py +2 -35
  92. truthound_dashboard/core/reporters/factory.py +526 -0
  93. truthound_dashboard/core/reporters/interfaces.py +745 -0
  94. truthound_dashboard/core/reporters/registry.py +1 -10
  95. truthound_dashboard/core/scheduler.py +165 -0
  96. truthound_dashboard/core/schema_evolution.py +3 -3
  97. truthound_dashboard/core/schema_watcher.py +1528 -0
  98. truthound_dashboard/core/services.py +595 -76
  99. truthound_dashboard/core/store_manager.py +810 -0
  100. truthound_dashboard/core/streaming_anomaly.py +169 -4
  101. truthound_dashboard/core/tiering.py +1309 -0
  102. truthound_dashboard/core/triggers/evaluators.py +178 -8
  103. truthound_dashboard/core/truthound_adapter.py +2620 -197
  104. truthound_dashboard/core/unified_alerts.py +23 -20
  105. truthound_dashboard/db/__init__.py +8 -0
  106. truthound_dashboard/db/database.py +8 -2
  107. truthound_dashboard/db/models.py +944 -25
  108. truthound_dashboard/db/repository.py +2 -0
  109. truthound_dashboard/main.py +11 -0
  110. truthound_dashboard/schemas/__init__.py +177 -16
  111. truthound_dashboard/schemas/base.py +44 -23
  112. truthound_dashboard/schemas/collaboration.py +19 -6
  113. truthound_dashboard/schemas/cross_alerts.py +19 -3
  114. truthound_dashboard/schemas/drift.py +61 -55
  115. truthound_dashboard/schemas/drift_monitor.py +67 -23
  116. truthound_dashboard/schemas/enterprise_sampling.py +653 -0
  117. truthound_dashboard/schemas/lineage.py +0 -33
  118. truthound_dashboard/schemas/mask.py +10 -8
  119. truthound_dashboard/schemas/model_monitoring.py +89 -10
  120. truthound_dashboard/schemas/notifications_advanced.py +13 -0
  121. truthound_dashboard/schemas/observability.py +453 -0
  122. truthound_dashboard/schemas/plugins.py +0 -280
  123. truthound_dashboard/schemas/profile.py +154 -247
  124. truthound_dashboard/schemas/quality_reporter.py +403 -0
  125. truthound_dashboard/schemas/reports.py +2 -2
  126. truthound_dashboard/schemas/rule_suggestion.py +8 -1
  127. truthound_dashboard/schemas/scan.py +4 -24
  128. truthound_dashboard/schemas/schedule.py +11 -3
  129. truthound_dashboard/schemas/schema_watcher.py +727 -0
  130. truthound_dashboard/schemas/source.py +17 -2
  131. truthound_dashboard/schemas/tiering.py +822 -0
  132. truthound_dashboard/schemas/triggers.py +16 -0
  133. truthound_dashboard/schemas/unified_alerts.py +7 -0
  134. truthound_dashboard/schemas/validation.py +0 -13
  135. truthound_dashboard/schemas/validators/base.py +41 -21
  136. truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
  137. truthound_dashboard/schemas/validators/localization_validators.py +273 -0
  138. truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
  139. truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
  140. truthound_dashboard/schemas/validators/referential_validators.py +312 -0
  141. truthound_dashboard/schemas/validators/registry.py +93 -8
  142. truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
  143. truthound_dashboard/schemas/versioning.py +1 -6
  144. truthound_dashboard/static/index.html +2 -2
  145. truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
  146. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
  147. truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
  148. truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
  149. truthound_dashboard/core/plugins/hooks/manager.py +0 -403
  150. truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
  151. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
  152. truthound_dashboard/core/reporters/junit_reporter.py +0 -233
  153. truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
  154. truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
  155. truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
  156. truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
  157. truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
  158. truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
  159. truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
  160. truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
  161. truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
  162. truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
  163. truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
  164. truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
  165. truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
  166. truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
  167. truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
  168. truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
  169. truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
  170. truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
  171. truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
  172. truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
  173. truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
  174. truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
  175. truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
  176. truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
  177. truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
  178. truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
  179. truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
  180. truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
  181. truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
  182. truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
  183. truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
  184. truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
  185. truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
  186. truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
  187. truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
  188. truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
  189. truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
  190. truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
  191. truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
  192. truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
  193. truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
  194. truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
  195. truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
  196. truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
  197. truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
  198. truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
  199. truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
  200. truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
  201. truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
  202. truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
  203. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,727 @@
1
+ """Schema Watcher Pydantic schemas.
2
+
3
+ This module defines schemas for the SchemaWatcher functionality,
4
+ including continuous schema monitoring, alerts, and run history.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any
12
+
13
+ from pydantic import Field, field_validator
14
+
15
+ from .base import BaseSchema, IDMixin, PaginatedResponse, TimestampMixin
16
+
17
+
18
+ # =============================================================================
19
+ # Enums
20
+ # =============================================================================
21
+
22
+
23
+ class SchemaWatcherStatus(str, Enum):
24
+ """Status of a schema watcher."""
25
+
26
+ ACTIVE = "active"
27
+ PAUSED = "paused"
28
+ STOPPED = "stopped"
29
+ ERROR = "error"
30
+
31
+
32
+ class SchemaWatcherAlertStatus(str, Enum):
33
+ """Status of a schema watcher alert."""
34
+
35
+ OPEN = "open"
36
+ ACKNOWLEDGED = "acknowledged"
37
+ RESOLVED = "resolved"
38
+ SUPPRESSED = "suppressed"
39
+
40
+
41
+ class SchemaWatcherAlertSeverity(str, Enum):
42
+ """Severity of schema watcher alert."""
43
+
44
+ CRITICAL = "critical"
45
+ HIGH = "high"
46
+ MEDIUM = "medium"
47
+ LOW = "low"
48
+ INFO = "info"
49
+
50
+
51
+ class VersionStrategy(str, Enum):
52
+ """Schema version numbering strategy."""
53
+
54
+ SEMANTIC = "semantic"
55
+ INCREMENTAL = "incremental"
56
+ TIMESTAMP = "timestamp"
57
+ GIT = "git"
58
+
59
+
60
+ class ImpactScope(str, Enum):
61
+ """Scope of schema change impact."""
62
+
63
+ LOCAL = "local"
64
+ DOWNSTREAM = "downstream"
65
+ SYSTEM = "system"
66
+
67
+
68
+ # =============================================================================
69
+ # Schema Watcher Schemas
70
+ # =============================================================================
71
+
72
+
73
+ class SchemaWatcherBase(BaseSchema):
74
+ """Base schema for schema watcher."""
75
+
76
+ name: str = Field(..., min_length=1, max_length=255, description="Watcher name")
77
+ source_id: str = Field(..., description="Source ID to watch")
78
+ poll_interval_seconds: int = Field(
79
+ default=60,
80
+ ge=10,
81
+ le=86400,
82
+ description="Polling interval in seconds (10s - 24h)",
83
+ )
84
+ only_breaking: bool = Field(
85
+ default=False,
86
+ description="Only alert on breaking changes",
87
+ )
88
+ enable_rename_detection: bool = Field(
89
+ default=True,
90
+ description="Enable column rename detection",
91
+ )
92
+ rename_similarity_threshold: float = Field(
93
+ default=0.8,
94
+ ge=0.5,
95
+ le=1.0,
96
+ description="Similarity threshold for rename detection (0.5-1.0)",
97
+ )
98
+ version_strategy: VersionStrategy = Field(
99
+ default=VersionStrategy.SEMANTIC,
100
+ description="Version numbering strategy",
101
+ )
102
+ notify_on_change: bool = Field(
103
+ default=True,
104
+ description="Send notifications when changes detected",
105
+ )
106
+ track_history: bool = Field(
107
+ default=True,
108
+ description="Track changes in schema history",
109
+ )
110
+
111
+
112
+ class SchemaWatcherCreate(SchemaWatcherBase):
113
+ """Schema for creating a new schema watcher."""
114
+
115
+ config: dict[str, Any] | None = Field(
116
+ default=None,
117
+ description="Additional configuration",
118
+ )
119
+
120
+
121
+ class SchemaWatcherUpdate(BaseSchema):
122
+ """Schema for updating a schema watcher."""
123
+
124
+ name: str | None = Field(
125
+ default=None, min_length=1, max_length=255, description="Watcher name"
126
+ )
127
+ poll_interval_seconds: int | None = Field(
128
+ default=None,
129
+ ge=10,
130
+ le=86400,
131
+ description="Polling interval in seconds",
132
+ )
133
+ only_breaking: bool | None = Field(
134
+ default=None,
135
+ description="Only alert on breaking changes",
136
+ )
137
+ enable_rename_detection: bool | None = Field(
138
+ default=None,
139
+ description="Enable column rename detection",
140
+ )
141
+ rename_similarity_threshold: float | None = Field(
142
+ default=None,
143
+ ge=0.5,
144
+ le=1.0,
145
+ description="Similarity threshold for rename detection",
146
+ )
147
+ version_strategy: VersionStrategy | None = Field(
148
+ default=None,
149
+ description="Version numbering strategy",
150
+ )
151
+ notify_on_change: bool | None = Field(
152
+ default=None,
153
+ description="Send notifications when changes detected",
154
+ )
155
+ track_history: bool | None = Field(
156
+ default=None,
157
+ description="Track changes in schema history",
158
+ )
159
+ config: dict[str, Any] | None = Field(
160
+ default=None,
161
+ description="Additional configuration",
162
+ )
163
+
164
+
165
+ class SchemaWatcherResponse(SchemaWatcherBase, IDMixin, TimestampMixin):
166
+ """Schema watcher response."""
167
+
168
+ status: SchemaWatcherStatus = Field(..., description="Current watcher status")
169
+ last_check_at: datetime | None = Field(
170
+ default=None, description="When last checked"
171
+ )
172
+ last_change_at: datetime | None = Field(
173
+ default=None, description="When last change detected"
174
+ )
175
+ next_check_at: datetime | None = Field(
176
+ default=None, description="When next check scheduled"
177
+ )
178
+ check_count: int = Field(default=0, description="Total checks performed")
179
+ change_count: int = Field(default=0, description="Total changes detected")
180
+ error_count: int = Field(default=0, description="Consecutive error count")
181
+ last_error: str | None = Field(default=None, description="Last error message")
182
+ config: dict[str, Any] | None = Field(
183
+ default=None, description="Additional configuration"
184
+ )
185
+
186
+ # Computed properties serialized
187
+ is_active: bool = Field(default=False, description="Whether watcher is active")
188
+ is_healthy: bool = Field(default=True, description="Whether watcher is healthy")
189
+ detection_rate: float = Field(default=0.0, description="Change detection rate")
190
+
191
+ # Source info
192
+ source_name: str | None = Field(default=None, description="Source name")
193
+
194
+
195
+ class SchemaWatcherSummary(BaseSchema):
196
+ """Summary of a schema watcher for lists."""
197
+
198
+ id: str = Field(..., description="Watcher ID")
199
+ name: str = Field(..., description="Watcher name")
200
+ source_id: str = Field(..., description="Source ID")
201
+ source_name: str | None = Field(default=None, description="Source name")
202
+ status: SchemaWatcherStatus = Field(..., description="Current status")
203
+ poll_interval_seconds: int = Field(..., description="Polling interval")
204
+ check_count: int = Field(default=0, description="Total checks")
205
+ change_count: int = Field(default=0, description="Total changes")
206
+ last_check_at: datetime | None = Field(default=None, description="Last check time")
207
+ next_check_at: datetime | None = Field(default=None, description="Next check time")
208
+ created_at: datetime = Field(..., description="Created at")
209
+
210
+
211
+ # =============================================================================
212
+ # Schema Watcher Alert Schemas
213
+ # =============================================================================
214
+
215
+
216
+ class SchemaWatcherAlertBase(BaseSchema):
217
+ """Base schema for schema watcher alert."""
218
+
219
+ title: str = Field(..., max_length=500, description="Alert title")
220
+ severity: SchemaWatcherAlertSeverity = Field(
221
+ default=SchemaWatcherAlertSeverity.MEDIUM,
222
+ description="Alert severity",
223
+ )
224
+
225
+
226
+ class SchemaWatcherAlertResponse(SchemaWatcherAlertBase, IDMixin, TimestampMixin):
227
+ """Schema watcher alert response."""
228
+
229
+ watcher_id: str = Field(..., description="Watcher ID")
230
+ source_id: str = Field(..., description="Source ID")
231
+ from_version_id: str | None = Field(default=None, description="Previous version ID")
232
+ to_version_id: str = Field(..., description="New version ID")
233
+ status: SchemaWatcherAlertStatus = Field(..., description="Alert status")
234
+ total_changes: int = Field(default=0, description="Total changes")
235
+ breaking_changes: int = Field(default=0, description="Breaking changes")
236
+ changes_summary: dict[str, Any] | None = Field(
237
+ default=None, description="Changes summary"
238
+ )
239
+ impact_scope: ImpactScope | None = Field(default=None, description="Impact scope")
240
+ affected_consumers: list[str] | None = Field(
241
+ default=None, description="Affected consumers"
242
+ )
243
+ recommendations: list[str] | None = Field(
244
+ default=None, description="Recommendations"
245
+ )
246
+
247
+ # Acknowledgment
248
+ acknowledged_at: datetime | None = Field(default=None, description="Acknowledged at")
249
+ acknowledged_by: str | None = Field(default=None, description="Acknowledged by")
250
+
251
+ # Resolution
252
+ resolved_at: datetime | None = Field(default=None, description="Resolved at")
253
+ resolved_by: str | None = Field(default=None, description="Resolved by")
254
+ resolution_notes: str | None = Field(default=None, description="Resolution notes")
255
+
256
+ # Computed
257
+ is_open: bool = Field(default=True, description="Whether alert is open")
258
+ has_breaking_changes: bool = Field(
259
+ default=False, description="Has breaking changes"
260
+ )
261
+ time_to_acknowledge: float | None = Field(
262
+ default=None, description="Time to acknowledge (seconds)"
263
+ )
264
+ time_to_resolve: float | None = Field(
265
+ default=None, description="Time to resolve (seconds)"
266
+ )
267
+
268
+ # Related info
269
+ source_name: str | None = Field(default=None, description="Source name")
270
+ watcher_name: str | None = Field(default=None, description="Watcher name")
271
+
272
+
273
+ class SchemaWatcherAlertSummary(BaseSchema):
274
+ """Summary of a schema watcher alert for lists."""
275
+
276
+ id: str = Field(..., description="Alert ID")
277
+ watcher_id: str = Field(..., description="Watcher ID")
278
+ source_id: str = Field(..., description="Source ID")
279
+ title: str = Field(..., description="Alert title")
280
+ severity: SchemaWatcherAlertSeverity = Field(..., description="Severity")
281
+ status: SchemaWatcherAlertStatus = Field(..., description="Status")
282
+ total_changes: int = Field(default=0, description="Total changes")
283
+ breaking_changes: int = Field(default=0, description="Breaking changes")
284
+ created_at: datetime = Field(..., description="Created at")
285
+ source_name: str | None = Field(default=None, description="Source name")
286
+
287
+
288
+ class SchemaWatcherAlertAcknowledge(BaseSchema):
289
+ """Schema for acknowledging an alert."""
290
+
291
+ acknowledged_by: str | None = Field(
292
+ default=None, max_length=255, description="Who acknowledged"
293
+ )
294
+
295
+
296
+ class SchemaWatcherAlertResolve(BaseSchema):
297
+ """Schema for resolving an alert."""
298
+
299
+ resolved_by: str | None = Field(
300
+ default=None, max_length=255, description="Who resolved"
301
+ )
302
+ resolution_notes: str | None = Field(
303
+ default=None, max_length=2000, description="Resolution notes"
304
+ )
305
+
306
+
307
+ # =============================================================================
308
+ # Schema Watcher Run Schemas
309
+ # =============================================================================
310
+
311
+
312
+ class SchemaWatcherRunStatus(str, Enum):
313
+ """Status of a schema watcher run."""
314
+
315
+ PENDING = "pending"
316
+ RUNNING = "running"
317
+ COMPLETED = "completed"
318
+ FAILED = "failed"
319
+
320
+
321
+ class SchemaWatcherRunResponse(BaseSchema, IDMixin):
322
+ """Schema watcher run response."""
323
+
324
+ watcher_id: str = Field(..., description="Watcher ID")
325
+ source_id: str = Field(..., description="Source ID")
326
+ started_at: datetime = Field(..., description="Run start time")
327
+ completed_at: datetime | None = Field(default=None, description="Run end time")
328
+ status: SchemaWatcherRunStatus = Field(..., description="Run status")
329
+ changes_detected: int = Field(default=0, description="Changes detected")
330
+ breaking_detected: int = Field(default=0, description="Breaking changes detected")
331
+ version_created_id: str | None = Field(
332
+ default=None, description="Created version ID"
333
+ )
334
+ alert_created_id: str | None = Field(default=None, description="Created alert ID")
335
+ duration_ms: float | None = Field(default=None, description="Duration in ms")
336
+ error_message: str | None = Field(default=None, description="Error message")
337
+ metadata: dict[str, Any] | None = Field(default=None, description="Run metadata")
338
+
339
+ # Computed
340
+ is_successful: bool = Field(default=False, description="Whether run succeeded")
341
+ has_changes: bool = Field(default=False, description="Whether changes detected")
342
+
343
+ # Related info
344
+ source_name: str | None = Field(default=None, description="Source name")
345
+ watcher_name: str | None = Field(default=None, description="Watcher name")
346
+
347
+
348
+ class SchemaWatcherRunSummary(BaseSchema):
349
+ """Summary of a schema watcher run for lists."""
350
+
351
+ id: str = Field(..., description="Run ID")
352
+ watcher_id: str = Field(..., description="Watcher ID")
353
+ source_id: str = Field(..., description="Source ID")
354
+ started_at: datetime = Field(..., description="Start time")
355
+ status: SchemaWatcherRunStatus = Field(..., description="Status")
356
+ changes_detected: int = Field(default=0, description="Changes detected")
357
+ breaking_detected: int = Field(default=0, description="Breaking detected")
358
+ duration_ms: float | None = Field(default=None, description="Duration")
359
+
360
+
361
+ # =============================================================================
362
+ # Statistics and Actions
363
+ # =============================================================================
364
+
365
+
366
+ class SchemaWatcherStatistics(BaseSchema):
367
+ """Statistics for schema watchers."""
368
+
369
+ total_watchers: int = Field(default=0, description="Total watchers")
370
+ active_watchers: int = Field(default=0, description="Active watchers")
371
+ paused_watchers: int = Field(default=0, description="Paused watchers")
372
+ error_watchers: int = Field(default=0, description="Watchers with errors")
373
+
374
+ total_alerts: int = Field(default=0, description="Total alerts")
375
+ open_alerts: int = Field(default=0, description="Open alerts")
376
+ acknowledged_alerts: int = Field(default=0, description="Acknowledged alerts")
377
+ resolved_alerts: int = Field(default=0, description="Resolved alerts")
378
+
379
+ total_runs: int = Field(default=0, description="Total runs")
380
+ successful_runs: int = Field(default=0, description="Successful runs")
381
+ failed_runs: int = Field(default=0, description="Failed runs")
382
+
383
+ total_changes_detected: int = Field(default=0, description="Total changes detected")
384
+ total_breaking_changes: int = Field(default=0, description="Total breaking changes")
385
+
386
+ avg_detection_rate: float = Field(default=0.0, description="Average detection rate")
387
+ avg_time_to_acknowledge: float | None = Field(
388
+ default=None, description="Average time to acknowledge (seconds)"
389
+ )
390
+ avg_time_to_resolve: float | None = Field(
391
+ default=None, description="Average time to resolve (seconds)"
392
+ )
393
+
394
+
395
+ class SchemaWatcherStatusAction(BaseSchema):
396
+ """Schema for status change action."""
397
+
398
+ status: SchemaWatcherStatus = Field(..., description="New status")
399
+
400
+
401
+ class SchemaWatcherCheckNowResponse(BaseSchema):
402
+ """Response for check now action."""
403
+
404
+ watcher_id: str = Field(..., description="Watcher ID")
405
+ run_id: str = Field(..., description="Run ID")
406
+ status: SchemaWatcherRunStatus = Field(..., description="Run status")
407
+ changes_detected: int = Field(default=0, description="Changes detected")
408
+ breaking_detected: int = Field(default=0, description="Breaking changes")
409
+ alert_created_id: str | None = Field(default=None, description="Alert ID if created")
410
+ version_created_id: str | None = Field(
411
+ default=None, description="Version ID if created"
412
+ )
413
+ duration_ms: float | None = Field(default=None, description="Duration")
414
+ message: str = Field(..., description="Result message")
415
+
416
+
417
+ # =============================================================================
418
+ # List Responses (using PaginatedResponse)
419
+ # =============================================================================
420
+
421
+ SchemaWatcherListResponse = PaginatedResponse[SchemaWatcherSummary]
422
+ SchemaWatcherAlertListResponse = PaginatedResponse[SchemaWatcherAlertSummary]
423
+ SchemaWatcherRunListResponse = PaginatedResponse[SchemaWatcherRunSummary]
424
+
425
+
426
+ # =============================================================================
427
+ # Schema Change Detection (truthound.profiler.evolution types)
428
+ # =============================================================================
429
+
430
+
431
+ class SchemaChangeType(str, Enum):
432
+ """Type of schema change."""
433
+
434
+ COLUMN_ADDED = "column_added"
435
+ COLUMN_REMOVED = "column_removed"
436
+ COLUMN_RENAMED = "column_renamed"
437
+ TYPE_CHANGED = "type_changed"
438
+ NULLABLE_CHANGED = "nullable_changed"
439
+ CONSTRAINT_CHANGED = "constraint_changed"
440
+
441
+
442
+ class SchemaChangeSeverity(str, Enum):
443
+ """Severity of a schema change."""
444
+
445
+ INFO = "info"
446
+ WARNING = "warning"
447
+ CRITICAL = "critical"
448
+
449
+
450
+ class CompatibilityLevel(str, Enum):
451
+ """Schema compatibility level."""
452
+
453
+ COMPATIBLE = "compatible"
454
+ MINOR = "minor"
455
+ BREAKING = "breaking"
456
+
457
+
458
+ class RenameConfidence(str, Enum):
459
+ """Confidence level for rename detection."""
460
+
461
+ HIGH = "high"
462
+ MEDIUM = "medium"
463
+ LOW = "low"
464
+
465
+
466
+ class SimilarityAlgorithm(str, Enum):
467
+ """Similarity algorithm for rename detection."""
468
+
469
+ COMPOSITE = "composite"
470
+ LEVENSHTEIN = "levenshtein"
471
+ JARO_WINKLER = "jaro_winkler"
472
+ NGRAM = "ngram"
473
+ TOKEN = "token"
474
+
475
+
476
+ class SchemaChangeDetail(BaseSchema):
477
+ """Detail of a single schema change."""
478
+
479
+ change_type: SchemaChangeType = Field(..., description="Type of change")
480
+ column_name: str = Field(..., description="Affected column name")
481
+ old_value: Any = Field(default=None, description="Previous value")
482
+ new_value: Any = Field(default=None, description="New value")
483
+ severity: SchemaChangeSeverity = Field(..., description="Change severity")
484
+ breaking: bool = Field(default=False, description="Is breaking change")
485
+ description: str = Field(default="", description="Change description")
486
+ migration_hint: str | None = Field(
487
+ default=None, description="Migration suggestion"
488
+ )
489
+
490
+
491
+ class RenameDetectionDetail(BaseSchema):
492
+ """Detail of a detected column rename."""
493
+
494
+ old_name: str = Field(..., description="Original column name")
495
+ new_name: str = Field(..., description="New column name")
496
+ similarity: float = Field(..., ge=0.0, le=1.0, description="Similarity score")
497
+ confidence: RenameConfidence = Field(..., description="Detection confidence")
498
+ reasons: list[str] = Field(default_factory=list, description="Detection reasons")
499
+
500
+
501
+ class RenameDetectionRequest(BaseSchema):
502
+ """Request for rename detection."""
503
+
504
+ added_columns: dict[str, str] = Field(
505
+ ..., description="Added columns {name: type}"
506
+ )
507
+ removed_columns: dict[str, str] = Field(
508
+ ..., description="Removed columns {name: type}"
509
+ )
510
+ similarity_threshold: float = Field(
511
+ default=0.8, ge=0.5, le=1.0, description="Similarity threshold"
512
+ )
513
+ require_type_match: bool = Field(
514
+ default=True, description="Require matching types"
515
+ )
516
+ allow_compatible_types: bool = Field(
517
+ default=True, description="Allow compatible type changes"
518
+ )
519
+ algorithm: SimilarityAlgorithm = Field(
520
+ default=SimilarityAlgorithm.COMPOSITE, description="Similarity algorithm"
521
+ )
522
+
523
+
524
+ class RenameDetectionResponse(BaseSchema):
525
+ """Response from rename detection."""
526
+
527
+ confirmed_renames: list[RenameDetectionDetail] = Field(
528
+ default_factory=list, description="Confirmed renames"
529
+ )
530
+ possible_renames: list[RenameDetectionDetail] = Field(
531
+ default_factory=list, description="Possible renames"
532
+ )
533
+ unmatched_added: list[str] = Field(
534
+ default_factory=list, description="Unmatched added columns"
535
+ )
536
+ unmatched_removed: list[str] = Field(
537
+ default_factory=list, description="Unmatched removed columns"
538
+ )
539
+
540
+
541
+ class SchemaDetectionRequest(BaseSchema):
542
+ """Request for schema change detection."""
543
+
544
+ current_schema: dict[str, Any] = Field(
545
+ ..., description="Current schema {column: type}"
546
+ )
547
+ baseline_schema: dict[str, Any] = Field(
548
+ ..., description="Baseline schema {column: type}"
549
+ )
550
+ detect_renames: bool = Field(
551
+ default=True, description="Enable rename detection"
552
+ )
553
+ rename_similarity_threshold: float = Field(
554
+ default=0.8, ge=0.5, le=1.0, description="Rename threshold"
555
+ )
556
+
557
+
558
+ class SchemaDetectionResponse(BaseSchema):
559
+ """Response from schema change detection."""
560
+
561
+ total_changes: int = Field(..., description="Total changes detected")
562
+ breaking_changes: int = Field(..., description="Breaking changes count")
563
+ compatibility_level: CompatibilityLevel = Field(
564
+ ..., description="Compatibility assessment"
565
+ )
566
+ changes: list[SchemaChangeDetail] = Field(
567
+ default_factory=list, description="List of changes"
568
+ )
569
+
570
+
571
+ # =============================================================================
572
+ # Schema Version History (truthound.profiler.evolution.SchemaHistory)
573
+ # =============================================================================
574
+
575
+
576
+ class SchemaVersionCreate(BaseSchema):
577
+ """Request to create a schema version."""
578
+
579
+ schema: dict[str, Any] = Field(..., description="Schema dictionary")
580
+ version: str | None = Field(
581
+ default=None, description="Explicit version (auto-generated if not provided)"
582
+ )
583
+ metadata: dict[str, Any] | None = Field(
584
+ default=None, description="Optional metadata"
585
+ )
586
+
587
+
588
+ class SchemaVersionResponse(BaseSchema):
589
+ """Schema version response."""
590
+
591
+ id: str = Field(..., description="Version ID")
592
+ version: str = Field(..., description="Version string")
593
+ schema: dict[str, Any] = Field(..., description="Schema dictionary")
594
+ metadata: dict[str, Any] | None = Field(default=None, description="Metadata")
595
+ created_at: datetime | None = Field(default=None, description="Created timestamp")
596
+ has_breaking_changes: bool = Field(
597
+ default=False, description="Has breaking changes from parent"
598
+ )
599
+ changes_from_parent: list[SchemaChangeDetail] | None = Field(
600
+ default=None, description="Changes from parent version"
601
+ )
602
+
603
+
604
+ class SchemaVersionSummary(BaseSchema):
605
+ """Schema version summary for lists."""
606
+
607
+ id: str = Field(..., description="Version ID")
608
+ version: str = Field(..., description="Version string")
609
+ column_count: int = Field(default=0, description="Number of columns")
610
+ created_at: datetime | None = Field(default=None, description="Created timestamp")
611
+ has_breaking_changes: bool = Field(default=False, description="Has breaking changes")
612
+
613
+
614
+ class SchemaDiffRequest(BaseSchema):
615
+ """Request for schema diff."""
616
+
617
+ from_version: str = Field(..., description="Source version")
618
+ to_version: str | None = Field(
619
+ default=None, description="Target version (latest if not provided)"
620
+ )
621
+
622
+
623
+ class SchemaDiffResponse(BaseSchema):
624
+ """Schema diff response."""
625
+
626
+ from_version: str = Field(..., description="Source version")
627
+ to_version: str = Field(..., description="Target version")
628
+ changes: list[SchemaChangeDetail] = Field(
629
+ default_factory=list, description="Changes between versions"
630
+ )
631
+ text_diff: str = Field(default="", description="Human-readable diff")
632
+
633
+
634
+ class SchemaHistoryCreate(BaseSchema):
635
+ """Request to create schema history storage."""
636
+
637
+ storage_path: str = Field(..., description="Path for file storage")
638
+ version_strategy: VersionStrategy = Field(
639
+ default=VersionStrategy.SEMANTIC, description="Version numbering strategy"
640
+ )
641
+ max_versions: int = Field(
642
+ default=100, ge=1, le=1000, description="Maximum versions to keep"
643
+ )
644
+ compress: bool = Field(default=True, description="Compress stored files")
645
+
646
+
647
+ class SchemaHistoryResponse(BaseSchema):
648
+ """Schema history response."""
649
+
650
+ history_id: str = Field(..., description="History instance ID")
651
+ storage_path: str = Field(..., description="Storage path")
652
+ version_strategy: VersionStrategy = Field(..., description="Version strategy")
653
+ max_versions: int = Field(..., description="Maximum versions")
654
+ version_count: int = Field(default=0, description="Current version count")
655
+ latest_version: str | None = Field(default=None, description="Latest version")
656
+
657
+
658
+ class SchemaRollbackRequest(BaseSchema):
659
+ """Request to rollback schema version."""
660
+
661
+ to_version: str = Field(..., description="Version to rollback to")
662
+ reason: str | None = Field(default=None, description="Reason for rollback")
663
+
664
+
665
+ # =============================================================================
666
+ # Impact Analysis (truthound.profiler.evolution.ImpactAnalyzer)
667
+ # =============================================================================
668
+
669
+
670
+ class ConsumerMapping(BaseSchema):
671
+ """Consumer to sources mapping."""
672
+
673
+ consumer: str = Field(..., description="Consumer name")
674
+ sources: list[str] = Field(..., description="Sources the consumer depends on")
675
+
676
+
677
+ class QueryMapping(BaseSchema):
678
+ """Source to queries mapping."""
679
+
680
+ source: str = Field(..., description="Source name")
681
+ queries: list[str] = Field(..., description="Queries using the source")
682
+
683
+
684
+ class ImpactAnalyzerSetup(BaseSchema):
685
+ """Setup request for impact analyzer."""
686
+
687
+ consumers: list[ConsumerMapping] | None = Field(
688
+ default=None, description="Consumer mappings"
689
+ )
690
+ queries: list[QueryMapping] | None = Field(
691
+ default=None, description="Query mappings"
692
+ )
693
+
694
+
695
+ class ImpactAnalysisResponse(BaseSchema):
696
+ """Impact analysis response."""
697
+
698
+ impact_scope: ImpactScope = Field(..., description="Impact scope")
699
+ affected_consumers: list[str] = Field(
700
+ default_factory=list, description="Affected consumers"
701
+ )
702
+ affected_queries: list[str] = Field(
703
+ default_factory=list, description="Affected queries"
704
+ )
705
+ data_risk_level: int = Field(
706
+ default=1, ge=1, le=5, description="Risk level (1-5)"
707
+ )
708
+ recommendations: list[str] = Field(
709
+ default_factory=list, description="Recommendations"
710
+ )
711
+
712
+
713
+ # =============================================================================
714
+ # Scheduler Status
715
+ # =============================================================================
716
+
717
+
718
+ class SchemaWatcherSchedulerStatus(BaseSchema):
719
+ """Status of the schema watcher scheduler."""
720
+
721
+ is_running: bool = Field(..., description="Whether scheduler is running")
722
+ active_watchers: int = Field(default=0, description="Active watchers")
723
+ next_check_at: datetime | None = Field(
724
+ default=None, description="Next scheduled check"
725
+ )
726
+ last_run_at: datetime | None = Field(default=None, description="Last scheduler run")
727
+ pending_checks: int = Field(default=0, description="Pending checks")