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,428 @@
1
+ """Lineage-related Pydantic schemas.
2
+
3
+ This module defines schemas for data lineage API operations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Literal
9
+
10
+ from pydantic import Field
11
+
12
+ from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
13
+
14
+ # Lineage node types
15
+ LineageNodeType = Literal["source", "transform", "sink"]
16
+
17
+ # Lineage edge types
18
+ LineageEdgeType = Literal["derives_from", "transforms_to", "joins_with", "filters_from"]
19
+
20
+ # Impact analysis direction
21
+ ImpactDirection = Literal["upstream", "downstream", "both"]
22
+
23
+
24
+ # =============================================================================
25
+ # Node Schemas
26
+ # =============================================================================
27
+
28
+
29
+ class LineageNodeBase(BaseSchema):
30
+ """Base lineage node schema with common fields."""
31
+
32
+ name: str = Field(
33
+ ...,
34
+ min_length=1,
35
+ max_length=255,
36
+ description="Human-readable node name",
37
+ examples=["Customer Data", "Sales Transform"],
38
+ )
39
+ node_type: LineageNodeType = Field(
40
+ ...,
41
+ description="Node type: source, transform, or sink",
42
+ examples=["source", "transform"],
43
+ )
44
+ source_id: str | None = Field(
45
+ default=None,
46
+ description="Reference to data source (if type is source)",
47
+ )
48
+ metadata: dict[str, Any] | None = Field(
49
+ default=None,
50
+ description="Additional metadata for the node",
51
+ )
52
+ position_x: float | None = Field(
53
+ default=None,
54
+ description="X coordinate for graph visualization",
55
+ )
56
+ position_y: float | None = Field(
57
+ default=None,
58
+ description="Y coordinate for graph visualization",
59
+ )
60
+
61
+
62
+ class LineageNodeCreate(LineageNodeBase):
63
+ """Schema for creating a new lineage node."""
64
+
65
+ pass
66
+
67
+
68
+ class LineageNodeUpdate(BaseSchema):
69
+ """Schema for updating an existing lineage node."""
70
+
71
+ name: str | None = Field(
72
+ default=None,
73
+ min_length=1,
74
+ max_length=255,
75
+ description="New node name",
76
+ )
77
+ metadata: dict[str, Any] | None = Field(
78
+ default=None,
79
+ description="New metadata",
80
+ )
81
+ position_x: float | None = Field(
82
+ default=None,
83
+ description="New X coordinate",
84
+ )
85
+ position_y: float | None = Field(
86
+ default=None,
87
+ description="New Y coordinate",
88
+ )
89
+
90
+
91
+ class LineageNodeResponse(LineageNodeBase, IDMixin, TimestampMixin):
92
+ """Schema for lineage node responses."""
93
+
94
+ # Additional computed fields
95
+ source_name: str | None = Field(
96
+ default=None,
97
+ description="Name of linked data source",
98
+ )
99
+ upstream_count: int = Field(
100
+ default=0,
101
+ description="Number of upstream nodes",
102
+ )
103
+ downstream_count: int = Field(
104
+ default=0,
105
+ description="Number of downstream nodes",
106
+ )
107
+
108
+
109
+ class LineageNodeSummary(BaseSchema):
110
+ """Minimal node summary for lists and references."""
111
+
112
+ id: str
113
+ name: str
114
+ node_type: LineageNodeType
115
+ source_id: str | None = None
116
+
117
+
118
+ class LineageNodeListResponse(ListResponseWrapper[LineageNodeResponse]):
119
+ """Paginated lineage node list response."""
120
+
121
+ pass
122
+
123
+
124
+ # =============================================================================
125
+ # Edge Schemas
126
+ # =============================================================================
127
+
128
+
129
+ class LineageEdgeBase(BaseSchema):
130
+ """Base lineage edge schema with common fields."""
131
+
132
+ source_node_id: str = Field(
133
+ ...,
134
+ description="ID of the source node (origin of data flow)",
135
+ )
136
+ target_node_id: str = Field(
137
+ ...,
138
+ description="ID of the target node (destination of data flow)",
139
+ )
140
+ edge_type: LineageEdgeType = Field(
141
+ default="derives_from",
142
+ description="Type of relationship between nodes",
143
+ )
144
+ metadata: dict[str, Any] | None = Field(
145
+ default=None,
146
+ description="Additional metadata for the edge (e.g., transformation details)",
147
+ )
148
+
149
+
150
+ class LineageEdgeCreate(LineageEdgeBase):
151
+ """Schema for creating a new lineage edge."""
152
+
153
+ pass
154
+
155
+
156
+ class LineageEdgeResponse(LineageEdgeBase, IDMixin):
157
+ """Schema for lineage edge responses."""
158
+
159
+ created_at: str = Field(..., description="Edge creation timestamp")
160
+
161
+ # Optional enrichment
162
+ source_node_name: str | None = Field(
163
+ default=None,
164
+ description="Name of source node",
165
+ )
166
+ target_node_name: str | None = Field(
167
+ default=None,
168
+ description="Name of target node",
169
+ )
170
+
171
+
172
+ class LineageEdgeListResponse(ListResponseWrapper[LineageEdgeResponse]):
173
+ """Paginated lineage edge list response."""
174
+
175
+ pass
176
+
177
+
178
+ # =============================================================================
179
+ # Graph Schemas
180
+ # =============================================================================
181
+
182
+
183
+ class LineageGraphResponse(BaseSchema):
184
+ """Complete lineage graph with nodes and edges."""
185
+
186
+ nodes: list[LineageNodeResponse] = Field(
187
+ default_factory=list,
188
+ description="All nodes in the graph",
189
+ )
190
+ edges: list[LineageEdgeResponse] = Field(
191
+ default_factory=list,
192
+ description="All edges in the graph",
193
+ )
194
+ total_nodes: int = Field(
195
+ default=0,
196
+ description="Total number of nodes",
197
+ )
198
+ total_edges: int = Field(
199
+ default=0,
200
+ description="Total number of edges",
201
+ )
202
+
203
+
204
+ # =============================================================================
205
+ # Impact Analysis Schemas
206
+ # =============================================================================
207
+
208
+
209
+ class ImpactAnalysisRequest(BaseSchema):
210
+ """Request for impact analysis."""
211
+
212
+ direction: ImpactDirection = Field(
213
+ default="both",
214
+ description="Direction of impact analysis",
215
+ )
216
+ max_depth: int = Field(
217
+ default=10,
218
+ ge=1,
219
+ le=50,
220
+ description="Maximum depth to traverse",
221
+ )
222
+
223
+
224
+ class ImpactAnalysisResponse(BaseSchema):
225
+ """Response for upstream/downstream impact analysis."""
226
+
227
+ root_node_id: str = Field(..., description="Starting node ID")
228
+ root_node_name: str = Field(..., description="Starting node name")
229
+ direction: ImpactDirection = Field(..., description="Analysis direction")
230
+
231
+ upstream_nodes: list[LineageNodeSummary] = Field(
232
+ default_factory=list,
233
+ description="Upstream (source) nodes",
234
+ )
235
+ downstream_nodes: list[LineageNodeSummary] = Field(
236
+ default_factory=list,
237
+ description="Downstream (dependent) nodes",
238
+ )
239
+ affected_sources: list[str] = Field(
240
+ default_factory=list,
241
+ description="IDs of affected data sources",
242
+ )
243
+
244
+ upstream_count: int = Field(default=0, description="Number of upstream nodes")
245
+ downstream_count: int = Field(default=0, description="Number of downstream nodes")
246
+ total_affected: int = Field(default=0, description="Total affected nodes")
247
+
248
+
249
+ # =============================================================================
250
+ # Auto-Discovery Schemas
251
+ # =============================================================================
252
+
253
+
254
+ class AutoDiscoverRequest(BaseSchema):
255
+ """Request to auto-discover lineage from a source."""
256
+
257
+ source_id: str = Field(..., description="Source ID to discover from")
258
+ include_fk_relations: bool = Field(
259
+ default=True,
260
+ description="Include foreign key relationships (for DB sources)",
261
+ )
262
+ max_depth: int = Field(
263
+ default=3,
264
+ ge=1,
265
+ le=10,
266
+ description="Maximum depth for discovery",
267
+ )
268
+
269
+
270
+ class AutoDiscoverResponse(BaseSchema):
271
+ """Response from auto-discovery."""
272
+
273
+ source_id: str = Field(..., description="Source ID that was analyzed")
274
+ discovered_nodes: int = Field(default=0, description="Number of nodes discovered")
275
+ discovered_edges: int = Field(default=0, description="Number of edges discovered")
276
+ graph: LineageGraphResponse = Field(
277
+ ...,
278
+ description="Discovered lineage graph",
279
+ )
280
+
281
+
282
+ # =============================================================================
283
+ # Position Update Schemas
284
+ # =============================================================================
285
+
286
+
287
+ class NodePosition(BaseSchema):
288
+ """Position update for a single node."""
289
+
290
+ id: str = Field(..., description="Node ID")
291
+ x: float = Field(..., description="New X coordinate")
292
+ y: float = Field(..., description="New Y coordinate")
293
+
294
+
295
+ class PositionUpdateRequest(BaseSchema):
296
+ """Batch position update request."""
297
+
298
+ positions: list[NodePosition] = Field(
299
+ ...,
300
+ min_length=1,
301
+ description="List of node positions to update",
302
+ )
303
+
304
+
305
+ class PositionUpdateResponse(BaseSchema):
306
+ """Response from position update."""
307
+
308
+ updated_count: int = Field(..., description="Number of positions updated")
309
+
310
+
311
+ # =============================================================================
312
+ # Anomaly Integration Schemas
313
+ # =============================================================================
314
+
315
+ # Anomaly status levels
316
+ AnomalyStatusLevel = Literal["unknown", "clean", "low", "medium", "high"]
317
+
318
+ # Impact severity levels
319
+ ImpactSeverityLevel = Literal["unknown", "none", "low", "medium", "high", "critical"]
320
+
321
+
322
+ class AnomalyStatus(BaseSchema):
323
+ """Anomaly status for a lineage node."""
324
+
325
+ status: AnomalyStatusLevel = Field(
326
+ default="unknown",
327
+ description="Anomaly status level",
328
+ )
329
+ anomaly_rate: float | None = Field(
330
+ default=None,
331
+ description="Rate of anomalies detected (0.0-1.0)",
332
+ )
333
+ anomaly_count: int | None = Field(
334
+ default=None,
335
+ description="Number of anomalies detected",
336
+ )
337
+ last_detection_at: str | None = Field(
338
+ default=None,
339
+ description="Timestamp of last anomaly detection",
340
+ )
341
+ algorithm: str | None = Field(
342
+ default=None,
343
+ description="Algorithm used for detection",
344
+ )
345
+
346
+
347
+ class LineageNodeWithAnomaly(LineageNodeResponse):
348
+ """Lineage node with anomaly status overlay."""
349
+
350
+ anomaly_status: AnomalyStatus = Field(
351
+ default_factory=AnomalyStatus,
352
+ description="Anomaly detection status for this node",
353
+ )
354
+
355
+
356
+ class LineageGraphWithAnomaliesResponse(BaseSchema):
357
+ """Lineage graph with anomaly status overlay for all nodes."""
358
+
359
+ nodes: list[LineageNodeWithAnomaly] = Field(
360
+ default_factory=list,
361
+ description="Nodes with anomaly status",
362
+ )
363
+ edges: list[LineageEdgeResponse] = Field(
364
+ default_factory=list,
365
+ description="All edges in the graph",
366
+ )
367
+ total_nodes: int = Field(
368
+ default=0,
369
+ description="Total number of nodes",
370
+ )
371
+ total_edges: int = Field(
372
+ default=0,
373
+ description="Total number of edges",
374
+ )
375
+
376
+
377
+ class ImpactedNode(BaseSchema):
378
+ """A node impacted by upstream anomalies."""
379
+
380
+ id: str = Field(..., description="Node ID")
381
+ name: str = Field(..., description="Node name")
382
+ node_type: LineageNodeType = Field(..., description="Node type")
383
+ source_id: str | None = Field(default=None, description="Linked source ID")
384
+ anomaly_status: AnomalyStatus | None = Field(
385
+ default=None,
386
+ description="Own anomaly status if available",
387
+ )
388
+ impact_severity: ImpactSeverityLevel = Field(
389
+ default="unknown",
390
+ description="Severity of impact from upstream anomalies",
391
+ )
392
+
393
+
394
+ class PropagationEdge(BaseSchema):
395
+ """An edge in the anomaly propagation path."""
396
+
397
+ id: str = Field(..., description="Edge ID")
398
+ source_node_id: str = Field(..., description="Source node ID")
399
+ target_node_id: str = Field(..., description="Target node ID")
400
+ edge_type: LineageEdgeType = Field(..., description="Edge type")
401
+
402
+
403
+ class AnomalyImpactResponse(BaseSchema):
404
+ """Response for anomaly impact analysis."""
405
+
406
+ source_node_id: str = Field(..., description="Starting node ID")
407
+ source_node_name: str = Field(..., description="Starting node name")
408
+ source_id: str = Field(..., description="Linked data source ID")
409
+ source_anomaly_status: AnomalyStatus | None = Field(
410
+ default=None,
411
+ description="Anomaly status of the source",
412
+ )
413
+ impacted_nodes: list[ImpactedNode] = Field(
414
+ default_factory=list,
415
+ description="Downstream nodes impacted by anomalies",
416
+ )
417
+ impacted_count: int = Field(
418
+ default=0,
419
+ description="Number of impacted nodes",
420
+ )
421
+ overall_severity: ImpactSeverityLevel = Field(
422
+ default="unknown",
423
+ description="Overall severity of the impact",
424
+ )
425
+ propagation_path: list[PropagationEdge] = Field(
426
+ default_factory=list,
427
+ description="Edges showing anomaly propagation path",
428
+ )
@@ -0,0 +1,154 @@
1
+ """Maintenance and retention policy schemas.
2
+
3
+ This module defines schemas for maintenance and retention policy API operations.
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
+ class RetentionPolicyConfig(BaseSchema):
17
+ """Configuration for data retention policies.
18
+
19
+ Defines how long different types of data are retained.
20
+ """
21
+
22
+ validation_retention_days: int = Field(
23
+ default=90,
24
+ ge=1,
25
+ le=3650,
26
+ description="Days to keep validation records (1-3650)",
27
+ )
28
+ profile_keep_per_source: int = Field(
29
+ default=5,
30
+ ge=1,
31
+ le=100,
32
+ description="Number of profile records to keep per source (1-100)",
33
+ )
34
+ notification_log_retention_days: int = Field(
35
+ default=30,
36
+ ge=1,
37
+ le=365,
38
+ description="Days to keep notification logs (1-365)",
39
+ )
40
+ run_vacuum: bool = Field(
41
+ default=True,
42
+ description="Run SQLite VACUUM after cleanup to reclaim space",
43
+ )
44
+ enabled: bool = Field(
45
+ default=True,
46
+ description="Whether automatic maintenance is enabled",
47
+ )
48
+
49
+
50
+ class RetentionPolicyResponse(RetentionPolicyConfig):
51
+ """Response with current retention policy configuration."""
52
+
53
+ pass
54
+
55
+
56
+ class CleanupResultItem(BaseSchema):
57
+ """Result of a single cleanup task."""
58
+
59
+ task_name: str = Field(..., description="Name of the cleanup task")
60
+ records_deleted: int = Field(
61
+ default=0, ge=0, description="Number of records deleted"
62
+ )
63
+ duration_ms: int = Field(default=0, ge=0, description="Duration in milliseconds")
64
+ success: bool = Field(default=True, description="Whether the task succeeded")
65
+ error: str | None = Field(default=None, description="Error message if failed")
66
+
67
+
68
+ class MaintenanceReportResponse(BaseSchema):
69
+ """Response for maintenance run results."""
70
+
71
+ started_at: datetime = Field(..., description="When maintenance started")
72
+ completed_at: datetime | None = Field(
73
+ default=None, description="When maintenance completed"
74
+ )
75
+ results: list[CleanupResultItem] = Field(
76
+ default_factory=list, description="Results of each cleanup task"
77
+ )
78
+ total_deleted: int = Field(
79
+ default=0, ge=0, description="Total records deleted across all tasks"
80
+ )
81
+ total_duration_ms: int = Field(
82
+ default=0, ge=0, description="Total duration in milliseconds"
83
+ )
84
+ vacuum_performed: bool = Field(
85
+ default=False, description="Whether VACUUM was run"
86
+ )
87
+ vacuum_error: str | None = Field(
88
+ default=None, description="VACUUM error message if failed"
89
+ )
90
+ success: bool = Field(default=True, description="Whether all tasks succeeded")
91
+
92
+
93
+ class CleanupTriggerRequest(BaseSchema):
94
+ """Request to trigger manual cleanup."""
95
+
96
+ tasks: list[str] | None = Field(
97
+ default=None,
98
+ description=(
99
+ "Specific tasks to run. If None, all tasks are run. "
100
+ "Available: validation_cleanup, profile_cleanup, notification_log_cleanup"
101
+ ),
102
+ examples=[["validation_cleanup", "profile_cleanup"]],
103
+ )
104
+ run_vacuum: bool = Field(
105
+ default=False,
106
+ description="Run VACUUM after cleanup (may take time for large databases)",
107
+ )
108
+
109
+
110
+ class MaintenanceStatusResponse(BaseSchema):
111
+ """Response with maintenance system status."""
112
+
113
+ enabled: bool = Field(..., description="Whether automatic maintenance is enabled")
114
+ last_run_at: datetime | None = Field(
115
+ default=None, description="Timestamp of last maintenance run"
116
+ )
117
+ next_scheduled_at: datetime | None = Field(
118
+ default=None, description="Timestamp of next scheduled run"
119
+ )
120
+ config: RetentionPolicyConfig = Field(
121
+ ..., description="Current retention policy configuration"
122
+ )
123
+ available_tasks: list[str] = Field(
124
+ ..., description="List of available cleanup tasks"
125
+ )
126
+
127
+
128
+ class CacheStatsResponse(BaseSchema):
129
+ """Response with cache statistics."""
130
+
131
+ total_entries: int = Field(..., ge=0, description="Total entries in cache")
132
+ expired_entries: int = Field(..., ge=0, description="Number of expired entries")
133
+ valid_entries: int = Field(..., ge=0, description="Number of valid entries")
134
+ max_size: int = Field(..., ge=0, description="Maximum cache size")
135
+ hit_rate: float | None = Field(
136
+ default=None,
137
+ ge=0,
138
+ le=1,
139
+ description="Cache hit rate (0-1) if available",
140
+ )
141
+
142
+
143
+ class CacheClearRequest(BaseSchema):
144
+ """Request to clear cache."""
145
+
146
+ pattern: str | None = Field(
147
+ default=None,
148
+ description="Optional pattern to match keys to clear (prefix match)",
149
+ examples=["validation:", "source:"],
150
+ )
151
+ namespace: str | None = Field(
152
+ default=None,
153
+ description="Specific cache namespace to clear",
154
+ )