detectkit 0.30.0__tar.gz → 0.30.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {detectkit-0.30.0/detectkit.egg-info → detectkit-0.30.1}/PKG-INFO +1 -1
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/__init__.py +1 -1
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/tune.py +14 -14
- detectkit-0.30.1/detectkit/tuning/assets/tune.js +51 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/payload.py +39 -5
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/server.py +31 -3
- {detectkit-0.30.0 → detectkit-0.30.1/detectkit.egg-info}/PKG-INFO +1 -1
- detectkit-0.30.0/detectkit/tuning/assets/tune.js +0 -50
- {detectkit-0.30.0 → detectkit-0.30.1}/LICENSE +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/MANIFEST.in +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/README.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/html_labeler.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/label_server.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/result.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/_output.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/main.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/profile.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/project_config.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/validator.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/interval.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/models.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/tables.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/html.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/stats.py +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/pyproject.toml +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/requirements.txt +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/setup.cfg +0 -0
- {detectkit-0.30.0 → detectkit-0.30.1}/setup.py +0 -0
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.30.
|
|
7
|
+
__version__ = "0.30.1"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -13,8 +13,6 @@ new config.
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from datetime import datetime
|
|
17
|
-
|
|
18
16
|
import click
|
|
19
17
|
|
|
20
18
|
from detectkit.cli._output import echo_done, echo_error, echo_noop
|
|
@@ -61,24 +59,26 @@ def run_tune(
|
|
|
61
59
|
|
|
62
60
|
from_dt = parse_date(from_date) if from_date else None
|
|
63
61
|
to_dt = parse_date(to_date) if to_date else None
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
echo_noop(name, "no datapoints — run `dtk run --select <name> --steps load` first")
|
|
68
|
-
return False
|
|
69
|
-
|
|
70
|
-
start = ts[0].astype("datetime64[ms]").astype(datetime)
|
|
71
|
-
end = ts[-1].astype("datetime64[ms]").astype(datetime)
|
|
62
|
+
# The builder resolves the window itself (recent ~TUNE_DEFAULT_POINTS by
|
|
63
|
+
# default, or the explicit --from/--to span) and reads only that slice — no
|
|
64
|
+
# need to pull the whole history just to find the bounds.
|
|
72
65
|
payload = build_tune_payload(
|
|
73
66
|
metric_config=config,
|
|
74
67
|
internal=internal_manager,
|
|
75
|
-
start=
|
|
76
|
-
end=
|
|
68
|
+
start=from_dt,
|
|
69
|
+
end=to_dt,
|
|
77
70
|
project_name=project_name,
|
|
78
71
|
)
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
n_points = len(payload["points"])
|
|
73
|
+
if n_points == 0:
|
|
74
|
+
echo_noop(
|
|
75
|
+
name,
|
|
76
|
+
"no datapoints in range — run `dtk run --select <name> --steps load` first, "
|
|
77
|
+
"or widen --from/--to",
|
|
78
|
+
)
|
|
81
79
|
return False
|
|
80
|
+
span = "the most recent points" if not (from_date or to_date) else "the selected window"
|
|
81
|
+
click.echo(f" Tuning on {n_points} points ({span}; pass --from/--to for a different span).")
|
|
82
82
|
|
|
83
83
|
# Static, read-only preview (no localhost server, no write-back).
|
|
84
84
|
if no_serve:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";(()=>{var xe={"--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 z(e){return getComputedStyle(document.documentElement).getPropertyValue(e).trim()||xe[e]||"#888"}function we(e){let n=e.replace("#","").trim();n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let l=parseInt(n,16);return n.length!==6||Number.isNaN(l)?[209,91,54]:[l>>16&255,l>>8&255,l&255]}function F(e,n){let[l,t,o]=we(e);return`rgba(${l},${t},${o},${n})`}function ce(e){let n=Math.max(1,window.devicePixelRatio||1),l=e.clientWidth||e.offsetWidth||0,t=e.clientHeight||e.offsetHeight||0;return e.width=Math.round(l*n),e.height=Math.round(t*n),n}var le=Number.isFinite;function ue(e,n,l,t,o,r,a,c,s,C,w,N){let $=n.length,D=Math.max(1,Math.round(a)),Q=o-t||1,U=0;for(let v=0;v<$;v++){let h=l[v];!le(h)||n[v]<t||n[v]>o||U++}if(e.strokeStyle=C,e.lineWidth=w*N,e.lineJoin="round",e.beginPath(),U<=D){let v=!1;for(let h=0;h<$;h++){let E=l[h],M=n[h];if(!le(E)||M<t||M>o){v=!1;continue}let A=c(M),H=s(E);v?e.lineTo(A,H):(e.moveTo(A,H),v=!0)}}else{let v=new Array(D).fill(null),h=new Array(D).fill(null);for(let M=0;M<$;M++){let A=l[M],H=n[M];if(!le(A)||H<t||H>o)continue;let P=Math.floor((H-t)/Q*(D-1));P=P<0?0:P>D-1?D-1:P,(v[P]===null||A<v[P])&&(v[P]=A),(h[P]===null||A>h[P])&&(h[P]=A)}let E=!1;for(let M=0;M<D;M++){if(h[M]===null){E=!1;continue}let A=r+M,H=s(h[M]),P=s(v[M]);E?e.lineTo(A,H):(e.moveTo(A,H),E=!0),e.lineTo(A,P)}}e.stroke()}function me(e){let n=Math.abs(e);return n>=1e3?e.toFixed(0):n>=10?e.toFixed(1):n>=1?e.toFixed(2):e.toFixed(3)}function de(e,n){let l=new Date(e).toISOString();return n<2*864e5?l.slice(5,16).replace("T"," "):l.slice(5,10)}function fe(e,n,l){return{left:n.l*l,top:n.t*l,right:e.width-n.r*l,bottom:e.height-n.b*l}}function pe(e,n,l,t,o,r,a){let c=fe(n,l,t),s=Math.max(c.left,Math.min(o(r),c.right));s<=c.left+.5||(e.save(),e.fillStyle="rgba(17,15,13,0.42)",e.fillRect(c.left,c.top,s-c.left,c.bottom-c.top),e.strokeStyle=F(z("--faint"),.7),e.lineWidth=1*t,e.setLineDash([4*t,4*t]),e.beginPath(),e.moveTo(s,c.top),e.lineTo(s,c.bottom),e.stroke(),e.setLineDash([]),e.fillStyle=F(z("--faint"),.95),e.font=`${10*t}px ui-monospace, monospace`,e.textAlign="left",e.textBaseline="top",e.fillText(a,s+6*t,c.top+5*t),e.restore())}function be(e,n,l,t,o,r,a){let c=fe(n,l,t),s=5*t;e.save();for(let C=0;C<r.length;C++){let w=r[C],N=o(w.t);if(N<c.left-1||N>c.right+1)continue;let $=a(w.kind);e.strokeStyle=F($,.45),e.lineWidth=1*t,e.beginPath(),e.moveTo(N,c.top),e.lineTo(N,c.bottom),e.stroke(),e.fillStyle=$,e.beginPath(),e.moveTo(N-s,c.top),e.lineTo(N+s,c.top),e.lineTo(N,c.top+s*1.4),e.closePath(),e.fill()}e.restore()}var ke={mad:1,zscore:2,iqr:4};function Se(e,n){let l=e.seasonalityData;if(!l||l.length===0)return 0;let t=0;for(let o of n){let r=new Set;for(let a of l)r.add(o.map(c=>{var s;return String((s=a==null?void 0:a[c])!=null?s:"")}).join("|"));t=Math.max(t,r.size)}return t}function he(e,n){let l=e.timestamps.length,t=Math.max(n.minSamples,ke[n.type]);if(n.smoothing==="sma"&&(t=Math.max(t,n.smoothingWindow-1)),n.smoothing==="ema"&&(t=Math.max(t,Math.ceil(5/n.smoothingAlpha))),n.inputType!=="values"&&(t=Math.max(t,1)),n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0){let r=Se(e,n.seasonalityComponents);if(r>0){let a=n.minSamplesPerGroup*r;n.windowSize>=a&&(t=Math.max(t,a))}}return Math.min(t,l)}var L={l:52,r:14,t:14,b:26},G=Number.isFinite;function ge(e,n={}){let l=e.getContext("2d");if(!l)throw new Error("chart: 2D context unavailable");let t=l,o=1,r=null,a=-1,c=0,s=0,C=1,w=0,N=1;function $(){o=ce(e)}let D=()=>e.width-(L.l+L.r)*o,Q=()=>e.height-(L.t+L.b)*o,U=()=>C-s||1,v=m=>L.l*o+(m-s)/U()*D(),h=m=>e.height-L.b*o-(m-w)/(N-w||1)*Q();function E(m){let b=r==null?void 0:r.series;if(!b||b.timestamps.length===0)return-1;let T=(m-L.l*o)/(D()||1),d=s+T*U(),g=b.timestamps,S=0,k=g.length-1;for(;S<k;){let O=S+k>>1;g[O]<d?S=O+1:k=O}return S>0&&d-g[S-1]<g[S]-d&&(S-=1),S}function M(m,b){let T=m.timestamps;s=T[0],C=T[T.length-1];let d=1/0,g=-1/0;for(let k of m.values)G(k)&&(k<d&&(d=k),k>g&&(g=k));for(let k of b)k.scored&&(G(k.lower)&&k.lower<d&&(d=k.lower),G(k.upper)&&k.upper>g&&(g=k.upper));(!G(d)||!G(g))&&(d=0,g=1),g<=d&&(g=d+1);let S=(g-d)*.06;w=d-S,N=g+S}function A(m,b,T){let d=r.series.timestamps;ue(t,d,m,s,C,L.l*o,D(),v,h,b,T,o)}function H(m,b){let T=[],d=-1;for(let g=Math.max(0,b);g<m.length;g++){let S=m[g];S.scored&&G(S.lower)&&G(S.upper)?d===-1&&(d=g):d!==-1&&(T.push([d,g-1]),d=-1)}return d!==-1&&T.push([d,m.length-1]),T}function P(){if(c=0,!r||e.width===0||e.height===0)return;let{series:m,scored:b,params:T,alerts:d}=r;if(m.timestamps.length===0){t.fillStyle=z("--term-bg"),t.fillRect(0,0,e.width,e.height);return}let g=z("--clay"),S=z("--st-anomaly"),k=z("--faint"),O=z("--muted");t.fillStyle=z("--term-bg"),t.fillRect(0,0,e.width,e.height),t.font=`${11*o}px ui-monospace, 'JetBrains Mono', monospace`,t.textBaseline="middle";for(let u=0;u<=4;u++){let y=w+(N-w)*u/4,x=h(y);t.strokeStyle=F(k,.1),t.lineWidth=1*o,t.beginPath(),t.moveTo(L.l*o,x),t.lineTo(e.width-L.r*o,x),t.stroke(),t.fillStyle=O,t.textAlign="right",t.fillText(me(y),(L.l-8)*o,x)}t.textBaseline="top";let j=U();for(let u=0;u<=5;u++){let y=s+j*u/5,x=v(y);t.fillStyle=O,t.textAlign=u===0?"left":u===5?"right":"center",t.fillText(de(y,j),x,(e.height-L.b+7)*o)}t.save(),t.beginPath(),t.rect(L.l*o,L.t*o,D(),Q()),t.clip();let X=b.length,B=Math.min(he(m,T),X),R=B<X?m.timestamps[B]:void 0,W=H(b,B);t.fillStyle=F(g,.13);for(let[u,y]of W){t.beginPath(),t.moveTo(v(b[u].timestamp),h(b[u].upper));for(let x=u+1;x<=y;x++)t.lineTo(v(b[x].timestamp),h(b[x].upper));for(let x=y;x>=u;x--)t.lineTo(v(b[x].timestamp),h(b[x].lower));t.closePath(),t.fill()}t.strokeStyle=F(g,.4),t.lineWidth=1*o;for(let[u,y]of W)for(let x of["upper","lower"]){t.beginPath();for(let I=u;I<=y;I++){let i=v(b[I].timestamp),f=h(b[I][x]);I===u?t.moveTo(i,f):t.lineTo(i,f)}t.stroke()}t.strokeStyle=F(k,.55),t.lineWidth=1*o,t.setLineDash([3*o,3*o]);for(let[u,y]of W){t.beginPath();for(let x=u;x<=y;x++){let I=b[x].center;if(!G(I))continue;let i=v(b[x].timestamp),f=h(I);x===u?t.moveTo(i,f):t.lineTo(i,f)}t.stroke()}if(t.setLineDash([]),T.smoothing!=="none"){A(m.values,F(g,.28),1.25);let u=b.map(y=>y.processedValue);A(u,g,1.6)}else A(m.values,g,1.5);for(let u=B;u<X;u++){let y=b[u];if(!y.scored||!G(y.value))continue;let x=v(y.timestamp),I=h(y.value);y.isAnomaly?(t.fillStyle=F(S,.18),t.beginPath(),t.arc(x,I,6*o,0,Math.PI*2),t.fill(),t.fillStyle=S,t.beginPath(),t.arc(x,I,3*o,0,Math.PI*2),t.fill()):m.truthAnomaly[u]&&(t.strokeStyle=F(O,.7),t.lineWidth=1.25*o,t.beginPath(),t.arc(x,I,3.5*o,0,Math.PI*2),t.stroke())}R!==void 0&&pe(t,e,L,o,v,R,"detection at full power \u2192"),a>=0&&a<b.length&&ee(a,T.windowSize,b,m,k),d&&d.length&&be(t,e,L,o,v,d,u=>u==="anomaly"?z("--st-anomaly"):u==="recovery"?z("--st-recovery"):z("--st-nodata")),t.restore()}function ee(m,b,T,d,g){let S=d.timestamps,k=Math.max(0,m-b),O=m-1,j=L.t*o,X=Q();if(O>=k){let W=d.intervalSeconds*1e3/U()*D()*.5,u=v(S[k])-W,y=v(S[O])+W;t.fillStyle="rgba(255,255,255,0.05)",t.fillRect(u,j,y-u,X),t.strokeStyle=F(g,.5),t.lineWidth=1*o,t.beginPath(),t.moveTo(u,j),t.lineTo(u,j+X),t.moveTo(y,j),t.lineTo(y,j+X),t.stroke()}let B=v(S[m]);t.strokeStyle=F(g,.85),t.lineWidth=1*o,t.setLineDash([2*o,2*o]),t.beginPath(),t.moveTo(B,j),t.lineTo(B,j+X),t.stroke(),t.setLineDash([]);let R=T[m];if(R.scored&&G(R.lower)&&G(R.upper)){let W=5*o;t.strokeStyle=F(z("--clay"),.85),t.lineWidth=1.5*o;for(let u of[R.lower,R.upper]){let y=h(u);t.beginPath(),t.moveTo(B-W,y),t.lineTo(B+W,y),t.stroke()}}if(G(R.value)){let W=h(R.value);t.fillStyle=z("--term-bg"),t.beginPath(),t.arc(B,W,4*o,0,Math.PI*2),t.fill(),t.strokeStyle=R.isAnomaly?z("--st-anomaly"):z("--clay"),t.lineWidth=2*o,t.beginPath(),t.arc(B,W,4*o,0,Math.PI*2),t.stroke()}}function J(){c===0&&(c=requestAnimationFrame(P))}function Z(){var T;if(!n.onHover||!r)return;if(a<0){n.onHover(null);return}let m=(T=r.scored[a])!=null?T:null,b={index:a,point:m,windowStart:Math.max(0,a-r.params.windowSize),windowEnd:a-1};n.onHover(b)}function oe(m){if(!r)return;let b=e.getBoundingClientRect(),T=(m.clientX-b.left)*o,d=E(T);d!==a&&(a=d,Z(),J())}function te(){a!==-1&&(a=-1,Z(),J())}e.addEventListener("mousemove",oe),e.addEventListener("mouseleave",te);function K(m){r=m,M(m.series,m.scored),a>=m.series.timestamps.length&&(a=-1),J()}function re(){$(),r&&(M(r.series,r.scored),J())}function ne(){e.removeEventListener("mousemove",oe),e.removeEventListener("mouseleave",te),c!==0&&cancelAnimationFrame(c),c=0,r=null}return $(),{render:K,resize:re,destroy:ne}}var Ce={mad:3,zscore:3,iqr:1.5},Te={mad:10,zscore:3,iqr:4},Me="dtk-tune";function p(e,n,l){let t=document.createElement(e);return n&&(t.className=n),l!=null&&(t.textContent=l),t}function ie(e,n,l,t){let o=p("div","dtk-ctl");o.appendChild(p("label","dtk-ctl-label",e));let r=p("div","dtk-seg"),a=l,c=[],s=()=>{c.forEach(C=>C.classList.toggle("on",C.dataset.v===a))};return n.forEach(C=>{let w=p("button","dtk-seg-btn",C.label);w.type="button",w.dataset.v=C.value,w.onclick=()=>{a=C.value,s(),t(a)},c.push(w),r.appendChild(w)}),s(),o.appendChild(r),{row:o,get:()=>a,set:C=>{a=C,s()}}}function se(e,n,l){var C;let t=p("div","dtk-ctl"),o=p("div","dtk-ctl-head"),r=p("label","dtk-ctl-label",e),a=p("span","dtk-ctl-val"),c=(C=n.fmt)!=null?C:(w=>String(w));o.appendChild(r),o.appendChild(a),t.appendChild(o);let s=p("input","dtk-range");return s.type="range",s.min=String(n.min),s.max=String(n.max),s.step=String(n.step),s.value=String(n.value),a.textContent=c(n.value),s.oninput=()=>{let w=Number(s.value);a.textContent=c(w),l(w)},t.appendChild(s),{row:t,get:()=>Number(s.value),setMax:w=>{s.max=String(w),Number(s.value)>w&&(s.value=String(w),a.textContent=c(w))}}}function ye(e){let n={threshold:e.threshold,window_size:e.windowSize};return e.windowWeights!=="none"&&(n.window_weights=e.windowWeights,e.windowWeights==="exponential"&&e.halfLife!=null&&(n.half_life=e.halfLife)),e.detrend!=="none"&&(n.detrend=e.detrend),e.smoothing!=="none"&&(n.smoothing=e.smoothing),e.inputType!=="values"&&(n.input_type=e.inputType),e.seasonalityComponents&&e.seasonalityComponents.length&&(n.seasonality_components=e.seasonalityComponents,n.min_samples_per_group=e.minSamplesPerGroup),n}function Ae(e,n){let l=ye(e),t=[`type: ${e.type}`];for(let[o,r]of Object.entries(l))t.push(`${o}=${typeof r=="object"?JSON.stringify(r):r}`);return t.push(`consecutive_anomalies=${n}`),t.join(" \xB7 ")}function De(e,n){var x,I;Le(),n.classList.add(Me),n.innerHTML="";let l=p("div","dtk-tune-root");n.appendChild(l);let t=e.points.length,o={timestamps:e.points.map(i=>i.t),values:e.points.map(i=>i.v==null?NaN:i.v),intervalSeconds:e.interval_seconds,truthAnomaly:new Array(t).fill(!1),seasonalityData:e.seasonality_columns.length?e.seasonality:void 0,seasonalityColumns:e.seasonality_columns.length?e.seasonality_columns:void 0},r=e.detector,a=e.consecutive_anomalies,c=(x=r.seasonalityComponents)!=null?x:[],s=new Set(c.flat()),C=c.length===1&&c[0].length>1,w=()=>{let i=e.seasonality_columns.filter(f=>s.has(f));return i.length?C?[i]:i.map(f=>[f]):null},N=()=>{var i;return{type:re.get(),threshold:ne.get(),windowSize:g.get(),minSamples:r.minSamples,inputType:r.inputType,smoothing:X.get(),smoothingAlpha:r.smoothingAlpha,smoothingWindow:r.smoothingWindow,windowWeights:S.get(),halfLife:S.get()==="exponential"?k.get():null,detrend:j.get(),seasonalityComponents:w(),minSamplesPerGroup:(i=Te[re.get()])!=null?i:r.minSamplesPerGroup,consecutiveAnomalies:a}},$=p("div","dtk-tune-header"),D=p("div","dtk-tune-titlerow");D.appendChild(p("h1","dtk-tune-title",e.metric));let Q=p("span","dtk-tune-badge",e.save_url?"manual tuning":"preview");D.appendChild(Q),$.appendChild(D);let U=e.project?`${e.project} \xB7 `:"";$.appendChild(p("div","dtk-tune-sub",`${U}${t} points \xB7 ${_e(e.interval_seconds)} grid`)),e.description&&$.appendChild(p("div","dtk-tune-desc",e.description)),l.appendChild($);let v=p("div","dtk-tune-grid"),h=p("div","dtk-tune-controls"),E=p("div","dtk-tune-main");v.appendChild(h),v.appendChild(E),l.appendChild(v);let M=p("div","dtk-tune-chart"),A=p("canvas");M.appendChild(A),E.appendChild(M);let H=p("div","dtk-tune-readout");E.appendChild(H);let P=ge(A,{onHover:i=>{if(!i||!i.point||!i.point.scored){H.textContent="";return}let f=i.point;H.textContent=`t=${Pe(f.timestamp)} value=${ae(f.value)} band=[${ae(f.lower)}, ${ae(f.upper)}]`+(f.isAnomaly?` \u26A0 ${f.direction} (sev ${f.severity.toFixed(2)})`:"")}}),ee=new Worker(URL.createObjectURL(new Blob([`"use strict";(()=>{var X={mad:1,zscore:2,iqr:4};function T(n){let e=0;for(let t of n)e+=t;return e}function ie(n){let e=n.map((t,r)=>r);return e.sort((t,r)=>n[t]<n[r]?-1:n[t]>n[r]?1:t-r),e}function se(n,e,t){let r=e.length;if(r===0)return NaN;if(n<=e[0])return t[0];if(n>=e[r-1])return t[r-1];for(let i=1;i<r;i++)if(n<=e[i]){let o=e[i-1],c=e[i],u=t[i-1],s=t[i];if(c===o)return u;let d=(n-o)/(c-o);return u+d*(s-u)}return t[r-1]}function F(n,e,t){let r=ie(n),i=r.map(b=>n[b]),o=r.map(b=>e[b]),c=T(o),u=new Array(o.length),s=0;for(let b=0;b<o.length;b++)s+=o[b],u[b]=(s-.5*o[b])/c;let d=t/100;return se(d,u,i)}function S(n,e){return F(n,e,50)}function ce(n,e,t){let r=t===void 0?S(n,e):t,i=n.map(o=>Math.abs(o-r));return S(i,e)}function ue(n,e){let t=T(e),r=0;for(let i=0;i<n.length;i++)r+=n[i]*(e[i]/t);return r}function me(n,e,t,r=0){let i=T(e),o=e.map(s=>s/i),c=t===void 0?T(n.map((s,d)=>s*o[d])):t,u=0;for(let s=0;s<n.length;s++)u+=o[s]*(n[s]-c)**2;if(r===1){let s=1-T(o.map(d=>d*d));s>1e-12&&(u/=s)}return Math.sqrt(u)}function le(n,e){return e.smoothing==="none"?n.slice():e.smoothing==="ema"?ae(n,e.smoothingAlpha):fe(n,e.smoothingWindow)}function ae(n,e){let t=n.length,r=new Array(t).fill(NaN);if(t===0)return r;let i=-1;for(let o=0;o<t;o++)if(!Number.isNaN(n[o])){i=o;break}if(i===-1)return r;r[i]=n[i];for(let o=i+1;o<t;o++)Number.isNaN(n[o])?r[o]=r[o-1]:r[o]=e*n[o]+(1-e)*r[o-1];return r}function fe(n,e){let t=n.length,r=new Array(t).fill(NaN);for(let i=0;i<t;i++){let o=Math.max(0,i-e+1),c=0,u=0;for(let s=o;s<=i;s++)Number.isNaN(n[s])||(c+=n[s],u+=1);r[i]=u>0?c/u:NaN}return r}function de(n,e){let t=n.length;if(e.inputType==="values")return n.slice();let r=new Array(t).fill(NaN);for(let i=1;i<t;i++){let o=n[i-1],c=n[i];e.inputType==="changes"?r[i]=(c-o)/o:e.inputType==="absolute_changes"?r[i]=c-o:r[i]=Math.log(c+1)-Math.log(o+1)}return r}function be(n,e){return n.halfLife!==null?n.halfLife:Math.max(n.windowSize/20,e/2,1)}function pe(n,e){if(n.windowWeights==="none")return null;let t=n.windowSize,r=new Array(t);if(n.windowWeights==="exponential"){let i=be(n,e);for(let o=1;o<=t;o++){let c=Math.min(o/i,1e3);r[o-1]=Math.pow(.5,c)}return r}for(let i=1;i<=t;i++)r[i-1]=(t+1-i)/t;return r}function K(n,e){return e===null?n.map(()=>1):n.map(t=>e[t-1])}function ge(n){let e=n[0];for(let t of n)t>e&&(e=t);return e}function ye(n){let e=n[0];for(let t of n)t<e&&(e=t);return e}function he(n,e,t){if(n.length<4)return 0;let r=(ge(e)+ye(e))/2,i=e.map(l=>l>r),o=i.map(l=>!l),c=0,u=0;for(let l=0;l<e.length;l++)i[l]?c++:u++;if(c<2||u<2)return 0;let s=(l,N)=>N.filter((L,y)=>l[y]),d=s(o,n),b=s(i,n),k=s(o,e),C=s(i,e),g=s(o,t),f=s(i,t),q=S(d,g),p=S(b,f),w=S(k,g),M=S(C,f);return M===w?0:(q-p)/(M-w)}var Se={mad:[["median","center"],["mad","spread"]],zscore:[["mean","center"],["std","spread"]],iqr:[["q1","center"],["q3","center"],["iqr","spread"]]};function Q(n,e,t){if(n==="mad"){let o=S(e,t);return{median:o,mad:ce(e,t,o)}}if(n==="zscore"){let o=ue(e,t);return{mean:o,std:me(e,t,o,1)}}let r=F(e,t,25),i=F(e,t,75);return{q1:r,q3:i,iqr:i-r}}function we(n,e,t){if(n==="mad"){if(e.mad===0)return[e.median-1e-10,e.median+1e-10];let r=t*1.4826*e.mad;return[e.median-r,e.median+r]}return n==="zscore"?e.std===0?[e.mean-1e-10,e.mean+1e-10]:[e.mean-t*e.std,e.mean+t*e.std]:e.iqr===0?[e.q1-1e-10,e.q3+1e-10]:[e.q1-t*e.iqr,e.q3+t*e.iqr]}function Me(n,e,t){if(n==="mad"){let r=1.4826*e.mad;return r>0?t/r:1/0}return n==="zscore"?e.std>0?t/e.std:1/0:e.iqr>0?t/e.iqr:1/0}function Ne(n,e){return n==="mad"?e.median:n==="zscore"?e.mean:(e.q1+e.q3)/2}function U(n,e,t,r,i){return{index:n,timestamp:e,value:t,processedValue:r,scored:!1,isAnomaly:!1,lower:NaN,upper:NaN,center:NaN,direction:null,severity:0,reason:i}}function Y(n,e){let{type:t}=e,r=Math.max(e.minSamples,X[t]),i=Se[t],{timestamps:o,values:c}=n,u=o.length,s=le(c,e),d=de(s,e),b=e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0,k=n.seasonalityData,C=pe(e,r),g=[];for(let f=0;f<u;f++){let q=c[f],p=d[f],w=o[f];if(Number.isNaN(p)){g.push(U(f,w,q,p,"missing_data"));continue}let M=Math.max(0,f-e.windowSize),l=f-M,N=d.slice(M,f),L=N.map(m=>!Number.isNaN(m)),y=[];for(let m=0;m<N.length;m++)L[m]&&y.push(N[m]);if(y.length<r){g.push(U(f,w,q,p,"insufficient_data"));continue}let O=new Array(l);for(let m=0;m<l;m++)O[m]=l-m;let z=[];for(let m=0;m<l;m++)L[m]&&z.push(O[m]);let V=K(z,C),P=0;e.detrend==="linear"&&(P=he(y,z,V));let $=P!==0?y.map((m,W)=>m+P*z[W]):y,G=Q(t,$,V),I={...G};if(b&&k){let m=k[f];for(let W of e.seasonalityComponents){let J=new Array(l).fill(!0);for(let a=0;a<l;a++){let D=k[M+a],x=!0;for(let j of W)if(!D||D[j]!==(m==null?void 0:m[j])){x=!1;break}J[a]=x}let _=[],E=[];for(let a=0;a<l;a++)L[a]&&J[a]&&(_.push(N[a]),E.push(O[a]));if(_.length<e.minSamplesPerGroup)continue;let ne=K(E,C),te=P!==0?_.map((a,D)=>a+P*E[D]):_,re=Q(t,te,ne);for(let[a,D]of i){let x=G[a],oe=(D==="spread"?x>0:x!==0)?re[a]/x:1;I[a]*=oe}}}let[h,A]=we(t,I,e.threshold);h>A&&([h,A]=[A,h]);let H=p<h||p>A,R=null,B=0;if(H){let m;p<h?(R="below",m=h-p):(R="above",m=p-A),B=Me(t,I,m)}let ee=Ne(t,I);g.push({index:f,timestamp:w,value:q,processedValue:p,scored:!0,isAnomaly:H,lower:h,upper:A,center:ee,direction:R,severity:B,reason:"ok"})}return g}function Ae(n,e){let t=n.seasonalityData;if(!t||t.length===0)return 0;let r=0;for(let i of e){let o=new Set;for(let c of t)o.add(i.map(u=>{var s;return String((s=c==null?void 0:c[u])!=null?s:"")}).join("|"));r=Math.max(r,o.size)}return r}function Z(n,e){let t=n.timestamps.length,r=Math.max(e.minSamples,X[e.type]);if(e.smoothing==="sma"&&(r=Math.max(r,e.smoothingWindow-1)),e.smoothing==="ema"&&(r=Math.max(r,Math.ceil(5/e.smoothingAlpha))),e.inputType!=="values"&&(r=Math.max(r,1)),e.seasonalityComponents!==null&&e.seasonalityComponents.length>0&&Array.isArray(n.seasonalityData)&&n.seasonalityData.length>0){let o=Ae(n,e.seasonalityComponents);if(o>0){let c=e.minSamplesPerGroup*o;e.windowSize>=c&&(r=Math.max(r,c))}}return Math.min(r,t)}var v=null;function De(n,e,t){let r=[],i=0;for(let o=0;o<n.length;o++){if(!(n[o].scored&&n[o].isAnomaly)){i=0;continue}let u=o>0&&n[o-1].scored&&n[o-1].isAnomaly,s=o>0&&n[o].timestamp-n[o-1].timestamp===e;i=u&&s?i+1:1,i===t&&r.push(o)}return r}self.onmessage=n=>{let e=n.data;if(e.type==="series"){v=e.series;return}if(e.type==="run"&&v){let t=e.params,r=Y(v,t),i=v.intervalSeconds*1e3,o=De(r,i,t.consecutiveAnomalies),c=Z(v,t),u=0;for(let s of r)s.scored&&s.isAnomaly&&u++;self.postMessage({type:"result",id:e.id,scored:r,fires:o,eff:c,flagged:u})}};})();
|
|
2
|
+
`],{type:"text/javascript"})));ee.postMessage({type:"series",series:o});let J=0,Z=null;ee.onmessage=i=>{let f=i.data;if(f.type!=="result"||f.id!==J||!Z)return;let q=Z,Y=f.fires.map(_=>({t:o.timestamps[_],kind:"anomaly"}));P.render({series:o,scored:f.scored,params:q,alerts:Y}),R.textContent=`${f.flagged} flagged \xB7 ${f.fires.length} alert${f.fires.length===1?"":"s"} \xB7 warm-up ${f.eff} pts`,u.textContent=Ae(q,a)},ee.onerror=()=>{R.textContent="recompute failed \u2014 see the browser console"};let oe=()=>{Z=N(),J+=1,R.textContent="computing\u2026",ee.postMessage({type:"run",id:J,params:Z})},te=0,K=()=>{te&&window.clearTimeout(te),te=window.setTimeout(oe,130)},re=ie("Detector",[{label:"MAD",value:"mad"},{label:"Z-Score",value:"zscore"},{label:"IQR",value:"iqr"}],r.type,i=>{var f;T((f=Ce[i])!=null?f:3),K()});h.appendChild(re.row);let ne=se("Threshold (\u03C3-equivalent)",{min:.5,max:10,step:.1,value:r.threshold,fmt:i=>i.toFixed(1)},K);h.appendChild(ne.row);let m=ne.row.querySelector("input"),b=ne.row.querySelector(".dtk-ctl-val"),T=i=>{m&&(m.value=String(i)),b&&(b.textContent=i.toFixed(1))},d=Math.max(50,Math.min(2e3,Math.floor(t/2))),g=se("Window size (points)",{min:10,max:d,step:5,value:Math.min(r.windowSize,d),fmt:i=>String(i)},K);h.appendChild(g.row);let S=ie("Recency weighting",[{label:"none",value:"none"},{label:"exponential",value:"exponential"},{label:"linear",value:"linear"}],r.windowWeights,i=>{O.style.display=i==="exponential"?"":"none",K()});h.appendChild(S.row);let k=se("Half-life (points)",{min:1,max:d,step:1,value:(I=r.halfLife)!=null?I:Math.max(5,Math.round(r.windowSize/20)),fmt:i=>String(i)},K),O=k.row;O.style.display=r.windowWeights==="exponential"?"":"none",h.appendChild(O);let j=ie("Detrend",[{label:"none",value:"none"},{label:"linear",value:"linear"}],r.detrend,K);h.appendChild(j.row);let X=ie("Smoothing",[{label:"none",value:"none"},{label:"EMA",value:"ema"},{label:"SMA",value:"sma"}],r.smoothing,K);if(h.appendChild(X.row),e.seasonality_columns.length){let i=p("div","dtk-ctl");i.appendChild(p("label","dtk-ctl-label","Seasonality conditioning"));let f=p("div","dtk-seg dtk-wrap");e.seasonality_columns.forEach(_=>{let V=p("button","dtk-seg-btn",_);V.type="button",V.classList.toggle("on",s.has(_)),V.onclick=()=>{s.has(_)?s.delete(_):s.add(_),V.classList.toggle("on",s.has(_)),K()},f.appendChild(V)}),i.appendChild(f);let q=p("label","dtk-check"),Y=p("input");Y.type="checkbox",Y.checked=C,Y.onchange=()=>{C=Y.checked,K()},q.appendChild(Y),q.appendChild(p("span",void 0,"conjoin selected into one group")),i.appendChild(q),h.appendChild(i)}let B=se("Alert: consecutive anomalies",{min:1,max:10,step:1,value:a,fmt:i=>String(i)},i=>{a=i,K()});h.appendChild(B.row);let R=p("div","dtk-tune-stat");E.appendChild(R);let W=p("div","dtk-tune-cfg");W.appendChild(p("span","dtk-tune-cfg-k","// effective config"));let u=p("code","dtk-tune-cfg-v");if(W.appendChild(u),E.appendChild(W),e.save_url){let i=p("div","dtk-tune-apply"),f=p("button","dtk-apply-btn","Apply to metric");f.type="button";let q=p("span","dtk-apply-msg");f.onclick=()=>{let Y=N();f.disabled=!0,q.className="dtk-apply-msg info",q.textContent="Applying\u2026",fetch(e.save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detector:{type:Y.type,params:ye(Y)},consecutive_anomalies:a})}).then(_=>_.ok?_.json():_.text().then(V=>{throw new Error(V||`HTTP ${_.status}`)})).then(_=>{var V;q.className="dtk-apply-msg ok",q.textContent=`Applied \u2192 ${(V=_.saved)!=null?V:"metric"} (previous archived). You can close this tab.`}).catch(_=>{f.disabled=!1,q.className="dtk-apply-msg err",q.textContent=`Apply failed: ${_.message}`})},i.appendChild(f),i.appendChild(q),E.appendChild(i)}else E.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."));oe();let y=0;window.addEventListener("resize",()=>{y&&cancelAnimationFrame(y),y=requestAnimationFrame(()=>P.resize())})}function ae(e){if(!Number.isFinite(e))return"\u2014";let n=Math.abs(e);return n!==0&&(n<.01||n>=1e6)?e.toExponential(2):Number(e.toFixed(n<1?4:2)).toString()}function Pe(e){return new Date(e).toISOString().slice(0,16).replace("T"," ")}function _e(e){return e%86400===0?`${e/86400}d`:e%3600===0?`${e/3600}h`:e%60===0?`${e/60}min`:`${e}s`}var ve=!1;function Le(){if(ve)return;ve=!0;let e=`
|
|
3
|
+
.dtk-tune{--c:#d15b36;--c7:#b4471f;--ink:#1b1916;--muted:#6e675b;--faint:#9a9384;
|
|
4
|
+
--paper:#f5f1e8;--surface:#fbf9f3;--border:#e6e0d4;--green:#2e9e73;--anom:#d63232;
|
|
5
|
+
--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
|
|
6
|
+
--sans:'Schibsted Grotesk',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
7
|
+
.dtk-tune-root{max-width:1200px;margin:0 auto;padding:24px 20px 56px;font-family:var(--sans);color:var(--ink);}
|
|
8
|
+
.dtk-tune-titlerow{display:flex;align-items:center;gap:12px;}
|
|
9
|
+
.dtk-tune-title{font-size:24px;margin:0;font-weight:700;}
|
|
10
|
+
.dtk-tune-badge{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
|
|
11
|
+
color:#fff;background:var(--c);border-radius:999px;padding:3px 10px;}
|
|
12
|
+
.dtk-tune-sub{color:var(--muted);font-size:13px;margin-top:4px;font-family:var(--mono);}
|
|
13
|
+
.dtk-tune-desc{color:var(--muted);font-size:13px;margin-top:8px;white-space:pre-wrap;}
|
|
14
|
+
.dtk-tune-grid{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-top:20px;align-items:start;}
|
|
15
|
+
@media(max-width:820px){.dtk-tune-grid{grid-template-columns:1fr;}}
|
|
16
|
+
.dtk-tune-controls{display:flex;flex-direction:column;gap:16px;background:var(--surface);
|
|
17
|
+
border:1px solid var(--border);border-radius:12px;padding:16px;}
|
|
18
|
+
.dtk-ctl{display:flex;flex-direction:column;gap:6px;}
|
|
19
|
+
.dtk-ctl-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
20
|
+
.dtk-ctl-label{font-size:12px;font-weight:600;color:var(--ink);}
|
|
21
|
+
.dtk-ctl-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
22
|
+
.dtk-seg{display:flex;gap:4px;background:var(--paper);border:1px solid var(--border);border-radius:8px;padding:3px;}
|
|
23
|
+
.dtk-seg.dtk-wrap{flex-wrap:wrap;}
|
|
24
|
+
.dtk-seg-btn{flex:1 1 auto;border:0;background:transparent;color:var(--muted);font-family:var(--sans);
|
|
25
|
+
font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;white-space:nowrap;}
|
|
26
|
+
.dtk-seg-btn:hover{color:var(--ink);}
|
|
27
|
+
.dtk-seg-btn.on{background:var(--c);color:#fff;font-weight:600;}
|
|
28
|
+
.dtk-range{width:100%;accent-color:var(--c);cursor:pointer;}
|
|
29
|
+
.dtk-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);margin-top:2px;cursor:pointer;}
|
|
30
|
+
.dtk-tune-main{display:flex;flex-direction:column;gap:10px;min-width:0;}
|
|
31
|
+
.dtk-tune-chart{position:relative;width:100%;height:420px;background:var(--surface);
|
|
32
|
+
border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
|
33
|
+
.dtk-tune-chart canvas{width:100%;height:100%;display:block;}
|
|
34
|
+
.dtk-tune-readout{font-family:var(--mono);font-size:12px;color:var(--muted);min-height:18px;}
|
|
35
|
+
.dtk-tune-stat{font-family:var(--mono);font-size:12px;color:var(--ink);}
|
|
36
|
+
.dtk-tune-cfg{background:var(--ink);color:#c9c2b4;border-radius:8px;padding:10px 12px;font-family:var(--mono);
|
|
37
|
+
font-size:12px;overflow-x:auto;}
|
|
38
|
+
.dtk-tune-cfg-k{color:var(--faint);display:block;margin-bottom:4px;}
|
|
39
|
+
.dtk-tune-cfg-v{color:#e6e0d4;white-space:pre-wrap;word-break:break-word;}
|
|
40
|
+
.dtk-tune-apply{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px;}
|
|
41
|
+
.dtk-apply-btn{background:var(--c);color:#fff;border:0;border-radius:8px;padding:10px 18px;font-family:var(--sans);
|
|
42
|
+
font-size:14px;font-weight:600;cursor:pointer;}
|
|
43
|
+
.dtk-apply-btn:hover{background:var(--c7);}
|
|
44
|
+
.dtk-apply-btn:disabled{opacity:.55;cursor:default;}
|
|
45
|
+
.dtk-apply-msg{font-size:13px;}
|
|
46
|
+
.dtk-apply-msg.ok{color:var(--green);}
|
|
47
|
+
.dtk-apply-msg.err{color:var(--anom);}
|
|
48
|
+
.dtk-apply-msg.info{color:var(--muted);}
|
|
49
|
+
.dtk-tune-note{font-size:13px;color:var(--muted);background:var(--surface);border:1px dashed var(--border);
|
|
50
|
+
border-radius:8px;padding:10px 12px;}
|
|
51
|
+
`,n=document.createElement("style");n.textContent=e,document.head.appendChild(n)}window.__DTK_TUNE__={render:De};})();
|
|
@@ -15,7 +15,28 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
from detectkit.config.metric_config import MetricConfig
|
|
17
17
|
from detectkit.database.internal_tables import InternalTablesManager
|
|
18
|
-
from detectkit.reporting.builder import _ms, _num_or_none, _parse_seasonality
|
|
18
|
+
from detectkit.reporting.builder import _ms, _num_or_none, _parse_seasonality
|
|
19
|
+
|
|
20
|
+
# How many (most recent) points to show when no explicit window is given.
|
|
21
|
+
#
|
|
22
|
+
# Tuning recomputes the detector client-side on every knob change, which is
|
|
23
|
+
# O(points x window); baking the whole history (10k-100k points) makes the page
|
|
24
|
+
# slow to load and laggy. The renderer runs detection in a Web Worker (off the UI
|
|
25
|
+
# thread), so the budget is "how many window-touches keep a debounced recompute
|
|
26
|
+
# under ~1s" rather than "what keeps the UI from freezing". We size the default
|
|
27
|
+
# point count INVERSELY to the seeded window — small windows can afford far more
|
|
28
|
+
# points than large ones — clamped to a render/payload-comfortable range. An
|
|
29
|
+
# explicit --from/--to span is honored in full (the user opted into that cost).
|
|
30
|
+
_TUNE_COMPUTE_BUDGET = 20_000_000 # ~points x window per recompute (off-thread)
|
|
31
|
+
_TUNE_MIN_POINTS = 3000
|
|
32
|
+
_TUNE_MAX_POINTS = 15000
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def default_window_points(seed_window: int) -> int:
|
|
36
|
+
"""Smart default point count for a seeded window size (budget-bound + clamped)."""
|
|
37
|
+
w = max(int(seed_window or 0), 1)
|
|
38
|
+
return max(_TUNE_MIN_POINTS, min(_TUNE_MAX_POINTS, round(_TUNE_COMPUTE_BUDGET / w)))
|
|
39
|
+
|
|
19
40
|
|
|
20
41
|
# Per-type interval-width defaults (mirror the detector class defaults and the
|
|
21
42
|
# website demo's DETECTOR_THRESHOLD_DEFAULT).
|
|
@@ -94,16 +115,29 @@ def build_tune_payload(
|
|
|
94
115
|
"""Assemble the interactive tuning payload from the persisted ``_dtk_datapoints``.
|
|
95
116
|
|
|
96
117
|
``save_url`` is the localhost POST endpoint the **Apply** button targets; it
|
|
97
|
-
is ``None`` for a static (read-only, no write-back) preview.
|
|
98
|
-
|
|
118
|
+
is ``None`` for a static (read-only, no write-back) preview. With no explicit
|
|
119
|
+
``start``/``end`` the window defaults to a budget-sized recent slice
|
|
120
|
+
(``default_window_points``), not the whole history.
|
|
99
121
|
"""
|
|
100
122
|
name = metric_config.name
|
|
101
123
|
interval = metric_config.get_interval()
|
|
102
124
|
interval_seconds = interval.seconds
|
|
103
125
|
|
|
104
|
-
start, end = resolve_window(internal, name, interval_seconds, start, end)
|
|
105
|
-
|
|
106
126
|
seed = _seed_detector(metric_config)
|
|
127
|
+
|
|
128
|
+
# Resolve the window. ``end`` defaults to the last datapoint. ``start``
|
|
129
|
+
# defaults to a budget-sized number of intervals before ``end`` (clamped to the
|
|
130
|
+
# first datapoint) — sized inversely to the seeded window, NOT the whole
|
|
131
|
+
# history — so the page stays interactive on large metrics. An explicit
|
|
132
|
+
# ``start`` (``--from``) is honored as-is.
|
|
133
|
+
if end is None:
|
|
134
|
+
end = internal.get_last_datapoint_timestamp(name)
|
|
135
|
+
if start is None and end is not None:
|
|
136
|
+
first = internal.get_first_datapoint_timestamp(name)
|
|
137
|
+
default_points = default_window_points(int(seed["windowSize"]))
|
|
138
|
+
lookback = end - timedelta(seconds=interval_seconds * default_points)
|
|
139
|
+
start = max(first, lookback) if first is not None else lookback
|
|
140
|
+
|
|
107
141
|
consecutive = 3
|
|
108
142
|
if metric_config.alerting:
|
|
109
143
|
consecutive = metric_config.alerting[0].consecutive_anomalies
|
|
@@ -11,11 +11,13 @@ knobs and retry.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import contextlib
|
|
14
15
|
import json
|
|
16
|
+
import os
|
|
15
17
|
import secrets
|
|
16
18
|
import threading
|
|
17
19
|
import webbrowser
|
|
18
|
-
from collections.abc import Callable
|
|
20
|
+
from collections.abc import Callable, Iterator
|
|
19
21
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
20
22
|
from pathlib import Path
|
|
21
23
|
from typing import Any, cast
|
|
@@ -27,6 +29,29 @@ from detectkit.tuning.html import render_tune_html
|
|
|
27
29
|
_MAX_BODY = 5_000_000 # generous cap on the posted config payload
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
@contextlib.contextmanager
|
|
33
|
+
def _quiet_stderr() -> Iterator[None]:
|
|
34
|
+
"""Silence OS-level stderr for the duration of the block.
|
|
35
|
+
|
|
36
|
+
``webbrowser.open`` shells out to ``xdg-open``, which prints a wall of
|
|
37
|
+
"browser not found" lines to stderr on a headless / WSL box. The launch is
|
|
38
|
+
best-effort (we already print the URL), so swallow that noise.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
42
|
+
except OSError:
|
|
43
|
+
yield
|
|
44
|
+
return
|
|
45
|
+
saved = os.dup(2)
|
|
46
|
+
try:
|
|
47
|
+
os.dup2(devnull, 2)
|
|
48
|
+
yield
|
|
49
|
+
finally:
|
|
50
|
+
os.dup2(saved, 2)
|
|
51
|
+
os.close(saved)
|
|
52
|
+
os.close(devnull)
|
|
53
|
+
|
|
54
|
+
|
|
30
55
|
class _TuneServer(ThreadingHTTPServer):
|
|
31
56
|
"""Localhost server holding the per-run state the handler reads/writes."""
|
|
32
57
|
|
|
@@ -136,10 +161,13 @@ def serve_tuner(
|
|
|
136
161
|
if on_ready is not None:
|
|
137
162
|
on_ready(url)
|
|
138
163
|
echo(f" Tuner: {url}")
|
|
139
|
-
echo(
|
|
164
|
+
echo(
|
|
165
|
+
" Open the URL above if no browser opens. Turn the knobs, then click Apply (Ctrl-C to cancel)."
|
|
166
|
+
)
|
|
140
167
|
if open_browser:
|
|
141
168
|
try:
|
|
142
|
-
|
|
169
|
+
with _quiet_stderr():
|
|
170
|
+
webbrowser.open(url)
|
|
143
171
|
except Exception:
|
|
144
172
|
pass
|
|
145
173
|
try:
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use strict";(()=>{var Me={"--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(e){return getComputedStyle(document.documentElement).getPropertyValue(e).trim()||Me[e]||"#888"}function Ae(e){let n=e.replace("#","").trim();n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]);let r=parseInt(n,16);return n.length!==6||Number.isNaN(r)?[209,91,54]:[r>>16&255,r>>8&255,r&255]}function X(e,n){let[r,t,o]=Ae(e);return`rgba(${r},${t},${o},${n})`}function me(e){let n=Math.max(1,window.devicePixelRatio||1),r=e.clientWidth||e.offsetWidth||0,t=e.clientHeight||e.offsetHeight||0;return e.width=Math.round(r*n),e.height=Math.round(t*n),n}var ae=Number.isFinite;function de(e,n,r,t,o,i,l,c,a,h,p,_){let R=n.length,M=Math.max(1,Math.round(l)),L=o-t||1,B=0;for(let g=0;g<R;g++){let k=r[g];!ae(k)||n[g]<t||n[g]>o||B++}if(e.strokeStyle=h,e.lineWidth=p*_,e.lineJoin="round",e.beginPath(),B<=M){let g=!1;for(let k=0;k<R;k++){let A=r[k],b=n[k];if(!ae(A)||b<t||b>o){g=!1;continue}let D=c(b),E=a(A);g?e.lineTo(D,E):(e.moveTo(D,E),g=!0)}}else{let g=new Array(M).fill(null),k=new Array(M).fill(null);for(let b=0;b<R;b++){let D=r[b],E=n[b];if(!ae(D)||E<t||E>o)continue;let P=Math.floor((E-t)/L*(M-1));P=P<0?0:P>M-1?M-1:P,(g[P]===null||D<g[P])&&(g[P]=D),(k[P]===null||D>k[P])&&(k[P]=D)}let A=!1;for(let b=0;b<M;b++){if(k[b]===null){A=!1;continue}let D=i+b,E=a(k[b]),P=a(g[b]);A?e.lineTo(D,E):(e.moveTo(D,E),A=!0),e.lineTo(D,P)}}e.stroke()}function fe(e){let n=Math.abs(e);return n>=1e3?e.toFixed(0):n>=10?e.toFixed(1):n>=1?e.toFixed(2):e.toFixed(3)}function pe(e,n){let r=new Date(e).toISOString();return n<2*864e5?r.slice(5,16).replace("T"," "):r.slice(5,10)}function be(e,n,r){return{left:n.l*r,top:n.t*r,right:e.width-n.r*r,bottom:e.height-n.b*r}}function he(e,n,r,t,o,i,l){let c=be(n,r,t),a=Math.max(c.left,Math.min(o(i),c.right));a<=c.left+.5||(e.save(),e.fillStyle="rgba(17,15,13,0.42)",e.fillRect(c.left,c.top,a-c.left,c.bottom-c.top),e.strokeStyle=X(j("--faint"),.7),e.lineWidth=1*t,e.setLineDash([4*t,4*t]),e.beginPath(),e.moveTo(a,c.top),e.lineTo(a,c.bottom),e.stroke(),e.setLineDash([]),e.fillStyle=X(j("--faint"),.95),e.font=`${10*t}px ui-monospace, monospace`,e.textAlign="left",e.textBaseline="top",e.fillText(l,a+6*t,c.top+5*t),e.restore())}function ge(e,n,r,t,o,i,l){let c=be(n,r,t),a=5*t;e.save();for(let h=0;h<i.length;h++){let p=i[h],_=o(p.t);if(_<c.left-1||_>c.right+1)continue;let R=l(p.kind);e.strokeStyle=X(R,.45),e.lineWidth=1*t,e.beginPath(),e.moveTo(_,c.top),e.lineTo(_,c.bottom),e.stroke(),e.fillStyle=R,e.beginPath(),e.moveTo(_-a,c.top),e.lineTo(_+a,c.top),e.lineTo(_,c.top+a*1.4),e.closePath(),e.fill()}e.restore()}var we={mad:1,zscore:2,iqr:4};function oe(e){let n=0;for(let r of e)n+=r;return n}function De(e){let n=e.map((r,t)=>t);return n.sort((r,t)=>e[r]<e[t]?-1:e[r]>e[t]?1:r-t),n}function Pe(e,n,r){let t=n.length;if(t===0)return NaN;if(e<=n[0])return r[0];if(e>=n[t-1])return r[t-1];for(let o=1;o<t;o++)if(e<=n[o]){let i=n[o-1],l=n[o],c=r[o-1],a=r[o];if(l===i)return c;let h=(e-i)/(l-i);return c+h*(a-c)}return r[t-1]}function ce(e,n,r){let t=De(e),o=t.map(p=>e[p]),i=t.map(p=>n[p]),l=oe(i),c=new Array(i.length),a=0;for(let p=0;p<i.length;p++)a+=i[p],c[p]=(a-.5*i[p])/l;let h=r/100;return Pe(h,c,o)}function ne(e,n){return ce(e,n,50)}function Le(e,n,r){let t=r===void 0?ne(e,n):r,o=e.map(i=>Math.abs(i-t));return ne(o,n)}function Ne(e,n){let r=oe(n),t=0;for(let o=0;o<e.length;o++)t+=e[o]*(n[o]/r);return t}function _e(e,n,r,t=0){let o=oe(n),i=n.map(a=>a/o),l=r===void 0?oe(e.map((a,h)=>a*i[h])):r,c=0;for(let a=0;a<e.length;a++)c+=i[a]*(e[a]-l)**2;if(t===1){let a=1-oe(i.map(h=>h*h));a>1e-12&&(c/=a)}return Math.sqrt(c)}function Ee(e,n){return n.smoothing==="none"?e.slice():n.smoothing==="ema"?We(e,n.smoothingAlpha):ze(e,n.smoothingWindow)}function We(e,n){let r=e.length,t=new Array(r).fill(NaN);if(r===0)return t;let o=-1;for(let i=0;i<r;i++)if(!Number.isNaN(e[i])){o=i;break}if(o===-1)return t;t[o]=e[o];for(let i=o+1;i<r;i++)Number.isNaN(e[i])?t[i]=t[i-1]:t[i]=n*e[i]+(1-n)*t[i-1];return t}function ze(e,n){let r=e.length,t=new Array(r).fill(NaN);for(let o=0;o<r;o++){let i=Math.max(0,o-n+1),l=0,c=0;for(let a=i;a<=o;a++)Number.isNaN(e[a])||(l+=e[a],c+=1);t[o]=c>0?l/c:NaN}return t}function Re(e,n){let r=e.length;if(n.inputType==="values")return e.slice();let t=new Array(r).fill(NaN);for(let o=1;o<r;o++){let i=e[o-1],l=e[o];n.inputType==="changes"?t[o]=(l-i)/i:n.inputType==="absolute_changes"?t[o]=l-i:t[o]=Math.log(l+1)-Math.log(i+1)}return t}function He(e,n){return e.halfLife!==null?e.halfLife:Math.max(e.windowSize/20,n/2,1)}function Ie(e,n){if(e.windowWeights==="none")return null;let r=e.windowSize,t=new Array(r);if(e.windowWeights==="exponential"){let o=He(e,n);for(let i=1;i<=r;i++){let l=Math.min(i/o,1e3);t[i-1]=Math.pow(.5,l)}return t}for(let o=1;o<=r;o++)t[o-1]=(r+1-o)/r;return t}function ve(e,n){return n===null?e.map(()=>1):e.map(r=>n[r-1])}function $e(e){let n=e[0];for(let r of e)r>n&&(n=r);return n}function qe(e){let n=e[0];for(let r of e)r<n&&(n=r);return n}function Oe(e,n,r){if(e.length<4)return 0;let t=($e(n)+qe(n))/2,o=n.map(b=>b>t),i=o.map(b=>!b),l=0,c=0;for(let b=0;b<n.length;b++)o[b]?l++:c++;if(l<2||c<2)return 0;let a=(b,D)=>D.filter((E,P)=>b[P]),h=a(i,e),p=a(o,e),_=a(i,n),R=a(o,n),M=a(i,r),L=a(o,r),B=ne(h,M),g=ne(p,L),k=ne(_,M),A=ne(R,L);return A===k?0:(B-g)/(A-k)}var Fe={mad:[["median","center"],["mad","spread"]],zscore:[["mean","center"],["std","spread"]],iqr:[["q1","center"],["q3","center"],["iqr","spread"]]};function ye(e,n,r){if(e==="mad"){let i=ne(n,r);return{median:i,mad:Le(n,r,i)}}if(e==="zscore"){let i=Ne(n,r);return{mean:i,std:_e(n,r,i,1)}}let t=ce(n,r,25),o=ce(n,r,75);return{q1:t,q3:o,iqr:o-t}}function je(e,n,r){if(e==="mad"){if(n.mad===0)return[n.median-1e-10,n.median+1e-10];let t=r*1.4826*n.mad;return[n.median-t,n.median+t]}return e==="zscore"?n.std===0?[n.mean-1e-10,n.mean+1e-10]:[n.mean-r*n.std,n.mean+r*n.std]:n.iqr===0?[n.q1-1e-10,n.q3+1e-10]:[n.q1-r*n.iqr,n.q3+r*n.iqr]}function Be(e,n,r){if(e==="mad"){let t=1.4826*n.mad;return t>0?r/t:1/0}return e==="zscore"?n.std>0?r/n.std:1/0:n.iqr>0?r/n.iqr:1/0}function Ge(e,n){return e==="mad"?n.median:e==="zscore"?n.mean:(n.q1+n.q3)/2}function xe(e,n,r,t,o){return{index:e,timestamp:n,value:r,processedValue:t,scored:!1,isAnomaly:!1,lower:NaN,upper:NaN,center:NaN,direction:null,severity:0,reason:o}}function ke(e,n){let{type:r}=n,t=Math.max(n.minSamples,we[r]),o=Fe[r],{timestamps:i,values:l}=e,c=i.length,a=Ee(l,n),h=Re(a,n),p=n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0,_=e.seasonalityData,R=Ie(n,t),M=[];for(let L=0;L<c;L++){let B=l[L],g=h[L],k=i[L];if(Number.isNaN(g)){M.push(xe(L,k,B,g,"missing_data"));continue}let A=Math.max(0,L-n.windowSize),b=L-A,D=h.slice(A,L),E=D.map(d=>!Number.isNaN(d)),P=[];for(let d=0;d<D.length;d++)E[d]&&P.push(D[d]);if(P.length<t){M.push(xe(L,k,B,g,"insufficient_data"));continue}let ee=new Array(b);for(let d=0;d<b;d++)ee[d]=b-d;let Y=[];for(let d=0;d<b;d++)E[d]&&Y.push(ee[d]);let H=ve(Y,R),V=0;n.detrend==="linear"&&(V=Oe(P,Y,H));let Q=V!==0?P.map((d,S)=>d+V*Y[S]):P,te=ye(r,Q,H),Z={...te};if(p&&_){let d=_[L];for(let S of n.seasonalityComponents){let G=new Array(b).fill(!0);for(let m=0;m<b;m++){let s=_[A+m],u=!0;for(let C of S)if(!s||s[C]!==(d==null?void 0:d[C])){u=!1;break}G[m]=u}let W=[],O=[];for(let m=0;m<b;m++)E[m]&&G[m]&&(W.push(D[m]),O.push(ee[m]));if(W.length<n.minSamplesPerGroup)continue;let F=ve(O,R),I=V!==0?W.map((m,s)=>m+V*O[s]):W,$=ye(r,I,F);for(let[m,s]of o){let u=te[m],z=(s==="spread"?u>0:u!==0)?$[m]/u:1;Z[m]*=z}}}let[U,f]=je(r,Z,n.threshold);U>f&&([U,f]=[f,U]);let y=g<U||g>f,T=null,v=0;if(y){let d;g<U?(T="below",d=U-g):(T="above",d=g-f),v=Be(r,Z,d)}let w=Ge(r,Z);M.push({index:L,timestamp:k,value:B,processedValue:g,scored:!0,isAnomaly:y,lower:U,upper:f,center:w,direction:T,severity:v,reason:"ok"})}return M}function Xe(e,n){let r=e.seasonalityData;if(!r||r.length===0)return 0;let t=0;for(let o of n){let i=new Set;for(let l of r)i.add(o.map(c=>{var a;return String((a=l==null?void 0:l[c])!=null?a:"")}).join("|"));t=Math.max(t,i.size)}return t}function ie(e,n){let r=e.timestamps.length,t=Math.max(n.minSamples,we[n.type]);if(n.smoothing==="sma"&&(t=Math.max(t,n.smoothingWindow-1)),n.smoothing==="ema"&&(t=Math.max(t,Math.ceil(5/n.smoothingAlpha))),n.inputType!=="values"&&(t=Math.max(t,1)),n.seasonalityComponents!==null&&n.seasonalityComponents.length>0&&Array.isArray(e.seasonalityData)&&e.seasonalityData.length>0){let i=Xe(e,n.seasonalityComponents);if(i>0){let l=n.minSamplesPerGroup*i;n.windowSize>=l&&(t=Math.max(t,l))}}return Math.min(t,r)}var q={l:52,r:14,t:14,b:26},J=Number.isFinite;function Se(e,n={}){let r=e.getContext("2d");if(!r)throw new Error("chart: 2D context unavailable");let t=r,o=1,i=null,l=-1,c=0,a=0,h=1,p=0,_=1;function R(){o=me(e)}let M=()=>e.width-(q.l+q.r)*o,L=()=>e.height-(q.t+q.b)*o,B=()=>h-a||1,g=f=>q.l*o+(f-a)/B()*M(),k=f=>e.height-q.b*o-(f-p)/(_-p||1)*L();function A(f){let y=i==null?void 0:i.series;if(!y||y.timestamps.length===0)return-1;let T=(f-q.l*o)/(M()||1),v=a+T*B(),w=y.timestamps,d=0,S=w.length-1;for(;d<S;){let G=d+S>>1;w[G]<v?d=G+1:S=G}return d>0&&v-w[d-1]<w[d]-v&&(d-=1),d}function b(f,y){let T=f.timestamps;a=T[0],h=T[T.length-1];let v=1/0,w=-1/0;for(let S of f.values)J(S)&&(S<v&&(v=S),S>w&&(w=S));for(let S of y)S.scored&&(J(S.lower)&&S.lower<v&&(v=S.lower),J(S.upper)&&S.upper>w&&(w=S.upper));(!J(v)||!J(w))&&(v=0,w=1),w<=v&&(w=v+1);let d=(w-v)*.06;p=v-d,_=w+d}function D(f,y,T){let v=i.series.timestamps;de(t,v,f,a,h,q.l*o,M(),g,k,y,T,o)}function E(f,y){let T=[],v=-1;for(let w=Math.max(0,y);w<f.length;w++){let d=f[w];d.scored&&J(d.lower)&&J(d.upper)?v===-1&&(v=w):v!==-1&&(T.push([v,w-1]),v=-1)}return v!==-1&&T.push([v,f.length-1]),T}function P(){if(c=0,!i||e.width===0||e.height===0)return;let{series:f,scored:y,params:T,alerts:v}=i;if(f.timestamps.length===0){t.fillStyle=j("--term-bg"),t.fillRect(0,0,e.width,e.height);return}let w=j("--clay"),d=j("--st-anomaly"),S=j("--faint"),G=j("--muted");t.fillStyle=j("--term-bg"),t.fillRect(0,0,e.width,e.height),t.font=`${11*o}px ui-monospace, 'JetBrains Mono', monospace`,t.textBaseline="middle";for(let m=0;m<=4;m++){let s=p+(_-p)*m/4,u=k(s);t.strokeStyle=X(S,.1),t.lineWidth=1*o,t.beginPath(),t.moveTo(q.l*o,u),t.lineTo(e.width-q.r*o,u),t.stroke(),t.fillStyle=G,t.textAlign="right",t.fillText(fe(s),(q.l-8)*o,u)}t.textBaseline="top";let W=B();for(let m=0;m<=5;m++){let s=a+W*m/5,u=g(s);t.fillStyle=G,t.textAlign=m===0?"left":m===5?"right":"center",t.fillText(pe(s,W),u,(e.height-q.b+7)*o)}t.save(),t.beginPath(),t.rect(q.l*o,q.t*o,M(),L()),t.clip();let O=y.length,F=Math.min(ie(f,T),O),I=F<O?f.timestamps[F]:void 0,$=E(y,F);t.fillStyle=X(w,.13);for(let[m,s]of $){t.beginPath(),t.moveTo(g(y[m].timestamp),k(y[m].upper));for(let u=m+1;u<=s;u++)t.lineTo(g(y[u].timestamp),k(y[u].upper));for(let u=s;u>=m;u--)t.lineTo(g(y[u].timestamp),k(y[u].lower));t.closePath(),t.fill()}t.strokeStyle=X(w,.4),t.lineWidth=1*o;for(let[m,s]of $)for(let u of["upper","lower"]){t.beginPath();for(let C=m;C<=s;C++){let z=g(y[C].timestamp),N=k(y[C][u]);C===m?t.moveTo(z,N):t.lineTo(z,N)}t.stroke()}t.strokeStyle=X(S,.55),t.lineWidth=1*o,t.setLineDash([3*o,3*o]);for(let[m,s]of $){t.beginPath();for(let u=m;u<=s;u++){let C=y[u].center;if(!J(C))continue;let z=g(y[u].timestamp),N=k(C);u===m?t.moveTo(z,N):t.lineTo(z,N)}t.stroke()}if(t.setLineDash([]),T.smoothing!=="none"){D(f.values,X(w,.28),1.25);let m=y.map(s=>s.processedValue);D(m,w,1.6)}else D(f.values,w,1.5);for(let m=F;m<O;m++){let s=y[m];if(!s.scored||!J(s.value))continue;let u=g(s.timestamp),C=k(s.value);s.isAnomaly?(t.fillStyle=X(d,.18),t.beginPath(),t.arc(u,C,6*o,0,Math.PI*2),t.fill(),t.fillStyle=d,t.beginPath(),t.arc(u,C,3*o,0,Math.PI*2),t.fill()):f.truthAnomaly[m]&&(t.strokeStyle=X(G,.7),t.lineWidth=1.25*o,t.beginPath(),t.arc(u,C,3.5*o,0,Math.PI*2),t.stroke())}I!==void 0&&he(t,e,q,o,g,I,"detection at full power \u2192"),l>=0&&l<y.length&&ee(l,T.windowSize,y,f,S),v&&v.length&&ge(t,e,q,o,g,v,m=>m==="anomaly"?j("--st-anomaly"):m==="recovery"?j("--st-recovery"):j("--st-nodata")),t.restore()}function ee(f,y,T,v,w){let d=v.timestamps,S=Math.max(0,f-y),G=f-1,W=q.t*o,O=L();if(G>=S){let $=v.intervalSeconds*1e3/B()*M()*.5,m=g(d[S])-$,s=g(d[G])+$;t.fillStyle="rgba(255,255,255,0.05)",t.fillRect(m,W,s-m,O),t.strokeStyle=X(w,.5),t.lineWidth=1*o,t.beginPath(),t.moveTo(m,W),t.lineTo(m,W+O),t.moveTo(s,W),t.lineTo(s,W+O),t.stroke()}let F=g(d[f]);t.strokeStyle=X(w,.85),t.lineWidth=1*o,t.setLineDash([2*o,2*o]),t.beginPath(),t.moveTo(F,W),t.lineTo(F,W+O),t.stroke(),t.setLineDash([]);let I=T[f];if(I.scored&&J(I.lower)&&J(I.upper)){let $=5*o;t.strokeStyle=X(j("--clay"),.85),t.lineWidth=1.5*o;for(let m of[I.lower,I.upper]){let s=k(m);t.beginPath(),t.moveTo(F-$,s),t.lineTo(F+$,s),t.stroke()}}if(J(I.value)){let $=k(I.value);t.fillStyle=j("--term-bg"),t.beginPath(),t.arc(F,$,4*o,0,Math.PI*2),t.fill(),t.strokeStyle=I.isAnomaly?j("--st-anomaly"):j("--clay"),t.lineWidth=2*o,t.beginPath(),t.arc(F,$,4*o,0,Math.PI*2),t.stroke()}}function Y(){c===0&&(c=requestAnimationFrame(P))}function H(){var T;if(!n.onHover||!i)return;if(l<0){n.onHover(null);return}let f=(T=i.scored[l])!=null?T:null,y={index:l,point:f,windowStart:Math.max(0,l-i.params.windowSize),windowEnd:l-1};n.onHover(y)}function V(f){if(!i)return;let y=e.getBoundingClientRect(),T=(f.clientX-y.left)*o,v=A(T);v!==l&&(l=v,H(),Y())}function Q(){l!==-1&&(l=-1,H(),Y())}e.addEventListener("mousemove",V),e.addEventListener("mouseleave",Q);function te(f){i=f,b(f.series,f.scored),l>=f.series.timestamps.length&&(l=-1),Y()}function Z(){R(),i&&(b(i.series,i.scored),Y())}function U(){e.removeEventListener("mousemove",V),e.removeEventListener("mouseleave",Q),c!==0&&cancelAnimationFrame(c),c=0,i=null}return R(),{render:te,resize:Z,destroy:U}}var Ye={mad:3,zscore:3,iqr:1.5},Ve={mad:10,zscore:3,iqr:4},Ke="dtk-tune";function x(e,n,r){let t=document.createElement(e);return n&&(t.className=n),r!=null&&(t.textContent=r),t}function se(e,n,r,t){let o=x("div","dtk-ctl");o.appendChild(x("label","dtk-ctl-label",e));let i=x("div","dtk-seg"),l=r,c=[],a=()=>{c.forEach(h=>h.classList.toggle("on",h.dataset.v===l))};return n.forEach(h=>{let p=x("button","dtk-seg-btn",h.label);p.type="button",p.dataset.v=h.value,p.onclick=()=>{l=h.value,a(),t(l)},c.push(p),i.appendChild(p)}),a(),o.appendChild(i),{row:o,get:()=>l,set:h=>{l=h,a()}}}function le(e,n,r){var h;let t=x("div","dtk-ctl"),o=x("div","dtk-ctl-head"),i=x("label","dtk-ctl-label",e),l=x("span","dtk-ctl-val"),c=(h=n.fmt)!=null?h:(p=>String(p));o.appendChild(i),o.appendChild(l),t.appendChild(o);let a=x("input","dtk-range");return a.type="range",a.min=String(n.min),a.max=String(n.max),a.step=String(n.step),a.value=String(n.value),l.textContent=c(n.value),a.oninput=()=>{let p=Number(a.value);l.textContent=c(p),r(p)},t.appendChild(a),{row:t,get:()=>Number(a.value),setMax:p=>{a.max=String(p),Number(a.value)>p&&(a.value=String(p),l.textContent=c(p))}}}function Je(e,n,r){let t=[],o=0;for(let i=0;i<e.length;i++){if(!(e[i].scored&&e[i].isAnomaly)){o=0;continue}let c=i>0&&e[i-1].scored&&e[i-1].isAnomaly,a=i>0&&e[i].timestamp-e[i-1].timestamp===n;o=c&&a?o+1:1,o===r&&t.push(i)}return t}function Te(e){let n={threshold:e.threshold,window_size:e.windowSize};return e.windowWeights!=="none"&&(n.window_weights=e.windowWeights,e.windowWeights==="exponential"&&e.halfLife!=null&&(n.half_life=e.halfLife)),e.detrend!=="none"&&(n.detrend=e.detrend),e.smoothing!=="none"&&(n.smoothing=e.smoothing),e.inputType!=="values"&&(n.input_type=e.inputType),e.seasonalityComponents&&e.seasonalityComponents.length&&(n.seasonality_components=e.seasonalityComponents,n.min_samples_per_group=e.minSamplesPerGroup),n}function Ue(e,n){let r=Te(e),t=[`type: ${e.type}`];for(let[o,i]of Object.entries(r))t.push(`${o}=${typeof i=="object"?JSON.stringify(i):i}`);return t.push(`consecutive_anomalies=${n}`),t.join(" \xB7 ")}function Qe(e,n){var $,m;tt(),n.classList.add(Ke),n.innerHTML="";let r=x("div","dtk-tune-root");n.appendChild(r);let t=e.points.length,o={timestamps:e.points.map(s=>s.t),values:e.points.map(s=>s.v==null?NaN:s.v),intervalSeconds:e.interval_seconds,truthAnomaly:new Array(t).fill(!1),seasonalityData:e.seasonality_columns.length?e.seasonality:void 0,seasonalityColumns:e.seasonality_columns.length?e.seasonality_columns:void 0},i=e.interval_seconds*1e3,l=e.detector,c=e.consecutive_anomalies,a=($=l.seasonalityComponents)!=null?$:[],h=new Set(a.flat()),p=a.length===1&&a[0].length>1,_=()=>{let s=e.seasonality_columns.filter(u=>h.has(u));return s.length?p?[s]:s.map(u=>[u]):null},R=()=>{var s;return{type:V.get(),threshold:Q.get(),windowSize:y.get(),minSamples:l.minSamples,inputType:l.inputType,smoothing:S.get(),smoothingAlpha:l.smoothingAlpha,smoothingWindow:l.smoothingWindow,windowWeights:T.get(),halfLife:T.get()==="exponential"?v.get():null,detrend:d.get(),seasonalityComponents:_(),minSamplesPerGroup:(s=Ve[V.get()])!=null?s:l.minSamplesPerGroup,consecutiveAnomalies:c}},M=x("div","dtk-tune-header"),L=x("div","dtk-tune-titlerow");L.appendChild(x("h1","dtk-tune-title",e.metric));let B=x("span","dtk-tune-badge",e.save_url?"manual tuning":"preview");L.appendChild(B),M.appendChild(L);let g=e.project?`${e.project} \xB7 `:"";M.appendChild(x("div","dtk-tune-sub",`${g}${t} points \xB7 ${et(e.interval_seconds)} grid`)),e.description&&M.appendChild(x("div","dtk-tune-desc",e.description)),r.appendChild(M);let k=x("div","dtk-tune-grid"),A=x("div","dtk-tune-controls"),b=x("div","dtk-tune-main");k.appendChild(A),k.appendChild(b),r.appendChild(k);let D=x("div","dtk-tune-chart"),E=x("canvas");D.appendChild(E),b.appendChild(D);let P=x("div","dtk-tune-readout");b.appendChild(P);let ee=Se(E,{onHover:s=>{if(!s||!s.point||!s.point.scored){P.textContent="";return}let u=s.point;P.textContent=`t=${Ze(u.timestamp)} value=${ue(u.value)} band=[${ue(u.lower)}, ${ue(u.upper)}]`+(u.isAnomaly?` \u26A0 ${u.direction} (sev ${u.severity.toFixed(2)})`:"")}}),Y=!1,H=()=>{Y||(Y=!0,requestAnimationFrame(()=>{Y=!1;let s=R(),u=ke(o,s),C=Je(u,i,s.consecutiveAnomalies),z=C.map(re=>({t:o.timestamps[re],kind:"anomaly"}));ee.render({series:o,scored:u,params:s,alerts:z});let N=ie(o,s),K=u.filter(re=>re.scored&&re.isAnomaly).length;W.textContent=`${K} flagged \xB7 ${C.length} alert${C.length===1?"":"s"} \xB7 warm-up ${N} pts`,F.textContent=Ue(s,c)}))},V=se("Detector",[{label:"MAD",value:"mad"},{label:"Z-Score",value:"zscore"},{label:"IQR",value:"iqr"}],l.type,s=>{var u;U((u=Ye[s])!=null?u:3),H()});A.appendChild(V.row);let Q=le("Threshold (\u03C3-equivalent)",{min:.5,max:10,step:.1,value:l.threshold,fmt:s=>s.toFixed(1)},H);A.appendChild(Q.row);let te=Q.row.querySelector("input"),Z=Q.row.querySelector(".dtk-ctl-val"),U=s=>{te&&(te.value=String(s)),Z&&(Z.textContent=s.toFixed(1))},f=Math.max(50,Math.min(2e3,t)),y=le("Window size (points)",{min:10,max:f,step:5,value:Math.min(l.windowSize,f),fmt:s=>String(s)},H);A.appendChild(y.row);let T=se("Recency weighting",[{label:"none",value:"none"},{label:"exponential",value:"exponential"},{label:"linear",value:"linear"}],l.windowWeights,s=>{w.style.display=s==="exponential"?"":"none",H()});A.appendChild(T.row);let v=le("Half-life (points)",{min:1,max:f,step:1,value:(m=l.halfLife)!=null?m:Math.max(5,Math.round(l.windowSize/20)),fmt:s=>String(s)},H),w=v.row;w.style.display=l.windowWeights==="exponential"?"":"none",A.appendChild(w);let d=se("Detrend",[{label:"none",value:"none"},{label:"linear",value:"linear"}],l.detrend,H);A.appendChild(d.row);let S=se("Smoothing",[{label:"none",value:"none"},{label:"EMA",value:"ema"},{label:"SMA",value:"sma"}],l.smoothing,H);if(A.appendChild(S.row),e.seasonality_columns.length){let s=x("div","dtk-ctl");s.appendChild(x("label","dtk-ctl-label","Seasonality conditioning"));let u=x("div","dtk-seg dtk-wrap");e.seasonality_columns.forEach(N=>{let K=x("button","dtk-seg-btn",N);K.type="button",K.classList.toggle("on",h.has(N)),K.onclick=()=>{h.has(N)?h.delete(N):h.add(N),K.classList.toggle("on",h.has(N)),H()},u.appendChild(K)}),s.appendChild(u);let C=x("label","dtk-check"),z=x("input");z.type="checkbox",z.checked=p,z.onchange=()=>{p=z.checked,H()},C.appendChild(z),C.appendChild(x("span",void 0,"conjoin selected into one group")),s.appendChild(C),A.appendChild(s)}let G=le("Alert: consecutive anomalies",{min:1,max:10,step:1,value:c,fmt:s=>String(s)},s=>{c=s,H()});A.appendChild(G.row);let W=x("div","dtk-tune-stat");b.appendChild(W);let O=x("div","dtk-tune-cfg");O.appendChild(x("span","dtk-tune-cfg-k","// effective config"));let F=x("code","dtk-tune-cfg-v");if(O.appendChild(F),b.appendChild(O),e.save_url){let s=x("div","dtk-tune-apply"),u=x("button","dtk-apply-btn","Apply to metric");u.type="button";let C=x("span","dtk-apply-msg");u.onclick=()=>{let z=R();u.disabled=!0,C.className="dtk-apply-msg info",C.textContent="Applying\u2026",fetch(e.save_url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({detector:{type:z.type,params:Te(z)},consecutive_anomalies:c})}).then(N=>N.ok?N.json():N.text().then(K=>{throw new Error(K||`HTTP ${N.status}`)})).then(N=>{var K;C.className="dtk-apply-msg ok",C.textContent=`Applied \u2192 ${(K=N.saved)!=null?K:"metric"} (previous archived). You can close this tab.`}).catch(N=>{u.disabled=!1,C.className="dtk-apply-msg err",C.textContent=`Apply failed: ${N.message}`})},s.appendChild(u),s.appendChild(C),b.appendChild(s)}else b.appendChild(x("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."));H();let I=0;window.addEventListener("resize",()=>{I&&cancelAnimationFrame(I),I=requestAnimationFrame(()=>ee.resize())})}function ue(e){if(!Number.isFinite(e))return"\u2014";let n=Math.abs(e);return n!==0&&(n<.01||n>=1e6)?e.toExponential(2):Number(e.toFixed(n<1?4:2)).toString()}function Ze(e){return new Date(e).toISOString().slice(0,16).replace("T"," ")}function et(e){return e%86400===0?`${e/86400}d`:e%3600===0?`${e/3600}h`:e%60===0?`${e/60}min`:`${e}s`}var Ce=!1;function tt(){if(Ce)return;Ce=!0;let e=`
|
|
2
|
-
.dtk-tune{--c:#d15b36;--c7:#b4471f;--ink:#1b1916;--muted:#6e675b;--faint:#9a9384;
|
|
3
|
-
--paper:#f5f1e8;--surface:#fbf9f3;--border:#e6e0d4;--green:#2e9e73;--anom:#d63232;
|
|
4
|
-
--mono:'JetBrains Mono',ui-monospace,Menlo,monospace;
|
|
5
|
-
--sans:'Schibsted Grotesk',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
|
|
6
|
-
.dtk-tune-root{max-width:1200px;margin:0 auto;padding:24px 20px 56px;font-family:var(--sans);color:var(--ink);}
|
|
7
|
-
.dtk-tune-titlerow{display:flex;align-items:center;gap:12px;}
|
|
8
|
-
.dtk-tune-title{font-size:24px;margin:0;font-weight:700;}
|
|
9
|
-
.dtk-tune-badge{font-family:var(--mono);font-size:11px;text-transform:uppercase;letter-spacing:.06em;
|
|
10
|
-
color:#fff;background:var(--c);border-radius:999px;padding:3px 10px;}
|
|
11
|
-
.dtk-tune-sub{color:var(--muted);font-size:13px;margin-top:4px;font-family:var(--mono);}
|
|
12
|
-
.dtk-tune-desc{color:var(--muted);font-size:13px;margin-top:8px;white-space:pre-wrap;}
|
|
13
|
-
.dtk-tune-grid{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-top:20px;align-items:start;}
|
|
14
|
-
@media(max-width:820px){.dtk-tune-grid{grid-template-columns:1fr;}}
|
|
15
|
-
.dtk-tune-controls{display:flex;flex-direction:column;gap:16px;background:var(--surface);
|
|
16
|
-
border:1px solid var(--border);border-radius:12px;padding:16px;}
|
|
17
|
-
.dtk-ctl{display:flex;flex-direction:column;gap:6px;}
|
|
18
|
-
.dtk-ctl-head{display:flex;justify-content:space-between;align-items:baseline;}
|
|
19
|
-
.dtk-ctl-label{font-size:12px;font-weight:600;color:var(--ink);}
|
|
20
|
-
.dtk-ctl-val{font-family:var(--mono);font-size:12px;color:var(--c7);}
|
|
21
|
-
.dtk-seg{display:flex;gap:4px;background:var(--paper);border:1px solid var(--border);border-radius:8px;padding:3px;}
|
|
22
|
-
.dtk-seg.dtk-wrap{flex-wrap:wrap;}
|
|
23
|
-
.dtk-seg-btn{flex:1 1 auto;border:0;background:transparent;color:var(--muted);font-family:var(--sans);
|
|
24
|
-
font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;white-space:nowrap;}
|
|
25
|
-
.dtk-seg-btn:hover{color:var(--ink);}
|
|
26
|
-
.dtk-seg-btn.on{background:var(--c);color:#fff;font-weight:600;}
|
|
27
|
-
.dtk-range{width:100%;accent-color:var(--c);cursor:pointer;}
|
|
28
|
-
.dtk-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);margin-top:2px;cursor:pointer;}
|
|
29
|
-
.dtk-tune-main{display:flex;flex-direction:column;gap:10px;min-width:0;}
|
|
30
|
-
.dtk-tune-chart{position:relative;width:100%;height:420px;background:var(--surface);
|
|
31
|
-
border:1px solid var(--border);border-radius:12px;overflow:hidden;}
|
|
32
|
-
.dtk-tune-chart canvas{width:100%;height:100%;display:block;}
|
|
33
|
-
.dtk-tune-readout{font-family:var(--mono);font-size:12px;color:var(--muted);min-height:18px;}
|
|
34
|
-
.dtk-tune-stat{font-family:var(--mono);font-size:12px;color:var(--ink);}
|
|
35
|
-
.dtk-tune-cfg{background:var(--ink);color:#c9c2b4;border-radius:8px;padding:10px 12px;font-family:var(--mono);
|
|
36
|
-
font-size:12px;overflow-x:auto;}
|
|
37
|
-
.dtk-tune-cfg-k{color:var(--faint);display:block;margin-bottom:4px;}
|
|
38
|
-
.dtk-tune-cfg-v{color:#e6e0d4;white-space:pre-wrap;word-break:break-word;}
|
|
39
|
-
.dtk-tune-apply{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:6px;}
|
|
40
|
-
.dtk-apply-btn{background:var(--c);color:#fff;border:0;border-radius:8px;padding:10px 18px;font-family:var(--sans);
|
|
41
|
-
font-size:14px;font-weight:600;cursor:pointer;}
|
|
42
|
-
.dtk-apply-btn:hover{background:var(--c7);}
|
|
43
|
-
.dtk-apply-btn:disabled{opacity:.55;cursor:default;}
|
|
44
|
-
.dtk-apply-msg{font-size:13px;}
|
|
45
|
-
.dtk-apply-msg.ok{color:var(--green);}
|
|
46
|
-
.dtk-apply-msg.err{color:var(--anom);}
|
|
47
|
-
.dtk-apply-msg.info{color:var(--muted);}
|
|
48
|
-
.dtk-tune-note{font-size:13px;color:var(--muted);background:var(--surface);border:1px dashed var(--border);
|
|
49
|
-
border-radius:8px;padding:10px 12px;}
|
|
50
|
-
`,n=document.createElement("style");n.textContent=e,document.head.appendChild(n)}window.__DTK_TUNE__={render:Qe};})();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|