detectkit 0.30.1__tar.gz → 0.31.1__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.30.1/detectkit.egg-info → detectkit-0.31.1}/PKG-INFO +15 -5
- {detectkit-0.30.1 → detectkit-0.31.1}/README.md +11 -2
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/__init__.py +1 -1
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/branding.py +9 -2
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/html_labeler.py +69 -7
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/cli.md +4 -2
- detectkit-0.31.1/detectkit/tuning/assets/tune.js +80 -0
- {detectkit-0.30.1 → detectkit-0.31.1/detectkit.egg-info}/PKG-INFO +15 -5
- {detectkit-0.30.1 → detectkit-0.31.1}/pyproject.toml +3 -2
- detectkit-0.30.1/detectkit/tuning/assets/tune.js +0 -51
- {detectkit-0.30.1 → detectkit-0.31.1}/LICENSE +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/MANIFEST.in +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/label_server.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/result.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/_output.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/main.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/config/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/config/profile.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/config/project_config.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/config/validator.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/core/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/core/interval.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/core/models.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/database/tables.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/tuning/html.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/tuning/server.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit/utils/stats.py +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/requirements.txt +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/setup.cfg +0 -0
- {detectkit-0.30.1 → detectkit-0.31.1}/setup.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.1
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Documentation, https://
|
|
7
|
+
Project-URL: Homepage, https://dtk.pipelab.dev
|
|
8
|
+
Project-URL: Documentation, https://dtk.pipelab.dev
|
|
9
9
|
Project-URL: Repository, https://github.com/alexeiveselov92/detectkit
|
|
10
|
+
Project-URL: Changelog, https://github.com/alexeiveselov92/detectkit/blob/main/CHANGELOG.md
|
|
10
11
|
Project-URL: Issues, https://github.com/alexeiveselov92/detectkit/issues
|
|
11
12
|
Keywords: monitoring,anomaly-detection,metrics,timeseries,alerting
|
|
12
13
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -66,14 +67,22 @@ Requires-Dist: psycopg2-binary>=2.9.0; extra == "integration"
|
|
|
66
67
|
Requires-Dist: pymysql>=1.0.0; extra == "integration"
|
|
67
68
|
Dynamic: license-file
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
<a href="https://dtk.pipelab.dev">
|
|
71
|
+
<picture>
|
|
72
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/detectkit-lockup-dark.svg">
|
|
73
|
+
<img src="assets/detectkit-lockup.svg" alt="detectkit" width="260">
|
|
74
|
+
</picture>
|
|
75
|
+
</a>
|
|
70
76
|
|
|
71
77
|
[](https://pypi.org/project/detectkit/)
|
|
72
78
|
[](https://pypi.org/project/detectkit/)
|
|
79
|
+
[](https://dtk.pipelab.dev)
|
|
73
80
|
|
|
74
81
|
**Metric monitoring with automatic anomaly detection.**
|
|
75
82
|
|
|
76
|
-
`detectkit` is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
83
|
+
[`detectkit`](https://dtk.pipelab.dev) is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
84
|
+
|
|
85
|
+
**Website, docs & live playground → [dtk.pipelab.dev](https://dtk.pipelab.dev)**
|
|
77
86
|
|
|
78
87
|
## Features
|
|
79
88
|
|
|
@@ -181,6 +190,7 @@ for r in results:
|
|
|
181
190
|
|
|
182
191
|
## Documentation
|
|
183
192
|
|
|
193
|
+
- **[dtk.pipelab.dev](https://dtk.pipelab.dev)** — full docs site, guides, reference, and a live in-browser playground
|
|
184
194
|
- [Getting Started](docs/getting-started/quickstart.md) — 5-minute quickstart
|
|
185
195
|
- [Configuration Guide](docs/guides/configuration.md) — all config options
|
|
186
196
|
- [Detectors Guide](docs/guides/detectors.md) — choosing the right detector
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<a href="https://dtk.pipelab.dev">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/detectkit-lockup-dark.svg">
|
|
4
|
+
<img src="assets/detectkit-lockup.svg" alt="detectkit" width="260">
|
|
5
|
+
</picture>
|
|
6
|
+
</a>
|
|
2
7
|
|
|
3
8
|
[](https://pypi.org/project/detectkit/)
|
|
4
9
|
[](https://pypi.org/project/detectkit/)
|
|
10
|
+
[](https://dtk.pipelab.dev)
|
|
5
11
|
|
|
6
12
|
**Metric monitoring with automatic anomaly detection.**
|
|
7
13
|
|
|
8
|
-
`detectkit` is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
14
|
+
[`detectkit`](https://dtk.pipelab.dev) is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
15
|
+
|
|
16
|
+
**Website, docs & live playground → [dtk.pipelab.dev](https://dtk.pipelab.dev)**
|
|
9
17
|
|
|
10
18
|
## Features
|
|
11
19
|
|
|
@@ -113,6 +121,7 @@ for r in results:
|
|
|
113
121
|
|
|
114
122
|
## Documentation
|
|
115
123
|
|
|
124
|
+
- **[dtk.pipelab.dev](https://dtk.pipelab.dev)** — full docs site, guides, reference, and a live in-browser playground
|
|
116
125
|
- [Getting Started](docs/getting-started/quickstart.md) — 5-minute quickstart
|
|
117
126
|
- [Configuration Guide](docs/guides/configuration.md) — all config options
|
|
118
127
|
- [Detectors Guide](docs/guides/detectors.md) — choosing the right detector
|
|
@@ -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.31.1"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -12,12 +12,19 @@ the brand avatar URL and name have a single source of truth.
|
|
|
12
12
|
# ``username`` and the email ``From`` display name).
|
|
13
13
|
BRAND_USERNAME = "detectkit"
|
|
14
14
|
|
|
15
|
+
# Public detectkit site — landing, docs, and the served brand assets. This is the
|
|
16
|
+
# **single source of truth** for the project's URL inside the package: the icon
|
|
17
|
+
# and alert-guide links below are derived from it, so if the domain ever changes
|
|
18
|
+
# only this line needs updating. (Docs/website Markdown and ``pyproject``
|
|
19
|
+
# metadata reference the URL directly and are updated in place.)
|
|
20
|
+
BRAND_SITE_URL = "https://dtk.pipelab.dev"
|
|
21
|
+
|
|
15
22
|
# Brand avatar served from the docs site. Slack/Mattermost render the webhook
|
|
16
23
|
# bot icon from a **raster** URL (an SVG is not rendered as a bot avatar), so
|
|
17
24
|
# this points at a PNG. Override per channel with ``icon_url`` (a custom image)
|
|
18
25
|
# or opt out of the avatar entirely with ``icon_emoji``. The PNG is generated by
|
|
19
26
|
# ``website/scripts/make-bot-icon.mjs`` and served from ``website/public/``.
|
|
20
|
-
BRAND_ICON_URL = "
|
|
27
|
+
BRAND_ICON_URL = f"{BRAND_SITE_URL}/bot-icon.png"
|
|
21
28
|
|
|
22
29
|
# Default "how to read this alert" link surfaced on every default-rendered alert,
|
|
23
30
|
# so non-operator stakeholders (PMs, analysts, on-call) seeing a notification can
|
|
@@ -26,5 +33,5 @@ BRAND_ICON_URL = "https://dtk.pipelab.dev/bot-icon.png"
|
|
|
26
33
|
# own runbook (or hide it) via ``ProjectConfig.alert_help_url`` — the resolved URL
|
|
27
34
|
# is stamped onto ``AlertData.help_url`` by the orchestrator. ``ALERT_GUIDE_LABEL``
|
|
28
35
|
# is the shared link text so every channel reads the same.
|
|
29
|
-
BRAND_ALERT_GUIDE_URL = "
|
|
36
|
+
BRAND_ALERT_GUIDE_URL = f"{BRAND_SITE_URL}/guides/reading-alerts/"
|
|
30
37
|
ALERT_GUIDE_LABEL = "How to read this alert"
|
|
@@ -141,7 +141,7 @@ _TEMPLATE = """<!doctype html>
|
|
|
141
141
|
box-shadow: var(--panel-shadow); }
|
|
142
142
|
.zoombar { display:flex; align-items:center; gap:8px; margin: 12px 0 6px; }
|
|
143
143
|
.rangelbl { margin-left: auto; color: var(--muted); font-size: 12px; font-family: var(--mono); }
|
|
144
|
-
canvas#ov { width: 100%; height:
|
|
144
|
+
canvas#ov { width: 100%; height: 78px; display:block; touch-action: none;
|
|
145
145
|
background: var(--term-bg); border: 1px solid var(--term-border); border-radius: 12px; cursor: grab;
|
|
146
146
|
box-shadow: var(--panel-shadow); }
|
|
147
147
|
.navhint { color: var(--faint); font-size: 12px; margin: 8px 2px 0; line-height: 1.55; }
|
|
@@ -256,7 +256,7 @@ PRELOAD.forEach(p => { const a = Date.parse(String(p.start).replace(' ','T')+'Z'
|
|
|
256
256
|
if (!isNaN(a) && !isNaN(b)) incidents.push({a: Math.min(a,b), b: Math.max(a,b), label: p.label || ''}); });
|
|
257
257
|
const c = document.getElementById('c'), ov = document.getElementById('ov');
|
|
258
258
|
const ctx = c.getContext('2d'), octx = ov.getContext('2d');
|
|
259
|
-
const M = {l:56, r:16, t:14, b:30}, OM = {l:56, r:16, t:8, b:
|
|
259
|
+
const M = {l:56, r:16, t:14, b:30}, OM = {l:56, r:16, t:8, b:20};
|
|
260
260
|
let dpr = 1, hover = null, dragging = null, ovAct = null;
|
|
261
261
|
// selObj: the currently selected incident (object ref, survives re-sorting);
|
|
262
262
|
// hoverRow/hoverDel: list-row / ✕-handle hover targets; threshold-capture state.
|
|
@@ -286,6 +286,42 @@ const fmtTs = ts => new Date(ts).toISOString().slice(0,19).replace('T',' ');
|
|
|
286
286
|
const fmtTick = (ts, sp) => { const s = new Date(ts).toISOString();
|
|
287
287
|
return sp < 2*86400000 ? s.slice(5,16).replace('T',' ') : s.slice(5,10); };
|
|
288
288
|
const fmtVal = v => { const a = Math.abs(v); return a>=1000 ? v.toFixed(0) : a>=10 ? v.toFixed(1) : v.toFixed(2); };
|
|
289
|
+
// Adaptive, UTC-aligned "nice" time gridlines so each point's real time is
|
|
290
|
+
// readable at a glance — placed at round calendar boundaries (the hour, day,
|
|
291
|
+
// month, year) rather than arbitrary even splits. Returns {ticks, step}.
|
|
292
|
+
const MS_S=1000, MS_MIN=60*MS_S, MS_H=60*MS_MIN, MS_D=24*MS_H;
|
|
293
|
+
const TICK_STEPS=[MS_S,2*MS_S,5*MS_S,10*MS_S,15*MS_S,30*MS_S,
|
|
294
|
+
MS_MIN,2*MS_MIN,5*MS_MIN,10*MS_MIN,15*MS_MIN,30*MS_MIN,
|
|
295
|
+
MS_H,2*MS_H,3*MS_H,6*MS_H,12*MS_H, MS_D,2*MS_D,3*MS_D,5*MS_D,7*MS_D,14*MS_D];
|
|
296
|
+
function niceTimeTicks(lo, hi, target) {
|
|
297
|
+
const span=Math.max(hi-lo, 1);
|
|
298
|
+
if (span > target*28*MS_D) { // month/year calendar ticks for long spans
|
|
299
|
+
const mSteps=[1,2,3,6,12,24,36,60,120,240]; let stepM=mSteps[mSteps.length-1];
|
|
300
|
+
for (const m of mSteps) { if (span/(m*30.44*MS_D) <= target) { stepM=m; break; } }
|
|
301
|
+
const d=new Date(lo); let y=d.getUTCFullYear(), mo=d.getUTCMonth();
|
|
302
|
+
if (stepM>=12) { const ys=stepM/12; y=Math.floor(y/ys)*ys; mo=0; }
|
|
303
|
+
else { mo=Math.floor(mo/stepM)*stepM; }
|
|
304
|
+
const ticks=[]; let t=Date.UTC(y,mo,1);
|
|
305
|
+
while (t<=hi) { if (t>=lo) ticks.push(t); mo+=stepM; y+=Math.floor(mo/12); mo%=12; t=Date.UTC(y,mo,1); }
|
|
306
|
+
return {ticks, step: stepM*30*MS_D};
|
|
307
|
+
}
|
|
308
|
+
let step=TICK_STEPS[TICK_STEPS.length-1];
|
|
309
|
+
for (const s of TICK_STEPS) { if (span/s <= target) { step=s; break; } }
|
|
310
|
+
let first;
|
|
311
|
+
if (step % MS_D === 0) { const d=new Date(lo); // align day-multiples to UTC midnight
|
|
312
|
+
first=Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
313
|
+
const k=step/MS_D, dayNo=Math.round(first/MS_D); first += ((k - (dayNo % k)) % k) * MS_D;
|
|
314
|
+
} else { first=Math.ceil(lo/step)*step; }
|
|
315
|
+
const ticks=[]; for (let t=first; t<=hi; t+=step) if (t>=lo) ticks.push(t);
|
|
316
|
+
return {ticks, step};
|
|
317
|
+
}
|
|
318
|
+
// Step-aware label: year / month / day / minute / second precision.
|
|
319
|
+
const fmtAxis = (ts, step) => { const s=new Date(ts).toISOString();
|
|
320
|
+
if (step >= 320*MS_D) return s.slice(0,4);
|
|
321
|
+
if (step >= 26*MS_D) return s.slice(0,7);
|
|
322
|
+
if (step >= MS_D) return s.slice(5,10);
|
|
323
|
+
if (step >= MS_H) return s.slice(5,16).replace('T',' ');
|
|
324
|
+
return s.slice(5,19).replace('T',' '); };
|
|
289
325
|
function fmtDur(ms) { const m = Math.round(ms/60000); if (m<60) return m+'m';
|
|
290
326
|
const h = Math.floor(m/60), mm = m%60; if (h<24) return h+'h'+(mm?(' '+mm+'m'):'');
|
|
291
327
|
const d = Math.floor(h/24), hh = h%24; return d+'d'+(hh?(' '+hh+'h'):''); }
|
|
@@ -399,10 +435,16 @@ function draw() {
|
|
|
399
435
|
ctx.strokeStyle='rgba(255,255,255,0.05)'; ctx.lineWidth=1*dpr;
|
|
400
436
|
ctx.beginPath(); ctx.moveTo(M.l*dpr,yy); ctx.lineTo(c.width-M.r*dpr,yy); ctx.stroke();
|
|
401
437
|
ctx.fillStyle='#6e675b'; ctx.textAlign='right'; ctx.fillText(fmtVal(v),(M.l-8)*dpr,yy); }
|
|
438
|
+
// Adaptive vertical time gridlines + round-boundary labels, so a point's place
|
|
439
|
+
// in real time reads off the grid (not only by chasing the cursor).
|
|
402
440
|
ctx.textBaseline = 'top';
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
ctx.
|
|
441
|
+
const xtk=niceTimeTicks(viewMin, viewMax, 7);
|
|
442
|
+
xtk.ticks.forEach(ts => { const xx=px(ts);
|
|
443
|
+
ctx.strokeStyle='rgba(255,255,255,0.05)'; ctx.lineWidth=1*dpr;
|
|
444
|
+
ctx.beginPath(); ctx.moveTo(xx, M.t*dpr); ctx.lineTo(xx, c.height-M.b*dpr); ctx.stroke();
|
|
445
|
+
ctx.fillStyle='#6e675b';
|
|
446
|
+
ctx.textAlign = xx < (M.l+24)*dpr ? 'left' : xx > c.width-(M.r+24)*dpr ? 'right' : 'center';
|
|
447
|
+
ctx.fillText(fmtAxis(ts, xtk.step), xx, c.height-(M.b-8)*dpr); });
|
|
406
448
|
ctx.save(); ctx.beginPath(); ctx.rect(M.l*dpr, M.t*dpr, plotW(), plotH()); ctx.clip();
|
|
407
449
|
// Threshold-capture preview bands (amber), under the committed incident bands.
|
|
408
450
|
if (thMode) { const val=thEff(); if (val!==null) thRuns().forEach(r => {
|
|
@@ -508,20 +550,40 @@ function drawHover() {
|
|
|
508
550
|
|
|
509
551
|
function drawOverview() {
|
|
510
552
|
octx.clearRect(0,0,ov.width,ov.height);
|
|
553
|
+
const otk=niceTimeTicks(tmin, tmax, 5), plotBot=ov.height-OM.b*dpr;
|
|
511
554
|
octx.save(); octx.beginPath(); octx.rect(OM.l*dpr, OM.t*dpr, ovWd(), ovHt()); octx.clip();
|
|
512
|
-
|
|
513
|
-
|
|
555
|
+
// faint vertical time gridlines behind the series
|
|
556
|
+
octx.strokeStyle='rgba(201,194,180,0.10)'; octx.lineWidth=1*dpr;
|
|
557
|
+
otk.ticks.forEach(ts => { const xx=ovpx(ts);
|
|
558
|
+
octx.beginPath(); octx.moveTo(xx, OM.t*dpr); octx.lineTo(xx, plotBot); octx.stroke(); });
|
|
514
559
|
drawSeries(octx, ovpx, ovpy, tmin, tmax, OM.l*dpr, ovWd(), 'rgba(209,91,54,0.7)', 1.1);
|
|
515
560
|
octx.restore();
|
|
516
561
|
const vx0=ovpx(viewMin), vx1=ovpx(viewMax);
|
|
562
|
+
// dim the out-of-view region (plot area only — leaves the label band clean)
|
|
517
563
|
octx.fillStyle='rgba(27,25,22,0.55)';
|
|
518
564
|
octx.fillRect(OM.l*dpr, OM.t*dpr, vx0-OM.l*dpr, ovHt());
|
|
519
565
|
octx.fillRect(vx1, OM.t*dpr, (ov.width-OM.r*dpr)-vx1, ovHt());
|
|
566
|
+
// incident markers, full-strength on top of the dim so every incident stays
|
|
567
|
+
// visible to navigate to — min width so a single-point/short span isn't
|
|
568
|
+
// sub-pixel on the full-span strip; the selected one is highlighted.
|
|
569
|
+
incidents.forEach(iv => { const x0=ovpx(iv.a), w=Math.max(ovpx(iv.b)-x0, 3*dpr);
|
|
570
|
+
octx.fillStyle = (iv===selObj) ? 'rgba(214,50,50,0.97)' : 'rgba(214,50,50,0.72)';
|
|
571
|
+
octx.fillRect(x0, OM.t*dpr, w, ovHt()); });
|
|
572
|
+
// current-view window frame + grab handles
|
|
520
573
|
octx.fillStyle='rgba(245,241,232,0.06)'; octx.fillRect(vx0, OM.t*dpr, vx1-vx0, ovHt());
|
|
521
574
|
octx.strokeStyle='#d15b36'; octx.lineWidth=1.5*dpr;
|
|
522
575
|
octx.strokeRect(vx0, OM.t*dpr+1, vx1-vx0, ovHt()-2);
|
|
523
576
|
octx.fillStyle='#d15b36'; const hy=OM.t*dpr+ovHt()/2-9*dpr;
|
|
524
577
|
octx.fillRect(vx0-2*dpr, hy, 4*dpr, 18*dpr); octx.fillRect(vx1-2*dpr, hy, 4*dpr, 18*dpr);
|
|
578
|
+
// absolute-time anchors under the strip, so a point's real time is readable
|
|
579
|
+
// across the whole series without hovering.
|
|
580
|
+
octx.font=(10*dpr)+'px ui-monospace, monospace'; octx.textBaseline='top';
|
|
581
|
+
otk.ticks.forEach(ts => { const xx=ovpx(ts);
|
|
582
|
+
octx.strokeStyle='rgba(201,194,180,0.25)'; octx.lineWidth=1*dpr;
|
|
583
|
+
octx.beginPath(); octx.moveTo(xx, plotBot); octx.lineTo(xx, plotBot+3*dpr); octx.stroke();
|
|
584
|
+
octx.fillStyle='#6e675b';
|
|
585
|
+
octx.textAlign = xx < (OM.l+26)*dpr ? 'left' : xx > ov.width-(OM.r+26)*dpr ? 'right' : 'center';
|
|
586
|
+
octx.fillText(fmtAxis(ts, otk.step), xx, plotBot+5*dpr); });
|
|
525
587
|
}
|
|
526
588
|
|
|
527
589
|
const tsAt = clientX => { const r=c.getBoundingClientRect();
|
|
@@ -78,8 +78,10 @@ Full reference: `autotune.md`.
|
|
|
78
78
|
The **manual, interactive** sibling of `dtk autotune`. Opens a localhost browser
|
|
79
79
|
view of the metric's **real** persisted series and lets you turn the detector's
|
|
80
80
|
knobs (type, threshold, window, recency weighting + half-life, detrend, smoothing,
|
|
81
|
-
seasonality
|
|
82
|
-
and flagged anomalies **recompute live**.
|
|
81
|
+
**seasonality groups**, alert `consecutive_anomalies`) while the confidence band
|
|
82
|
+
and flagged anomalies **recompute live**. The chart is **zoomable** (scroll/drag +
|
|
83
|
+
a navigator strip) and a **"Points shown"** slider trims the active sample to speed
|
|
84
|
+
up recompute on a long metric. Clicking **Apply** writes the chosen
|
|
83
85
|
config back into the metric YAML **in place** (autotune, by contrast, writes a new
|
|
84
86
|
`__tuned_<id>.yml` and never edits the original). Reads the metric's loaded
|
|
85
87
|
datapoints (run `dtk run --steps load` first if empty); the selector must resolve
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";(()=>{var nt={"--term-bg":"#211e1a","--clay":"#d15b36","--st-anomaly":"#d63232","--st-recovery":"#36a64f","--st-nodata":"#f0ad4e","--st-error":"#5a7a8c","--faint":"#9a9384","--muted":"#6e675b","--border":"#332f29","--term-border":"#332f29"};function O(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()||nt[t]||"#888"}function ot(t){let r=t.replace("#","").trim();r.length===3&&(r=r[0]+r[0]+r[1]+r[1]+r[2]+r[2]);let u=parseInt(r,16);return r.length!==6||Number.isNaN(u)?[209,91,54]:[u>>16&255,u>>8&255,u&255]}function R(t,r){let[u,e,d]=ot(t);return`rgba(${u},${e},${d},${r})`}function Be(t){let r=Math.max(1,window.devicePixelRatio||1),u=t.clientWidth||t.offsetWidth||0,e=t.clientHeight||t.offsetHeight||0;return t.width=Math.round(u*r),t.height=Math.round(e*r),r}var He=Number.isFinite;function ze(t,r,u,e,d,i,p,s,m,x,f,k){let z=r.length,S=Math.max(1,Math.round(p)),A=d-e||1,ie=0;for(let W=0;W<z;W++){let N=u[W];!He(N)||r[W]<e||r[W]>d||ie++}if(t.strokeStyle=x,t.lineWidth=f*k,t.lineJoin="round",t.beginPath(),ie<=S){let W=!1;for(let N=0;N<z;N++){let D=u[N],T=r[N];if(!He(D)||T<e||T>d){W=!1;continue}let E=s(T),I=m(D);W?t.lineTo(E,I):(t.moveTo(E,I),W=!0)}}else{let W=new Array(S).fill(null),N=new Array(S).fill(null);for(let T=0;T<z;T++){let E=u[T],I=r[T];if(!He(E)||I<e||I>d)continue;let P=Math.floor((I-e)/A*(S-1));P=P<0?0:P>S-1?S-1:P,(W[P]===null||E<W[P])&&(W[P]=E),(N[P]===null||E>N[P])&&(N[P]=E)}let D=!1;for(let T=0;T<S;T++){if(N[T]===null){D=!1;continue}let E=i+T,I=m(N[T]),P=m(W[T]);D?t.lineTo(E,I):(t.moveTo(E,I),D=!0),t.lineTo(E,P)}}t.stroke()}function je(t){let r=Math.abs(t);return r>=1e3?t.toFixed(0):r>=10?t.toFixed(1):r>=1?t.toFixed(2):t.toFixed(3)}function Ye(t,r){let u=new Date(t).toISOString();return r<2*864e5?u.slice(5,16).replace("T"," "):u.slice(5,10)}function Ue(t,r,u){return{left:r.l*u,top:r.t*u,right:t.width-r.r*u,bottom:t.height-r.b*u}}function Ge(t,r,u,e,d,i,p){let s=Ue(r,u,e),m=Math.max(s.left,Math.min(d(i),s.right));m<=s.left+.5||(t.save(),t.fillStyle="rgba(17,15,13,0.42)",t.fillRect(s.left,s.top,m-s.left,s.bottom-s.top),t.strokeStyle=R(O("--faint"),.7),t.lineWidth=1*e,t.setLineDash([4*e,4*e]),t.beginPath(),t.moveTo(m,s.top),t.lineTo(m,s.bottom),t.stroke(),t.setLineDash([]),t.fillStyle=R(O("--faint"),.95),t.font=`${10*e}px ui-monospace, monospace`,t.textAlign="left",t.textBaseline="top",t.fillText(p,m+6*e,s.top+5*e),t.restore())}function Ve(t,r,u,e,d,i,p){let s=Ue(r,u,e),m=5*e;t.save();for(let x=0;x<i.length;x++){let f=i[x],k=d(f.t);if(k<s.left-1||k>s.right+1)continue;let z=p(f.kind);t.strokeStyle=R(z,.45),t.lineWidth=1*e,t.beginPath(),t.moveTo(k,s.top),t.lineTo(k,s.bottom),t.stroke(),t.fillStyle=z,t.beginPath(),t.moveTo(k-m,s.top),t.lineTo(k+m,s.top),t.lineTo(k,s.top+m*1.4),t.closePath(),t.fill()}t.restore()}var rt={mad:1,zscore:2,iqr:4};function it(t,r){let u=t.seasonalityData;if(!u||u.length===0)return 0;let e=0;for(let d of r){let i=new Set;for(let p of u)i.add(d.map(s=>{var m;return String((m=p==null?void 0:p[s])!=null?m:"")}).join("|"));e=Math.max(e,i.size)}return e}function Ke(t,r){let u=t.timestamps.length,e=Math.max(r.minSamples,rt[r.type]);if(r.smoothing==="sma"&&(e=Math.max(e,r.smoothingWindow-1)),r.smoothing==="ema"&&(e=Math.max(e,Math.ceil(5/r.smoothingAlpha))),r.inputType!=="values"&&(e=Math.max(e,1)),r.seasonalityComponents!==null&&r.seasonalityComponents.length>0&&Array.isArray(t.seasonalityData)&&t.seasonalityData.length>0){let i=it(t,r.seasonalityComponents);if(i>0){let p=r.minSamplesPerGroup*i;r.windowSize>=p&&(e=Math.max(e,p))}}return Math.min(e,u)}var C={l:52,r:14,t:14,b:26},Ie=46,Xe=10,st=13,te=Number.isFinite,ce=1e3,ue=60*ce,de=60*ue,q=24*de,Oe=[ce,2*ce,5*ce,10*ce,15*ce,30*ce,ue,2*ue,5*ue,10*ue,15*ue,30*ue,de,2*de,3*de,6*de,12*de,q,2*q,3*q,5*q,7*q,14*q];function Je(t,r,u){let e=Math.max(r-t,1);if(e>u*28*q){let s=[1,2,3,6,12,24,36,60,120,240],m=s[s.length-1];for(let A of s)if(e/(A*30.44*q)<=u){m=A;break}let x=new Date(t),f=x.getUTCFullYear(),k=x.getUTCMonth();if(m>=12){let A=m/12;f=Math.floor(f/A)*A,k=0}else k=Math.floor(k/m)*m;let z=[],S=Date.UTC(f,k,1);for(;S<=r;)S>=t&&z.push(S),k+=m,f+=Math.floor(k/12),k%=12,S=Date.UTC(f,k,1);return{ticks:z,step:m*30*q}}let d=Oe[Oe.length-1];for(let s of Oe)if(e/s<=u){d=s;break}let i;if(d%q===0){let s=new Date(t);i=Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate());let m=d/q,x=Math.round(i/q);i+=(m-x%m)%m*q}else i=Math.ceil(t/d)*d;let p=[];for(let s=i;s<=r;s+=d)s>=t&&p.push(s);return{ticks:p,step:d}}function Qe(t,r){let u=new Date(t).toISOString();return r>=320*q?u.slice(0,4):r>=26*q?u.slice(0,7):r>=q?u.slice(5,10):r>=de?u.slice(5,16).replace("T"," "):u.slice(5,19).replace("T"," ")}function Ze(t,r={}){let u=t.getContext("2d");if(!u)throw new Error("chart: 2D context unavailable");let e=u,d=!!r.navigable,i=1,p=null,s=-1,m=0,x=0,f=1,k=0,z=1,S=0,A=1,ie="";function W(){i=Be(t)}let N=()=>d?(Xe+Ie)*i:0,D=()=>t.width-(C.l+C.r)*i,T=()=>t.height-(C.t+C.b)*i-N(),E=()=>d?S:x,I=()=>d?A:f,P=()=>I()-E()||1,Y=()=>f-x||1,$=n=>C.l*i+(n-E())/P()*D(),G=n=>t.height-C.b*i-N()-(n-k)/(z-k||1)*T(),V=()=>t.height-Ie*i,K=()=>(Ie-st)*i,ne=()=>t.width-(C.l+C.r)*i,Q=n=>C.l*i+(n-x)/Y()*ne(),Ae=n=>V()+K()-(n-k)/(z-k||1)*K(),X=(n,o,a)=>Math.max(o,Math.min(a,n)),oe=()=>{let n=p==null?void 0:p.series,o=n?n.timestamps.length:0,a=o>1?Y()/(o-1):1e3;return Math.max(a*8,1e3)};function J(n,o){let a=o-n,c=oe();if(a<c){let l=(n+o)/2;n=l-c/2,o=l+c/2,a=c}a>=Y()&&(n=x,o=f),n<x&&(o+=x-n,n=x),o>f&&(n-=o-f,o=f),S=X(n,x,f),A=X(o,x,f),re()}let De=n=>{let o=(n-C.l*i)/(D()||1);return E()+X(o,0,1)*P()},se=n=>{let o=(n-C.l*i)/(ne()||1);return x+X(o,0,1)*Y()};function Pe(n){let o=p==null?void 0:p.series;if(!o||o.timestamps.length===0)return-1;let a=(n-C.l*i)/(D()||1),c=E()+a*P(),l=o.timestamps,h=0,y=l.length-1;for(;h<y;){let _=h+y>>1;l[_]<c?h=_+1:y=_}return h>0&&c-l[h-1]<l[h]-c&&(h-=1),h}function Le(n,o){let a=n.timestamps;if(x=a[0],f=a[a.length-1],d){let y=`${a.length}:${x}:${f}`;y!==ie?(ie=y,S=x,A=f):(S=X(S,x,f),A=X(Math.max(A,S+oe()),x,f))}let c=1/0,l=-1/0;for(let y of n.values)te(y)&&(y<c&&(c=y),y>l&&(l=y));for(let y of o)y.scored&&(te(y.lower)&&y.lower<c&&(c=y.lower),te(y.upper)&&y.upper>l&&(l=y.upper));(!te(c)||!te(l))&&(c=0,l=1),l<=c&&(l=c+1);let h=(l-c)*.06;k=c-h,z=l+h}function ae(n,o,a){let c=p.series.timestamps;ze(e,c,n,E(),I(),C.l*i,D(),$,G,o,a,i)}function ge(n,o){let a=[],c=-1;for(let l=Math.max(0,o);l<n.length;l++){let h=n[l];h.scored&&te(h.lower)&&te(h.upper)?c===-1&&(c=l):c!==-1&&(a.push([c,l-1]),c=-1)}return c!==-1&&a.push([c,n.length-1]),a}function Z(){if(m=0,!p||t.width===0||t.height===0)return;let{series:n,scored:o,params:a,alerts:c}=p;if(n.timestamps.length===0){e.fillStyle=O("--term-bg"),e.fillRect(0,0,t.width,t.height);return}let l=O("--clay"),h=O("--st-anomaly"),y=O("--faint"),_=O("--muted");e.fillStyle=O("--term-bg"),e.fillRect(0,0,t.width,t.height),e.font=`${11*i}px ui-monospace, 'JetBrains Mono', monospace`,e.textBaseline="middle";for(let g=0;g<=4;g++){let v=k+(z-k)*g/4,w=G(v);e.strokeStyle=R(y,.1),e.lineWidth=1*i,e.beginPath(),e.moveTo(C.l*i,w),e.lineTo(t.width-C.r*i,w),e.stroke(),e.fillStyle=_,e.textAlign="right",e.fillText(je(v),(C.l-8)*i,w)}e.textBaseline="top";let F=t.height-C.b*i-N();if(d){let g=Je(E(),I(),7);for(let v of g.ticks){let w=$(v);e.strokeStyle=R(y,.1),e.lineWidth=1*i,e.beginPath(),e.moveTo(w,C.t*i),e.lineTo(w,F),e.stroke(),e.fillStyle=_,e.textAlign=w<(C.l+24)*i?"left":w>t.width-(C.r+24)*i?"right":"center",e.fillText(Qe(v,g.step),w,F+7*i)}}else{let g=P();for(let v=0;v<=5;v++){let w=x+g*v/5,U=$(w);e.fillStyle=_,e.textAlign=v===0?"left":v===5?"right":"center",e.fillText(Ye(w,g),U,F+7*i)}}e.save(),e.beginPath(),e.rect(C.l*i,C.t*i,D(),T()),e.clip();let B=o.length,M=Math.min(Ke(n,a),B),L=M<B?n.timestamps[M]:void 0,j=ge(o,M);e.fillStyle=R(l,.13);for(let[g,v]of j){e.beginPath(),e.moveTo($(o[g].timestamp),G(o[g].upper));for(let w=g+1;w<=v;w++)e.lineTo($(o[w].timestamp),G(o[w].upper));for(let w=v;w>=g;w--)e.lineTo($(o[w].timestamp),G(o[w].lower));e.closePath(),e.fill()}e.strokeStyle=R(l,.4),e.lineWidth=1*i;for(let[g,v]of j)for(let w of["upper","lower"]){e.beginPath();for(let U=g;U<=v;U++){let Ce=$(o[U].timestamp),Te=G(o[U][w]);U===g?e.moveTo(Ce,Te):e.lineTo(Ce,Te)}e.stroke()}e.strokeStyle=R(y,.55),e.lineWidth=1*i,e.setLineDash([3*i,3*i]);for(let[g,v]of j){e.beginPath();for(let w=g;w<=v;w++){let U=o[w].center;if(!te(U))continue;let Ce=$(o[w].timestamp),Te=G(U);w===g?e.moveTo(Ce,Te):e.lineTo(Ce,Te)}e.stroke()}if(e.setLineDash([]),a.smoothing!=="none"){ae(n.values,R(l,.28),1.25);let g=o.map(v=>v.processedValue);ae(g,l,1.6)}else ae(n.values,l,1.5);for(let g=M;g<B;g++){let v=o[g];if(!v.scored||!te(v.value))continue;let w=$(v.timestamp),U=G(v.value);v.isAnomaly?(e.fillStyle=R(h,.18),e.beginPath(),e.arc(w,U,6*i,0,Math.PI*2),e.fill(),e.fillStyle=h,e.beginPath(),e.arc(w,U,3*i,0,Math.PI*2),e.fill()):n.truthAnomaly[g]&&(e.strokeStyle=R(_,.7),e.lineWidth=1.25*i,e.beginPath(),e.arc(w,U,3.5*i,0,Math.PI*2),e.stroke())}L!==void 0&&Ge(e,t,C,i,$,L,"detection at full power \u2192"),s>=0&&s<o.length&&me(s,a.windowSize,o,n,y),c&&c.length&&Ve(e,t,C,i,$,c,g=>g==="anomaly"?O("--st-anomaly"):g==="recovery"?O("--st-recovery"):O("--st-nodata")),e.restore(),d&&ve(n,c,l,y,_)}function ve(n,o,a,c,l){let h=V(),y=h+K(),_=C.l*i,F=t.width-C.r*i,B=Je(x,f,5);e.save(),e.beginPath(),e.rect(_,h,ne(),K()),e.clip(),e.strokeStyle=R(c,.1),e.lineWidth=1*i;for(let g of B.ticks){let v=Q(g);e.beginPath(),e.moveTo(v,h),e.lineTo(v,y),e.stroke()}ze(e,n.timestamps,n.values,x,f,_,ne(),Q,Ae,R(a,.7),1.1,i),e.restore();let M=Q(S),L=Q(A);if(e.fillStyle="rgba(27,25,22,0.55)",e.fillRect(_,h,M-_,K()),e.fillRect(L,h,F-L,K()),o&&o.length){let g=O("--st-anomaly");e.fillStyle=R(g,.85);for(let v of o){let w=Q(v.t);e.fillRect(w-1*i,h,2*i,K())}}e.fillStyle="rgba(245,241,232,0.06)",e.fillRect(M,h,L-M,K()),e.strokeStyle=a,e.lineWidth=1.5*i,e.strokeRect(M,h+1,L-M,K()-2),e.fillStyle=a;let j=h+K()/2-8*i;e.fillRect(M-2*i,j,4*i,16*i),e.fillRect(L-2*i,j,4*i,16*i),e.font=`${10*i}px ui-monospace, monospace`,e.textBaseline="top",e.fillStyle=l;for(let g of B.ticks){let v=Q(g);e.strokeStyle=R(c,.25),e.lineWidth=1*i,e.beginPath(),e.moveTo(v,y),e.lineTo(v,y+3*i),e.stroke(),e.textAlign=v<(C.l+26)*i?"left":v>t.width-(C.r+26)*i?"right":"center",e.fillText(Qe(g,B.step),v,y+5*i)}}function me(n,o,a,c,l){let h=c.timestamps,y=Math.max(0,n-o),_=n-1,F=C.t*i,B=T();if(_>=y){let j=c.intervalSeconds*1e3/P()*D()*.5,g=$(h[y])-j,v=$(h[_])+j;e.fillStyle="rgba(255,255,255,0.05)",e.fillRect(g,F,v-g,B),e.strokeStyle=R(l,.5),e.lineWidth=1*i,e.beginPath(),e.moveTo(g,F),e.lineTo(g,F+B),e.moveTo(v,F),e.lineTo(v,F+B),e.stroke()}let M=$(h[n]);e.strokeStyle=R(l,.85),e.lineWidth=1*i,e.setLineDash([2*i,2*i]),e.beginPath(),e.moveTo(M,F),e.lineTo(M,F+B),e.stroke(),e.setLineDash([]);let L=a[n];if(L.scored&&te(L.lower)&&te(L.upper)){let j=5*i;e.strokeStyle=R(O("--clay"),.85),e.lineWidth=1.5*i;for(let g of[L.lower,L.upper]){let v=G(g);e.beginPath(),e.moveTo(M-j,v),e.lineTo(M+j,v),e.stroke()}}if(te(L.value)){let j=G(L.value);e.fillStyle=O("--term-bg"),e.beginPath(),e.arc(M,j,4*i,0,Math.PI*2),e.fill(),e.strokeStyle=L.isAnomaly?O("--st-anomaly"):O("--clay"),e.lineWidth=2*i,e.beginPath(),e.arc(M,j,4*i,0,Math.PI*2),e.stroke()}}function re(){m===0&&(m=requestAnimationFrame(Z))}function fe(){var a;if(!r.onHover||!p)return;if(s<0){r.onHover(null);return}let n=(a=p.scored[s])!=null?a:null,o={index:s,point:n,windowStart:Math.max(0,s-p.params.windowSize),windowEnd:s-1};r.onHover(o)}let ee=null,H=null,le=n=>{let o=t.getBoundingClientRect();return{x:(n.clientX-o.left)*i,y:(n.clientY-o.top)*i}},xe=n=>d&&n>=V(),ye=n=>{let o=Q(S),a=Q(A),c=8*i;return Math.abs(n-o)<=c?"l":Math.abs(n-a)<=c?"r":n>o&&n<a?"move":"out"};function pe(n){if(!p)return;let{x:o,y:a}=le(n);if(ee||H)return;if(xe(a)){s!==-1&&(s=-1,fe(),re());let l=ye(o);t.style.cursor=l==="l"||l==="r"?"ew-resize":l==="move"?"grab":"pointer";return}d&&(t.style.cursor="grab");let c=Pe(o);c!==s&&(s=c,fe(),re())}function we(){s!==-1&&(s=-1,fe(),re())}function ke(n){if(!d||!p)return;let{x:o,y:a}=le(n);if(a>=V()){let c=ye(o);if(c==="l")H={type:"l",grab:0,vMin:S,vMax:A};else if(c==="r")H={type:"r",grab:0,vMin:S,vMax:A};else if(c==="move")H={type:"move",grab:se(o),vMin:S,vMax:A};else{let l=se(o),h=P();J(l-h/2,l+h/2),H={type:"move",grab:l,vMin:S,vMax:A}}t.style.cursor="grabbing",n.preventDefault()}else a>C.t*i&&a<V()-Xe*i&&(ee={x:o,vMin:S,vMax:A},t.style.cursor="grabbing",n.preventDefault())}function Ee(n){if(!p)return;let{x:o}=le(n);if(ee){let a=(o-ee.x)*(ee.vMax-ee.vMin)/(D()||1);J(ee.vMin-a,ee.vMax-a)}else if(H){let a=se(o);H.type==="l"?J(Math.min(a,A-oe()),A):H.type==="r"?J(S,Math.max(a,S+oe())):J(H.vMin+(a-H.grab),H.vMax+(a-H.grab))}}function he(){(ee||H)&&(ee=null,H=null,t.style.cursor="grab")}function be(n){if(!d||!p)return;n.preventDefault();let{x:o,y:a}=le(n),c=X(P()*Math.pow(1.0015,n.deltaY),oe(),Y());if(a>=V()){let l=se(o);J(l-c/2,l+c/2)}else{let l=De(o),h=(l-S)/(P()||1);J(l-h*c,l-h*c+c)}}function Se(){d&&J(x,f)}t.addEventListener("mousemove",pe),t.addEventListener("mouseleave",we),d&&(t.addEventListener("mousedown",ke),t.addEventListener("wheel",be,{passive:!1}),t.addEventListener("dblclick",Se),window.addEventListener("mousemove",Ee),window.addEventListener("mouseup",he));function Me(n){p=n,Le(n.series,n.scored),s>=n.series.timestamps.length&&(s=-1),re()}function Ne(){W(),p&&(Le(p.series,p.scored),re())}function _e(){t.removeEventListener("mousemove",pe),t.removeEventListener("mouseleave",we),d&&(t.removeEventListener("mousedown",ke),t.removeEventListener("wheel",be),t.removeEventListener("dblclick",Se),window.removeEventListener("mousemove",Ee),window.removeEventListener("mouseup",he)),m!==0&&cancelAnimationFrame(m),m=0,p=null}return W(),{render:Me,resize:Ne,destroy:_e}}var at={mad:3,zscore:3,iqr:1.5},lt={mad:10,zscore:3,iqr:4},ct="dtk-tune";function b(t,r,u){let e=document.createElement(t);return r&&(e.className=r),u!=null&&(e.textContent=u),e}function $e(t,r){let u=b("label","dtk-ctl-label",t);if(r){u.title=r;let e=b("span","dtk-ctl-info","\u24D8");e.title=r,u.appendChild(document.createTextNode(" ")),u.appendChild(e)}return u}function We(t,r,u,e,d){let i=b("div","dtk-ctl");i.appendChild($e(t,d));let p=b("div","dtk-seg"),s=u,m=[],x=()=>{m.forEach(f=>f.classList.toggle("on",f.dataset.v===s))};return r.forEach(f=>{let k=b("button","dtk-seg-btn",f.label);k.type="button",k.dataset.v=f.value,k.onclick=()=>{s=f.value,x(),e(s)},m.push(k),p.appendChild(k)}),x(),i.appendChild(p),{row:i,get:()=>s,set:f=>{s=f,x()}}}function Re(t,r,u){var x;let e=b("div","dtk-ctl"),d=b("div","dtk-ctl-head"),i=$e(t,r.hint),p=b("span","dtk-ctl-val"),s=(x=r.fmt)!=null?x:(f=>String(f));d.appendChild(i),d.appendChild(p),e.appendChild(d);let m=b("input","dtk-range");return m.type="range",m.min=String(r.min),m.max=String(r.max),m.step=String(r.step),m.value=String(r.value),p.textContent=s(r.value),m.oninput=()=>{let f=Number(m.value);p.textContent=s(f),u(f)},e.appendChild(m),{row:e,get:()=>Number(m.value),setMax:f=>{m.max=String(f),Number(m.value)>f&&(m.value=String(f),p.textContent=s(f))}}}function tt(t){let r={threshold:t.threshold,window_size:t.windowSize};return t.windowWeights!=="none"&&(r.window_weights=t.windowWeights,t.windowWeights==="exponential"&&t.halfLife!=null&&(r.half_life=t.halfLife)),t.detrend!=="none"&&(r.detrend=t.detrend),t.smoothing!=="none"&&(r.smoothing=t.smoothing),t.inputType!=="values"&&(r.input_type=t.inputType),t.seasonalityComponents&&t.seasonalityComponents.length&&(r.seasonality_components=t.seasonalityComponents,r.min_samples_per_group=t.minSamplesPerGroup),r}function ut(t,r){let u=tt(t),e=[`type: ${t.type}`];for(let[d,i]of Object.entries(u))e.push(`${d}=${typeof i=="object"?JSON.stringify(i):i}`);return e.push(`consecutive_anomalies=${r}`),e.join(" \xB7 ")}function dt(t,r){var Ne,_e;pt(),r.classList.add(ct),r.innerHTML="";let u=b("div","dtk-tune-root");r.appendChild(u);let e=t.points.length,d={timestamps:t.points.map(n=>n.t),values:t.points.map(n=>n.v==null?NaN:n.v),intervalSeconds:t.interval_seconds,truthAnomaly:new Array(e).fill(!1),seasonalityData:t.seasonality_columns.length?t.seasonality:void 0,seasonalityColumns:t.seasonality_columns.length?t.seasonality_columns:void 0},i=d,p=n=>{let o=Math.max(0,e-n);return o<=0?d:{timestamps:d.timestamps.slice(o),values:d.values.slice(o),intervalSeconds:d.intervalSeconds,truthAnomaly:d.truthAnomaly.slice(o),seasonalityData:d.seasonalityData?d.seasonalityData.slice(o):void 0,seasonalityColumns:d.seasonalityColumns}},s=t.detector,m=t.consecutive_anomalies,x=(Ne=s.seasonalityComponents)!=null?Ne:[],f=new Map;x.forEach((n,o)=>n.forEach(a=>f.set(a,o+1)));let k=()=>{let n=0;f.forEach(a=>{a>n&&(n=a)});let o=[];for(let a=1;a<=n;a++){let c=t.seasonality_columns.filter(l=>f.get(l)===a);c.length&&o.push(c)}return o.length?o:null},z=()=>{var n;return{type:ve.get(),threshold:me.get(),windowSize:le.get(),minSamples:s.minSamples,inputType:s.inputType,smoothing:ke.get(),smoothingAlpha:s.smoothingAlpha,smoothingWindow:s.smoothingWindow,windowWeights:xe.get(),halfLife:xe.get()==="exponential"?ye.get():null,detrend:we.get(),seasonalityComponents:k(),minSamplesPerGroup:(n=lt[ve.get()])!=null?n:s.minSamplesPerGroup,consecutiveAnomalies:m}},S=b("div","dtk-tune-header"),A=b("div","dtk-tune-titlerow");A.appendChild(b("h1","dtk-tune-title",t.metric));let ie=b("span","dtk-tune-badge",t.save_url?"manual tuning":"preview");A.appendChild(ie),S.appendChild(A);let W=t.project?`${t.project} \xB7 `:"";S.appendChild(b("div","dtk-tune-sub",`${W}${e} points \xB7 ${ft(t.interval_seconds)} grid`)),t.description&&S.appendChild(b("div","dtk-tune-desc",t.description)),u.appendChild(S);let N=b("div","dtk-tune-grid"),D=b("div","dtk-tune-controls"),T=b("div","dtk-tune-main");N.appendChild(D),N.appendChild(T),u.appendChild(N);let E=b("div","dtk-tune-trim"),I=b("div","dtk-tune-trim-head");I.appendChild($e("Points shown","Trim the active sample to the most-recent N points. Fewer points recompute faster (cost grows with points \xD7 window) and make a shorter period easier to read \u2014 handy once you can see a smaller window/period is enough."));let P=b("span","dtk-tune-trim-val");I.appendChild(P),E.appendChild(I);let Y=b("input","dtk-range");Y.type="range",Y.min=String(Math.min(e,200)),Y.max=String(e),Y.step=String(Math.max(1,Math.round(e/200))),Y.value=String(e),E.appendChild(Y),T.appendChild(E);let $=b("div","dtk-tune-chart"),G=b("canvas");$.appendChild(G);let V=b("div","dtk-tune-spin");V.appendChild(b("span","dtk-spin-ring")),V.appendChild(b("span","dtk-spin-txt","computing\u2026")),$.appendChild(V),T.appendChild($);let K=b("div","dtk-tune-legend"),ne=(n,o,a)=>{let c=b("span","dtk-leg-item");c.title=a,c.appendChild(b("span",`dtk-leg-sw ${n}`)),c.appendChild(b("span","dtk-leg-txt",o)),K.appendChild(c)};ne("line","metric","The metric value over time."),ne("band","expected range","The detector's confidence band \u2014 values inside it read as normal."),ne("center","band center","The expected value at the middle of the band."),ne("dot","anomaly","A point the detector flagged as anomalous (outside the band)."),ne("alert","alert","Where an alert fired \u2014 enough consecutive anomalies to meet the rule."),T.appendChild(K);let Q=b("div","dtk-tune-readout");T.appendChild(Q);let Ae=Ze(G,{navigable:!0,onHover:n=>{if(!n||!n.point||!n.point.scored){Q.textContent="";return}let o=n.point;Q.textContent=`t=${mt(o.timestamp)} value=${qe(o.value)} band=[${qe(o.lower)}, ${qe(o.upper)}]`+(o.isAnomaly?` \u26A0 ${o.direction} (sev ${o.severity.toFixed(2)})`:"")}}),X=new Worker(URL.createObjectURL(new Blob([`"use strict";(()=>{var X={mad:1,zscore:2,iqr:4};function T(n){let e=0;for(let t of n)e+=t;return e}function ie(n){let e=n.map((t,r)=>r);return e.sort((t,r)=>n[t]<n[r]?-1:n[t]>n[r]?1:t-r),e}function se(n,e,t){let r=e.length;if(r===0)return NaN;if(n<=e[0])return t[0];if(n>=e[r-1])return t[r-1];for(let i=1;i<r;i++)if(n<=e[i]){let o=e[i-1],c=e[i],u=t[i-1],s=t[i];if(c===o)return u;let d=(n-o)/(c-o);return u+d*(s-u)}return t[r-1]}function F(n,e,t){let r=ie(n),i=r.map(b=>n[b]),o=r.map(b=>e[b]),c=T(o),u=new Array(o.length),s=0;for(let b=0;b<o.length;b++)s+=o[b],u[b]=(s-.5*o[b])/c;let d=t/100;return se(d,u,i)}function S(n,e){return F(n,e,50)}function ce(n,e,t){let r=t===void 0?S(n,e):t,i=n.map(o=>Math.abs(o-r));return S(i,e)}function ue(n,e){let t=T(e),r=0;for(let i=0;i<n.length;i++)r+=n[i]*(e[i]/t);return r}function me(n,e,t,r=0){let i=T(e),o=e.map(s=>s/i),c=t===void 0?T(n.map((s,d)=>s*o[d])):t,u=0;for(let s=0;s<n.length;s++)u+=o[s]*(n[s]-c)**2;if(r===1){let s=1-T(o.map(d=>d*d));s>1e-12&&(u/=s)}return Math.sqrt(u)}function le(n,e){return e.smoothing==="none"?n.slice():e.smoothing==="ema"?ae(n,e.smoothingAlpha):fe(n,e.smoothingWindow)}function ae(n,e){let t=n.length,r=new Array(t).fill(NaN);if(t===0)return r;let i=-1;for(let o=0;o<t;o++)if(!Number.isNaN(n[o])){i=o;break}if(i===-1)return r;r[i]=n[i];for(let o=i+1;o<t;o++)Number.isNaN(n[o])?r[o]=r[o-1]:r[o]=e*n[o]+(1-e)*r[o-1];return r}function fe(n,e){let t=n.length,r=new Array(t).fill(NaN);for(let i=0;i<t;i++){let o=Math.max(0,i-e+1),c=0,u=0;for(let s=o;s<=i;s++)Number.isNaN(n[s])||(c+=n[s],u+=1);r[i]=u>0?c/u:NaN}return r}function de(n,e){let t=n.length;if(e.inputType==="values")return n.slice();let r=new Array(t).fill(NaN);for(let i=1;i<t;i++){let o=n[i-1],c=n[i];e.inputType==="changes"?r[i]=(c-o)/o:e.inputType==="absolute_changes"?r[i]=c-o:r[i]=Math.log(c+1)-Math.log(o+1)}return r}function be(n,e){return n.halfLife!==null?n.halfLife:Math.max(n.windowSize/20,e/2,1)}function pe(n,e){if(n.windowWeights==="none")return null;let t=n.windowSize,r=new Array(t);if(n.windowWeights==="exponential"){let i=be(n,e);for(let o=1;o<=t;o++){let c=Math.min(o/i,1e3);r[o-1]=Math.pow(.5,c)}return r}for(let i=1;i<=t;i++)r[i-1]=(t+1-i)/t;return r}function K(n,e){return e===null?n.map(()=>1):n.map(t=>e[t-1])}function ge(n){let e=n[0];for(let t of n)t>e&&(e=t);return e}function ye(n){let e=n[0];for(let t of n)t<e&&(e=t);return e}function he(n,e,t){if(n.length<4)return 0;let r=(ge(e)+ye(e))/2,i=e.map(l=>l>r),o=i.map(l=>!l),c=0,u=0;for(let l=0;l<e.length;l++)i[l]?c++:u++;if(c<2||u<2)return 0;let s=(l,N)=>N.filter((L,y)=>l[y]),d=s(o,n),b=s(i,n),k=s(o,e),C=s(i,e),g=s(o,t),f=s(i,t),q=S(d,g),p=S(b,f),w=S(k,g),M=S(C,f);return M===w?0:(q-p)/(M-w)}var Se={mad:[["median","center"],["mad","spread"]],zscore:[["mean","center"],["std","spread"]],iqr:[["q1","center"],["q3","center"],["iqr","spread"]]};function Q(n,e,t){if(n==="mad"){let o=S(e,t);return{median:o,mad:ce(e,t,o)}}if(n==="zscore"){let o=ue(e,t);return{mean:o,std:me(e,t,o,1)}}let r=F(e,t,25),i=F(e,t,75);return{q1:r,q3:i,iqr:i-r}}function we(n,e,t){if(n==="mad"){if(e.mad===0)return[e.median-1e-10,e.median+1e-10];let r=t*1.4826*e.mad;return[e.median-r,e.median+r]}return n==="zscore"?e.std===0?[e.mean-1e-10,e.mean+1e-10]:[e.mean-t*e.std,e.mean+t*e.std]:e.iqr===0?[e.q1-1e-10,e.q3+1e-10]:[e.q1-t*e.iqr,e.q3+t*e.iqr]}function Me(n,e,t){if(n==="mad"){let r=1.4826*e.mad;return r>0?t/r:1/0}return n==="zscore"?e.std>0?t/e.std:1/0:e.iqr>0?t/e.iqr:1/0}function Ne(n,e){return n==="mad"?e.median:n==="zscore"?e.mean:(e.q1+e.q3)/2}function U(n,e,t,r,i){return{index:n,timestamp:e,value:t,processedValue:r,scored:!1,isAnomaly:!1,lower:NaN,upper:NaN,center:NaN,direction:null,severity:0,reason:i}}function Y(n,e){let{type:t}=e,r=Math.max(e.minSamples,X[t]),i=Se[t],{timestamps:o,values:c}=n,u=o.length,s=le(c,e),d=de(s,e),b=e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0,k=n.seasonalityData,C=pe(e,r),g=[];for(let f=0;f<u;f++){let q=c[f],p=d[f],w=o[f];if(Number.isNaN(p)){g.push(U(f,w,q,p,"missing_data"));continue}let M=Math.max(0,f-e.windowSize),l=f-M,N=d.slice(M,f),L=N.map(m=>!Number.isNaN(m)),y=[];for(let m=0;m<N.length;m++)L[m]&&y.push(N[m]);if(y.length<r){g.push(U(f,w,q,p,"insufficient_data"));continue}let O=new Array(l);for(let m=0;m<l;m++)O[m]=l-m;let z=[];for(let m=0;m<l;m++)L[m]&&z.push(O[m]);let V=K(z,C),P=0;e.detrend==="linear"&&(P=he(y,z,V));let $=P!==0?y.map((m,W)=>m+P*z[W]):y,G=Q(t,$,V),I={...G};if(b&&k){let m=k[f];for(let W of e.seasonalityComponents){let J=new Array(l).fill(!0);for(let a=0;a<l;a++){let D=k[M+a],x=!0;for(let j of W)if(!D||D[j]!==(m==null?void 0:m[j])){x=!1;break}J[a]=x}let _=[],E=[];for(let a=0;a<l;a++)L[a]&&J[a]&&(_.push(N[a]),E.push(O[a]));if(_.length<e.minSamplesPerGroup)continue;let ne=K(E,C),te=P!==0?_.map((a,D)=>a+P*E[D]):_,re=Q(t,te,ne);for(let[a,D]of i){let x=G[a],oe=(D==="spread"?x>0:x!==0)?re[a]/x:1;I[a]*=oe}}}let[h,A]=we(t,I,e.threshold);h>A&&([h,A]=[A,h]);let H=p<h||p>A,R=null,B=0;if(H){let m;p<h?(R="below",m=h-p):(R="above",m=p-A),B=Me(t,I,m)}let ee=Ne(t,I);g.push({index:f,timestamp:w,value:q,processedValue:p,scored:!0,isAnomaly:H,lower:h,upper:A,center:ee,direction:R,severity:B,reason:"ok"})}return g}function Ae(n,e){let t=n.seasonalityData;if(!t||t.length===0)return 0;let r=0;for(let i of e){let o=new Set;for(let c of t)o.add(i.map(u=>{var s;return String((s=c==null?void 0:c[u])!=null?s:"")}).join("|"));r=Math.max(r,o.size)}return r}function Z(n,e){let t=n.timestamps.length,r=Math.max(e.minSamples,X[e.type]);if(e.smoothing==="sma"&&(r=Math.max(r,e.smoothingWindow-1)),e.smoothing==="ema"&&(r=Math.max(r,Math.ceil(5/e.smoothingAlpha))),e.inputType!=="values"&&(r=Math.max(r,1)),e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0){let o=Ae(n,e.seasonalityComponents);if(o>0){let c=e.minSamplesPerGroup*o;e.windowSize>=c&&(r=Math.max(r,c))}}return Math.min(r,t)}var v=null;function De(n,e,t){let r=[],i=0;for(let o=0;o<n.length;o++){if(!(n[o].scored&&n[o].isAnomaly)){i=0;continue}let u=o>0&&n[o-1].scored&&n[o-1].isAnomaly,s=o>0&&n[o].timestamp-n[o-1].timestamp===e;i=u&&s?i+1:1,i===t&&r.push(o)}return r}self.onmessage=n=>{let e=n.data;if(e.type==="series"){v=e.series;return}if(e.type==="run"&&v){let t=e.params,r=Y(v,t),i=v.intervalSeconds*1e3,o=De(r,i,t.consecutiveAnomalies),c=Z(v,t),u=0;for(let s of r)s.scored&&s.isAnomaly&&u++;self.postMessage({type:"result",id:e.id,scored:r,fires:o,eff:c,flagged:u})}};})();
|
|
2
|
+
`],{type:"text/javascript"})));X.postMessage({type:"series",series:i});let oe=0,J=null;X.onmessage=n=>{let o=n.data;if(o.type!=="result"||o.id!==oe||!J)return;V.classList.remove("on");let a=J,c=o.fires.map(l=>({t:i.timestamps[l],kind:"anomaly"}));Ae.render({series:i,scored:o.scored,params:a,alerts:c}),he.textContent=`${o.flagged} flagged \xB7 ${o.fires.length} alert${o.fires.length===1?"":"s"} \xB7 warm-up ${o.eff} pts`,Se.textContent=ut(a,m)},X.onerror=()=>{V.classList.remove("on"),he.textContent="recompute failed \u2014 see the browser console"};let De=()=>{J=z(),oe+=1,V.classList.add("on"),X.postMessage({type:"run",id:oe,params:J})},se=n=>n>1?Fe(d.timestamps[e-1]-d.timestamps[e-n]):"\u2014",Pe=n=>{P.textContent=n>=e?`${e} pts \xB7 full (${se(e)})`:`${n} pts \xB7 ${se(n)}`};Pe(e);function Le(n){i=p(n),X.postMessage({type:"series",series:i}),Z()}let ae=0;Y.oninput=()=>{let n=Number(Y.value);Pe(n),ae&&window.clearTimeout(ae),ae=window.setTimeout(()=>Le(n),200)};let ge=0,Z=()=>{ge&&window.clearTimeout(ge),ge=window.setTimeout(De,130)},ve=We("Detector",[{label:"MAD",value:"mad"},{label:"Z-Score",value:"zscore"},{label:"IQR",value:"iqr"}],s.type,n=>{var o;ee((o=at[n])!=null?o:3),Z()},"The statistic used for the center/spread of the band: MAD (robust median, default), Z-Score (mean/std) or IQR (quartiles). All share the same windowing.");D.appendChild(ve.row);let me=Re("Threshold (\u03C3-equivalent)",{min:.5,max:10,step:.1,value:s.threshold,fmt:n=>n.toFixed(1),hint:"Band half-width in \u03C3-equivalents. Lower = tighter band = more flags; higher = wider band = fewer flags."},Z);D.appendChild(me.row);let re=me.row.querySelector("input"),fe=me.row.querySelector(".dtk-ctl-val"),ee=n=>{re&&(re.value=String(n)),fe&&(fe.textContent=n.toFixed(1))},H=Math.max(50,Math.min(2e3,Math.floor(e/2))),le=Re("Window size (points)",{min:10,max:H,step:5,value:Math.min(s.windowSize,H),fmt:n=>`${n} \xB7 ${Fe(n*t.interval_seconds*1e3)}`,hint:"How many trailing points form the baseline window for each scored point. Larger = steadier baseline (more history); smaller = adapts faster to shifts."},Z);D.appendChild(le.row);let xe=We("Recency weighting",[{label:"none",value:"none"},{label:"exponential",value:"exponential"},{label:"linear",value:"linear"}],s.windowWeights,n=>{pe.style.display=n==="exponential"?"":"none",Z()},"Weight recent points in the window more heavily: none (flat), exponential (half-life decay) or linear. Helps the baseline track a drifting level.");D.appendChild(xe.row);let ye=Re("Half-life (points)",{min:1,max:H,step:1,value:(_e=s.halfLife)!=null?_e:Math.max(5,Math.round(s.windowSize/20)),fmt:n=>`${n} \xB7 ${Fe(n*t.interval_seconds*1e3)}`,hint:"Exponential weighting only: the age (in points) at which a point counts half as much as the newest. Smaller = faster decay = fresher baseline."},Z),pe=ye.row;pe.style.display=s.windowWeights==="exponential"?"":"none",D.appendChild(pe);let we=We("Detrend",[{label:"none",value:"none"},{label:"linear",value:"linear"}],s.detrend,Z,"Remove a robust linear trend from each window before computing the band, so a steadily rising/falling metric is not flagged for the trend itself.");D.appendChild(we.row);let ke=We("Smoothing",[{label:"none",value:"none"},{label:"EMA",value:"ema"},{label:"SMA",value:"sma"}],s.smoothing,Z,"Smooth the series before detection (EMA or SMA) so single-point jitter does not flag. The detector judges the smoothed line; the raw values show as a faint ghost.");if(D.appendChild(ke.row),t.seasonality_columns.length){let n=t.seasonality_columns,o=b("div","dtk-ctl");o.appendChild($e("Seasonality groups","Condition the band on seasonal keys. Pick a group per column: columns in the SAME group are combined into one key (e.g. dow\xD7hour); separate groups each apply their own correction. Off = ignore that column."));let a=Math.min(n.length,6),c=[{label:"\u2014",value:"0"}];for(let l=1;l<=a;l++)c.push({label:`G${l}`,value:String(l)});n.forEach(l=>{let h=b("div","dtk-season-row");h.appendChild(b("span","dtk-season-col",l));let y=b("div","dtk-seg dtk-season-seg"),_=[],F=()=>{var M;return(M=f.get(l))!=null?M:0},B=()=>_.forEach(M=>M.classList.toggle("on",Number(M.dataset.v)===F()));c.forEach(M=>{let L=b("button","dtk-seg-btn",M.label);L.type="button",L.dataset.v=M.value,L.title=M.value==="0"?`ignore ${l}`:`put ${l} in group ${M.value}`,L.onclick=()=>{f.set(l,Number(M.value)),B(),Z()},_.push(L),y.appendChild(L)}),B(),h.appendChild(y),o.appendChild(h)}),D.appendChild(o)}let Ee=Re("Alert: consecutive anomalies",{min:1,max:10,step:1,value:m,fmt:n=>String(n),hint:"How many anomalies in a row are required before an alert fires. Higher = fewer, more-confident alerts (the \u25BC markers on the chart)."},n=>{m=n,Z()});D.appendChild(Ee.row);let he=b("div","dtk-tune-stat");T.appendChild(he);let be=b("div","dtk-tune-cfg");be.appendChild(b("span","dtk-tune-cfg-k","// effective config"));let Se=b("code","dtk-tune-cfg-v");if(be.appendChild(Se),T.appendChild(be),t.save_url){let n=b("div","dtk-tune-apply"),o=b("button","dtk-apply-btn","Apply to metric");o.type="button",o.title="Write this detector config back into the metric YAML (the previous version is archived under metrics/.history/). Trimming the sample does not change what is written.";let a=b("span","dtk-apply-msg");o.onclick=()=>{let c=z();o.disabled=!0,a.className="dtk-apply-msg info",a.textContent="Applying\u2026",fetch(t.save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detector:{type:c.type,params:tt(c)},consecutive_anomalies:m})}).then(l=>l.ok?l.json():l.text().then(h=>{throw new Error(h||`HTTP ${l.status}`)})).then(l=>{var h;a.className="dtk-apply-msg ok",a.textContent=`Applied \u2192 ${(h=l.saved)!=null?h:"metric"} (previous archived). You can close this tab.`}).catch(l=>{o.disabled=!1,a.className="dtk-apply-msg err",a.textContent=`Apply failed: ${l.message}`})},n.appendChild(o),n.appendChild(a),T.appendChild(n)}else T.appendChild(b("div","dtk-tune-note","Static preview \u2014 sliders recompute live, but there is no write-back. Run `dtk tune` (without --no-serve) to apply a config."));De();let Me=0;window.addEventListener("resize",()=>{Me&&cancelAnimationFrame(Me),Me=requestAnimationFrame(()=>Ae.resize())})}function qe(t){if(!Number.isFinite(t))return"\u2014";let r=Math.abs(t);return r!==0&&(r<.01||r>=1e6)?t.toExponential(2):Number(t.toFixed(r<1?4:2)).toString()}function mt(t){return new Date(t).toISOString().slice(0,16).replace("T"," ")}function Fe(t){let r=Math.round(t/6e4);if(r<60)return`${r}m`;let u=Math.floor(r/60);if(u<24)return`${u}h`;let e=Math.floor(u/24),d=u%24;return d?`${e}d ${d}h`:`${e}d`}function ft(t){return t%86400===0?`${t/86400}d`:t%3600===0?`${t/3600}h`:t%60===0?`${t/60}min`:`${t}s`}var et=!1;function pt(){if(et)return;et=!0;let t=`
|
|
3
|
+
.dtk-tune{--c:#d15b36;--c7:#b4471f;--ink:#1b1916;--muted:#6e675b;--faint:#9a9384;
|
|
4
|
+
--paper:#f5f1e8;--surface:#fbf9f3;--border:#e6e0d4;--green:#2e9e73;--anom:#d63232;
|
|
5
|
+
--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
|
|
6
|
+
--sans:'Schibsted Grotesk',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
7
|
+
.dtk-tune-root{max-width:1200px;margin:0 auto;padding:24px 20px 56px;font-family:var(--sans);color:var(--ink);}
|
|
8
|
+
.dtk-tune-titlerow{display:flex;align-items:center;gap:12px;}
|
|
9
|
+
.dtk-tune-title{font-size:24px;margin:0;font-weight:700;}
|
|
10
|
+
.dtk-tune-badge{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
|
|
11
|
+
color:#fff;background:var(--c);border-radius:999px;padding:3px 10px;}
|
|
12
|
+
.dtk-tune-sub{color:var(--muted);font-size:13px;margin-top:4px;font-family:var(--mono);}
|
|
13
|
+
.dtk-tune-desc{color:var(--muted);font-size:13px;margin-top:8px;white-space:pre-wrap;}
|
|
14
|
+
.dtk-tune-grid{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-top:20px;align-items:start;}
|
|
15
|
+
@media(max-width:820px){.dtk-tune-grid{grid-template-columns:1fr;}}
|
|
16
|
+
.dtk-tune-controls{display:flex;flex-direction:column;gap:16px;background:var(--surface);
|
|
17
|
+
border:1px solid var(--border);border-radius:12px;padding:16px;}
|
|
18
|
+
.dtk-ctl{display:flex;flex-direction:column;gap:6px;}
|
|
19
|
+
.dtk-ctl-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
20
|
+
.dtk-ctl-label{font-size:12px;font-weight:600;color:var(--ink);}
|
|
21
|
+
.dtk-ctl-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
22
|
+
.dtk-seg{display:flex;gap:4px;background:var(--paper);border:1px solid var(--border);border-radius:8px;padding:3px;}
|
|
23
|
+
.dtk-seg.dtk-wrap{flex-wrap:wrap;}
|
|
24
|
+
.dtk-seg-btn{flex:1 1 auto;border:0;background:transparent;color:var(--muted);font-family:var(--sans);
|
|
25
|
+
font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;white-space:nowrap;}
|
|
26
|
+
.dtk-seg-btn:hover{color:var(--ink);}
|
|
27
|
+
.dtk-seg-btn.on{background:var(--c);color:#fff;font-weight:600;}
|
|
28
|
+
.dtk-range{width:100%;accent-color:var(--c);cursor:pointer;}
|
|
29
|
+
.dtk-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);margin-top:2px;cursor:pointer;}
|
|
30
|
+
.dtk-tune-main{display:flex;flex-direction:column;gap:10px;min-width:0;}
|
|
31
|
+
.dtk-tune-chart{position:relative;width:100%;height:470px;background:var(--surface);
|
|
32
|
+
border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
|
33
|
+
.dtk-tune-chart canvas{width:100%;height:100%;display:block;}
|
|
34
|
+
.dtk-tune-readout{font-family:var(--mono);font-size:12px;color:var(--muted);min-height:18px;}
|
|
35
|
+
.dtk-tune-stat{font-family:var(--mono);font-size:12px;color:var(--ink);}
|
|
36
|
+
.dtk-tune-cfg{background:var(--ink);color:#c9c2b4;border-radius:8px;padding:10px 12px;font-family:var(--mono);
|
|
37
|
+
font-size:12px;overflow-x:auto;}
|
|
38
|
+
.dtk-tune-cfg-k{color:var(--faint);display:block;margin-bottom:4px;}
|
|
39
|
+
.dtk-tune-cfg-v{color:#e6e0d4;white-space:pre-wrap;word-break:break-word;}
|
|
40
|
+
.dtk-tune-apply{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px;}
|
|
41
|
+
.dtk-apply-btn{background:var(--c);color:#fff;border:0;border-radius:8px;padding:10px 18px;font-family:var(--sans);
|
|
42
|
+
font-size:14px;font-weight:600;cursor:pointer;}
|
|
43
|
+
.dtk-apply-btn:hover{background:var(--c7);}
|
|
44
|
+
.dtk-apply-btn:disabled{opacity:.55;cursor:default;}
|
|
45
|
+
.dtk-apply-msg{font-size:13px;}
|
|
46
|
+
.dtk-apply-msg.ok{color:var(--green);}
|
|
47
|
+
.dtk-apply-msg.err{color:var(--anom);}
|
|
48
|
+
.dtk-apply-msg.info{color:var(--muted);}
|
|
49
|
+
.dtk-tune-note{font-size:13px;color:var(--muted);background:var(--surface);border:1px dashed var(--border);
|
|
50
|
+
border-radius:8px;padding:10px 12px;}
|
|
51
|
+
.dtk-ctl-info{color:var(--faint);font-size:10px;cursor:help;vertical-align:super;}
|
|
52
|
+
.dtk-tune-trim{display:flex;flex-direction:column;gap:6px;background:var(--surface);
|
|
53
|
+
border:1px solid var(--border);border-radius:10px;padding:9px 12px;}
|
|
54
|
+
.dtk-tune-trim-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
55
|
+
.dtk-tune-trim-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
56
|
+
.dtk-tune-spin{position:absolute;top:10px;right:12px;display:none;align-items:center;gap:7px;
|
|
57
|
+
background:rgba(27,25,22,0.78);color:#e6e0d4;border:1px solid #332f29;border-radius:999px;
|
|
58
|
+
padding:4px 11px 4px 8px;font-family:var(--mono);font-size:11px;pointer-events:none;}
|
|
59
|
+
.dtk-tune-spin.on{display:inline-flex;}
|
|
60
|
+
.dtk-spin-ring{width:12px;height:12px;border-radius:50%;border:2px solid rgba(245,241,232,0.25);
|
|
61
|
+
border-top-color:var(--c);animation:dtk-spin .7s linear infinite;}
|
|
62
|
+
@keyframes dtk-spin{to{transform:rotate(360deg);}}
|
|
63
|
+
.dtk-tune-legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--muted);padding:2px 2px 0;}
|
|
64
|
+
.dtk-leg-item{display:inline-flex;align-items:center;gap:6px;cursor:help;}
|
|
65
|
+
.dtk-leg-sw{display:inline-block;flex:0 0 auto;}
|
|
66
|
+
.dtk-leg-sw.line{width:16px;height:3px;background:var(--c);border-radius:2px;}
|
|
67
|
+
.dtk-leg-sw.band{width:16px;height:11px;background:rgba(209,91,54,0.18);
|
|
68
|
+
border:1px solid rgba(209,91,54,0.5);border-radius:2px;}
|
|
69
|
+
.dtk-leg-sw.center{width:16px;height:2px;
|
|
70
|
+
background:repeating-linear-gradient(90deg,var(--faint) 0 4px,transparent 4px 7px);}
|
|
71
|
+
.dtk-leg-sw.dot{width:9px;height:9px;border-radius:50%;background:var(--anom);}
|
|
72
|
+
.dtk-leg-sw.alert{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;
|
|
73
|
+
border-top:7px solid var(--anom);}
|
|
74
|
+
.dtk-leg-txt{white-space:nowrap;}
|
|
75
|
+
.dtk-season-row{display:flex;align-items:center;justify-content:space-between;gap:8px;}
|
|
76
|
+
.dtk-season-col{font-family:var(--mono);font-size:11.5px;color:var(--muted);
|
|
77
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
78
|
+
.dtk-season-seg{flex:0 0 auto;padding:2px;}
|
|
79
|
+
.dtk-season-seg .dtk-seg-btn{flex:0 0 auto;padding:3px 7px;font-family:var(--mono);font-size:11px;}
|
|
80
|
+
`,r=document.createElement("style");r.textContent=t,document.head.appendChild(r)}window.__DTK_TUNE__={render:dt};})();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.1
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://
|
|
8
|
-
Project-URL: Documentation, https://
|
|
7
|
+
Project-URL: Homepage, https://dtk.pipelab.dev
|
|
8
|
+
Project-URL: Documentation, https://dtk.pipelab.dev
|
|
9
9
|
Project-URL: Repository, https://github.com/alexeiveselov92/detectkit
|
|
10
|
+
Project-URL: Changelog, https://github.com/alexeiveselov92/detectkit/blob/main/CHANGELOG.md
|
|
10
11
|
Project-URL: Issues, https://github.com/alexeiveselov92/detectkit/issues
|
|
11
12
|
Keywords: monitoring,anomaly-detection,metrics,timeseries,alerting
|
|
12
13
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -66,14 +67,22 @@ Requires-Dist: psycopg2-binary>=2.9.0; extra == "integration"
|
|
|
66
67
|
Requires-Dist: pymysql>=1.0.0; extra == "integration"
|
|
67
68
|
Dynamic: license-file
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
<a href="https://dtk.pipelab.dev">
|
|
71
|
+
<picture>
|
|
72
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/detectkit-lockup-dark.svg">
|
|
73
|
+
<img src="assets/detectkit-lockup.svg" alt="detectkit" width="260">
|
|
74
|
+
</picture>
|
|
75
|
+
</a>
|
|
70
76
|
|
|
71
77
|
[](https://pypi.org/project/detectkit/)
|
|
72
78
|
[](https://pypi.org/project/detectkit/)
|
|
79
|
+
[](https://dtk.pipelab.dev)
|
|
73
80
|
|
|
74
81
|
**Metric monitoring with automatic anomaly detection.**
|
|
75
82
|
|
|
76
|
-
`detectkit` is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
83
|
+
[`detectkit`](https://dtk.pipelab.dev) is a Python library for data analysts and engineers to monitor time-series metrics with automatic anomaly detection and alerting. dbt-like project structure and CLI.
|
|
84
|
+
|
|
85
|
+
**Website, docs & live playground → [dtk.pipelab.dev](https://dtk.pipelab.dev)**
|
|
77
86
|
|
|
78
87
|
## Features
|
|
79
88
|
|
|
@@ -181,6 +190,7 @@ for r in results:
|
|
|
181
190
|
|
|
182
191
|
## Documentation
|
|
183
192
|
|
|
193
|
+
- **[dtk.pipelab.dev](https://dtk.pipelab.dev)** — full docs site, guides, reference, and a live in-browser playground
|
|
184
194
|
- [Getting Started](docs/getting-started/quickstart.md) — 5-minute quickstart
|
|
185
195
|
- [Configuration Guide](docs/guides/configuration.md) — all config options
|
|
186
196
|
- [Detectors Guide](docs/guides/detectors.md) — choosing the right detector
|
|
@@ -78,9 +78,10 @@ integration = [
|
|
|
78
78
|
dtk = "detectkit.cli.main:cli"
|
|
79
79
|
|
|
80
80
|
[project.urls]
|
|
81
|
-
Homepage = "https://
|
|
82
|
-
Documentation = "https://
|
|
81
|
+
Homepage = "https://dtk.pipelab.dev"
|
|
82
|
+
Documentation = "https://dtk.pipelab.dev"
|
|
83
83
|
Repository = "https://github.com/alexeiveselov92/detectkit"
|
|
84
|
+
Changelog = "https://github.com/alexeiveselov92/detectkit/blob/main/CHANGELOG.md"
|
|
84
85
|
Issues = "https://github.com/alexeiveselov92/detectkit/issues"
|
|
85
86
|
|
|
86
87
|
[tool.setuptools.dynamic]
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";(()=>{var xe={"--term-bg":"#211e1a","--clay":"#d15b36","--st-anomaly":"#d63232","--st-recovery":"#36a64f","--st-nodata":"#f0ad4e","--st-error":"#5a7a8c","--faint":"#9a9384","--muted":"#6e675b","--border":"#332f29","--term-border":"#332f29"};function z(e){return getComputedStyle(document.documentElement).getPropertyValue(e).trim()||xe[e]||"#888"}function we(e){let n=e.replace("#","").trim();n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let l=parseInt(n,16);return n.length!==6||Number.isNaN(l)?[209,91,54]:[l>>16&255,l>>8&255,l&255]}function F(e,n){let[l,t,o]=we(e);return`rgba(${l},${t},${o},${n})`}function ce(e){let n=Math.max(1,window.devicePixelRatio||1),l=e.clientWidth||e.offsetWidth||0,t=e.clientHeight||e.offsetHeight||0;return e.width=Math.round(l*n),e.height=Math.round(t*n),n}var le=Number.isFinite;function ue(e,n,l,t,o,r,a,c,s,C,w,N){let $=n.length,D=Math.max(1,Math.round(a)),Q=o-t||1,U=0;for(let v=0;v<$;v++){let h=l[v];!le(h)||n[v]<t||n[v]>o||U++}if(e.strokeStyle=C,e.lineWidth=w*N,e.lineJoin="round",e.beginPath(),U<=D){let v=!1;for(let h=0;h<$;h++){let E=l[h],M=n[h];if(!le(E)||M<t||M>o){v=!1;continue}let A=c(M),H=s(E);v?e.lineTo(A,H):(e.moveTo(A,H),v=!0)}}else{let v=new Array(D).fill(null),h=new Array(D).fill(null);for(let M=0;M<$;M++){let A=l[M],H=n[M];if(!le(A)||H<t||H>o)continue;let P=Math.floor((H-t)/Q*(D-1));P=P<0?0:P>D-1?D-1:P,(v[P]===null||A<v[P])&&(v[P]=A),(h[P]===null||A>h[P])&&(h[P]=A)}let E=!1;for(let M=0;M<D;M++){if(h[M]===null){E=!1;continue}let A=r+M,H=s(h[M]),P=s(v[M]);E?e.lineTo(A,H):(e.moveTo(A,H),E=!0),e.lineTo(A,P)}}e.stroke()}function me(e){let n=Math.abs(e);return n>=1e3?e.toFixed(0):n>=10?e.toFixed(1):n>=1?e.toFixed(2):e.toFixed(3)}function de(e,n){let l=new Date(e).toISOString();return n<2*864e5?l.slice(5,16).replace("T"," "):l.slice(5,10)}function fe(e,n,l){return{left:n.l*l,top:n.t*l,right:e.width-n.r*l,bottom:e.height-n.b*l}}function pe(e,n,l,t,o,r,a){let c=fe(n,l,t),s=Math.max(c.left,Math.min(o(r),c.right));s<=c.left+.5||(e.save(),e.fillStyle="rgba(17,15,13,0.42)",e.fillRect(c.left,c.top,s-c.left,c.bottom-c.top),e.strokeStyle=F(z("--faint"),.7),e.lineWidth=1*t,e.setLineDash([4*t,4*t]),e.beginPath(),e.moveTo(s,c.top),e.lineTo(s,c.bottom),e.stroke(),e.setLineDash([]),e.fillStyle=F(z("--faint"),.95),e.font=`${10*t}px ui-monospace, monospace`,e.textAlign="left",e.textBaseline="top",e.fillText(a,s+6*t,c.top+5*t),e.restore())}function be(e,n,l,t,o,r,a){let c=fe(n,l,t),s=5*t;e.save();for(let C=0;C<r.length;C++){let w=r[C],N=o(w.t);if(N<c.left-1||N>c.right+1)continue;let $=a(w.kind);e.strokeStyle=F($,.45),e.lineWidth=1*t,e.beginPath(),e.moveTo(N,c.top),e.lineTo(N,c.bottom),e.stroke(),e.fillStyle=$,e.beginPath(),e.moveTo(N-s,c.top),e.lineTo(N+s,c.top),e.lineTo(N,c.top+s*1.4),e.closePath(),e.fill()}e.restore()}var ke={mad:1,zscore:2,iqr:4};function Se(e,n){let l=e.seasonalityData;if(!l||l.length===0)return 0;let t=0;for(let o of n){let r=new Set;for(let a of l)r.add(o.map(c=>{var s;return String((s=a==null?void 0:a[c])!=null?s:"")}).join("|"));t=Math.max(t,r.size)}return t}function he(e,n){let l=e.timestamps.length,t=Math.max(n.minSamples,ke[n.type]);if(n.smoothing==="sma"&&(t=Math.max(t,n.smoothingWindow-1)),n.smoothing==="ema"&&(t=Math.max(t,Math.ceil(5/n.smoothingAlpha))),n.inputType!=="values"&&(t=Math.max(t,1)),n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0){let r=Se(e,n.seasonalityComponents);if(r>0){let a=n.minSamplesPerGroup*r;n.windowSize>=a&&(t=Math.max(t,a))}}return Math.min(t,l)}var L={l:52,r:14,t:14,b:26},G=Number.isFinite;function ge(e,n={}){let l=e.getContext("2d");if(!l)throw new Error("chart: 2D context unavailable");let t=l,o=1,r=null,a=-1,c=0,s=0,C=1,w=0,N=1;function $(){o=ce(e)}let D=()=>e.width-(L.l+L.r)*o,Q=()=>e.height-(L.t+L.b)*o,U=()=>C-s||1,v=m=>L.l*o+(m-s)/U()*D(),h=m=>e.height-L.b*o-(m-w)/(N-w||1)*Q();function E(m){let b=r==null?void 0:r.series;if(!b||b.timestamps.length===0)return-1;let T=(m-L.l*o)/(D()||1),d=s+T*U(),g=b.timestamps,S=0,k=g.length-1;for(;S<k;){let O=S+k>>1;g[O]<d?S=O+1:k=O}return S>0&&d-g[S-1]<g[S]-d&&(S-=1),S}function M(m,b){let T=m.timestamps;s=T[0],C=T[T.length-1];let d=1/0,g=-1/0;for(let k of m.values)G(k)&&(k<d&&(d=k),k>g&&(g=k));for(let k of b)k.scored&&(G(k.lower)&&k.lower<d&&(d=k.lower),G(k.upper)&&k.upper>g&&(g=k.upper));(!G(d)||!G(g))&&(d=0,g=1),g<=d&&(g=d+1);let S=(g-d)*.06;w=d-S,N=g+S}function A(m,b,T){let d=r.series.timestamps;ue(t,d,m,s,C,L.l*o,D(),v,h,b,T,o)}function H(m,b){let T=[],d=-1;for(let g=Math.max(0,b);g<m.length;g++){let S=m[g];S.scored&&G(S.lower)&&G(S.upper)?d===-1&&(d=g):d!==-1&&(T.push([d,g-1]),d=-1)}return d!==-1&&T.push([d,m.length-1]),T}function P(){if(c=0,!r||e.width===0||e.height===0)return;let{series:m,scored:b,params:T,alerts:d}=r;if(m.timestamps.length===0){t.fillStyle=z("--term-bg"),t.fillRect(0,0,e.width,e.height);return}let g=z("--clay"),S=z("--st-anomaly"),k=z("--faint"),O=z("--muted");t.fillStyle=z("--term-bg"),t.fillRect(0,0,e.width,e.height),t.font=`${11*o}px ui-monospace, 'JetBrains Mono', monospace`,t.textBaseline="middle";for(let u=0;u<=4;u++){let y=w+(N-w)*u/4,x=h(y);t.strokeStyle=F(k,.1),t.lineWidth=1*o,t.beginPath(),t.moveTo(L.l*o,x),t.lineTo(e.width-L.r*o,x),t.stroke(),t.fillStyle=O,t.textAlign="right",t.fillText(me(y),(L.l-8)*o,x)}t.textBaseline="top";let j=U();for(let u=0;u<=5;u++){let y=s+j*u/5,x=v(y);t.fillStyle=O,t.textAlign=u===0?"left":u===5?"right":"center",t.fillText(de(y,j),x,(e.height-L.b+7)*o)}t.save(),t.beginPath(),t.rect(L.l*o,L.t*o,D(),Q()),t.clip();let X=b.length,B=Math.min(he(m,T),X),R=B<X?m.timestamps[B]:void 0,W=H(b,B);t.fillStyle=F(g,.13);for(let[u,y]of W){t.beginPath(),t.moveTo(v(b[u].timestamp),h(b[u].upper));for(let x=u+1;x<=y;x++)t.lineTo(v(b[x].timestamp),h(b[x].upper));for(let x=y;x>=u;x--)t.lineTo(v(b[x].timestamp),h(b[x].lower));t.closePath(),t.fill()}t.strokeStyle=F(g,.4),t.lineWidth=1*o;for(let[u,y]of W)for(let x of["upper","lower"]){t.beginPath();for(let I=u;I<=y;I++){let i=v(b[I].timestamp),f=h(b[I][x]);I===u?t.moveTo(i,f):t.lineTo(i,f)}t.stroke()}t.strokeStyle=F(k,.55),t.lineWidth=1*o,t.setLineDash([3*o,3*o]);for(let[u,y]of W){t.beginPath();for(let x=u;x<=y;x++){let I=b[x].center;if(!G(I))continue;let i=v(b[x].timestamp),f=h(I);x===u?t.moveTo(i,f):t.lineTo(i,f)}t.stroke()}if(t.setLineDash([]),T.smoothing!=="none"){A(m.values,F(g,.28),1.25);let u=b.map(y=>y.processedValue);A(u,g,1.6)}else A(m.values,g,1.5);for(let u=B;u<X;u++){let y=b[u];if(!y.scored||!G(y.value))continue;let x=v(y.timestamp),I=h(y.value);y.isAnomaly?(t.fillStyle=F(S,.18),t.beginPath(),t.arc(x,I,6*o,0,Math.PI*2),t.fill(),t.fillStyle=S,t.beginPath(),t.arc(x,I,3*o,0,Math.PI*2),t.fill()):m.truthAnomaly[u]&&(t.strokeStyle=F(O,.7),t.lineWidth=1.25*o,t.beginPath(),t.arc(x,I,3.5*o,0,Math.PI*2),t.stroke())}R!==void 0&&pe(t,e,L,o,v,R,"detection at full power \u2192"),a>=0&&a<b.length&&ee(a,T.windowSize,b,m,k),d&&d.length&&be(t,e,L,o,v,d,u=>u==="anomaly"?z("--st-anomaly"):u==="recovery"?z("--st-recovery"):z("--st-nodata")),t.restore()}function ee(m,b,T,d,g){let S=d.timestamps,k=Math.max(0,m-b),O=m-1,j=L.t*o,X=Q();if(O>=k){let W=d.intervalSeconds*1e3/U()*D()*.5,u=v(S[k])-W,y=v(S[O])+W;t.fillStyle="rgba(255,255,255,0.05)",t.fillRect(u,j,y-u,X),t.strokeStyle=F(g,.5),t.lineWidth=1*o,t.beginPath(),t.moveTo(u,j),t.lineTo(u,j+X),t.moveTo(y,j),t.lineTo(y,j+X),t.stroke()}let B=v(S[m]);t.strokeStyle=F(g,.85),t.lineWidth=1*o,t.setLineDash([2*o,2*o]),t.beginPath(),t.moveTo(B,j),t.lineTo(B,j+X),t.stroke(),t.setLineDash([]);let R=T[m];if(R.scored&&G(R.lower)&&G(R.upper)){let W=5*o;t.strokeStyle=F(z("--clay"),.85),t.lineWidth=1.5*o;for(let u of[R.lower,R.upper]){let y=h(u);t.beginPath(),t.moveTo(B-W,y),t.lineTo(B+W,y),t.stroke()}}if(G(R.value)){let W=h(R.value);t.fillStyle=z("--term-bg"),t.beginPath(),t.arc(B,W,4*o,0,Math.PI*2),t.fill(),t.strokeStyle=R.isAnomaly?z("--st-anomaly"):z("--clay"),t.lineWidth=2*o,t.beginPath(),t.arc(B,W,4*o,0,Math.PI*2),t.stroke()}}function J(){c===0&&(c=requestAnimationFrame(P))}function Z(){var T;if(!n.onHover||!r)return;if(a<0){n.onHover(null);return}let m=(T=r.scored[a])!=null?T:null,b={index:a,point:m,windowStart:Math.max(0,a-r.params.windowSize),windowEnd:a-1};n.onHover(b)}function oe(m){if(!r)return;let b=e.getBoundingClientRect(),T=(m.clientX-b.left)*o,d=E(T);d!==a&&(a=d,Z(),J())}function te(){a!==-1&&(a=-1,Z(),J())}e.addEventListener("mousemove",oe),e.addEventListener("mouseleave",te);function K(m){r=m,M(m.series,m.scored),a>=m.series.timestamps.length&&(a=-1),J()}function re(){$(),r&&(M(r.series,r.scored),J())}function ne(){e.removeEventListener("mousemove",oe),e.removeEventListener("mouseleave",te),c!==0&&cancelAnimationFrame(c),c=0,r=null}return $(),{render:K,resize:re,destroy:ne}}var Ce={mad:3,zscore:3,iqr:1.5},Te={mad:10,zscore:3,iqr:4},Me="dtk-tune";function p(e,n,l){let t=document.createElement(e);return n&&(t.className=n),l!=null&&(t.textContent=l),t}function ie(e,n,l,t){let o=p("div","dtk-ctl");o.appendChild(p("label","dtk-ctl-label",e));let r=p("div","dtk-seg"),a=l,c=[],s=()=>{c.forEach(C=>C.classList.toggle("on",C.dataset.v===a))};return n.forEach(C=>{let w=p("button","dtk-seg-btn",C.label);w.type="button",w.dataset.v=C.value,w.onclick=()=>{a=C.value,s(),t(a)},c.push(w),r.appendChild(w)}),s(),o.appendChild(r),{row:o,get:()=>a,set:C=>{a=C,s()}}}function se(e,n,l){var C;let t=p("div","dtk-ctl"),o=p("div","dtk-ctl-head"),r=p("label","dtk-ctl-label",e),a=p("span","dtk-ctl-val"),c=(C=n.fmt)!=null?C:(w=>String(w));o.appendChild(r),o.appendChild(a),t.appendChild(o);let s=p("input","dtk-range");return s.type="range",s.min=String(n.min),s.max=String(n.max),s.step=String(n.step),s.value=String(n.value),a.textContent=c(n.value),s.oninput=()=>{let w=Number(s.value);a.textContent=c(w),l(w)},t.appendChild(s),{row:t,get:()=>Number(s.value),setMax:w=>{s.max=String(w),Number(s.value)>w&&(s.value=String(w),a.textContent=c(w))}}}function ye(e){let n={threshold:e.threshold,window_size:e.windowSize};return e.windowWeights!=="none"&&(n.window_weights=e.windowWeights,e.windowWeights==="exponential"&&e.halfLife!=null&&(n.half_life=e.halfLife)),e.detrend!=="none"&&(n.detrend=e.detrend),e.smoothing!=="none"&&(n.smoothing=e.smoothing),e.inputType!=="values"&&(n.input_type=e.inputType),e.seasonalityComponents&&e.seasonalityComponents.length&&(n.seasonality_components=e.seasonalityComponents,n.min_samples_per_group=e.minSamplesPerGroup),n}function Ae(e,n){let l=ye(e),t=[`type: ${e.type}`];for(let[o,r]of Object.entries(l))t.push(`${o}=${typeof r=="object"?JSON.stringify(r):r}`);return t.push(`consecutive_anomalies=${n}`),t.join(" \xB7 ")}function De(e,n){var x,I;Le(),n.classList.add(Me),n.innerHTML="";let l=p("div","dtk-tune-root");n.appendChild(l);let t=e.points.length,o={timestamps:e.points.map(i=>i.t),values:e.points.map(i=>i.v==null?NaN:i.v),intervalSeconds:e.interval_seconds,truthAnomaly:new Array(t).fill(!1),seasonalityData:e.seasonality_columns.length?e.seasonality:void 0,seasonalityColumns:e.seasonality_columns.length?e.seasonality_columns:void 0},r=e.detector,a=e.consecutive_anomalies,c=(x=r.seasonalityComponents)!=null?x:[],s=new Set(c.flat()),C=c.length===1&&c[0].length>1,w=()=>{let i=e.seasonality_columns.filter(f=>s.has(f));return i.length?C?[i]:i.map(f=>[f]):null},N=()=>{var i;return{type:re.get(),threshold:ne.get(),windowSize:g.get(),minSamples:r.minSamples,inputType:r.inputType,smoothing:X.get(),smoothingAlpha:r.smoothingAlpha,smoothingWindow:r.smoothingWindow,windowWeights:S.get(),halfLife:S.get()==="exponential"?k.get():null,detrend:j.get(),seasonalityComponents:w(),minSamplesPerGroup:(i=Te[re.get()])!=null?i:r.minSamplesPerGroup,consecutiveAnomalies:a}},$=p("div","dtk-tune-header"),D=p("div","dtk-tune-titlerow");D.appendChild(p("h1","dtk-tune-title",e.metric));let Q=p("span","dtk-tune-badge",e.save_url?"manual tuning":"preview");D.appendChild(Q),$.appendChild(D);let U=e.project?`${e.project} \xB7 `:"";$.appendChild(p("div","dtk-tune-sub",`${U}${t} points \xB7 ${_e(e.interval_seconds)} grid`)),e.description&&$.appendChild(p("div","dtk-tune-desc",e.description)),l.appendChild($);let v=p("div","dtk-tune-grid"),h=p("div","dtk-tune-controls"),E=p("div","dtk-tune-main");v.appendChild(h),v.appendChild(E),l.appendChild(v);let M=p("div","dtk-tune-chart"),A=p("canvas");M.appendChild(A),E.appendChild(M);let H=p("div","dtk-tune-readout");E.appendChild(H);let P=ge(A,{onHover:i=>{if(!i||!i.point||!i.point.scored){H.textContent="";return}let f=i.point;H.textContent=`t=${Pe(f.timestamp)} value=${ae(f.value)} band=[${ae(f.lower)}, ${ae(f.upper)}]`+(f.isAnomaly?` \u26A0 ${f.direction} (sev ${f.severity.toFixed(2)})`:"")}}),ee=new Worker(URL.createObjectURL(new Blob([`"use strict";(()=>{var X={mad:1,zscore:2,iqr:4};function T(n){let e=0;for(let t of n)e+=t;return e}function ie(n){let e=n.map((t,r)=>r);return e.sort((t,r)=>n[t]<n[r]?-1:n[t]>n[r]?1:t-r),e}function se(n,e,t){let r=e.length;if(r===0)return NaN;if(n<=e[0])return t[0];if(n>=e[r-1])return t[r-1];for(let i=1;i<r;i++)if(n<=e[i]){let o=e[i-1],c=e[i],u=t[i-1],s=t[i];if(c===o)return u;let d=(n-o)/(c-o);return u+d*(s-u)}return t[r-1]}function F(n,e,t){let r=ie(n),i=r.map(b=>n[b]),o=r.map(b=>e[b]),c=T(o),u=new Array(o.length),s=0;for(let b=0;b<o.length;b++)s+=o[b],u[b]=(s-.5*o[b])/c;let d=t/100;return se(d,u,i)}function S(n,e){return F(n,e,50)}function ce(n,e,t){let r=t===void 0?S(n,e):t,i=n.map(o=>Math.abs(o-r));return S(i,e)}function ue(n,e){let t=T(e),r=0;for(let i=0;i<n.length;i++)r+=n[i]*(e[i]/t);return r}function me(n,e,t,r=0){let i=T(e),o=e.map(s=>s/i),c=t===void 0?T(n.map((s,d)=>s*o[d])):t,u=0;for(let s=0;s<n.length;s++)u+=o[s]*(n[s]-c)**2;if(r===1){let s=1-T(o.map(d=>d*d));s>1e-12&&(u/=s)}return Math.sqrt(u)}function le(n,e){return e.smoothing==="none"?n.slice():e.smoothing==="ema"?ae(n,e.smoothingAlpha):fe(n,e.smoothingWindow)}function ae(n,e){let t=n.length,r=new Array(t).fill(NaN);if(t===0)return r;let i=-1;for(let o=0;o<t;o++)if(!Number.isNaN(n[o])){i=o;break}if(i===-1)return r;r[i]=n[i];for(let o=i+1;o<t;o++)Number.isNaN(n[o])?r[o]=r[o-1]:r[o]=e*n[o]+(1-e)*r[o-1];return r}function fe(n,e){let t=n.length,r=new Array(t).fill(NaN);for(let i=0;i<t;i++){let o=Math.max(0,i-e+1),c=0,u=0;for(let s=o;s<=i;s++)Number.isNaN(n[s])||(c+=n[s],u+=1);r[i]=u>0?c/u:NaN}return r}function de(n,e){let t=n.length;if(e.inputType==="values")return n.slice();let r=new Array(t).fill(NaN);for(let i=1;i<t;i++){let o=n[i-1],c=n[i];e.inputType==="changes"?r[i]=(c-o)/o:e.inputType==="absolute_changes"?r[i]=c-o:r[i]=Math.log(c+1)-Math.log(o+1)}return r}function be(n,e){return n.halfLife!==null?n.halfLife:Math.max(n.windowSize/20,e/2,1)}function pe(n,e){if(n.windowWeights==="none")return null;let t=n.windowSize,r=new Array(t);if(n.windowWeights==="exponential"){let i=be(n,e);for(let o=1;o<=t;o++){let c=Math.min(o/i,1e3);r[o-1]=Math.pow(.5,c)}return r}for(let i=1;i<=t;i++)r[i-1]=(t+1-i)/t;return r}function K(n,e){return e===null?n.map(()=>1):n.map(t=>e[t-1])}function ge(n){let e=n[0];for(let t of n)t>e&&(e=t);return e}function ye(n){let e=n[0];for(let t of n)t<e&&(e=t);return e}function he(n,e,t){if(n.length<4)return 0;let r=(ge(e)+ye(e))/2,i=e.map(l=>l>r),o=i.map(l=>!l),c=0,u=0;for(let l=0;l<e.length;l++)i[l]?c++:u++;if(c<2||u<2)return 0;let s=(l,N)=>N.filter((L,y)=>l[y]),d=s(o,n),b=s(i,n),k=s(o,e),C=s(i,e),g=s(o,t),f=s(i,t),q=S(d,g),p=S(b,f),w=S(k,g),M=S(C,f);return M===w?0:(q-p)/(M-w)}var Se={mad:[["median","center"],["mad","spread"]],zscore:[["mean","center"],["std","spread"]],iqr:[["q1","center"],["q3","center"],["iqr","spread"]]};function Q(n,e,t){if(n==="mad"){let o=S(e,t);return{median:o,mad:ce(e,t,o)}}if(n==="zscore"){let o=ue(e,t);return{mean:o,std:me(e,t,o,1)}}let r=F(e,t,25),i=F(e,t,75);return{q1:r,q3:i,iqr:i-r}}function we(n,e,t){if(n==="mad"){if(e.mad===0)return[e.median-1e-10,e.median+1e-10];let r=t*1.4826*e.mad;return[e.median-r,e.median+r]}return n==="zscore"?e.std===0?[e.mean-1e-10,e.mean+1e-10]:[e.mean-t*e.std,e.mean+t*e.std]:e.iqr===0?[e.q1-1e-10,e.q3+1e-10]:[e.q1-t*e.iqr,e.q3+t*e.iqr]}function Me(n,e,t){if(n==="mad"){let r=1.4826*e.mad;return r>0?t/r:1/0}return n==="zscore"?e.std>0?t/e.std:1/0:e.iqr>0?t/e.iqr:1/0}function Ne(n,e){return n==="mad"?e.median:n==="zscore"?e.mean:(e.q1+e.q3)/2}function U(n,e,t,r,i){return{index:n,timestamp:e,value:t,processedValue:r,scored:!1,isAnomaly:!1,lower:NaN,upper:NaN,center:NaN,direction:null,severity:0,reason:i}}function Y(n,e){let{type:t}=e,r=Math.max(e.minSamples,X[t]),i=Se[t],{timestamps:o,values:c}=n,u=o.length,s=le(c,e),d=de(s,e),b=e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0,k=n.seasonalityData,C=pe(e,r),g=[];for(let f=0;f<u;f++){let q=c[f],p=d[f],w=o[f];if(Number.isNaN(p)){g.push(U(f,w,q,p,"missing_data"));continue}let M=Math.max(0,f-e.windowSize),l=f-M,N=d.slice(M,f),L=N.map(m=>!Number.isNaN(m)),y=[];for(let m=0;m<N.length;m++)L[m]&&y.push(N[m]);if(y.length<r){g.push(U(f,w,q,p,"insufficient_data"));continue}let O=new Array(l);for(let m=0;m<l;m++)O[m]=l-m;let z=[];for(let m=0;m<l;m++)L[m]&&z.push(O[m]);let V=K(z,C),P=0;e.detrend==="linear"&&(P=he(y,z,V));let $=P!==0?y.map((m,W)=>m+P*z[W]):y,G=Q(t,$,V),I={...G};if(b&&k){let m=k[f];for(let W of e.seasonalityComponents){let J=new Array(l).fill(!0);for(let a=0;a<l;a++){let D=k[M+a],x=!0;for(let j of W)if(!D||D[j]!==(m==null?void 0:m[j])){x=!1;break}J[a]=x}let _=[],E=[];for(let a=0;a<l;a++)L[a]&&J[a]&&(_.push(N[a]),E.push(O[a]));if(_.length<e.minSamplesPerGroup)continue;let ne=K(E,C),te=P!==0?_.map((a,D)=>a+P*E[D]):_,re=Q(t,te,ne);for(let[a,D]of i){let x=G[a],oe=(D==="spread"?x>0:x!==0)?re[a]/x:1;I[a]*=oe}}}let[h,A]=we(t,I,e.threshold);h>A&&([h,A]=[A,h]);let H=p<h||p>A,R=null,B=0;if(H){let m;p<h?(R="below",m=h-p):(R="above",m=p-A),B=Me(t,I,m)}let ee=Ne(t,I);g.push({index:f,timestamp:w,value:q,processedValue:p,scored:!0,isAnomaly:H,lower:h,upper:A,center:ee,direction:R,severity:B,reason:"ok"})}return g}function Ae(n,e){let t=n.seasonalityData;if(!t||t.length===0)return 0;let r=0;for(let i of e){let o=new Set;for(let c of t)o.add(i.map(u=>{var s;return String((s=c==null?void 0:c[u])!=null?s:"")}).join("|"));r=Math.max(r,o.size)}return r}function Z(n,e){let t=n.timestamps.length,r=Math.max(e.minSamples,X[e.type]);if(e.smoothing==="sma"&&(r=Math.max(r,e.smoothingWindow-1)),e.smoothing==="ema"&&(r=Math.max(r,Math.ceil(5/e.smoothingAlpha))),e.inputType!=="values"&&(r=Math.max(r,1)),e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0){let o=Ae(n,e.seasonalityComponents);if(o>0){let c=e.minSamplesPerGroup*o;e.windowSize>=c&&(r=Math.max(r,c))}}return Math.min(r,t)}var v=null;function De(n,e,t){let r=[],i=0;for(let o=0;o<n.length;o++){if(!(n[o].scored&&n[o].isAnomaly)){i=0;continue}let u=o>0&&n[o-1].scored&&n[o-1].isAnomaly,s=o>0&&n[o].timestamp-n[o-1].timestamp===e;i=u&&s?i+1:1,i===t&&r.push(o)}return r}self.onmessage=n=>{let e=n.data;if(e.type==="series"){v=e.series;return}if(e.type==="run"&&v){let t=e.params,r=Y(v,t),i=v.intervalSeconds*1e3,o=De(r,i,t.consecutiveAnomalies),c=Z(v,t),u=0;for(let s of r)s.scored&&s.isAnomaly&&u++;self.postMessage({type:"result",id:e.id,scored:r,fires:o,eff:c,flagged:u})}};})();
|
|
2
|
-
`],{type:"text/javascript"})));ee.postMessage({type:"series",series:o});let J=0,Z=null;ee.onmessage=i=>{let f=i.data;if(f.type!=="result"||f.id!==J||!Z)return;let q=Z,Y=f.fires.map(_=>({t:o.timestamps[_],kind:"anomaly"}));P.render({series:o,scored:f.scored,params:q,alerts:Y}),R.textContent=`${f.flagged} flagged \xB7 ${f.fires.length} alert${f.fires.length===1?"":"s"} \xB7 warm-up ${f.eff} pts`,u.textContent=Ae(q,a)},ee.onerror=()=>{R.textContent="recompute failed \u2014 see the browser console"};let oe=()=>{Z=N(),J+=1,R.textContent="computing\u2026",ee.postMessage({type:"run",id:J,params:Z})},te=0,K=()=>{te&&window.clearTimeout(te),te=window.setTimeout(oe,130)},re=ie("Detector",[{label:"MAD",value:"mad"},{label:"Z-Score",value:"zscore"},{label:"IQR",value:"iqr"}],r.type,i=>{var f;T((f=Ce[i])!=null?f:3),K()});h.appendChild(re.row);let ne=se("Threshold (\u03C3-equivalent)",{min:.5,max:10,step:.1,value:r.threshold,fmt:i=>i.toFixed(1)},K);h.appendChild(ne.row);let m=ne.row.querySelector("input"),b=ne.row.querySelector(".dtk-ctl-val"),T=i=>{m&&(m.value=String(i)),b&&(b.textContent=i.toFixed(1))},d=Math.max(50,Math.min(2e3,Math.floor(t/2))),g=se("Window size (points)",{min:10,max:d,step:5,value:Math.min(r.windowSize,d),fmt:i=>String(i)},K);h.appendChild(g.row);let S=ie("Recency weighting",[{label:"none",value:"none"},{label:"exponential",value:"exponential"},{label:"linear",value:"linear"}],r.windowWeights,i=>{O.style.display=i==="exponential"?"":"none",K()});h.appendChild(S.row);let k=se("Half-life (points)",{min:1,max:d,step:1,value:(I=r.halfLife)!=null?I:Math.max(5,Math.round(r.windowSize/20)),fmt:i=>String(i)},K),O=k.row;O.style.display=r.windowWeights==="exponential"?"":"none",h.appendChild(O);let j=ie("Detrend",[{label:"none",value:"none"},{label:"linear",value:"linear"}],r.detrend,K);h.appendChild(j.row);let X=ie("Smoothing",[{label:"none",value:"none"},{label:"EMA",value:"ema"},{label:"SMA",value:"sma"}],r.smoothing,K);if(h.appendChild(X.row),e.seasonality_columns.length){let i=p("div","dtk-ctl");i.appendChild(p("label","dtk-ctl-label","Seasonality conditioning"));let f=p("div","dtk-seg dtk-wrap");e.seasonality_columns.forEach(_=>{let V=p("button","dtk-seg-btn",_);V.type="button",V.classList.toggle("on",s.has(_)),V.onclick=()=>{s.has(_)?s.delete(_):s.add(_),V.classList.toggle("on",s.has(_)),K()},f.appendChild(V)}),i.appendChild(f);let q=p("label","dtk-check"),Y=p("input");Y.type="checkbox",Y.checked=C,Y.onchange=()=>{C=Y.checked,K()},q.appendChild(Y),q.appendChild(p("span",void 0,"conjoin selected into one group")),i.appendChild(q),h.appendChild(i)}let B=se("Alert: consecutive anomalies",{min:1,max:10,step:1,value:a,fmt:i=>String(i)},i=>{a=i,K()});h.appendChild(B.row);let R=p("div","dtk-tune-stat");E.appendChild(R);let W=p("div","dtk-tune-cfg");W.appendChild(p("span","dtk-tune-cfg-k","// effective config"));let u=p("code","dtk-tune-cfg-v");if(W.appendChild(u),E.appendChild(W),e.save_url){let i=p("div","dtk-tune-apply"),f=p("button","dtk-apply-btn","Apply to metric");f.type="button";let q=p("span","dtk-apply-msg");f.onclick=()=>{let Y=N();f.disabled=!0,q.className="dtk-apply-msg info",q.textContent="Applying\u2026",fetch(e.save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detector:{type:Y.type,params:ye(Y)},consecutive_anomalies:a})}).then(_=>_.ok?_.json():_.text().then(V=>{throw new Error(V||`HTTP ${_.status}`)})).then(_=>{var V;q.className="dtk-apply-msg ok",q.textContent=`Applied \u2192 ${(V=_.saved)!=null?V:"metric"} (previous archived). You can close this tab.`}).catch(_=>{f.disabled=!1,q.className="dtk-apply-msg err",q.textContent=`Apply failed: ${_.message}`})},i.appendChild(f),i.appendChild(q),E.appendChild(i)}else E.appendChild(p("div","dtk-tune-note","Static preview \u2014 sliders recompute live, but there is no write-back. Run `dtk tune` (without --no-serve) to apply a config."));oe();let y=0;window.addEventListener("resize",()=>{y&&cancelAnimationFrame(y),y=requestAnimationFrame(()=>P.resize())})}function ae(e){if(!Number.isFinite(e))return"\u2014";let n=Math.abs(e);return n!==0&&(n<.01||n>=1e6)?e.toExponential(2):Number(e.toFixed(n<1?4:2)).toString()}function Pe(e){return new Date(e).toISOString().slice(0,16).replace("T"," ")}function _e(e){return e%86400===0?`${e/86400}d`:e%3600===0?`${e/3600}h`:e%60===0?`${e/60}min`:`${e}s`}var ve=!1;function Le(){if(ve)return;ve=!0;let e=`
|
|
3
|
-
.dtk-tune{--c:#d15b36;--c7:#b4471f;--ink:#1b1916;--muted:#6e675b;--faint:#9a9384;
|
|
4
|
-
--paper:#f5f1e8;--surface:#fbf9f3;--border:#e6e0d4;--green:#2e9e73;--anom:#d63232;
|
|
5
|
-
--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
|
|
6
|
-
--sans:'Schibsted Grotesk',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
7
|
-
.dtk-tune-root{max-width:1200px;margin:0 auto;padding:24px 20px 56px;font-family:var(--sans);color:var(--ink);}
|
|
8
|
-
.dtk-tune-titlerow{display:flex;align-items:center;gap:12px;}
|
|
9
|
-
.dtk-tune-title{font-size:24px;margin:0;font-weight:700;}
|
|
10
|
-
.dtk-tune-badge{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
|
|
11
|
-
color:#fff;background:var(--c);border-radius:999px;padding:3px 10px;}
|
|
12
|
-
.dtk-tune-sub{color:var(--muted);font-size:13px;margin-top:4px;font-family:var(--mono);}
|
|
13
|
-
.dtk-tune-desc{color:var(--muted);font-size:13px;margin-top:8px;white-space:pre-wrap;}
|
|
14
|
-
.dtk-tune-grid{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-top:20px;align-items:start;}
|
|
15
|
-
@media(max-width:820px){.dtk-tune-grid{grid-template-columns:1fr;}}
|
|
16
|
-
.dtk-tune-controls{display:flex;flex-direction:column;gap:16px;background:var(--surface);
|
|
17
|
-
border:1px solid var(--border);border-radius:12px;padding:16px;}
|
|
18
|
-
.dtk-ctl{display:flex;flex-direction:column;gap:6px;}
|
|
19
|
-
.dtk-ctl-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
20
|
-
.dtk-ctl-label{font-size:12px;font-weight:600;color:var(--ink);}
|
|
21
|
-
.dtk-ctl-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
22
|
-
.dtk-seg{display:flex;gap:4px;background:var(--paper);border:1px solid var(--border);border-radius:8px;padding:3px;}
|
|
23
|
-
.dtk-seg.dtk-wrap{flex-wrap:wrap;}
|
|
24
|
-
.dtk-seg-btn{flex:1 1 auto;border:0;background:transparent;color:var(--muted);font-family:var(--sans);
|
|
25
|
-
font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;white-space:nowrap;}
|
|
26
|
-
.dtk-seg-btn:hover{color:var(--ink);}
|
|
27
|
-
.dtk-seg-btn.on{background:var(--c);color:#fff;font-weight:600;}
|
|
28
|
-
.dtk-range{width:100%;accent-color:var(--c);cursor:pointer;}
|
|
29
|
-
.dtk-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);margin-top:2px;cursor:pointer;}
|
|
30
|
-
.dtk-tune-main{display:flex;flex-direction:column;gap:10px;min-width:0;}
|
|
31
|
-
.dtk-tune-chart{position:relative;width:100%;height:420px;background:var(--surface);
|
|
32
|
-
border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
|
33
|
-
.dtk-tune-chart canvas{width:100%;height:100%;display:block;}
|
|
34
|
-
.dtk-tune-readout{font-family:var(--mono);font-size:12px;color:var(--muted);min-height:18px;}
|
|
35
|
-
.dtk-tune-stat{font-family:var(--mono);font-size:12px;color:var(--ink);}
|
|
36
|
-
.dtk-tune-cfg{background:var(--ink);color:#c9c2b4;border-radius:8px;padding:10px 12px;font-family:var(--mono);
|
|
37
|
-
font-size:12px;overflow-x:auto;}
|
|
38
|
-
.dtk-tune-cfg-k{color:var(--faint);display:block;margin-bottom:4px;}
|
|
39
|
-
.dtk-tune-cfg-v{color:#e6e0d4;white-space:pre-wrap;word-break:break-word;}
|
|
40
|
-
.dtk-tune-apply{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px;}
|
|
41
|
-
.dtk-apply-btn{background:var(--c);color:#fff;border:0;border-radius:8px;padding:10px 18px;font-family:var(--sans);
|
|
42
|
-
font-size:14px;font-weight:600;cursor:pointer;}
|
|
43
|
-
.dtk-apply-btn:hover{background:var(--c7);}
|
|
44
|
-
.dtk-apply-btn:disabled{opacity:.55;cursor:default;}
|
|
45
|
-
.dtk-apply-msg{font-size:13px;}
|
|
46
|
-
.dtk-apply-msg.ok{color:var(--green);}
|
|
47
|
-
.dtk-apply-msg.err{color:var(--anom);}
|
|
48
|
-
.dtk-apply-msg.info{color:var(--muted);}
|
|
49
|
-
.dtk-tune-note{font-size:13px;color:var(--muted);background:var(--surface);border:1px dashed var(--border);
|
|
50
|
-
border-radius:8px;padding:10px 12px;}
|
|
51
|
-
`,n=document.createElement("style");n.textContent=e,document.head.appendChild(n)}window.__DTK_TUNE__={render:De};})();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.1 → detectkit-0.31.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.1 → detectkit-0.31.1}/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
|