detectkit 0.25.0__tar.gz → 0.26.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.25.0/detectkit.egg-info → detectkit-0.26.1}/PKG-INFO +1 -1
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/__init__.py +1 -1
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/html_labeler.py +74 -17
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/autotune.md +4 -2
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +3 -0
- {detectkit-0.25.0 → detectkit-0.26.1/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.25.0 → detectkit-0.26.1}/LICENSE +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/MANIFEST.in +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/README.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/label_server.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/result.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/_output.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/main.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/config/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/config/profile.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/config/project_config.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/config/validator.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/core/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/core/interval.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/core/models.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/database/tables.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit/utils/stats.py +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/pyproject.toml +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/requirements.txt +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/setup.cfg +0 -0
- {detectkit-0.25.0 → detectkit-0.26.1}/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.26.1"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -121,6 +121,9 @@ _TEMPLATE = """<!doctype html>
|
|
|
121
121
|
.thbar input.num { width: 84px; font-family: var(--mono); }
|
|
122
122
|
.thbar input:focus, .thbar select:focus { outline: none; border-color: var(--nodata); }
|
|
123
123
|
.thbar button { padding: 7px 13px; }
|
|
124
|
+
.thbar .thscope { color: var(--faint); font-size: 12px; white-space: nowrap; }
|
|
125
|
+
.thbar .thscope.hint { font-style: italic; }
|
|
126
|
+
.thbar .thscope b { color: var(--nodata); font-weight: 600; font-style: normal; }
|
|
124
127
|
canvas#c { width: 100%; height: clamp(300px, 44vh, 500px); display:block; touch-action: none;
|
|
125
128
|
background: var(--term-surface); border: 1px solid var(--term-border); border-radius: 10px; cursor: crosshair; }
|
|
126
129
|
.zoombar { display:flex; align-items:center; gap:8px; margin: 10px 0 6px; }
|
|
@@ -177,6 +180,8 @@ _TEMPLATE = """<!doctype html>
|
|
|
177
180
|
<label>bridge gaps ≤
|
|
178
181
|
<input id="thgap" class="num" type="number" min="0" step="1" value="0" /> intervals
|
|
179
182
|
</label>
|
|
183
|
+
<span id="thscope" class="thscope"></span>
|
|
184
|
+
<button id="thwin" class="ghost" style="display:none" title="capture across the whole current view again">↺ whole view</button>
|
|
180
185
|
<button id="thadd" class="primary" disabled>Add 0 spans</button>
|
|
181
186
|
<button id="thdone" class="ghost">Done</button>
|
|
182
187
|
</div>
|
|
@@ -189,8 +194,10 @@ _TEMPLATE = """<!doctype html>
|
|
|
189
194
|
<canvas id="ov" aria-label="navigator — drag the window to pan, its edges to stretch the view"></canvas>
|
|
190
195
|
<div class="navhint">Drag on an empty area to mark an incident · drag an existing incident's edges to
|
|
191
196
|
adjust it, or its middle to move it · click its <b>✕</b> (or select it and press Delete) to remove it ·
|
|
192
|
-
use <b>Threshold capture</b> to grab every span past a horizontal line at once
|
|
193
|
-
the chart to it
|
|
197
|
+
use <b>Threshold capture</b> to grab every span past a horizontal line at once (click sets the line; drag
|
|
198
|
+
across the chart to limit it to a time window — handy when the metric behaves differently across periods) ·
|
|
199
|
+
<b>focus</b> a row to jump the chart to it · scroll to zoom, double-click to reset · drag the navigator
|
|
200
|
+
window below to pan.</div>
|
|
194
201
|
<div id="empty" class="empty">No incidents marked yet — drag across a span on the chart above.</div>
|
|
195
202
|
<ul id="list"></ul>
|
|
196
203
|
<footer>All times UTC · self-contained, nothing leaves your browser · re-label any time —
|
|
@@ -232,6 +239,9 @@ let dpr = 1, hover = null, dragging = null, ovAct = null;
|
|
|
232
239
|
// selObj: the currently selected incident (object ref, survives re-sorting);
|
|
233
240
|
// hoverRow/hoverDel: list-row / ✕-handle hover targets; threshold-capture state.
|
|
234
241
|
let selObj = null, hoverRow = -1, hoverDel = -1, thMode = false, thHover = null;
|
|
242
|
+
// Threshold-capture window: thDown tracks a press, thDragWin a live drag, capWin
|
|
243
|
+
// the committed custom window (null → capture within the current view).
|
|
244
|
+
let thDown = null, thDragWin = null, capWin = null;
|
|
235
245
|
|
|
236
246
|
const clamp = (x,a,b) => Math.max(a, Math.min(b, x));
|
|
237
247
|
const vspan = () => viewMax - viewMin;
|
|
@@ -304,18 +314,27 @@ const vAt = clientY => { const r=c.getBoundingClientRect();
|
|
|
304
314
|
// The active threshold value: the locked input wins, else the live cursor value.
|
|
305
315
|
function thEff() { const s=thvalEl.value.trim();
|
|
306
316
|
return (s!=='' && !isNaN(Number(s))) ? Number(s) : thHover; }
|
|
307
|
-
//
|
|
317
|
+
// The active capture window: a live/committed custom window, else the current
|
|
318
|
+
// view — so the threshold only grabs the period you're looking at (or painted),
|
|
319
|
+
// not the whole series. Returns [lo, hi] in ms.
|
|
320
|
+
function capRange() {
|
|
321
|
+
const w = thDragWin || capWin;
|
|
322
|
+
if (w) return [Math.min(w.a,w.b), Math.max(w.a,w.b)];
|
|
323
|
+
return [viewMin, viewMax]; }
|
|
324
|
+
// Contiguous runs of points on the chosen side of the line within the capture
|
|
325
|
+
// window, bridging short gaps.
|
|
308
326
|
function thRuns() { const val=thEff(); if (val===null) return [];
|
|
309
327
|
const dir=thdirEl.value, gapMax=Math.max(0, parseInt(thgapEl.value)||0);
|
|
328
|
+
const win=capRange(), lo=win[0], hi=win[1];
|
|
310
329
|
const runs=[]; let s=null, e=null, gap=0;
|
|
311
|
-
for (let i=0;i<N;i++) { const p=pts[i];
|
|
330
|
+
for (let i=0;i<N;i++) { const p=pts[i]; if (p.ts<lo || p.ts>hi) continue;
|
|
312
331
|
const q = p.v!==null && (dir==='above' ? p.v>val : p.v<val);
|
|
313
332
|
if (q) { if (s===null) s=p.ts; e=p.ts; gap=0; }
|
|
314
333
|
else if (s!==null) { gap++; if (gap>gapMax) { runs.push([s,e]); s=null; gap=0; } } }
|
|
315
334
|
if (s!==null) runs.push([s,e]);
|
|
316
335
|
return runs; }
|
|
317
336
|
function thCount() { const n=thRuns().length;
|
|
318
|
-
thaddEl.textContent = 'Add '+n+' span'+(n===1?'':'s'); thaddEl.disabled = n===0; }
|
|
337
|
+
thaddEl.textContent = 'Add '+n+' span'+(n===1?'':'s'); thaddEl.disabled = n===0; updateThWin(); }
|
|
319
338
|
// Add a captured span, merging it into any overlapping incidents (a single span
|
|
320
339
|
// can bridge several) into one band that keeps the first one's label.
|
|
321
340
|
function addCaptured(a,b) {
|
|
@@ -328,8 +347,8 @@ function addCaptured(a,b) {
|
|
|
328
347
|
if (host===null) incidents.push({a, b, label:''}); }
|
|
329
348
|
function toggleTh(on) { thMode = (on===undefined) ? !thMode : !!on;
|
|
330
349
|
thbtnEl.classList.toggle('active', thMode); thbarEl.style.display = thMode ? 'flex' : 'none';
|
|
331
|
-
if (thMode) { hover=null; } else { thHover=null; }
|
|
332
|
-
c.style.cursor = thMode ? '
|
|
350
|
+
if (thMode) { hover=null; } else { thHover=null; thDown=null; thDragWin=null; capWin=null; updateThWin(); }
|
|
351
|
+
c.style.cursor = thMode ? 'crosshair' : 'crosshair'; if (thMode) { updateThWin(); thCount(); } draw(); }
|
|
333
352
|
|
|
334
353
|
function rr(g,x,y,w,h,r) { g.beginPath();
|
|
335
354
|
g.moveTo(x+r,y); g.arcTo(x+w,y,x+w,y+h,r); g.arcTo(x+w,y+h,x,y+h,r);
|
|
@@ -376,9 +395,20 @@ function draw() {
|
|
|
376
395
|
if (dragging && dragging.mode==='new') { const x0=px(dragging.a), x1=px(dragging.b);
|
|
377
396
|
ctx.fillStyle='rgba(240,173,78,0.28)'; ctx.fillRect(Math.min(x0,x1), M.t*dpr, Math.abs(x1-x0), plotH()); }
|
|
378
397
|
drawSeries(ctx, px, py, viewMin, viewMax, M.l*dpr, plotW(), '#d15b36', 1.5);
|
|
379
|
-
if (thMode) {
|
|
380
|
-
|
|
381
|
-
|
|
398
|
+
if (thMode) {
|
|
399
|
+
const win=capRange(), narrow = !!(thDragWin || capWin);
|
|
400
|
+
const xlo=clamp(px(win[0]), M.l*dpr, c.width-M.r*dpr), xhi=clamp(px(win[1]), M.l*dpr, c.width-M.r*dpr);
|
|
401
|
+
if (narrow) { // dim everything outside the capture window
|
|
402
|
+
ctx.fillStyle='rgba(27,25,22,0.5)';
|
|
403
|
+
ctx.fillRect(M.l*dpr, M.t*dpr, xlo-M.l*dpr, plotH());
|
|
404
|
+
ctx.fillRect(xhi, M.t*dpr, (c.width-M.r*dpr)-xhi, plotH());
|
|
405
|
+
ctx.strokeStyle='rgba(240,173,78,0.7)'; ctx.lineWidth=1*dpr; ctx.setLineDash([3*dpr,3*dpr]);
|
|
406
|
+
ctx.beginPath(); ctx.moveTo(xlo,M.t*dpr); ctx.lineTo(xlo,c.height-M.b*dpr);
|
|
407
|
+
ctx.moveTo(xhi,M.t*dpr); ctx.lineTo(xhi,c.height-M.b*dpr); ctx.stroke(); ctx.setLineDash([]); }
|
|
408
|
+
const val=thEff();
|
|
409
|
+
if (val!==null && val>=vmin && val<=vmax) { const yy=py(val);
|
|
410
|
+
ctx.strokeStyle='#f0ad4e'; ctx.lineWidth=1.5*dpr; ctx.setLineDash([6*dpr,4*dpr]);
|
|
411
|
+
ctx.beginPath(); ctx.moveTo(xlo, yy); ctx.lineTo(xhi, yy); ctx.stroke(); ctx.setLineDash([]); } }
|
|
382
412
|
ctx.restore();
|
|
383
413
|
if (thMode) drawThLabel();
|
|
384
414
|
else if (dragging && !ovAct) drawDragLabel();
|
|
@@ -388,9 +418,13 @@ function draw() {
|
|
|
388
418
|
// Readout while picking a threshold: the line value, side, and how many spans
|
|
389
419
|
// would be captured.
|
|
390
420
|
function drawThLabel() {
|
|
391
|
-
const val=thEff()
|
|
392
|
-
|
|
393
|
-
|
|
421
|
+
const val=thEff(), win=capRange(), narrow = !!(thDragWin || capWin);
|
|
422
|
+
let text;
|
|
423
|
+
if (val===null) { // no line yet — prompt how to use the mode
|
|
424
|
+
text = 'drag the chart to pick a period · hover or type a value to set the line';
|
|
425
|
+
} else { const n=thRuns().length;
|
|
426
|
+
text='line '+fmtVal(val)+' · '+thdirEl.value+' · '+n+' span'+(n===1?'':'s')
|
|
427
|
+
+ (narrow ? (' · '+fmtDur(win[1]-win[0])+' window') : ''); }
|
|
394
428
|
ctx.font=(11*dpr)+'px ui-monospace, monospace';
|
|
395
429
|
const tw=ctx.measureText(text).width, bw=tw+14*dpr, bh=22*dpr, bx=M.l*dpr+6*dpr, by=M.t*dpr+2;
|
|
396
430
|
ctx.fillStyle='rgba(27,25,22,0.96)'; ctx.strokeStyle='#f0ad4e'; ctx.lineWidth=1*dpr;
|
|
@@ -491,7 +525,9 @@ function hitIncident(clientX, clientY) {
|
|
|
491
525
|
return null;
|
|
492
526
|
}
|
|
493
527
|
c.addEventListener('mousedown', e => {
|
|
494
|
-
|
|
528
|
+
// In threshold mode a press either sets the line (a click) or paints a capture
|
|
529
|
+
// window (a horizontal drag) — resolved on mouseup by how far it moved.
|
|
530
|
+
if (thMode) { thDown = {x:e.clientX, ts:tsAt(e.clientX)}; thHover = vAt(e.clientY); thCount(); draw(); return; }
|
|
495
531
|
const hit = hitIncident(e.clientX, e.clientY), t = tsAt(e.clientX);
|
|
496
532
|
if (hit && hit.edge==='del') { removeIncident(incidents[hit.i]); return; }
|
|
497
533
|
if (hit && hit.edge==='move') { const iv=incidents[hit.i]; selObj=iv;
|
|
@@ -501,7 +537,10 @@ c.addEventListener('mousedown', e => {
|
|
|
501
537
|
else { selObj=null; dragging={mode:'new', a:t, b:t, sx:e.clientX, cx:e.clientX}; draw(); }
|
|
502
538
|
});
|
|
503
539
|
c.addEventListener('mousemove', e => { if (ovAct) return;
|
|
504
|
-
if (thMode && !dragging) {
|
|
540
|
+
if (thMode && !dragging) {
|
|
541
|
+
if (thDown && Math.abs(e.clientX - thDown.x) > 6) { thDragWin = {a: thDown.ts, b: tsAt(e.clientX)}; }
|
|
542
|
+
else { if (thDown) thDragWin = null; thHover = vAt(e.clientY); }
|
|
543
|
+
thCount(); draw(); return; }
|
|
505
544
|
if (dragging) {
|
|
506
545
|
dragging.cx=e.clientX; const t=tsAt(e.clientX);
|
|
507
546
|
if (dragging.mode==='new') { dragging.b=t; }
|
|
@@ -539,8 +578,16 @@ window.addEventListener('mousemove', e => { if (!ovAct) return; const t=ovTsAtCs
|
|
|
539
578
|
if (ovAct.type==='l') setView(Math.min(t, viewMax-minSpan), viewMax);
|
|
540
579
|
else if (ovAct.type==='r') setView(viewMin, Math.max(t, viewMin+minSpan));
|
|
541
580
|
else { const d=t-ovAct.grab; setView(ovAct.vMin+d, ovAct.vMax+d); } });
|
|
542
|
-
window.addEventListener('mouseup',
|
|
581
|
+
window.addEventListener('mouseup', e => {
|
|
543
582
|
if (ovAct) { ovAct=null; return; }
|
|
583
|
+
if (thMode && thDown) {
|
|
584
|
+
if (Math.abs(e.clientX - thDown.x) > 6) { // a drag → set the capture window
|
|
585
|
+
const a=thDown.ts, b=tsAt(e.clientX); capWin = {a:Math.min(a,b), b:Math.max(a,b)};
|
|
586
|
+
} else { // a click → set the threshold line value
|
|
587
|
+
thvalEl.value = String(Math.round(vAt(e.clientY)*1000)/1000);
|
|
588
|
+
}
|
|
589
|
+
thDown=null; thDragWin=null; updateThWin(); thCount(); draw(); return;
|
|
590
|
+
}
|
|
544
591
|
if (!dragging) return;
|
|
545
592
|
if (dragging.mode==='new') {
|
|
546
593
|
if (Math.abs(dragging.cx-dragging.sx) > 4) {
|
|
@@ -574,7 +621,17 @@ window.focusInc = i => { const iv=incidents[i]; if (!iv) return; selObj=iv;
|
|
|
574
621
|
const thbtnEl=document.getElementById('thbtn'), thbarEl=document.getElementById('thbar');
|
|
575
622
|
const thvalEl=document.getElementById('thval'), thdirEl=document.getElementById('thdir');
|
|
576
623
|
const thgapEl=document.getElementById('thgap'), thaddEl=document.getElementById('thadd');
|
|
577
|
-
const thdoneEl=document.getElementById('thdone');
|
|
624
|
+
const thdoneEl=document.getElementById('thdone'), thwinEl=document.getElementById('thwin');
|
|
625
|
+
const thscopeEl=document.getElementById('thscope');
|
|
626
|
+
// Always-visible scope readout so the time-window control is discoverable
|
|
627
|
+
// (the ✕/↺ reset only appears once a window exists).
|
|
628
|
+
function updateThWin() {
|
|
629
|
+
thwinEl.style.display = capWin ? '' : 'none';
|
|
630
|
+
if (!thscopeEl) return;
|
|
631
|
+
const w = thDragWin || capWin;
|
|
632
|
+
if (w) { const r=capRange(); thscopeEl.innerHTML = 'period: <b>'+fmtDur(r[1]-r[0])+'</b>'; thscopeEl.className='thscope'; }
|
|
633
|
+
else { thscopeEl.textContent = 'period: current view — drag the chart to limit it'; thscopeEl.className='thscope hint'; } }
|
|
634
|
+
thwinEl.onclick = () => { capWin=null; updateThWin(); thCount(); draw(); };
|
|
578
635
|
thbtnEl.onclick = () => toggleTh();
|
|
579
636
|
thdoneEl.onclick = () => toggleTh(false);
|
|
580
637
|
thdirEl.onchange = () => { thCount(); draw(); };
|
|
@@ -99,8 +99,10 @@ user to recall timestamps** — it is the easiest, most reliable path:
|
|
|
99
99
|
move, click-drag to mark, drag an incident's edges to adjust, add a
|
|
100
100
|
description, optionally name the set), then clicks **Save & tune**. For many
|
|
101
101
|
clear outliers, **Threshold capture** grabs every span past a horizontal line
|
|
102
|
-
at once (above/below, with an optional gap-bridge);
|
|
103
|
-
|
|
102
|
+
at once (above/below, with an optional gap-bridge); it captures within the
|
|
103
|
+
current view by default, and dragging across the chart limits it to a time
|
|
104
|
+
window (different boundary per period). Each band's ✕ (or select + Delete)
|
|
105
|
+
removes one, and **focus** on a list row jumps the chart to it.
|
|
104
106
|
3. That writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml` automatically
|
|
105
107
|
(named after the metric, optional set name as a suffix; versioned —
|
|
106
108
|
re-labeling never overwrites) and the **same command continues into the tuning
|
{detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
@@ -101,6 +101,9 @@ user through it:
|
|
|
101
101
|
3. When many outliers are obvious, use **Threshold capture**: set a horizontal
|
|
102
102
|
line (hover the chart or type a value), pick **above/below**, optionally bridge
|
|
103
103
|
small gaps, and **Add N spans** marks them all at once — then tidy with the ✕.
|
|
104
|
+
It captures within the current view by default; **drag across the chart** to
|
|
105
|
+
limit it to a time window (so a metric that behaves differently across periods
|
|
106
|
+
can take a different boundary in each). **↺ whole view** clears the window.
|
|
104
107
|
4. Click **Save & tune**. The server writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml`
|
|
105
108
|
automatically (named after the metric, with the optional set name as a suffix;
|
|
106
109
|
versioned — re-labeling never overwrites) and the **same command
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.25.0 → detectkit-0.26.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.25.0 → detectkit-0.26.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
|