temptours+tours

This commit is contained in:
2025-12-09 11:27:02 -05:00
commit f28439c9bb
49 changed files with 18416 additions and 0 deletions

View File

@ -0,0 +1,341 @@
"""
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()