Files
managed-portal/managed/store_dwell_alert/app/config.py
skye.yue ea618fd674 Refactor store dwell alert management API and dwell engine
- Updated argument parsing in manage_api.py to include new threshold parameters.
- Enhanced _config_payload to include thresholds and webhook configurations.
- Modified _build_summary to track queue metrics and adjust alert reporting.
- Refactored DwellEngine to utilize queue thresholds for alerting and reporting.
- Added queue metrics calculations and status change tracking in dwell_engine.py.
- Updated notifier.py to support posting JSON events to webhooks.
- Adjusted example configuration to reflect new threshold parameters.
- Enhanced Docker entrypoint script for better process management.
- Updated tests to cover new queue metrics and thresholds.
- Improved ManagedServiceDetail and ManagedServices Vue components to display queue metrics.
2026-05-09 11:35:55 +08:00

115 lines
3.4 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
import yaml
@dataclass(slots=True)
class Thresholds:
queue_time_threshold_seconds: int = 300
crowded_count_threshold: int = 5
normal_count_threshold: int = 2
pause_timeout_seconds: int = 300
alert_cooldown_seconds: int = 600
@dataclass(slots=True)
class StreamConfig:
rtsp_url: str = ""
sample_fps: float = 2.0
reconnect_backoff_seconds: float = 5.0
@dataclass(slots=True)
class StaffConfig:
gallery_dir: str = "data/staff_gallery"
min_hits: int = 3
similarity_threshold: float = 0.9
@dataclass(slots=True)
class WebhookConfig:
url: str = ""
alert_url: str = ""
report_url: str = ""
timeout_seconds: float = 5.0
@dataclass(slots=True)
class EventSinkConfig:
path: str = "logs/events.jsonl"
@dataclass(slots=True)
class AppConfig:
camera_id: str
timezone: str = "Asia/Shanghai"
thresholds: Thresholds = field(default_factory=Thresholds)
stream: StreamConfig = field(default_factory=StreamConfig)
staff: StaffConfig = field(default_factory=StaffConfig)
event_sink: EventSinkConfig = field(default_factory=EventSinkConfig)
webhook: WebhookConfig = field(default_factory=WebhookConfig)
def _load_section(raw: dict, key: str, cls):
payload = dict(raw.get(key, {}))
if cls is Thresholds:
if (
"queue_time_threshold_seconds" not in payload
and "min_dwell_seconds" in payload
):
payload["queue_time_threshold_seconds"] = payload["min_dwell_seconds"]
if "crowded_count_threshold" not in payload and "min_people" in payload:
payload["crowded_count_threshold"] = payload["min_people"]
return cls(**payload)
def resolve_config_path(config_path: str | Path | None = None) -> Path:
if config_path is not None:
return Path(config_path).expanduser().resolve()
project_root = Path(__file__).resolve().parent.parent
local_path = project_root / "config" / "108.local.yaml"
if local_path.exists():
return local_path
return project_root / "config" / "config.example.yaml"
def resolve_project_root(config_path: str | Path) -> Path:
return Path(config_path).expanduser().resolve().parent.parent
def resolve_project_path(project_root: str | Path, raw_path: str | Path) -> Path:
path = Path(raw_path)
if path.is_absolute():
return path.resolve()
return (Path(project_root).resolve() / path).resolve()
def load_config_document(path: Path) -> dict:
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
def load_config(path: Path) -> AppConfig:
raw = load_config_document(path)
return AppConfig(
camera_id=raw["camera_id"],
timezone=raw.get("timezone", "Asia/Shanghai"),
thresholds=_load_section(raw, "thresholds", Thresholds),
stream=_load_section(raw, "stream", StreamConfig),
staff=_load_section(raw, "staff", StaffConfig),
event_sink=_load_section(raw, "event_sink", EventSinkConfig),
webhook=_load_section(raw, "webhook", WebhookConfig),
)
def save_config_document(path: Path, raw: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temp_path = path.with_suffix(path.suffix + ".tmp")
temp_path.write_text(
yaml.safe_dump(raw, allow_unicode=True, sort_keys=False),
encoding="utf-8",
)
temp_path.replace(path)