truthound-dashboard 1.4.4__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.4.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.4.dist-info/METADATA +0 -507
  203. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,10 @@
1
1
  """Profile API endpoints.
2
2
 
3
3
  This module provides endpoints for data profiling and comparison.
4
+
5
+ Note: truthound's th.profile() only supports (data, source) parameters.
6
+ Advanced options like sampling strategies, pattern detection configuration,
7
+ and correlation analysis are NOT supported by the underlying library.
4
8
  """
5
9
 
6
10
  from __future__ import annotations
@@ -11,12 +15,12 @@ from fastapi import APIRouter, HTTPException, Path, Query
11
15
 
12
16
  from truthound_dashboard.schemas import (
13
17
  LatestComparisonResponse,
18
+ ProfileAdvancedRequest,
14
19
  ProfileComparisonRequest,
15
20
  ProfileComparisonResponse,
16
21
  ProfileListResponse,
17
22
  ProfileRequest,
18
23
  ProfileResponse,
19
- ProfileTrendRequest,
20
24
  ProfileTrendResponse,
21
25
  )
22
26
 
@@ -29,7 +33,7 @@ router = APIRouter()
29
33
  "/sources/{source_id}/profile",
30
34
  response_model=ProfileResponse,
31
35
  summary="Profile source",
32
- description="Run data profiling on a source with optional sampling and pattern detection",
36
+ description="Run data profiling on a source",
33
37
  )
34
38
  async def profile_source(
35
39
  service: ProfileServiceDep,
@@ -39,19 +43,17 @@ async def profile_source(
39
43
  ) -> ProfileResponse:
40
44
  """Run data profiling on a source.
41
45
 
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
+ Note: truthound's th.profile() only supports (data, source) parameters.
47
+ Advanced configuration options are not supported.
46
48
 
47
49
  Args:
48
50
  service: Injected profile service.
49
51
  source_service: Injected source service.
50
52
  source_id: Source to profile.
51
- request: Optional profiling configuration.
53
+ request: Optional request body (not used, kept for API compatibility).
52
54
 
53
55
  Returns:
54
- Profiling result with column statistics, detected patterns, and sampling metadata.
56
+ Profiling result with column statistics.
55
57
 
56
58
  Raises:
57
59
  HTTPException: 404 if source not found, 500 on profiling error.
@@ -61,38 +63,73 @@ async def profile_source(
61
63
  if source is None:
62
64
  raise HTTPException(status_code=404, detail="Source not found")
63
65
 
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
66
+ try:
67
+ result = await service.profile_source(source_id)
68
+ return ProfileResponse.from_result(result)
69
+ except Exception as e:
70
+ raise HTTPException(status_code=500, detail=str(e))
71
+
72
+
73
+ @router.post(
74
+ "/sources/{source_id}/profile/advanced",
75
+ response_model=ProfileResponse,
76
+ summary="Profile source with advanced configuration",
77
+ description="Run data profiling with custom ProfilerConfig options",
78
+ )
79
+ async def profile_source_advanced(
80
+ service: ProfileServiceDep,
81
+ source_service: SourceServiceDep,
82
+ source_id: Annotated[str, Path(description="Source ID to profile")],
83
+ request: ProfileAdvancedRequest,
84
+ ) -> ProfileResponse:
85
+ """Run advanced data profiling with custom configuration.
86
+
87
+ Uses truthound's ProfilerConfig for fine-grained control over:
88
+ - Sampling: sample_size, random_seed
89
+ - Features: include_patterns, include_correlations, include_distributions
90
+ - Pattern detection: pattern_sample_size, min_pattern_match_ratio
91
+ - Output: top_n_values, correlation_threshold
92
+ - Performance: n_jobs
93
+
94
+ Args:
95
+ service: Injected profile service.
96
+ source_service: Injected source service.
97
+ source_id: Source to profile.
98
+ request: Advanced profiling configuration.
99
+
100
+ Returns:
101
+ Profiling result with column statistics.
102
+
103
+ Raises:
104
+ HTTPException: 404 if source not found, 501 if not supported, 500 on error.
105
+ """
106
+ # Verify source exists
107
+ source = await source_service.get_by_id(source_id)
108
+ if source is None:
109
+ raise HTTPException(status_code=404, detail="Source not found")
92
110
 
93
111
  try:
94
- result = await service.profile_source(source_id, **profile_kwargs)
112
+ # Convert request to config dict
113
+ config = {
114
+ "sample_size": request.sample_size,
115
+ "random_seed": request.random_seed,
116
+ "include_patterns": request.include_patterns,
117
+ "include_correlations": request.include_correlations,
118
+ "include_distributions": request.include_distributions,
119
+ "top_n_values": request.top_n_values,
120
+ "pattern_sample_size": request.pattern_sample_size,
121
+ "correlation_threshold": request.correlation_threshold,
122
+ "min_pattern_match_ratio": request.min_pattern_match_ratio,
123
+ "n_jobs": request.n_jobs,
124
+ }
125
+ result = await service.profile_source_advanced(source_id, config=config)
95
126
  return ProfileResponse.from_result(result)
127
+ except ImportError as e:
128
+ raise HTTPException(
129
+ status_code=501,
130
+ detail=f"Advanced profiling not available: {e}. "
131
+ "Please upgrade truthound to the latest version.",
132
+ )
96
133
  except Exception as e:
97
134
  raise HTTPException(status_code=500, detail=str(e))
98
135
 
@@ -102,6 +139,41 @@ async def profile_source(
102
139
  # =============================================================================
103
140
 
104
141
 
142
+ @router.get(
143
+ "/sources/{source_id}/profile/latest",
144
+ response_model=ProfileResponse | None,
145
+ summary="Get latest profile",
146
+ description="Retrieve the most recent profile result for a source",
147
+ )
148
+ async def get_latest_profile(
149
+ service: ProfileServiceDep,
150
+ source_service: SourceServiceDep,
151
+ source_id: Annotated[str, Path(description="Source ID")],
152
+ ) -> ProfileResponse | None:
153
+ """Get the latest profile for a source.
154
+
155
+ Args:
156
+ service: Injected profile service.
157
+ source_service: Injected source service.
158
+ source_id: Source ID.
159
+
160
+ Returns:
161
+ Latest profile result or null.
162
+
163
+ Raises:
164
+ HTTPException: 404 if source not found.
165
+ """
166
+ source = await source_service.get_by_id(source_id)
167
+ if source is None:
168
+ raise HTTPException(status_code=404, detail="Source not found")
169
+
170
+ profile = await service.get_latest_profile(source_id)
171
+ if profile is None:
172
+ return None
173
+
174
+ return ProfileResponse.from_result(profile)
175
+
176
+
105
177
  @router.get(
106
178
  "/sources/{source_id}/profiles",
107
179
  response_model=ProfileListResponse,
@@ -255,3 +327,93 @@ async def get_latest_profile_comparison(
255
327
  raise HTTPException(status_code=404, detail="Source not found")
256
328
 
257
329
  return await comparison_service.get_latest_comparison(source)
330
+
331
+
332
+ # =============================================================================
333
+ # Rule Generation from Profile
334
+ # =============================================================================
335
+
336
+
337
+ @router.post(
338
+ "/sources/{source_id}/profiles/generate-rules",
339
+ summary="Generate validation rules from profile",
340
+ description="Automatically generate validation rules based on profiled data characteristics.",
341
+ )
342
+ async def generate_rules_from_profile(
343
+ source_id: Annotated[str, Path(description="Source ID")],
344
+ service: ProfileServiceDep,
345
+ source_service: SourceServiceDep,
346
+ strictness: str = Query(
347
+ default="medium",
348
+ description="Rule strictness: loose, medium, strict",
349
+ ),
350
+ preset: str = Query(
351
+ default="default",
352
+ description="Rule preset: default, strict, loose, minimal, comprehensive, ci_cd, schema_only, format_only",
353
+ ),
354
+ include_categories: list[str] | None = Query(
355
+ default=None,
356
+ description="Rule categories to include (schema, stats, pattern, completeness, uniqueness, distribution)",
357
+ ),
358
+ exclude_categories: list[str] | None = Query(
359
+ default=None,
360
+ description="Rule categories to exclude",
361
+ ),
362
+ profile_if_needed: bool = Query(
363
+ default=True,
364
+ description="Profile source if no recent profile exists",
365
+ ),
366
+ sample_size: int | None = Query(
367
+ default=None,
368
+ description="Sample size for profiling if needed",
369
+ ),
370
+ ) -> dict:
371
+ """Generate validation rules from source profile.
372
+
373
+ Uses truthound's generate_suite() to automatically create validation
374
+ rules based on the profiled data characteristics.
375
+
376
+ The generated rules can be used directly with th.check() or saved
377
+ as a schema file for repeated validation.
378
+
379
+ Args:
380
+ source_id: Source ID to generate rules for.
381
+ service: Profile service.
382
+ source_service: Source service.
383
+ strictness: Rule strictness level.
384
+ preset: Rule generation preset.
385
+ include_categories: Rule categories to include.
386
+ exclude_categories: Rule categories to exclude.
387
+ profile_if_needed: Profile source if no recent profile exists.
388
+ sample_size: Sample size for profiling if needed.
389
+
390
+ Returns:
391
+ Generated rules with YAML content and metadata.
392
+
393
+ Raises:
394
+ HTTPException: 404 if source not found, 400 if no profile available.
395
+ """
396
+ # Verify source exists
397
+ source = await source_service.get_by_id(source_id)
398
+ if source is None:
399
+ raise HTTPException(status_code=404, detail="Source not found")
400
+
401
+ try:
402
+ result = await service.generate_rules_from_profile(
403
+ source_id,
404
+ strictness=strictness,
405
+ preset=preset,
406
+ include_categories=include_categories,
407
+ exclude_categories=exclude_categories,
408
+ profile_if_needed=profile_if_needed,
409
+ sample_size=sample_size,
410
+ )
411
+ return result
412
+ except ValueError as e:
413
+ raise HTTPException(status_code=400, detail=str(e))
414
+ except ImportError as e:
415
+ raise HTTPException(
416
+ status_code=501,
417
+ detail=f"Rule generation not available: {e}. "
418
+ "Please upgrade truthound to the latest version.",
419
+ )