detectkit 0.36.2__tar.gz → 0.37.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.36.2/detectkit.egg-info → detectkit-0.37.0}/PKG-INFO +1 -1
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/__init__.py +1 -1
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/html_labeler.py +67 -10
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/autotune.md +7 -4
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/cli.md +12 -6
- detectkit-0.37.0/detectkit/tuning/assets/tune.js +132 -0
- {detectkit-0.36.2 → detectkit-0.37.0/detectkit.egg-info}/PKG-INFO +1 -1
- detectkit-0.36.2/detectkit/tuning/assets/tune.js +0 -131
- {detectkit-0.36.2 → detectkit-0.37.0}/LICENSE +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/MANIFEST.in +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/README.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/label_server.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/core/models.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/tuning/html.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/tuning/server.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/pyproject.toml +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/requirements.txt +0 -0
- {detectkit-0.36.2 → detectkit-0.37.0}/setup.cfg +0 -0
- {detectkit-0.36.2 → detectkit-0.37.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.37.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -10,9 +10,11 @@ It can be **seeded with existing incidents** (the ``incidents=`` argument) so a
|
|
|
10
10
|
labels file can be opened and edited in place — the round-trip flow of filling
|
|
11
11
|
incidents in over time. Beyond click-drag marking it supports **threshold
|
|
12
12
|
capture** (grab every contiguous span above/below a horizontal line in one
|
|
13
|
-
gesture), **
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
gesture), **lasso capture** (draw a freeform loop around a cloud of points and
|
|
14
|
+
turn each grid-adjacent run — small gaps bridged — into one incident span),
|
|
15
|
+
**on-chart deletion** (each band carries an ✕ handle; the selected band also
|
|
16
|
+
responds to the Delete key), and an in-browser **Import** button that loads a
|
|
17
|
+
labels file you pick (YAML/JSON).
|
|
16
18
|
|
|
17
19
|
The page is offline-only — a browser cannot write to the project, so Export
|
|
18
20
|
downloads a **versioned** file named after the metric, with the optional set name
|
|
@@ -186,6 +188,7 @@ _TEMPLATE = """<!doctype html>
|
|
|
186
188
|
<input id="setname" class="setname" type="text" placeholder="name this set (optional)" />
|
|
187
189
|
<button id="export" class="primary">Export labels</button>
|
|
188
190
|
<button id="thbtn" class="ghost" title="capture every span above or below a horizontal line">Threshold capture</button>
|
|
191
|
+
<button id="lassobtn" class="ghost" title="draw a freeform loop around a cloud of points to grab them all at once">Lasso capture</button>
|
|
189
192
|
<button id="clear" class="ghost">Clear all</button>
|
|
190
193
|
<span id="summary" class="summary"></span>
|
|
191
194
|
</div>
|
|
@@ -216,8 +219,9 @@ _TEMPLATE = """<!doctype html>
|
|
|
216
219
|
adjust it, or its middle to move it · click its <b>✕</b> (or select it and press Delete) to remove it ·
|
|
217
220
|
use <b>Threshold capture</b> to grab every span past a horizontal line at once (click sets the line; drag
|
|
218
221
|
across the chart to limit it to a time window — handy when the metric behaves differently across periods) ·
|
|
219
|
-
<b>
|
|
220
|
-
|
|
222
|
+
use <b>Lasso capture</b> to draw a freeform loop around a cloud of points and turn each consecutive run into
|
|
223
|
+
an incident · <b>focus</b> a row to jump the chart to it · scroll to zoom, double-click to reset · drag the
|
|
224
|
+
navigator window below to pan.</div>
|
|
221
225
|
<div id="empty" class="empty">No incidents marked yet — drag across a span on the chart above.</div>
|
|
222
226
|
<ul id="list"></ul>
|
|
223
227
|
<footer>All times UTC · self-contained, nothing leaves your browser · re-label any time —
|
|
@@ -264,6 +268,10 @@ let selObj = null, hoverRow = -1, hoverDel = -1, thMode = false, thHover = null;
|
|
|
264
268
|
// Threshold-capture window: thDown tracks a press, thDragWin a live drag, capWin
|
|
265
269
|
// the committed custom window (null → capture within the current view).
|
|
266
270
|
let thDown = null, thDragWin = null, capWin = null;
|
|
271
|
+
// Lasso capture: draw a freeform loop around a cloud of points and turn each
|
|
272
|
+
// grid-adjacent run (small gaps bridged) into one incident span. lassoDrag holds
|
|
273
|
+
// the in-progress loop as DATA coords ({t, v}); off until the tool is toggled.
|
|
274
|
+
let lassoMode = false, lassoDrag = null; const LASSO_BRIDGE = 2;
|
|
267
275
|
// Restore a saved capture window so re-opening a labels file keeps the painted
|
|
268
276
|
// regime scope (only shown once threshold capture is toggled on).
|
|
269
277
|
if (CAPWINS && CAPWINS.length) { const w0 = CAPWINS[0];
|
|
@@ -426,9 +434,47 @@ function addCaptured(a,b) {
|
|
|
426
434
|
if (host===null) incidents.push({a, b, label:''}); }
|
|
427
435
|
function toggleTh(on) { thMode = (on===undefined) ? !thMode : !!on;
|
|
428
436
|
thbtnEl.classList.toggle('active', thMode); thbarEl.style.display = thMode ? 'flex' : 'none';
|
|
429
|
-
if (thMode) { hover=null;
|
|
437
|
+
if (thMode) { hover=null; if (lassoMode) toggleLasso(false); }
|
|
438
|
+
else { thHover=null; thDown=null; thDragWin=null; capWin=null; updateThWin(); }
|
|
430
439
|
c.style.cursor = thMode ? 'crosshair' : 'crosshair'; if (thMode) { updateThWin(); thCount(); } draw(); }
|
|
431
440
|
|
|
441
|
+
// ---- lasso capture --------------------------------------------------------
|
|
442
|
+
// Ray-casting point-in-polygon in DATA space ({t, v}); the loop and the test
|
|
443
|
+
// points map through the same per-axis affine, so screen-inside == data-inside.
|
|
444
|
+
function pointInPoly(t, v, poly) { let inside=false;
|
|
445
|
+
for (let i=0,j=poly.length-1;i<poly.length;j=i++) {
|
|
446
|
+
const ti=poly[i].t, vi=poly[i].v, tj=poly[j].t, vj=poly[j].v;
|
|
447
|
+
if (((vi>v)!==(vj>v)) && t < (tj-ti)*(v-vi)/((vj-vi)||1e-9)+ti) inside=!inside; }
|
|
448
|
+
return inside; }
|
|
449
|
+
// The raw point indices enclosed by the in-progress loop.
|
|
450
|
+
function lassoCapturedIdx() { const out=[]; if (!lassoDrag || lassoDrag.length<3) return out;
|
|
451
|
+
for (let i=0;i<N;i++) { const p=pts[i]; if (p.v===null) continue;
|
|
452
|
+
if (pointInPoly(p.ts, p.v, lassoDrag)) out.push(i); } return out; }
|
|
453
|
+
// Collapse the captured points into incident spans: a new span starts only when
|
|
454
|
+
// the gap to the previous capture exceeds LASSO_BRIDGE grid steps; each span is
|
|
455
|
+
// padded half a step each side so a lone point becomes one full-interval incident.
|
|
456
|
+
function commitLasso() { const cap=lassoCapturedIdx(); if (!cap.length) return;
|
|
457
|
+
const maxGap=(LASSO_BRIDGE+1)*step, half=step/2; let runStart=cap[0], prev=cap[0], added=0;
|
|
458
|
+
const push=(s,e)=>{ addCaptured(pts[s].ts-half, pts[e].ts+half); added++; };
|
|
459
|
+
for (let k=1;k<cap.length;k++) { const idx=cap[k];
|
|
460
|
+
if (pts[idx].ts-pts[prev].ts > maxGap) { push(runStart, prev); runStart=idx; } prev=idx; }
|
|
461
|
+
push(runStart, prev);
|
|
462
|
+
setMsg('Added '+added+' incident'+(added===1?'':'s')+' from the lasso — review and tidy below.', 'ok');
|
|
463
|
+
render(); }
|
|
464
|
+
// The lasso loop (dashed clay outline + faint fill) + a ring on every captured point.
|
|
465
|
+
function drawLasso() { if (!lassoDrag || lassoDrag.length<2) return;
|
|
466
|
+
ctx.beginPath(); ctx.moveTo(px(lassoDrag[0].t), py(lassoDrag[0].v));
|
|
467
|
+
for (let i=1;i<lassoDrag.length;i++) ctx.lineTo(px(lassoDrag[i].t), py(lassoDrag[i].v));
|
|
468
|
+
ctx.closePath(); ctx.fillStyle='rgba(209,91,54,0.08)'; ctx.fill();
|
|
469
|
+
ctx.strokeStyle='rgba(209,91,54,0.9)'; ctx.lineWidth=1.5*dpr; ctx.setLineDash([5*dpr,4*dpr]);
|
|
470
|
+
ctx.stroke(); ctx.setLineDash([]);
|
|
471
|
+
lassoCapturedIdx().forEach(i => { const X=px(pts[i].ts), Y=py(pts[i].v);
|
|
472
|
+
ctx.strokeStyle='#d63232'; ctx.lineWidth=2*dpr; ctx.beginPath(); ctx.arc(X,Y,7*dpr,0,7); ctx.stroke(); }); }
|
|
473
|
+
function toggleLasso(on) { lassoMode = (on===undefined) ? !lassoMode : !!on;
|
|
474
|
+
lassobtnEl.classList.toggle('active', lassoMode);
|
|
475
|
+
if (lassoMode) { hover=null; selObj=null; if (thMode) toggleTh(false); } else { lassoDrag=null; }
|
|
476
|
+
c.style.cursor='crosshair'; draw(); }
|
|
477
|
+
|
|
432
478
|
function rr(g,x,y,w,h,r) { g.beginPath();
|
|
433
479
|
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);
|
|
434
480
|
g.arcTo(x,y+h,x,y,r); g.arcTo(x,y,x+w,y,r); g.closePath(); }
|
|
@@ -496,6 +542,7 @@ function draw() {
|
|
|
496
542
|
if (val!==null && val>=vmin && val<=vmax) { const yy=py(val);
|
|
497
543
|
ctx.strokeStyle='#f0ad4e'; ctx.lineWidth=1.5*dpr; ctx.setLineDash([6*dpr,4*dpr]);
|
|
498
544
|
ctx.beginPath(); ctx.moveTo(xlo, yy); ctx.lineTo(xhi, yy); ctx.stroke(); ctx.setLineDash([]); } }
|
|
545
|
+
if (lassoMode) drawLasso();
|
|
499
546
|
ctx.restore();
|
|
500
547
|
if (thMode) drawThLabel();
|
|
501
548
|
else if (dragging && !ovAct) drawDragLabel();
|
|
@@ -612,7 +659,9 @@ const ovTsAtCss = clientX => { const r=ov.getBoundingClientRect();
|
|
|
612
659
|
const ovEdgeCss = ts => { const r=ov.getBoundingClientRect();
|
|
613
660
|
return r.left + OM.l + (ts-tmin)/fullSpan*(r.width-(OM.l+OM.r)); };
|
|
614
661
|
|
|
615
|
-
c.addEventListener('wheel', e => { e.preventDefault();
|
|
662
|
+
c.addEventListener('wheel', e => { e.preventDefault();
|
|
663
|
+
if (lassoMode && lassoDrag) return; // don't distort the loop mid-draw
|
|
664
|
+
const t=tsAt(e.clientX);
|
|
616
665
|
let s=clamp(vspan()*Math.pow(1.0015, e.deltaY), minSpan, fullSpan);
|
|
617
666
|
const f=(t-viewMin)/(vspan()||1); setView(t-f*s, t-f*s+s); }, {passive:false});
|
|
618
667
|
// Hit-test an existing incident's ✕ handle / edge / body in CSS px.
|
|
@@ -634,6 +683,8 @@ function hitIncident(clientX, clientY) {
|
|
|
634
683
|
return null;
|
|
635
684
|
}
|
|
636
685
|
c.addEventListener('mousedown', e => {
|
|
686
|
+
// Lasso mode: a press starts a freeform loop accumulated on mousemove.
|
|
687
|
+
if (lassoMode) { lassoDrag = [{t: tsAt(e.clientX), v: vAt(e.clientY)}]; draw(); return; }
|
|
637
688
|
// In threshold mode a press either sets the line (a click) or paints a capture
|
|
638
689
|
// window (a horizontal drag) — resolved on mouseup by how far it moved.
|
|
639
690
|
if (thMode) { thDown = {x:e.clientX, ts:tsAt(e.clientX)}; thHover = vAt(e.clientY); thCount(); draw(); return; }
|
|
@@ -646,6 +697,8 @@ c.addEventListener('mousedown', e => {
|
|
|
646
697
|
else { selObj=null; dragging={mode:'new', a:t, b:t, sx:e.clientX, cx:e.clientX}; draw(); }
|
|
647
698
|
});
|
|
648
699
|
c.addEventListener('mousemove', e => { if (ovAct) return;
|
|
700
|
+
if (lassoMode) { if (lassoDrag) { lassoDrag.push({t: tsAt(e.clientX), v: vAt(e.clientY)}); draw(); }
|
|
701
|
+
else { c.style.cursor='crosshair'; } return; }
|
|
649
702
|
if (thMode && !dragging) {
|
|
650
703
|
if (thDown && Math.abs(e.clientX - thDown.x) > 6) { thDragWin = {a: thDown.ts, b: tsAt(e.clientX)}; }
|
|
651
704
|
else { if (thDown) thDragWin = null; thHover = vAt(e.clientY); }
|
|
@@ -689,6 +742,7 @@ window.addEventListener('mousemove', e => { if (!ovAct) return; const t=ovTsAtCs
|
|
|
689
742
|
else { const d=t-ovAct.grab; setView(ovAct.vMin+d, ovAct.vMax+d); } });
|
|
690
743
|
window.addEventListener('mouseup', e => {
|
|
691
744
|
if (ovAct) { ovAct=null; return; }
|
|
745
|
+
if (lassoMode) { if (lassoDrag) { commitLasso(); lassoDrag=null; } draw(); return; }
|
|
692
746
|
if (thMode && thDown) {
|
|
693
747
|
if (Math.abs(e.clientX - thDown.x) > 6) { // a drag → set the capture window
|
|
694
748
|
const a=thDown.ts, b=tsAt(e.clientX); capWin = {a:Math.min(a,b), b:Math.max(a,b)};
|
|
@@ -713,12 +767,13 @@ function removeIncident(iv) { const k=incidents.indexOf(iv); if (k<0) return;
|
|
|
713
767
|
incidents.splice(k,1); if (selObj===iv) selObj=null; hoverDel=-1; render(); }
|
|
714
768
|
window.addEventListener('keydown', e => {
|
|
715
769
|
const t=e.target, typing = t && (t.tagName==='INPUT' || t.tagName==='TEXTAREA' || t.isContentEditable);
|
|
716
|
-
if (e.key==='Escape') { if (
|
|
717
|
-
|
|
770
|
+
if (e.key==='Escape') { if (lassoMode) toggleLasso(false); else if (thMode) toggleTh(false);
|
|
771
|
+
else if (selObj) { selObj=null; draw(); } return; }
|
|
772
|
+
if ((e.key==='Delete' || e.key==='Backspace') && selObj && !typing && !lassoMode) { e.preventDefault(); removeIncident(selObj); }
|
|
718
773
|
});
|
|
719
774
|
|
|
720
775
|
document.getElementById('zreset').onclick = () => setView(tmin, tmax);
|
|
721
|
-
c.addEventListener('dblclick', () => { if (!thMode) setView(tmin, tmax); });
|
|
776
|
+
c.addEventListener('dblclick', () => { if (!thMode && !lassoMode) setView(tmin, tmax); });
|
|
722
777
|
document.getElementById('clear').onclick = () => { incidents.length=0; selObj=null; render(); };
|
|
723
778
|
window.setLabel = (i, val) => { if (incidents[i]) incidents[i].label = val; };
|
|
724
779
|
window.rm = i => { const iv=incidents[i]; if (iv && selObj===iv) selObj=null; incidents.splice(i,1); render(); };
|
|
@@ -743,6 +798,8 @@ function updateThWin() {
|
|
|
743
798
|
thwinEl.onclick = () => { capWin=null; updateThWin(); thCount(); draw(); };
|
|
744
799
|
thbtnEl.onclick = () => toggleTh();
|
|
745
800
|
thdoneEl.onclick = () => toggleTh(false);
|
|
801
|
+
const lassobtnEl=document.getElementById('lassobtn');
|
|
802
|
+
lassobtnEl.onclick = () => toggleLasso();
|
|
746
803
|
thdirEl.onchange = () => { thCount(); draw(); };
|
|
747
804
|
thgapEl.oninput = () => { thCount(); draw(); };
|
|
748
805
|
thvalEl.oninput = () => { thCount(); draw(); };
|
|
@@ -80,9 +80,10 @@ dtk autotune --select <sel> [--incidents FILE] [--label] [--scoring METRIC] \
|
|
|
80
80
|
**continues into tuning on it**. It **seeds from the metric's newest saved set**
|
|
81
81
|
(or from `--incidents FILE|DIR` when given), so re-running `--label` continues
|
|
82
82
|
editing in place. Beyond click-drag marking it offers **Threshold capture**
|
|
83
|
-
(grab every span above/below a horizontal line in one gesture), **
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
(grab every span above/below a horizontal line in one gesture), **Lasso capture**
|
|
84
|
+
(loop around a cloud of points; each grid-adjacent run, gaps bridged, becomes one
|
|
85
|
+
incident span), **on-chart delete** (each band's ✕, or select + Delete key), and
|
|
86
|
+
**Import file…** (load a labels file you pick). `--no-serve` writes a static
|
|
86
87
|
`metrics/<name>__labeler.html` (Export downloads the file) and exits; `--no-open`
|
|
87
88
|
prints the URL instead of launching a browser.
|
|
88
89
|
- `--scoring` — `mcc` (default), `f1`, `f_beta`, `balanced_accuracy`, `roc_auc`,
|
|
@@ -129,7 +130,9 @@ user to recall timestamps** — it is the easiest, most reliable path:
|
|
|
129
130
|
at once (above/below, with an optional gap-bridge); it captures within the
|
|
130
131
|
current view by default, and dragging across the chart limits it to a time
|
|
131
132
|
window (different boundary per period) — the painted window is **saved with the
|
|
132
|
-
set and restored on reopen** (a `capture_windows:` block; metadata only).
|
|
133
|
+
set and restored on reopen** (a `capture_windows:` block; metadata only). For an
|
|
134
|
+
irregular cloud, **Lasso capture** loops around the points freehand and turns
|
|
135
|
+
each grid-adjacent run (small gaps bridged) into one incident span. Each
|
|
133
136
|
band's ✕ (or select + Delete)
|
|
134
137
|
removes one, and **focus** on a list row jumps the chart to it.
|
|
135
138
|
3. That writes `incidents/<metric>/<metric>[-<set>]-<UTC>.yml` automatically
|
|
@@ -89,16 +89,22 @@ datapoints (run `dtk run --steps load` first if empty); the selector must resolv
|
|
|
89
89
|
to a single metric.
|
|
90
90
|
|
|
91
91
|
**Mark incidents + see alert quality live.** A second, **synced** chart beneath
|
|
92
|
-
the detector view
|
|
93
|
-
drag its edges/middle to adjust, ✕ or
|
|
92
|
+
the detector view **mirrors the detector's anomaly dots** and lets you **mark the
|
|
93
|
+
real incidents** (drag to create a span, drag its edges/middle to adjust, ✕ or
|
|
94
|
+
Delete to remove), **Lasso anomalies** (loop around a cloud of anomaly dots — each
|
|
95
|
+
run of consecutive anomalies, small gaps bridged up to `consecutive_anomalies`,
|
|
96
|
+
becomes one incident span), or **Threshold capture**
|
|
94
97
|
every contiguous span past a horizontal line in one shot (set the line by click or
|
|
95
98
|
value, above/below, optional gap-bridging, optionally limited to a painted time
|
|
96
99
|
window — the same tool as the autotune labeler; the window is saved as
|
|
97
|
-
`capture_windows` and restored on reopen
|
|
100
|
+
`capture_windows` and restored on reopen; each captured span is widened to a full
|
|
101
|
+
interval so the alert lands inside). As you tune, a metrics
|
|
98
102
|
bar shows two operator numbers: **incident catch rate (recall)** — how many marked
|
|
99
|
-
incidents your config catches
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
incidents your config catches (an incident is caught when an alert's anomaly
|
|
104
|
+
**streak overlaps** it, not just the fire instant) — and **false-alert rate** —
|
|
105
|
+
what share of alerts fall outside any real incident ("≈1 in N false", keeping a
|
|
106
|
+
decimal so a mostly-false rate doesn't round to a misleading "1 in 1"); only
|
|
107
|
+
incidents within the loaded window are scored. **Save incidents** writes a
|
|
102
108
|
versioned `incidents/<metric>/*.yml` — the same store `dtk autotune` reads, so the
|
|
103
109
|
same labels feed the next supervised tune (it seeds from the newest file on open,
|
|
104
110
|
**anchoring the budget-sized loaded window on the seeded incidents** — ending just
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";(()=>{var yn={"--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 j(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()||yn[t]||"#888"}function wn(t){let d=t.replace("#","").trim();d.length===3&&(d=d[0]+d[0]+d[1]+d[1]+d[2]+d[2]);let h=parseInt(d,16);return d.length!==6||Number.isNaN(h)?[209,91,54]:[h>>16&255,h>>8&255,h&255]}function z(t,d){let[h,e,x]=wn(t);return`rgba(${h},${e},${x},${d})`}function un(t){let d=Math.max(1,window.devicePixelRatio||1),h=t.clientWidth||t.offsetWidth||0,e=t.clientHeight||t.offsetHeight||0;return t.width=Math.round(h*d),t.height=Math.round(e*d),d}var tn=Number.isFinite;function nn(t,d,h,e,x,N,F,b,i,g,y,$){let oe=d.length,L=Math.max(1,Math.round(F)),T=x-e||1,ve=0;for(let w=0;w<oe;w++){let te=h[w];!tn(te)||d[w]<e||d[w]>x||ve++}if(t.strokeStyle=g,t.lineWidth=y*$,t.lineJoin="round",t.beginPath(),ve<=L){let w=!1;for(let te=0;te<oe;te++){let Q=h[te],K=d[te];if(!tn(Q)||K<e||K>x){w=!1;continue}let re=b(K),Y=i(Q);w?t.lineTo(re,Y):(t.moveTo(re,Y),w=!0)}}else{let w=new Array(L).fill(null),te=new Array(L).fill(null);for(let K=0;K<oe;K++){let re=h[K],Y=d[K];if(!tn(re)||Y<e||Y>x)continue;let V=Math.floor((Y-e)/T*(L-1));V=V<0?0:V>L-1?L-1:V,(w[V]===null||re<w[V])&&(w[V]=re),(te[V]===null||re>te[V])&&(te[V]=re)}let Q=!1;for(let K=0;K<L;K++){if(te[K]===null){Q=!1;continue}let re=N+K,Y=i(te[K]),V=i(w[K]);Q?t.lineTo(re,Y):(t.moveTo(re,Y),Q=!0),t.lineTo(re,V)}}t.stroke()}function mn(t){let d=Math.abs(t);return d>=1e3?t.toFixed(0):d>=10?t.toFixed(1):d>=1?t.toFixed(2):t.toFixed(3)}function pn(t,d){let h=new Date(t).toISOString();return d<2*864e5?h.slice(5,16).replace("T"," "):h.slice(5,10)}function fn(t,d,h){return{left:d.l*h,top:d.t*h,right:t.width-d.r*h,bottom:t.height-d.b*h}}function hn(t,d,h,e,x,N,F){let b=fn(d,h,e),i=Math.max(b.left,Math.min(x(N),b.right));i<=b.left+.5||(t.save(),t.fillStyle="rgba(17,15,13,0.42)",t.fillRect(b.left,b.top,i-b.left,b.bottom-b.top),t.strokeStyle=z(j("--faint"),.7),t.lineWidth=1*e,t.setLineDash([4*e,4*e]),t.beginPath(),t.moveTo(i,b.top),t.lineTo(i,b.bottom),t.stroke(),t.setLineDash([]),t.fillStyle=z(j("--faint"),.95),t.font=`${10*e}px ui-monospace, monospace`,t.textAlign="left",t.textBaseline="top",t.fillText(F,i+6*e,b.top+5*e),t.restore())}function bn(t,d,h,e,x,N,F){let b=fn(d,h,e),i=5*e;t.save();for(let g=0;g<N.length;g++){let y=N[g],$=x(y.t);if($<b.left-1||$>b.right+1)continue;let oe=F(y.kind);t.strokeStyle=z(oe,.45),t.lineWidth=1*e,t.beginPath(),t.moveTo($,b.top),t.lineTo($,b.bottom),t.stroke(),t.fillStyle=oe,t.beginPath(),t.moveTo($-i,b.top),t.lineTo($+i,b.top),t.lineTo($,b.top+i*1.4),t.closePath(),t.fill()}t.restore()}var kn={mad:1,zscore:2,iqr:4};function Sn(t,d){let h=t.seasonalityData;if(!h||h.length===0)return 0;let e=0;for(let x of d){let N=new Set;for(let F of h)N.add(x.map(b=>{var i;return String((i=F==null?void 0:F[b])!=null?i:"")}).join("|"));e=Math.max(e,N.size)}return e}function Zt(t,d){let h=t.timestamps.length;if(d.type==="manual_bounds")return Math.min(d.inputType!=="values"?1:0,h);let e=Math.max(d.minSamples,kn[d.type]);if(d.smoothing==="sma"&&(e=Math.max(e,d.smoothingWindow-1)),d.smoothing==="ema"&&(e=Math.max(e,Math.ceil(5/d.smoothingAlpha))),d.inputType!=="values"&&(e=Math.max(e,1)),d.seasonalityComponents!==null&&d.seasonalityComponents.length>0&&Array.isArray(t.seasonalityData)&&t.seasonalityData.length>0){let N=Sn(t,d.seasonalityComponents);if(N>0){let F=d.minSamplesPerGroup*N;d.windowSize>=F&&(e=Math.max(e,F))}}return Math.min(e,h)}var D={l:52,r:14,t:14,b:26},on=46,Mn=10,Cn=13,ge=Number.isFinite,at=1e3,lt=60*at,dt=60*lt,pe=24*dt,rn=[at,2*at,5*at,10*at,15*at,30*at,lt,2*lt,5*lt,10*lt,15*lt,30*lt,dt,2*dt,3*dt,6*dt,12*dt,pe,2*pe,3*pe,5*pe,7*pe,14*pe];function gn(t,d,h){let e=Math.max(d-t,1);if(e>h*14*pe){let b=[1,2,3,6,12,24,36,60,120,240],i=b[b.length-1];for(let T of b)if(e/(T*30.44*pe)<=h){i=T;break}let g=new Date(t),y=g.getUTCFullYear(),$=g.getUTCMonth();if(i>=12){let T=i/12;y=Math.floor(y/T)*T,$=0}else $=Math.floor($/i)*i;let oe=[],L=Date.UTC(y,$,1);for(;L<=d;)L>=t&&oe.push(L),$+=i,y+=Math.floor($/12),$%=12,L=Date.UTC(y,$,1);return{ticks:oe,step:i*30*pe}}let x=rn[rn.length-1];for(let b of rn)if(e/b<=h){x=b;break}let N;if(x%pe===0){let b=new Date(t);N=Date.UTC(b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate());let i=x/pe,g=Math.round(N/pe);N+=(i-g%i)%i*pe}else N=Math.ceil(t/x)*x;let F=[];for(let b=N;b<=d;b+=x)b>=t&&F.push(b);return{ticks:F,step:x}}function sn(t,d){let h=new Date(t).toISOString();return d>=320*pe?h.slice(0,4):d>=26*pe?h.slice(0,7):d>=pe?h.slice(5,10):d>=dt?h.slice(5,16).replace("T"," "):h.slice(5,19).replace("T"," ")}function an(t,d={}){let h=t.getContext("2d");if(!h)throw new Error("chart: 2D context unavailable");let e=h,x=!!d.navigable,N=x&&d.showNavigator!==!1,F=d.yFit==="data",b=!!d.labeling,i=1,g=null,y=-1,$=0,oe=!!d.showZeroLine,L=[],T=null,ve=-1,w=null,te=!1,Q=!1,K="above",re=0,Y=null,V=null,fe=null,Se=null,Le=null,Ce=!1,O=null,H=0,q=1,xe=0,Te=1,X=0,ie=1,Bt="";function kt(){i=un(t)}let _e=()=>N?(Mn+on)*i:0,U=()=>t.width-(D.l+D.r)*i,G=()=>t.height-(D.t+D.b)*i-_e(),Pe=()=>x?X:H,je=()=>x?ie:q,Ne=()=>je()-Pe()||1,he=()=>q-H||1,I=n=>D.l*i+(n-Pe())/Ne()*U(),se=n=>t.height-D.b*i-_e()-(n-xe)/(Te-xe||1)*G(),Ae=()=>t.height-on*i,ye=()=>(on-Cn)*i,Ie=()=>t.width-(D.l+D.r)*i,be=n=>D.l*i+(n-H)/he()*Ie(),Xe=n=>Ae()+ye()-(n-xe)/(Te-xe||1)*ye(),Ot=(n,r,l)=>{let a=new Array(n.length).fill(!1);if(n.length===0)return a;let s=e.measureText(sn(n[0],l)).width+16*i,c=-1/0;for(let m=0;m<n.length;m++){let v=r(n[m]);v-c>=s&&(a[m]=!0,c=v)}return a},ee=(n,r,l)=>Math.max(r,Math.min(l,n)),He=()=>{let n=g==null?void 0:g.series,r=n?n.timestamps.length:0,l=r>1?he()/(r-1):1e3;return Math.max(l*8,1e3)};function De(n,r){var s;let l=r-n,a=He();if(l<a){let c=(n+r)/2;n=c-a/2,r=c+a/2,l=a}l>=he()&&(n=H,r=q),n<H&&(r+=H-n,n=H),r>q&&(n-=r-q,r=q),X=ee(n,H,q),ie=ee(r,H,q),te||(s=d.onViewChange)==null||s.call(d,X,ie),Q&&Z(),R()}function Jt(n,r){x&&(te=!0,De(n,r),te=!1)}let $e=n=>{let r=(n-D.l*i)/(U()||1);return Pe()+ee(r,0,1)*Ne()},Be=n=>{let r=(n-D.l*i)/(Ie()||1);return H+ee(r,0,1)*he()},Oe=n=>{let r=t.height-D.b*i-_e(),l=ee((r-n)/(G()||1),0,1);return xe+l*(Te-xe)};function ne(n){let r=g==null?void 0:g.series;if(!r||r.timestamps.length===0)return-1;let l=(n-D.l*i)/(U()||1),a=Pe()+l*Ne(),s=r.timestamps,c=0,m=s.length-1;for(;c<m;){let v=c+m>>1;s[v]<a?c=v+1:m=v}return c>0&&a-s[c-1]<s[c]-a&&(c-=1),c}function Ee(n,r){let l=n.timestamps;if(H=l[0],q=l[l.length-1],x){let m=`${l.length}:${H}:${q}`;m!==Bt?(Bt=m,X=H,ie=q):(X=ee(X,H,q),ie=ee(Math.max(ie,X+He()),H,q))}let a=1/0,s=-1/0;for(let m of n.values)ge(m)&&(m<a&&(a=m),m>s&&(s=m));if(!F)for(let m of r)m.scored&&(ge(m.lower)&&m.lower<a&&(a=m.lower),ge(m.upper)&&m.upper>s&&(s=m.upper));(!ge(a)||!ge(s))&&(a=0,s=1),oe&&(a>0&&(a=0),s<0&&(s=0)),s<=a&&(s=a+1);let c=(s-a)*.06;xe=a-c,Te=s+c}function St(n,r,l){let a=g.series.timestamps;nn(e,a,n,Pe(),je(),D.l*i,U(),I,se,r,l,i)}function Ft(n,r){let l=[],a=-1;for(let s=Math.max(0,r);s<n.length;s++){let c=n[s];c.scored&&ge(c.lower)&&ge(c.upper)?a===-1&&(a=s):a!==-1&&(l.push([a,s-1]),a=-1)}return a!==-1&&l.push([a,n.length-1]),l}function qt(){if($=0,!g||t.width===0||t.height===0)return;let{series:n,scored:r,params:l,alerts:a}=g;if(n.timestamps.length===0){e.fillStyle=j("--term-bg"),e.fillRect(0,0,t.width,t.height);return}let s=j("--clay"),c=j("--st-anomaly"),m=j("--faint"),v=j("--muted");e.fillStyle=j("--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 k=0;k<=4;k++){let C=xe+(Te-xe)*k/4,S=se(C);e.strokeStyle=z(m,.1),e.lineWidth=1*i,e.beginPath(),e.moveTo(D.l*i,S),e.lineTo(t.width-D.r*i,S),e.stroke(),e.fillStyle=v,e.textAlign="right",e.fillText(mn(C),(D.l-8)*i,S)}if(oe&&xe<=0&&Te>=0){let k=se(0);e.strokeStyle=z(v,.6),e.lineWidth=1.25*i,e.beginPath(),e.moveTo(D.l*i,k),e.lineTo(t.width-D.r*i,k),e.stroke(),e.fillStyle=v,e.textAlign="right",e.fillText("0",(D.l-8)*i,k)}e.textBaseline="top";let P=t.height-D.b*i-_e();if(x){let k=gn(Pe(),je(),7),C=Ot(k.ticks,I,k.step);for(let S=0;S<k.ticks.length;S++){let _=k.ticks[S],de=I(_);e.strokeStyle=z(m,.1),e.lineWidth=1*i,e.beginPath(),e.moveTo(de,D.t*i),e.lineTo(de,P),e.stroke(),C[S]&&(e.fillStyle=v,e.textAlign=de<(D.l+24)*i?"left":de>t.width-(D.r+24)*i?"right":"center",e.fillText(sn(_,k.step),de,P+7*i))}}else{let k=Ne();for(let C=0;C<=5;C++){let S=H+k*C/5,_=I(S);e.fillStyle=v,e.textAlign=C===0?"left":C===5?"right":"center",e.fillText(pn(S,k),_,P+7*i)}}e.save(),e.beginPath(),e.rect(D.l*i,D.t*i,U(),G()),e.clip(),jt(),mt();let A=r.length,E=Math.min(Zt(n,l),A),B=E<A?n.timestamps[E]:void 0,le=b?[]:Ft(r,E);e.fillStyle=z(s,.13);for(let[k,C]of le){e.beginPath(),e.moveTo(I(r[k].timestamp),se(r[k].upper));for(let S=k+1;S<=C;S++)e.lineTo(I(r[S].timestamp),se(r[S].upper));for(let S=C;S>=k;S--)e.lineTo(I(r[S].timestamp),se(r[S].lower));e.closePath(),e.fill()}e.strokeStyle=z(s,.4),e.lineWidth=1*i;for(let[k,C]of le)for(let S of["upper","lower"]){e.beginPath();for(let _=k;_<=C;_++){let de=I(r[_].timestamp),Ke=se(r[_][S]);_===k?e.moveTo(de,Ke):e.lineTo(de,Ke)}e.stroke()}e.strokeStyle=z(m,.55),e.lineWidth=1*i,e.setLineDash([3*i,3*i]);for(let[k,C]of le){e.beginPath();for(let S=k;S<=C;S++){let _=r[S].center;if(!ge(_))continue;let de=I(r[S].timestamp),Ke=se(_);S===k?e.moveTo(de,Ke):e.lineTo(de,Ke)}e.stroke()}if(e.setLineDash([]),l.smoothing!=="none"&&!b){St(n.values,z(s,.28),1.25);let k=r.map(C=>C.processedValue);St(k,s,1.6)}else St(n.values,s,1.5);for(let k=E;k<A;k++){let C=r[k];if(!C.scored||!ge(C.value))continue;let S=I(C.timestamp),_=se(C.value);C.isAnomaly?(e.fillStyle=z(c,.18),e.beginPath(),e.arc(S,_,6*i,0,Math.PI*2),e.fill(),e.fillStyle=c,e.beginPath(),e.arc(S,_,3*i,0,Math.PI*2),e.fill()):n.truthAnomaly[k]&&(e.strokeStyle=z(v,.7),e.lineWidth=1.25*i,e.beginPath(),e.arc(S,_,3.5*i,0,Math.PI*2),e.stroke())}B!==void 0&&!b&&hn(e,t,D,i,I,B,"detection at full power \u2192"),y>=0&&y<r.length&&ct(y,l.windowSize,r,n,m),a&&a.length&&bn(e,t,D,i,I,a,k=>k==="anomaly"?j("--st-anomaly"):k==="recovery"?j("--st-recovery"):j("--st-nodata")),Je(),ut(r,E,A),e.restore(),N&&Mt(n,a,s,m,v)}function Mt(n,r,l,a,s){let c=Ae(),m=c+ye(),v=D.l*i,P=t.width-D.r*i,A=gn(H,q,5);e.save(),e.beginPath(),e.rect(v,c,Ie(),ye()),e.clip(),e.strokeStyle=z(a,.1),e.lineWidth=1*i;for(let C of A.ticks){let S=be(C);e.beginPath(),e.moveTo(S,c),e.lineTo(S,m),e.stroke()}nn(e,n.timestamps,n.values,H,q,v,Ie(),be,Xe,z(l,.7),1.1,i),e.restore();let E=be(X),B=be(ie);if(e.fillStyle="rgba(27,25,22,0.55)",e.fillRect(v,c,E-v,ye()),e.fillRect(B,c,P-B,ye()),L.length){let C=j("--st-anomaly");e.fillStyle=z(C,.28);for(let S of L){let _=be(S.start),de=Math.max(be(S.end)-_,2*i);e.fillRect(_,c,de,ye())}}if(r&&r.length){let C=j("--st-anomaly");e.fillStyle=z(C,.85);for(let S of r){let _=be(S.t);e.fillRect(_-1*i,c,2*i,ye())}}e.fillStyle="rgba(245,241,232,0.06)",e.fillRect(E,c,B-E,ye()),e.strokeStyle=l,e.lineWidth=1.5*i,e.strokeRect(E,c+1,B-E,ye()-2),e.fillStyle=l;let le=c+ye()/2-8*i;e.fillRect(E-2*i,le,4*i,16*i),e.fillRect(B-2*i,le,4*i,16*i),e.font=`${10*i}px ui-monospace, monospace`,e.textBaseline="top",e.fillStyle=s;let k=Ot(A.ticks,be,A.step);for(let C=0;C<A.ticks.length;C++){let S=A.ticks[C],_=be(S);e.strokeStyle=z(a,.25),e.lineWidth=1*i,e.beginPath(),e.moveTo(_,m),e.lineTo(_,m+3*i),e.stroke(),k[C]&&(e.textAlign=_<(D.l+26)*i?"left":_>t.width-(D.r+26)*i?"right":"center",e.fillText(sn(S,A.step),_,m+5*i))}}function ct(n,r,l,a,s){let c=a.timestamps,m=Math.max(0,n-r),v=n-1,P=D.t*i,A=G();if(v>=m){let le=a.intervalSeconds*1e3/Ne()*U()*.5,k=I(c[m])-le,C=I(c[v])+le;e.fillStyle="rgba(255,255,255,0.05)",e.fillRect(k,P,C-k,A),e.strokeStyle=z(s,.5),e.lineWidth=1*i,e.beginPath(),e.moveTo(k,P),e.lineTo(k,P+A),e.moveTo(C,P),e.lineTo(C,P+A),e.stroke()}let E=I(c[n]);e.strokeStyle=z(s,.85),e.lineWidth=1*i,e.setLineDash([2*i,2*i]),e.beginPath(),e.moveTo(E,P),e.lineTo(E,P+A),e.stroke(),e.setLineDash([]);let B=l[n];if(B.scored&&ge(B.lower)&&ge(B.upper)){let le=5*i;e.strokeStyle=z(j("--clay"),.85),e.lineWidth=1.5*i;for(let k of[B.lower,B.upper]){let C=se(k);e.beginPath(),e.moveTo(E-le,C),e.lineTo(E+le,C),e.stroke()}}if(ge(B.value)){let le=se(B.value);e.fillStyle=j("--term-bg"),e.beginPath(),e.arc(E,le,4*i,0,Math.PI*2),e.fill(),e.strokeStyle=B.isAnomaly?j("--st-anomaly"):j("--clay"),e.lineWidth=2*i,e.beginPath(),e.arc(E,le,4*i,0,Math.PI*2),e.stroke()}}function Ct(n,r,l,a,s){e.beginPath(),e.moveTo(n+s,r),e.arcTo(n+l,r,n+l,r+a,s),e.arcTo(n+l,r+a,n,r+a,s),e.arcTo(n,r+a,n,r,s),e.arcTo(n,r,n+l,r,s),e.closePath()}function Ze(n,r){let l=j("--st-anomaly"),a=14*i,s=3*i,c=n-a-s,m=D.t*i+s;e.fillStyle=r?z(l,.95):"rgba(27,25,22,0.82)",e.strokeStyle=z(l,.9),e.lineWidth=1*i,Ct(c,m,a,a,3*i),e.fill(),e.stroke(),e.strokeStyle=r?"#fff":l,e.lineWidth=1.5*i;let v=4*i;e.beginPath(),e.moveTo(c+v,m+v),e.lineTo(c+a-v,m+a-v),e.moveTo(c+a-v,m+v),e.lineTo(c+v,m+a-v),e.stroke()}let Re=()=>Y!=null?Y:V,we=()=>{let n=Le||fe;return n?[Math.min(n.a,n.b),Math.max(n.a,n.b)]:[Pe(),je()]};function Ve(){let n=[],r=Re();if(r==null||!g)return n;let l=g.series.timestamps,a=g.series.values,[s,c]=we(),m=-1,v=-1,P=0;for(let A=0;A<l.length;A++){if(l[A]<s||l[A]>c)continue;let E=a[A];ge(E)&&(K==="above"?E>r:E<r)?(m===-1&&(m=l[A]),v=l[A],P=0):m!==-1&&(P++,P>re&&(n.push([m,v]),m=-1,P=0))}return m!==-1&&n.push([m,v]),n}function Z(){if(!d.onThresholdChange)return;let[n,r]=we(),l=Le||fe;d.onThresholdChange({value:Re(),locked:Y!=null,runs:Ve().length,window:l?{start:Math.min(l.a,l.b),end:Math.max(l.a,l.b)}:null,committed:fe!=null,windowMs:r-n})}function ze(n,r){let l=null;for(let a=L.length-1;a>=0;a--){let s=L[a];n<=s.end&&r>=s.start&&(l===null?(s.start=Math.min(s.start,n),s.end=Math.max(s.end,r),l=s):(l.start=Math.min(l.start,s.start),l.end=Math.max(l.end,s.end),T===s&&(T=l),L.splice(a,1)))}l===null&&L.push({start:n,end:r,label:""})}function jt(){if(!Q||Re()==null)return;let r=D.t*i,l=G(),a=j("--st-nodata");for(let[s,c]of Ve()){let m=I(s),v=Math.max(I(c)-m,2*i);e.fillStyle=z(a,.22),e.fillRect(m,r,v,l),e.strokeStyle=z(a,.6),e.lineWidth=1*i,e.strokeRect(m,r,v,l)}}function Je(){if(!Q)return;let n=D.t*i,r=t.height-D.b*i-_e(),l=D.l*i,a=t.width-D.r*i,s=j("--st-nodata"),[c,m]=we(),v=!!(Le||fe),P=ee(I(c),l,a),A=ee(I(m),l,a);v&&(e.fillStyle=z(j("--ink"),.5),e.fillRect(l,n,Math.max(0,P-l),G()),e.fillRect(A,n,Math.max(0,a-A),G()),e.strokeStyle=z(s,.7),e.lineWidth=1*i,e.setLineDash([3*i,3*i]),e.beginPath(),e.moveTo(P,n),e.lineTo(P,r),e.moveTo(A,n),e.lineTo(A,r),e.stroke(),e.setLineDash([]));let E=Re();if(E!=null&&E>=xe&&E<=Te){let B=se(E);e.strokeStyle=s,e.lineWidth=1.5*i,e.setLineDash([6*i,4*i]),e.beginPath(),e.moveTo(P,B),e.lineTo(A,B),e.stroke(),e.setLineDash([])}}let Fe=()=>g?g.series.intervalSeconds*1e3:1e3;function Tt(n,r,l){let a=!1;for(let s=0,c=l.length-1;s<l.length;c=s++){let m=l[s].x,v=l[s].y,P=l[c].x,A=l[c].y;v>r!=A>r&&n<(P-m)*(r-v)/(A-v||1e-9)+m&&(a=!a)}return a}function Qe(n,r,l){let a=[];if(!O||O.length<3)return a;for(let s=Math.max(0,r);s<l;s++){let c=n[s];!c.scored||!c.isAnomaly||!ge(c.value)||Tt(I(c.timestamp),se(c.value),O)&&a.push(s)}return a}function Ue(n,r){var A;if(!r.length)return[];let l=Fe(),s=(Math.max(1,(A=g==null?void 0:g.params.consecutiveAnomalies)!=null?A:1)+1)*l,c=l/2,m=[],v=r[0],P=r[0];for(let E=1;E<r.length;E++){let B=r[E];n[B].timestamp-n[P].timestamp>s&&(m.push([n[v].timestamp-c,n[P].timestamp+c]),v=B),P=B}return m.push([n[v].timestamp-c,n[P].timestamp+c]),m}function Vt(n,r,l){if(!d.onLassoChange)return;let a=Ce&&O?Qe(n,r,l):[];d.onLassoChange({active:!!O,anomalies:a.length,incidents:Ue(n,a).length})}function At(){if(!g||!O)return;let{scored:n}=g,r=n.length,l=Math.min(Zt(g.series,g.params),r),a=Ue(n,Qe(n,l,r));for(let[s,c]of a)ze(s,c);a.length&&Ge()}function ut(n,r,l){if(!Ce||!O||O.length<2)return;let a=j("--clay"),s=j("--st-anomaly");e.beginPath(),e.moveTo(O[0].x,O[0].y);for(let c=1;c<O.length;c++)e.lineTo(O[c].x,O[c].y);e.closePath(),e.fillStyle=z(a,.08),e.fill(),e.strokeStyle=z(a,.9),e.lineWidth=1.5*i,e.setLineDash([5*i,4*i]),e.stroke(),e.setLineDash([]);for(let c of Qe(n,r,l)){let m=I(n[c].timestamp),v=se(n[c].value);e.strokeStyle=s,e.lineWidth=2*i,e.beginPath(),e.arc(m,v,7*i,0,Math.PI*2),e.stroke()}}function mt(){let n=D.t*i,r=G(),l=j("--st-anomaly"),a=D.l*i,s=t.width-D.r*i;for(let c=0;c<L.length;c++){let m=L[c],v=I(m.start),P=I(m.end);if(P<a-1||v>s+1)continue;let A=Math.max(P-v,2*i),E=b&&m===T;e.fillStyle=z(l,E?.3:.16),e.fillRect(v,n,A,r),e.strokeStyle=z(l,E?.95:.5),e.lineWidth=(E?2:1)*i,e.strokeRect(v,n,A,r),b&&(e.fillStyle=z(l,.95),e.fillRect(v-1.5*i,n,3*i,r),e.fillRect(P-1.5*i,n,3*i,r),(P-v>=22*i||E)&&Ze(P,E||c===ve))}if(b&&w&&w.mode==="new"){let c=I(w.a),m=I(w.b);e.fillStyle=z(j("--st-nodata"),.28),e.fillRect(Math.min(c,m),n,Math.abs(m-c),r)}}function Ge(){var n;(n=d.onIncidentsChange)==null||n.call(d,L)}function et(n){let r=L.indexOf(n);r<0||(L.splice(r,1),T===n&&(T=null),ve=-1,Ge(),R())}function tt(n,r){let l=D.t*i,a=6*i;for(let s=0;s<L.length;s++){let c=I(L[s].end);if(c-I(L[s].start)>=22*i||L[s]===T){let m=14*i,v=3*i,P=c-m-v,A=l+v;if(n>=P&&n<=P+m&&r>=A&&r<=A+m)return{i:s,edge:"del"}}}for(let s=0;s<L.length;s++){let c=I(L[s].start),m=I(L[s].end);if(Math.abs(n-c)<=a)return{i:s,edge:"a"};if(Math.abs(n-m)<=a)return{i:s,edge:"b"}}for(let s=0;s<L.length;s++){let c=I(L[s].start),m=I(L[s].end);if(n>c+a&&n<m-a)return{i:s,edge:"move"}}return null}let nt=()=>{let n=g==null?void 0:g.series,r=n?n.timestamps.length:0;return r>1?Math.max(he()/(r-1),1):1e3};function R(){$===0&&($=requestAnimationFrame(qt))}function ke(){if(!g)return;let n=g.scored.length,r=Math.min(Zt(g.series,g.params),n);Vt(g.scored,r,n)}function qe(){var l;if(!d.onHover||!g)return;if(y<0){d.onHover(null);return}let n=(l=g.scored[y])!=null?l:null,r={index:y,point:n,windowStart:Math.max(0,y-g.params.windowSize),windowEnd:y-1};d.onHover(r)}let Me=null,ae=null,ot=n=>{let r=t.getBoundingClientRect();return{x:(n.clientX-r.left)*i,y:(n.clientY-r.top)*i}},Dt=n=>N&&n>=Ae(),pt=n=>{let r=be(X),l=be(ie),a=8*i;return Math.abs(n-r)<=a?"l":Math.abs(n-l)<=a?"r":n>r&&n<l?"move":"out"};function ue(n){if(!g)return;let{x:r,y:l}=ot(n);if(Me||ae||w)return;if(Dt(l)){y!==-1&&(y=-1,qe(),R());let s=pt(r);t.style.cursor=s==="l"||s==="r"?"ew-resize":s==="move"?"grab":"pointer";return}if(b&&Ce){y!==-1&&(y=-1,qe()),t.style.cursor="crosshair";return}if(b&&Q){y!==-1&&(y=-1,qe()),Y==null&&!Se&&(V=Oe(l),Z(),R()),t.style.cursor="crosshair";return}if(b){y!==-1&&(y=-1,qe());let s=tt(r,l),c=s&&s.edge==="del"?s.i:-1;c!==ve&&(ve=c,R()),t.style.cursor=s?s.edge==="del"?"pointer":s.edge==="move"?"grab":"ew-resize":"crosshair";return}x&&(t.style.cursor="grab");let a=ne(r);a!==y&&(y=a,qe(),R())}function rt(){y!==-1&&(y=-1,qe(),R())}function ft(n){if(!x||!g)return;let{x:r,y:l}=ot(n);if(N&&l>=Ae()){let a=pt(r);if(a==="l")ae={type:"l",grab:0,vMin:X,vMax:ie};else if(a==="r")ae={type:"r",grab:0,vMin:X,vMax:ie};else if(a==="move")ae={type:"move",grab:Be(r),vMin:X,vMax:ie};else{let s=Be(r),c=Ne();De(s-c/2,s+c/2),ae={type:"move",grab:s,vMin:X,vMax:ie}}t.style.cursor="grabbing",n.preventDefault();return}if(l>D.t*i&&l<t.height-D.b*i-_e()){if(b&&Ce){O=[{x:r,y:l}],ke(),t.style.cursor="crosshair",R(),n.preventDefault();return}if(b&&Q){Se={x:r,ts:$e(r)},Y==null&&(V=Oe(l)),Z(),R(),n.preventDefault();return}if(b){let a=$e(r),s=tt(r,l);if(s&&s.edge==="del"){et(L[s.i]),n.preventDefault();return}if(s&&s.edge==="move"){let c=L[s.i];T=c,w={mode:"move",iv:c,grab:a,a0:c.start,b0:c.end,moved:!1}}else if(s){let c=L[s.i];T=c,w={mode:"edge",iv:c,edge:s.edge,moved:!1}}else T=null,w={mode:"new",a,b:a,moved:!1};t.style.cursor="crosshair",R(),n.preventDefault();return}Me={x:r,vMin:X,vMax:ie},t.style.cursor="grabbing",n.preventDefault()}}function ht(n){if(!g)return;let{x:r,y:l}=ot(n);if(Ce&&O){O.push({x:r,y:l}),ke(),R();return}if(Q&&Se){Math.abs(r-Se.x)>6*i?Le={a:Se.ts,b:$e(r)}:(Le=null,Y==null&&(V=Oe(l))),Z(),R();return}if(w){let a=$e(r);if(w.mode==="new")w.b=a,Math.abs(I(w.b)-I(w.a))>4*i&&(w.moved=!0);else if(w.mode==="edge"){let s=w.iv,c=nt();w.edge==="a"?s.start=ee(Math.min(a,s.end-c),H,q):s.end=ee(Math.max(a,s.start+c),H,q),w.moved=!0}else{let s=w.iv,c=w.a0+(a-w.grab),m=w.b0+(a-w.grab);c<H&&(m+=H-c,c=H),m>q&&(c-=m-q,m=q),s.start=ee(c,H,q),s.end=ee(m,H,q),w.moved=!0}R();return}if(Me){let a=(r-Me.x)*(Me.vMax-Me.vMin)/(U()||1);De(Me.vMin-a,Me.vMax-a)}else if(ae){let a=Be(r);ae.type==="l"?De(Math.min(a,ie-He()),ie):ae.type==="r"?De(X,Math.max(a,X+He())):De(ae.vMin+(a-ae.grab),ae.vMax+(a-ae.grab))}}function Ye(n){if(Ce&&O){At(),O=null,ke(),R();return}if(Q&&Se){let{x:r,y:l}=ot(n);if(Math.abs(r-Se.x)>6*i){let a=Se.ts,s=$e(r);fe={a:Math.min(a,s),b:Math.max(a,s)}}else Y=Oe(l);Se=null,Le=null,Z(),R();return}if(w){let r=w;if(r.mode==="new")if(r.moved){let l=ee(Math.min(r.a,r.b),H,q),a=ee(Math.max(r.a,r.b),H,q),s={start:l,end:a,label:""};L.push(s),T=s}else T=null;else if(r.iv.start>r.iv.end){let l=r.iv.start;r.iv.start=r.iv.end,r.iv.end=l}w=null,t.style.cursor="crosshair",r.moved&&Ge(),R();return}(Me||ae)&&(Me=null,ae=null,t.style.cursor="grab")}function Lt(n){let r=n.target,l=!!r&&(r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.isContentEditable);if(Ce){n.key==="Escape"&&O&&(O=null,ke(),R());return}Q||l||!T||(n.key==="Delete"||n.key==="Backspace"?(n.preventDefault(),et(T)):n.key==="Escape"&&(T=null,R()))}function Pt(n){if(!x||!g)return;if(Ce&&O){n.preventDefault();return}n.preventDefault();let{x:r,y:l}=ot(n),a=ee(Ne()*Math.pow(1.0015,n.deltaY),He(),he());if(l>=Ae()){let s=Be(r);De(s-a/2,s+a/2)}else{let s=$e(r),c=(s-X)/(Ne()||1);De(s-c*a,s-c*a+a)}}function Ut(){x&&De(H,q)}t.addEventListener("mousemove",ue),t.addEventListener("mouseleave",rt),x&&(t.addEventListener("mousedown",ft),t.addEventListener("wheel",Pt,{passive:!1}),t.addEventListener("dblclick",Ut),window.addEventListener("mousemove",ht),window.addEventListener("mouseup",Ye)),b&&(window.addEventListener("keydown",Lt),t.style.cursor="crosshair");function Qt(n){g=n,!b&&n.incidents&&(L=n.incidents),Ee(n.series,n.scored),y>=n.series.timestamps.length&&(y=-1),R()}function Gt(n){oe!==n&&(oe=n,g&&Ee(g.series,g.scored),R())}function Nt(n){L=n,T=null,ve=-1,R()}function it(n){b&&(Q=n,n?(T=null,Ce=!1,O=null,ke()):(V=null,Se=null,Le=null),t.style.cursor="crosshair",Z(),R())}function Yt(n){b&&(Ce=n,n?(Q=!1,Se=null,Le=null,V=null,T=null,Z()):O=null,t.style.cursor="crosshair",ke(),R())}function Et(n){K=n,Z(),R()}function Wt(n){re=Math.max(0,Math.floor(n)||0),Z(),R()}function _t(n){Y=n!=null&&ge(n)?n:null,Z(),R()}function It(){let n=Ve(),r=Fe()/2;for(let[l,a]of n)ze(l-r,a+r);return n.length&&Ge(),Z(),R(),n.length}function Kt(){fe=null,Le=null,Z(),R()}function en(){return fe?{start:Math.min(fe.a,fe.b),end:Math.max(fe.a,fe.b)}:null}function Xt(n){fe=n?{a:n.start,b:n.end}:null,Z(),R()}function bt(){kt(),g&&(Ee(g.series,g.scored),R())}function st(){t.removeEventListener("mousemove",ue),t.removeEventListener("mouseleave",rt),x&&(t.removeEventListener("mousedown",ft),t.removeEventListener("wheel",Pt),t.removeEventListener("dblclick",Ut),window.removeEventListener("mousemove",ht),window.removeEventListener("mouseup",Ye)),b&&window.removeEventListener("keydown",Lt),$!==0&&cancelAnimationFrame($),$=0,g=null}return kt(),{render:Qt,resize:bt,setZeroLine:Gt,setViewWindow:Jt,setIncidents:Nt,setThresholdMode:it,setThresholdDirection:Et,setThresholdGap:Wt,setThresholdValue:_t,applyThreshold:It,setLassoMode:Yt,clearCaptureWindow:Kt,getCaptureWindow:en,setCaptureWindow:Xt,destroy:st}}var Tn={mad:3,zscore:3,iqr:1.5},An={mad:10,zscore:3,iqr:4},Dn="dtk-tune";function p(t,d,h){let e=document.createElement(t);return d&&(e.className=d),h!=null&&(e.textContent=h),e}function wt(t,d){let h=p("label","dtk-ctl-label",t);if(d){h.title=d;let e=p("span","dtk-ctl-info","\u24D8");e.title=d,h.appendChild(document.createTextNode(" ")),h.appendChild(e)}return h}function Rt(t,d,h,e,x){let N=p("div","dtk-ctl");N.appendChild(wt(t,x));let F=p("div","dtk-seg"),b=h,i=[],g=()=>{i.forEach(y=>y.classList.toggle("on",y.dataset.v===b))};return d.forEach(y=>{let $=p("button","dtk-seg-btn",y.label);$.type="button",$.dataset.v=y.value,$.onclick=()=>{b=y.value,g(),e(b)},i.push($),F.appendChild($)}),g(),N.appendChild(F),{row:N,get:()=>b,set:y=>{b=y,g()}}}function yt(t,d,h){var g;let e=p("div","dtk-ctl"),x=p("div","dtk-ctl-head"),N=wt(t,d.hint),F=p("span","dtk-ctl-val"),b=(g=d.fmt)!=null?g:(y=>String(y));x.appendChild(N),x.appendChild(F),e.appendChild(x);let i=p("input","dtk-range");return i.type="range",i.min=String(d.min),i.max=String(d.max),i.step=String(d.step),i.value=String(d.value),F.textContent=b(d.value),i.oninput=()=>{let y=Number(i.value);F.textContent=b(y),h(y)},e.appendChild(i),{row:e,get:()=>Number(i.value),setMax:y=>{i.max=String(y),Number(i.value)>y&&(i.value=String(y),F.textContent=b(y))}}}function xn(t){if(t.type==="manual_bounds"){let h={};return t.lowerBound!=null&&(h.lower_bound=t.lowerBound),t.upperBound!=null&&(h.upper_bound=t.upperBound),t.inputType!=="values"&&(h.input_type=t.inputType),h}let d={threshold:t.threshold,window_size:t.windowSize};return t.windowWeights!=="none"&&(d.window_weights=t.windowWeights,t.windowWeights==="exponential"&&t.halfLife!=null&&(d.half_life=t.halfLife)),t.detrend!=="none"&&(d.detrend=t.detrend),t.smoothing!=="none"&&(d.smoothing=t.smoothing),t.inputType!=="values"&&(d.input_type=t.inputType),t.seasonalityComponents&&t.seasonalityComponents.length&&(d.seasonality_components=t.seasonalityComponents,d.min_samples_per_group=t.minSamplesPerGroup),d}function Ln(t,d){let h=xn(t),e=[`type: ${t.type}`];for(let[x,N]of Object.entries(h))e.push(`${x}=${typeof N=="object"?JSON.stringify(N):N}`);return t.direction&&t.direction!=="any"&&e.push(`direction=${t.direction}`),e.push(`consecutive_anomalies=${d}`),e.join(" \xB7 ")}function Pn(t,d){var Ke,dn,cn;En(),d.classList.add(Dn),d.innerHTML="";let h=p("div","dtk-tune-root");d.appendChild(h);let e=t.points.length,x={timestamps:t.points.map(o=>o.t),values:t.points.map(o=>o.v==null?NaN:o.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},N=x,F=o=>{let u=Math.max(0,e-o);return u<=0?x:{timestamps:x.timestamps.slice(u),values:x.values.slice(u),intervalSeconds:x.intervalSeconds,truthAnomaly:x.truthAnomaly.slice(u),seasonalityData:x.seasonalityData?x.seasonalityData.slice(u):void 0,seasonalityColumns:x.seasonalityColumns}},b=o=>Date.parse(o.replace(" ","T")+"Z"),i=(t.incidents||[]).map(o=>({start:b(o.start),end:b(o.end),label:o.label||""})).filter(o=>Number.isFinite(o.start)&&Number.isFinite(o.end)).map(o=>({start:Math.min(o.start,o.end),end:Math.max(o.start,o.end),label:o.label})),g=(()=>{let o=(t.capture_windows||[])[0];if(!o)return null;let u=b(o.start),f=b(o.end);return!Number.isFinite(u)||!Number.isFinite(f)?null:{start:Math.min(u,f),end:Math.max(u,f)}})(),y=[],$=[],oe=[],L=[],T=t.detector,ve=t.consecutive_anomalies,w=x.values.filter(o=>Number.isFinite(o)).sort((o,u)=>o-u),te=w.length?w[0]:0,Q=w.length?w[w.length-1]:1,K=o=>w.length?w[Math.min(w.length-1,Math.max(0,Math.round(o*(w.length-1))))]:0,re=Math.max((Q-te)*.05,1e-9),Y=te-re,V=Q+re,fe=Math.max((V-Y)/400,1e-9),Se=T.lowerBound!=null?T.lowerBound:K(.05),Le=T.upperBound!=null?T.upperBound:K(.95),Ce=(Ke=T.seasonalityComponents)!=null?Ke:[],O=new Map;Ce.forEach((o,u)=>o.forEach(f=>O.set(f,u+1)));let H=()=>{let o=0;O.forEach(f=>{f>o&&(o=f)});let u=[];for(let f=1;f<=o;f++){let M=t.seasonality_columns.filter(W=>O.get(W)===f);M.length&&u.push(M)}return u.length?u:null},q=o=>{if(!o||!t.seasonality.length)return 0;let u=0;for(let f of o){let M=new Set;for(let W of t.seasonality)M.add(f.map(J=>{var We;return String((We=W==null?void 0:W[J])!=null?We:"")}).join("|"));u=Math.max(u,M.size)}return u},xe=()=>{var o;return{type:rt.get(),threshold:Ye.get(),windowSize:Nt.get(),minSamples:T.minSamples,inputType:T.inputType,smoothing:_t.get(),smoothingAlpha:T.smoothingAlpha,smoothingWindow:T.smoothingWindow,windowWeights:it.get(),halfLife:it.get()==="exponential"?Yt.get():null,detrend:Wt.get(),seasonalityComponents:H(),minSamplesPerGroup:(o=An[rt.get()])!=null?o:T.minSamplesPerGroup,consecutiveAnomalies:ve,direction:Kt.get(),lowerBound:ft.get(),upperBound:ht.get()}},Te=p("div","dtk-tune-header"),X=p("div","dtk-tune-titlerow");X.appendChild(p("h1","dtk-tune-title",t.metric));let ie=p("span","dtk-tune-badge",t.save_url?"manual tuning":"preview");X.appendChild(ie),Te.appendChild(X);let Bt=t.project?`${t.project} \xB7 `:"";Te.appendChild(p("div","dtk-tune-sub",`${Bt}${e} points \xB7 ${Nn(t.interval_seconds)} grid`)),t.description&&Te.appendChild(p("div","dtk-tune-desc",t.description)),h.appendChild(Te);let kt=p("div","dtk-tune-metrics");h.appendChild(kt);let _e=p("div","dtk-tune-grid"),U=p("div","dtk-tune-controls"),G=p("div","dtk-tune-main");_e.appendChild(U),_e.appendChild(G),h.appendChild(_e);let Pe=p("div","dtk-tune-trim"),je=p("div","dtk-tune-trim-head");je.appendChild(wt("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 Ne=p("span","dtk-tune-trim-val");je.appendChild(Ne),Pe.appendChild(je);let he=p("input","dtk-range");he.type="range",he.min=String(Math.min(e,200)),he.max=String(e),he.step=String(Math.max(1,Math.round(e/200))),he.value=String(e),Pe.appendChild(he),G.appendChild(Pe);let I=p("div","dtk-tune-chart"),se=p("canvas");I.appendChild(se);let Ae=p("div","dtk-tune-spin");Ae.appendChild(p("span","dtk-spin-ring")),Ae.appendChild(p("span","dtk-spin-txt","computing\u2026")),I.appendChild(Ae),G.appendChild(I);let ye=p("div","dtk-tune-legend"),Ie=(o,u,f)=>{let M=p("span","dtk-leg-item");M.title=f,M.appendChild(p("span",`dtk-leg-sw ${o}`)),M.appendChild(p("span","dtk-leg-txt",u)),ye.appendChild(M)};Ie("line","metric","The metric value over time."),Ie("band","expected range","The detector's confidence band \u2014 values inside it read as normal."),Ie("center","band center","The expected value at the middle of the band."),Ie("dot","anomaly","A point the detector flagged as anomalous (outside the band)."),Ie("alert","alert","Where an alert fired \u2014 enough consecutive anomalies to meet the rule."),G.appendChild(ye);let be=p("div","dtk-tune-readout");G.appendChild(be);let Xe=p("div","dtk-tune-warn");Xe.style.display="none",G.appendChild(Xe);let Ot=o=>{let u=o.seasonalityComponents,f=q(u),M=o.minSamplesPerGroup*f;u&&f>0&&o.windowSize<M?(Xe.textContent=`\u26A0 Seasonality inactive at this window: ${o.windowSize} < ${M} (min_samples_per_group ${o.minSamplesPerGroup} \xD7 ${f} key${f===1?"":"s"}). Each point keeps only ~${Math.floor(o.windowSize/f)} same-key point(s), so the band falls back to global statistics (seasonality has no effect). Raise the window to \u2265 ${M}.`,Xe.style.display=""):Xe.style.display="none"},ee=p("div","dtk-tune-labhead");ee.appendChild(wt("Real incidents","Drag on this chart to mark each real incident span \u2014 drag its edges to adjust, drag the middle to move, click its \u2715 (or select + Delete) to remove. Pan via the strip below, scroll to zoom (both charts move together). The metrics above update as you tune.")),ee.appendChild(p("span","dtk-tune-labhint","drag to mark \xB7 edges adjust \xB7 \u2715/Delete remove \xB7 strip pans")),G.appendChild(ee);let He=p("div","dtk-tune-chart dtk-tune-labchart"),De=p("canvas");He.appendChild(De),G.appendChild(He);let Jt=(o,u)=>{let f=t.interval_seconds*1e3/2,M=N.timestamps,W=(M.length?M[0]:0)-f,J=(M.length?M[M.length-1]:0)+f,We=u.filter(me=>me.end>=W&&me.start<=J),$t=(me,xt)=>me[1]>=xt.start-f&&me[0]<=xt.end+f,gt=0;for(let me of o)We.some(xt=>$t(me,xt))&>++;let vt=0;for(let me of We)o.some(xt=>$t(xt,me))&&vt++;let ce=o.length;return{realIncidents:We.length,caught:vt,recall:We.length?vt/We.length:NaN,totalAlerts:ce,correctAlerts:gt,falseAlerts:ce-gt,fdr:ce?(ce-gt)/ce:NaN}},$e=o=>Number.isFinite(o)?`${Math.round(o*100)}%`:"\u2014",Be=(o,u,f,M)=>`<span class="dtk-m-chip"><span class="dtk-m-dot" style="background:${M}"></span><span class="dtk-m-v">${o}</span><span class="dtk-m-l">${u}</span>`+(f?`<span class="dtk-m-sub">${f}</span>`:"")+"</span>";function Oe(){let o=Jt($,i),u=o.realIncidents>0,f;if(!u)f="mark incidents to measure";else if(o.totalAlerts===0)f="no alerts";else if(o.falseAlerts===0)f=`${$e(o.fdr)} \xB7 all correct`;else{let M=o.totalAlerts/o.falseAlerts,W=M>=9.5?String(Math.round(M)):String(Math.round(M*10)/10);f=`${$e(o.fdr)} \xB7 \u22481 in ${W} false`}kt.innerHTML=Be(String(o.realIncidents),"real incidents","","var(--c)")+Be(u?`${o.caught}/${o.realIncidents}`:"\u2014","caught",u?`recall ${$e(o.recall)}`:"mark incidents","var(--green)")+Be(String(o.totalAlerts),"alerts","","var(--c)")+Be(u?String(o.falseAlerts):"\u2014","false alerts",f,"var(--anom)")}let ne,Ee=an(se,{navigable:!0,showNavigator:!1,yFit:"data",onViewChange:(o,u)=>ne.setViewWindow(o,u),onHover:o=>{if(!o||!o.point||!o.point.scored){be.textContent="";return}let u=o.point;be.textContent=`t=${ln(u.timestamp)} value=${zt(u.value)} band=[${zt(u.lower)}, ${zt(u.upper)}]`+(u.isAnomaly?` \u26A0 ${u.direction} (sev ${u.severity.toFixed(2)})`:"")}}),St={type:"mad",threshold:3,windowSize:100,minSamples:30,inputType:"values",smoothing:"none",smoothingAlpha:.3,smoothingWindow:10,windowWeights:"none",halfLife:null,detrend:"none",seasonalityComponents:null,minSamplesPerGroup:10,consecutiveAnomalies:1},Ft=()=>{},qt=()=>{};ne=an(De,{navigable:!0,labeling:!0,yFit:"data",onViewChange:(o,u)=>Ee.setViewWindow(o,u),onIncidentsChange:()=>{s(),Oe(),Ee.setIncidents(i)},onThresholdChange:o=>Ft(o),onLassoChange:o=>qt(o)}),ne.setIncidents(i),g&&ne.setCaptureWindow(g);let Mt=()=>{let o=oe.length===N.timestamps.length?oe:[],u=ke!=null?ke:St;ne.render({series:N,scored:o,params:u,alerts:L,incidents:i})},ct=p("div","dtk-th"),Ct=p("div","dtk-th-toggles"),Ze=p("button","dtk-th-toggle","Threshold capture");Ze.type="button",Ze.title="Mark incidents fast: set a horizontal line and grab every contiguous span above (or below) it. Click the chart to set the line, drag across it to limit the capture to a time window.",Ct.appendChild(Ze);let Re=p("button","dtk-th-toggle","Lasso anomalies");Re.type="button",Re.title="Draw a freeform loop around a cloud of anomaly dots on the chart below \u2014 each run of consecutive anomalies (small gaps bridged) becomes one incident span. The ideal way to turn what the detector already flags into ground-truth incidents.",Ct.appendChild(Re),ct.appendChild(Ct);let we=p("div","dtk-th-bar"),Ve=p("select","dtk-th-sel");for(let[o,u]of[["above","above the line"],["below","below the line"]]){let f=p("option");f.value=o,f.textContent=u,Ve.appendChild(f)}let Z=p("input","dtk-th-num");Z.type="number",Z.step="any",Z.placeholder="hover chart";let ze=p("input","dtk-th-num");ze.type="number",ze.min="0",ze.step="1",ze.value="0";let jt=p("span","dtk-th-scope"),Je=p("button","dtk-inc-btn","\u21BA whole view");Je.type="button",Je.style.display="none";let Fe=p("button","dtk-apply-btn dtk-th-add","Add 0 spans");Fe.type="button",Fe.disabled=!0;let Tt=p("button","dtk-inc-btn","Done");Tt.type="button";let Qe=(o,u)=>{let f=p("label","dtk-th-grp");return f.appendChild(p("span","dtk-th-lbl",o)),f.appendChild(u),f};we.appendChild(Qe("grab points",Ve)),we.appendChild(Qe("line value",Z)),we.appendChild(Qe("bridge gaps \u2264",ze)),we.appendChild(jt),we.appendChild(Je),we.appendChild(Fe),we.appendChild(Tt),we.style.display="none",ct.appendChild(we);let Ue=p("div","dtk-th-bar"),Vt=p("span","dtk-th-scope","draw a loop around the anomaly dots on the chart below"),At=p("button","dtk-inc-btn","Done");At.type="button",Ue.appendChild(Vt),Ue.appendChild(At),Ue.style.display="none",ct.appendChild(Ue),G.insertBefore(ct,He);let ut=!1,mt=!1,Ge=!1,et=o=>{ut=o,Ze.classList.toggle("on",o),we.style.display=o?"flex":"none",ne.setThresholdMode(o),o&&mt&&tt(!1)},tt=o=>{mt=o,Re.classList.toggle("on",o),Ue.style.display=o?"flex":"none",ne.setLassoMode(o),o&&ut&&et(!1)};Ze.onclick=()=>et(!ut),Tt.onclick=()=>et(!1),Re.onclick=()=>tt(!mt),At.onclick=()=>tt(!1),Ve.onchange=()=>ne.setThresholdDirection(Ve.value),Z.oninput=()=>{let o=Z.value.trim();Ge=!0,ne.setThresholdValue(o!==""&&!isNaN(Number(o))?Number(o):null),Ge=!1},ze.oninput=()=>ne.setThresholdGap(Number(ze.value)||0),Je.onclick=()=>ne.clearCaptureWindow(),Fe.onclick=()=>{ne.applyThreshold()},window.addEventListener("keydown",o=>{o.key==="Escape"&&(ut?et(!1):mt&&tt(!1))}),qt=o=>{Vt.textContent=o.active?`${o.anomalies} anomal${o.anomalies===1?"y":"ies"} \u2192 ${o.incidents} incident${o.incidents===1?"":"s"} \u2014 release to add`:"draw a loop around the anomaly dots on the chart below"},Ft=o=>{Fe.textContent=`Add ${o.runs} span${o.runs===1?"":"s"}`,Fe.disabled=o.runs===0,o.locked&&o.value!=null&&!Ge&&(Z.value=String(Math.round(o.value*1e3)/1e3)),Je.style.display=o.committed?"":"none",jt.textContent=o.window?`scope: ${Ht(o.windowMs)} painted`:"scope: current view \u2014 drag the chart to limit it"};let nt=new Worker(URL.createObjectURL(new Blob([`"use strict";(()=>{var X={mad:1,zscore:2,iqr:4};function C(e){let n=0;for(let t of e)n+=t;return n}function un(e){let n=e.map((t,o)=>o);return n.sort((t,o)=>e[t]<e[o]?-1:e[t]>e[o]?1:t-o),n}function cn(e,n,t){let o=n.length;if(o===0)return NaN;if(e<=n[0])return t[0];if(e>=n[o-1])return t[o-1];for(let i=1;i<o;i++)if(e<=n[i]){let r=n[i-1],s=n[i],c=t[i-1],u=t[i];if(s===r)return c;let d=(e-r)/(s-r);return c+d*(u-c)}return t[o-1]}function V(e,n,t){let o=un(e),i=o.map(m=>e[m]),r=o.map(m=>n[m]),s=C(r),c=new Array(r.length),u=0;for(let m=0;m<r.length;m++)u+=r[m],c[m]=(u-.5*r[m])/s;let d=t/100;return cn(d,c,i)}function P(e,n){return V(e,n,50)}function ln(e,n,t){let o=t===void 0?P(e,n):t,i=e.map(r=>Math.abs(r-o));return P(i,n)}function mn(e,n){let t=C(n),o=0;for(let i=0;i<e.length;i++)o+=e[i]*(n[i]/t);return o}function an(e,n,t,o=0){let i=C(n),r=n.map(u=>u/i),s=t===void 0?C(e.map((u,d)=>u*r[d])):t,c=0;for(let u=0;u<e.length;u++)c+=r[u]*(e[u]-s)**2;if(o===1){let u=1-C(r.map(d=>d*d));u>1e-12&&(c/=u)}return Math.sqrt(c)}function fn(e,n){return n.smoothing==="none"?e.slice():n.smoothing==="ema"?dn(e,n.smoothingAlpha):bn(e,n.smoothingWindow)}function dn(e,n){let t=e.length,o=new Array(t).fill(NaN);if(t===0)return o;let i=-1;for(let r=0;r<t;r++)if(!Number.isNaN(e[r])){i=r;break}if(i===-1)return o;o[i]=e[i];for(let r=i+1;r<t;r++)Number.isNaN(e[r])?o[r]=o[r-1]:o[r]=n*e[r]+(1-n)*o[r-1];return o}function bn(e,n){let t=e.length,o=new Array(t).fill(NaN);for(let i=0;i<t;i++){let r=Math.max(0,i-n+1),s=0,c=0;for(let u=r;u<=i;u++)Number.isNaN(e[u])||(s+=e[u],c+=1);o[i]=c>0?s/c:NaN}return o}function Y(e,n){let t=e.length;if(n.inputType==="values")return e.slice();let o=new Array(t).fill(NaN);for(let i=1;i<t;i++){let r=e[i-1],s=e[i];n.inputType==="changes"?o[i]=(s-r)/r:n.inputType==="absolute_changes"?o[i]=s-r:o[i]=Math.log(s+1)-Math.log(r+1)}return o}function pn(e,n){return e.halfLife!==null?e.halfLife:Math.max(e.windowSize/20,n/2,1)}function yn(e,n){if(e.windowWeights==="none")return null;let t=e.windowSize,o=new Array(t);if(e.windowWeights==="exponential"){let i=pn(e,n);for(let r=1;r<=t;r++){let s=Math.min(r/i,1e3);o[r-1]=Math.pow(.5,s)}return o}for(let i=1;i<=t;i++)o[i-1]=(t+1-i)/t;return o}function Q(e,n){return n===null?e.map(()=>1):e.map(t=>n[t-1])}function gn(e){let n=e[0];for(let t of e)t>n&&(n=t);return n}function hn(e){let n=e[0];for(let t of e)t<n&&(n=t);return n}function Sn(e,n,t){if(e.length<4)return 0;let o=(gn(n)+hn(n))/2,i=n.map(f=>f>o),r=i.map(f=>!f),s=0,c=0;for(let f=0;f<n.length;f++)i[f]?s++:c++;if(s<2||c<2)return 0;let u=(f,M)=>M.filter((x,w)=>f[w]),d=u(r,e),m=u(i,e),N=u(r,n),y=u(i,n),h=u(r,t),a=u(i,t),A=P(d,h),p=P(m,a),S=P(N,h),g=P(y,a);return g===S?0:(A-p)/(g-S)}var wn={mad:[["median","center"],["mad","spread"]],zscore:[["mean","center"],["std","spread"]],iqr:[["q1","center"],["q3","center"],["iqr","spread"]]};function U(e,n,t){if(e==="mad"){let r=P(n,t);return{median:r,mad:ln(n,t,r)}}if(e==="zscore"){let r=mn(n,t);return{mean:r,std:an(n,t,r,1)}}let o=V(n,t,25),i=V(n,t,75);return{q1:o,q3:i,iqr:i-o}}function Nn(e,n,t){if(e==="mad"){if(n.mad===0)return[n.median-1e-10,n.median+1e-10];let o=t*1.4826*n.mad;return[n.median-o,n.median+o]}return e==="zscore"?n.std===0?[n.mean-1e-10,n.mean+1e-10]:[n.mean-t*n.std,n.mean+t*n.std]:n.iqr===0?[n.q1-1e-10,n.q3+1e-10]:[n.q1-t*n.iqr,n.q3+t*n.iqr]}function An(e,n,t){if(e==="mad"){let o=1.4826*n.mad;return o>0?t/o:1/0}return e==="zscore"?n.std>0?t/n.std:1/0:n.iqr>0?t/n.iqr:1/0}function Mn(e,n){return e==="mad"?n.median:e==="zscore"?n.mean:(n.q1+n.q3)/2}function j(e,n,t,o,i){return{index:e,timestamp:n,value:t,processedValue:o,scored:!1,isAnomaly:!1,lower:NaN,upper:NaN,center:NaN,direction:null,severity:0,reason:i}}function Dn(e,n){var d,m,N;let{timestamps:t,values:o}=e,i=t.length,r=Y(o,n),s=(d=n.lowerBound)!=null?d:null,c=(m=n.upperBound)!=null?m:null,u=[];for(let y=0;y<i;y++){let h=o[y],a=r[y],A=t[y];if(Number.isNaN(a)){u.push(j(y,A,h,a,"missing_data"));continue}let p=!1,S=null,g=0;s!==null&&a<s&&(p=!0,S="below",g=s-a),c!==null&&a>c&&(p=!0,S="above",g=a-c);let f=0;if(p)if(s!==null&&c!==null){let T=c-s;f=T>0?g/T:1/0}else f=g;let M=s!=null?s:-1/0,x=c!=null?c:1/0,w=s!==null&&c!==null?(s+c)/2:(N=s!=null?s:c)!=null?N:NaN;u.push({index:y,timestamp:A,value:h,processedValue:a,scored:!0,isAnomaly:p,lower:M,upper:x,center:w,direction:S,severity:f,reason:"ok"})}return u}function Z(e,n){if(n.type==="manual_bounds")return Dn(e,n);let t=n.type,o=Math.max(n.minSamples,X[t]),i=wn[t],{timestamps:r,values:s}=e,c=r.length,u=fn(s,n),d=Y(u,n),m=n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0,N=e.seasonalityData,y=yn(n,o),h=[];for(let a=0;a<c;a++){let A=s[a],p=d[a],S=r[a];if(Number.isNaN(p)){h.push(j(a,S,A,p,"missing_data"));continue}let g=Math.max(0,a-n.windowSize),f=a-g,M=d.slice(g,a),x=M.map(l=>!Number.isNaN(l)),w=[];for(let l=0;l<M.length;l++)x[l]&&w.push(M[l]);if(w.length<o){h.push(j(a,S,A,p,"insufficient_data"));continue}let T=new Array(f);for(let l=0;l<f;l++)T[l]=f-l;let z=[];for(let l=0;l<f;l++)x[l]&&z.push(T[l]);let B=Q(z,y),W=0;n.detrend==="linear"&&(W=Sn(w,z,B));let nn=W!==0?w.map((l,O)=>l+W*z[O]):w,G=U(t,nn,B),I={...G};if(m&&N){let l=N[a];for(let O of n.seasonalityComponents){let K=new Array(f).fill(!0);for(let b=0;b<f;b++){let q=N[g+b],v=!0;for(let E of O)if(!q||q[E]!==(l==null?void 0:l[E])){v=!1;break}K[b]=v}let L=[],F=[];for(let b=0;b<f;b++)x[b]&&K[b]&&(L.push(M[b]),F.push(T[b]));if(L.length<n.minSamplesPerGroup)continue;let tn=Q(F,y),on=W!==0?L.map((b,q)=>b+W*F[q]):L,rn=U(t,on,tn);for(let[b,q]of i){let v=G[b],sn=(q==="spread"?v>0:v!==0)?rn[b]/v:1;I[b]*=sn}}}let[D,k]=Nn(t,I,n.threshold);D>k&&([D,k]=[k,D]);let H=p<D||p>k,R=null,J=0;if(H){let l;p<D?(R="below",l=D-p):(R="above",l=p-k),J=An(t,I,l)}let en=Mn(t,I);h.push({index:a,timestamp:S,value:A,processedValue:p,scored:!0,isAnomaly:H,lower:D,upper:k,center:en,direction:R,severity:J,reason:"ok"})}return h}function Pn(e,n){let t=e.seasonalityData;if(!t||t.length===0)return 0;let o=0;for(let i of n){let r=new Set;for(let s of t)r.add(i.map(c=>{var u;return String((u=s==null?void 0:s[c])!=null?u:"")}).join("|"));o=Math.max(o,r.size)}return o}function $(e,n){let t=e.timestamps.length;if(n.type==="manual_bounds")return Math.min(n.inputType!=="values"?1:0,t);let o=Math.max(n.minSamples,X[n.type]);if(n.smoothing==="sma"&&(o=Math.max(o,n.smoothingWindow-1)),n.smoothing==="ema"&&(o=Math.max(o,Math.ceil(5/n.smoothingAlpha))),n.inputType!=="values"&&(o=Math.max(o,1)),n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0){let r=Pn(e,n.seasonalityComponents);if(r>0){let s=n.minSamplesPerGroup*r;n.windowSize>=s&&(o=Math.max(o,s))}}return Math.min(o,t)}var _=null;function xn(e,n){if(!n||n==="any")return;let t=n==="up"?"above":"below";for(let o of e)o.isAnomaly&&o.direction!==t&&(o.isAnomaly=!1)}function Tn(e,n,t){let o=[],i=e.length,r=0;for(;r<i;){if(!(e[r].scored&&e[r].isAnomaly)){r++;continue}let s=r;for(;s+1<i&&e[s+1].scored&&e[s+1].isAnomaly&&e[s+1].timestamp-e[s].timestamp===n;)s++;s-r+1>=t&&o.push({fire:r+t-1,startTs:e[r].timestamp,endTs:e[s].timestamp}),r=s+1}return o}self.onmessage=e=>{let n=e.data;if(n.type==="series"){_=n.series;return}if(n.type==="run"&&_){let t=n.params,o=Z(_,t);xn(o,t.direction);let i=_.intervalSeconds*1e3,r=Tn(o,i,t.consecutiveAnomalies),s=r.map(m=>m.fire),c=r.map(m=>[m.startTs,m.endTs]),u=$(_,t),d=0;for(let m of o)m.scored&&m.isAnomaly&&d++;self.postMessage({type:"result",id:n.id,scored:o,fires:s,fireSpans:c,eff:u,flagged:d})}};})();
|
|
2
|
+
`],{type:"text/javascript"})));nt.postMessage({type:"series",series:N});let R=0,ke=null;nt.onmessage=o=>{let u=o.data;if(u.type!=="result"||u.id!==R||!ke)return;Ae.classList.remove("on");let f=ke,M=u.fires.map(J=>N.timestamps[J]),W=M.map(J=>({t:J,kind:"anomaly"}));Ee.render({series:N,scored:u.scored,params:f,alerts:W,incidents:i}),oe=u.scored,y=M,$=u.fireSpans,L=W,Mt(),Oe(),v.textContent=`${u.flagged} flagged \xB7 ${u.fires.length} alert${u.fires.length===1?"":"s"} \xB7 warm-up ${u.eff} pts`,Ot(f),A.textContent=Ln(f,ve)},nt.onerror=()=>{Ae.classList.remove("on"),v.textContent="recompute failed \u2014 see the browser console"};let qe=()=>{ke=xe(),R+=1,Ae.classList.add("on"),nt.postMessage({type:"run",id:R,params:ke})},Me=o=>o>1?Ht(x.timestamps[e-1]-x.timestamps[e-o]):"\u2014",ae=o=>{Ne.textContent=o>=e?`${e} pts \xB7 full (${Me(e)})`:`${o} pts \xB7 ${Me(o)}`};ae(e);function ot(o){N=F(o),nt.postMessage({type:"series",series:N}),Mt(),ue()}let Dt=0;he.oninput=()=>{let o=Number(he.value);ae(o),Dt&&window.clearTimeout(Dt),Dt=window.setTimeout(()=>ot(o),200)};let pt=0,ue=()=>{pt&&window.clearTimeout(pt),pt=window.setTimeout(qe,130)},rt=Rt("Detector",[{label:"MAD",value:"mad"},{label:"Z-Score",value:"zscore"},{label:"IQR",value:"iqr"},{label:"Manual",value:"manual_bounds"}],T.type,o=>{var u;Ut((u=Tn[o])!=null?u:3),m(),ue()},"The statistic for the band: MAD (robust median, default), Z-Score (mean/std) or IQR (quartiles) \u2014 all windowed \u2014 or Manual (fixed lower/upper thresholds, no window/history).");U.appendChild(rt.row);let ft=yt("Lower bound",{min:Y,max:V,step:fe,value:Se,fmt:o=>zt(o),hint:"Manual bounds: values below this read as anomalous. Drag in from the data range to see how many points fall outside (and how many alerts that yields)."},ue);U.appendChild(ft.row);let ht=yt("Upper bound",{min:Y,max:V,step:fe,value:Le,fmt:o=>zt(o),hint:"Manual bounds: values above this read as anomalous."},ue);U.appendChild(ht.row);let Ye=yt("Threshold (\u03C3-equivalent)",{min:.5,max:10,step:.1,value:T.threshold,fmt:o=>o.toFixed(1),hint:"Band half-width in \u03C3-equivalents. Lower = tighter band = more flags; higher = wider band = fewer flags."},ue);U.appendChild(Ye.row);let Lt=Ye.row.querySelector("input"),Pt=Ye.row.querySelector(".dtk-ctl-val"),Ut=o=>{Lt&&(Lt.value=String(o)),Pt&&(Pt.textContent=o.toFixed(1))},Qt=Math.max(50,Math.min(2e3,Math.floor(e/2))),Gt=Math.max(Qt,T.windowSize),Nt=yt("Window size (points)",{min:Math.max(1,Math.min(10,T.windowSize)),max:Gt,step:1,value:T.windowSize,fmt:o=>`${o} \xB7 ${Ht(o*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."},ue);U.appendChild(Nt.row);let it=Rt("Recency weighting",[{label:"none",value:"none"},{label:"exponential",value:"exponential"},{label:"linear",value:"linear"}],T.windowWeights,()=>{m(),ue()},"Weight recent points in the window more heavily: none (flat), exponential (half-life decay) or linear. Helps the baseline track a drifting level.");U.appendChild(it.row);let Yt=yt("Half-life (points)",{min:1,max:Gt,step:1,value:(dn=T.halfLife)!=null?dn:Math.max(5,Math.round(T.windowSize/20)),fmt:o=>`${o} \xB7 ${Ht(o*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."},ue),Et=Yt.row;Et.style.display=T.windowWeights==="exponential"?"":"none",U.appendChild(Et);let Wt=Rt("Detrend",[{label:"none",value:"none"},{label:"linear",value:"linear"}],T.detrend,ue,"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.");U.appendChild(Wt.row);let _t=Rt("Smoothing",[{label:"none",value:"none"},{label:"EMA",value:"ema"},{label:"SMA",value:"sma"}],T.smoothing,ue,"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.");U.appendChild(_t.row);let It=null;if(t.seasonality_columns.length){let o=t.seasonality_columns,u=p("div","dtk-ctl");It=u,u.appendChild(wt("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 f=Math.min(o.length,6),M=[{label:"\u2014",value:"0"}];for(let W=1;W<=f;W++)M.push({label:`G${W}`,value:String(W)});o.forEach(W=>{let J=p("div","dtk-season-row");J.appendChild(p("span","dtk-season-col",W));let We=p("div","dtk-seg dtk-season-seg"),$t=[],gt=()=>{var ce;return(ce=O.get(W))!=null?ce:0},vt=()=>$t.forEach(ce=>ce.classList.toggle("on",Number(ce.dataset.v)===gt()));M.forEach(ce=>{let me=p("button","dtk-seg-btn",ce.label);me.type="button",me.dataset.v=ce.value,me.title=ce.value==="0"?`ignore ${W}`:`put ${W} in group ${ce.value}`,me.onclick=()=>{O.set(W,Number(ce.value)),vt(),ue()},$t.push(me),We.appendChild(me)}),vt(),J.appendChild(We),u.appendChild(J)}),U.appendChild(u)}let Kt=Rt("Direction",[{label:"both",value:"any"},{label:"up",value:"up"},{label:"down",value:"down"}],(cn=t.direction)!=null?cn:"any",ue,"Which anomalies to show and count toward alerts: both directions, only spikes ABOVE the band (up) or only drops BELOW it (down). A preview filter mirroring the alert direction policy \u2014 it never changes the band itself.");U.appendChild(Kt.row);let en=yt("Alert: consecutive anomalies",{min:1,max:10,step:1,value:ve,fmt:o=>String(o),hint:"How many anomalies in a row are required before an alert fires. Higher = fewer, more-confident alerts (the \u25BC markers on the chart)."},o=>{ve=o,ue()});U.appendChild(en.row);let Xt=p("div","dtk-ctl"),bt=p("label","dtk-check"),st=p("input");st.type="checkbox",st.onchange=()=>{Ee.setZeroLine(st.checked),ne.setZeroLine(st.checked)},bt.title="Draw a horizontal line at y = 0 and include zero in the scale \u2014 for real-valued metrics best read relative to zero.",bt.appendChild(st),bt.appendChild(document.createTextNode(" Show y = 0 line")),Xt.appendChild(bt),U.appendChild(Xt);let n=p("div","dtk-ctl dtk-incidents");n.appendChild(wt("Marked incidents","The real incidents marked on the lower chart. Edit a label, focus to zoom both charts to it, or remove it. Save the set below to incidents/<metric>/ \u2014 the same store dtk autotune reads."));let r=p("div","dtk-inc-list");n.appendChild(r),U.appendChild(n);function l(o){let u=Math.max((o.end-o.start)*.5,t.interval_seconds*1e3*5);Ee.setViewWindow(o.start-u,o.end+u),ne.setViewWindow(o.start-u,o.end+u)}function a(o){let u=i.indexOf(o);u>=0&&i.splice(u,1),ne.setIncidents(i),Ee.setIncidents(i),s(),Oe()}function s(){r.innerHTML="";let o=[...i].sort((u,f)=>u.start-f.start);if(!o.length){r.appendChild(p("div","dtk-inc-empty","None yet \u2014 drag across the lower chart to mark one."));return}for(let u of o){let f=p("div","dtk-inc-row");f.appendChild(p("span","dtk-inc-span",`${ln(u.start)} \u2192 ${ln(u.end)}`)),f.appendChild(p("span","dtk-inc-dur",Ht(Math.max(0,u.end-u.start))));let M=p("input","dtk-inc-label");M.type="text",M.value=u.label||"",M.placeholder="label (optional)",M.oninput=()=>{u.label=M.value},f.appendChild(M);let W=p("button","dtk-inc-btn","focus");W.type="button",W.onclick=()=>l(u),f.appendChild(W);let J=p("button","dtk-inc-btn dtk-inc-del","\u2715");J.type="button",J.title="remove this incident",J.onclick=()=>a(u),f.appendChild(J),r.appendChild(f)}}let c=[Ye.row,Nt.row,it.row,Wt.row,_t.row];It&&c.push(It);function m(){let o=rt.get()==="manual_bounds";for(let u of c)u.style.display=o?"none":"";ft.row.style.display=o?"":"none",ht.row.style.display=o?"":"none",Et.style.display=!o&&it.get()==="exponential"?"":"none"}m();let v=p("div","dtk-tune-stat");G.appendChild(v);let P=p("div","dtk-tune-cfg");P.appendChild(p("span","dtk-tune-cfg-k","// effective config"));let A=p("code","dtk-tune-cfg-v");if(P.appendChild(A),G.appendChild(P),t.save_url){let o=p("div","dtk-tune-apply"),u=p("button","dtk-apply-btn","Apply to metric");u.type="button",u.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 f=p("span","dtk-apply-msg");u.onclick=()=>{let M=xe();u.disabled=!0,f.className="dtk-apply-msg info",f.textContent="Applying\u2026",fetch(t.save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detector:{type:M.type,params:xn(M)},consecutive_anomalies:ve})}).then(W=>W.ok?W.json():W.text().then(J=>{throw new Error(J||`HTTP ${W.status}`)})).then(W=>{var J;f.className="dtk-apply-msg ok",f.textContent=`Applied \u2192 ${(J=W.saved)!=null?J:"metric"} (previous archived). You can close this tab.`}).catch(W=>{u.disabled=!1,f.className="dtk-apply-msg err",f.textContent=`Apply failed: ${W.message}`})},o.appendChild(u),o.appendChild(f),G.appendChild(o)}else G.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."));let E=o=>'"'+o.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"',B=o=>new Date(o).toISOString().slice(0,19).replace("T"," "),le=()=>{let o=[`metric: ${t.metric}`,"timezone: UTC"],u=[...i].sort((M,W)=>M.start-W.start);if(!u.length)o.push("incidents: []");else{o.push("incidents:");for(let M of u){let W=M.label?`, label: ${E(M.label)}`:"";o.push(` - {start: "${B(M.start)}", end: "${B(M.end)}"${W}}`)}}let f=ne.getCaptureWindow();return f&&(o.push("capture_windows:"),o.push(` - {start: "${B(f.start)}", end: "${B(f.end)}"}`)),o.join(`
|
|
3
|
+
`)+`
|
|
4
|
+
`},k=p("div","dtk-tune-apply"),C=p("input","dtk-setname");C.type="text",C.placeholder="name this set (optional)";let S=p("button","dtk-apply-btn dtk-labels-btn",t.labels_save_url?"Save incidents":"Download incidents");S.type="button",S.title="Write the marked incidents to incidents/<metric>/ (versioned) so they also feed the next `dtk autotune`. Saving labels does not end tuning \u2014 keep adjusting and apply the detector when ready.";let _=p("span","dtk-apply-msg");S.onclick=()=>{let o=le();if(!t.labels_save_url){let u=new Blob([o],{type:"text/yaml"}),f=p("a");f.href=URL.createObjectURL(u),f.download=`${t.metric}.yml`,f.click(),URL.revokeObjectURL(f.href),_.className="dtk-apply-msg ok",_.textContent=`Downloaded \u2014 drop it into incidents/${t.metric}/`;return}_.className="dtk-apply-msg info",_.textContent="Saving\u2026",fetch(t.labels_save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:C.value,yaml:o})}).then(u=>u.ok?u.json():u.text().then(f=>{throw new Error(f||`HTTP ${u.status}`)})).then(u=>{var f;_.className="dtk-apply-msg ok",_.textContent=`Saved \u2192 ${(f=u.saved)!=null?f:"incidents"} (keep tuning, or Apply the detector)`}).catch(u=>{_.className="dtk-apply-msg err",_.textContent=`Save failed: ${u.message}`})},k.appendChild(C),k.appendChild(S),k.appendChild(_),G.appendChild(k),Mt(),s(),qe(),Oe();let de=0;window.addEventListener("resize",()=>{de&&cancelAnimationFrame(de),de=requestAnimationFrame(()=>{Ee.resize(),ne.resize()})})}function zt(t){if(!Number.isFinite(t))return"\u2014";let d=Math.abs(t);return d!==0&&(d<.01||d>=1e6)?t.toExponential(2):Number(t.toFixed(d<1?4:2)).toString()}function ln(t){return new Date(t).toISOString().slice(0,16).replace("T"," ")}function Ht(t){let d=Math.round(t/6e4);if(d<60)return`${d}m`;let h=Math.floor(d/60);if(h<24)return`${h}h`;let e=Math.floor(h/24),x=h%24;return x?`${e}d ${x}h`:`${e}d`}function Nn(t){return t%86400===0?`${t/86400}d`:t%3600===0?`${t/3600}h`:t%60===0?`${t/60}min`:`${t}s`}var vn=!1;function En(){if(vn)return;vn=!0;let t=`
|
|
5
|
+
.dtk-tune{--c:#d15b36;--c7:#b4471f;--ink:#1b1916;--muted:#6e675b;--faint:#9a9384;
|
|
6
|
+
--paper:#f5f1e8;--surface:#fbf9f3;--border:#e6e0d4;--green:#2e9e73;--anom:#d63232;
|
|
7
|
+
--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
|
|
8
|
+
--sans:'Schibsted Grotesk',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
9
|
+
.dtk-tune-root{max-width:1200px;margin:0 auto;padding:24px 20px 56px;font-family:var(--sans);color:var(--ink);}
|
|
10
|
+
.dtk-tune-titlerow{display:flex;align-items:center;gap:12px;}
|
|
11
|
+
.dtk-tune-title{font-size:24px;margin:0;font-weight:700;}
|
|
12
|
+
.dtk-tune-badge{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
|
|
13
|
+
color:#fff;background:var(--c);border-radius:999px;padding:3px 10px;}
|
|
14
|
+
.dtk-tune-sub{color:var(--muted);font-size:13px;margin-top:4px;font-family:var(--mono);}
|
|
15
|
+
.dtk-tune-desc{color:var(--muted);font-size:13px;margin-top:8px;white-space:pre-wrap;}
|
|
16
|
+
.dtk-tune-grid{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-top:20px;align-items:start;}
|
|
17
|
+
@media(max-width:820px){.dtk-tune-grid{grid-template-columns:1fr;}}
|
|
18
|
+
.dtk-tune-controls{display:flex;flex-direction:column;gap:16px;background:var(--surface);
|
|
19
|
+
border:1px solid var(--border);border-radius:12px;padding:16px;}
|
|
20
|
+
.dtk-ctl{display:flex;flex-direction:column;gap:6px;}
|
|
21
|
+
.dtk-ctl-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
22
|
+
.dtk-ctl-label{font-size:12px;font-weight:600;color:var(--ink);}
|
|
23
|
+
.dtk-ctl-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
24
|
+
.dtk-seg{display:flex;gap:4px;background:var(--paper);border:1px solid var(--border);border-radius:8px;padding:3px;}
|
|
25
|
+
.dtk-seg.dtk-wrap{flex-wrap:wrap;}
|
|
26
|
+
.dtk-seg-btn{flex:1 1 auto;border:0;background:transparent;color:var(--muted);font-family:var(--sans);
|
|
27
|
+
font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;white-space:nowrap;}
|
|
28
|
+
.dtk-seg-btn:hover{color:var(--ink);}
|
|
29
|
+
.dtk-seg-btn.on{background:var(--c);color:#fff;font-weight:600;}
|
|
30
|
+
.dtk-range{width:100%;accent-color:var(--c);cursor:pointer;}
|
|
31
|
+
.dtk-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);margin-top:2px;cursor:pointer;}
|
|
32
|
+
.dtk-tune-main{display:flex;flex-direction:column;gap:10px;min-width:0;}
|
|
33
|
+
.dtk-tune-chart{position:relative;width:100%;height:470px;background:var(--surface);
|
|
34
|
+
border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
|
35
|
+
.dtk-tune-chart canvas{width:100%;height:100%;display:block;}
|
|
36
|
+
.dtk-tune-readout{font-family:var(--mono);font-size:12px;color:var(--muted);min-height:18px;}
|
|
37
|
+
.dtk-tune-stat{font-family:var(--mono);font-size:12px;color:var(--ink);}
|
|
38
|
+
.dtk-tune-warn{font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--c7);
|
|
39
|
+
background:rgba(240,173,78,0.13);border:1px solid rgba(240,173,78,0.5);border-radius:8px;padding:8px 11px;}
|
|
40
|
+
.dtk-tune-cfg{background:var(--ink);color:#c9c2b4;border-radius:8px;padding:10px 12px;font-family:var(--mono);
|
|
41
|
+
font-size:12px;overflow-x:auto;}
|
|
42
|
+
.dtk-tune-cfg-k{color:var(--faint);display:block;margin-bottom:4px;}
|
|
43
|
+
.dtk-tune-cfg-v{color:#e6e0d4;white-space:pre-wrap;word-break:break-word;}
|
|
44
|
+
.dtk-tune-apply{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px;}
|
|
45
|
+
.dtk-apply-btn{background:var(--c);color:#fff;border:0;border-radius:8px;padding:10px 18px;font-family:var(--sans);
|
|
46
|
+
font-size:14px;font-weight:600;cursor:pointer;}
|
|
47
|
+
.dtk-apply-btn:hover{background:var(--c7);}
|
|
48
|
+
.dtk-apply-btn:disabled{opacity:.55;cursor:default;}
|
|
49
|
+
.dtk-apply-msg{font-size:13px;}
|
|
50
|
+
.dtk-apply-msg.ok{color:var(--green);}
|
|
51
|
+
.dtk-apply-msg.err{color:var(--anom);}
|
|
52
|
+
.dtk-apply-msg.info{color:var(--muted);}
|
|
53
|
+
.dtk-tune-note{font-size:13px;color:var(--muted);background:var(--surface);border:1px dashed var(--border);
|
|
54
|
+
border-radius:8px;padding:10px 12px;}
|
|
55
|
+
.dtk-ctl-info{color:var(--faint);font-size:10px;cursor:help;vertical-align:super;}
|
|
56
|
+
.dtk-tune-trim{display:flex;flex-direction:column;gap:6px;background:var(--surface);
|
|
57
|
+
border:1px solid var(--border);border-radius:10px;padding:9px 12px;}
|
|
58
|
+
.dtk-tune-trim-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
59
|
+
.dtk-tune-trim-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
60
|
+
.dtk-tune-spin{position:absolute;top:10px;right:12px;display:none;align-items:center;gap:7px;
|
|
61
|
+
background:rgba(27,25,22,0.78);color:#e6e0d4;border:1px solid #332f29;border-radius:999px;
|
|
62
|
+
padding:4px 11px 4px 8px;font-family:var(--mono);font-size:11px;pointer-events:none;}
|
|
63
|
+
.dtk-tune-spin.on{display:inline-flex;}
|
|
64
|
+
.dtk-spin-ring{width:12px;height:12px;border-radius:50%;border:2px solid rgba(245,241,232,0.25);
|
|
65
|
+
border-top-color:var(--c);animation:dtk-spin .7s linear infinite;}
|
|
66
|
+
@keyframes dtk-spin{to{transform:rotate(360deg);}}
|
|
67
|
+
.dtk-tune-legend{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--muted);padding:2px 2px 0;}
|
|
68
|
+
.dtk-leg-item{display:inline-flex;align-items:center;gap:6px;cursor:help;}
|
|
69
|
+
.dtk-leg-sw{display:inline-block;flex:0 0 auto;}
|
|
70
|
+
.dtk-leg-sw.line{width:16px;height:3px;background:var(--c);border-radius:2px;}
|
|
71
|
+
.dtk-leg-sw.band{width:16px;height:11px;background:rgba(209,91,54,0.18);
|
|
72
|
+
border:1px solid rgba(209,91,54,0.5);border-radius:2px;}
|
|
73
|
+
.dtk-leg-sw.center{width:16px;height:2px;
|
|
74
|
+
background:repeating-linear-gradient(90deg,var(--faint) 0 4px,transparent 4px 7px);}
|
|
75
|
+
.dtk-leg-sw.dot{width:9px;height:9px;border-radius:50%;background:var(--anom);}
|
|
76
|
+
.dtk-leg-sw.alert{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;
|
|
77
|
+
border-top:7px solid var(--anom);}
|
|
78
|
+
.dtk-leg-txt{white-space:nowrap;}
|
|
79
|
+
.dtk-season-row{display:flex;align-items:center;justify-content:space-between;gap:8px;}
|
|
80
|
+
.dtk-season-col{font-family:var(--mono);font-size:11.5px;color:var(--muted);
|
|
81
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
82
|
+
.dtk-season-seg{flex:0 0 auto;padding:2px;}
|
|
83
|
+
.dtk-season-seg .dtk-seg-btn{flex:0 0 auto;padding:3px 7px;font-family:var(--mono);font-size:11px;}
|
|
84
|
+
.dtk-tune-metrics{display:flex;flex-wrap:wrap;gap:10px;margin:16px 0 2px;}
|
|
85
|
+
.dtk-m-chip{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;background:var(--surface);
|
|
86
|
+
border:1px solid var(--border);border-radius:10px;font-size:13px;}
|
|
87
|
+
.dtk-m-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;}
|
|
88
|
+
.dtk-m-v{font-family:var(--mono);font-weight:700;font-size:15px;color:var(--ink);}
|
|
89
|
+
.dtk-m-l{color:var(--faint);font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
|
90
|
+
.dtk-m-sub{color:var(--muted);font-family:var(--mono);font-size:11.5px;}
|
|
91
|
+
.dtk-tune-labhead{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
|
|
92
|
+
margin-top:4px;flex-wrap:wrap;}
|
|
93
|
+
.dtk-tune-labhint{font-family:var(--mono);font-size:11px;color:var(--faint);}
|
|
94
|
+
.dtk-tune-labchart{height:240px;}
|
|
95
|
+
.dtk-th{display:flex;flex-direction:column;gap:8px;margin:2px 0 6px;}
|
|
96
|
+
.dtk-th-toggles{display:flex;gap:8px;flex-wrap:wrap;}
|
|
97
|
+
.dtk-th-toggle{align-self:flex-start;border:1px solid var(--border);background:var(--surface);
|
|
98
|
+
color:var(--muted);border-radius:8px;padding:6px 12px;font-family:var(--sans);font-size:12.5px;cursor:pointer;}
|
|
99
|
+
.dtk-th-toggle:hover{border-color:var(--c);color:var(--c7);}
|
|
100
|
+
.dtk-th-toggle.on{background:var(--c);border-color:var(--c);color:#fff;}
|
|
101
|
+
.dtk-th-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px 14px;padding:11px 13px;
|
|
102
|
+
background:var(--surface);border:1px solid var(--border);border-radius:10px;}
|
|
103
|
+
.dtk-th-grp{display:flex;flex-direction:column;gap:3px;}
|
|
104
|
+
.dtk-th-lbl{font-family:var(--mono);font-size:10.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.05em;}
|
|
105
|
+
.dtk-th-num,.dtk-th-sel{background:var(--paper);color:var(--ink);border:1px solid var(--border);
|
|
106
|
+
border-radius:6px;padding:5px 8px;font-family:var(--mono);font-size:12px;}
|
|
107
|
+
.dtk-th-num{width:96px;}
|
|
108
|
+
.dtk-th-num:focus,.dtk-th-sel:focus{outline:none;border-color:var(--c);}
|
|
109
|
+
.dtk-th-scope{font-family:var(--mono);font-size:11px;color:var(--muted);align-self:center;flex:1 1 160px;}
|
|
110
|
+
.dtk-th-add{padding:7px 14px;}
|
|
111
|
+
.dtk-th-add:disabled{opacity:.5;cursor:default;}
|
|
112
|
+
.dtk-incidents{gap:8px;}
|
|
113
|
+
.dtk-inc-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow:auto;}
|
|
114
|
+
.dtk-inc-empty{font-size:12px;color:var(--faint);font-style:italic;}
|
|
115
|
+
.dtk-inc-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;background:var(--paper);
|
|
116
|
+
border:1px solid var(--border);border-radius:7px;padding:6px 8px;}
|
|
117
|
+
.dtk-inc-span{font-family:var(--mono);font-size:10.5px;color:var(--ink);}
|
|
118
|
+
.dtk-inc-dur{font-family:var(--mono);font-size:10.5px;color:var(--muted);}
|
|
119
|
+
.dtk-inc-label{flex:1 1 90px;min-width:70px;background:var(--surface);color:var(--ink);
|
|
120
|
+
border:1px solid var(--border);border-radius:5px;padding:4px 7px;font-family:var(--sans);font-size:11.5px;}
|
|
121
|
+
.dtk-inc-label:focus{outline:none;border-color:var(--c);}
|
|
122
|
+
.dtk-inc-btn{border:1px solid var(--border);background:var(--surface);color:var(--muted);border-radius:6px;
|
|
123
|
+
padding:3px 8px;font-size:11px;cursor:pointer;font-family:var(--sans);}
|
|
124
|
+
.dtk-inc-btn:hover{border-color:var(--c);color:var(--c7);}
|
|
125
|
+
.dtk-inc-del{color:var(--anom);}
|
|
126
|
+
.dtk-setname{background:var(--surface);color:var(--ink);border:1px solid var(--border);border-radius:8px;
|
|
127
|
+
padding:9px 11px;font-family:var(--sans);font-size:13px;min-width:180px;}
|
|
128
|
+
.dtk-setname::placeholder{color:var(--faint);}
|
|
129
|
+
.dtk-setname:focus{outline:none;border-color:var(--c);}
|
|
130
|
+
.dtk-labels-btn{background:var(--surface);color:var(--ink);border:1px solid var(--border);}
|
|
131
|
+
.dtk-labels-btn:hover{background:var(--paper);border-color:var(--c);color:var(--c7);}
|
|
132
|
+
`,d=document.createElement("style");d.textContent=t,document.head.appendChild(d)}window.__DTK_TUNE__={render:Pn};})();
|