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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. truthound_dashboard/api/alerts.py +258 -0
  2. truthound_dashboard/api/anomaly.py +1302 -0
  3. truthound_dashboard/api/cross_alerts.py +352 -0
  4. truthound_dashboard/api/deps.py +143 -0
  5. truthound_dashboard/api/drift_monitor.py +540 -0
  6. truthound_dashboard/api/lineage.py +1151 -0
  7. truthound_dashboard/api/maintenance.py +363 -0
  8. truthound_dashboard/api/middleware.py +373 -1
  9. truthound_dashboard/api/model_monitoring.py +805 -0
  10. truthound_dashboard/api/notifications_advanced.py +2452 -0
  11. truthound_dashboard/api/plugins.py +2096 -0
  12. truthound_dashboard/api/profile.py +211 -14
  13. truthound_dashboard/api/reports.py +853 -0
  14. truthound_dashboard/api/router.py +147 -0
  15. truthound_dashboard/api/rule_suggestions.py +310 -0
  16. truthound_dashboard/api/schema_evolution.py +231 -0
  17. truthound_dashboard/api/sources.py +47 -3
  18. truthound_dashboard/api/triggers.py +190 -0
  19. truthound_dashboard/api/validations.py +13 -0
  20. truthound_dashboard/api/validators.py +333 -4
  21. truthound_dashboard/api/versioning.py +309 -0
  22. truthound_dashboard/api/websocket.py +301 -0
  23. truthound_dashboard/core/__init__.py +27 -0
  24. truthound_dashboard/core/anomaly.py +1395 -0
  25. truthound_dashboard/core/anomaly_explainer.py +633 -0
  26. truthound_dashboard/core/cache.py +206 -0
  27. truthound_dashboard/core/cached_services.py +422 -0
  28. truthound_dashboard/core/charts.py +352 -0
  29. truthound_dashboard/core/connections.py +1069 -42
  30. truthound_dashboard/core/cross_alerts.py +837 -0
  31. truthound_dashboard/core/drift_monitor.py +1477 -0
  32. truthound_dashboard/core/drift_sampling.py +669 -0
  33. truthound_dashboard/core/i18n/__init__.py +42 -0
  34. truthound_dashboard/core/i18n/detector.py +173 -0
  35. truthound_dashboard/core/i18n/messages.py +564 -0
  36. truthound_dashboard/core/lineage.py +971 -0
  37. truthound_dashboard/core/maintenance.py +443 -5
  38. truthound_dashboard/core/model_monitoring.py +1043 -0
  39. truthound_dashboard/core/notifications/channels.py +1020 -1
  40. truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
  41. truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
  42. truthound_dashboard/core/notifications/deduplication/service.py +400 -0
  43. truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
  44. truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
  45. truthound_dashboard/core/notifications/dispatcher.py +43 -0
  46. truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
  47. truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
  48. truthound_dashboard/core/notifications/escalation/engine.py +429 -0
  49. truthound_dashboard/core/notifications/escalation/models.py +336 -0
  50. truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
  51. truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
  52. truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
  53. truthound_dashboard/core/notifications/events.py +49 -0
  54. truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
  55. truthound_dashboard/core/notifications/metrics/base.py +528 -0
  56. truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
  57. truthound_dashboard/core/notifications/routing/__init__.py +169 -0
  58. truthound_dashboard/core/notifications/routing/combinators.py +184 -0
  59. truthound_dashboard/core/notifications/routing/config.py +375 -0
  60. truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
  61. truthound_dashboard/core/notifications/routing/engine.py +382 -0
  62. truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
  63. truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
  64. truthound_dashboard/core/notifications/routing/rules.py +625 -0
  65. truthound_dashboard/core/notifications/routing/validator.py +678 -0
  66. truthound_dashboard/core/notifications/service.py +2 -0
  67. truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
  68. truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
  69. truthound_dashboard/core/notifications/throttling/builder.py +311 -0
  70. truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
  71. truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
  72. truthound_dashboard/core/openlineage.py +1028 -0
  73. truthound_dashboard/core/plugins/__init__.py +39 -0
  74. truthound_dashboard/core/plugins/docs/__init__.py +39 -0
  75. truthound_dashboard/core/plugins/docs/extractor.py +703 -0
  76. truthound_dashboard/core/plugins/docs/renderers.py +804 -0
  77. truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
  78. truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
  79. truthound_dashboard/core/plugins/hooks/manager.py +403 -0
  80. truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
  81. truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
  82. truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
  83. truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
  84. truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
  85. truthound_dashboard/core/plugins/loader.py +504 -0
  86. truthound_dashboard/core/plugins/registry.py +810 -0
  87. truthound_dashboard/core/plugins/reporter_executor.py +588 -0
  88. truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
  89. truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
  90. truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
  91. truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
  92. truthound_dashboard/core/plugins/sandbox.py +617 -0
  93. truthound_dashboard/core/plugins/security/__init__.py +68 -0
  94. truthound_dashboard/core/plugins/security/analyzer.py +535 -0
  95. truthound_dashboard/core/plugins/security/policies.py +311 -0
  96. truthound_dashboard/core/plugins/security/protocols.py +296 -0
  97. truthound_dashboard/core/plugins/security/signing.py +842 -0
  98. truthound_dashboard/core/plugins/security.py +446 -0
  99. truthound_dashboard/core/plugins/validator_executor.py +401 -0
  100. truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
  101. truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
  102. truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
  103. truthound_dashboard/core/plugins/versioning/semver.py +266 -0
  104. truthound_dashboard/core/profile_comparison.py +601 -0
  105. truthound_dashboard/core/report_history.py +570 -0
  106. truthound_dashboard/core/reporters/__init__.py +57 -0
  107. truthound_dashboard/core/reporters/base.py +296 -0
  108. truthound_dashboard/core/reporters/csv_reporter.py +155 -0
  109. truthound_dashboard/core/reporters/html_reporter.py +598 -0
  110. truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
  111. truthound_dashboard/core/reporters/i18n/base.py +494 -0
  112. truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
  113. truthound_dashboard/core/reporters/json_reporter.py +160 -0
  114. truthound_dashboard/core/reporters/junit_reporter.py +233 -0
  115. truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
  116. truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
  117. truthound_dashboard/core/reporters/registry.py +272 -0
  118. truthound_dashboard/core/rule_generator.py +2088 -0
  119. truthound_dashboard/core/scheduler.py +822 -12
  120. truthound_dashboard/core/schema_evolution.py +858 -0
  121. truthound_dashboard/core/services.py +152 -9
  122. truthound_dashboard/core/statistics.py +718 -0
  123. truthound_dashboard/core/streaming_anomaly.py +883 -0
  124. truthound_dashboard/core/triggers/__init__.py +45 -0
  125. truthound_dashboard/core/triggers/base.py +226 -0
  126. truthound_dashboard/core/triggers/evaluators.py +609 -0
  127. truthound_dashboard/core/triggers/factory.py +363 -0
  128. truthound_dashboard/core/unified_alerts.py +870 -0
  129. truthound_dashboard/core/validation_limits.py +509 -0
  130. truthound_dashboard/core/versioning.py +709 -0
  131. truthound_dashboard/core/websocket/__init__.py +59 -0
  132. truthound_dashboard/core/websocket/manager.py +512 -0
  133. truthound_dashboard/core/websocket/messages.py +130 -0
  134. truthound_dashboard/db/__init__.py +30 -0
  135. truthound_dashboard/db/models.py +3375 -3
  136. truthound_dashboard/main.py +22 -0
  137. truthound_dashboard/schemas/__init__.py +396 -1
  138. truthound_dashboard/schemas/anomaly.py +1258 -0
  139. truthound_dashboard/schemas/base.py +4 -0
  140. truthound_dashboard/schemas/cross_alerts.py +334 -0
  141. truthound_dashboard/schemas/drift_monitor.py +890 -0
  142. truthound_dashboard/schemas/lineage.py +428 -0
  143. truthound_dashboard/schemas/maintenance.py +154 -0
  144. truthound_dashboard/schemas/model_monitoring.py +374 -0
  145. truthound_dashboard/schemas/notifications_advanced.py +1363 -0
  146. truthound_dashboard/schemas/openlineage.py +704 -0
  147. truthound_dashboard/schemas/plugins.py +1293 -0
  148. truthound_dashboard/schemas/profile.py +420 -34
  149. truthound_dashboard/schemas/profile_comparison.py +242 -0
  150. truthound_dashboard/schemas/reports.py +285 -0
  151. truthound_dashboard/schemas/rule_suggestion.py +434 -0
  152. truthound_dashboard/schemas/schema_evolution.py +164 -0
  153. truthound_dashboard/schemas/source.py +117 -2
  154. truthound_dashboard/schemas/triggers.py +511 -0
  155. truthound_dashboard/schemas/unified_alerts.py +223 -0
  156. truthound_dashboard/schemas/validation.py +25 -1
  157. truthound_dashboard/schemas/validators/__init__.py +11 -0
  158. truthound_dashboard/schemas/validators/base.py +151 -0
  159. truthound_dashboard/schemas/versioning.py +152 -0
  160. truthound_dashboard/static/index.html +2 -2
  161. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/METADATA +142 -22
  162. truthound_dashboard-1.4.0.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.0.dist-info}/WHEEL +0 -0
  168. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/entry_points.txt +0 -0
  169. {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,434 @@
1
+ """Rule suggestion Pydantic schemas.
2
+
3
+ This module defines schemas for automatic rule generation
4
+ from profile data, including suggestions and application.
5
+
6
+ Features:
7
+ - Strictness levels (loose, medium, strict)
8
+ - Preset templates (default, comprehensive, minimal, etc.)
9
+ - Multiple export formats (yaml, json, python, toml)
10
+ - Category-based filtering
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from datetime import datetime
16
+ from enum import Enum
17
+ from typing import Any
18
+
19
+ from pydantic import Field
20
+
21
+ from .base import BaseSchema
22
+
23
+
24
+ # =============================================================================
25
+ # Enums for Rule Generation Options
26
+ # =============================================================================
27
+
28
+
29
+ class StrictnessLevel(str, Enum):
30
+ """Strictness level for rule generation.
31
+
32
+ Determines how permissive or strict generated rules will be.
33
+ """
34
+
35
+ LOOSE = "loose" # Permissive thresholds, fewer rules
36
+ MEDIUM = "medium" # Balanced defaults (recommended)
37
+ STRICT = "strict" # Tight thresholds, comprehensive rules
38
+
39
+
40
+ class RulePreset(str, Enum):
41
+ """Preset templates for rule generation.
42
+
43
+ Pre-configured combinations of categories and settings.
44
+ """
45
+
46
+ DEFAULT = "default" # General purpose
47
+ STRICT = "strict" # Production data
48
+ LOOSE = "loose" # Development/testing
49
+ MINIMAL = "minimal" # Essential rules only
50
+ COMPREHENSIVE = "comprehensive" # All available rules
51
+ CI_CD = "ci_cd" # Optimized for CI/CD pipelines
52
+ SCHEMA_ONLY = "schema_only" # Structure validation only
53
+ FORMAT_ONLY = "format_only" # Format/pattern rules only
54
+ CROSS_COLUMN = "cross_column" # Focus on cross-column relationships
55
+ DATA_INTEGRITY = "data_integrity" # Comprehensive data integrity
56
+
57
+
58
+ class RuleExportFormat(str, Enum):
59
+ """Export format for generated rules."""
60
+
61
+ YAML = "yaml" # Human-readable (default)
62
+ JSON = "json" # Machine-readable
63
+ PYTHON = "python" # Executable Python code
64
+ TOML = "toml" # Config-friendly
65
+
66
+
67
+ class RuleCategory(str, Enum):
68
+ """Categories of validation rules."""
69
+
70
+ SCHEMA = "schema" # Column existence, type constraints
71
+ STATISTICS = "stats" # Range, threshold rules
72
+ PATTERN = "pattern" # Regex, format rules
73
+ COMPLETENESS = "completeness" # Null ratio rules
74
+ UNIQUENESS = "uniqueness" # Primary key, unique constraints
75
+ DISTRIBUTION = "distribution" # Allowed values, cardinality
76
+ RELATIONSHIP = "relationship" # Cross-column relationships
77
+ MULTI_COLUMN = "multi_column" # Multi-column validations
78
+
79
+
80
+ class CrossColumnRuleType(str, Enum):
81
+ """Types of cross-column validation rules."""
82
+
83
+ # Composite Key Rules
84
+ COMPOSITE_KEY = "composite_key" # Multi-column uniqueness
85
+
86
+ # Arithmetic Relationships
87
+ COLUMN_SUM = "column_sum" # columns sum to target
88
+ COLUMN_PRODUCT = "column_product" # columns multiply to target
89
+ COLUMN_DIFFERENCE = "column_difference" # a - b = expected
90
+ COLUMN_RATIO = "column_ratio" # a / b = expected
91
+ COLUMN_PERCENTAGE = "column_percentage" # percentage relationship
92
+
93
+ # Comparison Relationships
94
+ COLUMN_COMPARISON = "column_comparison" # a > b, a < b, etc.
95
+ COLUMN_CHAIN_COMPARISON = "column_chain_comparison" # a < b < c
96
+
97
+ # Dependency Relationships
98
+ COLUMN_DEPENDENCY = "column_dependency" # functional dependency
99
+ COLUMN_IMPLICATION = "column_implication" # if A then B
100
+ COLUMN_COEXISTENCE = "column_coexistence" # all null or all non-null
101
+ COLUMN_MUTUAL_EXCLUSIVITY = "column_mutual_exclusivity" # at most one non-null
102
+
103
+ # Statistical Relationships
104
+ COLUMN_CORRELATION = "column_correlation" # correlation between columns
105
+
106
+ # Referential Integrity
107
+ REFERENTIAL_INTEGRITY = "referential_integrity" # foreign key validation
108
+
109
+
110
+ # =============================================================================
111
+ # Suggested Rule Schemas
112
+ # =============================================================================
113
+
114
+
115
+ class SuggestedRule(BaseSchema):
116
+ """A single suggested validation rule."""
117
+
118
+ column: str = Field(..., description="Target column name (or primary column for multi-column rules)")
119
+ validator_name: str = Field(..., description="Validator to apply")
120
+ params: dict[str, Any] = Field(
121
+ default_factory=dict, description="Validator parameters"
122
+ )
123
+ confidence: float = Field(
124
+ ...,
125
+ ge=0.0,
126
+ le=1.0,
127
+ description="Confidence score (0.0 to 1.0)",
128
+ )
129
+ reason: str = Field(..., description="Why this rule is suggested")
130
+ severity_suggestion: str = Field(
131
+ default="medium",
132
+ description="Suggested severity level",
133
+ )
134
+ category: RuleCategory | str = Field(
135
+ default=RuleCategory.SCHEMA,
136
+ description="Rule category",
137
+ )
138
+ # Cross-column rule fields
139
+ is_cross_column: bool = Field(
140
+ default=False,
141
+ description="Whether this is a cross-column rule",
142
+ )
143
+ related_columns: list[str] = Field(
144
+ default_factory=list,
145
+ description="Additional columns involved in cross-column rules",
146
+ )
147
+ cross_column_type: CrossColumnRuleType | None = Field(
148
+ default=None,
149
+ description="Type of cross-column relationship (if applicable)",
150
+ )
151
+
152
+
153
+ class CrossColumnRuleSuggestion(BaseSchema):
154
+ """A suggested cross-column validation rule with detailed relationship info."""
155
+
156
+ id: str = Field(default_factory=lambda: "", description="Unique suggestion ID")
157
+ rule_type: CrossColumnRuleType = Field(..., description="Type of cross-column rule")
158
+ columns: list[str] = Field(..., description="Columns involved in the relationship")
159
+ validator_name: str = Field(..., description="Validator to apply")
160
+ params: dict[str, Any] = Field(
161
+ default_factory=dict, description="Validator parameters"
162
+ )
163
+ confidence: float = Field(
164
+ ...,
165
+ ge=0.0,
166
+ le=1.0,
167
+ description="Confidence score (0.0 to 1.0)",
168
+ )
169
+ reason: str = Field(..., description="Why this rule is suggested")
170
+ severity_suggestion: str = Field(
171
+ default="medium",
172
+ description="Suggested severity level (high, medium, low)",
173
+ )
174
+ evidence: dict[str, Any] = Field(
175
+ default_factory=dict,
176
+ description="Statistical evidence supporting the suggestion",
177
+ )
178
+ sample_violations: list[dict[str, Any]] = Field(
179
+ default_factory=list,
180
+ description="Sample rows that would violate this rule",
181
+ )
182
+
183
+
184
+ class RuleSuggestionRequest(BaseSchema):
185
+ """Request to generate rule suggestions."""
186
+
187
+ use_latest_profile: bool = Field(
188
+ default=True,
189
+ description="Use the latest profile for suggestions",
190
+ )
191
+ profile_id: str | None = Field(
192
+ default=None,
193
+ description="Specific profile ID to use (if not latest)",
194
+ )
195
+ min_confidence: float = Field(
196
+ default=0.5,
197
+ ge=0.0,
198
+ le=1.0,
199
+ description="Minimum confidence threshold for suggestions",
200
+ )
201
+
202
+ # Advanced options
203
+ strictness: StrictnessLevel = Field(
204
+ default=StrictnessLevel.MEDIUM,
205
+ description="Strictness level for generated rules",
206
+ )
207
+ preset: RulePreset | None = Field(
208
+ default=None,
209
+ description="Preset template to use (overrides category settings)",
210
+ )
211
+ include_categories: list[RuleCategory] | None = Field(
212
+ default=None,
213
+ description="Only include rules from these categories",
214
+ )
215
+ exclude_categories: list[RuleCategory] | None = Field(
216
+ default=None,
217
+ description="Exclude rules from these categories",
218
+ )
219
+ include_types: list[str] | None = Field(
220
+ default=None,
221
+ description="Only suggest rules for these validator types",
222
+ )
223
+ exclude_columns: list[str] | None = Field(
224
+ default=None,
225
+ description="Columns to exclude from suggestions",
226
+ )
227
+
228
+ # Cross-column rule options
229
+ enable_cross_column: bool = Field(
230
+ default=True,
231
+ description="Enable cross-column rule suggestions",
232
+ )
233
+ include_cross_column_types: list[CrossColumnRuleType] | None = Field(
234
+ default=None,
235
+ description="Only include these cross-column rule types",
236
+ )
237
+ exclude_cross_column_types: list[CrossColumnRuleType] | None = Field(
238
+ default=None,
239
+ description="Exclude these cross-column rule types",
240
+ )
241
+ sample_data_rows: int = Field(
242
+ default=1000,
243
+ ge=100,
244
+ le=10000,
245
+ description="Number of sample data rows to analyze for cross-column rules",
246
+ )
247
+
248
+
249
+ class RuleSuggestionResponse(BaseSchema):
250
+ """Response containing suggested rules."""
251
+
252
+ source_id: str = Field(..., description="Source ID")
253
+ source_name: str = Field(..., description="Source name")
254
+ profile_id: str = Field(..., description="Profile ID used for suggestions")
255
+ suggestions: list[SuggestedRule] = Field(
256
+ default_factory=list, description="List of single-column suggested rules"
257
+ )
258
+ cross_column_suggestions: list[CrossColumnRuleSuggestion] = Field(
259
+ default_factory=list, description="List of cross-column suggested rules"
260
+ )
261
+ total_suggestions: int = Field(default=0, description="Total suggestions count (single + cross-column)")
262
+ high_confidence_count: int = Field(
263
+ default=0, description="Suggestions with confidence >= 0.8"
264
+ )
265
+ cross_column_count: int = Field(
266
+ default=0, description="Number of cross-column suggestions"
267
+ )
268
+ generated_at: datetime = Field(..., description="When suggestions were generated")
269
+
270
+ # Generation settings used
271
+ strictness: StrictnessLevel = Field(
272
+ default=StrictnessLevel.MEDIUM,
273
+ description="Strictness level used for generation",
274
+ )
275
+ preset: RulePreset | None = Field(
276
+ default=None,
277
+ description="Preset template used (if any)",
278
+ )
279
+ categories_included: list[RuleCategory] = Field(
280
+ default_factory=list,
281
+ description="Rule categories included in suggestions",
282
+ )
283
+
284
+ # Category breakdown
285
+ by_category: dict[str, int] = Field(
286
+ default_factory=dict,
287
+ description="Count of suggestions by category",
288
+ )
289
+
290
+ # Cross-column breakdown
291
+ by_cross_column_type: dict[str, int] = Field(
292
+ default_factory=dict,
293
+ description="Count of cross-column suggestions by type",
294
+ )
295
+
296
+
297
+ # =============================================================================
298
+ # Apply Rules Schemas
299
+ # =============================================================================
300
+
301
+
302
+ class ApplyRulesRequest(BaseSchema):
303
+ """Request to apply selected rule suggestions."""
304
+
305
+ suggestions: list[SuggestedRule] = Field(
306
+ ..., description="Selected suggestions to apply"
307
+ )
308
+ create_new_rule: bool = Field(
309
+ default=True,
310
+ description="Create a new rule set (vs updating existing)",
311
+ )
312
+ rule_name: str | None = Field(
313
+ default=None,
314
+ description="Name for the new rule set",
315
+ )
316
+ rule_description: str | None = Field(
317
+ default=None,
318
+ description="Description for the new rule set",
319
+ )
320
+
321
+
322
+ class ApplyRulesResponse(BaseSchema):
323
+ """Response after applying rule suggestions."""
324
+
325
+ source_id: str = Field(..., description="Source ID")
326
+ rule_id: str = Field(..., description="Created/updated rule ID")
327
+ rule_name: str = Field(..., description="Rule name")
328
+ applied_count: int = Field(..., description="Number of rules applied")
329
+ validators: list[str] = Field(
330
+ default_factory=list, description="Applied validator names"
331
+ )
332
+ created_at: datetime = Field(..., description="When rule was created/updated")
333
+
334
+
335
+ # =============================================================================
336
+ # Suggestion Statistics
337
+ # =============================================================================
338
+
339
+
340
+ class SuggestionStats(BaseSchema):
341
+ """Statistics about rule suggestions for a source."""
342
+
343
+ source_id: str = Field(..., description="Source ID")
344
+ last_suggestion_at: datetime | None = Field(
345
+ default=None, description="Last suggestion generation time"
346
+ )
347
+ total_suggestions_generated: int = Field(
348
+ default=0, description="Total suggestions ever generated"
349
+ )
350
+ total_suggestions_applied: int = Field(
351
+ default=0, description="Total suggestions applied"
352
+ )
353
+ suggestion_types: dict[str, int] = Field(
354
+ default_factory=dict,
355
+ description="Count of suggestions by validator type",
356
+ )
357
+
358
+
359
+ # =============================================================================
360
+ # Export Schemas
361
+ # =============================================================================
362
+
363
+
364
+ class ExportRulesRequest(BaseSchema):
365
+ """Request to export generated rules in various formats."""
366
+
367
+ suggestions: list[SuggestedRule] = Field(
368
+ ..., description="Rules to export"
369
+ )
370
+ format: RuleExportFormat = Field(
371
+ default=RuleExportFormat.YAML,
372
+ description="Export format",
373
+ )
374
+ include_metadata: bool = Field(
375
+ default=True,
376
+ description="Include generation metadata in export",
377
+ )
378
+ rule_name: str = Field(
379
+ default="auto_generated_rules",
380
+ description="Name for the exported rule set",
381
+ )
382
+ description: str | None = Field(
383
+ default=None,
384
+ description="Description for the rule set",
385
+ )
386
+
387
+
388
+ class ExportRulesResponse(BaseSchema):
389
+ """Response containing exported rules content."""
390
+
391
+ content: str = Field(..., description="Exported content in requested format")
392
+ format: RuleExportFormat = Field(..., description="Export format used")
393
+ filename: str = Field(..., description="Suggested filename")
394
+ rule_count: int = Field(..., description="Number of rules exported")
395
+ generated_at: datetime = Field(..., description="When export was generated")
396
+
397
+
398
+ # =============================================================================
399
+ # Preset Configuration
400
+ # =============================================================================
401
+
402
+
403
+ class PresetInfo(BaseSchema):
404
+ """Information about a rule generation preset."""
405
+
406
+ name: RulePreset = Field(..., description="Preset name")
407
+ display_name: str = Field(..., description="Human-readable name")
408
+ description: str = Field(..., description="What this preset does")
409
+ strictness: StrictnessLevel = Field(..., description="Default strictness")
410
+ categories: list[RuleCategory] = Field(
411
+ default_factory=list,
412
+ description="Categories included in this preset",
413
+ )
414
+ recommended_for: str = Field(
415
+ default="",
416
+ description="Use case this preset is recommended for",
417
+ )
418
+
419
+
420
+ class PresetsResponse(BaseSchema):
421
+ """Response listing available presets."""
422
+
423
+ presets: list[PresetInfo] = Field(
424
+ default_factory=list, description="Available presets"
425
+ )
426
+ strictness_levels: list[str] = Field(
427
+ default_factory=list, description="Available strictness levels"
428
+ )
429
+ categories: list[str] = Field(
430
+ default_factory=list, description="Available categories"
431
+ )
432
+ export_formats: list[str] = Field(
433
+ default_factory=list, description="Available export formats"
434
+ )
@@ -0,0 +1,164 @@
1
+ """Schema evolution Pydantic schemas.
2
+
3
+ This module defines schemas for schema evolution detection,
4
+ version tracking, and change notifications.
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
14
+
15
+ from .base import BaseSchema, IDMixin, TimestampMixin
16
+
17
+
18
+ class SchemaChangeType(str, Enum):
19
+ """Type of schema change."""
20
+
21
+ COLUMN_ADDED = "column_added"
22
+ COLUMN_REMOVED = "column_removed"
23
+ TYPE_CHANGED = "type_changed"
24
+ NULLABLE_CHANGED = "nullable_changed"
25
+ CONSTRAINT_CHANGED = "constraint_changed"
26
+ COLUMN_RENAMED = "column_renamed"
27
+
28
+
29
+ class SchemaChangeSeverity(str, Enum):
30
+ """Severity level of schema change."""
31
+
32
+ BREAKING = "breaking"
33
+ WARNING = "warning"
34
+ NON_BREAKING = "non_breaking"
35
+
36
+
37
+ # =============================================================================
38
+ # Schema Version Schemas
39
+ # =============================================================================
40
+
41
+
42
+ class SchemaVersionBase(BaseSchema):
43
+ """Base schema for schema version."""
44
+
45
+ version_number: int = Field(..., description="Sequential version number")
46
+ column_count: int = Field(..., description="Number of columns in this version")
47
+ columns: list[str] = Field(
48
+ default_factory=list, description="List of column names"
49
+ )
50
+
51
+
52
+ class SchemaVersionResponse(SchemaVersionBase, IDMixin, TimestampMixin):
53
+ """Schema version response."""
54
+
55
+ source_id: str = Field(..., description="Source ID")
56
+ schema_id: str = Field(..., description="Schema record ID")
57
+ schema_hash: str = Field(..., description="SHA256 hash of schema structure")
58
+ column_snapshot: dict[str, Any] = Field(
59
+ default_factory=dict, description="Full column definitions"
60
+ )
61
+
62
+
63
+ class SchemaVersionSummary(BaseSchema):
64
+ """Summary of a schema version for lists."""
65
+
66
+ id: str = Field(..., description="Version ID")
67
+ version_number: int = Field(..., description="Version number")
68
+ column_count: int = Field(..., description="Number of columns")
69
+ created_at: datetime = Field(..., description="When version was created")
70
+
71
+
72
+ class SchemaVersionListResponse(BaseSchema):
73
+ """List response for schema versions."""
74
+
75
+ versions: list[SchemaVersionSummary] = Field(
76
+ default_factory=list, description="List of schema versions"
77
+ )
78
+ total: int = Field(default=0, description="Total count")
79
+ source_id: str = Field(..., description="Source ID")
80
+
81
+
82
+ # =============================================================================
83
+ # Schema Change Schemas
84
+ # =============================================================================
85
+
86
+
87
+ class SchemaChangeBase(BaseSchema):
88
+ """Base schema for individual change."""
89
+
90
+ change_type: SchemaChangeType = Field(..., description="Type of change")
91
+ column_name: str = Field(..., description="Affected column name")
92
+ old_value: str | None = Field(default=None, description="Previous value")
93
+ new_value: str | None = Field(default=None, description="New value")
94
+ severity: SchemaChangeSeverity = Field(
95
+ default=SchemaChangeSeverity.NON_BREAKING,
96
+ description="Severity of the change",
97
+ )
98
+
99
+
100
+ class SchemaChangeResponse(SchemaChangeBase, IDMixin):
101
+ """Response schema for a single schema change."""
102
+
103
+ source_id: str = Field(..., description="Source ID")
104
+ from_version_id: str | None = Field(
105
+ default=None, description="Previous version ID"
106
+ )
107
+ to_version_id: str = Field(..., description="New version ID")
108
+ description: str = Field(..., description="Human-readable description")
109
+ created_at: datetime = Field(..., description="When change was detected")
110
+
111
+
112
+ class SchemaChangeListResponse(BaseSchema):
113
+ """List response for schema changes."""
114
+
115
+ changes: list[SchemaChangeResponse] = Field(
116
+ default_factory=list, description="List of changes"
117
+ )
118
+ total: int = Field(default=0, description="Total count")
119
+ source_id: str = Field(..., description="Source ID")
120
+
121
+
122
+ # =============================================================================
123
+ # Schema Evolution Detection Schemas
124
+ # =============================================================================
125
+
126
+
127
+ class SchemaEvolutionRequest(BaseSchema):
128
+ """Request to detect schema changes."""
129
+
130
+ force_relearn: bool = Field(
131
+ default=False,
132
+ description="Force re-learning schema even if unchanged",
133
+ )
134
+
135
+
136
+ class SchemaEvolutionResponse(BaseSchema):
137
+ """Response for schema evolution detection."""
138
+
139
+ source_id: str = Field(..., description="Source ID")
140
+ source_name: str = Field(..., description="Source name")
141
+ from_version: int | None = Field(
142
+ default=None, description="Previous version number"
143
+ )
144
+ to_version: int = Field(..., description="New version number")
145
+ has_changes: bool = Field(..., description="Whether changes were detected")
146
+ total_changes: int = Field(default=0, description="Total number of changes")
147
+ breaking_changes: int = Field(default=0, description="Number of breaking changes")
148
+ changes: list[SchemaChangeResponse] = Field(
149
+ default_factory=list, description="List of detected changes"
150
+ )
151
+ detected_at: datetime = Field(..., description="When detection was performed")
152
+
153
+
154
+ class SchemaEvolutionSummary(BaseSchema):
155
+ """Summary of schema evolution for a source."""
156
+
157
+ source_id: str = Field(..., description="Source ID")
158
+ current_version: int = Field(..., description="Current version number")
159
+ total_versions: int = Field(..., description="Total number of versions")
160
+ total_changes: int = Field(..., description="Total changes across all versions")
161
+ breaking_changes: int = Field(..., description="Total breaking changes")
162
+ last_change_at: datetime | None = Field(
163
+ default=None, description="Last change timestamp"
164
+ )