SIENTIAPDE-2212
SIENTIAPDE-2212-drift-janela-e-chunk-period: Add configurable chunk period and dynamic window calculation for drift, with warnings for invalid periods.
This commit is contained in:
@@ -19,6 +19,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
from orchestrator.utils.orchestrator_functions import (
|
||||
build_tag_config,
|
||||
chunk_period_warning,
|
||||
drift,
|
||||
frequency_alert_warning,
|
||||
gather_read_tags,
|
||||
@@ -371,7 +372,9 @@ class Formatters(SientiaMonitoring):
|
||||
configuration to determine which schedules need to be created, updated, or deleted.
|
||||
Uses timestamp comparison to identify schedules that have changed.
|
||||
Also sends a SIMPLE_METRICS_FREQUENCY_ALERT_BELOW_FLOOR WARNING for laborious schedules
|
||||
whose frequency_alert is below 1m (the value is kept) or unparseable (it is ignored).
|
||||
whose frequency_alert is below 1m (the value is kept) or unparseable (it is ignored),
|
||||
and a DRIFT_CHUNK_PERIOD_INVALID WARNING for drift schedules whose configured
|
||||
chunk_period is not supported by laborious (replaced by 'min').
|
||||
|
||||
Args:
|
||||
input_data (dict[str, Any]): The input data containing:
|
||||
@@ -430,6 +433,20 @@ class Formatters(SientiaMonitoring):
|
||||
block='create_schedule_config',
|
||||
level=NotificationLevel.WARNING,
|
||||
)
|
||||
warning = chunk_period_warning(schedule.get('chunk_period_configured'))
|
||||
if warning:
|
||||
self.send_notification(
|
||||
metadata={
|
||||
**metadata,
|
||||
'schedule_name': schedule_name,
|
||||
'model_id': schedule.get('model_id'),
|
||||
'model_name': schedule.get('model_name'),
|
||||
},
|
||||
notification_id='DRIFT_CHUNK_PERIOD_INVALID',
|
||||
message=warning,
|
||||
block='create_schedule_config',
|
||||
level=NotificationLevel.WARNING,
|
||||
)
|
||||
|
||||
for namespace, schedules in current_schedule_config.items():
|
||||
for schedule_name in schedules:
|
||||
|
||||
@@ -220,6 +220,9 @@ class TemporalManager(SientiaMonitoring):
|
||||
task_queue_name = self._build_task_queue_name(workflow_type, schedule)
|
||||
schedule['task_queue'] = task_queue_name
|
||||
|
||||
# No `policy`: schedules rely on Temporal's default SKIP overlap. The
|
||||
# laborious `DriftAnalysis` cache assumes at most one running drift
|
||||
# workflow per model; revisit its locking if a policy is ever added here.
|
||||
await client.create_schedule(
|
||||
schedule_name,
|
||||
Schedule(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from math import ceil
|
||||
from typing import Any
|
||||
|
||||
from orchestrator.utils.converters import parse_frequency
|
||||
@@ -43,6 +44,28 @@ def common_config(config: dict[str, Any]):
|
||||
}
|
||||
|
||||
|
||||
# Kept in sync by hand with the `chunk_period` validation in
|
||||
# sientia-dataops-laborious/laborious/activities/model_metrics.py (no shared contract).
|
||||
DRIFT_CHUNK_PERIODS = ('min', 's')
|
||||
# Re-reads the tail of the previous window so rows ingested up to 1 minute late still count.
|
||||
DRIFT_WINDOW_MARGIN_MINUTES = 1
|
||||
|
||||
|
||||
def _drift_interval_minutes(config: dict[str, Any], frequency: Any) -> int:
|
||||
"""
|
||||
Drift window in minutes: `interval_minutes` as configured (no margin), otherwise the
|
||||
schedule frequency rounded up to whole minutes (min 1) plus DRIFT_WINDOW_MARGIN_MINUTES.
|
||||
"""
|
||||
if 'interval_minutes' in config:
|
||||
return config['interval_minutes']
|
||||
try:
|
||||
seconds = parse_frequency(str(frequency))
|
||||
except ValueError:
|
||||
# create_schedules fails this schedule on its own; don't kill the tick for every model.
|
||||
return 60
|
||||
return max(1, ceil(seconds / 60)) + DRIFT_WINDOW_MARGIN_MINUTES
|
||||
|
||||
|
||||
def drift(config: dict[str, Any]):
|
||||
"""
|
||||
Build drift configuration from pipeline config.
|
||||
@@ -52,25 +75,52 @@ def drift(config: dict[str, Any]):
|
||||
|
||||
Args:
|
||||
config (dict[str, Any]): Pipeline configuration containing:
|
||||
- interval_minutes (int, optional): Time window for data comparison in minutes (default: 60)
|
||||
- interval_minutes (int, optional): Time window for data comparison in minutes,
|
||||
used as-is (no margin). Default: `frequency` rounded up to whole minutes
|
||||
(min 1) plus a 1-minute margin for late data (2 with the default '1m').
|
||||
Pipelines whose ingestion lag exceeds 1 minute should set it explicitly
|
||||
(frequency + lag); `interval_minutes: 60` keeps the legacy window.
|
||||
- chunk_period (str, optional): Drift bucket size, 'min' or 's' (default: 'min').
|
||||
Any other value falls back to 'min' and create_schedule_config warns.
|
||||
- drift_metrics (list[str], optional): Statistical metrics to compute (default:
|
||||
['kolmogorov_smirnov', 'jensen_shannon', 'wasserstein'])
|
||||
- Additional fields from common_config
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Drift detection configuration with source/target tables,
|
||||
time interval, and metrics specifications. Results stored in 'drift_metrics' table
|
||||
time interval, chunk period and metrics specifications. Results stored in
|
||||
'drift_metrics' table
|
||||
"""
|
||||
return {
|
||||
**common_config(config),
|
||||
base = common_config(config)
|
||||
configured_chunk_period = config.get('chunk_period')
|
||||
chunk_period_valid = configured_chunk_period in DRIFT_CHUNK_PERIODS
|
||||
output = {
|
||||
**base,
|
||||
'schema': 'sientia_data',
|
||||
'source_table_name': 'laborious_data',
|
||||
'target_table_name': 'drift_metrics',
|
||||
'interval': config.get('interval_minutes', 60),
|
||||
'interval': _drift_interval_minutes(config, base['frequency']),
|
||||
'chunk_period': configured_chunk_period if chunk_period_valid else 'min',
|
||||
'drift_metrics': config.get(
|
||||
'drift_metrics', ['kolmogorov_smirnov', 'jensen_shannon', 'wasserstein']
|
||||
),
|
||||
}
|
||||
if configured_chunk_period is not None and not chunk_period_valid:
|
||||
# Forwarded so create_schedule_config can warn without re-reading the pipeline.
|
||||
output['chunk_period_configured'] = configured_chunk_period
|
||||
return output
|
||||
|
||||
|
||||
def chunk_period_warning(chunk_period: Any) -> str | None:
|
||||
"""
|
||||
Warning message when a configured drift `chunk_period` is not supported by laborious,
|
||||
otherwise None. Warn only: `drift()` already replaced it with 'min'.
|
||||
"""
|
||||
if chunk_period is None or chunk_period in DRIFT_CHUNK_PERIODS:
|
||||
return None
|
||||
return (
|
||||
f"chunk_period={chunk_period!r} is not one of {DRIFT_CHUNK_PERIODS}; falling back to 'min'"
|
||||
)
|
||||
|
||||
|
||||
def simple_metrics(config: dict[str, Any]):
|
||||
|
||||
Reference in New Issue
Block a user