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.
- 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 +645 -23
- 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 +15 -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.1.dist-info/METADATA +312 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.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.4.dist-info/METADATA +0 -507
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.4.4.dist-info → truthound_dashboard-1.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -19,7 +19,7 @@ import uuid
|
|
|
19
19
|
from datetime import datetime, timedelta
|
|
20
20
|
from typing import TYPE_CHECKING, Any
|
|
21
21
|
|
|
22
|
-
from sqlalchemy import select, func, and_
|
|
22
|
+
from sqlalchemy import select, func, and_, desc
|
|
23
23
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
24
24
|
|
|
25
25
|
from .drift_sampling import (
|
|
@@ -80,7 +80,7 @@ class DriftMonitorService:
|
|
|
80
80
|
Raises:
|
|
81
81
|
ValueError: If source not found.
|
|
82
82
|
"""
|
|
83
|
-
from truthound_dashboard.core.
|
|
83
|
+
from truthound_dashboard.core.services import DriftService
|
|
84
84
|
from truthound_dashboard.db.models import Source
|
|
85
85
|
|
|
86
86
|
# Get source details for display
|
|
@@ -332,21 +332,40 @@ class DriftMonitorService:
|
|
|
332
332
|
logger.info(f"Deleted drift monitor: {monitor_id}")
|
|
333
333
|
return True
|
|
334
334
|
|
|
335
|
-
async def run_monitor(self, monitor_id: str) -> "DriftComparison | None":
|
|
335
|
+
async def run_monitor(self, monitor_id: str, force: bool = False) -> "DriftComparison | None":
|
|
336
336
|
"""Execute a drift monitoring run.
|
|
337
337
|
|
|
338
|
+
Creates a DriftMonitorRun record to track execution history,
|
|
339
|
+
then performs drift comparison and creates alerts if needed.
|
|
340
|
+
|
|
338
341
|
Args:
|
|
339
342
|
monitor_id: Monitor ID.
|
|
343
|
+
force: If True, run even if monitor is paused (for manual runs).
|
|
340
344
|
|
|
341
345
|
Returns:
|
|
342
346
|
Drift comparison result or None on error.
|
|
343
347
|
"""
|
|
344
|
-
from truthound_dashboard.core.
|
|
348
|
+
from truthound_dashboard.core.services import DriftService
|
|
349
|
+
from truthound_dashboard.db.models import DriftMonitorRun
|
|
345
350
|
|
|
346
351
|
monitor = await self.get_monitor(monitor_id)
|
|
347
|
-
if not monitor
|
|
352
|
+
if not monitor:
|
|
353
|
+
return None
|
|
354
|
+
# Skip status check if force=True (manual run) or if status is active
|
|
355
|
+
if not force and monitor.status != "active":
|
|
348
356
|
return None
|
|
349
357
|
|
|
358
|
+
# Create run record to track execution
|
|
359
|
+
start_time = datetime.utcnow()
|
|
360
|
+
run = DriftMonitorRun(
|
|
361
|
+
id=str(uuid.uuid4()),
|
|
362
|
+
monitor_id=monitor.id,
|
|
363
|
+
status="running",
|
|
364
|
+
created_at=start_time,
|
|
365
|
+
)
|
|
366
|
+
self.session.add(run)
|
|
367
|
+
await self.session.flush() # Get run.id without committing
|
|
368
|
+
|
|
350
369
|
try:
|
|
351
370
|
# Create drift service and run comparison
|
|
352
371
|
drift_service = DriftService(self.session)
|
|
@@ -358,45 +377,79 @@ class DriftMonitorService:
|
|
|
358
377
|
columns=monitor.columns_json,
|
|
359
378
|
)
|
|
360
379
|
|
|
380
|
+
# Calculate duration
|
|
381
|
+
end_time = datetime.utcnow()
|
|
382
|
+
duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
|
383
|
+
|
|
384
|
+
# Extract drifted column names
|
|
385
|
+
drifted_columns = []
|
|
386
|
+
if comparison.result_json and "columns" in comparison.result_json:
|
|
387
|
+
drifted_columns = [
|
|
388
|
+
col["column"]
|
|
389
|
+
for col in comparison.result_json["columns"]
|
|
390
|
+
if col.get("drifted", False)
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
# Update run record with results
|
|
394
|
+
run.status = "completed"
|
|
395
|
+
run.has_drift = comparison.has_drift
|
|
396
|
+
run.max_drift_score = comparison.drift_percentage
|
|
397
|
+
run.total_columns = comparison.total_columns
|
|
398
|
+
run.drifted_columns = len(drifted_columns)
|
|
399
|
+
run.column_results = comparison.result_json
|
|
400
|
+
run.duration_ms = duration_ms
|
|
401
|
+
run.completed_at = end_time
|
|
402
|
+
|
|
361
403
|
# Update monitor stats
|
|
362
|
-
monitor.last_run_at =
|
|
404
|
+
monitor.last_run_at = end_time
|
|
363
405
|
monitor.total_runs += 1
|
|
364
|
-
monitor.last_drift_detected = comparison.has_drift
|
|
365
406
|
|
|
366
407
|
if comparison.has_drift:
|
|
367
408
|
monitor.drift_detected_count += 1
|
|
368
409
|
monitor.consecutive_drift_count += 1
|
|
369
410
|
|
|
370
|
-
# Create alert if configured
|
|
411
|
+
# Create alert if configured, linking to this run
|
|
371
412
|
if monitor.alert_on_drift:
|
|
372
|
-
await self._create_drift_alert(monitor, comparison)
|
|
413
|
+
await self._create_drift_alert(monitor, comparison, run.id)
|
|
373
414
|
else:
|
|
374
415
|
monitor.consecutive_drift_count = 0
|
|
375
416
|
|
|
376
417
|
await self.session.commit()
|
|
377
418
|
await self.session.refresh(monitor)
|
|
419
|
+
await self.session.refresh(run)
|
|
378
420
|
|
|
379
421
|
logger.info(
|
|
380
|
-
f"Drift monitor {monitor_id} run complete:
|
|
422
|
+
f"Drift monitor {monitor_id} run complete: "
|
|
423
|
+
f"run_id={run.id}, drift={comparison.has_drift}, duration={duration_ms}ms"
|
|
381
424
|
)
|
|
382
425
|
return comparison
|
|
383
426
|
|
|
384
427
|
except Exception as e:
|
|
385
|
-
|
|
428
|
+
# Update run record with error
|
|
429
|
+
end_time = datetime.utcnow()
|
|
430
|
+
run.status = "failed"
|
|
431
|
+
run.error_message = str(e)
|
|
432
|
+
run.duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
|
433
|
+
run.completed_at = end_time
|
|
434
|
+
|
|
386
435
|
monitor.status = "error"
|
|
387
436
|
await self.session.commit()
|
|
437
|
+
|
|
438
|
+
logger.error(f"Drift monitor {monitor_id} run failed: {e}")
|
|
388
439
|
return None
|
|
389
440
|
|
|
390
441
|
async def _create_drift_alert(
|
|
391
442
|
self,
|
|
392
443
|
monitor: "DriftMonitor",
|
|
393
444
|
comparison: "DriftComparison",
|
|
445
|
+
run_id: str | None = None,
|
|
394
446
|
) -> "DriftAlert":
|
|
395
447
|
"""Create a drift alert.
|
|
396
448
|
|
|
397
449
|
Args:
|
|
398
450
|
monitor: Drift monitor.
|
|
399
451
|
comparison: Drift comparison result.
|
|
452
|
+
run_id: Optional DriftMonitorRun ID to link to this alert.
|
|
400
453
|
|
|
401
454
|
Returns:
|
|
402
455
|
Created alert.
|
|
@@ -426,21 +479,164 @@ class DriftMonitorService:
|
|
|
426
479
|
alert = DriftAlert(
|
|
427
480
|
id=str(uuid.uuid4()),
|
|
428
481
|
monitor_id=monitor.id,
|
|
429
|
-
|
|
482
|
+
run_id=run_id, # Link to DriftMonitorRun for execution history tracking
|
|
430
483
|
severity=severity,
|
|
431
|
-
|
|
432
|
-
|
|
484
|
+
drift_score=drift_pct,
|
|
485
|
+
affected_columns=drifted_columns,
|
|
433
486
|
message=f"Drift detected: {drift_pct:.1f}% of columns drifted ({len(drifted_columns)} columns)",
|
|
434
|
-
status="
|
|
487
|
+
status="active",
|
|
435
488
|
)
|
|
436
489
|
|
|
437
490
|
self.session.add(alert)
|
|
438
|
-
|
|
439
|
-
await self.session.
|
|
491
|
+
# Don't commit here - let the caller manage the transaction
|
|
492
|
+
await self.session.flush()
|
|
440
493
|
|
|
441
|
-
logger.info(f"Created drift alert: {alert.id} (severity={severity})")
|
|
494
|
+
logger.info(f"Created drift alert: {alert.id} (severity={severity}, run_id={run_id})")
|
|
442
495
|
return alert
|
|
443
496
|
|
|
497
|
+
# Run History Management
|
|
498
|
+
|
|
499
|
+
async def list_runs(
|
|
500
|
+
self,
|
|
501
|
+
monitor_id: str,
|
|
502
|
+
status: str | None = None,
|
|
503
|
+
limit: int = 50,
|
|
504
|
+
offset: int = 0,
|
|
505
|
+
) -> tuple[list["DriftMonitorRun"], int]:
|
|
506
|
+
"""List drift monitor runs.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
monitor_id: Monitor ID to list runs for.
|
|
510
|
+
status: Filter by status (running, completed, failed).
|
|
511
|
+
limit: Maximum number of runs.
|
|
512
|
+
offset: Number to skip.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
Tuple of (runs, total_count).
|
|
516
|
+
"""
|
|
517
|
+
from truthound_dashboard.db.models import DriftMonitorRun
|
|
518
|
+
|
|
519
|
+
query = select(DriftMonitorRun).where(DriftMonitorRun.monitor_id == monitor_id)
|
|
520
|
+
count_query = select(func.count(DriftMonitorRun.id)).where(
|
|
521
|
+
DriftMonitorRun.monitor_id == monitor_id
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
if status:
|
|
525
|
+
query = query.where(DriftMonitorRun.status == status)
|
|
526
|
+
count_query = count_query.where(DriftMonitorRun.status == status)
|
|
527
|
+
|
|
528
|
+
# Order by created_at descending (newest first)
|
|
529
|
+
query = query.order_by(desc(DriftMonitorRun.created_at))
|
|
530
|
+
query = query.offset(offset).limit(limit)
|
|
531
|
+
|
|
532
|
+
result = await self.session.execute(query)
|
|
533
|
+
runs = list(result.scalars().all())
|
|
534
|
+
|
|
535
|
+
count_result = await self.session.execute(count_query)
|
|
536
|
+
total = count_result.scalar() or 0
|
|
537
|
+
|
|
538
|
+
return runs, total
|
|
539
|
+
|
|
540
|
+
async def get_run(self, run_id: str) -> "DriftMonitorRun | None":
|
|
541
|
+
"""Get a specific run by ID.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
run_id: Run ID.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
Run or None if not found.
|
|
548
|
+
"""
|
|
549
|
+
from truthound_dashboard.db.models import DriftMonitorRun
|
|
550
|
+
|
|
551
|
+
result = await self.session.execute(
|
|
552
|
+
select(DriftMonitorRun).where(DriftMonitorRun.id == run_id)
|
|
553
|
+
)
|
|
554
|
+
return result.scalar_one_or_none()
|
|
555
|
+
|
|
556
|
+
async def get_latest_run(self, monitor_id: str) -> "DriftMonitorRun | None":
|
|
557
|
+
"""Get the latest run for a monitor.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
monitor_id: Monitor ID.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Latest run or None if no runs exist.
|
|
564
|
+
"""
|
|
565
|
+
from truthound_dashboard.db.models import DriftMonitorRun
|
|
566
|
+
|
|
567
|
+
result = await self.session.execute(
|
|
568
|
+
select(DriftMonitorRun)
|
|
569
|
+
.where(DriftMonitorRun.monitor_id == monitor_id)
|
|
570
|
+
.order_by(desc(DriftMonitorRun.created_at))
|
|
571
|
+
.limit(1)
|
|
572
|
+
)
|
|
573
|
+
return result.scalar_one_or_none()
|
|
574
|
+
|
|
575
|
+
async def get_run_statistics(self, monitor_id: str) -> dict:
|
|
576
|
+
"""Get run statistics for a monitor.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
monitor_id: Monitor ID.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Dictionary with run statistics.
|
|
583
|
+
"""
|
|
584
|
+
from truthound_dashboard.db.models import DriftMonitorRun
|
|
585
|
+
|
|
586
|
+
# Total runs
|
|
587
|
+
total_result = await self.session.execute(
|
|
588
|
+
select(func.count(DriftMonitorRun.id)).where(
|
|
589
|
+
DriftMonitorRun.monitor_id == monitor_id
|
|
590
|
+
)
|
|
591
|
+
)
|
|
592
|
+
total_runs = total_result.scalar() or 0
|
|
593
|
+
|
|
594
|
+
# Completed runs
|
|
595
|
+
completed_result = await self.session.execute(
|
|
596
|
+
select(func.count(DriftMonitorRun.id)).where(
|
|
597
|
+
DriftMonitorRun.monitor_id == monitor_id,
|
|
598
|
+
DriftMonitorRun.status == "completed",
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
completed_runs = completed_result.scalar() or 0
|
|
602
|
+
|
|
603
|
+
# Failed runs
|
|
604
|
+
failed_result = await self.session.execute(
|
|
605
|
+
select(func.count(DriftMonitorRun.id)).where(
|
|
606
|
+
DriftMonitorRun.monitor_id == monitor_id,
|
|
607
|
+
DriftMonitorRun.status == "failed",
|
|
608
|
+
)
|
|
609
|
+
)
|
|
610
|
+
failed_runs = failed_result.scalar() or 0
|
|
611
|
+
|
|
612
|
+
# Runs with drift
|
|
613
|
+
drift_result = await self.session.execute(
|
|
614
|
+
select(func.count(DriftMonitorRun.id)).where(
|
|
615
|
+
DriftMonitorRun.monitor_id == monitor_id,
|
|
616
|
+
DriftMonitorRun.has_drift == True, # noqa: E712
|
|
617
|
+
)
|
|
618
|
+
)
|
|
619
|
+
runs_with_drift = drift_result.scalar() or 0
|
|
620
|
+
|
|
621
|
+
# Average duration
|
|
622
|
+
avg_duration_result = await self.session.execute(
|
|
623
|
+
select(func.avg(DriftMonitorRun.duration_ms)).where(
|
|
624
|
+
DriftMonitorRun.monitor_id == monitor_id,
|
|
625
|
+
DriftMonitorRun.status == "completed",
|
|
626
|
+
)
|
|
627
|
+
)
|
|
628
|
+
avg_duration_ms = avg_duration_result.scalar() or 0
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
"total_runs": total_runs,
|
|
632
|
+
"completed_runs": completed_runs,
|
|
633
|
+
"failed_runs": failed_runs,
|
|
634
|
+
"runs_with_drift": runs_with_drift,
|
|
635
|
+
"success_rate": (completed_runs / total_runs * 100) if total_runs > 0 else 0,
|
|
636
|
+
"drift_rate": (runs_with_drift / completed_runs * 100) if completed_runs > 0 else 0,
|
|
637
|
+
"avg_duration_ms": int(avg_duration_ms),
|
|
638
|
+
}
|
|
639
|
+
|
|
444
640
|
# Alert Management
|
|
445
641
|
|
|
446
642
|
async def list_alerts(
|
|
@@ -1153,7 +1349,7 @@ class DriftMonitorService:
|
|
|
1153
1349
|
|
|
1154
1350
|
# Run the comparison with sampling
|
|
1155
1351
|
# In a real implementation, this would call truthound.compare with sampling
|
|
1156
|
-
from truthound_dashboard.core.
|
|
1352
|
+
from truthound_dashboard.core.services import DriftService
|
|
1157
1353
|
|
|
1158
1354
|
drift_service = DriftService(self.session)
|
|
1159
1355
|
|
|
@@ -1228,7 +1424,7 @@ class DriftMonitorService:
|
|
|
1228
1424
|
monitor.total_runs += 1
|
|
1229
1425
|
|
|
1230
1426
|
has_drift = len(all_drifted_columns) > 0
|
|
1231
|
-
|
|
1427
|
+
# last_drift_detected is computed from latest_run, no need to set
|
|
1232
1428
|
|
|
1233
1429
|
if has_drift:
|
|
1234
1430
|
monitor.drift_detected_count += 1
|