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
@@ -1,17 +1,26 @@
1
1
  """Profile API endpoints.
2
2
 
3
- This module provides endpoints for data profiling.
3
+ This module provides endpoints for data profiling and comparison.
4
4
  """
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
8
  from typing import Annotated
9
9
 
10
- from fastapi import APIRouter, HTTPException, Path
10
+ from fastapi import APIRouter, HTTPException, Path, Query
11
11
 
12
- from truthound_dashboard.schemas import ProfileRequest, ProfileResponse
12
+ from truthound_dashboard.schemas import (
13
+ LatestComparisonResponse,
14
+ ProfileComparisonRequest,
15
+ ProfileComparisonResponse,
16
+ ProfileListResponse,
17
+ ProfileRequest,
18
+ ProfileResponse,
19
+ ProfileTrendRequest,
20
+ ProfileTrendResponse,
21
+ )
13
22
 
14
- from .deps import ProfileServiceDep, SourceServiceDep
23
+ from .deps import ProfileComparisonServiceDep, ProfileServiceDep, SourceServiceDep
15
24
 
16
25
  router = APIRouter()
17
26
 
@@ -20,7 +29,7 @@ router = APIRouter()
20
29
  "/sources/{source_id}/profile",
21
30
  response_model=ProfileResponse,
22
31
  summary="Profile source",
23
- description="Run data profiling on a source with optional sampling",
32
+ description="Run data profiling on a source with optional sampling and pattern detection",
24
33
  )
25
34
  async def profile_source(
26
35
  service: ProfileServiceDep,
@@ -30,31 +39,219 @@ async def profile_source(
30
39
  ) -> ProfileResponse:
31
40
  """Run data profiling on a source.
32
41
 
42
+ Supports advanced configuration including:
43
+ - Sampling strategies: none, head, random, systematic, stratified, reservoir, adaptive, hash
44
+ - Pattern detection: email, phone, uuid, url, ip_address, credit_card, etc.
45
+ - Statistical analysis options: histograms, correlations, cardinality
46
+
33
47
  Args:
34
48
  service: Injected profile service.
35
49
  source_service: Injected source service.
36
50
  source_id: Source to profile.
37
- request: Optional profiling configuration with sample_size.
51
+ request: Optional profiling configuration.
38
52
 
39
53
  Returns:
40
- Profiling result with column statistics.
54
+ Profiling result with column statistics, detected patterns, and sampling metadata.
41
55
 
42
56
  Raises:
43
- HTTPException: 404 if source not found.
57
+ HTTPException: 404 if source not found, 500 on profiling error.
44
58
  """
45
59
  # Verify source exists
46
60
  source = await source_service.get_by_id(source_id)
47
61
  if source is None:
48
62
  raise HTTPException(status_code=404, detail="Source not found")
49
63
 
50
- # Extract sample_size from request if provided
51
- sample_size = request.sample_size if request else None
64
+ # Build profiling kwargs from request
65
+ profile_kwargs: dict = {}
66
+
67
+ if request:
68
+ # Handle sampling configuration
69
+ if request.sampling:
70
+ # Advanced sampling config takes precedence
71
+ profile_kwargs["sampling_strategy"] = request.sampling.strategy
72
+ profile_kwargs["sample_size"] = request.sampling.sample_size
73
+ profile_kwargs["confidence_level"] = request.sampling.confidence_level
74
+ profile_kwargs["margin_of_error"] = request.sampling.margin_of_error
75
+ profile_kwargs["strata_column"] = request.sampling.strata_column
76
+ profile_kwargs["seed"] = request.sampling.seed
77
+ elif request.sample_size:
78
+ # Backward compatible simple sample_size
79
+ profile_kwargs["sample_size"] = request.sample_size
80
+
81
+ # Handle pattern detection configuration
82
+ if request.pattern_detection:
83
+ profile_kwargs["enable_pattern_detection"] = request.pattern_detection.enabled
84
+ profile_kwargs["pattern_sample_size"] = request.pattern_detection.sample_size
85
+ profile_kwargs["min_pattern_confidence"] = request.pattern_detection.min_confidence
86
+ profile_kwargs["patterns_to_detect"] = request.pattern_detection.patterns_to_detect
87
+
88
+ # Additional profiling options
89
+ profile_kwargs["include_histograms"] = request.include_histograms
90
+ profile_kwargs["include_correlations"] = request.include_correlations
91
+ profile_kwargs["include_cardinality"] = request.include_cardinality
52
92
 
53
93
  try:
54
- result = await service.profile_source(
55
- source_id,
56
- sample_size=sample_size,
57
- )
94
+ result = await service.profile_source(source_id, **profile_kwargs)
58
95
  return ProfileResponse.from_result(result)
59
96
  except Exception as e:
60
97
  raise HTTPException(status_code=500, detail=str(e))
98
+
99
+
100
+ # =============================================================================
101
+ # Profile History and Comparison Endpoints
102
+ # =============================================================================
103
+
104
+
105
+ @router.get(
106
+ "/sources/{source_id}/profiles",
107
+ response_model=ProfileListResponse,
108
+ summary="List profiles",
109
+ description="Get profile history for a source.",
110
+ )
111
+ async def list_profiles(
112
+ source_id: Annotated[str, Path(description="Source ID")],
113
+ comparison_service: ProfileComparisonServiceDep,
114
+ source_service: SourceServiceDep,
115
+ limit: int = Query(default=20, ge=1, le=100, description="Maximum profiles to return"),
116
+ offset: int = Query(default=0, ge=0, description="Number to skip"),
117
+ ) -> ProfileListResponse:
118
+ """List profile history for a source.
119
+
120
+ Args:
121
+ source_id: Source ID.
122
+ comparison_service: Profile comparison service.
123
+ source_service: Source service.
124
+ limit: Maximum profiles to return.
125
+ offset: Number to skip.
126
+
127
+ Returns:
128
+ List of profile summaries.
129
+ """
130
+ # Verify source exists
131
+ source = await source_service.get_by_id(source_id)
132
+ if source is None:
133
+ raise HTTPException(status_code=404, detail="Source not found")
134
+
135
+ profiles = await comparison_service.list_profiles(
136
+ source_id, limit=limit, offset=offset
137
+ )
138
+
139
+ return ProfileListResponse(
140
+ profiles=profiles,
141
+ total=len(profiles),
142
+ source_id=source_id,
143
+ )
144
+
145
+
146
+ @router.post(
147
+ "/profiles/compare",
148
+ response_model=ProfileComparisonResponse,
149
+ summary="Compare profiles",
150
+ description="Compare two specific profiles.",
151
+ )
152
+ async def compare_profiles(
153
+ request: ProfileComparisonRequest,
154
+ comparison_service: ProfileComparisonServiceDep,
155
+ profile_service: ProfileServiceDep,
156
+ source_service: SourceServiceDep,
157
+ ) -> ProfileComparisonResponse:
158
+ """Compare two profiles.
159
+
160
+ Args:
161
+ request: Comparison request with profile IDs.
162
+ comparison_service: Profile comparison service.
163
+ profile_service: Profile service.
164
+ source_service: Source service.
165
+
166
+ Returns:
167
+ Profile comparison result.
168
+ """
169
+ # Get baseline profile to determine source
170
+ baseline = await profile_service.get(request.baseline_profile_id)
171
+ if baseline is None:
172
+ raise HTTPException(
173
+ status_code=404,
174
+ detail=f"Baseline profile {request.baseline_profile_id} not found",
175
+ )
176
+
177
+ # Get source
178
+ source = await source_service.get_by_id(baseline.source_id)
179
+ if source is None:
180
+ raise HTTPException(status_code=404, detail="Source not found")
181
+
182
+ try:
183
+ result = await comparison_service.compare_profiles(
184
+ source,
185
+ request.baseline_profile_id,
186
+ request.current_profile_id,
187
+ significance_threshold=request.significance_threshold,
188
+ )
189
+ return result
190
+ except ValueError as e:
191
+ raise HTTPException(status_code=400, detail=str(e))
192
+
193
+
194
+ @router.get(
195
+ "/sources/{source_id}/profiles/trend",
196
+ response_model=ProfileTrendResponse,
197
+ summary="Get profile trends",
198
+ description="Get time-series profile trends for a source.",
199
+ )
200
+ async def get_profile_trend(
201
+ source_id: Annotated[str, Path(description="Source ID")],
202
+ comparison_service: ProfileComparisonServiceDep,
203
+ source_service: SourceServiceDep,
204
+ period: str = Query(default="30d", description="Time period (e.g., 7d, 30d, 90d)"),
205
+ granularity: str = Query(default="daily", description="Data granularity"),
206
+ ) -> ProfileTrendResponse:
207
+ """Get profile trends for a source.
208
+
209
+ Args:
210
+ source_id: Source ID.
211
+ comparison_service: Profile comparison service.
212
+ source_service: Source service.
213
+ period: Time period to analyze.
214
+ granularity: Data granularity.
215
+
216
+ Returns:
217
+ Profile trend data.
218
+ """
219
+ # Verify source exists
220
+ source = await source_service.get_by_id(source_id)
221
+ if source is None:
222
+ raise HTTPException(status_code=404, detail="Source not found")
223
+
224
+ result = await comparison_service.get_profile_trend(
225
+ source, period=period, granularity=granularity
226
+ )
227
+
228
+ return result
229
+
230
+
231
+ @router.get(
232
+ "/sources/{source_id}/profiles/latest-comparison",
233
+ response_model=LatestComparisonResponse,
234
+ summary="Compare latest profiles",
235
+ description="Compare the latest profile with the previous one.",
236
+ )
237
+ async def get_latest_profile_comparison(
238
+ source_id: Annotated[str, Path(description="Source ID")],
239
+ comparison_service: ProfileComparisonServiceDep,
240
+ source_service: SourceServiceDep,
241
+ ) -> LatestComparisonResponse:
242
+ """Compare latest profile with previous.
243
+
244
+ Args:
245
+ source_id: Source ID.
246
+ comparison_service: Profile comparison service.
247
+ source_service: Source service.
248
+
249
+ Returns:
250
+ Latest comparison result.
251
+ """
252
+ # Verify source exists
253
+ source = await source_service.get_by_id(source_id)
254
+ if source is None:
255
+ raise HTTPException(status_code=404, detail="Source not found")
256
+
257
+ return await comparison_service.get_latest_comparison(source)