342 lines
10 KiB
Python
342 lines
10 KiB
Python
"""
|
||
Moteur de résultats – Version TP + DETAIL brut
|
||
----------------------------------------------
|
||
Étapes couvertes :
|
||
1) Lire les courses actives (reader_config).
|
||
2) Charger en mémoire la configuration des timing points / contrôleurs
|
||
à partir de reader_config_timingpoint + reader_config_detail
|
||
(clef = 'clef_controleur').
|
||
3) Pour chaque course active :
|
||
- lire resultv2_<con_id>_lecture.last_processed_read_id
|
||
- lire les nouvelles lectures dans reader_lectures
|
||
- associer chaque lecture à un coureur (cla_list_tag) et à un TP
|
||
(via chrinfo_location → config_detail)
|
||
- écrire une ligne brute dans resultv2_<con_id>_detail
|
||
avec con_id, dossard, tp_id, tp_name, chrinfo_location, priorité, etc.
|
||
"""
|
||
|
||
from typing import Optional, Dict, List
|
||
from mysql.connector import Error
|
||
from engine.db import get_connection
|
||
|
||
BATCH_SIZE = 500
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 1) COURSES ACTIVES
|
||
# ---------------------------------------------------------
|
||
def get_active_con_ids(cur) -> List[int]:
|
||
"""Retourne tous les con_id actifs."""
|
||
sql = "SELECT con_id FROM reader_config WHERE rea_active = 1"
|
||
cur.execute(sql)
|
||
rows = cur.fetchall()
|
||
return [int(r["con_id"]) for r in rows]
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 2) CHARGER LA CONFIG TP / CONTRÔLEURS EN MÉMOIRE
|
||
# ---------------------------------------------------------
|
||
def load_tp_config(cur) -> Dict[str, List[Dict]]:
|
||
"""
|
||
Charge la configuration des timing points et contrôleurs.
|
||
|
||
On lit :
|
||
- reader_config_timingpoint (tp_id, con_id, tp_name)
|
||
- reader_config_detail (det_type=1, det_clef='clef_controleur')
|
||
|
||
On retourne un dict :
|
||
clé = chrinfo_location (ex: 'PRO5655')
|
||
valeur= liste de dicts { con_id, tp_id, tp_name, priority }
|
||
"""
|
||
sql = """
|
||
SELECT
|
||
tp.tp_id,
|
||
tp.con_id,
|
||
tp.tp_name,
|
||
d.det_value1 AS chr_location,
|
||
d.det_value2 AS priority
|
||
FROM reader_config_timingpoint tp
|
||
JOIN reader_config_detail d
|
||
ON d.det_type = 1
|
||
AND d.det_ref1_id = tp.tp_id
|
||
AND d.det_clef = 'clef_controleur'
|
||
"""
|
||
cur.execute(sql)
|
||
rows = cur.fetchall()
|
||
|
||
tp_map: Dict[str, List[Dict]] = {}
|
||
|
||
for row in rows:
|
||
loc = row["chr_location"]
|
||
if loc not in tp_map:
|
||
tp_map[loc] = []
|
||
# normaliser la priorité (det_value2) → int, défaut 1
|
||
try:
|
||
prio = int(row["priority"]) if row["priority"] is not None else 1
|
||
except ValueError:
|
||
prio = 1
|
||
|
||
tp_map[loc].append(
|
||
{
|
||
"con_id": int(row["con_id"]),
|
||
"tp_id": int(row["tp_id"]),
|
||
"tp_name": row["tp_name"],
|
||
"priority": prio,
|
||
}
|
||
)
|
||
|
||
# DEBUG : afficher ce qu'on a chargé
|
||
print("[DEBUG] TP config chargée :")
|
||
if not tp_map:
|
||
print(" (aucune ligne trouvée dans reader_config_detail avec det_clef='clef_controleur')")
|
||
else:
|
||
for loc, items in tp_map.items():
|
||
descs = [
|
||
f"con_id={it['con_id']} tp_id={it['tp_id']} "
|
||
f"tp_name={it['tp_name']} prio={it['priority']}"
|
||
for it in items
|
||
]
|
||
print(f" location={loc} -> " + " | ".join(descs))
|
||
|
||
return tp_map
|
||
|
||
|
||
def resolve_tp_for_lecture(tp_map: Dict[str, List[Dict]], con_id: int, chr_location: str) -> Optional[Dict]:
|
||
"""
|
||
À partir de chrinfo_location, trouve le TP applicable pour cette course.
|
||
|
||
- On cherche dans tp_map[chr_location]
|
||
- On filtre sur con_id
|
||
- S'il y a plusieurs TP possibles, on prend la meilleure priorité (plus petit chiffre).
|
||
"""
|
||
if chr_location not in tp_map:
|
||
return None
|
||
|
||
candidates = [tp for tp in tp_map[chr_location] if tp["con_id"] == con_id]
|
||
if not candidates:
|
||
return None
|
||
|
||
# Choisir la meilleure priorité
|
||
best = sorted(candidates, key=lambda t: t["priority"])[0]
|
||
return best
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 3) LECTURE DE L'ÉTAT MOTEUR
|
||
# ---------------------------------------------------------
|
||
def get_last_processed_read_id(cur, engine_table: str) -> Optional[int]:
|
||
sql = f"SELECT last_processed_read_id FROM `{engine_table}` WHERE id = 1"
|
||
cur.execute(sql)
|
||
row = cur.fetchone()
|
||
return int(row["last_processed_read_id"]) if row else None
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 4) LECTURES BRUTES
|
||
# ---------------------------------------------------------
|
||
def get_new_lectures(cur, last_processed: int):
|
||
"""Lit les nouvelles lectures à partir de rcourse_id."""
|
||
sql = """
|
||
SELECT rcourse_id, chrinfo_tag, chrinfo_time, chrinfo_location
|
||
FROM reader_lectures
|
||
WHERE rcourse_id > %s
|
||
ORDER BY rcourse_id ASC
|
||
LIMIT %s
|
||
"""
|
||
cur.execute(sql, (last_processed, BATCH_SIZE))
|
||
return cur.fetchall()
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 5) MATCH TAG → COUREUR
|
||
# ---------------------------------------------------------
|
||
def get_classement_by_tag(cur, classement_table: str, tag: str) -> Optional[Dict]:
|
||
"""
|
||
Trouve le coureur correspondant à un tag RFID.
|
||
Retourne cla_info_dossard (ID officiel du coureur).
|
||
"""
|
||
pattern = f"%*{tag}*%"
|
||
sql = f"""
|
||
SELECT cla_info_dossard, con_id, cla_list_tag
|
||
FROM `{classement_table}`
|
||
WHERE cla_list_tag LIKE %s
|
||
LIMIT 1
|
||
"""
|
||
cur.execute(sql, (pattern,))
|
||
return cur.fetchone()
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 6) ÉCRITURE DANS DETAIL
|
||
# ---------------------------------------------------------
|
||
def insert_detail_record(
|
||
cur,
|
||
cnx,
|
||
con_id: int,
|
||
dossard: str,
|
||
lecture: Dict,
|
||
tp_id: int,
|
||
tp_name: str,
|
||
controller_location: str,
|
||
priority: int,
|
||
):
|
||
"""
|
||
Insère une ligne brute dans resultv2_<con_id>_detail.
|
||
"""
|
||
detail_table = f"resultv2_{con_id}_detail"
|
||
|
||
sql = f"""
|
||
INSERT INTO `{detail_table}`
|
||
(con_id, tp_id, tp_name,
|
||
cla_info_dossard, rcourse_id,
|
||
timestamp_read, raw_tag,
|
||
reader_location, controller_priority,
|
||
source)
|
||
VALUES (%s, %s, %s,
|
||
%s, %s,
|
||
%s, %s,
|
||
%s, %s,
|
||
%s)
|
||
"""
|
||
|
||
data = (
|
||
con_id,
|
||
tp_id,
|
||
tp_name,
|
||
dossard,
|
||
lecture["rcourse_id"],
|
||
lecture["chrinfo_time"],
|
||
lecture["chrinfo_tag"],
|
||
controller_location,
|
||
priority,
|
||
"LIVE", # pour l'instant
|
||
)
|
||
|
||
cur.execute(sql, data)
|
||
cnx.commit()
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 7) METTRE À JOUR LE POINTEUR
|
||
# ---------------------------------------------------------
|
||
def update_last_processed_read_id(cur, cnx, engine_table: str, new_value: int):
|
||
sql = f"""
|
||
UPDATE `{engine_table}`
|
||
SET last_processed_read_id = %s,
|
||
updated_at = NOW()
|
||
WHERE id = 1
|
||
"""
|
||
cur.execute(sql, (new_value,))
|
||
cnx.commit()
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 8) TRAITER UNE COURSE
|
||
# ---------------------------------------------------------
|
||
def process_course(cur, cnx, con_id: int, tp_map: Dict[str, List[Dict]]):
|
||
"""Traite toutes les nouvelles lectures pour une course donnée."""
|
||
engine_table = f"resultv2_{con_id}_lecture"
|
||
classement_table = f"resultv2_{con_id}_classement"
|
||
|
||
last_processed = get_last_processed_read_id(cur, engine_table)
|
||
if last_processed is None:
|
||
print(f"[ERREUR] Pas de ligne id=1 dans {engine_table}.")
|
||
return
|
||
|
||
print(f"[INFO] CON_ID={con_id} last_processed_read_id={last_processed}")
|
||
|
||
lectures = get_new_lectures(cur, last_processed)
|
||
if not lectures:
|
||
print(f"[INFO] CON_ID={con_id} : aucune nouvelle lecture.")
|
||
return
|
||
|
||
max_rcourse_id = last_processed
|
||
|
||
for lecture in lectures:
|
||
rcourse_id = int(lecture["rcourse_id"])
|
||
tag = str(lecture["chrinfo_tag"])
|
||
chr_location = str(lecture["chrinfo_location"])
|
||
max_rcourse_id = max(max_rcourse_id, rcourse_id)
|
||
|
||
# 1) Trouver le coureur via TAG
|
||
coureur = get_classement_by_tag(cur, classement_table, tag)
|
||
if not coureur:
|
||
print(f"[WARN] CON_ID={con_id} rcourse_id={rcourse_id}: tag {tag} introuvable dans classement")
|
||
continue
|
||
|
||
dossard = coureur["cla_info_dossard"]
|
||
|
||
# 2) Trouver le TP via chrinfo_location
|
||
tp_info = resolve_tp_for_lecture(tp_map, con_id, chr_location)
|
||
if not tp_info:
|
||
print(
|
||
f"[WARN] CON_ID={con_id} rcourse_id={rcourse_id}: "
|
||
f"location {chr_location} introuvable dans config TP"
|
||
)
|
||
continue
|
||
|
||
tp_id = tp_info["tp_id"]
|
||
tp_name = tp_info["tp_name"]
|
||
priority = tp_info["priority"]
|
||
|
||
# DEBUG : affichage de ce qui est utilisé pour cette lecture
|
||
print(
|
||
f"[DEBUG] CON_ID={con_id} rcourse_id={rcourse_id} "
|
||
f"loc={chr_location} -> tp_id={tp_id} tp_name={tp_name} prio={priority}"
|
||
)
|
||
|
||
# 3) Écrire la ligne dans DETAIL
|
||
insert_detail_record(
|
||
cur,
|
||
cnx,
|
||
con_id,
|
||
dossard,
|
||
lecture,
|
||
tp_id,
|
||
tp_name,
|
||
chr_location,
|
||
priority,
|
||
)
|
||
|
||
print(
|
||
f"[OK] CON_ID={con_id} rcourse_id={rcourse_id} tag={tag} "
|
||
f"→ dossard={dossard}, tp={tp_name} (id={tp_id})"
|
||
)
|
||
|
||
update_last_processed_read_id(cur, cnx, engine_table, max_rcourse_id)
|
||
print(f"[OK] CON_ID={con_id} last_processed_read_id → {max_rcourse_id}")
|
||
|
||
|
||
# ---------------------------------------------------------
|
||
# 9) BOUCLE PRINCIPALE
|
||
# ---------------------------------------------------------
|
||
def run_once():
|
||
try:
|
||
cnx = get_connection()
|
||
cur = cnx.cursor(dictionary=True)
|
||
|
||
# Charger config TP/contrôleurs UNE FOIS
|
||
tp_map = load_tp_config(cur)
|
||
|
||
con_ids = get_active_con_ids(cur)
|
||
if not con_ids:
|
||
print("[INFO] Aucune course active.")
|
||
return
|
||
|
||
for con_id in con_ids:
|
||
process_course(cur, cnx, con_id, tp_map)
|
||
|
||
except Error as e:
|
||
print(f"[ERREUR MYSQL] {e}")
|
||
|
||
finally:
|
||
try:
|
||
cur.close()
|
||
cnx.close()
|
||
except:
|
||
pass
|
||
|
||
|
||
if __name__ == "__main__":
|
||
run_once()
|