detectkit 0.62.0__tar.gz → 0.63.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.62.0/detectkit.egg-info → detectkit-0.63.0}/PKG-INFO +3 -2
- {detectkit-0.62.0 → detectkit-0.63.0}/README.md +2 -1
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/__init__.py +1 -1
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/cli.md +7 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/metrics.md +8 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/overview.md +3 -2
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/loaders/metric_loader.py +83 -0
- {detectkit-0.62.0 → detectkit-0.63.0/detectkit.egg-info}/PKG-INFO +3 -2
- {detectkit-0.62.0 → detectkit-0.63.0}/LICENSE +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/MANIFEST.in +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/discord.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/googlechat.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/ntfy.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/teams.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/axis_spec.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-tune/SKILL.md +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/mcp.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/osi.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/ui.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/metric_io.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/core/models.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/duckdb_manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/autoreg.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/loaders/errors.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/context.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/errors.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/serialize.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/server.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/mcp/tools.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/errors.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/exporter.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/importer.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/osi_model.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/semantic/query_gen.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/assets/tune.js +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/html.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/tuning/server.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/assets/ui.js +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/html.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/jobs.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/metric_files.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/overview.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/ui/server.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/pyproject.toml +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/requirements.txt +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/setup.cfg +0 -0
- {detectkit-0.62.0 → detectkit-0.63.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.63.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -110,7 +110,7 @@ Dynamic: license-file
|
|
|
110
110
|
- **Hybrid mode** — read metric SQL from a billed-per-query warehouse while all pipeline state lives in a cheap local database
|
|
111
111
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
112
112
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
113
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
113
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + five skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
114
114
|
|
|
115
115
|
## Installation
|
|
116
116
|
|
|
@@ -122,6 +122,7 @@ With database drivers:
|
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
pip install detectkit[clickhouse] # ClickHouse
|
|
125
|
+
pip install detectkit[duckdb] # DuckDB - single file, no server
|
|
125
126
|
pip install detectkit[all-db] # All databases
|
|
126
127
|
```
|
|
127
128
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
- **Hybrid mode** — read metric SQL from a billed-per-query warehouse while all pipeline state lives in a cheap local database
|
|
29
29
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
30
30
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
31
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
31
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + five skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
34
34
|
|
|
@@ -40,6 +40,7 @@ With database drivers:
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
pip install detectkit[clickhouse] # ClickHouse
|
|
43
|
+
pip install detectkit[duckdb] # DuckDB - single file, no server
|
|
43
44
|
pip install detectkit[all-db] # All databases
|
|
44
45
|
```
|
|
45
46
|
|
|
@@ -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.63.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -458,6 +458,13 @@ fail their step when `dtk run` / `dtk autotune` / `dtk clean` exits non-zero
|
|
|
458
458
|
(see "Exit codes" above), and can additionally parse `dtk run --json`'s stdout
|
|
459
459
|
summary for per-metric detail.
|
|
460
460
|
|
|
461
|
+
On GitHub Actions specifically, detectkit ships a ready-made composite action
|
|
462
|
+
instead of hand-rolling those steps: `uses: alexeiveselov92/detectkit@vX.Y.Z`
|
|
463
|
+
installs detectkit from PyPI and runs `dtk run` / `dtk autotune` / `dtk clean`
|
|
464
|
+
in a given project directory. It surfaces the 0/1/2 exit-code contract (see
|
|
465
|
+
"Exit codes" above) as the job's own outcome, and exposes the
|
|
466
|
+
`dtk run --json` summary as an action output for downstream steps to consume.
|
|
467
|
+
|
|
461
468
|
## Troubleshooting
|
|
462
469
|
|
|
463
470
|
- **"Metric not found"** — selector doesn't match. Use the bare name
|
|
@@ -119,6 +119,14 @@ The query must return a **timestamp** column and a numeric **value** column
|
|
|
119
119
|
(default names `timestamp` / `value`; remap via `query_columns`). It may also
|
|
120
120
|
return seasonality columns (declare them in `query_columns.seasonality`).
|
|
121
121
|
|
|
122
|
+
Returned timestamps must already be **bucketed to the interval grid**
|
|
123
|
+
(`toStartOfInterval` / `time_bucket` / `date_trunc`) — gap filling maps rows
|
|
124
|
+
onto the grid by exact match, and the grid's phase comes from
|
|
125
|
+
`loading_start_time`. A batch that returns rows on a different phase (e.g.
|
|
126
|
+
`:28` past the hour vs a grid phased at `:00`) can land 100% off-grid and
|
|
127
|
+
load as silently all-`NULL`; detectkit logs a one-time warning naming both
|
|
128
|
+
phases when that happens.
|
|
129
|
+
|
|
122
130
|
## Seasonality features
|
|
123
131
|
|
|
124
132
|
Two ways to provide seasonality keys that detectors group by:
|
|
@@ -4,8 +4,9 @@ detectkit is a Python library and CLI (`dtk`) for monitoring time-series
|
|
|
4
4
|
metrics with automatic anomaly detection and multi-channel alerting. It is
|
|
5
5
|
**dbt-like**: metrics live as YAML + SQL in a project directory, and you run
|
|
6
6
|
them with one command. Core logic is pure numpy (no pandas). **ClickHouse,
|
|
7
|
-
PostgreSQL and
|
|
8
|
-
dialect of your metric queries differ between them
|
|
7
|
+
PostgreSQL, MySQL/MariaDB and DuckDB are all fully supported** — only the
|
|
8
|
+
connection and the SQL dialect of your metric queries differ between them
|
|
9
|
+
(DuckDB additionally needs no server at all — just a local file).
|
|
9
10
|
|
|
10
11
|
## The pipeline: load → detect → alert
|
|
11
12
|
|
|
@@ -9,6 +9,7 @@ Loads time-series data from databases with:
|
|
|
9
9
|
- Integration with InternalTablesManager
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import logging
|
|
12
13
|
from datetime import datetime, timedelta
|
|
13
14
|
|
|
14
15
|
import numpy as np
|
|
@@ -21,6 +22,30 @@ from detectkit.loaders.query_template import QueryTemplate
|
|
|
21
22
|
from detectkit.utils.datetime_utils import now_utc_naive, to_naive_utc
|
|
22
23
|
from detectkit.utils.json_utils import json_dumps_sorted
|
|
23
24
|
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _format_offset_seconds(seconds: int) -> str:
|
|
29
|
+
"""Render an offset-within-interval (seconds) as a human clock offset.
|
|
30
|
+
|
|
31
|
+
Used to describe a grid/source "phase" without assuming a particular
|
|
32
|
+
interval unit: ``0`` -> ``":00"``, ``1680`` -> ``":28"`` (28 minutes),
|
|
33
|
+
``90`` -> ``":01:30"``, ``3661`` -> ``"1:01:01"``.
|
|
34
|
+
"""
|
|
35
|
+
hours, remainder = divmod(int(seconds), 3600)
|
|
36
|
+
minutes, secs = divmod(remainder, 60)
|
|
37
|
+
if hours:
|
|
38
|
+
return f"{hours}:{minutes:02d}:{secs:02d}"
|
|
39
|
+
if secs:
|
|
40
|
+
return f":{minutes:02d}:{secs:02d}"
|
|
41
|
+
return f":{minutes:02d}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _format_datetime64(ts: np.datetime64) -> str:
|
|
45
|
+
"""Render a datetime64 as ``YYYY-MM-DD HH:MM:SS`` (naive UTC convention)."""
|
|
46
|
+
dt: datetime = ts.astype("datetime64[s]").astype(datetime)
|
|
47
|
+
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
48
|
+
|
|
24
49
|
|
|
25
50
|
class MetricLoader:
|
|
26
51
|
"""
|
|
@@ -76,6 +101,9 @@ class MetricLoader:
|
|
|
76
101
|
self.internal_manager = internal_manager
|
|
77
102
|
self.source_profile_name = source_profile_name
|
|
78
103
|
self.query_template = QueryTemplate()
|
|
104
|
+
# Emitted at most once per loader instance (one instance per metric
|
|
105
|
+
# per run) - see _check_grid_alignment.
|
|
106
|
+
self._warned_grid_misalignment = False
|
|
79
107
|
|
|
80
108
|
def load(
|
|
81
109
|
self,
|
|
@@ -246,6 +274,7 @@ class MetricLoader:
|
|
|
246
274
|
timestamp_array, value_array = self._fill_gaps(
|
|
247
275
|
timestamp_array, value_array, from_date, to_date, interval_seconds
|
|
248
276
|
)
|
|
277
|
+
self._check_grid_alignment(original_timestamps, timestamp_array, interval_seconds)
|
|
249
278
|
|
|
250
279
|
# Realign query-provided seasonality to the gap-filled grid.
|
|
251
280
|
# Gap rows can appear ANYWHERE in the range and _fill_gaps also
|
|
@@ -423,6 +452,60 @@ class MetricLoader:
|
|
|
423
452
|
|
|
424
453
|
return full_timestamps, filled_values
|
|
425
454
|
|
|
455
|
+
def _check_grid_alignment(
|
|
456
|
+
self,
|
|
457
|
+
original_timestamps: np.ndarray,
|
|
458
|
+
grid_timestamps: np.ndarray,
|
|
459
|
+
interval_seconds: int,
|
|
460
|
+
) -> None:
|
|
461
|
+
"""Warn once if a non-empty query result landed entirely off the grid.
|
|
462
|
+
|
|
463
|
+
``_fill_gaps`` maps source rows onto the gap-filled grid by exact
|
|
464
|
+
timestamp match. If the query returns rows bucketed to a different
|
|
465
|
+
grid "phase" than ``loading_start_time`` establishes (e.g. rows at
|
|
466
|
+
:28 past the hour against a 10min grid phased at :00), NONE of them
|
|
467
|
+
match and the batch loads as silently 100% NULL - no error, no
|
|
468
|
+
partial data, just an empty-looking series. Partial alignment is not
|
|
469
|
+
the trap this guards against (some rows landing is normal, e.g. a
|
|
470
|
+
source with occasional off-grid duplicates), so this only fires when
|
|
471
|
+
the overlap is exactly zero. Observability only - does not change
|
|
472
|
+
what gets loaded.
|
|
473
|
+
"""
|
|
474
|
+
if self._warned_grid_misalignment:
|
|
475
|
+
return
|
|
476
|
+
if len(original_timestamps) == 0 or len(grid_timestamps) == 0:
|
|
477
|
+
return
|
|
478
|
+
if np.any(np.isin(original_timestamps, grid_timestamps)):
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
self._warned_grid_misalignment = True
|
|
482
|
+
|
|
483
|
+
grid_phase_seconds = int(
|
|
484
|
+
grid_timestamps[0].astype("datetime64[s]").astype(np.int64) % interval_seconds
|
|
485
|
+
)
|
|
486
|
+
source_offsets = (
|
|
487
|
+
original_timestamps.astype("datetime64[s]").astype(np.int64) % interval_seconds
|
|
488
|
+
)
|
|
489
|
+
offsets, counts = np.unique(source_offsets, return_counts=True)
|
|
490
|
+
source_phase_seconds = int(offsets[int(np.argmax(counts))])
|
|
491
|
+
|
|
492
|
+
sample_ts = original_timestamps[0]
|
|
493
|
+
nearest_ts = grid_timestamps[int(np.argmin(np.abs(grid_timestamps - sample_ts)))]
|
|
494
|
+
|
|
495
|
+
logger.warning(
|
|
496
|
+
"%s: query returned %d row(s) but none align with the metric's %s grid "
|
|
497
|
+
"(grid phase %s, source phase %s). e.g. source row %s vs nearest grid "
|
|
498
|
+
"slot %s. Bucket the query's timestamps (toStartOfInterval / time_bucket "
|
|
499
|
+
"/ date_trunc) or set loading_start_time to match the source phase.",
|
|
500
|
+
self.config.name,
|
|
501
|
+
len(original_timestamps),
|
|
502
|
+
self.config.interval,
|
|
503
|
+
_format_offset_seconds(grid_phase_seconds),
|
|
504
|
+
_format_offset_seconds(source_phase_seconds),
|
|
505
|
+
_format_datetime64(sample_ts),
|
|
506
|
+
_format_datetime64(nearest_ts),
|
|
507
|
+
)
|
|
508
|
+
|
|
426
509
|
def _extract_seasonality(
|
|
427
510
|
self,
|
|
428
511
|
timestamps: np.ndarray,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.63.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -110,7 +110,7 @@ Dynamic: license-file
|
|
|
110
110
|
- **Hybrid mode** — read metric SQL from a billed-per-query warehouse while all pipeline state lives in a cheap local database
|
|
111
111
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
112
112
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
113
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
113
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + five skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
114
114
|
|
|
115
115
|
## Installation
|
|
116
116
|
|
|
@@ -122,6 +122,7 @@ With database drivers:
|
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
124
|
pip install detectkit[clickhouse] # ClickHouse
|
|
125
|
+
pip install detectkit[duckdb] # DuckDB - single file, no server
|
|
125
126
|
pip install detectkit[all-db] # All databases
|
|
126
127
|
```
|
|
127
128
|
|
|
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
|
{detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.62.0 → detectkit-0.63.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.62.0 → detectkit-0.63.0}/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
|
|
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
|