detectkit 0.1.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.
Files changed (49) hide show
  1. detectkit/__init__.py +17 -0
  2. detectkit/alerting/__init__.py +13 -0
  3. detectkit/alerting/channels/__init__.py +21 -0
  4. detectkit/alerting/channels/base.py +191 -0
  5. detectkit/alerting/channels/email.py +146 -0
  6. detectkit/alerting/channels/factory.py +193 -0
  7. detectkit/alerting/channels/mattermost.py +53 -0
  8. detectkit/alerting/channels/slack.py +55 -0
  9. detectkit/alerting/channels/telegram.py +110 -0
  10. detectkit/alerting/channels/webhook.py +139 -0
  11. detectkit/alerting/orchestrator.py +368 -0
  12. detectkit/cli/__init__.py +1 -0
  13. detectkit/cli/commands/__init__.py +1 -0
  14. detectkit/cli/commands/init.py +282 -0
  15. detectkit/cli/commands/run.py +427 -0
  16. detectkit/cli/commands/test_alert.py +184 -0
  17. detectkit/cli/main.py +186 -0
  18. detectkit/config/__init__.py +30 -0
  19. detectkit/config/metric_config.py +467 -0
  20. detectkit/config/profile.py +285 -0
  21. detectkit/config/project_config.py +164 -0
  22. detectkit/core/__init__.py +6 -0
  23. detectkit/core/interval.py +132 -0
  24. detectkit/core/models.py +106 -0
  25. detectkit/database/__init__.py +27 -0
  26. detectkit/database/clickhouse_manager.py +385 -0
  27. detectkit/database/internal_tables.py +581 -0
  28. detectkit/database/manager.py +324 -0
  29. detectkit/database/tables.py +134 -0
  30. detectkit/detectors/__init__.py +6 -0
  31. detectkit/detectors/base.py +222 -0
  32. detectkit/detectors/factory.py +138 -0
  33. detectkit/detectors/statistical/__init__.py +8 -0
  34. detectkit/detectors/statistical/iqr.py +230 -0
  35. detectkit/detectors/statistical/mad.py +423 -0
  36. detectkit/detectors/statistical/manual_bounds.py +177 -0
  37. detectkit/detectors/statistical/zscore.py +225 -0
  38. detectkit/loaders/__init__.py +6 -0
  39. detectkit/loaders/metric_loader.py +470 -0
  40. detectkit/loaders/query_template.py +164 -0
  41. detectkit/orchestration/__init__.py +9 -0
  42. detectkit/orchestration/task_manager.py +698 -0
  43. detectkit/utils/__init__.py +1 -0
  44. detectkit-0.1.0.dist-info/METADATA +231 -0
  45. detectkit-0.1.0.dist-info/RECORD +49 -0
  46. detectkit-0.1.0.dist-info/WHEEL +5 -0
  47. detectkit-0.1.0.dist-info/entry_points.txt +2 -0
  48. detectkit-0.1.0.dist-info/licenses/LICENSE +21 -0
  49. detectkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,184 @@
1
+ """
2
+ Test alert command - send test alert to configured channels.
3
+
4
+ Allows testing alert rendering and channel delivery without real anomalies.
5
+ Useful for:
6
+ - Verifying Mattermost/Slack message formatting
7
+ - Testing webhook connectivity
8
+ - Previewing alert templates
9
+ """
10
+
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ import numpy as np
16
+
17
+ from detectkit.alerting.channels.base import AlertData
18
+ from detectkit.alerting.channels.factory import AlertChannelFactory
19
+ from detectkit.config.metric_config import MetricConfig
20
+
21
+
22
+ def create_mock_alert_data(
23
+ metric_config: MetricConfig,
24
+ timezone_display: str = "UTC",
25
+ ) -> AlertData:
26
+ """
27
+ Create realistic mock AlertData for testing.
28
+
29
+ Args:
30
+ metric_config: Metric configuration
31
+ timezone_display: Timezone for display
32
+
33
+ Returns:
34
+ AlertData with mock anomaly data
35
+ """
36
+ # Use current time
37
+ now = datetime.now(timezone.utc)
38
+
39
+ # Create realistic mock data
40
+ return AlertData(
41
+ metric_name=metric_config.name,
42
+ timestamp=np.datetime64(now, "ms"),
43
+ timezone=timezone_display,
44
+ value=0.8532, # Mock anomalous value
45
+ confidence_lower=0.4521,
46
+ confidence_upper=0.6234,
47
+ detector_name="MADDetector:threshold=3.0",
48
+ detector_params='{"threshold": 3.0, "window_size": 8640}',
49
+ direction="above",
50
+ severity=4.52,
51
+ detection_metadata={
52
+ "global_median": 0.5123,
53
+ "adjusted_median": 0.5234,
54
+ "seasonality_groups": [
55
+ {
56
+ "group": ["offset_10minutes", "league_day"],
57
+ "median_multiplier": 1.023,
58
+ "mad_multiplier": 0.876,
59
+ "group_size": 23,
60
+ }
61
+ ],
62
+ },
63
+ consecutive_count=3,
64
+ )
65
+
66
+
67
+ def run_test_alert(metric_name: str, profile: Optional[str] = None):
68
+ """
69
+ Send test alert for specified metric.
70
+
71
+ Args:
72
+ metric_name: Name of metric to test alert for
73
+ profile: Optional profile override
74
+ """
75
+ # Load project config
76
+ project_root = Path.cwd()
77
+ project_config_path = project_root / "detectkit_project.yml"
78
+
79
+ if not project_config_path.exists():
80
+ print("Error: No detectkit_project.yml found in current directory")
81
+ print("Run this command from your detectkit project root")
82
+ return
83
+
84
+ # Load project config manually (avoid validation issues)
85
+ import yaml
86
+
87
+ with open(project_config_path) as f:
88
+ project_data = yaml.safe_load(f)
89
+
90
+ metrics_dir_name = project_data.get("metrics_path", "metrics")
91
+
92
+ # Find metric config
93
+ metrics_dir = project_root / metrics_dir_name
94
+ metric_files = list(metrics_dir.glob("**/*.yml")) + list(
95
+ metrics_dir.glob("**/*.yaml")
96
+ )
97
+
98
+ metric_config = None
99
+ for metric_file in metric_files:
100
+ try:
101
+ config = MetricConfig.from_yaml_file(metric_file)
102
+ if config.name == metric_name:
103
+ metric_config = config
104
+ break
105
+ except Exception:
106
+ continue
107
+
108
+ if not metric_config:
109
+ print(f"Error: Metric '{metric_name}' not found")
110
+ print(f"Searched in: {metrics_dir}")
111
+ return
112
+
113
+ # Check if alerting is configured
114
+ if not metric_config.alerting or not metric_config.alerting.enabled:
115
+ print(f"Error: Alerting not enabled for metric '{metric_name}'")
116
+ print("Enable alerting in metric config (alerting.enabled: true)")
117
+ return
118
+
119
+ if not metric_config.alerting.channels:
120
+ print(f"Error: No alert channels configured for metric '{metric_name}'")
121
+ print("Add channels in metric config (alerting.channels: [...])")
122
+ return
123
+
124
+ # Load profiles
125
+ profiles_path = project_root / "profiles.yml"
126
+ if not profiles_path.exists():
127
+ print("Error: profiles.yml not found")
128
+ return
129
+
130
+ import yaml
131
+
132
+ with open(profiles_path) as f:
133
+ profiles_data = yaml.safe_load(f)
134
+
135
+ alert_channels_config = profiles_data.get("alert_channels", {})
136
+
137
+ # Get timezone for display
138
+ timezone_display = metric_config.alerting.timezone or "UTC"
139
+
140
+ # Create mock alert data
141
+ print(f"\n📨 Sending test alert for metric: {metric_name}")
142
+ print(f" Timezone: {timezone_display}")
143
+ print(f" Channels: {', '.join(metric_config.alerting.channels)}\n")
144
+
145
+ alert_data = create_mock_alert_data(metric_config, timezone_display)
146
+
147
+ # Send to each configured channel
148
+ success_count = 0
149
+ for channel_name in metric_config.alerting.channels:
150
+ if channel_name not in alert_channels_config:
151
+ print(f"⚠️ Channel '{channel_name}' not found in profiles.yml - skipping")
152
+ continue
153
+
154
+ channel_config = alert_channels_config[channel_name]
155
+
156
+ try:
157
+ # Create channel instance
158
+ # channel_config должен содержать 'type' + остальные параметры
159
+ channel = AlertChannelFactory.create_from_config(channel_config)
160
+
161
+ # Get custom template if configured
162
+ template = None
163
+ if metric_config.alerting.template_consecutive:
164
+ template = metric_config.alerting.template_consecutive
165
+
166
+ # Send alert
167
+ print(f" → Sending to {channel_name}...", end=" ")
168
+ success = channel.send(alert_data, template=template)
169
+
170
+ if success:
171
+ print("✓ SUCCESS")
172
+ success_count += 1
173
+ else:
174
+ print("✗ FAILED")
175
+
176
+ except Exception as e:
177
+ print(f"✗ ERROR: {e}")
178
+
179
+ # Summary
180
+ print(f"\n{'✓' if success_count > 0 else '✗'} Sent test alert to {success_count}/{len(metric_config.alerting.channels)} channels")
181
+
182
+ if success_count > 0:
183
+ print("\n💡 Check your configured channels to verify message formatting")
184
+ print(f" Mock data used: value=0.8532, confidence=[0.4521, 0.6234], severity=4.52")
detectkit/cli/main.py ADDED
@@ -0,0 +1,186 @@
1
+ """
2
+ Main CLI entry point for detectkit.
3
+
4
+ Provides dbt-like commands:
5
+ - dtk init <project_name>
6
+ - dtk run --select <selector>
7
+ """
8
+
9
+ import click
10
+
11
+
12
+ @click.group()
13
+ @click.version_option(version="0.1.0", prog_name="detectkit")
14
+ def cli():
15
+ """
16
+ detectkit - Metric monitoring with automatic anomaly detection.
17
+
18
+ A dbt-like tool for monitoring time-series metrics with anomaly detection
19
+ and alerting.
20
+
21
+ Examples:
22
+ dtk init my_project
23
+ dtk run --select cpu_usage
24
+ dtk run --select tag:critical --steps load,detect
25
+ """
26
+ pass
27
+
28
+
29
+ @cli.command()
30
+ @click.argument("project_name")
31
+ @click.option(
32
+ "--target-dir",
33
+ "-d",
34
+ default=".",
35
+ help="Directory to create project in (default: current directory)",
36
+ )
37
+ def init(project_name: str, target_dir: str):
38
+ """
39
+ Initialize a new detectkit project.
40
+
41
+ Creates project structure with configuration files and directories:
42
+ - detectkit_project.yml (project config)
43
+ - profiles.yml (database connections)
44
+ - metrics/ (metric definitions)
45
+ - sql/ (SQL queries)
46
+
47
+ Example:
48
+ dtk init my_monitoring_project
49
+ dtk init analytics --target-dir /opt/projects
50
+ """
51
+ from detectkit.cli.commands.init import run_init
52
+
53
+ run_init(project_name, target_dir)
54
+
55
+
56
+ @cli.command()
57
+ @click.option(
58
+ "--select",
59
+ "-s",
60
+ help="Selector for metrics to run (metric name, path, or tag)",
61
+ required=True,
62
+ )
63
+ @click.option(
64
+ "--exclude",
65
+ "-e",
66
+ help="Selector for metrics to exclude (metric name, path, or tag)",
67
+ )
68
+ @click.option(
69
+ "--steps",
70
+ default="load,detect,alert",
71
+ help="Pipeline steps to execute (default: load,detect,alert)",
72
+ )
73
+ @click.option(
74
+ "--from",
75
+ "from_date",
76
+ help="Start date for data loading (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)",
77
+ )
78
+ @click.option(
79
+ "--to",
80
+ "to_date",
81
+ help="End date for data loading (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)",
82
+ )
83
+ @click.option(
84
+ "--full-refresh",
85
+ is_flag=True,
86
+ help="Delete all existing data and reload from scratch",
87
+ )
88
+ @click.option(
89
+ "--force",
90
+ is_flag=True,
91
+ help="Ignore task locks (use with caution)",
92
+ )
93
+ @click.option(
94
+ "--profile",
95
+ help="Profile to use (default: from project config)",
96
+ )
97
+ def run(
98
+ select: str,
99
+ exclude: str,
100
+ steps: str,
101
+ from_date: str,
102
+ to_date: str,
103
+ full_refresh: bool,
104
+ force: bool,
105
+ profile: str,
106
+ ):
107
+ """
108
+ Run metric processing pipeline.
109
+
110
+ Select metrics to process using --select:
111
+ - Metric name: --select cpu_usage
112
+ - Path pattern: --select metrics/critical/*.yml
113
+ - Tag: --select tag:critical
114
+
115
+ Control pipeline steps with --steps:
116
+ - All steps: --steps load,detect,alert (default)
117
+ - Load only: --steps load
118
+ - Detect and alert: --steps detect,alert
119
+
120
+ Examples:
121
+ # Run all steps for single metric
122
+ dtk run --select cpu_usage
123
+
124
+ # Load data only for multiple metrics
125
+ dtk run --select "tag:critical" --steps load
126
+
127
+ # Reload data from specific date
128
+ dtk run --select cpu_usage --from 2024-01-01
129
+
130
+ # Full refresh (delete and reload all data)
131
+ dtk run --select cpu_usage --full-refresh
132
+
133
+ # Force run (ignore locks)
134
+ dtk run --select cpu_usage --force
135
+ """
136
+ from detectkit.cli.commands.run import run_command
137
+
138
+ run_command(
139
+ select=select,
140
+ exclude=exclude,
141
+ steps=steps,
142
+ from_date=from_date,
143
+ to_date=to_date,
144
+ full_refresh=full_refresh,
145
+ force=force,
146
+ profile=profile,
147
+ )
148
+
149
+
150
+ @cli.command()
151
+ @click.argument("metric_name")
152
+ @click.option(
153
+ "--profile",
154
+ help="Profile to use (default: from project config)",
155
+ )
156
+ def test_alert(metric_name: str, profile: str):
157
+ """
158
+ Send test alert for a metric.
159
+
160
+ Sends a test alert with mock anomaly data to all configured channels
161
+ for the specified metric. Useful for:
162
+ - Testing alert channel connectivity
163
+ - Verifying message formatting and rendering
164
+ - Previewing custom alert templates
165
+
166
+ The test alert uses realistic mock data:
167
+ - Current timestamp
168
+ - Mock anomaly value (0.8532)
169
+ - Mock confidence interval [0.4521, 0.6234]
170
+ - Mock severity (4.52)
171
+ - 3 consecutive anomalies
172
+
173
+ Examples:
174
+ # Test alert for single metric
175
+ dtk test-alert cpu_usage
176
+
177
+ # Test with specific profile
178
+ dtk test-alert cpu_usage --profile production
179
+ """
180
+ from detectkit.cli.commands.test_alert import run_test_alert
181
+
182
+ run_test_alert(metric_name=metric_name, profile=profile)
183
+
184
+
185
+ if __name__ == "__main__":
186
+ cli()
@@ -0,0 +1,30 @@
1
+ """Configuration management for detectkit."""
2
+
3
+ from detectkit.config.profile import ProfileConfig, ProfilesConfig
4
+ from detectkit.config.metric_config import (
5
+ MetricConfig,
6
+ DetectorConfig,
7
+ AlertConfig,
8
+ QueryColumnsConfig,
9
+ TablesConfig,
10
+ )
11
+ from detectkit.config.project_config import (
12
+ ProjectConfig,
13
+ ProjectPathsConfig,
14
+ ProjectTablesConfig,
15
+ ProjectTimeoutsConfig,
16
+ )
17
+
18
+ __all__ = [
19
+ "ProfileConfig",
20
+ "ProfilesConfig",
21
+ "MetricConfig",
22
+ "DetectorConfig",
23
+ "AlertConfig",
24
+ "QueryColumnsConfig",
25
+ "TablesConfig",
26
+ "ProjectConfig",
27
+ "ProjectPathsConfig",
28
+ "ProjectTablesConfig",
29
+ "ProjectTimeoutsConfig",
30
+ ]