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
truthound_dashboard/db/models.py
CHANGED
|
@@ -213,15 +213,54 @@ class Source(Base, UUIDMixin, TimestampMixin):
|
|
|
213
213
|
assets: Mapped[list[CatalogAsset]] = relationship(
|
|
214
214
|
"CatalogAsset",
|
|
215
215
|
back_populates="source",
|
|
216
|
+
cascade="all, delete-orphan",
|
|
216
217
|
lazy="selectin",
|
|
217
218
|
)
|
|
218
219
|
# Generated reports for this source
|
|
219
220
|
generated_reports: Mapped[list["GeneratedReport"]] = relationship(
|
|
220
221
|
"GeneratedReport",
|
|
221
222
|
back_populates="source",
|
|
223
|
+
cascade="all, delete-orphan",
|
|
222
224
|
lazy="selectin",
|
|
223
225
|
order_by="desc(GeneratedReport.created_at)",
|
|
224
226
|
)
|
|
227
|
+
# Anomaly detections for this source
|
|
228
|
+
anomaly_detections: Mapped[list["AnomalyDetection"]] = relationship(
|
|
229
|
+
"AnomalyDetection",
|
|
230
|
+
back_populates="source",
|
|
231
|
+
cascade="all, delete-orphan",
|
|
232
|
+
lazy="selectin",
|
|
233
|
+
)
|
|
234
|
+
# Drift comparisons where this source is the baseline
|
|
235
|
+
baseline_comparisons: Mapped[list["DriftComparison"]] = relationship(
|
|
236
|
+
"DriftComparison",
|
|
237
|
+
foreign_keys="[DriftComparison.baseline_source_id]",
|
|
238
|
+
back_populates="baseline_source",
|
|
239
|
+
cascade="all, delete-orphan",
|
|
240
|
+
lazy="selectin",
|
|
241
|
+
)
|
|
242
|
+
# Drift comparisons where this source is the current
|
|
243
|
+
current_comparisons: Mapped[list["DriftComparison"]] = relationship(
|
|
244
|
+
"DriftComparison",
|
|
245
|
+
foreign_keys="[DriftComparison.current_source_id]",
|
|
246
|
+
back_populates="current_source",
|
|
247
|
+
cascade="all, delete-orphan",
|
|
248
|
+
lazy="selectin",
|
|
249
|
+
)
|
|
250
|
+
# Data masks for this source
|
|
251
|
+
data_masks: Mapped[list["DataMask"]] = relationship(
|
|
252
|
+
"DataMask",
|
|
253
|
+
back_populates="source",
|
|
254
|
+
cascade="all, delete-orphan",
|
|
255
|
+
lazy="selectin",
|
|
256
|
+
)
|
|
257
|
+
# PII scans for this source
|
|
258
|
+
pii_scans: Mapped[list["PIIScan"]] = relationship(
|
|
259
|
+
"PIIScan",
|
|
260
|
+
back_populates="source",
|
|
261
|
+
cascade="all, delete-orphan",
|
|
262
|
+
lazy="selectin",
|
|
263
|
+
)
|
|
225
264
|
|
|
226
265
|
@property
|
|
227
266
|
def source_path(self) -> str | None:
|
|
@@ -321,7 +360,8 @@ class Rule(Base, UUIDMixin, TimestampMixin):
|
|
|
321
360
|
@property
|
|
322
361
|
def column_count(self) -> int:
|
|
323
362
|
"""Get number of columns with rules defined."""
|
|
324
|
-
|
|
363
|
+
rules = self.column_rules
|
|
364
|
+
return len(rules) if rules else 0
|
|
325
365
|
|
|
326
366
|
def deactivate(self) -> None:
|
|
327
367
|
"""Mark this rule as inactive."""
|
|
@@ -722,12 +762,12 @@ class DriftComparison(Base, UUIDMixin, TimestampMixin):
|
|
|
722
762
|
baseline_source: Mapped[Source] = relationship(
|
|
723
763
|
"Source",
|
|
724
764
|
foreign_keys=[baseline_source_id],
|
|
725
|
-
|
|
765
|
+
back_populates="baseline_comparisons",
|
|
726
766
|
)
|
|
727
767
|
current_source: Mapped[Source] = relationship(
|
|
728
768
|
"Source",
|
|
729
769
|
foreign_keys=[current_source_id],
|
|
730
|
-
|
|
770
|
+
back_populates="current_comparisons",
|
|
731
771
|
)
|
|
732
772
|
|
|
733
773
|
@property
|
|
@@ -825,7 +865,7 @@ class DataMask(Base, UUIDMixin):
|
|
|
825
865
|
# Relationships
|
|
826
866
|
source: Mapped[Source] = relationship(
|
|
827
867
|
"Source",
|
|
828
|
-
|
|
868
|
+
back_populates="data_masks",
|
|
829
869
|
)
|
|
830
870
|
|
|
831
871
|
@property
|
|
@@ -939,7 +979,7 @@ class PIIScan(Base, UUIDMixin):
|
|
|
939
979
|
# Relationships
|
|
940
980
|
source: Mapped[Source] = relationship(
|
|
941
981
|
"Source",
|
|
942
|
-
|
|
982
|
+
back_populates="pii_scans",
|
|
943
983
|
)
|
|
944
984
|
|
|
945
985
|
@property
|
|
@@ -1240,6 +1280,7 @@ class GlossaryCategory(Base, UUIDMixin, TimestampMixin):
|
|
|
1240
1280
|
"GlossaryCategory",
|
|
1241
1281
|
remote_side="GlossaryCategory.id",
|
|
1242
1282
|
back_populates="children",
|
|
1283
|
+
lazy="selectin",
|
|
1243
1284
|
)
|
|
1244
1285
|
children: Mapped[list[GlossaryCategory]] = relationship(
|
|
1245
1286
|
"GlossaryCategory",
|
|
@@ -1307,6 +1348,7 @@ class GlossaryTerm(Base, UUIDMixin, TimestampMixin):
|
|
|
1307
1348
|
category: Mapped[GlossaryCategory | None] = relationship(
|
|
1308
1349
|
"GlossaryCategory",
|
|
1309
1350
|
back_populates="terms",
|
|
1351
|
+
lazy="selectin",
|
|
1310
1352
|
)
|
|
1311
1353
|
history: Mapped[list[TermHistory]] = relationship(
|
|
1312
1354
|
"TermHistory",
|
|
@@ -1538,6 +1580,7 @@ class CatalogAsset(Base, UUIDMixin, TimestampMixin):
|
|
|
1538
1580
|
source: Mapped[Source | None] = relationship(
|
|
1539
1581
|
"Source",
|
|
1540
1582
|
back_populates="assets",
|
|
1583
|
+
lazy="selectin",
|
|
1541
1584
|
)
|
|
1542
1585
|
columns: Mapped[list[AssetColumn]] = relationship(
|
|
1543
1586
|
"AssetColumn",
|
|
@@ -2548,6 +2591,7 @@ class LineageNode(Base, UUIDMixin, TimestampMixin):
|
|
|
2548
2591
|
__table_args__ = (
|
|
2549
2592
|
Index("idx_lineage_nodes_type", "node_type"),
|
|
2550
2593
|
Index("idx_lineage_nodes_source", "source_id"),
|
|
2594
|
+
Index("idx_lineage_nodes_name_type", "name", "node_type", unique=True),
|
|
2551
2595
|
)
|
|
2552
2596
|
|
|
2553
2597
|
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
@@ -2873,7 +2917,7 @@ class AnomalyDetection(Base, UUIDMixin):
|
|
|
2873
2917
|
# Relationships
|
|
2874
2918
|
source: Mapped[Source] = relationship(
|
|
2875
2919
|
"Source",
|
|
2876
|
-
|
|
2920
|
+
back_populates="anomaly_detections",
|
|
2877
2921
|
)
|
|
2878
2922
|
|
|
2879
2923
|
@property
|
|
@@ -3696,12 +3740,17 @@ class DriftMonitor(Base, UUIDMixin, TimestampMixin):
|
|
|
3696
3740
|
current_source_id: Reference to current Source.
|
|
3697
3741
|
status: Monitor status.
|
|
3698
3742
|
method: Drift detection method.
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3743
|
+
threshold: Drift threshold for detection.
|
|
3744
|
+
alert_threshold_critical: Critical drift threshold for alerts.
|
|
3745
|
+
alert_threshold_high: High drift threshold for alerts.
|
|
3746
|
+
columns_json: Columns to monitor (None = all).
|
|
3747
|
+
cron_expression: Optional cron expression for scheduling.
|
|
3748
|
+
alert_on_drift: Whether to create alerts on drift.
|
|
3749
|
+
notification_channel_ids_json: IDs of notification channels.
|
|
3703
3750
|
last_run_at: Last run timestamp.
|
|
3704
|
-
|
|
3751
|
+
total_runs: Total number of runs.
|
|
3752
|
+
drift_detected_count: Number of runs with drift detected.
|
|
3753
|
+
consecutive_drift_count: Consecutive runs with drift.
|
|
3705
3754
|
config: Additional configuration.
|
|
3706
3755
|
"""
|
|
3707
3756
|
|
|
@@ -3733,12 +3782,17 @@ class DriftMonitor(Base, UUIDMixin, TimestampMixin):
|
|
|
3733
3782
|
index=True,
|
|
3734
3783
|
)
|
|
3735
3784
|
method: Mapped[str] = mapped_column(String(30), nullable=False, default="auto")
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3785
|
+
threshold: Mapped[float] = mapped_column(Float, default=0.05, nullable=False)
|
|
3786
|
+
alert_threshold_critical: Mapped[float] = mapped_column(Float, default=0.3, nullable=False)
|
|
3787
|
+
alert_threshold_high: Mapped[float] = mapped_column(Float, default=0.2, nullable=False)
|
|
3788
|
+
columns_json: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
3789
|
+
cron_expression: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
3790
|
+
alert_on_drift: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
3791
|
+
notification_channel_ids_json: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
3740
3792
|
last_run_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
3741
|
-
|
|
3793
|
+
total_runs: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
3794
|
+
drift_detected_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
3795
|
+
consecutive_drift_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
3742
3796
|
config: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
|
3743
3797
|
|
|
3744
3798
|
# Relationships
|
|
@@ -3776,6 +3830,13 @@ class DriftMonitor(Base, UUIDMixin, TimestampMixin):
|
|
|
3776
3830
|
"""Get the most recent run."""
|
|
3777
3831
|
return self.runs[0] if self.runs else None
|
|
3778
3832
|
|
|
3833
|
+
@property
|
|
3834
|
+
def last_drift_detected(self) -> bool | None:
|
|
3835
|
+
"""Check if drift was detected in the most recent run."""
|
|
3836
|
+
if self.latest_run is None:
|
|
3837
|
+
return None
|
|
3838
|
+
return self.latest_run.has_drift
|
|
3839
|
+
|
|
3779
3840
|
def pause(self) -> None:
|
|
3780
3841
|
"""Pause this monitor."""
|
|
3781
3842
|
self.status = DriftMonitorStatus.PAUSED.value
|
|
@@ -3784,10 +3845,10 @@ class DriftMonitor(Base, UUIDMixin, TimestampMixin):
|
|
|
3784
3845
|
"""Resume this monitor."""
|
|
3785
3846
|
self.status = DriftMonitorStatus.ACTIVE.value
|
|
3786
3847
|
|
|
3787
|
-
def mark_run(self
|
|
3788
|
-
"""Mark as run and update
|
|
3848
|
+
def mark_run(self) -> None:
|
|
3849
|
+
"""Mark as run and update counters."""
|
|
3789
3850
|
self.last_run_at = datetime.utcnow()
|
|
3790
|
-
self.
|
|
3851
|
+
self.total_runs += 1
|
|
3791
3852
|
|
|
3792
3853
|
|
|
3793
3854
|
class DriftMonitorRun(Base, UUIDMixin):
|
|
@@ -3949,12 +4010,16 @@ class DriftAlert(Base, UUIDMixin):
|
|
|
3949
4010
|
drift_score: Mapped[float] = mapped_column(Float, nullable=False)
|
|
3950
4011
|
affected_columns: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
3951
4012
|
message: Mapped[str] = mapped_column(Text, nullable=False)
|
|
4013
|
+
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
3952
4014
|
acknowledged_by: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
3953
4015
|
acknowledged_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
3954
4016
|
resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
3955
4017
|
created_at: Mapped[datetime] = mapped_column(
|
|
3956
4018
|
DateTime, default=datetime.utcnow, nullable=False
|
|
3957
4019
|
)
|
|
4020
|
+
updated_at: Mapped[datetime | None] = mapped_column(
|
|
4021
|
+
DateTime, onupdate=datetime.utcnow, nullable=True
|
|
4022
|
+
)
|
|
3958
4023
|
|
|
3959
4024
|
# Relationships
|
|
3960
4025
|
monitor: Mapped[DriftMonitor] = relationship(
|
|
@@ -4327,12 +4392,8 @@ class ReportFormatType(str, Enum):
|
|
|
4327
4392
|
"""Report output format types."""
|
|
4328
4393
|
|
|
4329
4394
|
HTML = "html"
|
|
4330
|
-
PDF = "pdf"
|
|
4331
4395
|
CSV = "csv"
|
|
4332
4396
|
JSON = "json"
|
|
4333
|
-
MARKDOWN = "markdown"
|
|
4334
|
-
JUNIT = "junit"
|
|
4335
|
-
EXCEL = "excel"
|
|
4336
4397
|
CUSTOM = "custom"
|
|
4337
4398
|
|
|
4338
4399
|
|
|
@@ -4359,8 +4420,8 @@ class GeneratedReport(Base, UUIDMixin, TimestampMixin):
|
|
|
4359
4420
|
reporter_id: Optional reference to custom reporter used.
|
|
4360
4421
|
name: Human-readable report name.
|
|
4361
4422
|
description: Optional description.
|
|
4362
|
-
format: Report output format (html,
|
|
4363
|
-
theme: Theme used for HTML
|
|
4423
|
+
format: Report output format (html, csv, etc.).
|
|
4424
|
+
theme: Theme used for HTML reports.
|
|
4364
4425
|
locale: Language locale used.
|
|
4365
4426
|
status: Generation status.
|
|
4366
4427
|
file_path: Path to stored report file (if persisted).
|
|
@@ -5044,3 +5105,861 @@ class PluginHook(Base, UUIDMixin, TimestampMixin):
|
|
|
5044
5105
|
self.total_execution_ms += execution_ms
|
|
5045
5106
|
if error:
|
|
5046
5107
|
self.last_error = error
|
|
5108
|
+
|
|
5109
|
+
|
|
5110
|
+
# =============================================================================
|
|
5111
|
+
# Storage Tiering Models (truthound 1.2.10+)
|
|
5112
|
+
# =============================================================================
|
|
5113
|
+
|
|
5114
|
+
|
|
5115
|
+
class TierType(str, Enum):
|
|
5116
|
+
"""Storage tier types."""
|
|
5117
|
+
|
|
5118
|
+
HOT = "hot"
|
|
5119
|
+
WARM = "warm"
|
|
5120
|
+
COLD = "cold"
|
|
5121
|
+
ARCHIVE = "archive"
|
|
5122
|
+
|
|
5123
|
+
|
|
5124
|
+
class MigrationDirection(str, Enum):
|
|
5125
|
+
"""Migration direction for tier policies."""
|
|
5126
|
+
|
|
5127
|
+
DEMOTE = "demote"
|
|
5128
|
+
PROMOTE = "promote"
|
|
5129
|
+
|
|
5130
|
+
|
|
5131
|
+
class TierPolicyType(str, Enum):
|
|
5132
|
+
"""Types of tier policies."""
|
|
5133
|
+
|
|
5134
|
+
AGE_BASED = "age_based"
|
|
5135
|
+
ACCESS_BASED = "access_based"
|
|
5136
|
+
SIZE_BASED = "size_based"
|
|
5137
|
+
SCHEDULED = "scheduled"
|
|
5138
|
+
COMPOSITE = "composite"
|
|
5139
|
+
CUSTOM = "custom"
|
|
5140
|
+
|
|
5141
|
+
|
|
5142
|
+
class StorageTierModel(Base, UUIDMixin, TimestampMixin):
|
|
5143
|
+
"""Storage tier definition model.
|
|
5144
|
+
|
|
5145
|
+
Represents a storage tier with its backend configuration.
|
|
5146
|
+
|
|
5147
|
+
Attributes:
|
|
5148
|
+
id: Unique identifier (UUID).
|
|
5149
|
+
name: Unique tier name (hot, warm, cold, archive, or custom).
|
|
5150
|
+
tier_type: Type classification (HOT, WARM, COLD, ARCHIVE).
|
|
5151
|
+
store_type: Backend store type (filesystem, s3, gcs, etc.).
|
|
5152
|
+
store_config: JSON configuration for the store backend.
|
|
5153
|
+
priority: Read order priority (lower = higher priority).
|
|
5154
|
+
cost_per_gb: Cost per GB for cost analysis.
|
|
5155
|
+
retrieval_time_ms: Expected retrieval latency in milliseconds.
|
|
5156
|
+
tier_metadata: Additional tier metadata.
|
|
5157
|
+
is_active: Whether the tier is active.
|
|
5158
|
+
"""
|
|
5159
|
+
|
|
5160
|
+
__tablename__ = "storage_tiers"
|
|
5161
|
+
|
|
5162
|
+
__table_args__ = (
|
|
5163
|
+
Index("idx_storage_tiers_name", "name", unique=True),
|
|
5164
|
+
Index("idx_storage_tiers_type", "tier_type"),
|
|
5165
|
+
Index("idx_storage_tiers_priority", "priority"),
|
|
5166
|
+
)
|
|
5167
|
+
|
|
5168
|
+
name: Mapped[str] = mapped_column(String(50), nullable=False, unique=True)
|
|
5169
|
+
tier_type: Mapped[str] = mapped_column(
|
|
5170
|
+
String(20),
|
|
5171
|
+
nullable=False,
|
|
5172
|
+
default=TierType.HOT.value,
|
|
5173
|
+
)
|
|
5174
|
+
store_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
5175
|
+
store_config: Mapped[dict[str, Any]] = mapped_column(
|
|
5176
|
+
JSON, nullable=False, default=dict
|
|
5177
|
+
)
|
|
5178
|
+
priority: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
|
5179
|
+
cost_per_gb: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
5180
|
+
retrieval_time_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
5181
|
+
tier_metadata: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
|
5182
|
+
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
5183
|
+
|
|
5184
|
+
# Relationships
|
|
5185
|
+
policies_from: Mapped[list["TierPolicyModel"]] = relationship(
|
|
5186
|
+
"TierPolicyModel",
|
|
5187
|
+
foreign_keys="TierPolicyModel.from_tier_id",
|
|
5188
|
+
back_populates="from_tier",
|
|
5189
|
+
lazy="selectin",
|
|
5190
|
+
)
|
|
5191
|
+
policies_to: Mapped[list["TierPolicyModel"]] = relationship(
|
|
5192
|
+
"TierPolicyModel",
|
|
5193
|
+
foreign_keys="TierPolicyModel.to_tier_id",
|
|
5194
|
+
back_populates="to_tier",
|
|
5195
|
+
lazy="selectin",
|
|
5196
|
+
)
|
|
5197
|
+
|
|
5198
|
+
|
|
5199
|
+
class TierPolicyModel(Base, UUIDMixin, TimestampMixin):
|
|
5200
|
+
"""Tier migration policy model.
|
|
5201
|
+
|
|
5202
|
+
Stores tier migration policy configuration including composite policies.
|
|
5203
|
+
Supports AgeBasedTierPolicy, AccessBasedTierPolicy, SizeBasedTierPolicy,
|
|
5204
|
+
ScheduledTierPolicy, CompositeTierPolicy, and CustomTierPolicy.
|
|
5205
|
+
|
|
5206
|
+
Attributes:
|
|
5207
|
+
id: Unique identifier (UUID).
|
|
5208
|
+
name: Policy name.
|
|
5209
|
+
description: Policy description.
|
|
5210
|
+
policy_type: Type of policy (age_based, access_based, etc.).
|
|
5211
|
+
from_tier_id: Source tier ID.
|
|
5212
|
+
to_tier_id: Destination tier ID.
|
|
5213
|
+
direction: Migration direction (demote/promote).
|
|
5214
|
+
config: JSON configuration specific to policy type.
|
|
5215
|
+
is_active: Whether policy is active.
|
|
5216
|
+
priority: Execution priority (lower = runs first).
|
|
5217
|
+
parent_id: Parent composite policy ID (for nested policies).
|
|
5218
|
+
"""
|
|
5219
|
+
|
|
5220
|
+
__tablename__ = "tier_policies"
|
|
5221
|
+
|
|
5222
|
+
__table_args__ = (
|
|
5223
|
+
Index("idx_tier_policies_name", "name"),
|
|
5224
|
+
Index("idx_tier_policies_type", "policy_type"),
|
|
5225
|
+
Index("idx_tier_policies_from_tier", "from_tier_id"),
|
|
5226
|
+
Index("idx_tier_policies_to_tier", "to_tier_id"),
|
|
5227
|
+
Index("idx_tier_policies_parent", "parent_id"),
|
|
5228
|
+
Index("idx_tier_policies_active", "is_active"),
|
|
5229
|
+
)
|
|
5230
|
+
|
|
5231
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
5232
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5233
|
+
policy_type: Mapped[str] = mapped_column(
|
|
5234
|
+
String(30),
|
|
5235
|
+
nullable=False,
|
|
5236
|
+
default=TierPolicyType.AGE_BASED.value,
|
|
5237
|
+
)
|
|
5238
|
+
from_tier_id: Mapped[str] = mapped_column(
|
|
5239
|
+
String(36),
|
|
5240
|
+
ForeignKey("storage_tiers.id", ondelete="CASCADE"),
|
|
5241
|
+
nullable=False,
|
|
5242
|
+
)
|
|
5243
|
+
to_tier_id: Mapped[str] = mapped_column(
|
|
5244
|
+
String(36),
|
|
5245
|
+
ForeignKey("storage_tiers.id", ondelete="CASCADE"),
|
|
5246
|
+
nullable=False,
|
|
5247
|
+
)
|
|
5248
|
+
direction: Mapped[str] = mapped_column(
|
|
5249
|
+
String(20),
|
|
5250
|
+
nullable=False,
|
|
5251
|
+
default=MigrationDirection.DEMOTE.value,
|
|
5252
|
+
)
|
|
5253
|
+
config: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
|
5254
|
+
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
5255
|
+
priority: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
5256
|
+
parent_id: Mapped[str | None] = mapped_column(
|
|
5257
|
+
String(36),
|
|
5258
|
+
ForeignKey("tier_policies.id", ondelete="CASCADE"),
|
|
5259
|
+
nullable=True,
|
|
5260
|
+
)
|
|
5261
|
+
|
|
5262
|
+
# Relationships
|
|
5263
|
+
from_tier: Mapped["StorageTierModel"] = relationship(
|
|
5264
|
+
"StorageTierModel",
|
|
5265
|
+
foreign_keys=[from_tier_id],
|
|
5266
|
+
back_populates="policies_from",
|
|
5267
|
+
lazy="selectin",
|
|
5268
|
+
)
|
|
5269
|
+
to_tier: Mapped["StorageTierModel"] = relationship(
|
|
5270
|
+
"StorageTierModel",
|
|
5271
|
+
foreign_keys=[to_tier_id],
|
|
5272
|
+
back_populates="policies_to",
|
|
5273
|
+
lazy="selectin",
|
|
5274
|
+
)
|
|
5275
|
+
parent: Mapped["TierPolicyModel | None"] = relationship(
|
|
5276
|
+
"TierPolicyModel",
|
|
5277
|
+
remote_side="TierPolicyModel.id",
|
|
5278
|
+
back_populates="children",
|
|
5279
|
+
foreign_keys=[parent_id],
|
|
5280
|
+
)
|
|
5281
|
+
children: Mapped[list["TierPolicyModel"]] = relationship(
|
|
5282
|
+
"TierPolicyModel",
|
|
5283
|
+
back_populates="parent",
|
|
5284
|
+
cascade="all, delete-orphan",
|
|
5285
|
+
lazy="selectin",
|
|
5286
|
+
)
|
|
5287
|
+
|
|
5288
|
+
@property
|
|
5289
|
+
def is_composite(self) -> bool:
|
|
5290
|
+
"""Check if this is a composite policy."""
|
|
5291
|
+
return self.policy_type == TierPolicyType.COMPOSITE.value
|
|
5292
|
+
|
|
5293
|
+
@property
|
|
5294
|
+
def child_count(self) -> int:
|
|
5295
|
+
"""Get number of child policies."""
|
|
5296
|
+
return len(self.children) if self.children else 0
|
|
5297
|
+
|
|
5298
|
+
|
|
5299
|
+
class TieringConfigModel(Base, UUIDMixin, TimestampMixin):
|
|
5300
|
+
"""Tiering configuration model.
|
|
5301
|
+
|
|
5302
|
+
Stores the main tiering configuration including default tier,
|
|
5303
|
+
promotion settings, and batch processing options.
|
|
5304
|
+
|
|
5305
|
+
Attributes:
|
|
5306
|
+
id: Unique identifier (UUID).
|
|
5307
|
+
name: Configuration name.
|
|
5308
|
+
default_tier_id: Default tier for new items.
|
|
5309
|
+
enable_promotion: Whether to auto-promote on frequent access.
|
|
5310
|
+
promotion_threshold: Access count to trigger promotion.
|
|
5311
|
+
check_interval_hours: Hours between auto-checks.
|
|
5312
|
+
batch_size: Items per migration batch.
|
|
5313
|
+
enable_parallel_migration: Whether to enable parallel migration.
|
|
5314
|
+
max_parallel_migrations: Maximum concurrent migrations.
|
|
5315
|
+
is_active: Whether configuration is active.
|
|
5316
|
+
"""
|
|
5317
|
+
|
|
5318
|
+
__tablename__ = "tiering_configs"
|
|
5319
|
+
|
|
5320
|
+
__table_args__ = (
|
|
5321
|
+
Index("idx_tiering_configs_name", "name", unique=True),
|
|
5322
|
+
Index("idx_tiering_configs_active", "is_active"),
|
|
5323
|
+
)
|
|
5324
|
+
|
|
5325
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
|
5326
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5327
|
+
default_tier_id: Mapped[str | None] = mapped_column(
|
|
5328
|
+
String(36),
|
|
5329
|
+
ForeignKey("storage_tiers.id", ondelete="SET NULL"),
|
|
5330
|
+
nullable=True,
|
|
5331
|
+
)
|
|
5332
|
+
enable_promotion: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
5333
|
+
promotion_threshold: Mapped[int] = mapped_column(Integer, nullable=False, default=10)
|
|
5334
|
+
check_interval_hours: Mapped[int] = mapped_column(Integer, nullable=False, default=24)
|
|
5335
|
+
batch_size: Mapped[int] = mapped_column(Integer, nullable=False, default=100)
|
|
5336
|
+
enable_parallel_migration: Mapped[bool] = mapped_column(
|
|
5337
|
+
Boolean, nullable=False, default=False
|
|
5338
|
+
)
|
|
5339
|
+
max_parallel_migrations: Mapped[int] = mapped_column(
|
|
5340
|
+
Integer, nullable=False, default=4
|
|
5341
|
+
)
|
|
5342
|
+
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
5343
|
+
|
|
5344
|
+
# Relationships
|
|
5345
|
+
default_tier: Mapped["StorageTierModel | None"] = relationship(
|
|
5346
|
+
"StorageTierModel",
|
|
5347
|
+
foreign_keys=[default_tier_id],
|
|
5348
|
+
lazy="selectin",
|
|
5349
|
+
)
|
|
5350
|
+
|
|
5351
|
+
|
|
5352
|
+
class TierMigrationHistoryModel(Base, UUIDMixin):
|
|
5353
|
+
"""Tier migration history model.
|
|
5354
|
+
|
|
5355
|
+
Tracks migration operations for auditing and analysis.
|
|
5356
|
+
|
|
5357
|
+
Attributes:
|
|
5358
|
+
id: Unique identifier (UUID).
|
|
5359
|
+
policy_id: Reference to the policy that triggered migration.
|
|
5360
|
+
item_id: ID of the migrated item.
|
|
5361
|
+
from_tier_id: Source tier ID.
|
|
5362
|
+
to_tier_id: Destination tier ID.
|
|
5363
|
+
size_bytes: Size of migrated item.
|
|
5364
|
+
started_at: When migration started.
|
|
5365
|
+
completed_at: When migration completed.
|
|
5366
|
+
status: Migration status (pending, in_progress, completed, failed).
|
|
5367
|
+
error_message: Error message if failed.
|
|
5368
|
+
"""
|
|
5369
|
+
|
|
5370
|
+
__tablename__ = "tier_migration_history"
|
|
5371
|
+
|
|
5372
|
+
__table_args__ = (
|
|
5373
|
+
Index("idx_tier_migration_policy", "policy_id"),
|
|
5374
|
+
Index("idx_tier_migration_item", "item_id"),
|
|
5375
|
+
Index("idx_tier_migration_status", "status"),
|
|
5376
|
+
Index("idx_tier_migration_started", "started_at"),
|
|
5377
|
+
)
|
|
5378
|
+
|
|
5379
|
+
policy_id: Mapped[str | None] = mapped_column(
|
|
5380
|
+
String(36),
|
|
5381
|
+
ForeignKey("tier_policies.id", ondelete="SET NULL"),
|
|
5382
|
+
nullable=True,
|
|
5383
|
+
)
|
|
5384
|
+
item_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
5385
|
+
from_tier_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
5386
|
+
to_tier_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
5387
|
+
size_bytes: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
5388
|
+
started_at: Mapped[datetime] = mapped_column(
|
|
5389
|
+
DateTime, nullable=False, default=datetime.utcnow
|
|
5390
|
+
)
|
|
5391
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5392
|
+
status: Mapped[str] = mapped_column(
|
|
5393
|
+
String(20), nullable=False, default="pending"
|
|
5394
|
+
)
|
|
5395
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5396
|
+
|
|
5397
|
+
@property
|
|
5398
|
+
def duration_ms(self) -> float | None:
|
|
5399
|
+
"""Calculate migration duration in milliseconds."""
|
|
5400
|
+
if self.started_at and self.completed_at:
|
|
5401
|
+
return (self.completed_at - self.started_at).total_seconds() * 1000
|
|
5402
|
+
return None
|
|
5403
|
+
|
|
5404
|
+
|
|
5405
|
+
# =============================================================================
|
|
5406
|
+
# Schema Watcher Models (truthound 1.2.10+)
|
|
5407
|
+
# =============================================================================
|
|
5408
|
+
|
|
5409
|
+
|
|
5410
|
+
class SchemaWatcherStatus(str, Enum):
|
|
5411
|
+
"""Status of a schema watcher."""
|
|
5412
|
+
|
|
5413
|
+
ACTIVE = "active"
|
|
5414
|
+
PAUSED = "paused"
|
|
5415
|
+
STOPPED = "stopped"
|
|
5416
|
+
ERROR = "error"
|
|
5417
|
+
|
|
5418
|
+
|
|
5419
|
+
class SchemaWatcherAlertStatus(str, Enum):
|
|
5420
|
+
"""Status of a schema watcher alert."""
|
|
5421
|
+
|
|
5422
|
+
OPEN = "open"
|
|
5423
|
+
ACKNOWLEDGED = "acknowledged"
|
|
5424
|
+
RESOLVED = "resolved"
|
|
5425
|
+
SUPPRESSED = "suppressed"
|
|
5426
|
+
|
|
5427
|
+
|
|
5428
|
+
class SchemaWatcherAlertSeverity(str, Enum):
|
|
5429
|
+
"""Severity of schema watcher alert."""
|
|
5430
|
+
|
|
5431
|
+
CRITICAL = "critical"
|
|
5432
|
+
HIGH = "high"
|
|
5433
|
+
MEDIUM = "medium"
|
|
5434
|
+
LOW = "low"
|
|
5435
|
+
INFO = "info"
|
|
5436
|
+
|
|
5437
|
+
|
|
5438
|
+
class SchemaWatcherRunStatus(str, Enum):
|
|
5439
|
+
"""Status of a schema watcher run."""
|
|
5440
|
+
|
|
5441
|
+
PENDING = "pending"
|
|
5442
|
+
RUNNING = "running"
|
|
5443
|
+
COMPLETED = "completed"
|
|
5444
|
+
FAILED = "failed"
|
|
5445
|
+
|
|
5446
|
+
|
|
5447
|
+
class SchemaWatcherModel(Base, UUIDMixin, TimestampMixin):
|
|
5448
|
+
"""Schema Watcher for continuous schema monitoring.
|
|
5449
|
+
|
|
5450
|
+
Implements truthound's SchemaWatcher functionality for continuous
|
|
5451
|
+
monitoring of schema changes with configurable polling intervals,
|
|
5452
|
+
alert thresholds, and notification integration.
|
|
5453
|
+
|
|
5454
|
+
Attributes:
|
|
5455
|
+
id: Unique identifier (UUID).
|
|
5456
|
+
name: Human-readable name for the watcher.
|
|
5457
|
+
source_id: Reference to the Source being watched.
|
|
5458
|
+
status: Current watcher status (active, paused, stopped, error).
|
|
5459
|
+
poll_interval_seconds: How often to check for schema changes.
|
|
5460
|
+
only_breaking: Only alert on breaking changes.
|
|
5461
|
+
enable_rename_detection: Enable column rename detection.
|
|
5462
|
+
rename_similarity_threshold: Threshold for rename detection (0.0-1.0).
|
|
5463
|
+
version_strategy: Version numbering strategy (semantic, incremental, timestamp, git).
|
|
5464
|
+
notify_on_change: Send notifications when changes detected.
|
|
5465
|
+
track_history: Track changes in schema history.
|
|
5466
|
+
last_check_at: When the watcher last checked for changes.
|
|
5467
|
+
last_change_at: When the last change was detected.
|
|
5468
|
+
next_check_at: When the next check is scheduled.
|
|
5469
|
+
check_count: Total number of checks performed.
|
|
5470
|
+
change_count: Total number of changes detected.
|
|
5471
|
+
error_count: Number of consecutive errors.
|
|
5472
|
+
last_error: Last error message if any.
|
|
5473
|
+
config: Additional configuration as JSON.
|
|
5474
|
+
"""
|
|
5475
|
+
|
|
5476
|
+
__tablename__ = "schema_watchers"
|
|
5477
|
+
|
|
5478
|
+
__table_args__ = (
|
|
5479
|
+
Index("idx_schema_watcher_source", "source_id"),
|
|
5480
|
+
Index("idx_schema_watcher_status", "status"),
|
|
5481
|
+
Index("idx_schema_watcher_next_check", "next_check_at"),
|
|
5482
|
+
)
|
|
5483
|
+
|
|
5484
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
5485
|
+
source_id: Mapped[str] = mapped_column(
|
|
5486
|
+
String(36),
|
|
5487
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5488
|
+
nullable=False,
|
|
5489
|
+
index=True,
|
|
5490
|
+
)
|
|
5491
|
+
status: Mapped[str] = mapped_column(
|
|
5492
|
+
String(20),
|
|
5493
|
+
nullable=False,
|
|
5494
|
+
default=SchemaWatcherStatus.ACTIVE.value,
|
|
5495
|
+
)
|
|
5496
|
+
poll_interval_seconds: Mapped[int] = mapped_column(
|
|
5497
|
+
Integer,
|
|
5498
|
+
nullable=False,
|
|
5499
|
+
default=60, # Default 60 seconds
|
|
5500
|
+
)
|
|
5501
|
+
only_breaking: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
5502
|
+
enable_rename_detection: Mapped[bool] = mapped_column(
|
|
5503
|
+
Boolean, default=True, nullable=False
|
|
5504
|
+
)
|
|
5505
|
+
rename_similarity_threshold: Mapped[float] = mapped_column(
|
|
5506
|
+
Float, default=0.8, nullable=False
|
|
5507
|
+
)
|
|
5508
|
+
version_strategy: Mapped[str] = mapped_column(
|
|
5509
|
+
String(20),
|
|
5510
|
+
default="semantic",
|
|
5511
|
+
nullable=False,
|
|
5512
|
+
)
|
|
5513
|
+
notify_on_change: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
5514
|
+
track_history: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
5515
|
+
|
|
5516
|
+
# Monitoring state
|
|
5517
|
+
last_check_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5518
|
+
last_change_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5519
|
+
next_check_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5520
|
+
check_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5521
|
+
change_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5522
|
+
error_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5523
|
+
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5524
|
+
|
|
5525
|
+
# Additional configuration
|
|
5526
|
+
watcher_config: Mapped[dict[str, Any] | None] = mapped_column(
|
|
5527
|
+
"config", JSON, nullable=True
|
|
5528
|
+
)
|
|
5529
|
+
|
|
5530
|
+
# Relationships
|
|
5531
|
+
source: Mapped[Source] = relationship("Source", lazy="selectin")
|
|
5532
|
+
alerts: Mapped[list[SchemaWatcherAlertModel]] = relationship(
|
|
5533
|
+
"SchemaWatcherAlertModel",
|
|
5534
|
+
back_populates="watcher",
|
|
5535
|
+
cascade="all, delete-orphan",
|
|
5536
|
+
lazy="selectin",
|
|
5537
|
+
order_by="desc(SchemaWatcherAlertModel.created_at)",
|
|
5538
|
+
)
|
|
5539
|
+
runs: Mapped[list[SchemaWatcherRunModel]] = relationship(
|
|
5540
|
+
"SchemaWatcherRunModel",
|
|
5541
|
+
back_populates="watcher",
|
|
5542
|
+
cascade="all, delete-orphan",
|
|
5543
|
+
lazy="selectin",
|
|
5544
|
+
order_by="desc(SchemaWatcherRunModel.started_at)",
|
|
5545
|
+
)
|
|
5546
|
+
|
|
5547
|
+
@property
|
|
5548
|
+
def is_active(self) -> bool:
|
|
5549
|
+
"""Check if watcher is actively running."""
|
|
5550
|
+
return self.status == SchemaWatcherStatus.ACTIVE.value
|
|
5551
|
+
|
|
5552
|
+
@property
|
|
5553
|
+
def is_healthy(self) -> bool:
|
|
5554
|
+
"""Check if watcher is healthy (no errors)."""
|
|
5555
|
+
return self.error_count == 0 and self.status != SchemaWatcherStatus.ERROR.value
|
|
5556
|
+
|
|
5557
|
+
@property
|
|
5558
|
+
def detection_rate(self) -> float:
|
|
5559
|
+
"""Calculate change detection rate."""
|
|
5560
|
+
if self.check_count == 0:
|
|
5561
|
+
return 0.0
|
|
5562
|
+
return self.change_count / self.check_count
|
|
5563
|
+
|
|
5564
|
+
|
|
5565
|
+
class SchemaWatcherAlertModel(Base, UUIDMixin, TimestampMixin):
|
|
5566
|
+
"""Schema Watcher Alert for tracking schema change notifications.
|
|
5567
|
+
|
|
5568
|
+
Records alerts generated by the SchemaWatcher when schema changes
|
|
5569
|
+
are detected, with support for acknowledgment and resolution.
|
|
5570
|
+
|
|
5571
|
+
Attributes:
|
|
5572
|
+
id: Unique identifier (UUID).
|
|
5573
|
+
watcher_id: Reference to the SchemaWatcher.
|
|
5574
|
+
source_id: Reference to the Source.
|
|
5575
|
+
from_version_id: Previous schema version ID.
|
|
5576
|
+
to_version_id: New schema version ID.
|
|
5577
|
+
title: Alert title.
|
|
5578
|
+
severity: Alert severity level.
|
|
5579
|
+
status: Alert status (open, acknowledged, resolved, suppressed).
|
|
5580
|
+
total_changes: Total number of changes in this alert.
|
|
5581
|
+
breaking_changes: Number of breaking changes.
|
|
5582
|
+
changes_summary: JSON summary of changes.
|
|
5583
|
+
impact_scope: Scope of impact (local, downstream, system).
|
|
5584
|
+
affected_consumers: List of affected downstream consumers.
|
|
5585
|
+
recommendations: List of recommended actions.
|
|
5586
|
+
acknowledged_at: When alert was acknowledged.
|
|
5587
|
+
acknowledged_by: Who acknowledged the alert.
|
|
5588
|
+
resolved_at: When alert was resolved.
|
|
5589
|
+
resolved_by: Who resolved the alert.
|
|
5590
|
+
resolution_notes: Notes about resolution.
|
|
5591
|
+
"""
|
|
5592
|
+
|
|
5593
|
+
__tablename__ = "schema_watcher_alerts"
|
|
5594
|
+
|
|
5595
|
+
__table_args__ = (
|
|
5596
|
+
Index("idx_watcher_alert_watcher", "watcher_id"),
|
|
5597
|
+
Index("idx_watcher_alert_source", "source_id"),
|
|
5598
|
+
Index("idx_watcher_alert_status", "status"),
|
|
5599
|
+
Index("idx_watcher_alert_severity", "severity"),
|
|
5600
|
+
Index("idx_watcher_alert_created", "created_at"),
|
|
5601
|
+
)
|
|
5602
|
+
|
|
5603
|
+
watcher_id: Mapped[str] = mapped_column(
|
|
5604
|
+
String(36),
|
|
5605
|
+
ForeignKey("schema_watchers.id", ondelete="CASCADE"),
|
|
5606
|
+
nullable=False,
|
|
5607
|
+
index=True,
|
|
5608
|
+
)
|
|
5609
|
+
source_id: Mapped[str] = mapped_column(
|
|
5610
|
+
String(36),
|
|
5611
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5612
|
+
nullable=False,
|
|
5613
|
+
index=True,
|
|
5614
|
+
)
|
|
5615
|
+
from_version_id: Mapped[str | None] = mapped_column(
|
|
5616
|
+
String(36),
|
|
5617
|
+
ForeignKey("schema_versions.id", ondelete="SET NULL"),
|
|
5618
|
+
nullable=True,
|
|
5619
|
+
)
|
|
5620
|
+
to_version_id: Mapped[str] = mapped_column(
|
|
5621
|
+
String(36),
|
|
5622
|
+
ForeignKey("schema_versions.id", ondelete="CASCADE"),
|
|
5623
|
+
nullable=False,
|
|
5624
|
+
)
|
|
5625
|
+
title: Mapped[str] = mapped_column(String(500), nullable=False)
|
|
5626
|
+
severity: Mapped[str] = mapped_column(
|
|
5627
|
+
String(20),
|
|
5628
|
+
nullable=False,
|
|
5629
|
+
default=SchemaWatcherAlertSeverity.MEDIUM.value,
|
|
5630
|
+
)
|
|
5631
|
+
status: Mapped[str] = mapped_column(
|
|
5632
|
+
String(20),
|
|
5633
|
+
nullable=False,
|
|
5634
|
+
default=SchemaWatcherAlertStatus.OPEN.value,
|
|
5635
|
+
)
|
|
5636
|
+
total_changes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5637
|
+
breaking_changes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5638
|
+
changes_summary: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
|
5639
|
+
impact_scope: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
|
5640
|
+
affected_consumers: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
5641
|
+
recommendations: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
5642
|
+
|
|
5643
|
+
# Acknowledgment tracking
|
|
5644
|
+
acknowledged_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5645
|
+
acknowledged_by: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
5646
|
+
|
|
5647
|
+
# Resolution tracking
|
|
5648
|
+
resolved_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5649
|
+
resolved_by: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
5650
|
+
resolution_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5651
|
+
|
|
5652
|
+
# Relationships
|
|
5653
|
+
watcher: Mapped[SchemaWatcherModel] = relationship(
|
|
5654
|
+
"SchemaWatcherModel",
|
|
5655
|
+
back_populates="alerts",
|
|
5656
|
+
lazy="selectin",
|
|
5657
|
+
)
|
|
5658
|
+
source: Mapped[Source] = relationship("Source", lazy="selectin")
|
|
5659
|
+
from_version: Mapped[SchemaVersion | None] = relationship(
|
|
5660
|
+
"SchemaVersion",
|
|
5661
|
+
foreign_keys=[from_version_id],
|
|
5662
|
+
lazy="selectin",
|
|
5663
|
+
)
|
|
5664
|
+
to_version: Mapped[SchemaVersion] = relationship(
|
|
5665
|
+
"SchemaVersion",
|
|
5666
|
+
foreign_keys=[to_version_id],
|
|
5667
|
+
lazy="selectin",
|
|
5668
|
+
)
|
|
5669
|
+
|
|
5670
|
+
@property
|
|
5671
|
+
def is_open(self) -> bool:
|
|
5672
|
+
"""Check if alert is still open."""
|
|
5673
|
+
return self.status == SchemaWatcherAlertStatus.OPEN.value
|
|
5674
|
+
|
|
5675
|
+
@property
|
|
5676
|
+
def is_resolved(self) -> bool:
|
|
5677
|
+
"""Check if alert has been resolved."""
|
|
5678
|
+
return self.status == SchemaWatcherAlertStatus.RESOLVED.value
|
|
5679
|
+
|
|
5680
|
+
@property
|
|
5681
|
+
def has_breaking_changes(self) -> bool:
|
|
5682
|
+
"""Check if alert contains breaking changes."""
|
|
5683
|
+
return self.breaking_changes > 0
|
|
5684
|
+
|
|
5685
|
+
@property
|
|
5686
|
+
def time_to_acknowledge(self) -> float | None:
|
|
5687
|
+
"""Calculate time to acknowledge in seconds."""
|
|
5688
|
+
if self.created_at and self.acknowledged_at:
|
|
5689
|
+
return (self.acknowledged_at - self.created_at).total_seconds()
|
|
5690
|
+
return None
|
|
5691
|
+
|
|
5692
|
+
@property
|
|
5693
|
+
def time_to_resolve(self) -> float | None:
|
|
5694
|
+
"""Calculate time to resolve in seconds."""
|
|
5695
|
+
if self.created_at and self.resolved_at:
|
|
5696
|
+
return (self.resolved_at - self.created_at).total_seconds()
|
|
5697
|
+
return None
|
|
5698
|
+
|
|
5699
|
+
|
|
5700
|
+
class SchemaWatcherRunModel(Base, UUIDMixin):
|
|
5701
|
+
"""Schema Watcher Run history for tracking check executions.
|
|
5702
|
+
|
|
5703
|
+
Records each execution of the schema watcher check operation
|
|
5704
|
+
for auditing and performance analysis.
|
|
5705
|
+
|
|
5706
|
+
Attributes:
|
|
5707
|
+
id: Unique identifier (UUID).
|
|
5708
|
+
watcher_id: Reference to the SchemaWatcher.
|
|
5709
|
+
source_id: Reference to the Source.
|
|
5710
|
+
started_at: When the check started.
|
|
5711
|
+
completed_at: When the check completed.
|
|
5712
|
+
status: Run status (pending, running, completed, failed).
|
|
5713
|
+
changes_detected: Number of changes detected.
|
|
5714
|
+
breaking_detected: Number of breaking changes detected.
|
|
5715
|
+
version_created_id: ID of new schema version if created.
|
|
5716
|
+
alert_created_id: ID of alert if created.
|
|
5717
|
+
duration_ms: Duration of the check in milliseconds.
|
|
5718
|
+
error_message: Error message if failed.
|
|
5719
|
+
run_metadata: Additional metadata as JSON.
|
|
5720
|
+
"""
|
|
5721
|
+
|
|
5722
|
+
__tablename__ = "schema_watcher_runs"
|
|
5723
|
+
|
|
5724
|
+
__table_args__ = (
|
|
5725
|
+
Index("idx_watcher_run_watcher", "watcher_id"),
|
|
5726
|
+
Index("idx_watcher_run_source", "source_id"),
|
|
5727
|
+
Index("idx_watcher_run_status", "status"),
|
|
5728
|
+
Index("idx_watcher_run_started", "started_at"),
|
|
5729
|
+
)
|
|
5730
|
+
|
|
5731
|
+
watcher_id: Mapped[str] = mapped_column(
|
|
5732
|
+
String(36),
|
|
5733
|
+
ForeignKey("schema_watchers.id", ondelete="CASCADE"),
|
|
5734
|
+
nullable=False,
|
|
5735
|
+
index=True,
|
|
5736
|
+
)
|
|
5737
|
+
source_id: Mapped[str] = mapped_column(
|
|
5738
|
+
String(36),
|
|
5739
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5740
|
+
nullable=False,
|
|
5741
|
+
index=True,
|
|
5742
|
+
)
|
|
5743
|
+
started_at: Mapped[datetime] = mapped_column(
|
|
5744
|
+
DateTime, nullable=False, default=datetime.utcnow
|
|
5745
|
+
)
|
|
5746
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
5747
|
+
status: Mapped[str] = mapped_column(
|
|
5748
|
+
String(20), nullable=False, default="pending"
|
|
5749
|
+
)
|
|
5750
|
+
changes_detected: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5751
|
+
breaking_detected: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
5752
|
+
version_created_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
5753
|
+
alert_created_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
5754
|
+
duration_ms: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
5755
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5756
|
+
run_metadata: Mapped[dict[str, Any] | None] = mapped_column(
|
|
5757
|
+
"metadata", JSON, nullable=True
|
|
5758
|
+
)
|
|
5759
|
+
|
|
5760
|
+
# Relationships
|
|
5761
|
+
watcher: Mapped[SchemaWatcherModel] = relationship(
|
|
5762
|
+
"SchemaWatcherModel",
|
|
5763
|
+
back_populates="runs",
|
|
5764
|
+
lazy="selectin",
|
|
5765
|
+
)
|
|
5766
|
+
source: Mapped[Source] = relationship("Source", lazy="selectin")
|
|
5767
|
+
|
|
5768
|
+
@property
|
|
5769
|
+
def is_successful(self) -> bool:
|
|
5770
|
+
"""Check if run completed successfully."""
|
|
5771
|
+
return self.status == "completed" and not self.error_message
|
|
5772
|
+
|
|
5773
|
+
@property
|
|
5774
|
+
def has_changes(self) -> bool:
|
|
5775
|
+
"""Check if changes were detected in this run."""
|
|
5776
|
+
return self.changes_detected > 0
|
|
5777
|
+
|
|
5778
|
+
|
|
5779
|
+
# =============================================================================
|
|
5780
|
+
# Cross-Alert Models (Persistent Storage)
|
|
5781
|
+
# =============================================================================
|
|
5782
|
+
|
|
5783
|
+
|
|
5784
|
+
class CrossAlertConfig(Base, UUIDMixin, TimestampMixin):
|
|
5785
|
+
"""Cross-alert configuration model.
|
|
5786
|
+
|
|
5787
|
+
Stores configuration for cross-feature alert correlation between
|
|
5788
|
+
anomaly detection and drift monitoring. Replaces in-memory storage.
|
|
5789
|
+
|
|
5790
|
+
Attributes:
|
|
5791
|
+
id: Unique identifier (UUID).
|
|
5792
|
+
source_id: Optional source ID for source-specific config, None for global.
|
|
5793
|
+
enabled: Whether cross-alert is enabled.
|
|
5794
|
+
trigger_drift_on_anomaly: Auto-trigger drift check when anomaly spikes.
|
|
5795
|
+
trigger_anomaly_on_drift: Auto-trigger anomaly check when drift detected.
|
|
5796
|
+
thresholds: JSON dict with threshold values.
|
|
5797
|
+
notify_on_correlation: Send notification when correlation found.
|
|
5798
|
+
notification_channel_ids: Channel IDs for notifications.
|
|
5799
|
+
cooldown_seconds: Cooldown period between auto-triggers.
|
|
5800
|
+
last_anomaly_trigger_at: Last time anomaly triggered drift check.
|
|
5801
|
+
last_drift_trigger_at: Last time drift triggered anomaly check.
|
|
5802
|
+
"""
|
|
5803
|
+
|
|
5804
|
+
__tablename__ = "cross_alert_configs"
|
|
5805
|
+
|
|
5806
|
+
__table_args__ = (
|
|
5807
|
+
Index("idx_cross_alert_config_source", "source_id"),
|
|
5808
|
+
Index("idx_cross_alert_config_enabled", "enabled"),
|
|
5809
|
+
)
|
|
5810
|
+
|
|
5811
|
+
source_id: Mapped[str | None] = mapped_column(
|
|
5812
|
+
String(36),
|
|
5813
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5814
|
+
nullable=True,
|
|
5815
|
+
unique=True,
|
|
5816
|
+
index=True,
|
|
5817
|
+
)
|
|
5818
|
+
enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
5819
|
+
trigger_drift_on_anomaly: Mapped[bool] = mapped_column(
|
|
5820
|
+
Boolean, default=True, nullable=False
|
|
5821
|
+
)
|
|
5822
|
+
trigger_anomaly_on_drift: Mapped[bool] = mapped_column(
|
|
5823
|
+
Boolean, default=True, nullable=False
|
|
5824
|
+
)
|
|
5825
|
+
thresholds: Mapped[dict[str, Any]] = mapped_column(
|
|
5826
|
+
JSON,
|
|
5827
|
+
nullable=False,
|
|
5828
|
+
default=lambda: {
|
|
5829
|
+
"anomaly_rate_threshold": 0.1,
|
|
5830
|
+
"anomaly_count_threshold": 10,
|
|
5831
|
+
"drift_percentage_threshold": 10.0,
|
|
5832
|
+
"drift_columns_threshold": 2,
|
|
5833
|
+
},
|
|
5834
|
+
)
|
|
5835
|
+
notify_on_correlation: Mapped[bool] = mapped_column(
|
|
5836
|
+
Boolean, default=True, nullable=False
|
|
5837
|
+
)
|
|
5838
|
+
notification_channel_ids: Mapped[list[str] | None] = mapped_column(
|
|
5839
|
+
JSON, nullable=True
|
|
5840
|
+
)
|
|
5841
|
+
cooldown_seconds: Mapped[int] = mapped_column(Integer, default=300, nullable=False)
|
|
5842
|
+
last_anomaly_trigger_at: Mapped[datetime | None] = mapped_column(
|
|
5843
|
+
DateTime, nullable=True
|
|
5844
|
+
)
|
|
5845
|
+
last_drift_trigger_at: Mapped[datetime | None] = mapped_column(
|
|
5846
|
+
DateTime, nullable=True
|
|
5847
|
+
)
|
|
5848
|
+
|
|
5849
|
+
# Relationships
|
|
5850
|
+
source: Mapped[Source | None] = relationship("Source", lazy="selectin")
|
|
5851
|
+
|
|
5852
|
+
|
|
5853
|
+
class CrossAlertCorrelation(Base, UUIDMixin):
|
|
5854
|
+
"""Cross-alert correlation record model.
|
|
5855
|
+
|
|
5856
|
+
Stores detected correlations between anomaly and drift alerts.
|
|
5857
|
+
Replaces in-memory _correlations list.
|
|
5858
|
+
|
|
5859
|
+
Attributes:
|
|
5860
|
+
id: Unique identifier (UUID).
|
|
5861
|
+
source_id: Reference to the data source.
|
|
5862
|
+
correlation_strength: Strength level (strong, moderate, weak).
|
|
5863
|
+
confidence_score: Confidence score between 0 and 1.
|
|
5864
|
+
time_delta_seconds: Time difference between alerts.
|
|
5865
|
+
anomaly_alert_id: ID of the anomaly detection record.
|
|
5866
|
+
drift_alert_id: ID of the drift alert record.
|
|
5867
|
+
anomaly_data: JSON with anomaly alert details.
|
|
5868
|
+
drift_data: JSON with drift alert details.
|
|
5869
|
+
common_columns: List of columns affected by both.
|
|
5870
|
+
suggested_action: Suggested action for this correlation.
|
|
5871
|
+
notes: Optional notes.
|
|
5872
|
+
created_at: When the correlation was detected.
|
|
5873
|
+
"""
|
|
5874
|
+
|
|
5875
|
+
__tablename__ = "cross_alert_correlations"
|
|
5876
|
+
|
|
5877
|
+
__table_args__ = (
|
|
5878
|
+
Index("idx_cross_correlation_source", "source_id"),
|
|
5879
|
+
Index("idx_cross_correlation_strength", "correlation_strength"),
|
|
5880
|
+
Index("idx_cross_correlation_created", "created_at"),
|
|
5881
|
+
)
|
|
5882
|
+
|
|
5883
|
+
source_id: Mapped[str] = mapped_column(
|
|
5884
|
+
String(36),
|
|
5885
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5886
|
+
nullable=False,
|
|
5887
|
+
index=True,
|
|
5888
|
+
)
|
|
5889
|
+
correlation_strength: Mapped[str] = mapped_column(
|
|
5890
|
+
String(20), nullable=False
|
|
5891
|
+
) # strong, moderate, weak
|
|
5892
|
+
confidence_score: Mapped[float] = mapped_column(Float, nullable=False)
|
|
5893
|
+
time_delta_seconds: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
5894
|
+
anomaly_alert_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
5895
|
+
drift_alert_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
5896
|
+
anomaly_data: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
|
5897
|
+
drift_data: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
|
5898
|
+
common_columns: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
|
5899
|
+
suggested_action: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5900
|
+
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5901
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
5902
|
+
DateTime, default=datetime.utcnow, nullable=False
|
|
5903
|
+
)
|
|
5904
|
+
|
|
5905
|
+
# Relationships
|
|
5906
|
+
source: Mapped[Source] = relationship("Source", lazy="selectin")
|
|
5907
|
+
|
|
5908
|
+
|
|
5909
|
+
class CrossAlertTriggerEvent(Base, UUIDMixin):
|
|
5910
|
+
"""Cross-alert auto-trigger event record model.
|
|
5911
|
+
|
|
5912
|
+
Stores records of auto-triggered checks between anomaly and drift.
|
|
5913
|
+
Replaces in-memory _auto_trigger_events list.
|
|
5914
|
+
|
|
5915
|
+
Attributes:
|
|
5916
|
+
id: Unique identifier (UUID).
|
|
5917
|
+
source_id: Reference to the data source.
|
|
5918
|
+
trigger_type: Type of trigger (anomaly_to_drift, drift_to_anomaly).
|
|
5919
|
+
trigger_alert_id: ID of the alert that triggered this.
|
|
5920
|
+
trigger_alert_type: Type of triggering alert (anomaly, drift).
|
|
5921
|
+
result_id: ID of the resulting check (drift comparison or anomaly detection).
|
|
5922
|
+
correlation_found: Whether a correlation was found.
|
|
5923
|
+
correlation_id: ID of the correlation if found.
|
|
5924
|
+
status: Status (pending, running, completed, failed, skipped).
|
|
5925
|
+
error_message: Error message if failed.
|
|
5926
|
+
skipped_reason: Reason if skipped.
|
|
5927
|
+
created_at: When the event was created.
|
|
5928
|
+
"""
|
|
5929
|
+
|
|
5930
|
+
__tablename__ = "cross_alert_trigger_events"
|
|
5931
|
+
|
|
5932
|
+
__table_args__ = (
|
|
5933
|
+
Index("idx_cross_trigger_source", "source_id"),
|
|
5934
|
+
Index("idx_cross_trigger_type", "trigger_type"),
|
|
5935
|
+
Index("idx_cross_trigger_status", "status"),
|
|
5936
|
+
Index("idx_cross_trigger_created", "created_at"),
|
|
5937
|
+
)
|
|
5938
|
+
|
|
5939
|
+
source_id: Mapped[str] = mapped_column(
|
|
5940
|
+
String(36),
|
|
5941
|
+
ForeignKey("sources.id", ondelete="CASCADE"),
|
|
5942
|
+
nullable=False,
|
|
5943
|
+
index=True,
|
|
5944
|
+
)
|
|
5945
|
+
trigger_type: Mapped[str] = mapped_column(
|
|
5946
|
+
String(30), nullable=False
|
|
5947
|
+
) # anomaly_to_drift, drift_to_anomaly
|
|
5948
|
+
trigger_alert_id: Mapped[str] = mapped_column(String(36), nullable=False)
|
|
5949
|
+
trigger_alert_type: Mapped[str] = mapped_column(
|
|
5950
|
+
String(20), nullable=False
|
|
5951
|
+
) # anomaly, drift
|
|
5952
|
+
result_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
5953
|
+
correlation_found: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
5954
|
+
correlation_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
5955
|
+
status: Mapped[str] = mapped_column(
|
|
5956
|
+
String(20), nullable=False, default="pending"
|
|
5957
|
+
) # pending, running, completed, failed, skipped
|
|
5958
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5959
|
+
skipped_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
5960
|
+
created_at: Mapped[datetime] = mapped_column(
|
|
5961
|
+
DateTime, default=datetime.utcnow, nullable=False
|
|
5962
|
+
)
|
|
5963
|
+
|
|
5964
|
+
# Relationships
|
|
5965
|
+
source: Mapped[Source] = relationship("Source", lazy="selectin")
|