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
|
@@ -7,15 +7,19 @@ supporting sliding window detection and online learning.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
10
12
|
from collections import deque
|
|
11
13
|
from dataclasses import dataclass, field
|
|
12
|
-
from datetime import datetime
|
|
14
|
+
from datetime import datetime, timedelta
|
|
13
15
|
from enum import Enum
|
|
14
16
|
from typing import Any
|
|
15
17
|
from uuid import uuid4
|
|
16
18
|
|
|
17
19
|
import numpy as np
|
|
18
20
|
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
class StreamingSessionStatus(str, Enum):
|
|
21
25
|
"""Status of a streaming session."""
|
|
@@ -130,6 +134,7 @@ class StreamingSession:
|
|
|
130
134
|
created_at: datetime
|
|
131
135
|
started_at: datetime | None = None
|
|
132
136
|
stopped_at: datetime | None = None
|
|
137
|
+
last_active_at: datetime | None = None
|
|
133
138
|
config: dict[str, Any] = field(default_factory=dict)
|
|
134
139
|
|
|
135
140
|
# Runtime state (not persisted)
|
|
@@ -140,10 +145,16 @@ class StreamingSession:
|
|
|
140
145
|
_ema_values: dict[str, float] = field(default_factory=dict)
|
|
141
146
|
|
|
142
147
|
def __post_init__(self) -> None:
|
|
143
|
-
"""Initialize column statistics."""
|
|
148
|
+
"""Initialize column statistics and activity tracking."""
|
|
144
149
|
for col in self.columns:
|
|
145
150
|
self._column_stats[col] = StreamingStatistics()
|
|
146
151
|
self._ema_values[col] = 0.0
|
|
152
|
+
if self.last_active_at is None:
|
|
153
|
+
self.last_active_at = self.created_at
|
|
154
|
+
|
|
155
|
+
def touch(self) -> None:
|
|
156
|
+
"""Update last_active_at to current time."""
|
|
157
|
+
self.last_active_at = datetime.utcnow()
|
|
147
158
|
|
|
148
159
|
def to_dict(self) -> dict[str, Any]:
|
|
149
160
|
"""Convert to dictionary."""
|
|
@@ -158,6 +169,7 @@ class StreamingSession:
|
|
|
158
169
|
"created_at": self.created_at.isoformat(),
|
|
159
170
|
"started_at": self.started_at.isoformat() if self.started_at else None,
|
|
160
171
|
"stopped_at": self.stopped_at.isoformat() if self.stopped_at else None,
|
|
172
|
+
"last_active_at": self.last_active_at.isoformat() if self.last_active_at else None,
|
|
161
173
|
"config": self.config,
|
|
162
174
|
"statistics": {col: stats.to_dict() for col, stats in self._column_stats.items()},
|
|
163
175
|
"total_points": len(self._buffer),
|
|
@@ -165,6 +177,89 @@ class StreamingSession:
|
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
|
|
180
|
+
# =============================================================================
|
|
181
|
+
# Session Cleanup Policies
|
|
182
|
+
# =============================================================================
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class SessionCleanupPolicy(ABC):
|
|
186
|
+
"""Abstract policy for determining if a streaming session should be cleaned up.
|
|
187
|
+
|
|
188
|
+
Implement this interface to define custom cleanup strategies.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def should_cleanup(self, session: StreamingSession, now: datetime) -> bool:
|
|
193
|
+
"""Determine if a session should be removed.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
session: The streaming session to evaluate.
|
|
197
|
+
now: Current timestamp.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
True if the session should be cleaned up.
|
|
201
|
+
"""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class IdleTTLPolicy(SessionCleanupPolicy):
|
|
206
|
+
"""Remove sessions that have been idle longer than their TTL.
|
|
207
|
+
|
|
208
|
+
Supports per-status TTL configuration so that stopped/error sessions
|
|
209
|
+
are cleaned up faster than active ones.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
# Default TTL per session status (in seconds)
|
|
213
|
+
DEFAULT_STATUS_TTLS: dict[StreamingSessionStatus, int] = {
|
|
214
|
+
StreamingSessionStatus.STOPPED: 300, # 5 min
|
|
215
|
+
StreamingSessionStatus.ERROR: 600, # 10 min
|
|
216
|
+
StreamingSessionStatus.CREATED: 900, # 15 min
|
|
217
|
+
StreamingSessionStatus.PAUSED: 1800, # 30 min
|
|
218
|
+
StreamingSessionStatus.RUNNING: 3600, # 60 min
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
default_ttl_seconds: int = 1800,
|
|
224
|
+
status_ttls: dict[StreamingSessionStatus, int] | None = None,
|
|
225
|
+
) -> None:
|
|
226
|
+
self._default_ttl = default_ttl_seconds
|
|
227
|
+
self._status_ttls = status_ttls or dict(self.DEFAULT_STATUS_TTLS)
|
|
228
|
+
|
|
229
|
+
def should_cleanup(self, session: StreamingSession, now: datetime) -> bool:
|
|
230
|
+
ttl = self._status_ttls.get(session.status, self._default_ttl)
|
|
231
|
+
reference_time = session.last_active_at or session.created_at
|
|
232
|
+
return (now - reference_time) > timedelta(seconds=ttl)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class CompositeCleanupPolicy(SessionCleanupPolicy):
|
|
236
|
+
"""Combine multiple cleanup policies with AND/OR logic.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
policies: List of policies to combine.
|
|
240
|
+
require_all: If True, all policies must agree (AND).
|
|
241
|
+
If False, any policy is sufficient (OR). Default: False.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
policies: list[SessionCleanupPolicy],
|
|
247
|
+
require_all: bool = False,
|
|
248
|
+
) -> None:
|
|
249
|
+
self._policies = policies
|
|
250
|
+
self._require_all = require_all
|
|
251
|
+
|
|
252
|
+
def should_cleanup(self, session: StreamingSession, now: datetime) -> bool:
|
|
253
|
+
if self._require_all:
|
|
254
|
+
return all(p.should_cleanup(session, now) for p in self._policies)
|
|
255
|
+
return any(p.should_cleanup(session, now) for p in self._policies)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# =============================================================================
|
|
259
|
+
# Streaming Anomaly Detector
|
|
260
|
+
# =============================================================================
|
|
261
|
+
|
|
262
|
+
|
|
168
263
|
class StreamingAnomalyDetector:
|
|
169
264
|
"""Real-time streaming anomaly detection service.
|
|
170
265
|
|
|
@@ -173,12 +268,80 @@ class StreamingAnomalyDetector:
|
|
|
173
268
|
- Multiple algorithms (Z-score, EMA, etc.)
|
|
174
269
|
- Online learning / model updates
|
|
175
270
|
- Alert callbacks for real-time notifications
|
|
271
|
+
- Automatic session cleanup via configurable policies
|
|
176
272
|
"""
|
|
177
273
|
|
|
178
|
-
def __init__(
|
|
179
|
-
|
|
274
|
+
def __init__(
|
|
275
|
+
self,
|
|
276
|
+
cleanup_policy: SessionCleanupPolicy | None = None,
|
|
277
|
+
cleanup_interval_seconds: int = 60,
|
|
278
|
+
) -> None:
|
|
279
|
+
"""Initialize the streaming detector.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
cleanup_policy: Policy for automatic session cleanup.
|
|
283
|
+
Defaults to IdleTTLPolicy.
|
|
284
|
+
cleanup_interval_seconds: How often to run cleanup (in seconds).
|
|
285
|
+
"""
|
|
180
286
|
self._sessions: dict[str, StreamingSession] = {}
|
|
181
287
|
self._lock = asyncio.Lock()
|
|
288
|
+
self._cleanup_policy = cleanup_policy or IdleTTLPolicy()
|
|
289
|
+
self._cleanup_interval = cleanup_interval_seconds
|
|
290
|
+
self._cleanup_task: asyncio.Task[None] | None = None
|
|
291
|
+
|
|
292
|
+
# =========================================================================
|
|
293
|
+
# Lifecycle
|
|
294
|
+
# =========================================================================
|
|
295
|
+
|
|
296
|
+
async def start(self) -> None:
|
|
297
|
+
"""Start the background session cleanup task."""
|
|
298
|
+
if self._cleanup_task is None:
|
|
299
|
+
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
300
|
+
logger.info("Streaming session cleanup task started")
|
|
301
|
+
|
|
302
|
+
async def stop(self) -> None:
|
|
303
|
+
"""Stop the background session cleanup task."""
|
|
304
|
+
if self._cleanup_task is not None:
|
|
305
|
+
self._cleanup_task.cancel()
|
|
306
|
+
try:
|
|
307
|
+
await self._cleanup_task
|
|
308
|
+
except asyncio.CancelledError:
|
|
309
|
+
pass
|
|
310
|
+
self._cleanup_task = None
|
|
311
|
+
logger.info("Streaming session cleanup task stopped")
|
|
312
|
+
|
|
313
|
+
async def _cleanup_loop(self) -> None:
|
|
314
|
+
"""Background loop that periodically removes expired sessions."""
|
|
315
|
+
while True:
|
|
316
|
+
try:
|
|
317
|
+
await asyncio.sleep(self._cleanup_interval)
|
|
318
|
+
removed = await self._cleanup_expired_sessions()
|
|
319
|
+
if removed:
|
|
320
|
+
logger.info(
|
|
321
|
+
"Cleaned up %d expired streaming session(s)", removed
|
|
322
|
+
)
|
|
323
|
+
except asyncio.CancelledError:
|
|
324
|
+
break
|
|
325
|
+
except Exception:
|
|
326
|
+
logger.exception("Error during streaming session cleanup")
|
|
327
|
+
|
|
328
|
+
async def _cleanup_expired_sessions(self) -> int:
|
|
329
|
+
"""Remove sessions that match the cleanup policy.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Number of sessions removed.
|
|
333
|
+
"""
|
|
334
|
+
now = datetime.utcnow()
|
|
335
|
+
async with self._lock:
|
|
336
|
+
to_remove = [
|
|
337
|
+
sid
|
|
338
|
+
for sid, session in self._sessions.items()
|
|
339
|
+
if self._cleanup_policy.should_cleanup(session, now)
|
|
340
|
+
]
|
|
341
|
+
for sid in to_remove:
|
|
342
|
+
logger.debug("Removing expired streaming session %s", sid)
|
|
343
|
+
del self._sessions[sid]
|
|
344
|
+
return len(to_remove)
|
|
182
345
|
|
|
183
346
|
# =========================================================================
|
|
184
347
|
# Session Management
|
|
@@ -244,6 +407,7 @@ class StreamingAnomalyDetector:
|
|
|
244
407
|
|
|
245
408
|
session.status = StreamingSessionStatus.RUNNING
|
|
246
409
|
session.started_at = datetime.utcnow()
|
|
410
|
+
session.touch()
|
|
247
411
|
|
|
248
412
|
return session
|
|
249
413
|
|
|
@@ -333,6 +497,7 @@ class StreamingAnomalyDetector:
|
|
|
333
497
|
if session.status != StreamingSessionStatus.RUNNING:
|
|
334
498
|
raise ValueError(f"Session '{session_id}' is not running")
|
|
335
499
|
|
|
500
|
+
session.touch()
|
|
336
501
|
timestamp = timestamp or datetime.utcnow()
|
|
337
502
|
|
|
338
503
|
# Store data point in buffer
|