detectkit 0.19.0__tar.gz → 0.20.0__tar.gz
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-0.19.0/detectkit.egg-info → detectkit-0.20.0}/PKG-INFO +1 -1
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/__init__.py +1 -1
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/CLAUDE.section.md +11 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/autotune.md +14 -5
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +25 -7
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +9 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/autotune.py +14 -1
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/init.py +64 -2
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/config/metric_config.py +43 -1
- {detectkit-0.19.0 → detectkit-0.20.0/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.19.0 → detectkit-0.20.0}/LICENSE +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/MANIFEST.in +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/README.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/html_labeler.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/core/models.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/pyproject.toml +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/requirements.txt +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/setup.cfg +0 -0
- {detectkit-0.19.0 → detectkit-0.20.0}/setup.py +0 -0
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.20.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -11,6 +11,17 @@ configure alerting and channels, run the pipeline, and debug why an alert did
|
|
|
11
11
|
(or didn't) fire. Stay numpy/SQL/YAML-first and follow the project's existing
|
|
12
12
|
conventions.
|
|
13
13
|
|
|
14
|
+
**Database access for _you_ (recommended, not required).** detectkit itself
|
|
15
|
+
connects to the database directly via its drivers — it **never** needs an MCP to
|
|
16
|
+
run. But you assist far better with **read access to the same database** (e.g. a
|
|
17
|
+
database MCP for the project's ClickHouse / PostgreSQL / MySQL): you can inspect
|
|
18
|
+
a metric's series, find real incidents to label for `dtk autotune`, sanity-check
|
|
19
|
+
a metric query before running it, and confirm detections — instead of asking the
|
|
20
|
+
user to run every query by hand. Without it, fall back to
|
|
21
|
+
`dtk autotune --select <metric> --label` (a visual labeler) and to asking the
|
|
22
|
+
user. This access is optional and separate from the `profiles.yml` connection
|
|
23
|
+
detectkit uses for the pipeline.
|
|
24
|
+
|
|
14
25
|
### Where to look (read the matching file before answering)
|
|
15
26
|
|
|
16
27
|
The full, authoritative reference lives in `.claude/rules/detectkit/`. These
|
|
@@ -35,9 +35,12 @@ dtk autotune --select <sel> [--incidents FILE] [--label] [--scoring METRIC] \
|
|
|
35
35
|
[--from DATE] [--to DATE] [--profile NAME] [--force] [--dry-run]
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
- `--incidents FILE` — a labels file (below) → **supervised** tuning.
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
- `--incidents FILE` — a labels file (below) → **supervised** tuning. With no
|
|
39
|
+
labels file, an interactive terminal prompts to enter incidents inline;
|
|
40
|
+
declining (or running non-interactively) tunes **unsupervised**.
|
|
41
|
+
- `--label` — write a self-contained HTML chart of the series to
|
|
42
|
+
`metrics/<name>__labeler.html`; the user marks incidents in a browser and its
|
|
43
|
+
**Export** button downloads a labels file. Generate-and-exit (no DB writes).
|
|
41
44
|
- `--scoring` — `mcc` (default), `f1`, `f_beta`, `balanced_accuracy`, `roc_auc`,
|
|
42
45
|
`pr_auc`. MCC uses the whole confusion matrix and suits rare anomalies.
|
|
43
46
|
- `--dry-run` — run the search but persist nothing and write no config.
|
|
@@ -76,14 +79,20 @@ autotune:
|
|
|
76
79
|
detector_types: [mad, zscore] # restrict candidates (subset of mad/zscore/iqr)
|
|
77
80
|
scoring_metric: mcc # default optimization target
|
|
78
81
|
beta: 1.0 # only for scoring_metric: f_beta
|
|
79
|
-
labels_file: incidents/orders.yml
|
|
82
|
+
labels_file: incidents/orders.yml # external labels file, OR inline (below)
|
|
83
|
+
# incidents: # inline labels — mutually exclusive with labels_file
|
|
84
|
+
# - {start: "2026-05-02 14:00:00", end: "2026-05-02 16:30:00", label: outage}
|
|
85
|
+
# - {at: "2026-05-11 09:05:00", label: deploy spike}
|
|
86
|
+
# incidents_timezone: UTC # interprets the naive times above (default UTC)
|
|
80
87
|
seasonality_candidates: [hour, day_of_week]
|
|
81
88
|
fixed_params: {window_size: 4320} # pin hyperparameters (excluded from search)
|
|
82
89
|
folds: 5
|
|
83
90
|
max_history: 50000 # cap training points
|
|
84
91
|
```
|
|
85
92
|
|
|
86
|
-
|
|
93
|
+
Label precedence (highest first): `--incidents` flag → `labels_file` → inline
|
|
94
|
+
`incidents` → interactive prompt → none. `labels_file` and `incidents` are
|
|
95
|
+
mutually exclusive. `--scoring` likewise overrides `scoring_metric`.
|
|
87
96
|
|
|
88
97
|
## The annotated config
|
|
89
98
|
|
{detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
@@ -57,10 +57,13 @@ If there are no extra signals, the built-ins are enough — continue.
|
|
|
57
57
|
## Step 2 — Gather incident history → write the labels file
|
|
58
58
|
|
|
59
59
|
Supervised tuning needs known incidents. The labels file is the contract; you
|
|
60
|
-
fill it from the user's plain-language description.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
fill it from the user's plain-language description. (If you have read access to
|
|
61
|
+
the database — e.g. a database MCP — query the metric's series yourself to spot
|
|
62
|
+
candidate incidents and propose them for confirmation, rather than relying only
|
|
63
|
+
on memory.) Resolve each incident to **UTC** and classify it as an interval
|
|
64
|
+
(`{start, end}`) for a sustained incident or a point (`{at}`) for a spike. Read
|
|
65
|
+
the resolved times back to confirm, then write `incidents/<metric>.yml` (the
|
|
66
|
+
`dtk init` scaffold already created `incidents/` beside `metrics/`):
|
|
64
67
|
|
|
65
68
|
```yaml
|
|
66
69
|
# incidents/<metric>.yml — known anomalies for supervised autotuning.
|
|
@@ -76,9 +79,24 @@ incidents:
|
|
|
76
79
|
label: deploy spike
|
|
77
80
|
```
|
|
78
81
|
|
|
79
|
-
The same schema works as JSON.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
The same schema works as JSON. **Inline alternative:** for just one or two
|
|
83
|
+
incidents, declare the same entries directly under the metric's `autotune:` block
|
|
84
|
+
(`incidents:` + optional `incidents_timezone:`) instead of a separate file —
|
|
85
|
+
mutually exclusive with `labels_file`, and `--incidents` still overrides it:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
autotune:
|
|
89
|
+
incidents:
|
|
90
|
+
- {start: "2026-05-02 14:00:00", end: "2026-05-02 16:30:00", label: outage}
|
|
91
|
+
- {at: "2026-05-11 09:05:00", label: deploy spike}
|
|
92
|
+
incidents_timezone: UTC # optional; default UTC
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If the user can't enumerate incidents, say so and go to Step 3 unsupervised — or
|
|
96
|
+
offer `dtk autotune --select <name> --label`, which writes a clickable HTML chart
|
|
97
|
+
to `metrics/<name>__labeler.html`; they mark incidents in a browser and its
|
|
98
|
+
Export button downloads a labels file in this exact format to feed back via
|
|
99
|
+
`--incidents`.
|
|
82
100
|
|
|
83
101
|
## Step 3 — Run autotune
|
|
84
102
|
|
{detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
@@ -38,6 +38,15 @@ the nearest ancestor that has it). If there is none, ask for a project name and
|
|
|
38
38
|
run `dtk init <name>`, then work inside that folder. If several projects sit
|
|
39
39
|
side by side, ask which one to set up.
|
|
40
40
|
|
|
41
|
+
> **Recommended (optional): give _yourself_ read access to the database.**
|
|
42
|
+
> detectkit connects to the DB directly via its drivers and never needs an MCP
|
|
43
|
+
> to run — the `profiles.yml` connection you set up below is all the *tool*
|
|
44
|
+
> needs. But you'll help much better with read access to the same database (e.g.
|
|
45
|
+
> a database MCP): you can inspect metric series, find incidents to label for
|
|
46
|
+
> `dtk autotune`, sanity-check queries, and confirm detections yourself. Suggest
|
|
47
|
+
> the user connect one if they haven't; if they can't, you can still do
|
|
48
|
+
> everything by asking them to run queries and by using `dtk autotune --label`.
|
|
49
|
+
|
|
41
50
|
## Step 1 — Pick the database backend
|
|
42
51
|
|
|
43
52
|
**ClickHouse, PostgreSQL and MySQL are all fully supported.** Ask which one the
|
|
@@ -101,7 +101,11 @@ def _resolve_labels(
|
|
|
101
101
|
autotune_cfg: AutoTuneConfig,
|
|
102
102
|
project_root: Path,
|
|
103
103
|
) -> tuple[IncidentLabels, str]:
|
|
104
|
-
"""Resolve labels by precedence
|
|
104
|
+
"""Resolve labels by precedence.
|
|
105
|
+
|
|
106
|
+
``--incidents`` flag > config ``labels_file`` > config inline ``incidents`` >
|
|
107
|
+
interactive prompt > none (unsupervised).
|
|
108
|
+
"""
|
|
105
109
|
path = incidents_path or autotune_cfg.labels_file
|
|
106
110
|
if path:
|
|
107
111
|
file_path = Path(path)
|
|
@@ -112,6 +116,15 @@ def _resolve_labels(
|
|
|
112
116
|
)
|
|
113
117
|
return labels, f"file {file_path}"
|
|
114
118
|
|
|
119
|
+
if autotune_cfg.incidents:
|
|
120
|
+
labels = parse_incident_labels(
|
|
121
|
+
{"incidents": autotune_cfg.incidents, "timezone": autotune_cfg.incidents_timezone},
|
|
122
|
+
interval_seconds=interval_seconds,
|
|
123
|
+
metric_name=metric_name,
|
|
124
|
+
)
|
|
125
|
+
n = len(autotune_cfg.incidents)
|
|
126
|
+
return labels, f"inline config ({n} incident{'s' if n != 1 else ''})"
|
|
127
|
+
|
|
115
128
|
if sys.stdin.isatty():
|
|
116
129
|
return _prompt_labels(interval_seconds=interval_seconds), "interactive"
|
|
117
130
|
|
|
@@ -130,6 +130,48 @@ _BUCKET_SQL = {
|
|
|
130
130
|
),
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
# Example incidents (labels) file for supervised `dtk autotune`. Lives in
|
|
134
|
+
# incidents/ beside metrics/; pointed at via `--incidents` or a metric's
|
|
135
|
+
# `autotune.labels_file`. See docs/guides/autotuning.md.
|
|
136
|
+
_EXAMPLE_INCIDENTS = """# Example incidents (labels) file for supervised `dtk autotune`.
|
|
137
|
+
#
|
|
138
|
+
# Tell autotune WHICH points were real incidents so it can pick the detector,
|
|
139
|
+
# threshold, seasonality and alert window that catch them while keeping false
|
|
140
|
+
# positives down. Hand it to autotune with:
|
|
141
|
+
#
|
|
142
|
+
# dtk autotune --select example_cpu_usage --incidents incidents/example_cpu_usage.yml
|
|
143
|
+
#
|
|
144
|
+
# ...or reference it from the metric's `autotune.labels_file:` (see
|
|
145
|
+
# metrics/example_cpu_usage.yml). Without any labels, autotune falls back to an
|
|
146
|
+
# unsupervised objective (low false-positive rate + stable cross-fold separation).
|
|
147
|
+
#
|
|
148
|
+
# Format:
|
|
149
|
+
# - YAML or JSON. ALL times are UTC unless `timezone:` says otherwise.
|
|
150
|
+
# - Each incident is EITHER an interval ({start, end}) for a sustained problem
|
|
151
|
+
# OR a point ({at}) for a single spike — never both keys. `end` is inclusive.
|
|
152
|
+
# - `label` is optional free text (documentation only).
|
|
153
|
+
#
|
|
154
|
+
# Tip: can't list incidents from memory? Run
|
|
155
|
+
# dtk autotune --select example_cpu_usage --label
|
|
156
|
+
# to get a clickable HTML chart; mark incidents in a browser and export this file.
|
|
157
|
+
|
|
158
|
+
# Optional — must match the metric `name:` it labels (autotune refuses a mismatch).
|
|
159
|
+
metric: example_cpu_usage
|
|
160
|
+
|
|
161
|
+
# Optional — interprets the naive times below (defaults to UTC).
|
|
162
|
+
timezone: UTC
|
|
163
|
+
|
|
164
|
+
incidents:
|
|
165
|
+
# Interval incident (a sustained problem):
|
|
166
|
+
- start: "2026-05-02 14:00:00"
|
|
167
|
+
end: "2026-05-02 16:30:00"
|
|
168
|
+
label: example sustained spike # optional, free text
|
|
169
|
+
|
|
170
|
+
# Point incident (a single anomalous timestamp):
|
|
171
|
+
- at: "2026-05-11 09:05:00"
|
|
172
|
+
label: example one-off spike
|
|
173
|
+
"""
|
|
174
|
+
|
|
133
175
|
# Alert-channel section (backend-independent); appended after the profiles.
|
|
134
176
|
_ALERT_CHANNELS = """
|
|
135
177
|
# Alert channels (referenced by name from a metric's alerting.channels)
|
|
@@ -190,7 +232,9 @@ def run_init(project_name: str, target_dir: str, db_type: str = "clickhouse"):
|
|
|
190
232
|
├── detectkit_project.yml
|
|
191
233
|
├── profiles.yml
|
|
192
234
|
├── metrics/
|
|
193
|
-
│ └── .
|
|
235
|
+
│ └── example_cpu_usage.yml
|
|
236
|
+
├── incidents/
|
|
237
|
+
│ └── example_cpu_usage.yml # labels for supervised `dtk autotune`
|
|
194
238
|
└── sql/
|
|
195
239
|
└── .gitkeep
|
|
196
240
|
"""
|
|
@@ -216,12 +260,16 @@ def run_init(project_name: str, target_dir: str, db_type: str = "clickhouse"):
|
|
|
216
260
|
|
|
217
261
|
# Create subdirectories
|
|
218
262
|
(target_path / "metrics").mkdir(exist_ok=True)
|
|
263
|
+
(target_path / "incidents").mkdir(exist_ok=True)
|
|
219
264
|
(target_path / "sql").mkdir(exist_ok=True)
|
|
220
265
|
|
|
221
|
-
# Create .gitkeep files
|
|
266
|
+
# Create .gitkeep files (incidents/ is kept by its example file below)
|
|
222
267
|
(target_path / "metrics" / ".gitkeep").touch()
|
|
223
268
|
(target_path / "sql" / ".gitkeep").touch()
|
|
224
269
|
|
|
270
|
+
# Example incidents (labels) file for supervised `dtk autotune`
|
|
271
|
+
(target_path / "incidents" / "example_cpu_usage.yml").write_text(_EXAMPLE_INCIDENTS)
|
|
272
|
+
|
|
225
273
|
# Create detectkit_project.yml
|
|
226
274
|
project_config = f"""# detectkit project configuration
|
|
227
275
|
name: {project_name_clean}
|
|
@@ -337,6 +385,19 @@ alerting:
|
|
|
337
385
|
no_data_alert: false # alert when the latest interval has no data
|
|
338
386
|
# alert_cooldown: "2h" # recommended: suppress repeats of a persisting anomaly
|
|
339
387
|
|
|
388
|
+
# Auto-tuning (optional) — `dtk autotune --select example_cpu_usage` picks the
|
|
389
|
+
# detector config for you. Supply known incidents to tune supervised; either
|
|
390
|
+
# point at a labels file OR declare them inline (the two are mutually exclusive).
|
|
391
|
+
# autotune:
|
|
392
|
+
# enabled: true
|
|
393
|
+
# # (a) external labels file (see incidents/example_cpu_usage.yml):
|
|
394
|
+
# labels_file: incidents/example_cpu_usage.yml
|
|
395
|
+
# # (b) — or — inline incidents:
|
|
396
|
+
# # incidents:
|
|
397
|
+
# # - {start: "2026-05-02 14:00:00", end: "2026-05-02 16:30:00", label: outage}
|
|
398
|
+
# # - {at: "2026-05-11 09:05:00", label: deploy spike}
|
|
399
|
+
# # incidents_timezone: UTC # interprets the naive times above (default UTC)
|
|
400
|
+
|
|
340
401
|
# Tags for selection
|
|
341
402
|
tags:
|
|
342
403
|
- critical
|
|
@@ -368,6 +429,7 @@ detectkit monitoring project.
|
|
|
368
429
|
- `detectkit_project.yml` - Project configuration
|
|
369
430
|
- `profiles.yml` - Database connection profiles
|
|
370
431
|
- `metrics/` - Metric definitions (YAML files)
|
|
432
|
+
- `incidents/` - Labeled incidents for supervised `dtk autotune` (optional)
|
|
371
433
|
- `sql/` - SQL query files (optional)
|
|
372
434
|
|
|
373
435
|
## Commands
|
|
@@ -320,7 +320,11 @@ class AutoTuneConfig(BaseModel):
|
|
|
320
320
|
detector_types: [mad, zscore] # restrict candidates
|
|
321
321
|
scoring_metric: mcc # optimization target
|
|
322
322
|
beta: 1.0 # only for scoring_metric: f_beta
|
|
323
|
-
labels_file: incidents/orders.yml
|
|
323
|
+
labels_file: incidents/orders.yml # external labels file, OR inline:
|
|
324
|
+
incidents: # inline labels (mutually exclusive)
|
|
325
|
+
- {start: "2026-05-02 14:00:00", end: "2026-05-02 16:30:00"}
|
|
326
|
+
- {at: "2026-05-11 09:05:00"}
|
|
327
|
+
incidents_timezone: UTC # interprets the naive times above
|
|
324
328
|
seasonality_candidates: [hour, day_of_week]
|
|
325
329
|
fixed_params: # pinned, not searched
|
|
326
330
|
window_size: 4320
|
|
@@ -344,6 +348,16 @@ class AutoTuneConfig(BaseModel):
|
|
|
344
348
|
labels_file: str | None = Field(
|
|
345
349
|
default=None, description="Project-relative path to a canonical labels file"
|
|
346
350
|
)
|
|
351
|
+
incidents: list[dict[str, Any]] | None = Field(
|
|
352
|
+
default=None,
|
|
353
|
+
description="Inline labeled incidents (alternative to labels_file). Each entry is "
|
|
354
|
+
"{start, end} for a sustained incident or {at} for a single point; an optional "
|
|
355
|
+
"'label' is free-text documentation.",
|
|
356
|
+
)
|
|
357
|
+
incidents_timezone: str | None = Field(
|
|
358
|
+
default=None,
|
|
359
|
+
description="Timezone that interprets the naive times in `incidents` (default UTC)",
|
|
360
|
+
)
|
|
347
361
|
seasonality_candidates: list[str] | None = Field(
|
|
348
362
|
default=None, description="Restrict the seasonality columns the search may use"
|
|
349
363
|
)
|
|
@@ -422,6 +436,34 @@ class AutoTuneConfig(BaseModel):
|
|
|
422
436
|
raise ValueError("max_history must be at least 1")
|
|
423
437
|
return v
|
|
424
438
|
|
|
439
|
+
@model_validator(mode="after")
|
|
440
|
+
def validate_inline_incidents(self) -> "AutoTuneConfig":
|
|
441
|
+
"""Validate inline incidents: not alongside labels_file, and well-formed.
|
|
442
|
+
|
|
443
|
+
Reuses the canonical labels parser so an inline block is validated by the
|
|
444
|
+
same rules as a labels file (timestamp formats, interval-vs-point shape,
|
|
445
|
+
timezone), failing fast at config load.
|
|
446
|
+
"""
|
|
447
|
+
if self.labels_file and self.incidents:
|
|
448
|
+
raise ValueError(
|
|
449
|
+
"Set either 'labels_file' or 'incidents', not both "
|
|
450
|
+
"(inline incidents and an external labels file are mutually exclusive)"
|
|
451
|
+
)
|
|
452
|
+
if self.incidents_timezone and not self.incidents:
|
|
453
|
+
raise ValueError("incidents_timezone has no effect without 'incidents'")
|
|
454
|
+
if self.incidents is not None:
|
|
455
|
+
# Local import to avoid a config <-> autotune import cycle at module load.
|
|
456
|
+
from detectkit.autotune.labels import parse_incident_labels
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
parse_incident_labels(
|
|
460
|
+
{"incidents": self.incidents, "timezone": self.incidents_timezone},
|
|
461
|
+
interval_seconds=1,
|
|
462
|
+
)
|
|
463
|
+
except Exception as exc: # ValueError, bad timezone (KeyError), etc.
|
|
464
|
+
raise ValueError(f"invalid 'incidents': {exc}") from exc
|
|
465
|
+
return self
|
|
466
|
+
|
|
425
467
|
|
|
426
468
|
class MetricConfig(BaseModel):
|
|
427
469
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.19.0 → detectkit-0.20.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|