detectkit 0.54.0__tar.gz → 0.55.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.54.0/detectkit.egg-info → detectkit-0.55.1}/PKG-INFO +1 -1
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/__init__.py +1 -1
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/cli.md +32 -9
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/overview.md +3 -1
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +8 -6
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/ui.py +4 -1
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/__init__.py +2 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/osi_model.py +29 -13
- detectkit-0.55.1/detectkit/ui/assets/ui.js +518 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/metric_files.py +15 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/server.py +238 -7
- {detectkit-0.54.0 → detectkit-0.55.1/detectkit.egg-info}/PKG-INFO +1 -1
- detectkit-0.54.0/detectkit/ui/assets/ui.js +0 -323
- {detectkit-0.54.0 → detectkit-0.55.1}/LICENSE +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/MANIFEST.in +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/README.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/axis_spec.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/result.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/_output.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-tune/SKILL.md +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/osi.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/main.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/metric_io.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/profile.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/project_config.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/config/validator.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/core/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/core/interval.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/core/models.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/database/tables.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/autoreg.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/errors.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/exporter.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/importer.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/semantic/query_gen.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/assets/tune.js +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/html.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/tuning/server.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/html.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/jobs.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/ui/overview.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit/utils/stats.py +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/pyproject.toml +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/requirements.txt +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/setup.cfg +0 -0
- {detectkit-0.54.0 → detectkit-0.55.1}/setup.py +0 -0
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.55.1"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -224,20 +224,39 @@ exactly as if typed into a terminal.
|
|
|
224
224
|
|
|
225
225
|
### Managing metrics from the UI
|
|
226
226
|
|
|
227
|
-
The cockpit header has a **New metric** button
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
The cockpit header has a **New metric** button, and every metric row an
|
|
228
|
+
**Edit** action; both open an editor overlay with **two tabs sharing one
|
|
229
|
+
draft**: **Builder** — a structured form over the whole config (basics,
|
|
230
|
+
schedule/loading, seasonality, minimal detector rows with type + 1-2 key
|
|
231
|
+
params — fine-tuning belongs in `dtk tune` —, alerting with a channel
|
|
232
|
+
multi-select seeded from `profiles.yml` channel names, `ai_context`; SQL in
|
|
233
|
+
a syntax-highlighted pane, `query_file` paths read-only; a **From OSI**
|
|
234
|
+
sub-tab compiles a pasted OSI semantic-model metric through the same code
|
|
235
|
+
path as `dtk osi import`) — and **YAML**, the raw text, kept for whole-config
|
|
236
|
+
pastes. The last-edited tab wins, never silently: leaving an edited YAML tab
|
|
237
|
+
validates server-side first and blocks the switch on error; leaving an
|
|
238
|
+
edited Builder re-emits the YAML. A debounced live-validation chip re-checks
|
|
239
|
+
the draft while typing. Keys the form doesn't model (`autotune:`, custom
|
|
240
|
+
templates, unknown detector types/params, a multi-entry alerting list)
|
|
241
|
+
round-trip verbatim, listed under "Preserved fields". Create writes
|
|
242
|
+
`metrics/[<folder>/]<name>.yml`; after a create, a **next-steps strip**
|
|
243
|
+
offers **Load & detect** (`dtk run --steps load,detect` for just that metric
|
|
244
|
+
— no alert step, so an untuned config can't spam a channel) and then **Open
|
|
245
|
+
tune** on the loaded series. **Save** validates
|
|
231
246
|
server-side **before any write**: YAML syntax → full `MetricConfig` → a deep
|
|
232
247
|
detector-params check (each factory-known detector is actually constructed) —
|
|
233
248
|
an invalid config lands in the editor's error pane with nothing written. A
|
|
234
249
|
successful save **archives the previous file verbatim** to
|
|
235
250
|
`metrics/.history/<metric>/` (the same archive `dtk tune`'s Apply writes,
|
|
236
|
-
excluded from metric discovery) and only then overwrites in place
|
|
237
|
-
|
|
238
|
-
with a newline)
|
|
239
|
-
|
|
240
|
-
|
|
251
|
+
excluded from metric discovery) and only then overwrites in place. A
|
|
252
|
+
YAML-tab save writes the text typed, comments intact (normalized only to end
|
|
253
|
+
with a newline); a Builder save **re-emits the YAML deterministically,
|
|
254
|
+
dropping hand-written comments** (the archive keeps the previous file; edit
|
|
255
|
+
comment-heavy files from the YAML tab). A save is refused when the file
|
|
256
|
+
changed on disk after the editor was opened (a `dtk tune` Apply or another
|
|
257
|
+
session saved first) — reopen the metric rather than overwrite it. A file
|
|
258
|
+
that doesn't parse opens YAML-only, with the parse error on the disabled
|
|
259
|
+
Builder tab. Renaming via `name:`
|
|
241
260
|
is allowed — uniqueness is enforced project-wide — and rows under the old name
|
|
242
261
|
stay in the `_dtk_*` tables until `dtk clean`. **Delete** lives in the edit
|
|
243
262
|
overlay behind an explicit confirmation step, and the server additionally
|
|
@@ -320,6 +339,10 @@ dtk osi import model.osi.yml -m total_sales -i 1h -o metrics/ # scaffold a metr
|
|
|
320
339
|
dtk osi export -o semantic/detectkit.osi.yml # publish back to OSI
|
|
321
340
|
```
|
|
322
341
|
|
|
342
|
+
The `dtk ui` metric Builder's **From OSI** sub-tab does the import
|
|
343
|
+
interactively (paste a model, pick a metric/target, Compile) through the
|
|
344
|
+
same code path — see "Managing metrics from the UI" above.
|
|
345
|
+
|
|
323
346
|
## Common workflows
|
|
324
347
|
|
|
325
348
|
```bash
|
|
@@ -106,7 +106,9 @@ same reports in a browser overlay plus an overview table (alert frequency,
|
|
|
106
106
|
freshness — netting out any configured `loading_delay` so a deliberately
|
|
107
107
|
delayed metric doesn't read as stale — quality when incidents are labeled), a
|
|
108
108
|
panel that drives `dtk run` / `dtk autotune` / `dtk unlock`, and an in-browser
|
|
109
|
-
editor to create, edit and delete metric YAML files
|
|
109
|
+
editor to create, edit and delete metric YAML files — a structured **Builder**
|
|
110
|
+
form (with OSI import and a post-create load-then-tune flow) next to a raw
|
|
111
|
+
**YAML** tab. See `cli.md`.
|
|
110
112
|
|
|
111
113
|
## Glossary
|
|
112
114
|
|
{detectkit-0.54.0 → detectkit-0.55.1}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
@@ -16,12 +16,14 @@ need detail on any field, read the matching file under
|
|
|
16
16
|
`project.md`); this skill is the procedure, those are the reference.
|
|
17
17
|
|
|
18
18
|
`dtk ui` also has a browser editor for metric YAMLs (a **New metric** button
|
|
19
|
-
and, per row, an **Edit** action), validated the same way before it writes
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
and, per row, an **Edit** action), validated the same way before it writes:
|
|
20
|
+
a structured **Builder** form (highlighted SQL pane, channel picker, an OSI
|
|
21
|
+
import sub-tab, a post-create load-then-tune strip) next to a raw **YAML**
|
|
22
|
+
tab. Prefer this skill when the user is working with you in the terminal —
|
|
23
|
+
it gathers the query, detector and alerting choices and explains each one in
|
|
24
|
+
context; point them at the UI when they'd rather click through the same flow
|
|
25
|
+
themselves, or for a quick manual edit while already watching the cockpit
|
|
26
|
+
(nudging a threshold, adding a channel).
|
|
25
27
|
|
|
26
28
|
## Step 0 — Confirm you're in a detectkit project
|
|
27
29
|
|
|
@@ -18,7 +18,7 @@ from detectkit.config.profile import ProfilesConfig
|
|
|
18
18
|
from detectkit.config.project_config import ProjectConfig
|
|
19
19
|
from detectkit.database.internal_tables import InternalTablesManager
|
|
20
20
|
from detectkit.ui.overview import ALL_WINDOW_PRESETS
|
|
21
|
-
from detectkit.ui.server import serve_ui
|
|
21
|
+
from detectkit.ui.server import build_form_meta, serve_ui
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def run_ui(
|
|
@@ -82,6 +82,8 @@ def run_ui(
|
|
|
82
82
|
click.echo(click.style(f"detectkit UI: {project_config.name}", fg="cyan", bold=True))
|
|
83
83
|
click.echo(f" {len(metrics)} metric(s) covered by --select {select} (window: {window})")
|
|
84
84
|
|
|
85
|
+
form_meta = build_form_meta(profiles_config)
|
|
86
|
+
|
|
85
87
|
try:
|
|
86
88
|
serve_ui(
|
|
87
89
|
project_config=project_config,
|
|
@@ -92,6 +94,7 @@ def run_ui(
|
|
|
92
94
|
profile=profile,
|
|
93
95
|
echo=click.echo,
|
|
94
96
|
open_browser=not no_open,
|
|
97
|
+
form_meta=form_meta,
|
|
95
98
|
)
|
|
96
99
|
except KeyboardInterrupt:
|
|
97
100
|
pass
|
|
@@ -24,6 +24,7 @@ from detectkit.semantic.osi_model import (
|
|
|
24
24
|
DETECTKIT_VENDOR,
|
|
25
25
|
OsiSemanticModel,
|
|
26
26
|
load_osi_models,
|
|
27
|
+
parse_osi_models,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
__all__ = [
|
|
@@ -38,4 +39,5 @@ __all__ = [
|
|
|
38
39
|
"export_models",
|
|
39
40
|
"import_osi_metric",
|
|
40
41
|
"load_osi_models",
|
|
42
|
+
"parse_osi_models",
|
|
41
43
|
]
|
|
@@ -171,21 +171,25 @@ def normalize_ai_context(raw: Any) -> dict[str, Any] | None:
|
|
|
171
171
|
return None
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def
|
|
175
|
-
"""Parse
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
174
|
+
def parse_osi_models(text: str, *, source: str = "<input>") -> list[OsiSemanticModel]:
|
|
175
|
+
"""Parse OSI YAML *text* into a list of :class:`OsiSemanticModel`.
|
|
176
|
+
|
|
177
|
+
The text seam behind :func:`load_osi_models`: it takes the raw YAML string
|
|
178
|
+
directly, so a caller with no file on disk — e.g. the ``dtk ui`` Builder's
|
|
179
|
+
"From OSI" paste box, which hands the browser's textarea contents straight
|
|
180
|
+
to the server — can parse a model without writing a temp file first.
|
|
181
|
+
Accepts the canonical ``semantic_model:`` root (a list, per the OSI
|
|
182
|
+
examples) as well as a single mapping or a bare list, for robustness.
|
|
183
|
+
Raises :class:`OsiParseError` on empty/malformed text; *source* names the
|
|
184
|
+
origin (a file path, or a caller-chosen marker like ``"<pasted OSI
|
|
185
|
+
model>"``) so error messages stay actionable without a real path.
|
|
180
186
|
"""
|
|
181
|
-
if not path.exists():
|
|
182
|
-
raise OsiParseError(f"OSI model file not found: {path}")
|
|
183
187
|
try:
|
|
184
|
-
raw = yaml.safe_load(
|
|
188
|
+
raw = yaml.safe_load(text)
|
|
185
189
|
except yaml.YAMLError as exc:
|
|
186
|
-
raise OsiParseError(f"invalid YAML in {
|
|
190
|
+
raise OsiParseError(f"invalid YAML in {source}: {exc}") from exc
|
|
187
191
|
if not raw:
|
|
188
|
-
raise OsiParseError(f"empty OSI model file: {
|
|
192
|
+
raise OsiParseError(f"empty OSI model file: {source}")
|
|
189
193
|
|
|
190
194
|
node: Any = raw.get("semantic_model", raw) if isinstance(raw, dict) else raw
|
|
191
195
|
items = node if isinstance(node, list) else [node]
|
|
@@ -196,12 +200,24 @@ def load_osi_models(path: Path) -> list[OsiSemanticModel]:
|
|
|
196
200
|
try:
|
|
197
201
|
models.append(OsiSemanticModel.model_validate(item))
|
|
198
202
|
except Exception as exc: # pydantic ValidationError, etc.
|
|
199
|
-
raise OsiParseError(f"could not parse a semantic_model in {
|
|
203
|
+
raise OsiParseError(f"could not parse a semantic_model in {source}: {exc}") from exc
|
|
200
204
|
if not models:
|
|
201
|
-
raise OsiParseError(f"no semantic_model found in {
|
|
205
|
+
raise OsiParseError(f"no semantic_model found in {source}")
|
|
202
206
|
return models
|
|
203
207
|
|
|
204
208
|
|
|
209
|
+
def load_osi_models(path: Path) -> list[OsiSemanticModel]:
|
|
210
|
+
"""Parse an OSI YAML file into a list of :class:`OsiSemanticModel`.
|
|
211
|
+
|
|
212
|
+
Existence-checks *path*, reads it, then delegates to :func:`parse_osi_models`
|
|
213
|
+
with ``source=str(path)`` — the same parsing/error-message behavior as
|
|
214
|
+
before the text/path split, just re-homed onto the text seam.
|
|
215
|
+
"""
|
|
216
|
+
if not path.exists():
|
|
217
|
+
raise OsiParseError(f"OSI model file not found: {path}")
|
|
218
|
+
return parse_osi_models(path.read_text(), source=str(path))
|
|
219
|
+
|
|
220
|
+
|
|
205
221
|
def find_metric(
|
|
206
222
|
models: list[OsiSemanticModel], metric_name: str
|
|
207
223
|
) -> tuple[OsiSemanticModel, OsiMetric]:
|