detectkit 0.56.1__tar.gz → 0.56.2__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.56.1/detectkit.egg-info → detectkit-0.56.2}/PKG-INFO +1 -1
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/__init__.py +1 -1
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_base.py +11 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_decision.py +13 -2
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/metric_config.py +29 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/_alert_step.py +7 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/_base.py +18 -1
- {detectkit-0.56.1 → detectkit-0.56.2/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.56.1 → detectkit-0.56.2}/LICENSE +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/MANIFEST.in +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/README.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/axis_spec.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/result.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/_output.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-tune/SKILL.md +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/osi.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/ui.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/main.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/metric_io.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/profile.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/project_config.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/config/validator.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/core/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/core/interval.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/core/models.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/database/tables.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/base.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/autoreg.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/errors.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/exporter.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/importer.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/osi_model.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/semantic/query_gen.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/assets/tune.js +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/html.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/tuning/server.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/assets/ui.js +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/html.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/jobs.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/metric_files.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/overview.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/ui/server.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit/utils/stats.py +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/pyproject.toml +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/requirements.txt +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/setup.cfg +0 -0
- {detectkit-0.56.1 → detectkit-0.56.2}/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.56.
|
|
7
|
+
__version__ = "0.56.2"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -38,6 +38,7 @@ class _OrchestratorBase:
|
|
|
38
38
|
help_url: str | None = None,
|
|
39
39
|
ai_synonyms: list[str] | None = None,
|
|
40
40
|
loading_delay_seconds: int = 0,
|
|
41
|
+
grid_phase_seconds: int = 0,
|
|
41
42
|
):
|
|
42
43
|
self.metric_name = metric_name
|
|
43
44
|
self.interval = interval
|
|
@@ -69,6 +70,16 @@ class _OrchestratorBase:
|
|
|
69
70
|
# step AND the recovery mixin's own fetch bound) computes the same
|
|
70
71
|
# delay-aware boundary as the load step's maturity cut-off.
|
|
71
72
|
self.loading_delay_seconds = int(loading_delay_seconds or 0)
|
|
73
|
+
# Phase of the metric's interval grid on the epoch clock, in
|
|
74
|
+
# [0, interval). The loader anchors datapoints on loading_start_time, so
|
|
75
|
+
# a non-epoch-aligned start puts the stored grid at an arbitrary phase;
|
|
76
|
+
# ``get_last_complete_point`` floors to THIS phase (not plain epoch time)
|
|
77
|
+
# so its exact-timestamp no-data lookup asks for a boundary the loader
|
|
78
|
+
# actually writes. Constructor state, like ``loading_delay_seconds`` —
|
|
79
|
+
# the two together keep the no-data expectation in lockstep with the load
|
|
80
|
+
# step's grid. Defaults to 0 (the epoch grid), so direct-API callers and
|
|
81
|
+
# epoch-aligned metrics are unchanged (issue #114).
|
|
82
|
+
self.grid_phase_seconds = int(grid_phase_seconds or 0) % interval.seconds
|
|
72
83
|
|
|
73
84
|
@staticmethod
|
|
74
85
|
def _group_by_timestamp(
|
|
@@ -474,6 +474,15 @@ class _DecisionMixin(_OrchestratorBase):
|
|
|
474
474
|
def get_last_complete_point(self, now: datetime | None = None) -> datetime:
|
|
475
475
|
"""Floor ``now`` to the previous fully completed *mature* interval boundary.
|
|
476
476
|
|
|
477
|
+
Flooring happens on the metric's **own** interval grid, whose phase on
|
|
478
|
+
the epoch clock is ``grid_phase_seconds`` (0 = epoch-aligned). The loader
|
|
479
|
+
anchors datapoints on ``loading_start_time``, so a non-epoch-aligned
|
|
480
|
+
start (e.g. ``:07`` on a 10-minute grid) puts the stored series at an
|
|
481
|
+
arbitrary phase; flooring plain epoch time would return a boundary the
|
|
482
|
+
loader never writes and make the exact-timestamp no-data lookup fire a
|
|
483
|
+
permanent false alert (issue #114). Phasing here keeps the no-data
|
|
484
|
+
expectation on the same grid the load step persists.
|
|
485
|
+
|
|
477
486
|
With a ``loading_delay`` configured, the loader only trusts
|
|
478
487
|
``[t, t+interval)`` once ``now >= t + interval + delay`` — so the same
|
|
479
488
|
delay shifts the effective now back here BEFORE flooring, keeping the
|
|
@@ -487,10 +496,12 @@ class _DecisionMixin(_OrchestratorBase):
|
|
|
487
496
|
now = to_aware_utc(now)
|
|
488
497
|
|
|
489
498
|
interval_seconds = self.interval.seconds
|
|
499
|
+
phase = self.grid_phase_seconds # already normalised into [0, interval)
|
|
490
500
|
# Shift the effective now back by the maturity delay BEFORE flooring
|
|
491
501
|
# (subtract-then-floor keeps the boundary correct for delays that
|
|
492
|
-
# aren't a multiple of the interval)
|
|
502
|
+
# aren't a multiple of the interval), then floor onto the metric's grid
|
|
503
|
+
# phase rather than the epoch grid.
|
|
493
504
|
effective_epoch = int(now.timestamp()) - self.loading_delay_seconds
|
|
494
|
-
floored = (effective_epoch // interval_seconds) * interval_seconds
|
|
505
|
+
floored = ((effective_epoch - phase) // interval_seconds) * interval_seconds + phase
|
|
495
506
|
last_complete = floored - interval_seconds
|
|
496
507
|
return datetime.fromtimestamp(last_complete, tz=timezone.utc)
|
|
@@ -5,6 +5,7 @@ Defines configuration structure for individual metrics loaded from YAML files.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
@@ -1051,3 +1052,31 @@ def resolve_loading_delay_seconds(
|
|
|
1051
1052
|
continue
|
|
1052
1053
|
return loading_delay_to_seconds(value)
|
|
1053
1054
|
return 0
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def resolve_grid_phase_seconds(loading_start_time: str | None, interval_seconds: int) -> int:
|
|
1058
|
+
"""Phase of the metric's interval grid on the epoch clock, in ``[0, interval)``.
|
|
1059
|
+
|
|
1060
|
+
The loader anchors every datapoint on ``loading_start_time`` (first run) or
|
|
1061
|
+
the resume cursor descended from it, so the stored grid sits at
|
|
1062
|
+
``loading_start_time_epoch % interval`` — an arbitrary phase whenever the
|
|
1063
|
+
start isn't a multiple of the interval (e.g. ``00:07:00`` on a 10-minute
|
|
1064
|
+
grid). The alert step's no-data check does an **exact-timestamp** lookup, so
|
|
1065
|
+
it must floor to that same phase; flooring plain epoch time (phase 0) instead
|
|
1066
|
+
asks for a boundary the loader never writes and fires a permanent false
|
|
1067
|
+
no-data alert. This is the single seam that keeps the two grid phases in
|
|
1068
|
+
lockstep — the mirror of :func:`resolve_loading_delay_seconds` for the
|
|
1069
|
+
maturity delay (see issue #114).
|
|
1070
|
+
|
|
1071
|
+
Returns ``0`` (the epoch grid — unchanged behaviour) when no
|
|
1072
|
+
``loading_start_time`` is set or it can't be parsed in the canonical
|
|
1073
|
+
``"%Y-%m-%d %H:%M:%S"`` UTC form the loader uses.
|
|
1074
|
+
"""
|
|
1075
|
+
if not loading_start_time or interval_seconds <= 0:
|
|
1076
|
+
return 0
|
|
1077
|
+
try:
|
|
1078
|
+
start = datetime.strptime(loading_start_time, "%Y-%m-%d %H:%M:%S")
|
|
1079
|
+
except (ValueError, TypeError):
|
|
1080
|
+
return 0
|
|
1081
|
+
epoch = int(start.replace(tzinfo=timezone.utc).timestamp())
|
|
1082
|
+
return epoch % interval_seconds
|
|
@@ -33,6 +33,12 @@ class _AlertStepMixin(_TaskManagerBase):
|
|
|
33
33
|
# every other last-complete-point computation) must not run ahead of
|
|
34
34
|
# what load has had a chance to persist under a loading_delay.
|
|
35
35
|
loading_delay_seconds = self._loading_delay_seconds(config)
|
|
36
|
+
# Same grid phase as the load step: the no-data check floors to the
|
|
37
|
+
# metric's own interval grid (anchored on loading_start_time), not plain
|
|
38
|
+
# epoch time, so a non-epoch-aligned start (e.g. :07 on a 10min grid)
|
|
39
|
+
# doesn't make the exact-timestamp lookup ask for a boundary the loader
|
|
40
|
+
# never writes — a permanent false no-data alert (issue #114).
|
|
41
|
+
grid_phase_seconds = self._grid_phase_seconds(config)
|
|
36
42
|
|
|
37
43
|
for i, alerting_config in enumerate(active_configs):
|
|
38
44
|
if multi:
|
|
@@ -75,6 +81,7 @@ class _AlertStepMixin(_TaskManagerBase):
|
|
|
75
81
|
project_name=getattr(self.project_config, "name", None),
|
|
76
82
|
help_url=help_url,
|
|
77
83
|
loading_delay_seconds=loading_delay_seconds,
|
|
84
|
+
grid_phase_seconds=grid_phase_seconds,
|
|
78
85
|
)
|
|
79
86
|
|
|
80
87
|
last_point = orchestrator.get_last_complete_point()
|
|
@@ -7,7 +7,11 @@ from datetime import datetime
|
|
|
7
7
|
from detectkit.alerting.channels.base import BaseAlertChannel
|
|
8
8
|
from detectkit.alerting.channels.factory import AlertChannelFactory
|
|
9
9
|
from detectkit.alerting.orchestrator import DetectionRecord, hydrate_detection_records
|
|
10
|
-
from detectkit.config.metric_config import
|
|
10
|
+
from detectkit.config.metric_config import (
|
|
11
|
+
MetricConfig,
|
|
12
|
+
resolve_grid_phase_seconds,
|
|
13
|
+
resolve_loading_delay_seconds,
|
|
14
|
+
)
|
|
11
15
|
from detectkit.database.internal_tables import InternalTablesManager
|
|
12
16
|
|
|
13
17
|
|
|
@@ -40,6 +44,19 @@ class _TaskManagerBase:
|
|
|
40
44
|
getattr(self.project_config, "loading_delay", None),
|
|
41
45
|
)
|
|
42
46
|
|
|
47
|
+
def _grid_phase_seconds(self, config: MetricConfig) -> int:
|
|
48
|
+
"""Phase of *config*'s interval grid on the epoch clock (metric-derived).
|
|
49
|
+
|
|
50
|
+
The loader anchors the stored grid on ``loading_start_time``; the alert
|
|
51
|
+
step's exact-timestamp no-data lookup must floor to the same phase, so
|
|
52
|
+
this one seam feeds the orchestrator's ``get_last_complete_point`` — the
|
|
53
|
+
mirror of ``_loading_delay_seconds`` for the grid phase (issue #114).
|
|
54
|
+
"""
|
|
55
|
+
return resolve_grid_phase_seconds(
|
|
56
|
+
config.loading_start_time,
|
|
57
|
+
config.get_interval().seconds,
|
|
58
|
+
)
|
|
59
|
+
|
|
43
60
|
def _load_recent_detections(
|
|
44
61
|
self,
|
|
45
62
|
metric_name: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.56.1 → detectkit-0.56.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|