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.
Files changed (139) hide show
  1. {detectkit-0.30.0/detectkit.egg-info → detectkit-0.30.1}/PKG-INFO +1 -1
  2. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/__init__.py +1 -1
  3. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/tune.py +14 -14
  4. detectkit-0.30.1/detectkit/tuning/assets/tune.js +51 -0
  5. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/payload.py +39 -5
  6. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/server.py +31 -3
  7. {detectkit-0.30.0 → detectkit-0.30.1/detectkit.egg-info}/PKG-INFO +1 -1
  8. detectkit-0.30.0/detectkit/tuning/assets/tune.js +0 -50
  9. {detectkit-0.30.0 → detectkit-0.30.1}/LICENSE +0 -0
  10. {detectkit-0.30.0 → detectkit-0.30.1}/MANIFEST.in +0 -0
  11. {detectkit-0.30.0 → detectkit-0.30.1}/README.md +0 -0
  12. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/__init__.py +0 -0
  13. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/__init__.py +0 -0
  14. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/base.py +0 -0
  15. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/branding.py +0 -0
  16. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/email.py +0 -0
  17. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/factory.py +0 -0
  18. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/mattermost.py +0 -0
  19. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/slack.py +0 -0
  20. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/telegram.py +0 -0
  21. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/channels/webhook.py +0 -0
  22. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/__init__.py +0 -0
  23. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_base.py +0 -0
  24. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
  25. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_decision.py +0 -0
  26. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
  27. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_recovery.py +0 -0
  28. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_replay.py +0 -0
  29. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/_types.py +0 -0
  30. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
  31. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/__init__.py +0 -0
  32. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/_base.py +0 -0
  33. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/_types.py +0 -0
  34. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/autotuner.py +0 -0
  35. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/config_emitter.py +0 -0
  36. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/crossval.py +0 -0
  37. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/detector_select.py +0 -0
  38. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/distribution.py +0 -0
  39. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/grid_search.py +0 -0
  40. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/html_labeler.py +0 -0
  41. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/label_server.py +0 -0
  42. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/labels.py +0 -0
  43. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/result.py +0 -0
  44. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/scoring.py +0 -0
  45. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/seasonality_search.py +0 -0
  46. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/settings.py +0 -0
  47. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/autotune/window_select.py +0 -0
  48. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/__init__.py +0 -0
  49. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/_output.py +0 -0
  50. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
  51. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
  52. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
  53. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/cli.md +0 -0
  54. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
  55. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
  56. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/overview.md +0 -0
  57. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/rules/project.md +0 -0
  58. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
  59. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
  60. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
  61. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
  62. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/__init__.py +0 -0
  63. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/autotune.py +0 -0
  64. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/clean.py +0 -0
  65. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/init.py +0 -0
  66. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/init_claude.py +0 -0
  67. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/run.py +0 -0
  68. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/test_alert.py +0 -0
  69. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/commands/unlock.py +0 -0
  70. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/cli/main.py +0 -0
  71. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/__init__.py +0 -0
  72. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/metric_config.py +0 -0
  73. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/profile.py +0 -0
  74. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/project_config.py +0 -0
  75. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/config/validator.py +0 -0
  76. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/__init__.py +0 -0
  77. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/interval.py +0 -0
  78. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/core/models.py +0 -0
  79. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/__init__.py +0 -0
  80. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/_sql_manager.py +0 -0
  81. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/clickhouse_manager.py +0 -0
  82. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/__init__.py +0 -0
  83. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_alert_states.py +0 -0
  84. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
  85. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_base.py +0 -0
  86. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_datapoints.py +0 -0
  87. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_detections.py +0 -0
  88. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_maintenance.py +0 -0
  89. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_metrics.py +0 -0
  90. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_schema.py +0 -0
  91. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/_tasks.py +0 -0
  92. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/internal_tables/manager.py +0 -0
  93. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/manager.py +0 -0
  94. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/mysql_manager.py +0 -0
  95. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/postgres_manager.py +0 -0
  96. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/database/tables.py +0 -0
  97. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/__init__.py +0 -0
  98. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/base.py +0 -0
  99. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/factory.py +0 -0
  100. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/seasonality.py +0 -0
  101. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/__init__.py +0 -0
  102. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/_windowed.py +0 -0
  103. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/iqr.py +0 -0
  104. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/mad.py +0 -0
  105. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/manual_bounds.py +0 -0
  106. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/detectors/statistical/zscore.py +0 -0
  107. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/__init__.py +0 -0
  108. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/metric_loader.py +0 -0
  109. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/loaders/query_template.py +0 -0
  110. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/__init__.py +0 -0
  111. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/error_dispatch.py +0 -0
  112. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/__init__.py +0 -0
  113. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
  114. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_base.py +0 -0
  115. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
  116. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_load_step.py +0 -0
  117. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/_types.py +0 -0
  118. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/orchestration/task_manager/manager.py +0 -0
  119. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/__init__.py +0 -0
  120. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/assets/report.js +0 -0
  121. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/builder.py +0 -0
  122. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/reporting/html_report.py +0 -0
  123. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/__init__.py +0 -0
  124. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/config_writer.py +0 -0
  125. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/tuning/html.py +0 -0
  126. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/__init__.py +0 -0
  127. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/datetime_utils.py +0 -0
  128. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/env_interpolation.py +0 -0
  129. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/json_utils.py +0 -0
  130. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit/utils/stats.py +0 -0
  131. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/SOURCES.txt +0 -0
  132. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/dependency_links.txt +0 -0
  133. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/entry_points.txt +0 -0
  134. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/requires.txt +0 -0
  135. {detectkit-0.30.0 → detectkit-0.30.1}/detectkit.egg-info/top_level.txt +0 -0
  136. {detectkit-0.30.0 → detectkit-0.30.1}/pyproject.toml +0 -0
  137. {detectkit-0.30.0 → detectkit-0.30.1}/requirements.txt +0 -0
  138. {detectkit-0.30.0 → detectkit-0.30.1}/setup.cfg +0 -0
  139. {detectkit-0.30.0 → detectkit-0.30.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.30.0
3
+ Version: 0.30.1
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.30.0"
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
- data = internal_manager.load_datapoints(name, from_timestamp=from_dt, to_timestamp=to_dt)
65
- ts = data["timestamp"]
66
- if len(ts) == 0:
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=start,
76
- end=end,
68
+ start=from_dt,
69
+ end=to_dt,
77
70
  project_name=project_name,
78
71
  )
79
- if not payload["points"]:
80
- echo_noop(name, "no datapoints in the resolved window")
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, resolve_window
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. The series is
98
- read over the same default window as the report (``resolve_window``).
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(" Turn the knobs in the browser, then click Apply to metric (Ctrl-C to cancel).")
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
- webbrowser.open(url)
169
+ with _quiet_stderr():
170
+ webbrowser.open(url)
143
171
  except Exception:
144
172
  pass
145
173
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.30.0
3
+ Version: 0.30.1
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
@@ -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