diff --git a/a faire b/a faire new file mode 100644 index 0000000..af7601d --- /dev/null +++ b/a faire @@ -0,0 +1,15 @@ +Notes pour correction du moteur Python + +Les priorités ne fonctionnent pas. +La logique actuelle doit être revue. + +La validation des intervalles est mal faite. +Elle doit être basée directement sur l’heure pour obtenir un rapprochement plus uniforme. + +Les tours ne fonctionnent pas. +Le calcul du tour et du temps de tour doit être revu complètement. + +Concept général à corriger : +Lorsqu’une entrée est jugée « bonne », on doit l’écrire immédiatement dans la base de données, +avant d’appliquer n’importe quelle autre validation. +Parce que ces lectures doivent être conservées quoi qu’il arrive. \ No newline at end of file diff --git a/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc b/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc index ea65716..650ea1f 100644 Binary files a/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc and b/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc differ diff --git a/engine/engine_components/__pycache__/process_course.cpython-313.pyc b/engine/engine_components/__pycache__/process_course.cpython-313.pyc index 32db8f9..5d5a273 100644 Binary files a/engine/engine_components/__pycache__/process_course.cpython-313.pyc and b/engine/engine_components/__pycache__/process_course.cpython-313.pyc differ diff --git a/engine/engine_components/__pycache__/regles.cpython-313.pyc b/engine/engine_components/__pycache__/regles.cpython-313.pyc index c9e9e82..8f94770 100644 Binary files a/engine/engine_components/__pycache__/regles.cpython-313.pyc and b/engine/engine_components/__pycache__/regles.cpython-313.pyc differ diff --git a/engine/engine_components/detail_writer.py b/engine/engine_components/detail_writer.py index 69989ca..951ad89 100644 --- a/engine/engine_components/detail_writer.py +++ b/engine/engine_components/detail_writer.py @@ -87,7 +87,7 @@ def insert_detail_record( try: cur.execute(sql, data) cnx.commit() - + return cur.lastrowid except mysql.connector.IntegrityError as e: # Duplicate key = lecture déjà traitée → on ignore et on continue if e.errno == 1062: diff --git a/engine/engine_components/process_course.py b/engine/engine_components/process_course.py index e5513b0..5fbb42d 100644 --- a/engine/engine_components/process_course.py +++ b/engine/engine_components/process_course.py @@ -11,19 +11,15 @@ from zoneinfo import ZoneInfo from decimal import Decimal from engine.utils import format_temps_decimal from engine.engine_components.regles import resolve_priority +from engine.engine_components.regles import check_interval_rule +from engine.engine_components.regles import get_last_valid_lecture_time +from engine.engine_components.regles import ts_to_utc +from engine.engine_components.regles import update_detail_status_zero LOCAL_TZ = ZoneInfo("America/Toronto") -# ------------------------------------------------- -# 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) - + # ------------------------------------------------- # Normalisation du temps de course (start/end) @@ -128,9 +124,12 @@ def process_course(cur, cnx, con_id: int): coureur = get_classement_by_tag(cur, classement_table, tag) if not coureur: - continue - - dossard = coureur["cla_info_dossard"] + dossard = "???" + status = 0 + reason_code = "UNKNOWN_BIB" + reason_ref_id = None + else: + dossard = coureur["cla_info_dossard"] # ------------------------------------------------- # Résoudre timing point @@ -148,77 +147,9 @@ def process_course(cur, cnx, con_id: int): detail_table = f"resultv2_{con_id}_detail" # ------------------------------------------------- - # Règle de priorité + # 🔥 INSÉRER IMMÉDIATEMENT DANS DETAILS 🔥 # ------------------------------------------------- - status, reason_code, reason_ref_id, forced_tour = resolve_priority( - cur, cnx, detail_table, tp_id, dossard, timestamp, prio, det_id, tp_config - ) - - if status == 0: - 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 - ) - continue - - # ------------------------------------------------- - # 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 - - last_dt = get_last_valid_lecture_time(cur, detail_table, tp_id, dossard) - - if last_dt and interval_sec > 0: - delta = (dt_utc - last_dt).total_seconds() - - if delta < interval_sec: - status = 0 - reason_code = f"SKIP_INTERVAL {delta:.1f}s < {interval_sec}s" - reason_ref_id = det_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 - ) - continue - - # ------------------------------------------------- - # Calcul tour (placeholder) - # ------------------------------------------------- - tour = 1 - tempstour = Decimal("0") - tempstour_str = "0" - - insert_detail_record( + detail_id=insert_detail_record( cur, cnx, con_id, dossard, lec, tp_id, tp_name, @@ -227,14 +158,122 @@ def process_course(cur, cnx, con_id: int): status=status, reason_code=reason_code, reason_ref_id=reason_ref_id, - tour=tour, - tempstour=tempstour, - tempstour_str=tempstour_str + tour=None, + tempstour=None, + tempstour_str=None ) - if status == 1: - log_info(f"[OK] id={rcourse_id} tag={tag} dossard={dossard} tp={tp_name} prio={prio}") + # 👉 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 d’intervalle + status_interval, rc_interval, rr_interval = check_interval_rule( + cur, + detail_table, + tp_id, + dossard, + timestamp, + interval_sec, + det_id + + ) + + # Si la règle d’intervalle rejette + if status_interval == 0: + 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 + status_tour = check_tour_rule( + cur, + detail_table, + tp_id, + dossard, + timestamp, + det_id + + ) + + # Si la règle d’tour rejette + if status_interval == 0: + 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 + + + + + + + #tour, prev_ts = compute_tour(cur, detail_table, tp_id, dossard) + + #if forced_tour is not None: + # tour = forced_tour + + #if prev_ts is None: + # tempstour = Decimal("0") + #else: + # tempstour = timestamp - prev_ts + + #tempstour_str = format_temps_decimal(tempstour) + + + + + 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 # ------------------------------------------------- @@ -242,25 +281,4 @@ def process_course(cur, cnx, con_id: int): -# ------------------------------------------------- -# Dernière lecture valide -# ------------------------------------------------- -def get_last_valid_lecture_time(cur, detail_table, tp_id, dossard): - - cur.execute(f""" - SELECT timestamp_read - FROM `{detail_table}` - WHERE tp_id = %s - AND cla_info_dossard = %s - AND status = 1 - ORDER BY detail_id DESC - LIMIT 1 - """, (tp_id, dossard)) - - row = cur.fetchone() - - if not row: - return None - - ts_decimal = Decimal(str(row["timestamp_read"])) - return ts_to_utc(ts_decimal) + \ No newline at end of file diff --git a/engine/engine_components/regles.py b/engine/engine_components/regles.py index fdf6d36..7c8c72d 100644 --- a/engine/engine_components/regles.py +++ b/engine/engine_components/regles.py @@ -1,5 +1,7 @@ # regles.py from engine.logger import log_info, log_debug +from decimal import Decimal +from datetime import datetime, timezone # ------------------------------------------------- # Règle de priorité : remplace, rejette, ou force tour # ------------------------------------------------- @@ -94,4 +96,126 @@ def resolve_priority( # Même priorité → rejet - return 0, "EQUAL_PRIORITY", det_id, None \ No newline at end of file + 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 = 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 + 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)) + log_debug( + f"SELECT timestamp_read FROM `{detail_table}` " + f"WHERE tp_id = {tp_id} AND cla_info_dossard = '{dossard}' " + f"AND status = 1 AND timestamp_read < {current_ts} " + f"ORDER BY timestamp_read DESC LIMIT 1;" +) + row = cur.fetchone() + log_debug(f"[get_last_valid_lecture_time] result={row}") + if not row: + return None + + return Decimal(str(row["timestamp_read"])) + + +# ------------------------------------------------- +# 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 + + ): + + + cur.execute(f""" + SELECT tour, timestamp_read + FROM `{detail_table}` + WHERE tp_id = %s + AND cla_info_dossard = %s + AND status = 1 + ORDER BY detail_id DESC + LIMIT 1 + """, (tp_id, dossard)) + + row = cur.fetchone() + + if not row or row["tour"] is None: + return 1, None + + last_tour = row["tour"] + prev_ts = Decimal(str(row["timestamp_read"])) + + return last_tour + 1, prev_ts + + +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() \ No newline at end of file