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

265 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# process_course.py
from engine.engine_components.tp_config import load_tp_config_for_con, resolve_tp_for_lecture
from engine.engine_components.course_config import load_course_config
from engine.engine_components.engine_state import get_last_processed_read_id, update_last_processed_read_id
from engine.engine_components.lectures import get_new_lectures
from engine.engine_components.classement import get_classement_by_tag
from engine.engine_components.detail_writer import insert_detail_record
from engine.logger import log_info, log_debug
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
from decimal import Decimal
from engine.utils import format_temps_decimal
from engine.engine_components import regles
LOCAL_TZ = ZoneInfo("America/Toronto")
# -------------------------------------------------
# Normalisation du temps de course (start/end)
# -------------------------------------------------
def _normalize_course_time(dt):
if dt is None:
return None, None
dt_utc = dt.replace(tzinfo=timezone.utc)
dt_local = dt_utc
return dt_local, dt_utc
# -------------------------------------------------
# Moteur principal : traitement d'une course
# -------------------------------------------------
def process_course(cur, cnx, con_id: int):
tp_config = load_tp_config_for_con(cur, con_id)
course_cfg = load_course_config(cur, con_id)
raw_start = course_cfg["start_time"]
raw_end = course_cfg["end_time"]
start_local, start_utc = _normalize_course_time(raw_start)
end_local, end_utc = _normalize_course_time(raw_end)
engine_table = f"resultv2_{con_id}_lecture"
classement_table = f"resultv2_{con_id}_classement"
# -------------------------------------------------
# Pause
# -------------------------------------------------
cur.execute(f"SELECT process_status FROM `{engine_table}` LIMIT 1")
row = cur.fetchone()
status = (row["process_status"] or "").upper()
if status == "PAUSE_REQUEST":
cur.execute(f"UPDATE `{engine_table}` SET process_status = 'PAUSE'")
cnx.commit()
log_info(f"[PAUSE] con_id={con_id} — pause appliquée avant traitement.")
return
if status == "PAUSE":
log_info(f"[PAUSE] con_id={con_id} — course déjà en pause.")
return
# -------------------------------------------------
# Lire dernier ID traité
# -------------------------------------------------
last_processed = get_last_processed_read_id(cur, engine_table)
if last_processed is None:
last_processed = 0
log_info(f"[STATE] con_id={con_id} last_processed={last_processed}")
# -------------------------------------------------
# Récupère les lectures non traitées
# -------------------------------------------------
lectures = get_new_lectures(cur, last_processed)
log_info(f"[NEW LECTURES] con_id={con_id} count={len(lectures)}")
if not lectures:
return
max_id = last_processed
# -------------------------------------------------
# Boucle sur chaque lecture brute
# -------------------------------------------------
for lec in lectures:
rcourse_id = int(lec["rcourse_id"])
chr_loc = lec["chrinfo_location"]
timestamp = Decimal(lec["chrinfo_time"])
max_id = max(max_id, rcourse_id)
dt_utc = regles.ts_to_utc(timestamp)
dt_local = dt_utc.astimezone(LOCAL_TZ)
log_debug(f"[READ] id={rcourse_id} utc={dt_utc} loc={chr_loc}")
# -------------------------------------------------
# Validation start/end
# -------------------------------------------------
if start_utc and dt_utc < start_utc:
log_debug(f"[REJECT START] id={rcourse_id} dt_utc={dt_utc} < start_utc={start_utc}")
continue
if end_utc and dt_utc > end_utc:
log_debug(f"[REJECT END] id={rcourse_id} dt_utc={dt_utc} > end_utc={end_utc}")
continue
status = 1
reason_code = None
reason_ref_id = None
# -------------------------------------------------
# Vérifier BIB dans classement
# -------------------------------------------------
tag = lec["chrinfo_tag"]
coureur = get_classement_by_tag(cur, classement_table, tag)
if not coureur:
dossard = "???"
status = 0
reason_code = "UNKNOWN_BIB"
reason_ref_id = None
else:
dossard = coureur["cla_info_dossard"]
# -------------------------------------------------
# Résoudre timing point
# -------------------------------------------------
tp_info = resolve_tp_for_lecture(tp_config, con_id, chr_loc)
if not tp_info:
continue
tp_id = tp_info["tp_id"]
tp_name = tp_info["tp_name"]
prio = tp_info["priority"]
controller = tp_info["controller_name"]
det_id = tp_info["det_id"]
detail_table = f"resultv2_{con_id}_detail"
# -------------------------------------------------
# 🔥 INSÉRER IMMÉDIATEMENT DANS DETAILS 🔥
# -------------------------------------------------
detail_id=insert_detail_record(
cur, cnx, con_id,
dossard, lec,
tp_id, tp_name,
chr_loc, prio, controller,
dt_utc,
status=status,
reason_code=reason_code,
reason_ref_id=reason_ref_id,
tour=None,
tempstour=None,
tempstour_str=None
)
# 👉 Après ceci, TU CONTINUES TA LOGIQUE (priorité, intervalle, tours)
# mais la donnée brute est déjà sauvée.
# on traite pas les dossard ???
if dossard != "???":
# -------------------------------------------------
# Règle de priorité
# -------------------------------------------------
#status, reason_code, reason_ref_id, forced_tour = resolve_priority(
# cur, cnx, detail_table, tp_id, dossard, timestamp, prio, det_id, tp_config
#)
# -------------------------------------------------
# Règle d'intervalle
# -------------------------------------------------
interval_cfg = (
tp_config[tp_id]["config"]
.get("config_tp_interval", {})
.get("clef_temp_minimum_read_entre", [])
)
interval_sec = 0
if interval_cfg:
try:
interval_sec = int(interval_cfg[0]["value1"])
except:
interval_sec = 0
# Appel de la règle dintervalle
status_interval, rc_interval, rr_interval = regles.check_interval_rule(
cur,
detail_table,
tp_id,
dossard,
timestamp,
interval_sec,
det_id
)
# Si la règle dintervalle rejette
if status_interval == 0:
regles.update_detail_status_zero(
cur,
cnx,
detail_table,
detail_id, # <-- l'ID que tu as obtenu juste après insert_detail_record
rc_interval, # reason_code retourné par la règle
rr_interval # reason_ref_id retourné par la règle
)
# Et on arrête le traitement de cette lecture
continue
# -------------------------------------------------
# Calcul du tour + temps de tour
# -------------------------------------------------
# Appel de la règle tour
tour, tempstour,tempstour_str = regles.check_tour_rule(
cur,
detail_table,
tp_id,
dossard,
timestamp,
det_id
)
regles.update_detail(
cur,
cnx,
detail_table,
detail_id,
tour,
tempstour,
tempstour_str
)
if status == 1:
log_info(f"[OK] id={rcourse_id} tag={tag} dossard={dossard} tp={tp_name} prio={prio}")
else:
log_info(f"[dossard ???] id={rcourse_id} ")
# -------------------------------------------------
# Mise à jour du pointeur
# -------------------------------------------------
update_last_processed_read_id(cur, cnx, engine_table, max_id)