Files
ms1reader-engine/engine/engine_components/regles.py
2025-12-12 10:49:13 -05:00

245 lines
6.8 KiB
Python

# regles.py
from engine.logger import log_info, log_debug
from decimal import Decimal
from datetime import datetime, timezone
from engine.utils import format_temps_decimal
# -------------------------------------------------
# Règle de priorité : remplace, rejette, ou force tour
# -------------------------------------------------
def resolve_priority(
cur,
cnx,
detail_table,
tp_id,
dossard,
new_timestamp,
new_priority,
det_id,
tp_config
):
# -------------------------------------------------
# Lecture de la fenêtre backup pour priorité
# -------------------------------------------------
cfg = (
tp_config[tp_id]["config"]
.get("config_tp", {})
.get("clef_temp_bbk", [])
)
backup_window = 0
if cfg:
try:
backup_window = int(cfg[0]["value1"])
except:
backup_window = 0
log_debug(f"[resolve_priority] backup_window={backup_window}")
# -------------------------------------------------
# Si backup_window = 0 → toujours accepter
# -------------------------------------------------
if backup_window <= 0:
return 1, None, None, None
# -------------------------------------------------
# Recherche d'une lecture existante dans la fenêtre
# -------------------------------------------------
cur.execute(f"""
SELECT detail_id, controller_priority, timestamp_read, tour
FROM `{detail_table}`
WHERE tp_id = %s
AND cla_info_dossard = %s
AND status = 1
AND ABS(timestamp_read - %s) <= %s
LIMIT 1
""", (tp_id, dossard, str(new_timestamp), backup_window))
row = cur.fetchone()
# -------------------------------------------------
# Aucun doublon → accepter
# -------------------------------------------------
if not row:
return 1, None, None, None
old_id = row["detail_id"]
old_priority = row["controller_priority"]
old_tour = row["tour"]
# -------------------------------------------------
# Comparaison des priorités
# -------------------------------------------------
# Nouvelle lecture moins prioritaire → rejet
if new_priority > old_priority:
return 0, "LOWER_PRIORITY", det_id, None
# Nouvelle lecture plus prioritaire → remplacer
if new_priority < old_priority:
cur.execute(f"""
UPDATE `{detail_table}`
SET
status = 0,
reason_code = 'REPLACED_BY_HIGHER_PRIORITY',
reason_ref_id = %s
WHERE detail_id = %s
""", (det_id, old_id))
cnx.commit()
forced_tour = old_tour
return 1, None, None, forced_tour
# Même priorité → rejet
return 0, "EQUAL_PRIORITY", det_id, None
# -------------------------------------------------
# Regle interval
# ------------------------------------------------
def check_interval_rule(cur, detail_table, tp_id, dossard, timestamp, interval_sec, det_id):
"""
Retourne :
status (1 ou 0)
reason_code
reason_ref_id
"""
log_debug(f"[INTERVAL] tp_id={tp_id} dossard={dossard} interval_sec={interval_sec}")
last_timestamp, last_tour = get_last_valid_lecture_time(cur, detail_table, tp_id, dossard, timestamp)
log_debug(f"[INTERVAL] last_valid={last_timestamp} current={timestamp}")
if last_timestamp and interval_sec > 0:
delta = (timestamp - last_timestamp)
log_debug(f"[INTERVAL] delta={delta:.3f}s")
if delta < interval_sec:
reason = f"SKIP_INTERVAL {delta:.1f}s < {interval_sec}s"
log_debug(
f"[INTERVAL] REJECT dossard={dossard} tp_id={tp_id} "
f"reason='{reason}' det_id={det_id}"
)
return (0, reason, det_id)
log_debug(f"[INTERVAL] OK dossard={dossard} tp_id={tp_id}")
return (1, None, None)
# -------------------------------------------------
# Dernière lecture valide (timestamp_read < T)
# -------------------------------------------------
def get_last_valid_lecture_time(cur, detail_table, tp_id, dossard, current_ts):
log_debug(f"[get_last_valid_lecture_time] tp={tp_id} dossard={dossard} current={current_ts}")
cur.execute(f"""
SELECT timestamp_read, tour
FROM `{detail_table}`
WHERE tp_id = %s
AND cla_info_dossard = %s
AND status = 1
AND timestamp_read < %s
ORDER BY timestamp_read DESC
LIMIT 1
""", (tp_id, dossard, current_ts))
row = cur.fetchone()
log_debug(f"[get_last_valid_lecture_time] result={row}")
if not row:
return None, None
return (
Decimal(str(row["timestamp_read"])),
row["tour"]
)
# -------------------------------------------------
# Conversion d'un timestamp Decimal en datetime UTC
# -------------------------------------------------
def ts_to_utc(ts):
ts = Decimal(str(ts))
seconds = int(ts)
micro = int((ts - Decimal(seconds)) * Decimal(1_000_000))
return datetime.fromtimestamp(seconds, tz=timezone.utc).replace(microsecond=micro)
# -------------------------------------------------
# Recherche du dernier tour pour un coureur à un TP
# -------------------------------------------------
#def compute_tour(cur, detail_table, tp_id, dossard):
def check_tour_rule(
cur,
detail_table,
tp_id,
dossard,
timestamp,
det_id
):
last_timestamp, last_tour = get_last_valid_lecture_time(
cur, detail_table, tp_id, dossard, timestamp
)
if last_tour is None:
last_tour=0
if last_timestamp is None:
last_timestamp = Decimal("0")
tour=last_tour+1;
tempstour = timestamp - last_timestamp
tempstour_str = format_temps_decimal(tempstour)
return tour, tempstour,tempstour_str
def update_detail(
cur,
cnx,
detail_table,
detail_id,
tour,
tempstour,
tempstour_str
):
cur.execute(f"""
UPDATE `{detail_table}`
SET tour = %s,
tempstour = %s,
tempstour_str = %s,
reason_code = NULL,
reason_ref_id = NULL
WHERE detail_id = %s
""", (
tour,
tempstour,
tempstour_str,
detail_id
))
cnx.commit()
def update_detail_status_zero(cur, cnx, detail_table, detail_id, reason_code, reason_ref_id):
"""
Met à jour un enregistrement Detail existant pour le passer en status=0,
avec reason_code et reason_ref_id.
"""
cur.execute(f"""
UPDATE `{detail_table}`
SET status = 0,
reason_code = %s,
reason_ref_id = %s
WHERE detail_id = %s
""", (reason_code, reason_ref_id, detail_id))
cnx.commit()