# 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()