detectkit 0.36.1__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.
Files changed (139) hide show
  1. {detectkit-0.36.1/detectkit.egg-info → detectkit-0.37.0}/PKG-INFO +1 -1
  2. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/__init__.py +1 -1
  3. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/html_labeler.py +67 -10
  4. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/autotune.md +7 -4
  5. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/cli.md +16 -8
  6. detectkit-0.37.0/detectkit/tuning/assets/tune.js +132 -0
  7. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/tuning/payload.py +48 -42
  8. {detectkit-0.36.1 → detectkit-0.37.0/detectkit.egg-info}/PKG-INFO +1 -1
  9. detectkit-0.36.1/detectkit/tuning/assets/tune.js +0 -131
  10. {detectkit-0.36.1 → detectkit-0.37.0}/LICENSE +0 -0
  11. {detectkit-0.36.1 → detectkit-0.37.0}/MANIFEST.in +0 -0
  12. {detectkit-0.36.1 → detectkit-0.37.0}/README.md +0 -0
  13. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/__init__.py +0 -0
  14. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/__init__.py +0 -0
  15. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/base.py +0 -0
  16. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/branding.py +0 -0
  17. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/email.py +0 -0
  18. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/factory.py +0 -0
  19. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/mattermost.py +0 -0
  20. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/slack.py +0 -0
  21. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/telegram.py +0 -0
  22. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/channels/webhook.py +0 -0
  23. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
  24. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_base.py +0 -0
  25. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
  26. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
  27. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
  28. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
  29. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
  30. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/_types.py +0 -0
  31. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
  32. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/__init__.py +0 -0
  33. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/_base.py +0 -0
  34. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/_types.py +0 -0
  35. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/autotuner.py +0 -0
  36. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/config_emitter.py +0 -0
  37. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/crossval.py +0 -0
  38. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/detector_select.py +0 -0
  39. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/distribution.py +0 -0
  40. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/grid_search.py +0 -0
  41. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/label_server.py +0 -0
  42. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/labels.py +0 -0
  43. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/result.py +0 -0
  44. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/scoring.py +0 -0
  45. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/seasonality_search.py +0 -0
  46. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/settings.py +0 -0
  47. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/autotune/window_select.py +0 -0
  48. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/__init__.py +0 -0
  49. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/_output.py +0 -0
  50. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
  51. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
  52. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
  53. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
  54. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
  55. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/rules/project.md +0 -0
  56. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
  57. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
  58. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
  59. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
  60. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/__init__.py +0 -0
  61. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/autotune.py +0 -0
  62. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/clean.py +0 -0
  63. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/init.py +0 -0
  64. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/init_claude.py +0 -0
  65. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/run.py +0 -0
  66. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/test_alert.py +0 -0
  67. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/tune.py +0 -0
  68. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/commands/unlock.py +0 -0
  69. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/cli/main.py +0 -0
  70. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/config/__init__.py +0 -0
  71. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/config/metric_config.py +0 -0
  72. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/config/profile.py +0 -0
  73. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/config/project_config.py +0 -0
  74. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/config/validator.py +0 -0
  75. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/core/__init__.py +0 -0
  76. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/core/interval.py +0 -0
  77. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/core/models.py +0 -0
  78. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/__init__.py +0 -0
  79. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/_sql_manager.py +0 -0
  80. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/clickhouse_manager.py +0 -0
  81. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/__init__.py +0 -0
  82. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
  83. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
  84. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_base.py +0 -0
  85. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
  86. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_detections.py +0 -0
  87. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
  88. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_metrics.py +0 -0
  89. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_schema.py +0 -0
  90. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/_tasks.py +0 -0
  91. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/internal_tables/manager.py +0 -0
  92. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/manager.py +0 -0
  93. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/mysql_manager.py +0 -0
  94. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/postgres_manager.py +0 -0
  95. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/database/tables.py +0 -0
  96. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/__init__.py +0 -0
  97. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/base.py +0 -0
  98. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/factory.py +0 -0
  99. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/seasonality.py +0 -0
  100. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/__init__.py +0 -0
  101. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/_windowed.py +0 -0
  102. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/iqr.py +0 -0
  103. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/mad.py +0 -0
  104. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
  105. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/detectors/statistical/zscore.py +0 -0
  106. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/loaders/__init__.py +0 -0
  107. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/loaders/metric_loader.py +0 -0
  108. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/loaders/query_template.py +0 -0
  109. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/__init__.py +0 -0
  110. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/error_dispatch.py +0 -0
  111. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
  112. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
  113. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_base.py +0 -0
  114. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
  115. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
  116. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/_types.py +0 -0
  117. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/orchestration/task_manager/manager.py +0 -0
  118. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/reporting/__init__.py +0 -0
  119. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/reporting/assets/report.js +0 -0
  120. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/reporting/builder.py +0 -0
  121. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/reporting/html_report.py +0 -0
  122. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/tuning/__init__.py +0 -0
  123. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/tuning/config_writer.py +0 -0
  124. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/tuning/html.py +0 -0
  125. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/tuning/server.py +0 -0
  126. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/utils/__init__.py +0 -0
  127. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/utils/datetime_utils.py +0 -0
  128. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/utils/env_interpolation.py +0 -0
  129. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/utils/json_utils.py +0 -0
  130. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit/utils/stats.py +0 -0
  131. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit.egg-info/SOURCES.txt +0 -0
  132. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit.egg-info/dependency_links.txt +0 -0
  133. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit.egg-info/entry_points.txt +0 -0
  134. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit.egg-info/requires.txt +0 -0
  135. {detectkit-0.36.1 → detectkit-0.37.0}/detectkit.egg-info/top_level.txt +0 -0
  136. {detectkit-0.36.1 → detectkit-0.37.0}/pyproject.toml +0 -0
  137. {detectkit-0.36.1 → detectkit-0.37.0}/requirements.txt +0 -0
  138. {detectkit-0.36.1 → detectkit-0.37.0}/setup.cfg +0 -0
  139. {detectkit-0.36.1 → detectkit-0.37.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.36.1
3
+ Version: 0.37.0
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
@@ -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.36.1"
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), **on-chart deletion** (each band carries an handle; the selected
14
- band also responds to the Delete key), and an in-browser **Import** button that
15
- loads a labels file you pick (YAML/JSON).
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>focus</b> a row to jump the chart to it · scroll to zoom, double-click to reset · drag the navigator
220
- window below to pan.</div>
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; } else { thHover=null; thDown=null; thDragWin=null; capWin=null; updateThWin(); }
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(); const t=tsAt(e.clientX);
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 (thMode) toggleTh(false); else if (selObj) { selObj=null; draw(); } return; }
717
- if ((e.key==='Delete' || e.key==='Backspace') && selObj && !typing) { e.preventDefault(); removeIncident(selObj); }
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), **on-chart
84
- delete** (each band's ✕, or select + Delete key), and **Import file…** (load a
85
- labels file you pick). `--no-serve` writes a static
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). Each
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,20 +89,28 @@ 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 lets you **mark the real incidents** (drag to create a span,
93
- drag its edges/middle to adjust, ✕ or Delete to remove), or **Threshold capture**
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). As you tune, a metrics
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 and **false-alert rate** what share of alerts
100
- fall outside any real incident ("≈1 in N false"); only incidents within the loaded
101
- window are scored. **Save incidents** writes a
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
- **widening the loaded window to cover seeded incidents** so older ones still render
105
- and count). Saving incidents does not end the session; only **Apply** does. A
110
+ **anchoring the budget-sized loaded window on the seeded incidents** ending just
111
+ past the latest one rather than at the last datapoint so they render/count
112
+ without an old outlier pulling the whole history in). Saving incidents does not end
113
+ the session; only **Apply** does. A
106
114
  **y = 0 line** toggle (shared with `dtk run --report`) shows the metric relative to zero.
107
115
 
108
116
  Safe write-back: the config is validated before anything is written, the previous
@@ -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))&&gt++;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};})();