detectkit 0.2.4__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.
- detectkit/__init__.py +17 -0
- detectkit/alerting/__init__.py +13 -0
- detectkit/alerting/channels/__init__.py +21 -0
- detectkit/alerting/channels/base.py +193 -0
- detectkit/alerting/channels/email.py +146 -0
- detectkit/alerting/channels/factory.py +193 -0
- detectkit/alerting/channels/mattermost.py +53 -0
- detectkit/alerting/channels/slack.py +55 -0
- detectkit/alerting/channels/telegram.py +110 -0
- detectkit/alerting/channels/webhook.py +139 -0
- detectkit/alerting/orchestrator.py +369 -0
- detectkit/cli/__init__.py +1 -0
- detectkit/cli/commands/__init__.py +1 -0
- detectkit/cli/commands/init.py +282 -0
- detectkit/cli/commands/run.py +486 -0
- detectkit/cli/commands/test_alert.py +184 -0
- detectkit/cli/main.py +186 -0
- detectkit/config/__init__.py +30 -0
- detectkit/config/metric_config.py +499 -0
- detectkit/config/profile.py +285 -0
- detectkit/config/project_config.py +164 -0
- detectkit/config/validator.py +124 -0
- detectkit/core/__init__.py +6 -0
- detectkit/core/interval.py +132 -0
- detectkit/core/models.py +106 -0
- detectkit/database/__init__.py +27 -0
- detectkit/database/clickhouse_manager.py +393 -0
- detectkit/database/internal_tables.py +724 -0
- detectkit/database/manager.py +324 -0
- detectkit/database/tables.py +138 -0
- detectkit/detectors/__init__.py +6 -0
- detectkit/detectors/base.py +441 -0
- detectkit/detectors/factory.py +138 -0
- detectkit/detectors/statistical/__init__.py +8 -0
- detectkit/detectors/statistical/iqr.py +508 -0
- detectkit/detectors/statistical/mad.py +478 -0
- detectkit/detectors/statistical/manual_bounds.py +206 -0
- detectkit/detectors/statistical/zscore.py +491 -0
- detectkit/loaders/__init__.py +6 -0
- detectkit/loaders/metric_loader.py +470 -0
- detectkit/loaders/query_template.py +164 -0
- detectkit/orchestration/__init__.py +9 -0
- detectkit/orchestration/task_manager.py +746 -0
- detectkit/utils/__init__.py +17 -0
- detectkit/utils/stats.py +196 -0
- detectkit-0.2.4.dist-info/METADATA +237 -0
- detectkit-0.2.4.dist-info/RECORD +51 -0
- detectkit-0.2.4.dist-info/WHEEL +5 -0
- detectkit-0.2.4.dist-info/entry_points.txt +2 -0
- detectkit-0.2.4.dist-info/licenses/LICENSE +21 -0
- detectkit-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base database manager interface.
|
|
3
|
+
|
|
4
|
+
Provides universal methods for database operations WITHOUT hardcoding
|
|
5
|
+
specific table logic (e.g., _dtk_datapoints, _dtk_detections).
|
|
6
|
+
|
|
7
|
+
The manager is database-agnostic and provides generic operations:
|
|
8
|
+
- execute_query(): Run SQL and return results
|
|
9
|
+
- create_table(): Create table from TableModel
|
|
10
|
+
- table_exists(): Check if table exists
|
|
11
|
+
- insert_batch(): Insert batch of data
|
|
12
|
+
- get_last_timestamp(): Get last timestamp for a metric
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from detectkit.core.models import TableModel
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseDatabaseManager(ABC):
|
|
25
|
+
"""
|
|
26
|
+
Universal database manager interface.
|
|
27
|
+
|
|
28
|
+
This class provides GENERIC methods for database operations.
|
|
29
|
+
It does NOT hardcode logic for internal tables (_dtk_datapoints, etc.).
|
|
30
|
+
|
|
31
|
+
Internal table management is handled by higher-level classes that
|
|
32
|
+
use these generic methods.
|
|
33
|
+
|
|
34
|
+
Key Design Principles:
|
|
35
|
+
1. Universal methods (not table-specific)
|
|
36
|
+
2. Works with any table via table_name parameter
|
|
37
|
+
3. Type conversion handled internally
|
|
38
|
+
4. Connection pooling and error handling
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def execute_query(
|
|
43
|
+
self,
|
|
44
|
+
query: str,
|
|
45
|
+
params: Optional[Dict[str, Any]] = None
|
|
46
|
+
) -> List[Dict[str, Any]]:
|
|
47
|
+
"""
|
|
48
|
+
Execute SQL query and return results as list of dictionaries.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
query: SQL query to execute
|
|
52
|
+
params: Optional query parameters for parameterized queries
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of dictionaries where each dict represents a row
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
DatabaseError: If query execution fails
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> results = manager.execute_query(
|
|
62
|
+
... "SELECT * FROM metrics WHERE name = %(name)s",
|
|
63
|
+
... {"name": "cpu_usage"}
|
|
64
|
+
... )
|
|
65
|
+
>>> for row in results:
|
|
66
|
+
... print(row['timestamp'], row['value'])
|
|
67
|
+
"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def create_table(
|
|
72
|
+
self,
|
|
73
|
+
table_name: str,
|
|
74
|
+
table_model: TableModel,
|
|
75
|
+
if_not_exists: bool = True
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Create table from TableModel definition.
|
|
79
|
+
|
|
80
|
+
Converts database-agnostic TableModel into database-specific DDL.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
table_name: Name of table to create
|
|
84
|
+
table_model: Table schema definition
|
|
85
|
+
if_not_exists: Add IF NOT EXISTS clause
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
DatabaseError: If table creation fails
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> model = TableModel(
|
|
92
|
+
... columns=[
|
|
93
|
+
... ColumnDefinition("id", "Int32"),
|
|
94
|
+
... ColumnDefinition("value", "Float64", nullable=True),
|
|
95
|
+
... ],
|
|
96
|
+
... primary_key=["id"],
|
|
97
|
+
... engine="MergeTree",
|
|
98
|
+
... order_by=["id"]
|
|
99
|
+
... )
|
|
100
|
+
>>> manager.create_table("my_metrics", model)
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def table_exists(
|
|
106
|
+
self,
|
|
107
|
+
table_name: str,
|
|
108
|
+
schema: Optional[str] = None
|
|
109
|
+
) -> bool:
|
|
110
|
+
"""
|
|
111
|
+
Check if table exists in database.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
table_name: Name of table to check
|
|
115
|
+
schema: Optional schema/database name (if None, use default)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if table exists, False otherwise
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
>>> if not manager.table_exists("_dtk_datapoints"):
|
|
122
|
+
... manager.create_table("_dtk_datapoints", datapoints_model)
|
|
123
|
+
"""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
def insert_batch(
|
|
128
|
+
self,
|
|
129
|
+
table_name: str,
|
|
130
|
+
data: Dict[str, np.ndarray],
|
|
131
|
+
conflict_strategy: str = "ignore"
|
|
132
|
+
) -> int:
|
|
133
|
+
"""
|
|
134
|
+
Insert batch of data into table.
|
|
135
|
+
|
|
136
|
+
Universal method that works with any table - NOT specific to
|
|
137
|
+
internal tables.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
table_name: Name of table to insert into
|
|
141
|
+
data: Dictionary mapping column names to numpy arrays
|
|
142
|
+
All arrays must have same length
|
|
143
|
+
conflict_strategy: How to handle conflicts:
|
|
144
|
+
- "ignore": Skip rows with duplicate primary keys
|
|
145
|
+
- "replace": Replace existing rows
|
|
146
|
+
- "fail": Raise error on conflict
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Number of rows inserted (may be less than input if conflicts ignored)
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ValueError: If arrays have different lengths
|
|
153
|
+
DatabaseError: If insertion fails
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
>>> data = {
|
|
157
|
+
... "metric_name": np.array(["cpu", "cpu"]),
|
|
158
|
+
... "timestamp": np.array([dt1, dt2]),
|
|
159
|
+
... "value": np.array([0.5, 0.6]),
|
|
160
|
+
... }
|
|
161
|
+
>>> rows_inserted = manager.insert_batch(
|
|
162
|
+
... "_dtk_datapoints", data, conflict_strategy="ignore"
|
|
163
|
+
... )
|
|
164
|
+
"""
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def get_last_timestamp(
|
|
169
|
+
self,
|
|
170
|
+
table_name: str,
|
|
171
|
+
metric_name: str,
|
|
172
|
+
timestamp_column: str = "timestamp"
|
|
173
|
+
) -> Optional[datetime]:
|
|
174
|
+
"""
|
|
175
|
+
Get last timestamp for a specific metric in a table.
|
|
176
|
+
|
|
177
|
+
Universal method that works with any table containing metric_name
|
|
178
|
+
and timestamp columns.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
table_name: Table to query
|
|
182
|
+
metric_name: Value to filter by metric_name column
|
|
183
|
+
timestamp_column: Name of timestamp column (default: "timestamp")
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Last timestamp or None if no data found
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> last_ts = manager.get_last_timestamp(
|
|
190
|
+
... "_dtk_datapoints", "cpu_usage"
|
|
191
|
+
... )
|
|
192
|
+
>>> if last_ts:
|
|
193
|
+
... print(f"Last data point at {last_ts}")
|
|
194
|
+
"""
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def upsert_task_status(
|
|
199
|
+
self,
|
|
200
|
+
metric_name: str,
|
|
201
|
+
detector_id: str,
|
|
202
|
+
process_type: str,
|
|
203
|
+
status: str,
|
|
204
|
+
last_processed_timestamp: Optional[datetime] = None,
|
|
205
|
+
error_message: Optional[str] = None,
|
|
206
|
+
timeout_seconds: int = 3600
|
|
207
|
+
) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Update or insert task status (for locking and idempotency).
|
|
210
|
+
|
|
211
|
+
This method is critical for:
|
|
212
|
+
1. Task locking: Prevent concurrent runs of same task
|
|
213
|
+
2. Idempotency: Store last_processed_timestamp to resume from interruptions
|
|
214
|
+
|
|
215
|
+
Implementation varies by database:
|
|
216
|
+
- ClickHouse: DELETE + INSERT (no native UPSERT)
|
|
217
|
+
- PostgreSQL: INSERT ... ON CONFLICT DO UPDATE
|
|
218
|
+
- MySQL: INSERT ... ON DUPLICATE KEY UPDATE
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
metric_name: Metric identifier
|
|
222
|
+
detector_id: Detector identifier (or "load" for loading tasks)
|
|
223
|
+
process_type: Type of process ("load" or "detect")
|
|
224
|
+
status: Task status ("running", "completed", "failed")
|
|
225
|
+
last_processed_timestamp: Last successfully processed timestamp
|
|
226
|
+
error_message: Error message if status is "failed"
|
|
227
|
+
timeout_seconds: Task timeout in seconds
|
|
228
|
+
|
|
229
|
+
Example:
|
|
230
|
+
>>> # Start task
|
|
231
|
+
>>> manager.upsert_task_status(
|
|
232
|
+
... "cpu_usage", "load", "load", "running",
|
|
233
|
+
... timeout_seconds=3600
|
|
234
|
+
... )
|
|
235
|
+
>>> # Update progress
|
|
236
|
+
>>> manager.upsert_task_status(
|
|
237
|
+
... "cpu_usage", "load", "load", "running",
|
|
238
|
+
... last_processed_timestamp=datetime(2024, 1, 1, 12, 0)
|
|
239
|
+
... )
|
|
240
|
+
>>> # Complete task
|
|
241
|
+
>>> manager.upsert_task_status(
|
|
242
|
+
... "cpu_usage", "load", "load", "completed",
|
|
243
|
+
... last_processed_timestamp=datetime(2024, 1, 1, 23, 59)
|
|
244
|
+
... )
|
|
245
|
+
"""
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
@abstractmethod
|
|
250
|
+
def internal_location(self) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Get full location path for internal tables.
|
|
253
|
+
|
|
254
|
+
Format depends on database:
|
|
255
|
+
- ClickHouse: "database_name"
|
|
256
|
+
- PostgreSQL: "schema_name"
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Full path to internal schema/database
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
>>> manager.internal_location
|
|
263
|
+
'detectk_internal'
|
|
264
|
+
"""
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
@abstractmethod
|
|
269
|
+
def data_location(self) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Get full location path for user data tables.
|
|
272
|
+
|
|
273
|
+
Format depends on database:
|
|
274
|
+
- ClickHouse: "database_name"
|
|
275
|
+
- PostgreSQL: "schema_name"
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Full path to data schema/database
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
>>> manager.data_location
|
|
282
|
+
'analytics'
|
|
283
|
+
"""
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
def get_full_table_name(
|
|
287
|
+
self,
|
|
288
|
+
table_name: str,
|
|
289
|
+
use_internal: bool = True
|
|
290
|
+
) -> str:
|
|
291
|
+
"""
|
|
292
|
+
Get fully qualified table name.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
table_name: Table name
|
|
296
|
+
use_internal: If True, use internal_location, else data_location
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Fully qualified table name
|
|
300
|
+
|
|
301
|
+
Example:
|
|
302
|
+
>>> manager.get_full_table_name("_dtk_datapoints", use_internal=True)
|
|
303
|
+
'detectk_internal._dtk_datapoints'
|
|
304
|
+
"""
|
|
305
|
+
location = self.internal_location if use_internal else self.data_location
|
|
306
|
+
return f"{location}.{table_name}"
|
|
307
|
+
|
|
308
|
+
@abstractmethod
|
|
309
|
+
def close(self) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Close database connection and cleanup resources.
|
|
312
|
+
|
|
313
|
+
Example:
|
|
314
|
+
>>> manager.close()
|
|
315
|
+
"""
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
def __enter__(self):
|
|
319
|
+
"""Context manager entry."""
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
323
|
+
"""Context manager exit - close connection."""
|
|
324
|
+
self.close()
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal table models for detectk.
|
|
3
|
+
|
|
4
|
+
Defines schemas for internal tables:
|
|
5
|
+
- _dtk_datapoints: Metric data points
|
|
6
|
+
- _dtk_detections: Anomaly detections
|
|
7
|
+
- _dtk_tasks: Task status and locking
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from detectkit.core.models import ColumnDefinition, TableModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_datapoints_table_model() -> TableModel:
|
|
14
|
+
"""
|
|
15
|
+
Get TableModel for _dtk_datapoints table.
|
|
16
|
+
|
|
17
|
+
Schema:
|
|
18
|
+
- metric_name: Metric identifier
|
|
19
|
+
- timestamp: Data point timestamp (UTC, millisecond precision)
|
|
20
|
+
- value: Metric value (nullable for missing data)
|
|
21
|
+
- seasonality_data: JSON with seasonality components (hour, day_of_week, etc.)
|
|
22
|
+
- interval_seconds: Interval in seconds
|
|
23
|
+
- seasonality_columns: Comma-separated list of seasonality columns used
|
|
24
|
+
- created_at: When record was created (UTC, millisecond precision)
|
|
25
|
+
|
|
26
|
+
Primary Key: (metric_name, timestamp)
|
|
27
|
+
"""
|
|
28
|
+
return TableModel(
|
|
29
|
+
columns=[
|
|
30
|
+
ColumnDefinition("metric_name", "String"),
|
|
31
|
+
ColumnDefinition("timestamp", "DateTime64(3, 'UTC')"),
|
|
32
|
+
ColumnDefinition("value", "Nullable(Float64)", nullable=True),
|
|
33
|
+
ColumnDefinition("seasonality_data", "String"),
|
|
34
|
+
ColumnDefinition("interval_seconds", "Int32"),
|
|
35
|
+
ColumnDefinition("seasonality_columns", "String"),
|
|
36
|
+
ColumnDefinition("created_at", "DateTime64(3, 'UTC')"),
|
|
37
|
+
],
|
|
38
|
+
primary_key=["metric_name", "timestamp"],
|
|
39
|
+
engine="ReplacingMergeTree(created_at)",
|
|
40
|
+
order_by=["metric_name", "timestamp"],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_detections_table_model() -> TableModel:
|
|
45
|
+
"""
|
|
46
|
+
Get TableModel for _dtk_detections table.
|
|
47
|
+
|
|
48
|
+
Schema:
|
|
49
|
+
- metric_name: Metric identifier
|
|
50
|
+
- detector_id: Detector identifier (hash of class + params)
|
|
51
|
+
- detector_name: Detector class name (e.g., "MADDetector", "ZScoreDetector")
|
|
52
|
+
- timestamp: Detection timestamp (UTC, millisecond precision)
|
|
53
|
+
- is_anomaly: Whether point is anomalous
|
|
54
|
+
- confidence_lower: Lower confidence bound
|
|
55
|
+
- confidence_upper: Upper confidence bound
|
|
56
|
+
- value: Actual metric value (ALWAYS original value)
|
|
57
|
+
- processed_value: Value analyzed by detector (may be smoothed/transformed)
|
|
58
|
+
- detector_params: JSON with sorted detector parameters
|
|
59
|
+
- detection_metadata: JSON with missing_ratio, severity, direction, etc.
|
|
60
|
+
- created_at: When detection was performed (UTC, millisecond precision)
|
|
61
|
+
|
|
62
|
+
Primary Key: (metric_name, detector_id, timestamp)
|
|
63
|
+
"""
|
|
64
|
+
return TableModel(
|
|
65
|
+
columns=[
|
|
66
|
+
ColumnDefinition("metric_name", "String"),
|
|
67
|
+
ColumnDefinition("detector_id", "String"),
|
|
68
|
+
ColumnDefinition("detector_name", "String"),
|
|
69
|
+
ColumnDefinition("timestamp", "DateTime64(3, 'UTC')"),
|
|
70
|
+
ColumnDefinition("is_anomaly", "Bool"),
|
|
71
|
+
ColumnDefinition("confidence_lower", "Nullable(Float64)", nullable=True),
|
|
72
|
+
ColumnDefinition("confidence_upper", "Nullable(Float64)", nullable=True),
|
|
73
|
+
ColumnDefinition("value", "Nullable(Float64)", nullable=True),
|
|
74
|
+
ColumnDefinition("processed_value", "Nullable(Float64)", nullable=True),
|
|
75
|
+
ColumnDefinition("detector_params", "String"),
|
|
76
|
+
ColumnDefinition("detection_metadata", "String"),
|
|
77
|
+
ColumnDefinition("created_at", "DateTime64(3, 'UTC')"),
|
|
78
|
+
],
|
|
79
|
+
primary_key=["metric_name", "detector_id", "timestamp"],
|
|
80
|
+
engine="ReplacingMergeTree(created_at)",
|
|
81
|
+
order_by=["metric_name", "detector_id", "timestamp"],
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_tasks_table_model() -> TableModel:
|
|
86
|
+
"""
|
|
87
|
+
Get TableModel for _dtk_tasks table.
|
|
88
|
+
|
|
89
|
+
Schema:
|
|
90
|
+
- metric_name: Metric identifier
|
|
91
|
+
- detector_id: Detector identifier (or "load" for loading tasks)
|
|
92
|
+
- process_type: Type of process ("load" or "detect")
|
|
93
|
+
- status: Task status ("running", "completed", "failed")
|
|
94
|
+
- started_at: When task started (UTC, millisecond precision)
|
|
95
|
+
- updated_at: Last update timestamp (UTC, millisecond precision)
|
|
96
|
+
- last_processed_timestamp: Last successfully processed timestamp
|
|
97
|
+
- error_message: Error message if failed (nullable)
|
|
98
|
+
- timeout_seconds: Task timeout in seconds
|
|
99
|
+
|
|
100
|
+
Primary Key: (metric_name, detector_id, process_type)
|
|
101
|
+
|
|
102
|
+
This table serves dual purpose:
|
|
103
|
+
1. Locking: Only one process can run for a given (metric, detector, type)
|
|
104
|
+
2. Resume: Stores last_processed_timestamp to resume from interruptions
|
|
105
|
+
"""
|
|
106
|
+
return TableModel(
|
|
107
|
+
columns=[
|
|
108
|
+
ColumnDefinition("metric_name", "String"),
|
|
109
|
+
ColumnDefinition("detector_id", "String"),
|
|
110
|
+
ColumnDefinition("process_type", "String"),
|
|
111
|
+
ColumnDefinition("status", "String"),
|
|
112
|
+
ColumnDefinition("started_at", "DateTime64(3, 'UTC')"),
|
|
113
|
+
ColumnDefinition("updated_at", "DateTime64(3, 'UTC')"),
|
|
114
|
+
ColumnDefinition(
|
|
115
|
+
"last_processed_timestamp",
|
|
116
|
+
"Nullable(DateTime64(3, 'UTC'))",
|
|
117
|
+
nullable=True
|
|
118
|
+
),
|
|
119
|
+
ColumnDefinition("error_message", "Nullable(String)", nullable=True),
|
|
120
|
+
ColumnDefinition("timeout_seconds", "Int32"),
|
|
121
|
+
],
|
|
122
|
+
primary_key=["metric_name", "detector_id", "process_type"],
|
|
123
|
+
engine="MergeTree",
|
|
124
|
+
order_by=["metric_name", "detector_id", "process_type"],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Table names as constants
|
|
129
|
+
TABLE_DATAPOINTS = "_dtk_datapoints"
|
|
130
|
+
TABLE_DETECTIONS = "_dtk_detections"
|
|
131
|
+
TABLE_TASKS = "_dtk_tasks"
|
|
132
|
+
|
|
133
|
+
# Map of table names to model factories
|
|
134
|
+
INTERNAL_TABLES = {
|
|
135
|
+
TABLE_DATAPOINTS: get_datapoints_table_model,
|
|
136
|
+
TABLE_DETECTIONS: get_detections_table_model,
|
|
137
|
+
TABLE_TASKS: get_tasks_table_model,
|
|
138
|
+
}
|