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.
- truthound_dashboard/api/alerts.py +75 -86
- truthound_dashboard/api/anomaly.py +7 -13
- truthound_dashboard/api/cross_alerts.py +38 -52
- truthound_dashboard/api/drift.py +49 -59
- truthound_dashboard/api/drift_monitor.py +234 -79
- truthound_dashboard/api/enterprise_sampling.py +498 -0
- truthound_dashboard/api/history.py +57 -5
- truthound_dashboard/api/lineage.py +3 -48
- truthound_dashboard/api/maintenance.py +104 -49
- truthound_dashboard/api/mask.py +1 -2
- truthound_dashboard/api/middleware.py +2 -1
- truthound_dashboard/api/model_monitoring.py +435 -311
- truthound_dashboard/api/notifications.py +227 -191
- truthound_dashboard/api/notifications_advanced.py +21 -20
- truthound_dashboard/api/observability.py +586 -0
- truthound_dashboard/api/plugins.py +2 -433
- truthound_dashboard/api/profile.py +199 -37
- truthound_dashboard/api/quality_reporter.py +701 -0
- truthound_dashboard/api/reports.py +7 -16
- truthound_dashboard/api/router.py +66 -0
- truthound_dashboard/api/rule_suggestions.py +5 -5
- truthound_dashboard/api/scan.py +17 -19
- truthound_dashboard/api/schedules.py +85 -50
- truthound_dashboard/api/schema_evolution.py +6 -6
- truthound_dashboard/api/schema_watcher.py +667 -0
- truthound_dashboard/api/sources.py +98 -27
- truthound_dashboard/api/tiering.py +1323 -0
- truthound_dashboard/api/triggers.py +14 -11
- truthound_dashboard/api/validations.py +12 -11
- truthound_dashboard/api/versioning.py +1 -6
- truthound_dashboard/core/__init__.py +129 -3
- truthound_dashboard/core/actions/__init__.py +62 -0
- truthound_dashboard/core/actions/custom.py +426 -0
- truthound_dashboard/core/actions/notifications.py +910 -0
- truthound_dashboard/core/actions/storage.py +472 -0
- truthound_dashboard/core/actions/webhook.py +281 -0
- truthound_dashboard/core/anomaly.py +262 -67
- truthound_dashboard/core/anomaly_explainer.py +4 -3
- truthound_dashboard/core/backends/__init__.py +67 -0
- truthound_dashboard/core/backends/base.py +299 -0
- truthound_dashboard/core/backends/errors.py +191 -0
- truthound_dashboard/core/backends/factory.py +423 -0
- truthound_dashboard/core/backends/mock_backend.py +451 -0
- truthound_dashboard/core/backends/truthound_backend.py +718 -0
- truthound_dashboard/core/checkpoint/__init__.py +87 -0
- truthound_dashboard/core/checkpoint/adapters.py +814 -0
- truthound_dashboard/core/checkpoint/checkpoint.py +491 -0
- truthound_dashboard/core/checkpoint/runner.py +270 -0
- truthound_dashboard/core/connections.py +437 -10
- truthound_dashboard/core/converters/__init__.py +14 -0
- truthound_dashboard/core/converters/truthound.py +620 -0
- truthound_dashboard/core/cross_alerts.py +540 -320
- truthound_dashboard/core/datasource_factory.py +1672 -0
- truthound_dashboard/core/drift_monitor.py +216 -20
- truthound_dashboard/core/enterprise_sampling.py +1291 -0
- truthound_dashboard/core/interfaces/__init__.py +225 -0
- truthound_dashboard/core/interfaces/actions.py +652 -0
- truthound_dashboard/core/interfaces/base.py +247 -0
- truthound_dashboard/core/interfaces/checkpoint.py +676 -0
- truthound_dashboard/core/interfaces/protocols.py +664 -0
- truthound_dashboard/core/interfaces/reporters.py +650 -0
- truthound_dashboard/core/interfaces/routing.py +646 -0
- truthound_dashboard/core/interfaces/triggers.py +619 -0
- truthound_dashboard/core/lineage.py +407 -71
- truthound_dashboard/core/model_monitoring.py +431 -3
- truthound_dashboard/core/notifications/base.py +4 -0
- truthound_dashboard/core/notifications/channels.py +501 -1203
- truthound_dashboard/core/notifications/deduplication/__init__.py +81 -115
- truthound_dashboard/core/notifications/deduplication/service.py +131 -348
- truthound_dashboard/core/notifications/dispatcher.py +202 -11
- truthound_dashboard/core/notifications/escalation/__init__.py +119 -106
- truthound_dashboard/core/notifications/escalation/engine.py +168 -358
- truthound_dashboard/core/notifications/routing/__init__.py +88 -128
- truthound_dashboard/core/notifications/routing/engine.py +90 -317
- truthound_dashboard/core/notifications/stats_aggregator.py +246 -1
- truthound_dashboard/core/notifications/throttling/__init__.py +67 -50
- truthound_dashboard/core/notifications/throttling/builder.py +117 -255
- truthound_dashboard/core/notifications/truthound_adapter.py +842 -0
- truthound_dashboard/core/phase5/collaboration.py +1 -1
- truthound_dashboard/core/plugins/lifecycle/__init__.py +0 -13
- truthound_dashboard/core/quality_reporter.py +1359 -0
- truthound_dashboard/core/report_history.py +0 -6
- truthound_dashboard/core/reporters/__init__.py +175 -14
- truthound_dashboard/core/reporters/adapters.py +943 -0
- truthound_dashboard/core/reporters/base.py +0 -3
- truthound_dashboard/core/reporters/builtin/__init__.py +18 -0
- truthound_dashboard/core/reporters/builtin/csv_reporter.py +111 -0
- truthound_dashboard/core/reporters/builtin/html_reporter.py +270 -0
- truthound_dashboard/core/reporters/builtin/json_reporter.py +127 -0
- truthound_dashboard/core/reporters/compat.py +266 -0
- truthound_dashboard/core/reporters/csv_reporter.py +2 -35
- truthound_dashboard/core/reporters/factory.py +526 -0
- truthound_dashboard/core/reporters/interfaces.py +745 -0
- truthound_dashboard/core/reporters/registry.py +1 -10
- truthound_dashboard/core/scheduler.py +165 -0
- truthound_dashboard/core/schema_evolution.py +3 -3
- truthound_dashboard/core/schema_watcher.py +1528 -0
- truthound_dashboard/core/services.py +595 -76
- truthound_dashboard/core/store_manager.py +810 -0
- truthound_dashboard/core/streaming_anomaly.py +169 -4
- truthound_dashboard/core/tiering.py +1309 -0
- truthound_dashboard/core/triggers/evaluators.py +178 -8
- truthound_dashboard/core/truthound_adapter.py +2620 -197
- truthound_dashboard/core/unified_alerts.py +23 -20
- truthound_dashboard/db/__init__.py +8 -0
- truthound_dashboard/db/database.py +8 -2
- truthound_dashboard/db/models.py +944 -25
- truthound_dashboard/db/repository.py +2 -0
- truthound_dashboard/main.py +11 -0
- truthound_dashboard/schemas/__init__.py +177 -16
- truthound_dashboard/schemas/base.py +44 -23
- truthound_dashboard/schemas/collaboration.py +19 -6
- truthound_dashboard/schemas/cross_alerts.py +19 -3
- truthound_dashboard/schemas/drift.py +61 -55
- truthound_dashboard/schemas/drift_monitor.py +67 -23
- truthound_dashboard/schemas/enterprise_sampling.py +653 -0
- truthound_dashboard/schemas/lineage.py +0 -33
- truthound_dashboard/schemas/mask.py +10 -8
- truthound_dashboard/schemas/model_monitoring.py +89 -10
- truthound_dashboard/schemas/notifications_advanced.py +13 -0
- truthound_dashboard/schemas/observability.py +453 -0
- truthound_dashboard/schemas/plugins.py +0 -280
- truthound_dashboard/schemas/profile.py +154 -247
- truthound_dashboard/schemas/quality_reporter.py +403 -0
- truthound_dashboard/schemas/reports.py +2 -2
- truthound_dashboard/schemas/rule_suggestion.py +8 -1
- truthound_dashboard/schemas/scan.py +4 -24
- truthound_dashboard/schemas/schedule.py +11 -3
- truthound_dashboard/schemas/schema_watcher.py +727 -0
- truthound_dashboard/schemas/source.py +17 -2
- truthound_dashboard/schemas/tiering.py +822 -0
- truthound_dashboard/schemas/triggers.py +16 -0
- truthound_dashboard/schemas/unified_alerts.py +7 -0
- truthound_dashboard/schemas/validation.py +0 -13
- truthound_dashboard/schemas/validators/base.py +41 -21
- truthound_dashboard/schemas/validators/business_rule_validators.py +244 -0
- truthound_dashboard/schemas/validators/localization_validators.py +273 -0
- truthound_dashboard/schemas/validators/ml_feature_validators.py +308 -0
- truthound_dashboard/schemas/validators/profiling_validators.py +275 -0
- truthound_dashboard/schemas/validators/referential_validators.py +312 -0
- truthound_dashboard/schemas/validators/registry.py +93 -8
- truthound_dashboard/schemas/validators/timeseries_validators.py +389 -0
- truthound_dashboard/schemas/versioning.py +1 -6
- truthound_dashboard/static/index.html +2 -2
- truthound_dashboard-1.5.0.dist-info/METADATA +309 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/RECORD +149 -148
- truthound_dashboard/core/plugins/hooks/__init__.py +0 -63
- truthound_dashboard/core/plugins/hooks/decorators.py +0 -367
- truthound_dashboard/core/plugins/hooks/manager.py +0 -403
- truthound_dashboard/core/plugins/hooks/protocols.py +0 -265
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +0 -584
- truthound_dashboard/core/reporters/junit_reporter.py +0 -233
- truthound_dashboard/core/reporters/markdown_reporter.py +0 -207
- truthound_dashboard/core/reporters/pdf_reporter.py +0 -209
- truthound_dashboard/static/assets/_baseUniq-BcrSP13d.js +0 -1
- truthound_dashboard/static/assets/arc-DlYjKwIL.js +0 -1
- truthound_dashboard/static/assets/architectureDiagram-VXUJARFQ-Bb2drbQM.js +0 -36
- truthound_dashboard/static/assets/blockDiagram-VD42YOAC-BlsPG1CH.js +0 -122
- truthound_dashboard/static/assets/c4Diagram-YG6GDRKO-B9JdUoaC.js +0 -10
- truthound_dashboard/static/assets/channel-Q6mHF1Hd.js +0 -1
- truthound_dashboard/static/assets/chunk-4BX2VUAB-DmyoPVuJ.js +0 -1
- truthound_dashboard/static/assets/chunk-55IACEB6-Bcz6Siv8.js +0 -1
- truthound_dashboard/static/assets/chunk-B4BG7PRW-Br3G5Rum.js +0 -165
- truthound_dashboard/static/assets/chunk-DI55MBZ5-DuM9c23u.js +0 -220
- truthound_dashboard/static/assets/chunk-FMBD7UC4-DNU-5mvT.js +0 -15
- truthound_dashboard/static/assets/chunk-QN33PNHL-Im2yNcmS.js +0 -1
- truthound_dashboard/static/assets/chunk-QZHKN3VN-kZr8XFm1.js +0 -1
- truthound_dashboard/static/assets/chunk-TZMSLE5B-Q__360q_.js +0 -1
- truthound_dashboard/static/assets/classDiagram-2ON5EDUG-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/classDiagram-v2-WZHVMYZB-vtixxUyK.js +0 -1
- truthound_dashboard/static/assets/clone-BOt2LwD0.js +0 -1
- truthound_dashboard/static/assets/cose-bilkent-S5V4N54A-CBDw6iac.js +0 -1
- truthound_dashboard/static/assets/dagre-6UL2VRFP-XdKqmmY9.js +0 -4
- truthound_dashboard/static/assets/diagram-PSM6KHXK-DAZ8nx9V.js +0 -24
- truthound_dashboard/static/assets/diagram-QEK2KX5R-BRvDTbGD.js +0 -43
- truthound_dashboard/static/assets/diagram-S2PKOQOG-bQcczUkl.js +0 -24
- truthound_dashboard/static/assets/erDiagram-Q2GNP2WA-DPje7VMN.js +0 -60
- truthound_dashboard/static/assets/flowDiagram-NV44I4VS-B7BVtFVS.js +0 -162
- truthound_dashboard/static/assets/ganttDiagram-JELNMOA3-D6WKSS7U.js +0 -267
- truthound_dashboard/static/assets/gitGraphDiagram-NY62KEGX-D3vtVd3y.js +0 -65
- truthound_dashboard/static/assets/graph-BKgNKZVp.js +0 -1
- truthound_dashboard/static/assets/index-C6JSrkHo.css +0 -1
- truthound_dashboard/static/assets/index-DkU82VsU.js +0 -1800
- truthound_dashboard/static/assets/infoDiagram-WHAUD3N6-DnNCT429.js +0 -2
- truthound_dashboard/static/assets/journeyDiagram-XKPGCS4Q-DGiMozqS.js +0 -139
- truthound_dashboard/static/assets/kanban-definition-3W4ZIXB7-BV2gUgli.js +0 -89
- truthound_dashboard/static/assets/katex-Cu_Erd72.js +0 -261
- truthound_dashboard/static/assets/layout-DI2MfQ5G.js +0 -1
- truthound_dashboard/static/assets/min-DYdgXVcT.js +0 -1
- truthound_dashboard/static/assets/mindmap-definition-VGOIOE7T-C7x4ruxz.js +0 -68
- truthound_dashboard/static/assets/pieDiagram-ADFJNKIX-CAJaAB9f.js +0 -30
- truthound_dashboard/static/assets/quadrantDiagram-AYHSOK5B-DeqwDI46.js +0 -7
- truthound_dashboard/static/assets/requirementDiagram-UZGBJVZJ-e3XDpZIM.js +0 -64
- truthound_dashboard/static/assets/sankeyDiagram-TZEHDZUN-CNnAv5Ux.js +0 -10
- truthound_dashboard/static/assets/sequenceDiagram-WL72ISMW-Dsne-Of3.js +0 -145
- truthound_dashboard/static/assets/stateDiagram-FKZM4ZOC-Ee0sQXyb.js +0 -1
- truthound_dashboard/static/assets/stateDiagram-v2-4FDKWEC3-B26KqW_W.js +0 -1
- truthound_dashboard/static/assets/timeline-definition-IT6M3QCI-DZYi2yl3.js +0 -61
- truthound_dashboard/static/assets/treemap-KMMF4GRG-CY3f8In2.js +0 -128
- truthound_dashboard/static/assets/unmerged_dictionaries-Dd7xcPWG.js +0 -1
- truthound_dashboard/static/assets/xychartDiagram-PRI3JC2R-CS7fydZZ.js +0 -7
- truthound_dashboard-1.4.3.dist-info/METADATA +0 -505
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.3.dist-info → truthound_dashboard-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,40 +1,10 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Dashboard-specific routing adapters using truthound.checkpoint.routing.
|
|
2
2
|
|
|
3
|
-
This module provides
|
|
4
|
-
|
|
3
|
+
This module provides adapters that convert Dashboard notification events
|
|
4
|
+
into truthound RouteContext objects for use with truthound's routing system.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- Route: Defines a route with rule, actions, and priority
|
|
9
|
-
- RoutingResult: Result of route matching
|
|
10
|
-
- ActionRouter: Main routing engine
|
|
11
|
-
|
|
12
|
-
Example:
|
|
13
|
-
# Create routes
|
|
14
|
-
routes = [
|
|
15
|
-
Route(
|
|
16
|
-
name="critical_alerts",
|
|
17
|
-
rule=SeverityRule(min_severity="critical"),
|
|
18
|
-
actions=["pagerduty-channel"],
|
|
19
|
-
priority=100,
|
|
20
|
-
),
|
|
21
|
-
Route(
|
|
22
|
-
name="default",
|
|
23
|
-
rule=AlwaysRule(),
|
|
24
|
-
actions=["slack-channel"],
|
|
25
|
-
priority=0,
|
|
26
|
-
),
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
# Create router
|
|
30
|
-
router = ActionRouter(routes=routes)
|
|
31
|
-
|
|
32
|
-
# Match event
|
|
33
|
-
context = RouteContext(event=validation_event)
|
|
34
|
-
result = await router.match(context)
|
|
35
|
-
|
|
36
|
-
for route in result.matched_routes:
|
|
37
|
-
print(f"Matched: {route.name} -> {route.actions}")
|
|
6
|
+
The main purpose is to bridge the Dashboard's event system with truthound's
|
|
7
|
+
routing infrastructure.
|
|
38
8
|
"""
|
|
39
9
|
|
|
40
10
|
from __future__ import annotations
|
|
@@ -43,18 +13,18 @@ from dataclasses import dataclass, field
|
|
|
43
13
|
from datetime import datetime
|
|
44
14
|
from typing import TYPE_CHECKING, Any
|
|
45
15
|
|
|
46
|
-
from .
|
|
16
|
+
from truthound.checkpoint.routing.base import RouteContext
|
|
47
17
|
|
|
48
18
|
if TYPE_CHECKING:
|
|
49
19
|
from truthound_dashboard.core.notifications.base import NotificationEvent
|
|
50
20
|
|
|
51
21
|
|
|
52
22
|
@dataclass
|
|
53
|
-
class
|
|
54
|
-
"""
|
|
23
|
+
class DashboardRouteContext:
|
|
24
|
+
"""Dashboard-specific context that wraps truthound's RouteContext.
|
|
55
25
|
|
|
56
|
-
|
|
57
|
-
|
|
26
|
+
This provides helper methods for extracting data from Dashboard
|
|
27
|
+
notification events.
|
|
58
28
|
|
|
59
29
|
Attributes:
|
|
60
30
|
event: The notification event being routed.
|
|
@@ -68,315 +38,118 @@ class RouteContext:
|
|
|
68
38
|
|
|
69
39
|
def get_severity(self) -> str | None:
|
|
70
40
|
"""Get event severity if available."""
|
|
71
|
-
# Try event data first
|
|
72
41
|
if hasattr(self.event, "severity"):
|
|
73
42
|
return self.event.severity
|
|
74
43
|
if hasattr(self.event, "has_critical") and self.event.has_critical:
|
|
75
44
|
return "critical"
|
|
76
45
|
if hasattr(self.event, "has_high") and self.event.has_high:
|
|
77
46
|
return "high"
|
|
78
|
-
|
|
79
|
-
# Try metadata
|
|
80
47
|
return self.metadata.get("severity")
|
|
81
48
|
|
|
82
|
-
def get_issue_count(self) -> int
|
|
49
|
+
def get_issue_count(self) -> int:
|
|
83
50
|
"""Get issue count if available."""
|
|
84
51
|
if hasattr(self.event, "total_issues"):
|
|
85
52
|
return self.event.total_issues
|
|
86
|
-
return self.metadata.get("issue_count")
|
|
53
|
+
return self.metadata.get("issue_count", 0)
|
|
87
54
|
|
|
88
|
-
def get_pass_rate(self) -> float
|
|
55
|
+
def get_pass_rate(self) -> float:
|
|
89
56
|
"""Get validation pass rate if available."""
|
|
90
57
|
if hasattr(self.event, "pass_rate"):
|
|
91
58
|
return self.event.pass_rate
|
|
92
|
-
return self.metadata.get("pass_rate")
|
|
93
|
-
|
|
94
|
-
def get_tags(self) -> list[str]:
|
|
95
|
-
"""Get context tags."""
|
|
96
|
-
tags = list(self.metadata.get("tags", []))
|
|
59
|
+
return self.metadata.get("pass_rate", 100.0)
|
|
97
60
|
|
|
98
|
-
|
|
61
|
+
def get_tags(self) -> dict[str, str]:
|
|
62
|
+
"""Get context tags as dict."""
|
|
63
|
+
tags = dict(self.metadata.get("tags", {}))
|
|
99
64
|
if self.event.source_name:
|
|
100
|
-
tags
|
|
101
|
-
|
|
102
|
-
tags.append(f"type:{self.event.event_type}")
|
|
103
|
-
|
|
65
|
+
tags["source"] = self.event.source_name
|
|
66
|
+
tags["event_type"] = self.event.event_type
|
|
104
67
|
return tags
|
|
105
68
|
|
|
106
|
-
def
|
|
107
|
-
"""
|
|
108
|
-
if self.event.source_name:
|
|
109
|
-
return self.event.source_name
|
|
110
|
-
return self.metadata.get("data_asset")
|
|
111
|
-
|
|
112
|
-
def get_metadata(self, key: str) -> Any:
|
|
113
|
-
"""Get metadata value by key."""
|
|
114
|
-
# Check event data first
|
|
115
|
-
if hasattr(self.event, "data") and key in self.event.data:
|
|
116
|
-
return self.event.data[key]
|
|
117
|
-
return self.metadata.get(key)
|
|
118
|
-
|
|
119
|
-
def get_status(self) -> str | None:
|
|
120
|
-
"""Get validation status."""
|
|
121
|
-
if hasattr(self.event, "status"):
|
|
122
|
-
return self.event.status
|
|
123
|
-
# Infer from event type
|
|
124
|
-
if self.event.event_type in ("validation_failed", "schedule_failed"):
|
|
125
|
-
return "failure"
|
|
126
|
-
return self.metadata.get("status")
|
|
127
|
-
|
|
128
|
-
def get_error_message(self) -> str | None:
|
|
129
|
-
"""Get error message if available."""
|
|
130
|
-
if hasattr(self.event, "error_message"):
|
|
131
|
-
return self.event.error_message
|
|
132
|
-
return self.metadata.get("error_message")
|
|
133
|
-
|
|
69
|
+
def to_truthound_context(self) -> RouteContext:
|
|
70
|
+
"""Convert to truthound RouteContext.
|
|
134
71
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@classmethod
|
|
173
|
-
def from_dict(cls, data: dict[str, Any]) -> "Route | None":
|
|
174
|
-
"""Create Route from dictionary."""
|
|
175
|
-
rule_data = data.get("rule")
|
|
176
|
-
if not rule_data:
|
|
177
|
-
return None
|
|
178
|
-
|
|
179
|
-
rule = BaseRule.from_dict(rule_data)
|
|
180
|
-
if rule is None:
|
|
181
|
-
return None
|
|
182
|
-
|
|
183
|
-
return cls(
|
|
184
|
-
name=data.get("name", "unnamed"),
|
|
185
|
-
rule=rule,
|
|
186
|
-
actions=data.get("actions", []),
|
|
187
|
-
priority=data.get("priority", 0),
|
|
188
|
-
is_active=data.get("is_active", True),
|
|
189
|
-
escalation_policy_id=data.get("escalation_policy_id"),
|
|
190
|
-
stop_on_match=data.get("stop_on_match", False),
|
|
191
|
-
metadata=data.get("metadata", {}),
|
|
72
|
+
Creates a RouteContext object that can be used with truthound's
|
|
73
|
+
ActionRouter for rule evaluation.
|
|
74
|
+
"""
|
|
75
|
+
# Determine severity counts
|
|
76
|
+
severity = self.get_severity()
|
|
77
|
+
critical_issues = 1 if severity == "critical" else 0
|
|
78
|
+
high_issues = 1 if severity == "high" else 0
|
|
79
|
+
medium_issues = 1 if severity == "medium" else 0
|
|
80
|
+
low_issues = 1 if severity == "low" else 0
|
|
81
|
+
info_issues = 1 if severity == "info" else 0
|
|
82
|
+
|
|
83
|
+
# Determine status
|
|
84
|
+
status = "success"
|
|
85
|
+
if hasattr(self.event, "event_type"):
|
|
86
|
+
if "failed" in self.event.event_type.lower():
|
|
87
|
+
status = "failure"
|
|
88
|
+
elif "error" in self.event.event_type.lower():
|
|
89
|
+
status = "error"
|
|
90
|
+
elif "warning" in self.event.event_type.lower():
|
|
91
|
+
status = "warning"
|
|
92
|
+
|
|
93
|
+
return RouteContext(
|
|
94
|
+
checkpoint_name=self.event.source_name or "dashboard_event",
|
|
95
|
+
run_id=self.metadata.get("run_id", "dashboard"),
|
|
96
|
+
status=status,
|
|
97
|
+
data_asset=self.event.source_name or "unknown",
|
|
98
|
+
run_time=self.timestamp,
|
|
99
|
+
total_issues=self.get_issue_count(),
|
|
100
|
+
critical_issues=critical_issues,
|
|
101
|
+
high_issues=high_issues,
|
|
102
|
+
medium_issues=medium_issues,
|
|
103
|
+
low_issues=low_issues,
|
|
104
|
+
info_issues=info_issues,
|
|
105
|
+
pass_rate=self.get_pass_rate(),
|
|
106
|
+
tags=self.get_tags(),
|
|
107
|
+
metadata=self.metadata,
|
|
108
|
+
error=self.metadata.get("error"),
|
|
192
109
|
)
|
|
193
110
|
|
|
194
111
|
|
|
195
112
|
@dataclass
|
|
196
|
-
class
|
|
197
|
-
"""Result of
|
|
198
|
-
|
|
199
|
-
Attributes:
|
|
200
|
-
matched_routes: Routes that matched the context.
|
|
201
|
-
all_actions: Deduplicated list of all action channel IDs.
|
|
202
|
-
evaluation_time_ms: Time taken to evaluate routes.
|
|
203
|
-
context: The evaluated context.
|
|
204
|
-
"""
|
|
205
|
-
|
|
206
|
-
matched_routes: list[Route]
|
|
207
|
-
all_actions: list[str]
|
|
208
|
-
evaluation_time_ms: float
|
|
209
|
-
context: RouteContext
|
|
210
|
-
|
|
211
|
-
@property
|
|
212
|
-
def has_matches(self) -> bool:
|
|
213
|
-
"""Check if any routes matched."""
|
|
214
|
-
return len(self.matched_routes) > 0
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
class ActionRouter:
|
|
218
|
-
"""Main routing engine.
|
|
219
|
-
|
|
220
|
-
Evaluates events against configured routes and returns
|
|
221
|
-
matching routes sorted by priority.
|
|
222
|
-
|
|
223
|
-
Routes are evaluated in priority order (highest first).
|
|
224
|
-
If a route has `stop_on_match=True`, lower priority routes
|
|
225
|
-
are skipped once it matches.
|
|
113
|
+
class DashboardRoutingResult:
|
|
114
|
+
"""Result of routing evaluation for Dashboard notifications.
|
|
226
115
|
|
|
227
116
|
Attributes:
|
|
228
|
-
|
|
229
|
-
|
|
117
|
+
matched_routes: List of route names that matched.
|
|
118
|
+
channel_ids: Set of channel IDs to send notifications to.
|
|
119
|
+
timestamp: When routing was evaluated.
|
|
120
|
+
context: The context that was evaluated.
|
|
230
121
|
"""
|
|
231
122
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
) -> None:
|
|
237
|
-
"""Initialize the router.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
routes: List of routes to evaluate.
|
|
241
|
-
default_route: Fallback route if nothing matches.
|
|
242
|
-
"""
|
|
243
|
-
self.routes = routes or []
|
|
244
|
-
self.default_route = default_route
|
|
245
|
-
self._sort_routes()
|
|
246
|
-
|
|
247
|
-
def _sort_routes(self) -> None:
|
|
248
|
-
"""Sort routes by priority (highest first)."""
|
|
249
|
-
self.routes.sort(key=lambda r: r.priority, reverse=True)
|
|
250
|
-
|
|
251
|
-
def add_route(self, route: Route) -> None:
|
|
252
|
-
"""Add a route to the router.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
route: Route to add.
|
|
256
|
-
"""
|
|
257
|
-
self.routes.append(route)
|
|
258
|
-
self._sort_routes()
|
|
259
|
-
|
|
260
|
-
def remove_route(self, name: str) -> bool:
|
|
261
|
-
"""Remove a route by name.
|
|
262
|
-
|
|
263
|
-
Args:
|
|
264
|
-
name: Route name to remove.
|
|
265
|
-
|
|
266
|
-
Returns:
|
|
267
|
-
True if route was found and removed.
|
|
268
|
-
"""
|
|
269
|
-
for i, route in enumerate(self.routes):
|
|
270
|
-
if route.name == name:
|
|
271
|
-
del self.routes[i]
|
|
272
|
-
return True
|
|
273
|
-
return False
|
|
274
|
-
|
|
275
|
-
def get_route(self, name: str) -> Route | None:
|
|
276
|
-
"""Get a route by name.
|
|
277
|
-
|
|
278
|
-
Args:
|
|
279
|
-
name: Route name.
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
Route if found, None otherwise.
|
|
283
|
-
"""
|
|
284
|
-
for route in self.routes:
|
|
285
|
-
if route.name == name:
|
|
286
|
-
return route
|
|
287
|
-
return None
|
|
288
|
-
|
|
289
|
-
async def match(self, context: RouteContext) -> RoutingResult:
|
|
290
|
-
"""Evaluate all routes against the context.
|
|
291
|
-
|
|
292
|
-
Args:
|
|
293
|
-
context: The routing context to evaluate.
|
|
294
|
-
|
|
295
|
-
Returns:
|
|
296
|
-
RoutingResult with matched routes and actions.
|
|
297
|
-
"""
|
|
298
|
-
import time
|
|
299
|
-
|
|
300
|
-
start_time = time.perf_counter()
|
|
301
|
-
matched_routes: list[Route] = []
|
|
302
|
-
all_actions: set[str] = set()
|
|
303
|
-
|
|
304
|
-
for route in self.routes:
|
|
305
|
-
# Skip inactive routes
|
|
306
|
-
if not route.is_active:
|
|
307
|
-
continue
|
|
308
|
-
|
|
309
|
-
# Evaluate rule
|
|
310
|
-
try:
|
|
311
|
-
if await route.rule.matches(context):
|
|
312
|
-
matched_routes.append(route)
|
|
313
|
-
all_actions.update(route.actions)
|
|
314
|
-
|
|
315
|
-
# Stop if this route has stop_on_match
|
|
316
|
-
if route.stop_on_match:
|
|
317
|
-
break
|
|
318
|
-
except Exception:
|
|
319
|
-
# Log error but continue with other routes
|
|
320
|
-
continue
|
|
321
|
-
|
|
322
|
-
# Use default route if nothing matched
|
|
323
|
-
if not matched_routes and self.default_route:
|
|
324
|
-
if self.default_route.is_active:
|
|
325
|
-
try:
|
|
326
|
-
if await self.default_route.rule.matches(context):
|
|
327
|
-
matched_routes.append(self.default_route)
|
|
328
|
-
all_actions.update(self.default_route.actions)
|
|
329
|
-
except Exception:
|
|
330
|
-
pass
|
|
123
|
+
matched_routes: list[str] = field(default_factory=list)
|
|
124
|
+
channel_ids: set[str] = field(default_factory=set)
|
|
125
|
+
timestamp: datetime = field(default_factory=datetime.utcnow)
|
|
126
|
+
context: DashboardRouteContext | None = None
|
|
331
127
|
|
|
332
|
-
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
333
128
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
)
|
|
129
|
+
def create_route_context_from_event(
|
|
130
|
+
event: "NotificationEvent",
|
|
131
|
+
metadata: dict[str, Any] | None = None,
|
|
132
|
+
) -> RouteContext:
|
|
133
|
+
"""Create a truthound RouteContext from a Dashboard notification event.
|
|
340
134
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
event: "NotificationEvent",
|
|
344
|
-
metadata: dict[str, Any] | None = None,
|
|
345
|
-
) -> list[str]:
|
|
346
|
-
"""Convenience method to get channel IDs for an event.
|
|
135
|
+
This is a convenience function for converting Dashboard events into
|
|
136
|
+
truthound's routing context.
|
|
347
137
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
138
|
+
Args:
|
|
139
|
+
event: The notification event.
|
|
140
|
+
metadata: Optional additional metadata.
|
|
351
141
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
"""
|
|
355
|
-
context = RouteContext(
|
|
356
|
-
event=event,
|
|
357
|
-
metadata=metadata or {},
|
|
358
|
-
)
|
|
359
|
-
result = await self.match(context)
|
|
360
|
-
return result.all_actions
|
|
142
|
+
Returns:
|
|
143
|
+
RouteContext for use with truthound's ActionRouter.
|
|
361
144
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return {
|
|
365
|
-
"routes": [r.to_dict() for r in self.routes],
|
|
366
|
-
"default_route": self.default_route.to_dict() if self.default_route else None,
|
|
367
|
-
}
|
|
145
|
+
Example:
|
|
146
|
+
from truthound.checkpoint.routing import ActionRouter
|
|
368
147
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
default_route = None
|
|
379
|
-
if data.get("default_route"):
|
|
380
|
-
default_route = Route.from_dict(data["default_route"])
|
|
381
|
-
|
|
382
|
-
return cls(routes=routes, default_route=default_route)
|
|
148
|
+
context = create_route_context_from_event(event, {"env": "prod"})
|
|
149
|
+
matched = router.route(context)
|
|
150
|
+
"""
|
|
151
|
+
dashboard_context = DashboardRouteContext(
|
|
152
|
+
event=event,
|
|
153
|
+
metadata=metadata or {},
|
|
154
|
+
)
|
|
155
|
+
return dashboard_context.to_truthound_context()
|