detectkit 0.23.1__tar.gz → 0.24.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.23.1/detectkit.egg-info → detectkit-0.24.0}/PKG-INFO +1 -1
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/__init__.py +1 -1
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/config_emitter.py +17 -5
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/html_labeler.py +36 -8
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/label_server.py +14 -6
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/autotune.md +4 -2
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +3 -2
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/autotune.py +4 -4
- {detectkit-0.23.1 → detectkit-0.24.0/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.23.1 → detectkit-0.24.0}/LICENSE +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/MANIFEST.in +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/README.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/core/models.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/pyproject.toml +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/requirements.txt +0 -0
- {detectkit-0.23.1 → detectkit-0.24.0}/setup.cfg +0 -0
- {detectkit-0.23.1 → detectkit-0.24.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.24.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -85,11 +85,23 @@ def _build_body(original: MetricConfig, result: AutoTuneResult, new_name: str) -
|
|
|
85
85
|
body["loading_start_time"] = original.loading_start_time
|
|
86
86
|
body["loading_batch_size"] = original.loading_batch_size
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
# The top-level `seasonality_columns` field feeds the *built-in* time-feature
|
|
89
|
+
# extractor and is validated against the built-in allowlist (hour/day_of_week/…).
|
|
90
|
+
# When the metric instead sources seasonality from the query
|
|
91
|
+
# (`query_columns.seasonality`, e.g. a custom `league_day`), the loader ignores
|
|
92
|
+
# `seasonality_columns` entirely and the chosen grouping rides in the detector's
|
|
93
|
+
# `seasonality_components`. So never copy the chosen columns here in that mode —
|
|
94
|
+
# they may be custom names the validator rejects, and they are already declared
|
|
95
|
+
# in the preserved `query_columns`.
|
|
96
|
+
if original.query_columns and original.query_columns.seasonality:
|
|
97
|
+
if original.seasonality_columns:
|
|
98
|
+
body["seasonality_columns"] = original.seasonality_columns
|
|
99
|
+
else:
|
|
100
|
+
scalar_cols = _flatten_scalar_seasonality(result.chosen_seasonality)
|
|
101
|
+
if scalar_cols:
|
|
102
|
+
body["seasonality_columns"] = scalar_cols
|
|
103
|
+
elif original.seasonality_columns:
|
|
104
|
+
body["seasonality_columns"] = original.seasonality_columns
|
|
93
105
|
|
|
94
106
|
body["detectors"] = [
|
|
95
107
|
{"type": result.chosen_detector_type, "params": result.chosen_detector_params}
|
|
@@ -7,7 +7,8 @@ file in the canonical schema, fed back via
|
|
|
7
7
|
``dtk autotune --select <metric> --incidents <file-or-dir>``.
|
|
8
8
|
|
|
9
9
|
The page is offline-only — a browser cannot write to the project, so Export
|
|
10
|
-
downloads a **versioned** file
|
|
10
|
+
downloads a **versioned** file named after the metric, with the optional set name
|
|
11
|
+
folded in as a suffix (``<metric>[-<name>]-<UTC-stamp>.yml``); drop it into
|
|
11
12
|
``incidents/<metric>/`` to keep every labeling round (``--incidents`` accepts
|
|
12
13
|
that directory and uses the newest version).
|
|
13
14
|
|
|
@@ -58,6 +59,11 @@ _TEMPLATE = """<!doctype html>
|
|
|
58
59
|
.brand span { color: var(--faint); font-size: 12px; }
|
|
59
60
|
h1 { font-size: 18px; line-height: 1.3; margin: 0 0 6px; color: var(--paper); font-weight: 600; }
|
|
60
61
|
h1 code { color: var(--clay); font-family: var(--mono); font-size: .82em; }
|
|
62
|
+
.ichip { display:inline-flex; align-items:center; gap:6px; vertical-align: middle; margin-left: 8px;
|
|
63
|
+
font-family: var(--mono); font-size: 12px; font-weight: 500; color: var(--paper);
|
|
64
|
+
background: rgba(209,91,54,0.16); border: 1px solid var(--clay); border-radius: 999px; padding: 3px 10px; }
|
|
65
|
+
.ichip .d { width:6px; height:6px; border-radius:50%; background: var(--clay); }
|
|
66
|
+
.ichip b { color: var(--clay); font-weight: 700; }
|
|
61
67
|
.hint { color: var(--faint); font-size: 13px; margin: 0 0 18px; line-height: 1.55; }
|
|
62
68
|
.hint code, code.k { color: var(--term-text); font-family: var(--mono); font-size: 12px;
|
|
63
69
|
background: var(--term-surface); border: 1px solid var(--term-border); border-radius: 5px; padding: 1px 6px; }
|
|
@@ -106,7 +112,8 @@ _TEMPLATE = """<!doctype html>
|
|
|
106
112
|
<svg viewBox="0 0 100 100" aria-hidden="true"><rect x="3" y="3" width="94" height="94" rx="26" fill="#D15B36"/><polyline points="14,62 36,62 50,22 64,62 86,62" fill="none" stroke="#FBF9F3" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="50" cy="22" r="6.5" fill="#FBF9F3"/></svg>
|
|
107
113
|
<b>detectkit</b><span>· incident labeler</span>
|
|
108
114
|
</div>
|
|
109
|
-
<h1>Label incidents — <code>__METRIC__</code
|
|
115
|
+
<h1>Label incidents — <code>__METRIC__</code><span id="intervalchip" class="ichip"
|
|
116
|
+
title="The metric's sampling interval — the spacing between points, taken straight from the metric."></span></h1>
|
|
110
117
|
<p class="hint">Click-drag across the chart to mark each real incident, add a short description, then
|
|
111
118
|
<b>Export</b>. Save the file into <code class="k">incidents/__METRIC__/</code> and run
|
|
112
119
|
<code class="k">dtk autotune --select __METRIC__ --incidents incidents/__METRIC__/</code></p>
|
|
@@ -129,7 +136,7 @@ _TEMPLATE = """<!doctype html>
|
|
|
129
136
|
<div id="empty" class="empty">No incidents marked yet — drag across a span on the chart above.</div>
|
|
130
137
|
<ul id="list"></ul>
|
|
131
138
|
<footer>All times UTC · self-contained, nothing leaves your browser · re-label any time —
|
|
132
|
-
exports are versioned (<code>__METRIC__-<timestamp>.yml</code>), so keep every round in
|
|
139
|
+
exports are versioned (<code>__METRIC__[-<name>]-<timestamp>.yml</code>), so keep every round in
|
|
133
140
|
<code>incidents/__METRIC__/</code>. Generated by <code>dtk autotune --label</code>.</footer>
|
|
134
141
|
</div>
|
|
135
142
|
<script>
|
|
@@ -138,6 +145,9 @@ const DATA = __PAYLOAD__;
|
|
|
138
145
|
// and Export POSTs straight into incidents/<metric>/. As a static file it is null,
|
|
139
146
|
// and Export falls back to a browser download.
|
|
140
147
|
const SAVE_URL = __SAVE_URL__;
|
|
148
|
+
// The metric's sampling interval (seconds). Passed straight from the metric when
|
|
149
|
+
// known; otherwise inferred from the median spacing of points.
|
|
150
|
+
const INTERVAL_S = __INTERVAL__;
|
|
141
151
|
const pts = DATA.points.map(p => ({ts: Date.parse(p.t.replace(' ','T')+'Z'), v: p.v}));
|
|
142
152
|
const N = pts.length;
|
|
143
153
|
const vraw = pts.filter(p => p.v !== null).map(p => p.v);
|
|
@@ -442,10 +452,13 @@ exportBtn.onclick = () => {
|
|
|
442
452
|
const d=new Date();
|
|
443
453
|
const stamp=d.getUTCFullYear()+pad2(d.getUTCMonth()+1)+pad2(d.getUTCDate())+'T'
|
|
444
454
|
+pad2(d.getUTCHours())+pad2(d.getUTCMinutes())+pad2(d.getUTCSeconds())+'Z';
|
|
445
|
-
|
|
455
|
+
// Always name the file after the metric; the optional set name is a suffix.
|
|
456
|
+
const s = name.trim() ? slug(name) : '';
|
|
457
|
+
const suffix = (s && s !== '__METRIC__') ? '-' + s : '';
|
|
458
|
+
const fname = '__METRIC__' + suffix + '-' + stamp + '.yml';
|
|
446
459
|
const blob=new Blob([y], {type:'text/yaml'}); const a=document.createElement('a');
|
|
447
|
-
a.href=URL.createObjectURL(blob); a.download=
|
|
448
|
-
setMsg('Downloaded ' +
|
|
460
|
+
a.href=URL.createObjectURL(blob); a.download=fname; a.click();
|
|
461
|
+
setMsg('Downloaded ' + fname + ' — move it into incidents/__METRIC__/ and re-run.', 'info');
|
|
449
462
|
}
|
|
450
463
|
};
|
|
451
464
|
|
|
@@ -455,19 +468,33 @@ function drawAll() { draw(); drawOverview();
|
|
|
455
468
|
function fit() { dpr = window.devicePixelRatio || 1;
|
|
456
469
|
c.width=c.clientWidth*dpr; c.height=c.clientHeight*dpr;
|
|
457
470
|
ov.width=ov.clientWidth*dpr; ov.height=ov.clientHeight*dpr; drawAll(); }
|
|
471
|
+
function fmtInterval(s) { if (s<=0) return '?';
|
|
472
|
+
if (s%86400===0) return (s/86400)+'d'; if (s%3600===0) return (s/3600)+'h';
|
|
473
|
+
if (s%60===0) return (s/60)+'min'; return s+'s'; }
|
|
474
|
+
function medianIntervalSec() { if (N<2) return 0;
|
|
475
|
+
const d=[]; for (let i=1;i<N;i++) d.push(pts[i].ts-pts[i-1].ts);
|
|
476
|
+
d.sort((a,b)=>a-b); return Math.round(d[Math.floor(d.length/2)]/1000); }
|
|
477
|
+
const intervalSec = (typeof INTERVAL_S==='number' && INTERVAL_S>0) ? INTERVAL_S : medianIntervalSec();
|
|
478
|
+
document.getElementById('intervalchip').innerHTML =
|
|
479
|
+
'<span class="d"></span>interval <b>'+fmtInterval(intervalSec)+'</b>';
|
|
458
480
|
window.addEventListener('resize', fit); fit(); render();
|
|
459
481
|
</script>
|
|
460
482
|
"""
|
|
461
483
|
|
|
462
484
|
|
|
463
485
|
def render_labeler_html(
|
|
464
|
-
metric_name: str,
|
|
486
|
+
metric_name: str,
|
|
487
|
+
data: dict[str, np.ndarray],
|
|
488
|
+
*,
|
|
489
|
+
save_url: str | None = None,
|
|
490
|
+
interval_seconds: int | None = None,
|
|
465
491
|
) -> str:
|
|
466
492
|
"""Return a self-contained HTML labeler page for *metric_name*'s series.
|
|
467
493
|
|
|
468
494
|
With ``save_url`` (set by ``dtk autotune --label``'s local server) the page's
|
|
469
495
|
Export button POSTs the labels straight to that endpoint; without it (a static
|
|
470
|
-
file) Export falls back to a browser download.
|
|
496
|
+
file) Export falls back to a browser download. ``interval_seconds`` is the
|
|
497
|
+
metric's sampling interval shown as a chip (inferred from the data if omitted).
|
|
471
498
|
"""
|
|
472
499
|
import json
|
|
473
500
|
|
|
@@ -481,5 +508,6 @@ def render_labeler_html(
|
|
|
481
508
|
return (
|
|
482
509
|
_TEMPLATE.replace("__PAYLOAD__", payload)
|
|
483
510
|
.replace("__SAVE_URL__", json.dumps(save_url))
|
|
511
|
+
.replace("__INTERVAL__", json.dumps(interval_seconds))
|
|
484
512
|
.replace("__METRIC__", metric_name)
|
|
485
513
|
)
|
|
@@ -31,9 +31,12 @@ _MAX_BODY = 5_000_000 # generous cap on the posted labels payload
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _sanitize(name: str) -> str:
|
|
34
|
-
"""Filesystem-safe slug for a label-set name;
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
"""Filesystem-safe slug for a label-set name; ``""`` when blank.
|
|
35
|
+
|
|
36
|
+
Used as an optional *suffix* appended to the metric-named filename, so a blank
|
|
37
|
+
name simply yields no suffix (the file is named after the metric alone).
|
|
38
|
+
"""
|
|
39
|
+
return _NAME_RE.sub("-", name.strip().lower()).strip("-")
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
def _stamp() -> str:
|
|
@@ -87,14 +90,16 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
87
90
|
|
|
88
91
|
payload = json.loads(self.rfile.read(length).decode("utf-8"))
|
|
89
92
|
yaml_text = str(payload.get("yaml", ""))
|
|
90
|
-
|
|
93
|
+
suffix = _sanitize(str(payload.get("name", "")))
|
|
91
94
|
raw = _yaml.safe_load(yaml_text)
|
|
92
95
|
# validate against the canonical schema before writing anything
|
|
93
96
|
parse_incident_labels(
|
|
94
97
|
raw, interval_seconds=srv.interval_seconds, metric_name=srv.metric
|
|
95
98
|
)
|
|
96
99
|
srv.incidents_dir.mkdir(parents=True, exist_ok=True)
|
|
97
|
-
|
|
100
|
+
# Always name the file after the metric; the optional set name is a suffix.
|
|
101
|
+
stem = f"{srv.metric}-{suffix}" if suffix else srv.metric
|
|
102
|
+
out = srv.incidents_dir / f"{stem}-{_stamp()}.yml"
|
|
98
103
|
out.write_text(yaml_text, encoding="utf-8")
|
|
99
104
|
srv.saved_path = out
|
|
100
105
|
except Exception as exc:
|
|
@@ -126,7 +131,10 @@ def build_label_server(
|
|
|
126
131
|
server.incidents_dir = incidents_dir
|
|
127
132
|
server.interval_seconds = interval_seconds
|
|
128
133
|
server.html = render_labeler_html(
|
|
129
|
-
metric_name,
|
|
134
|
+
metric_name,
|
|
135
|
+
data,
|
|
136
|
+
save_url=f"http://127.0.0.1:{port}/save?token={token}",
|
|
137
|
+
interval_seconds=interval_seconds,
|
|
130
138
|
)
|
|
131
139
|
return server, f"http://127.0.0.1:{port}/?token={token}"
|
|
132
140
|
|
|
@@ -53,7 +53,8 @@ dtk autotune --select <sel> [--incidents FILE] [--label] [--scoring METRIC] \
|
|
|
53
53
|
non-interactively) tunes **unsupervised**.
|
|
54
54
|
- `--label` — open the interactive labeler (zoom/pan, edit incident edges,
|
|
55
55
|
per-incident descriptions, named sets). **Default:** a local 127.0.0.1 server +
|
|
56
|
-
browser; **Save & tune** writes `incidents/<
|
|
56
|
+
browser; **Save & tune** writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml`
|
|
57
|
+
(named after the metric, optional set name as a suffix) and the run
|
|
57
58
|
**continues into tuning on it**. `--no-serve` writes a static
|
|
58
59
|
`metrics/<name>__labeler.html` (Export downloads the file) and exits; `--no-open`
|
|
59
60
|
prints the URL instead of launching a browser.
|
|
@@ -91,7 +92,8 @@ user to recall timestamps** — it is the easiest, most reliable path:
|
|
|
91
92
|
2. The user marks incidents on the chart (scroll to zoom, drag the navigator to
|
|
92
93
|
move, click-drag to mark, drag an incident's edges to adjust, add a
|
|
93
94
|
description, optionally name the set), then clicks **Save & tune**.
|
|
94
|
-
3. That writes `incidents/<
|
|
95
|
+
3. That writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml` automatically
|
|
96
|
+
(named after the metric, optional set name as a suffix; versioned —
|
|
95
97
|
re-labeling never overwrites) and the **same command continues into the tuning
|
|
96
98
|
run** on it. No manual file moving.
|
|
97
99
|
4. To re-tune later on saved sets, point `--incidents` at the folder
|
{detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
@@ -96,8 +96,9 @@ user through it:
|
|
|
96
96
|
with an optional **description**). Adjust one by dragging its **edges**, or its
|
|
97
97
|
**middle** to move it; optionally **name the set**; *remove* / *Clear all* fix
|
|
98
98
|
mistakes.
|
|
99
|
-
3. Click **Save & tune**. The server writes `incidents/<
|
|
100
|
-
automatically (
|
|
99
|
+
3. Click **Save & tune**. The server writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml`
|
|
100
|
+
automatically (named after the metric, with the optional set name as a suffix;
|
|
101
|
+
versioned — re-labeling never overwrites) and the **same command
|
|
101
102
|
continues into the tuning run on it**. No manual file moving.
|
|
102
103
|
|
|
103
104
|
(`--no-serve` is the offline fallback: it writes a static
|
|
@@ -100,9 +100,9 @@ _LABELS_GLOBS = ("*.yml", "*.yaml", "*.json")
|
|
|
100
100
|
def _labels_files(directory: Path) -> list[Path]:
|
|
101
101
|
"""All labels files in *directory*, oldest→newest.
|
|
102
102
|
|
|
103
|
-
The labeler saves versioned, ISO-stamped names (``<set
|
|
104
|
-
chronologically, so name order is chronological (tie-broken by mtime).
|
|
105
|
-
lets ``incidents/<metric>/`` keep every labeling round on disk.
|
|
103
|
+
The labeler saves versioned, ISO-stamped names (``<metric>[-<set>]-<UTC>.yml``)
|
|
104
|
+
which sort chronologically, so name order is chronological (tie-broken by mtime).
|
|
105
|
+
This lets ``incidents/<metric>/`` keep every labeling round on disk.
|
|
106
106
|
"""
|
|
107
107
|
files: list[Path] = []
|
|
108
108
|
for pattern in _LABELS_GLOBS:
|
|
@@ -348,7 +348,7 @@ def _tune_one(
|
|
|
348
348
|
if label:
|
|
349
349
|
click.echo(click.style(f"Processing metric: {name}", fg="cyan", bold=True))
|
|
350
350
|
if no_serve:
|
|
351
|
-
html = render_labeler_html(name, data)
|
|
351
|
+
html = render_labeler_html(name, data, interval_seconds=interval_seconds)
|
|
352
352
|
out = project_root / "metrics" / f"{metric_path.stem}__labeler.html"
|
|
353
353
|
out.write_text(html, encoding="utf-8")
|
|
354
354
|
click.echo(f" Wrote labeler: {out.relative_to(project_root)}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.23.1 → detectkit-0.24.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|