detectkit 0.24.0__tar.gz → 0.24.2__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.24.0/detectkit.egg-info → detectkit-0.24.2}/PKG-INFO +1 -1
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/__init__.py +1 -1
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/config_emitter.py +11 -3
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/autotune.md +2 -1
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/cli.md +8 -7
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/detectors.md +1 -1
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +7 -5
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/init_claude.py +10 -7
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_datapoints.py +15 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/_detect_step.py +24 -0
- {detectkit-0.24.0 → detectkit-0.24.2/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.24.0 → detectkit-0.24.2}/LICENSE +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/MANIFEST.in +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/README.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/html_labeler.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/label_server.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/result.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/_output.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/main.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/config/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/config/profile.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/config/project_config.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/config/validator.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/core/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/core/interval.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/core/models.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/database/tables.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit/utils/stats.py +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/pyproject.toml +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/requirements.txt +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/setup.cfg +0 -0
- {detectkit-0.24.0 → detectkit-0.24.2}/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.24.
|
|
7
|
+
__version__ = "0.24.2"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -103,9 +103,17 @@ def _build_body(original: MetricConfig, result: AutoTuneResult, new_name: str) -
|
|
|
103
103
|
elif original.seasonality_columns:
|
|
104
104
|
body["seasonality_columns"] = original.seasonality_columns
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
# Pin the detector's detection start to the load start so the first
|
|
107
|
+
# `dtk run` on the generated config detects across all loaded history. A
|
|
108
|
+
# fresh detector that omits `start_time` has no lower bound (no prior
|
|
109
|
+
# detections, no --from); DETECT falls back to loading_start_time, but
|
|
110
|
+
# emitting it keeps the generated config explicit and self-sufficient on
|
|
111
|
+
# older detectkit. `start_time` is execution-level and excluded from the
|
|
112
|
+
# detector_id hash, so it never changes detector identity.
|
|
113
|
+
detector_params = dict(result.chosen_detector_params)
|
|
114
|
+
if "start_time" not in detector_params and body.get("loading_start_time"):
|
|
115
|
+
detector_params["start_time"] = body["loading_start_time"]
|
|
116
|
+
body["detectors"] = [{"type": result.chosen_detector_type, "params": detector_params}]
|
|
109
117
|
alerting = _build_alerting(original, result)
|
|
110
118
|
if alerting is not None:
|
|
111
119
|
body["alerting"] = alerting
|
|
@@ -32,7 +32,8 @@ same windowed detectors and `detector_id` identity). The fastest path is the
|
|
|
32
32
|
4. **History window** — on near-ties uses a trend-gated tie-break: a stationary
|
|
33
33
|
series prefers the **larger** `window_size` ("more history is better"), a
|
|
34
34
|
trending / regime-shifting one the **smaller**; sets `loading_start_time` to
|
|
35
|
-
cover the lead-in
|
|
35
|
+
cover the lead-in (and pins the detector's `start_time` to it, so the first
|
|
36
|
+
`dtk run` detects across all loaded history).
|
|
36
37
|
5. **Alert window** (supervised only) — sweeps `consecutive_anomalies` on the
|
|
37
38
|
labeled incidents.
|
|
38
39
|
|
|
@@ -20,15 +20,16 @@ Run all commands from a project directory (the one containing
|
|
|
20
20
|
|
|
21
21
|
Used by `run`, `unlock`, and `clean` (drift mode). Three forms:
|
|
22
22
|
|
|
23
|
-
- **Metric name** — `--select cpu_usage`.
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
- **Metric name** — `--select cpu_usage`. Resolves to `metrics/cpu_usage.yml` at
|
|
24
|
+
the root, then falls back to a recursive search by the YAML `name:` field in any
|
|
25
|
+
subdirectory. Do **not** add `.yml` (it is appended). This matches the metric by
|
|
26
|
+
**name**, and every operation is keyed by that `name` inside the YAML.
|
|
26
27
|
- **Path / glob** — `--select "metrics/critical/*.yml"`, `--select "api_*"`,
|
|
27
28
|
`--select "metrics/**/*.yml"`. Searches recursively via glob; keep `.yml`.
|
|
28
29
|
- **Tag** — `--select tag:critical`. Searches recursively for metrics whose
|
|
29
30
|
`tags:` list contains that tag.
|
|
30
31
|
|
|
31
|
-
`--select "*"` selects everything. `--exclude / -e` removes matches
|
|
32
|
+
`--select "*"` selects everything. `--exclude / -e` (on `dtk run`) removes matches
|
|
32
33
|
(`--select "*" --exclude "metrics/staging/*"`). Metric names must be unique
|
|
33
34
|
across the project; duplicates raise an error listing the conflicting files.
|
|
34
35
|
|
|
@@ -39,9 +40,9 @@ dtk run --select <sel> [--steps load,detect,alert] [--from DATE] [--to DATE] \
|
|
|
39
40
|
[--full-refresh] [--force] [--profile NAME]
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
- `--steps` —
|
|
43
|
-
|
|
44
|
-
`--steps detect,alert` (skip load).
|
|
43
|
+
- `--steps` — which of `load`, `detect`, `alert` to run (default all); they always
|
|
44
|
+
execute in `load → detect → alert` order. Examples: `--steps load` (verify the
|
|
45
|
+
query), `--steps detect` (rerun detection only), `--steps detect,alert` (skip load).
|
|
45
46
|
- `--from DATE` / `--to DATE` — `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS`, UTC.
|
|
46
47
|
Affects only the `load` step. `--from` overrides the metric's
|
|
47
48
|
`loading_start_time`; `--to` defaults to now.
|
|
@@ -67,7 +67,7 @@ expected interval for the current point.
|
|
|
67
67
|
half_life: null # exponential half-life: int points or "3d"/"12h"
|
|
68
68
|
detrend: null # null | linear
|
|
69
69
|
# --- execution (NOT hashed) ---
|
|
70
|
-
start_time: "2024-01-01 00:00:00" # when detection begins
|
|
70
|
+
start_time: "2024-01-01 00:00:00" # optional; when detection begins (default: loading_start_time)
|
|
71
71
|
batch_size: 500
|
|
72
72
|
```
|
|
73
73
|
|
{detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
@@ -158,8 +158,10 @@ dtk autotune --select <name>
|
|
|
158
158
|
none yet, load first: `dtk run --select <name> --steps load` (optionally
|
|
159
159
|
`--from <date>` to backfill history — more history tunes better). The default
|
|
160
160
|
**supervised** scoring metric is **MCC** (robust to rare anomalies). Override
|
|
161
|
-
only with reason: `--scoring
|
|
162
|
-
|
|
161
|
+
only with reason: `--scoring f_beta` (with a higher `beta:` in the `autotune:`
|
|
162
|
+
block) when a miss is worse than a false page, `--scoring f1` for a balanced
|
|
163
|
+
trade-off, etc. The valid values are `mcc`, `f1`, `f_beta`, `balanced_accuracy`,
|
|
164
|
+
`roc_auc`, `pr_auc`. Run `dtk autotune --help` to confirm the live flags. Use
|
|
163
165
|
`--dry-run` to search without writing anything.
|
|
164
166
|
|
|
165
167
|
**Unsupervised runs** (no labels) do **not** optimize MCC or any labeled metric —
|
|
@@ -191,9 +193,9 @@ The `#` comment header walks the whole decision; summarize for the user:
|
|
|
191
193
|
`Objective : unsupervised (band-fit + flag-budget) = …` (it never claims an
|
|
192
194
|
`mcc =` score it didn't compute). Read off the value + the per-fold CV spread.
|
|
193
195
|
|
|
194
|
-
Offer alternatives: a re-run with a different `--scoring` (e.g.
|
|
195
|
-
recall
|
|
196
|
-
`_dtk_autotune_runs` audit table.
|
|
196
|
+
Offer alternatives: a re-run with a different `--scoring` (e.g. `f_beta` with a
|
|
197
|
+
higher `beta` to favor recall, or `f1` for balance) or a nudged parameter. See
|
|
198
|
+
`autotune.md` for the `_dtk_autotune_runs` audit table.
|
|
197
199
|
|
|
198
200
|
## Step 5 — Show how the monitoring behaves (DB inspection query)
|
|
199
201
|
|
|
@@ -10,9 +10,9 @@ It writes three things into the target directory:
|
|
|
10
10
|
- ``CLAUDE.md`` — created if absent, otherwise a managed detectkit block is
|
|
11
11
|
injected/refreshed between HTML-comment markers (existing content is kept).
|
|
12
12
|
- ``.claude/rules/detectkit/`` — the reference docs the assistant reads on
|
|
13
|
-
demand (overview, cli, project, metrics, detectors, alerting).
|
|
13
|
+
demand (overview, cli, project, metrics, detectors, alerting, autotune).
|
|
14
14
|
- ``.claude/skills/`` — user-facing skills (``dtk-setup-project``,
|
|
15
|
-
``dtk-new-metric``, ``dtk-feedback``).
|
|
15
|
+
``dtk-new-metric``, ``dtk-autotune``, ``dtk-feedback``).
|
|
16
16
|
|
|
17
17
|
The source of truth for all of the above lives in ``detectkit/cli/assets/claude``
|
|
18
18
|
and ships with the package, so re-running this command after upgrading detectkit
|
|
@@ -38,12 +38,15 @@ if TYPE_CHECKING:
|
|
|
38
38
|
# strings, so it is never evaluated at runtime — safe on 3.10).
|
|
39
39
|
from importlib.resources.abc import Traversable
|
|
40
40
|
|
|
41
|
-
# Region in CLAUDE.md owned by this command. The BEGIN marker
|
|
42
|
-
#
|
|
43
|
-
#
|
|
41
|
+
# Region in CLAUDE.md owned by this command. The BEGIN marker is intentionally
|
|
42
|
+
# version-less: stamping the version here made the block churn on every release
|
|
43
|
+
# (a no-op upgrade still rewrote the marker), pushing users to re-run for nothing.
|
|
44
|
+
# The block now changes only when its content actually changes, so a refresh is a
|
|
45
|
+
# true no-op otherwise. The regex still matches the old *versioned* markers
|
|
46
|
+
# (`<!-- BEGIN detectkit v0.23.2 ... -->`), so refreshing an existing file
|
|
47
|
+
# replaces the whole region in place rather than appending a duplicate.
|
|
44
48
|
_BEGIN = (
|
|
45
|
-
|
|
46
|
-
"(managed by `dtk init-claude` — do not edit between these markers) -->"
|
|
49
|
+
"<!-- BEGIN detectkit (managed by `dtk init-claude` — do not edit between these markers) -->"
|
|
47
50
|
)
|
|
48
51
|
_END = "<!-- END detectkit -->"
|
|
49
52
|
_BLOCK_RE = re.compile(r"<!-- BEGIN detectkit.*?-->.*?<!-- END detectkit -->", re.DOTALL)
|
|
@@ -40,6 +40,21 @@ class _DatapointsMixin(_InternalTablesBase):
|
|
|
40
40
|
last_ts = self._manager.get_last_timestamp(full_table_name, metric_name)
|
|
41
41
|
return self._normalize_max_timestamp(last_ts)
|
|
42
42
|
|
|
43
|
+
def get_first_datapoint_timestamp(self, metric_name: str) -> datetime | None:
|
|
44
|
+
"""Return the earliest timestamp stored for *metric_name*, if any."""
|
|
45
|
+
full_table_name = self._manager.get_full_table_name(TABLE_DATAPOINTS, use_internal=True)
|
|
46
|
+
query = f"""
|
|
47
|
+
SELECT min(timestamp) AS first_ts
|
|
48
|
+
FROM {full_table_name}
|
|
49
|
+
WHERE metric_name = %(metric_name)s
|
|
50
|
+
"""
|
|
51
|
+
result = self._manager.execute_query(query, {"metric_name": metric_name})
|
|
52
|
+
if not result:
|
|
53
|
+
return None
|
|
54
|
+
# ClickHouse min() over an empty selection yields the epoch sentinel
|
|
55
|
+
# rather than NULL; _normalize_max_timestamp maps that back to None.
|
|
56
|
+
return self._normalize_max_timestamp(result[0].get("first_ts"))
|
|
57
|
+
|
|
43
58
|
def get_value_at(self, metric_name: str, timestamp: datetime) -> float | None:
|
|
44
59
|
"""Return the stored ``value`` for an exact timestamp.
|
|
45
60
|
|
|
@@ -78,6 +78,16 @@ class _DetectStepMixin(_TaskManagerBase):
|
|
|
78
78
|
actual_from = max(actual_from, start_time) if actual_from else start_time
|
|
79
79
|
|
|
80
80
|
actual_from = to_naive_utc(actual_from)
|
|
81
|
+
|
|
82
|
+
# No lower bound from --from, the resume point, or the detector's
|
|
83
|
+
# `start_time`: a first-ever detect for a detector that omits
|
|
84
|
+
# `start_time` (every `dtk autotune` config does). Fall back to the
|
|
85
|
+
# metric's load start so the run detects across all loaded history,
|
|
86
|
+
# instead of mistaking "no lower bound" for "nothing to do" and
|
|
87
|
+
# silently reporting "already up to date" without ever detecting.
|
|
88
|
+
if actual_from is None:
|
|
89
|
+
actual_from = self._detect_fallback_start(config)
|
|
90
|
+
|
|
81
91
|
if not actual_from or actual_from >= actual_to:
|
|
82
92
|
click.echo(" │ Nothing to detect (already up to date)")
|
|
83
93
|
continue
|
|
@@ -200,3 +210,17 @@ class _DetectStepMixin(_TaskManagerBase):
|
|
|
200
210
|
)
|
|
201
211
|
)
|
|
202
212
|
return {"anomalies_count": anomalies_count}
|
|
213
|
+
|
|
214
|
+
def _detect_fallback_start(self, config: MetricConfig) -> datetime | None:
|
|
215
|
+
"""Lower bound for a first-ever detect when nothing else pins it.
|
|
216
|
+
|
|
217
|
+
Mirrors the LOAD step: prefer the configured ``loading_start_time``,
|
|
218
|
+
else the metric's earliest stored datapoint. Returns ``None`` only when
|
|
219
|
+
the metric has no configured start and no data at all (genuinely nothing
|
|
220
|
+
to detect).
|
|
221
|
+
"""
|
|
222
|
+
if config.loading_start_time:
|
|
223
|
+
return to_naive_utc(
|
|
224
|
+
datetime.fromisoformat(config.loading_start_time.replace("Z", "+00:00"))
|
|
225
|
+
)
|
|
226
|
+
return to_naive_utc(self.internal.get_first_datapoint_timestamp(config.name))
|
|
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.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.24.0 → detectkit-0.24.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/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
|