223 lines
7.7 KiB
Python
223 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
import yaml
|
|
|
|
INPUTS_DIR = Path("data/inputs")
|
|
DOMAINS_FILE = Path("domains.yaml")
|
|
|
|
|
|
def utc_now_iso() -> str:
|
|
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
|
|
def utc_today_date() -> str:
|
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
|
|
|
|
def domain_file(domain: str) -> Path:
|
|
return INPUTS_DIR / f"{domain}.yaml"
|
|
|
|
|
|
def load_domain(domain: str) -> dict:
|
|
path = domain_file(domain)
|
|
if not path.exists():
|
|
return {}
|
|
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
if data is None:
|
|
return {}
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f"Fichier invalide: {path}")
|
|
return data
|
|
|
|
|
|
def save_domain(domain: str, data: dict) -> Path:
|
|
path = domain_file(domain)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
yaml.safe_dump(data, sort_keys=False, allow_unicode=True),
|
|
encoding="utf-8",
|
|
)
|
|
return path
|
|
|
|
|
|
def list_items(domain: str) -> list[str]:
|
|
return list(load_domain(domain).keys())
|
|
|
|
|
|
def get_item(domain: str, item_key: str) -> dict:
|
|
data = load_domain(domain)
|
|
if item_key not in data:
|
|
raise KeyError(item_key)
|
|
item = data[item_key]
|
|
if not isinstance(item, dict):
|
|
raise ValueError(f"Entrée invalide pour {item_key}")
|
|
return item
|
|
|
|
|
|
def set_item(domain: str, item_key: str, value: str, origin: str = "manual") -> dict:
|
|
data = load_domain(domain)
|
|
data[item_key] = {
|
|
"value": value,
|
|
"captured_at": utc_now_iso(),
|
|
"origin": origin,
|
|
}
|
|
save_domain(domain, data)
|
|
return data[item_key]
|
|
|
|
|
|
def set_counter_pair(domain: str, item_key: str, current_value: str, reference_value: str, origin: str = "manual") -> dict:
|
|
data = load_domain(domain)
|
|
data[item_key] = {
|
|
"value": current_value,
|
|
"reference_value": reference_value,
|
|
"captured_at": utc_now_iso(),
|
|
"origin": origin,
|
|
}
|
|
save_domain(domain, data)
|
|
return data[item_key]
|
|
|
|
|
|
def complete_item(domain: str, item_key: str, origin: str = "manual") -> dict:
|
|
return set_item(domain, item_key, utc_today_date(), origin=origin)
|
|
|
|
|
|
def load_domains_definition() -> dict:
|
|
if not DOMAINS_FILE.exists():
|
|
raise ValueError(f"Fichier introuvable: {DOMAINS_FILE}")
|
|
data = yaml.safe_load(DOMAINS_FILE.read_text(encoding="utf-8"))
|
|
if not isinstance(data, dict):
|
|
raise ValueError("domains.yaml invalide")
|
|
domains = data.get("domains")
|
|
if not isinstance(domains, dict):
|
|
raise ValueError("domains.yaml invalide: clé 'domains' absente ou invalide")
|
|
return domains
|
|
|
|
|
|
def get_defined_service(domain: str, item_key: str) -> dict:
|
|
domains = load_domains_definition()
|
|
items = domains.get(domain)
|
|
if not isinstance(items, list):
|
|
raise KeyError(domain)
|
|
|
|
for item in items:
|
|
if isinstance(item, dict) and item.get("name") == item_key:
|
|
return item
|
|
|
|
raise KeyError(item_key)
|
|
|
|
|
|
def compute_state_for_item(domain: str, item_key: str) -> str:
|
|
service = get_defined_service(domain, item_key)
|
|
probe = service.get("probe")
|
|
if not isinstance(probe, dict):
|
|
return "MOCK"
|
|
|
|
probe_type = str(probe.get("type", "")).strip()
|
|
source = probe.get("source", {})
|
|
thresholds = probe.get("thresholds", {})
|
|
metric = probe.get("metric", {})
|
|
source_type = str(source.get("type", "")).strip()
|
|
metric_unit = str(metric.get("unit", "")).strip()
|
|
|
|
item = get_item(domain, item_key)
|
|
|
|
if probe_type in {"elapsed_time", "days_until_due"}:
|
|
if source_type != "manual_date":
|
|
return "CRITICAL"
|
|
if metric_unit != "days":
|
|
return "CRITICAL"
|
|
|
|
value = str(item.get("value", "")).strip()
|
|
dt = datetime.strptime(value, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
|
now = datetime.now(timezone.utc)
|
|
|
|
if probe_type == "elapsed_time":
|
|
days = (now - dt).total_seconds() / 86400.0
|
|
if "critical_gte" in thresholds and days >= float(thresholds["critical_gte"]):
|
|
return "CRITICAL"
|
|
if "warning_gte" in thresholds and days >= float(thresholds["warning_gte"]):
|
|
return "WARNING"
|
|
if "ok_gte" in thresholds and days >= float(thresholds["ok_gte"]):
|
|
return "OK"
|
|
if "unknown_lt" in thresholds and days < float(thresholds["unknown_lt"]):
|
|
return "UNKNOWN"
|
|
return "CRITICAL"
|
|
|
|
days = (dt - now).total_seconds() / 86400.0
|
|
if "critical_lt" in thresholds and days < float(thresholds["critical_lt"]):
|
|
return "CRITICAL"
|
|
if "warning_lt" in thresholds and days < float(thresholds["warning_lt"]):
|
|
return "WARNING"
|
|
if "ok_lt" in thresholds and days < float(thresholds["ok_lt"]):
|
|
return "OK"
|
|
if "unknown_gte" in thresholds and days >= float(thresholds["unknown_gte"]):
|
|
return "UNKNOWN"
|
|
return "CRITICAL"
|
|
|
|
if probe_type == "elapsed_distance":
|
|
if source_type != "manual_counter":
|
|
return "CRITICAL"
|
|
if metric_unit != "km":
|
|
return "CRITICAL"
|
|
|
|
current_value = float(str(item.get("value", "")).strip())
|
|
reference_value = float(str(item.get("reference_value", "")).strip())
|
|
distance = current_value - reference_value
|
|
|
|
if "unknown_lt" in thresholds and distance < float(thresholds["unknown_lt"]):
|
|
return "UNKNOWN"
|
|
if "critical_gte" in thresholds and distance >= float(thresholds["critical_gte"]):
|
|
return "CRITICAL"
|
|
if "warning_gte" in thresholds and distance >= float(thresholds["warning_gte"]):
|
|
return "WARNING"
|
|
if "ok_gte" in thresholds and distance >= float(thresholds["ok_gte"]):
|
|
return "OK"
|
|
return "CRITICAL"
|
|
|
|
if probe_type == "remaining_quantity":
|
|
if source_type != "manual_value":
|
|
return "CRITICAL"
|
|
if metric_unit not in {"%", "units", "litres"}:
|
|
return "CRITICAL"
|
|
|
|
value = float(str(item.get("value", "")).strip())
|
|
|
|
if "critical_lt" in thresholds and value < float(thresholds["critical_lt"]):
|
|
return "CRITICAL"
|
|
if "warning_lt" in thresholds and value < float(thresholds["warning_lt"]):
|
|
return "WARNING"
|
|
if "ok_gte" in thresholds and value >= float(thresholds["ok_gte"]):
|
|
return "OK"
|
|
if "unknown_gte" in thresholds and value >= float(thresholds["unknown_gte"]):
|
|
return "UNKNOWN"
|
|
return "CRITICAL"
|
|
|
|
if probe_type == "current_value":
|
|
if source_type != "manual_value":
|
|
return "CRITICAL"
|
|
if metric_unit not in {"%", "units", "litres", "volts", "watts"}:
|
|
return "CRITICAL"
|
|
|
|
value = float(str(item.get("value", "")).strip())
|
|
|
|
if "critical_lt" in thresholds and value < float(thresholds["critical_lt"]):
|
|
return "CRITICAL"
|
|
if "critical_gte" in thresholds and value >= float(thresholds["critical_gte"]):
|
|
return "CRITICAL"
|
|
if "warning_lt" in thresholds and value < float(thresholds["warning_lt"]):
|
|
return "WARNING"
|
|
if "warning_gte" in thresholds and value >= float(thresholds["warning_gte"]):
|
|
return "WARNING"
|
|
if "ok_gte" in thresholds and value >= float(thresholds["ok_gte"]):
|
|
return "OK"
|
|
if "ok_lt" in thresholds and value < float(thresholds["ok_lt"]):
|
|
return "OK"
|
|
if "unknown_gte" in thresholds and value >= float(thresholds["unknown_gte"]):
|
|
return "UNKNOWN"
|
|
if "unknown_lt" in thresholds and value < float(thresholds["unknown_lt"]):
|
|
return "UNKNOWN"
|
|
return "CRITICAL"
|
|
|
|
return "CRITICAL"
|