truthound-dashboard 1.4.4__py3-none-any.whl → 1.5.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 (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 +645 -23
  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 +15 -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.1.dist-info/METADATA +312 -0
  146. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.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.1.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,814 @@
1
+ """Adapters for integrating truthound checkpoint API with dashboard abstractions.
2
+
3
+ Provides adapter classes that bridge between truthound's checkpoint module
4
+ and the dashboard's abstraction layer, enabling loose coupling while
5
+ supporting the full truthound checkpoint functionality.
6
+
7
+ Key features supported:
8
+ - Checkpoint execution (sync and async)
9
+ - Action orchestration (12 action types)
10
+ - CI/CD reporter integration (12 platforms)
11
+ - Trigger management (cron, schedule, event, file watch)
12
+ - Transaction management (saga pattern, idempotency)
13
+ - Advanced notifications (routing, deduplication, throttling, escalation)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ from truthound_dashboard.core.interfaces.actions import (
22
+ ActionContext,
23
+ ActionResult,
24
+ ActionStatus,
25
+ BaseAction,
26
+ )
27
+ from truthound_dashboard.core.interfaces.checkpoint import (
28
+ CheckpointConfig,
29
+ CheckpointResult,
30
+ CheckpointStatus,
31
+ )
32
+
33
+ if TYPE_CHECKING:
34
+ pass
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ class TruthoundCheckpointAdapter:
40
+ """Adapter for truthound checkpoint API integration.
41
+
42
+ This adapter provides a bridge between truthound's native checkpoint
43
+ module and the dashboard's abstraction layer. It allows using truthound
44
+ checkpoints while maintaining the dashboard's loose coupling.
45
+
46
+ Features:
47
+ - Convert truthound checkpoint results to dashboard format
48
+ - Map truthound actions to dashboard actions
49
+ - Support truthound's routing and trigger systems
50
+ - Maintain backward compatibility with existing dashboard code
51
+
52
+ Example:
53
+ from truthound.checkpoint import Checkpoint as TruthoundCheckpoint
54
+
55
+ # Create a truthound checkpoint
56
+ th_checkpoint = TruthoundCheckpoint(
57
+ datasource=source,
58
+ actions=[SlackAction(webhook_url="...")],
59
+ )
60
+
61
+ # Wrap with adapter
62
+ adapter = TruthoundCheckpointAdapter(th_checkpoint)
63
+
64
+ # Use through dashboard interfaces
65
+ result = await adapter.run()
66
+ """
67
+
68
+ def __init__(self, truthound_checkpoint: Any) -> None:
69
+ """Initialize adapter.
70
+
71
+ Args:
72
+ truthound_checkpoint: Native truthound Checkpoint instance.
73
+ """
74
+ self._checkpoint = truthound_checkpoint
75
+
76
+ @property
77
+ def name(self) -> str:
78
+ """Get checkpoint name."""
79
+ return getattr(self._checkpoint, "name", "unnamed")
80
+
81
+ @property
82
+ def config(self) -> CheckpointConfig:
83
+ """Get checkpoint configuration as dashboard format."""
84
+ cp = self._checkpoint
85
+
86
+ return CheckpointConfig(
87
+ name=getattr(cp, "name", ""),
88
+ description=getattr(cp, "description", ""),
89
+ source_name=getattr(cp, "datasource_name", ""),
90
+ validators=list(getattr(cp, "validators", [])),
91
+ validator_config=dict(getattr(cp, "validator_config", {})),
92
+ tags=dict(getattr(cp, "tags", {})),
93
+ enabled=getattr(cp, "enabled", True),
94
+ timeout_seconds=getattr(cp, "timeout", 300),
95
+ )
96
+
97
+ async def run(
98
+ self,
99
+ trigger_context: dict[str, Any] | None = None,
100
+ ) -> CheckpointResult:
101
+ """Run the truthound checkpoint and convert result.
102
+
103
+ Args:
104
+ trigger_context: Optional trigger context.
105
+
106
+ Returns:
107
+ Dashboard-format checkpoint result.
108
+ """
109
+ try:
110
+ # Run truthound checkpoint
111
+ th_result = await self._run_truthound()
112
+
113
+ # Convert to dashboard format
114
+ return self._convert_result(th_result, trigger_context)
115
+
116
+ except Exception as e:
117
+ logger.error(f"Truthound checkpoint failed: {str(e)}")
118
+ return CheckpointResult(
119
+ checkpoint_name=self.name,
120
+ run_id=str(getattr(self._checkpoint, "run_id", "")),
121
+ status=CheckpointStatus.ERROR,
122
+ error_message=str(e),
123
+ trigger_context=trigger_context or {},
124
+ )
125
+
126
+ async def _run_truthound(self) -> Any:
127
+ """Run the native truthound checkpoint.
128
+
129
+ Returns:
130
+ Truthound checkpoint result.
131
+ """
132
+ import asyncio
133
+
134
+ # Check if checkpoint has async run method
135
+ if hasattr(self._checkpoint, "run_async"):
136
+ return await self._checkpoint.run_async()
137
+ elif hasattr(self._checkpoint, "run"):
138
+ # Run sync method in executor
139
+ loop = asyncio.get_event_loop()
140
+ return await loop.run_in_executor(None, self._checkpoint.run)
141
+ else:
142
+ raise AttributeError("Checkpoint has no run method")
143
+
144
+ def _convert_result(
145
+ self,
146
+ th_result: Any,
147
+ trigger_context: dict[str, Any] | None,
148
+ ) -> CheckpointResult:
149
+ """Convert truthound result to dashboard format.
150
+
151
+ Args:
152
+ th_result: Truthound checkpoint result.
153
+ trigger_context: Trigger context.
154
+
155
+ Returns:
156
+ Dashboard-format result.
157
+ """
158
+ # Extract status
159
+ status_str = getattr(th_result, "status", "error")
160
+ status_map = {
161
+ "success": CheckpointStatus.SUCCESS,
162
+ "failure": CheckpointStatus.FAILURE,
163
+ "error": CheckpointStatus.ERROR,
164
+ "warning": CheckpointStatus.WARNING,
165
+ "pending": CheckpointStatus.PENDING,
166
+ "running": CheckpointStatus.RUNNING,
167
+ }
168
+ status = status_map.get(status_str.lower(), CheckpointStatus.ERROR)
169
+
170
+ # Extract validation result
171
+ validation_result = getattr(th_result, "validation_result", None)
172
+
173
+ # Build checkpoint result
174
+ result = CheckpointResult(
175
+ checkpoint_name=self.name,
176
+ run_id=str(getattr(th_result, "run_id", "")),
177
+ status=status,
178
+ started_at=getattr(th_result, "started_at", None),
179
+ completed_at=getattr(th_result, "completed_at", None),
180
+ duration_ms=getattr(th_result, "duration_ms", 0.0),
181
+ source_name=getattr(th_result, "source_name", ""),
182
+ trigger_context=trigger_context or {},
183
+ )
184
+
185
+ # Extract issue counts from validation result
186
+ if validation_result:
187
+ result.row_count = getattr(validation_result, "row_count", 0)
188
+ result.column_count = getattr(validation_result, "column_count", 0)
189
+ result.issue_count = getattr(validation_result, "total_issues", 0)
190
+ result.critical_count = getattr(validation_result, "critical_issues", 0)
191
+ result.high_count = getattr(validation_result, "high_issues", 0)
192
+ result.medium_count = getattr(validation_result, "medium_issues", 0)
193
+ result.low_count = getattr(validation_result, "low_issues", 0)
194
+ result.has_critical = getattr(validation_result, "has_critical", False)
195
+ result.has_high = getattr(validation_result, "has_high", False)
196
+ result.issues = getattr(validation_result, "issues", [])
197
+
198
+ # Convert action results
199
+ th_action_results = getattr(th_result, "action_results", [])
200
+ result.action_results = [
201
+ self._convert_action_result(ar) for ar in th_action_results
202
+ ]
203
+
204
+ return result
205
+
206
+ def _convert_action_result(self, th_result: Any) -> ActionResult:
207
+ """Convert truthound action result to dashboard format.
208
+
209
+ Args:
210
+ th_result: Truthound action result.
211
+
212
+ Returns:
213
+ Dashboard-format action result.
214
+ """
215
+ status_str = getattr(th_result, "status", "failure")
216
+ status_map = {
217
+ "success": ActionStatus.SUCCESS,
218
+ "failure": ActionStatus.FAILURE,
219
+ "skipped": ActionStatus.SKIPPED,
220
+ "pending": ActionStatus.PENDING,
221
+ "running": ActionStatus.RUNNING,
222
+ }
223
+ status = status_map.get(status_str.lower(), ActionStatus.FAILURE)
224
+
225
+ return ActionResult(
226
+ action_name=getattr(th_result, "action_name", "unknown"),
227
+ action_type=getattr(th_result, "action_type", "unknown"),
228
+ status=status,
229
+ message=getattr(th_result, "message", ""),
230
+ started_at=getattr(th_result, "started_at", None),
231
+ completed_at=getattr(th_result, "completed_at", None),
232
+ duration_ms=getattr(th_result, "duration_ms", 0.0),
233
+ details=getattr(th_result, "details", {}),
234
+ error=getattr(th_result, "error", None),
235
+ )
236
+
237
+
238
+ class TruthoundActionAdapter(BaseAction):
239
+ """Adapter for wrapping truthound actions as dashboard actions.
240
+
241
+ Allows using truthound's native action implementations within
242
+ the dashboard's action framework.
243
+
244
+ Example:
245
+ from truthound.checkpoint.actions import SlackAction
246
+
247
+ th_action = SlackAction(webhook_url="...")
248
+ dashboard_action = TruthoundActionAdapter(th_action)
249
+
250
+ # Use in checkpoint
251
+ checkpoint.add_action(dashboard_action)
252
+ """
253
+
254
+ def __init__(self, truthound_action: Any) -> None:
255
+ """Initialize adapter.
256
+
257
+ Args:
258
+ truthound_action: Native truthound action instance.
259
+ """
260
+ from truthound_dashboard.core.interfaces.actions import ActionConfig
261
+
262
+ super().__init__(ActionConfig())
263
+ self._action = truthound_action
264
+
265
+ @property
266
+ def name(self) -> str:
267
+ """Get action name."""
268
+ return getattr(self._action, "name", "truthound_action")
269
+
270
+ @property
271
+ def action_type(self) -> str:
272
+ """Get action type."""
273
+ return getattr(self._action, "action_type", "external")
274
+
275
+ def _do_execute(self, context: ActionContext) -> ActionResult:
276
+ """Execute the truthound action.
277
+
278
+ Args:
279
+ context: Action context.
280
+
281
+ Returns:
282
+ Action result.
283
+ """
284
+ try:
285
+ # Build truthound context if needed
286
+ th_context = self._build_truthound_context(context)
287
+
288
+ # Execute truthound action
289
+ if hasattr(self._action, "execute"):
290
+ th_result = self._action.execute(th_context)
291
+ elif hasattr(self._action, "__call__"):
292
+ th_result = self._action(th_context)
293
+ else:
294
+ raise AttributeError("Action has no execute method")
295
+
296
+ # Convert result
297
+ return self._convert_result(th_result)
298
+
299
+ except Exception as e:
300
+ return ActionResult(
301
+ action_name=self.name,
302
+ action_type=self.action_type,
303
+ status=ActionStatus.FAILURE,
304
+ message=f"Truthound action failed: {str(e)}",
305
+ error=str(e),
306
+ )
307
+
308
+ def _build_truthound_context(self, context: ActionContext) -> Any:
309
+ """Build truthound action context from dashboard context.
310
+
311
+ Args:
312
+ context: Dashboard action context.
313
+
314
+ Returns:
315
+ Truthound-compatible context.
316
+ """
317
+ # Try to import truthound context class
318
+ try:
319
+ from truthound.checkpoint.actions import ActionContext as TruthoundActionContext
320
+ return TruthoundActionContext(
321
+ checkpoint_result=context.checkpoint_result,
322
+ run_id=context.run_id,
323
+ checkpoint_name=context.checkpoint_name,
324
+ tags=context.tags,
325
+ metadata=context.metadata,
326
+ )
327
+ except ImportError:
328
+ # Return a dict-like context
329
+ return {
330
+ "checkpoint_result": context.checkpoint_result,
331
+ "run_id": context.run_id,
332
+ "checkpoint_name": context.checkpoint_name,
333
+ "tags": context.tags,
334
+ "metadata": context.metadata,
335
+ }
336
+
337
+ def _convert_result(self, th_result: Any) -> ActionResult:
338
+ """Convert truthound action result.
339
+
340
+ Args:
341
+ th_result: Truthound result.
342
+
343
+ Returns:
344
+ Dashboard action result.
345
+ """
346
+ if th_result is None:
347
+ return ActionResult(
348
+ action_name=self.name,
349
+ action_type=self.action_type,
350
+ status=ActionStatus.SUCCESS,
351
+ message="Action completed",
352
+ )
353
+
354
+ status_str = getattr(th_result, "status", "success")
355
+ status_map = {
356
+ "success": ActionStatus.SUCCESS,
357
+ "failure": ActionStatus.FAILURE,
358
+ "skipped": ActionStatus.SKIPPED,
359
+ }
360
+ status = status_map.get(status_str.lower(), ActionStatus.SUCCESS)
361
+
362
+ return ActionResult(
363
+ action_name=self.name,
364
+ action_type=self.action_type,
365
+ status=status,
366
+ message=getattr(th_result, "message", ""),
367
+ details=getattr(th_result, "details", {}),
368
+ error=getattr(th_result, "error", None),
369
+ )
370
+
371
+
372
+ def create_checkpoint_from_truthound(th_checkpoint: Any) -> TruthoundCheckpointAdapter:
373
+ """Factory function to create a dashboard checkpoint from truthound checkpoint.
374
+
375
+ Args:
376
+ th_checkpoint: Truthound checkpoint instance.
377
+
378
+ Returns:
379
+ Wrapped checkpoint adapter.
380
+ """
381
+ return TruthoundCheckpointAdapter(th_checkpoint)
382
+
383
+
384
+ def wrap_truthound_action(th_action: Any) -> TruthoundActionAdapter:
385
+ """Factory function to wrap a truthound action for dashboard use.
386
+
387
+ Args:
388
+ th_action: Truthound action instance.
389
+
390
+ Returns:
391
+ Wrapped action adapter.
392
+ """
393
+ return TruthoundActionAdapter(th_action)
394
+
395
+
396
+ # =============================================================================
397
+ # Factory Functions for Truthound Checkpoints
398
+ # =============================================================================
399
+
400
+
401
+ def create_truthound_checkpoint(
402
+ name: str,
403
+ data_source: Any,
404
+ validators: list[str] | None = None,
405
+ actions: list[Any] | None = None,
406
+ triggers: list[Any] | None = None,
407
+ **config_options: Any,
408
+ ) -> TruthoundCheckpointAdapter | None:
409
+ """Create a truthound checkpoint with the new 2.x API.
410
+
411
+ This factory function creates a checkpoint using truthound's native
412
+ checkpoint API and wraps it with the dashboard adapter.
413
+
414
+ Args:
415
+ name: Checkpoint name.
416
+ data_source: Data source (file path, DataSource, or config dict).
417
+ validators: List of validator names to run.
418
+ actions: List of action instances.
419
+ triggers: List of trigger instances.
420
+ **config_options: Additional checkpoint configuration options.
421
+
422
+ Returns:
423
+ TruthoundCheckpointAdapter or None if truthound unavailable.
424
+
425
+ Example:
426
+ from truthound.checkpoint.actions import SlackNotification
427
+
428
+ checkpoint = create_truthound_checkpoint(
429
+ name="daily_validation",
430
+ data_source="data.csv",
431
+ validators=["null", "duplicate", "range"],
432
+ actions=[
433
+ SlackNotification(
434
+ webhook_url="https://hooks.slack.com/...",
435
+ notify_on="failure",
436
+ ),
437
+ ],
438
+ )
439
+
440
+ if checkpoint:
441
+ result = await checkpoint.run()
442
+ """
443
+ try:
444
+ from truthound.checkpoint import Checkpoint
445
+
446
+ # Create truthound checkpoint
447
+ th_checkpoint = Checkpoint(
448
+ name=name,
449
+ data_source=data_source,
450
+ validators=validators or [],
451
+ actions=actions or [],
452
+ **config_options,
453
+ )
454
+
455
+ # Add triggers
456
+ if triggers:
457
+ for trigger in triggers:
458
+ th_checkpoint.add_trigger(trigger)
459
+
460
+ # Wrap with adapter
461
+ return TruthoundCheckpointAdapter(th_checkpoint)
462
+
463
+ except ImportError:
464
+ logger.warning("truthound.checkpoint not available")
465
+ return None
466
+
467
+
468
+ def create_checkpoint_from_config(
469
+ config: dict[str, Any] | "CheckpointConfig",
470
+ ) -> TruthoundCheckpointAdapter | None:
471
+ """Create a truthound checkpoint from configuration.
472
+
473
+ This factory function creates a checkpoint from a configuration
474
+ dictionary or CheckpointConfig object.
475
+
476
+ Args:
477
+ config: Configuration dict or CheckpointConfig.
478
+
479
+ Returns:
480
+ TruthoundCheckpointAdapter or None if truthound unavailable.
481
+
482
+ Example:
483
+ config = {
484
+ "name": "daily_validation",
485
+ "data_source": "data.csv",
486
+ "validators": ["null", "duplicate"],
487
+ "actions": [
488
+ {
489
+ "type": "slack",
490
+ "webhook_url": "https://hooks.slack.com/...",
491
+ "notify_on": "failure",
492
+ }
493
+ ],
494
+ }
495
+ checkpoint = create_checkpoint_from_config(config)
496
+ """
497
+ try:
498
+ from truthound.checkpoint import Checkpoint, CheckpointConfig as TruthoundConfig
499
+
500
+ # Convert to dict if needed
501
+ if isinstance(config, CheckpointConfig):
502
+ config_dict = config.to_dict()
503
+ else:
504
+ config_dict = config
505
+
506
+ # Build truthound config
507
+ th_config = TruthoundConfig(
508
+ name=config_dict.get("name", ""),
509
+ data_source=config_dict.get("data_source") or config_dict.get("source_name"),
510
+ validators=config_dict.get("validators", []),
511
+ validator_config=config_dict.get("validator_config", {}),
512
+ schema=config_dict.get("schema_path"),
513
+ auto_schema=config_dict.get("auto_schema", False),
514
+ tags=config_dict.get("tags", {}),
515
+ timeout_seconds=config_dict.get("timeout_seconds", 300),
516
+ )
517
+
518
+ # Create checkpoint
519
+ th_checkpoint = Checkpoint(config=th_config)
520
+
521
+ # Create actions from config
522
+ actions_config = config_dict.get("actions", [])
523
+ for action_config in actions_config:
524
+ action = _create_action_from_config(action_config)
525
+ if action:
526
+ th_checkpoint.add_action(action)
527
+
528
+ # Create triggers from config
529
+ triggers_config = config_dict.get("triggers", [])
530
+ for trigger_config in triggers_config:
531
+ trigger = _create_trigger_from_config(trigger_config)
532
+ if trigger:
533
+ th_checkpoint.add_trigger(trigger)
534
+
535
+ return TruthoundCheckpointAdapter(th_checkpoint)
536
+
537
+ except ImportError:
538
+ logger.warning("truthound.checkpoint not available")
539
+ return None
540
+
541
+
542
+ def _create_action_from_config(config: dict[str, Any]) -> Any:
543
+ """Create an action from configuration dict.
544
+
545
+ Supports all 12 action types from truthound.
546
+
547
+ Args:
548
+ config: Action configuration.
549
+
550
+ Returns:
551
+ Action instance or None.
552
+ """
553
+ action_type = config.get("type", "").lower()
554
+
555
+ try:
556
+ if action_type == "store_result":
557
+ from truthound.checkpoint.actions import StoreValidationResult
558
+
559
+ return StoreValidationResult(
560
+ store_path=config.get("store_path", "./results"),
561
+ store_type=config.get("store_type", "file"),
562
+ format=config.get("format", "json"),
563
+ partition_by=config.get("partition_by"),
564
+ retention_days=config.get("retention_days"),
565
+ compress=config.get("compress", False),
566
+ )
567
+
568
+ elif action_type == "update_docs":
569
+ from truthound.checkpoint.actions import UpdateDataDocs
570
+
571
+ return UpdateDataDocs(
572
+ site_path=config.get("site_path", "./docs"),
573
+ format=config.get("format", "html"),
574
+ include_history=config.get("include_history", True),
575
+ )
576
+
577
+ elif action_type == "slack":
578
+ from truthound.checkpoint.actions import SlackNotification
579
+
580
+ return SlackNotification(
581
+ webhook_url=config["webhook_url"],
582
+ channel=config.get("channel"),
583
+ notify_on=config.get("notify_on", "failure"),
584
+ include_details=config.get("include_details", True),
585
+ )
586
+
587
+ elif action_type == "email":
588
+ from truthound.checkpoint.actions import EmailNotification
589
+
590
+ return EmailNotification(
591
+ to=config.get("to", []),
592
+ subject=config.get("subject"),
593
+ notify_on=config.get("notify_on", "failure"),
594
+ smtp_host=config.get("smtp_host"),
595
+ smtp_port=config.get("smtp_port"),
596
+ )
597
+
598
+ elif action_type == "webhook":
599
+ from truthound.checkpoint.actions import WebhookAction
600
+
601
+ return WebhookAction(
602
+ url=config["url"],
603
+ method=config.get("method", "POST"),
604
+ auth_type=config.get("auth_type", "none"),
605
+ auth_credentials=config.get("auth_credentials"),
606
+ headers=config.get("headers"),
607
+ )
608
+
609
+ elif action_type == "pagerduty":
610
+ from truthound.checkpoint.actions import PagerDutyAction
611
+
612
+ return PagerDutyAction(
613
+ service_key=config["service_key"],
614
+ notify_on=config.get("notify_on", "failure"),
615
+ )
616
+
617
+ elif action_type == "github":
618
+ from truthound.checkpoint.actions import GitHubAction
619
+
620
+ return GitHubAction()
621
+
622
+ elif action_type == "teams":
623
+ from truthound.checkpoint.actions import TeamsNotification
624
+
625
+ return TeamsNotification(
626
+ webhook_url=config["webhook_url"],
627
+ notify_on=config.get("notify_on", "failure"),
628
+ )
629
+
630
+ elif action_type == "opsgenie":
631
+ from truthound.checkpoint.actions import OpsGenieAction
632
+
633
+ return OpsGenieAction(
634
+ api_key=config["api_key"],
635
+ notify_on=config.get("notify_on", "failure"),
636
+ )
637
+
638
+ elif action_type == "discord":
639
+ from truthound.checkpoint.actions import DiscordNotification
640
+
641
+ return DiscordNotification(
642
+ webhook_url=config["webhook_url"],
643
+ notify_on=config.get("notify_on", "failure"),
644
+ )
645
+
646
+ elif action_type == "telegram":
647
+ from truthound.checkpoint.actions import TelegramNotification
648
+
649
+ return TelegramNotification(
650
+ bot_token=config["bot_token"],
651
+ chat_id=config["chat_id"],
652
+ notify_on=config.get("notify_on", "failure"),
653
+ )
654
+
655
+ elif action_type == "custom":
656
+ from truthound.checkpoint.actions import CustomAction
657
+
658
+ return CustomAction(
659
+ callback=config.get("callback"),
660
+ shell_command=config.get("shell_command"),
661
+ )
662
+
663
+ else:
664
+ logger.warning(f"Unknown action type: {action_type}")
665
+ return None
666
+
667
+ except (ImportError, KeyError) as e:
668
+ logger.warning(f"Failed to create action {action_type}: {e}")
669
+ return None
670
+
671
+
672
+ def _create_trigger_from_config(config: dict[str, Any]) -> Any:
673
+ """Create a trigger from configuration dict.
674
+
675
+ Args:
676
+ config: Trigger configuration.
677
+
678
+ Returns:
679
+ Trigger instance or None.
680
+ """
681
+ trigger_type = config.get("type", "").lower()
682
+
683
+ try:
684
+ if trigger_type == "schedule":
685
+ from truthound.checkpoint.triggers import ScheduleTrigger
686
+
687
+ return ScheduleTrigger(
688
+ interval_hours=config.get("interval_hours"),
689
+ interval_minutes=config.get("interval_minutes"),
690
+ run_on_weekdays=config.get("run_on_weekdays"),
691
+ )
692
+
693
+ elif trigger_type == "cron":
694
+ from truthound.checkpoint.triggers import CronTrigger
695
+
696
+ return CronTrigger(expression=config["expression"])
697
+
698
+ elif trigger_type == "event":
699
+ from truthound.checkpoint.triggers import EventTrigger
700
+
701
+ return EventTrigger(
702
+ event_type=config["event_type"],
703
+ event_filter=config.get("event_filter"),
704
+ debounce_seconds=config.get("debounce_seconds"),
705
+ )
706
+
707
+ elif trigger_type == "file_watch":
708
+ from truthound.checkpoint.triggers import FileWatchTrigger
709
+
710
+ return FileWatchTrigger(
711
+ paths=config.get("paths", []),
712
+ patterns=config.get("patterns", ["*"]),
713
+ recursive=config.get("recursive", False),
714
+ )
715
+
716
+ else:
717
+ logger.warning(f"Unknown trigger type: {trigger_type}")
718
+ return None
719
+
720
+ except (ImportError, KeyError) as e:
721
+ logger.warning(f"Failed to create trigger {trigger_type}: {e}")
722
+ return None
723
+
724
+
725
+ # =============================================================================
726
+ # CI/CD Integration Helpers
727
+ # =============================================================================
728
+
729
+
730
+ def get_ci_reporter_for_checkpoint(checkpoint_result: CheckpointResult) -> Any:
731
+ """Get a CI reporter for the checkpoint result.
732
+
733
+ Auto-detects the CI environment and returns an appropriate reporter.
734
+
735
+ Args:
736
+ checkpoint_result: Checkpoint result to report.
737
+
738
+ Returns:
739
+ CI reporter or None if not in CI environment.
740
+ """
741
+ from truthound_dashboard.core.reporters import (
742
+ create_ci_reporter,
743
+ is_ci_environment,
744
+ )
745
+
746
+ if not is_ci_environment():
747
+ return None
748
+
749
+ return create_ci_reporter()
750
+
751
+
752
+ async def report_checkpoint_to_ci(checkpoint_result: CheckpointResult) -> bool:
753
+ """Report checkpoint result to CI platform.
754
+
755
+ This function detects the CI environment and reports the result
756
+ appropriately (e.g., GitHub step summary, annotations, etc.).
757
+
758
+ Args:
759
+ checkpoint_result: Result to report.
760
+
761
+ Returns:
762
+ True if reported successfully.
763
+ """
764
+ from truthound_dashboard.core.reporters import (
765
+ create_ci_reporter,
766
+ is_ci_environment,
767
+ get_detected_ci_platform,
768
+ )
769
+ from truthound_dashboard.core.reporters.interfaces import ReportData
770
+
771
+ if not is_ci_environment():
772
+ return False
773
+
774
+ reporter = create_ci_reporter()
775
+ if not reporter:
776
+ return False
777
+
778
+ try:
779
+ # Convert checkpoint result to report data
780
+ report_data = ReportData(
781
+ validation_id=checkpoint_result.run_id,
782
+ source_id=checkpoint_result.source_name,
783
+ source_name=checkpoint_result.source_name,
784
+ status=checkpoint_result.status.value,
785
+ )
786
+
787
+ # Generate and output report
788
+ output = await reporter.generate(report_data)
789
+
790
+ # Print to stdout for CI
791
+ if isinstance(output.content, str):
792
+ print(output.content)
793
+ else:
794
+ print(output.content.decode())
795
+
796
+ return True
797
+
798
+ except Exception as e:
799
+ logger.error(f"Failed to report to CI: {e}")
800
+ return False
801
+
802
+
803
+ def is_truthound_checkpoint_available() -> bool:
804
+ """Check if truthound checkpoint module is available.
805
+
806
+ Returns:
807
+ True if truthound.checkpoint can be imported.
808
+ """
809
+ try:
810
+ from truthound.checkpoint import Checkpoint # noqa: F401
811
+
812
+ return True
813
+ except ImportError:
814
+ return False