temptours+tours
This commit is contained in:
341
engine - 20251204am/engine_loop v1.py
Normal file
341
engine - 20251204am/engine_loop v1.py
Normal 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()
|
||||
Reference in New Issue
Block a user