detectkit 0.43.0__tar.gz → 0.44.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {detectkit-0.43.0/detectkit.egg-info → detectkit-0.44.0}/PKG-INFO +1 -1
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/__init__.py +1 -1
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/autotuner.py +30 -2
- detectkit-0.44.0/detectkit/cli/_output.py +112 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/autotune.md +6 -4
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/cli.md +6 -4
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/autotune.py +11 -30
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/assets/tune.js +3 -3
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/server.py +128 -3
- {detectkit-0.43.0 → detectkit-0.44.0/detectkit.egg-info}/PKG-INFO +1 -1
- detectkit-0.43.0/detectkit/cli/_output.py +0 -50
- {detectkit-0.43.0 → detectkit-0.44.0}/LICENSE +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/MANIFEST.in +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/README.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/core/models.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/html.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/pyproject.toml +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/requirements.txt +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/setup.cfg +0 -0
- {detectkit-0.43.0 → detectkit-0.44.0}/setup.py +0 -0
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.44.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -6,7 +6,9 @@ command handles loading, persistence, config emission and candidate cleanup.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import contextlib
|
|
10
|
+
import logging
|
|
11
|
+
from collections.abc import Callable, Iterator
|
|
10
12
|
from datetime import datetime, timedelta
|
|
11
13
|
from typing import Any
|
|
12
14
|
|
|
@@ -27,6 +29,31 @@ from detectkit.detectors.factory import DetectorFactory
|
|
|
27
29
|
|
|
28
30
|
_ALERT_WINDOW_GRID = (1, 2, 3, 4, 5)
|
|
29
31
|
|
|
32
|
+
# The windowed detectors emit a one-time "seasonality group can't fill this
|
|
33
|
+
# window → falls back to global" warning per *instance*. A tune builds dozens of
|
|
34
|
+
# throwaway candidate detectors, so that warning would flood the terminal and
|
|
35
|
+
# bury the structured decision log. The engine already surfaces an under-fill of
|
|
36
|
+
# the *chosen* seasonality as a `window` advisory in its own log, so the raw
|
|
37
|
+
# per-candidate warnings are pure noise here.
|
|
38
|
+
_WINDOWED_LOGGER = "detectkit.detectors.statistical._windowed"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@contextlib.contextmanager
|
|
42
|
+
def _quiet_per_candidate_warnings() -> Iterator[None]:
|
|
43
|
+
"""Silence the windowed detectors' per-instance under-fill warning for a tune.
|
|
44
|
+
|
|
45
|
+
Scoped + restored, so a real ``dtk run`` (which builds one detector and *does*
|
|
46
|
+
want the warning) is unaffected — only the candidate sweep inside the engine
|
|
47
|
+
is quieted.
|
|
48
|
+
"""
|
|
49
|
+
logger = logging.getLogger(_WINDOWED_LOGGER)
|
|
50
|
+
prev_level = logger.level
|
|
51
|
+
logger.setLevel(logging.ERROR)
|
|
52
|
+
try:
|
|
53
|
+
yield
|
|
54
|
+
finally:
|
|
55
|
+
logger.setLevel(prev_level)
|
|
56
|
+
|
|
30
57
|
|
|
31
58
|
def _ts_to_dt(ts64: np.datetime64) -> datetime:
|
|
32
59
|
ms = int(ts64.astype("datetime64[ms]").astype(np.int64))
|
|
@@ -196,4 +223,5 @@ def run_autotune_engine(
|
|
|
196
223
|
settings=settings,
|
|
197
224
|
on_stage=on_stage,
|
|
198
225
|
)
|
|
199
|
-
|
|
226
|
+
with _quiet_per_candidate_warnings():
|
|
227
|
+
return tuner.tune()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Shared CLI output helpers so every command renders in one house style.
|
|
2
|
+
|
|
3
|
+
Mirrors the load → detect → alert pipeline's tree look (``┌─ / │ / └─``) used by
|
|
4
|
+
``dtk run`` so the maintenance commands (``dtk clean``, ``dtk unlock``) match it
|
|
5
|
+
instead of each inventing its own formatting.
|
|
6
|
+
|
|
7
|
+
House conventions:
|
|
8
|
+
- A metric *with* something to report is a tree: a cyan-bold ``┌─ <name>``
|
|
9
|
+
header followed by one child line per item (``│ `` for all but the last,
|
|
10
|
+
``└─ `` for the last).
|
|
11
|
+
- A metric with *nothing* to do is a single ``•`` line.
|
|
12
|
+
- A per-metric error is a red ``✗`` line (to stderr).
|
|
13
|
+
- The final summary is a cyan-bold ``Done. …`` line.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
# Autotune-engine stage name → display title for the streamed run-log tree.
|
|
23
|
+
# Most stages render as ``stage.upper()``; only the two underscored names need a
|
|
24
|
+
# space so the header reads cleanly (``DETECTOR SELECT`` / ``GRID SEARCH``).
|
|
25
|
+
AUTOTUNE_STAGE_TITLES = {
|
|
26
|
+
"labels": "LABELS",
|
|
27
|
+
"seasonality": "SEASONALITY",
|
|
28
|
+
"detector_select": "DETECTOR SELECT",
|
|
29
|
+
"grid_search": "GRID SEARCH",
|
|
30
|
+
"window": "WINDOW",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def echo_block(
|
|
35
|
+
title: str,
|
|
36
|
+
children: list[str],
|
|
37
|
+
*,
|
|
38
|
+
warnings: list[str] | None = None,
|
|
39
|
+
echo: Callable[[str], None] = click.echo,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Print a cyan-bold ``┌─ title`` header with ``│``/``└─`` child lines.
|
|
42
|
+
|
|
43
|
+
The injectable core of the house tree style (``dtk run``'s load/detect/alert
|
|
44
|
+
blocks): ``warnings`` render as yellow ``│`` continuation lines above the
|
|
45
|
+
children, the last child gets the ``└─`` elbow. ``echo`` defaults to
|
|
46
|
+
``click.echo`` but can be any line sink, so the tune server can stream the
|
|
47
|
+
same blocks through its own output callback. ``children`` must be non-empty.
|
|
48
|
+
"""
|
|
49
|
+
echo(click.style(f" ┌─ {title}", fg="cyan", bold=True))
|
|
50
|
+
for warning in warnings or []:
|
|
51
|
+
echo(click.style(f" │ ⚠ {warning}", fg="yellow", bold=True))
|
|
52
|
+
last = len(children) - 1
|
|
53
|
+
for i, child in enumerate(children):
|
|
54
|
+
prefix = " └─ " if i == last else " │ "
|
|
55
|
+
echo(f"{prefix}{child}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def echo_tree(name: str, children: list[str], *, warnings: list[str] | None = None) -> None:
|
|
59
|
+
"""Print a ``┌─ name`` header with ``│``/``└─`` child lines.
|
|
60
|
+
|
|
61
|
+
``warnings`` (if any) are rendered as yellow ``│`` continuation lines above
|
|
62
|
+
the children. ``children`` must be non-empty (a metric with no items should
|
|
63
|
+
use :func:`echo_noop` instead). Thin wrapper over :func:`echo_block` (the
|
|
64
|
+
CLI-default ``click.echo`` sink).
|
|
65
|
+
"""
|
|
66
|
+
echo_block(name, children, warnings=warnings)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class StageLogRenderer:
|
|
70
|
+
"""Stream ``(stage, line)`` engine progress as the run-log tree.
|
|
71
|
+
|
|
72
|
+
Opens a cyan-bold ``┌─ TITLE`` header the first time each stage appears and
|
|
73
|
+
prints every subsequent line for that stage as a ``│ `` child — the same
|
|
74
|
+
look as ``dtk run``'s load/detect/alert blocks. ``titles`` maps an engine
|
|
75
|
+
stage name to its display title (unmapped names fall back to ``upper()``);
|
|
76
|
+
``echo`` is injectable so one renderer drives both the CLI (``click.echo``)
|
|
77
|
+
and the ``dtk tune`` server (its own output callback). Build a fresh renderer
|
|
78
|
+
per run so the first stage re-opens its header.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
titles: dict[str, str] | None = None,
|
|
85
|
+
echo: Callable[[str], None] = click.echo,
|
|
86
|
+
) -> None:
|
|
87
|
+
self._open: str | None = None
|
|
88
|
+
self._titles = titles or {}
|
|
89
|
+
self._echo = echo
|
|
90
|
+
|
|
91
|
+
def __call__(self, stage: str, line: str) -> None:
|
|
92
|
+
if self._open != stage:
|
|
93
|
+
title = self._titles.get(stage, stage.upper())
|
|
94
|
+
self._echo(click.style(f" ┌─ {title}", fg="cyan", bold=True))
|
|
95
|
+
self._open = stage
|
|
96
|
+
self._echo(f" │ {line}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def echo_noop(name: str, reason: str) -> None:
|
|
100
|
+
"""A metric with nothing to do — a single ``•`` line."""
|
|
101
|
+
click.echo(f" • {name}: {reason}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def echo_error(name: str, message: str) -> None:
|
|
105
|
+
"""A per-metric failure — a red ``✗`` line on stderr."""
|
|
106
|
+
click.echo(click.style(f" ✗ {name}: {message}", fg="red"), err=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def echo_done(summary: str) -> None:
|
|
110
|
+
"""The closing ``Done. …`` summary (cyan, bold), preceded by a blank line."""
|
|
111
|
+
click.echo()
|
|
112
|
+
click.echo(click.style(f"Done. {summary}", fg="cyan", bold=True))
|
|
@@ -21,10 +21,12 @@ same windowed detectors and `detector_id` identity). The fastest path is the
|
|
|
21
21
|
> **You can also run this engine *inside* `dtk tune`** — its **Autotune** mode runs
|
|
22
22
|
> the same search **server-side** (using the incidents you've marked as ground
|
|
23
23
|
> truth), re-seeds the knobs with the winner, and lets you **Apply** in place. That's
|
|
24
|
-
> the same computation as this command
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
> run
|
|
24
|
+
> the same computation as this command, with two differences: it tunes on the
|
|
25
|
+
> **window currently shown** in the cockpit (the **Points shown** trim — the series
|
|
26
|
+
> you see and score) rather than the full capped history, and it edits the metric YAML
|
|
27
|
+
> in place (advisory — no run record / `__tuned_<id>.yml` / persisted detections)
|
|
28
|
+
> rather than emitting a new tuned file. Reach for `dtk autotune` when you want the
|
|
29
|
+
> audited run + tuned file + persisted winner detections.
|
|
28
30
|
|
|
29
31
|
## What it searches
|
|
30
32
|
|
|
@@ -109,10 +109,12 @@ anomalies** — loop a cloud of anomaly dots, each consecutive run, gaps bridged
|
|
|
109
109
|
`consecutive_anomalies`, becomes one span — or **Threshold capture** every span past
|
|
110
110
|
a horizontal line, each widened to a full interval so the alert lands inside; the
|
|
111
111
|
painted window saves as `capture_windows`), and **Autotune** (**Run autotune**
|
|
112
|
-
launches the **real** `dtk autotune` engine **server-side** over the
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
launches the **real** `dtk autotune` engine **server-side** over the **window
|
|
113
|
+
currently shown** (the **Points shown** trim — the same series you see and score, not
|
|
114
|
+
the full history), using your marked incidents as ground truth, then **re-seeds every
|
|
115
|
+
knob** with the winner and shows the score + decision log; the band leads like Tune;
|
|
116
|
+
each run also streams a structured `LABELS → … → RESULT` log to the terminal, like
|
|
117
|
+
`dtk run`). The Autotune mode is **advisory** — it computes + re-seeds only and persists nothing (no
|
|
116
118
|
run record / `__tuned_<id>.yml` / detections, so `dtk tune` stays lock-free); review
|
|
117
119
|
the band and **Apply** to write it back. It honours the metric's `autotune:` block,
|
|
118
120
|
is supervised when incidents are marked (also picks `consecutive_anomalies`) else
|
|
@@ -31,7 +31,14 @@ from detectkit.autotune.labels import (
|
|
|
31
31
|
parse_incident_labels,
|
|
32
32
|
)
|
|
33
33
|
from detectkit.autotune.runner import build_settings, cap_history, resolve_scoring
|
|
34
|
-
from detectkit.cli._output import
|
|
34
|
+
from detectkit.cli._output import (
|
|
35
|
+
AUTOTUNE_STAGE_TITLES,
|
|
36
|
+
StageLogRenderer,
|
|
37
|
+
echo_block,
|
|
38
|
+
echo_done,
|
|
39
|
+
echo_error,
|
|
40
|
+
echo_noop,
|
|
41
|
+
)
|
|
35
42
|
from detectkit.cli.commands.run import find_project_root, parse_date, select_metrics
|
|
36
43
|
from detectkit.config.metric_config import AutoTuneConfig, MetricConfig
|
|
37
44
|
from detectkit.config.profile import ProfilesConfig
|
|
@@ -41,28 +48,6 @@ from detectkit.detectors.base import DetectionResult
|
|
|
41
48
|
from detectkit.detectors.factory import DetectorFactory
|
|
42
49
|
from detectkit.utils.json_utils import json_dumps_sorted
|
|
43
50
|
|
|
44
|
-
_STAGE_TITLES = {
|
|
45
|
-
"labels": "LABELS",
|
|
46
|
-
"seasonality": "SEASONALITY",
|
|
47
|
-
"detector_select": "DETECTOR SELECT",
|
|
48
|
-
"grid_search": "GRID SEARCH",
|
|
49
|
-
"window": "WINDOW",
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class _StageRenderer:
|
|
54
|
-
"""Streams engine progress as the run-log tree (header per stage + │ lines)."""
|
|
55
|
-
|
|
56
|
-
def __init__(self) -> None:
|
|
57
|
-
self._open: str | None = None
|
|
58
|
-
|
|
59
|
-
def __call__(self, stage: str, line: str) -> None:
|
|
60
|
-
if self._open != stage:
|
|
61
|
-
title = _STAGE_TITLES.get(stage, stage.upper())
|
|
62
|
-
click.echo(click.style(f" ┌─ {title}", fg="cyan", bold=True))
|
|
63
|
-
self._open = stage
|
|
64
|
-
click.echo(f" │ {line}")
|
|
65
|
-
|
|
66
51
|
|
|
67
52
|
def _results_to_batch(results: list[DetectionResult]) -> dict[str, np.ndarray]:
|
|
68
53
|
"""Convert detect() output into the ``save_detections`` batch shape."""
|
|
@@ -358,7 +343,7 @@ def _tune_one(
|
|
|
358
343
|
ground_truth=ground_truth,
|
|
359
344
|
interval_seconds=interval_seconds,
|
|
360
345
|
settings=settings,
|
|
361
|
-
on_stage=
|
|
346
|
+
on_stage=StageLogRenderer(titles=AUTOTUNE_STAGE_TITLES),
|
|
362
347
|
)
|
|
363
348
|
|
|
364
349
|
run_id = compute_run_id(result)
|
|
@@ -434,9 +419,7 @@ def _finalize(
|
|
|
434
419
|
|
|
435
420
|
if dry_run:
|
|
436
421
|
children.append("dry-run: no config written, no detections persisted")
|
|
437
|
-
|
|
438
|
-
for i, child in enumerate(children):
|
|
439
|
-
click.echo(f" {'└─' if i == len(children) - 1 else '│ '} {child}")
|
|
422
|
+
echo_block("RESULT", children)
|
|
440
423
|
return
|
|
441
424
|
|
|
442
425
|
# Persist the run record (the audit trail).
|
|
@@ -518,9 +501,7 @@ def _finalize(
|
|
|
518
501
|
f"persisted winner, pruned {pruned} superseded run(s)"
|
|
519
502
|
)
|
|
520
503
|
children.append(f"Re-run with: dtk run --select {name}__tuned_{run_id}")
|
|
521
|
-
|
|
522
|
-
for i, child in enumerate(children):
|
|
523
|
-
click.echo(f" {'└─' if i == len(children) - 1 else '│ '} {child}")
|
|
504
|
+
echo_block("RESULT", children)
|
|
524
505
|
|
|
525
506
|
|
|
526
507
|
def _prune_prior_winners(
|