truthound-dashboard 1.4.3__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.3.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.3.dist-info/METADATA +0 -505
  203. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
  204. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
  205. {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,281 @@
1
+ """Webhook action implementation.
2
+
3
+ Provides a generic HTTP webhook action for integrating with
4
+ external systems via HTTP POST requests.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from datetime import datetime
13
+ from typing import Any
14
+
15
+ from truthound_dashboard.core.interfaces.actions import (
16
+ ActionConfig,
17
+ ActionContext,
18
+ ActionResult,
19
+ ActionStatus,
20
+ BaseAction,
21
+ NotifyCondition,
22
+ register_action,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class WebhookConfig(ActionConfig):
30
+ """Configuration for webhook action.
31
+
32
+ Attributes:
33
+ url: Webhook URL.
34
+ method: HTTP method (POST, PUT, PATCH).
35
+ headers: Additional HTTP headers.
36
+ auth_type: Authentication type (none, basic, bearer, api_key).
37
+ auth_username: Basic auth username.
38
+ auth_password: Basic auth password.
39
+ auth_token: Bearer token or API key.
40
+ auth_header: Header name for API key auth.
41
+ payload_template: Custom payload template (Jinja2).
42
+ include_issues: Include issues in payload.
43
+ max_issues: Maximum issues to include.
44
+ verify_ssl: Verify SSL certificates.
45
+ retry_on_5xx: Retry on 5xx errors.
46
+ """
47
+
48
+ url: str = ""
49
+ method: str = "POST"
50
+ headers: dict[str, str] = field(default_factory=dict)
51
+ auth_type: str = "none" # none, basic, bearer, api_key
52
+ auth_username: str = ""
53
+ auth_password: str = ""
54
+ auth_token: str = ""
55
+ auth_header: str = "X-API-Key"
56
+ payload_template: str | None = None
57
+ include_issues: bool = True
58
+ max_issues: int = 100
59
+ verify_ssl: bool = True
60
+ retry_on_5xx: bool = True
61
+
62
+ def __post_init__(self):
63
+ self.name = self.name or "webhook"
64
+
65
+
66
+ @register_action("webhook")
67
+ class WebhookAction(BaseAction):
68
+ """Generic HTTP webhook action.
69
+
70
+ Sends validation results to external systems via HTTP POST.
71
+ Supports various authentication methods and custom payloads.
72
+
73
+ Example:
74
+ action = WebhookAction(
75
+ url="https://api.example.com/validations",
76
+ auth_type="bearer",
77
+ auth_token="secret-token",
78
+ )
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ url: str = "",
84
+ method: str = "POST",
85
+ headers: dict[str, str] | None = None,
86
+ notify_on: NotifyCondition = NotifyCondition.ALWAYS,
87
+ config: WebhookConfig | dict[str, Any] | None = None,
88
+ **kwargs: Any,
89
+ ) -> None:
90
+ if config is None:
91
+ config = WebhookConfig(
92
+ url=url,
93
+ method=method,
94
+ headers=headers or {},
95
+ notify_on=notify_on,
96
+ **kwargs,
97
+ )
98
+ elif isinstance(config, dict):
99
+ config = WebhookConfig(**config)
100
+
101
+ super().__init__(config)
102
+ self._webhook_config: WebhookConfig = config
103
+
104
+ @property
105
+ def action_type(self) -> str:
106
+ return "webhook"
107
+
108
+ def _do_execute(self, context: ActionContext) -> ActionResult:
109
+ """Send webhook request."""
110
+ import httpx
111
+
112
+ result = context.checkpoint_result
113
+ payload = self._build_payload(context)
114
+ headers = self._build_headers()
115
+
116
+ try:
117
+ with httpx.Client(
118
+ timeout=self._config.timeout_seconds,
119
+ verify=self._webhook_config.verify_ssl,
120
+ ) as client:
121
+ # Build auth
122
+ auth = None
123
+ if self._webhook_config.auth_type == "basic":
124
+ auth = (
125
+ self._webhook_config.auth_username,
126
+ self._webhook_config.auth_password,
127
+ )
128
+
129
+ response = client.request(
130
+ method=self._webhook_config.method,
131
+ url=self._webhook_config.url,
132
+ json=payload,
133
+ headers=headers,
134
+ auth=auth,
135
+ )
136
+
137
+ # Check for retry on 5xx
138
+ if (
139
+ response.status_code >= 500
140
+ and self._webhook_config.retry_on_5xx
141
+ and self._config.retry_count > 0
142
+ ):
143
+ # Simple retry logic
144
+ for i in range(self._config.retry_count):
145
+ response = client.request(
146
+ method=self._webhook_config.method,
147
+ url=self._webhook_config.url,
148
+ json=payload,
149
+ headers=headers,
150
+ auth=auth,
151
+ )
152
+ if response.status_code < 500:
153
+ break
154
+
155
+ response.raise_for_status()
156
+
157
+ return ActionResult(
158
+ action_name=self.name,
159
+ action_type=self.action_type,
160
+ status=ActionStatus.SUCCESS,
161
+ message=f"Webhook sent to {self._webhook_config.url}",
162
+ details={
163
+ "url": self._webhook_config.url,
164
+ "status_code": response.status_code,
165
+ },
166
+ )
167
+ except httpx.HTTPError as e:
168
+ return ActionResult(
169
+ action_name=self.name,
170
+ action_type=self.action_type,
171
+ status=ActionStatus.FAILURE,
172
+ message=f"Webhook failed: {str(e)}",
173
+ error=str(e),
174
+ )
175
+
176
+ def _build_headers(self) -> dict[str, str]:
177
+ """Build HTTP headers including authentication."""
178
+ headers = {
179
+ "Content-Type": "application/json",
180
+ "User-Agent": "Truthound-Dashboard/1.0",
181
+ **self._webhook_config.headers,
182
+ }
183
+
184
+ # Add authentication headers
185
+ if self._webhook_config.auth_type == "bearer":
186
+ headers["Authorization"] = f"Bearer {self._webhook_config.auth_token}"
187
+ elif self._webhook_config.auth_type == "api_key":
188
+ headers[self._webhook_config.auth_header] = self._webhook_config.auth_token
189
+
190
+ return headers
191
+
192
+ def _build_payload(self, context: ActionContext) -> dict[str, Any]:
193
+ """Build webhook payload."""
194
+ result = context.checkpoint_result
195
+
196
+ # Use custom template if provided
197
+ if self._webhook_config.payload_template:
198
+ return self._render_template(context)
199
+
200
+ # Default payload structure
201
+ payload = {
202
+ "event": "validation_completed",
203
+ "timestamp": datetime.now().isoformat(),
204
+ "checkpoint": {
205
+ "name": result.checkpoint_name,
206
+ "run_id": result.run_id,
207
+ "status": result.status.value,
208
+ },
209
+ "source": {
210
+ "name": result.source_name,
211
+ "row_count": result.row_count,
212
+ "column_count": result.column_count,
213
+ },
214
+ "summary": {
215
+ "total_issues": result.issue_count,
216
+ "critical": result.critical_count,
217
+ "high": result.high_count,
218
+ "medium": result.medium_count,
219
+ "low": result.low_count,
220
+ "has_critical": result.has_critical,
221
+ "has_high": result.has_high,
222
+ },
223
+ "timing": {
224
+ "started_at": result.started_at.isoformat() if result.started_at else None,
225
+ "completed_at": result.completed_at.isoformat() if result.completed_at else None,
226
+ "duration_ms": result.duration_ms,
227
+ },
228
+ "metadata": result.metadata,
229
+ }
230
+
231
+ # Include issues if configured
232
+ if self._webhook_config.include_issues and result.issues:
233
+ payload["issues"] = result.issues[: self._webhook_config.max_issues]
234
+ if len(result.issues) > self._webhook_config.max_issues:
235
+ payload["issues_truncated"] = True
236
+ payload["total_issues_count"] = len(result.issues)
237
+
238
+ return payload
239
+
240
+ def _render_template(self, context: ActionContext) -> dict[str, Any]:
241
+ """Render custom payload template using Jinja2."""
242
+ try:
243
+ from jinja2 import Environment
244
+ except ImportError:
245
+ logger.warning("Jinja2 not available, using default payload")
246
+ return self._build_payload(context)
247
+
248
+ result = context.checkpoint_result
249
+
250
+ # Build template context
251
+ template_context = {
252
+ "result": result,
253
+ "checkpoint_name": result.checkpoint_name,
254
+ "run_id": result.run_id,
255
+ "status": result.status.value,
256
+ "source_name": result.source_name,
257
+ "row_count": result.row_count,
258
+ "column_count": result.column_count,
259
+ "issue_count": result.issue_count,
260
+ "critical_count": result.critical_count,
261
+ "high_count": result.high_count,
262
+ "medium_count": result.medium_count,
263
+ "low_count": result.low_count,
264
+ "has_critical": result.has_critical,
265
+ "has_high": result.has_high,
266
+ "issues": result.issues,
267
+ "metadata": result.metadata,
268
+ "tags": context.tags,
269
+ "timestamp": datetime.now().isoformat(),
270
+ }
271
+
272
+ env = Environment()
273
+ template = env.from_string(self._webhook_config.payload_template)
274
+ rendered = template.render(**template_context)
275
+
276
+ # Parse as JSON
277
+ try:
278
+ return json.loads(rendered)
279
+ except json.JSONDecodeError:
280
+ # Return as wrapped string
281
+ return {"payload": rendered}