From 5873ab490093c6c02a85460c38f39a62a87535ad Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 10 Dec 2025 18:04:10 -0500 Subject: [PATCH] valide les double mais preirite et interval pas ok --- .../__pycache__/detail_writer.cpython-313.pyc | Bin 2253 -> 2742 bytes .../process_course.cpython-313.pyc | Bin 7623 -> 7028 bytes .../__pycache__/regles.cpython-313.pyc | Bin 2029 -> 2029 bytes engine/engine_components/detail_writer.py | 17 ++- engine/engine_components/process_course.py | 120 ++++-------------- engine/engine_components/regles.py | 6 +- 6 files changed, 44 insertions(+), 99 deletions(-) diff --git a/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc b/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc index 62e6e0bb7729812b27c5db195e866b294541a747..ea657168285afa4986cde3b4a8a558a3aacec554 100644 GIT binary patch delta 907 zcmZXSO=uHA6vyA}W|Q6QM?RXQwO|?*ihHmqtu1ObwoR*TC2H1+(3Db>&61FA=p+$~ zNX|jDEkV(XdhJmJ!K?954`PLaVJ#@)!5)N_f`}qGlUiGFmVNX4&%DR%`_I1iJqs$E zvg`&j?tD3xeIP6;ZI#PJ-31LaG!A13qcH|E5vrMS7CXYQ^JtRBCOOTKbZT7Er8x<^ z2=n9>2)mQc9kzn6J#~Y^MXDe1ud@ z#wISD8yyZ%;-xixF mf#k8}CMq<+(?l<{@3nnR^n=H5w=^*Tp*`P&6!8i234Z{Ko!P+v delta 393 zcmYk$y-UMD6aeu1N-j;Cq^UM&!Hz;y#KA#9>Q=DTF6m|#X+=`B(rb&Z9i)RxOVCN& z9h?gO7fvcjCRYdHpy2GwP2O?u{T{q~cQ3_P*PdIJiC}%)?nG~VYA+pBpt?ile>=zq zBt!uXalk?nXkZ=8;TYgdh_~~BSwbDw!GfM)KlhDc$n>#CI(X*dRUGekej(S339g^@NB*xywWqWvj zS`k}Ns#mT6l46YGebYN>V(dyCS$WA74P?7Aqi7<#AhU`&lyjt|Xr~R1Vjh)Nq^np! RPC+gxE+TLFx0q7>;}3Y3So8n@ diff --git a/engine/engine_components/__pycache__/process_course.cpython-313.pyc b/engine/engine_components/__pycache__/process_course.cpython-313.pyc index 21cb0c1f3940baf277e7b65bfe207452d670596d..32db8f922c77750dd86ef44f2bfcc48c0cc139e2 100644 GIT binary patch delta 1319 zcmZvbOH5-`7=X{|y?t=|Zf~JadJDC1DKIeQ&AfmRWJ)W;nL7_VP&>2|m{Rn%V zMw97cGU<3UA;tv@7uC2RHe=YyPGVvL)<5P*e);=9&i|kP z{P*kO<38PyR?7e)fBrqV_?GvH&I-SO((;r;Iy(@ZNKfcOhC7WtGGw%aC0cYN6R~k$ zz!3^s3Zrx%gl_z%{HCm|D10Z6$e^##snW^dEqs}2mpcH6`0CB=Sp~>5$6*S5SKw=#`z={@34u!|K39r_yUn4;hC(03bF>^m<%YcX)Wxk1?e~=Y&?&r3a{6@aX_LACRZ4LQ~3iatnX$ z7W*XHlj0+`T16B%>!S??QvPau)11HyZGs3u(w`^k?Zkf|-=- z73_q4f|Iaca1#y)oDKw-+Tmn`vJh!i4LA@9;8m@c32$Gd+~8Hn4;DVthGn?j@KC?1 zL&3@E=w$f%_{@!wDg3kHBDCWM9NbyLLyUk5JZ!)bSC;nEV&quCKL0S`(h%R zSzJz(wV9PnEE!EF=2uejw5TLmd^`Td_y}IYNmE#HnHaIJR+%NkU$(GNVgN< z*UetMW>(=3&Gn`XF|u34C>6!Z(i88>?n(->8$cMwJG;tBUWr^DOnur3wxFqeqwv}*q$QWmmNRQv$+RF zeRIiX&nZe=Xa4>s%HP{!w~||PJKejDp`5zJ`Epu(&@f=FFu-8U()+AqeGE@!>q=Hf zR(q&&;#aKQDkspGa@QUY<4>*r$=ftASdZ`0K;E>`oS)kA{XmaAudG0*^_1M)`_3!VX zwkj!kr0y(6UbA`uPls1K817Wi_|*8=b^M3z^S8@7>E+Xz*z#*pF%gUNa`7rTKnzMS zNT`?KCg2sKAn7Ry0}_@b#0bhvCLN6@<`pvK&KGTgqOoV~>b{!IyW2ay**k(-^o9qR* zror4kknEvk$r6_AVVzlGmOb=sO_p>JTzbPyMwVtzdob09ZfUlibCI@d`UigZ{J;PI zbI$*AH29;BI8-QP0Md)^y(?VzHetlLZO3;Vy?`-OL^EJoDJ8@iTef<5WZV{t@)eX-NoKnR31pjof1>q9dDw2|uc!89=g zT({BYenF%YrA5Tc8v%GFsaZ6FDK+$Sja-0!a_HCeriBA~(*&jFEm~j%H~Wv=dY%IO zr^~A*K>(XvhekrWO$?1GH^P}72yYP8-3tZ;HQ`(JJ?!r?&wMZPAHZsM3Hz&p-sV$l zvzt#nlaDyVAdJuRVHdNAps3zHf?IxUwPP*eVT+tl~C*8S- zLl0BkgBq$QNVulNw4UyC!^@IzMF#2?wCklsSHun2f8`yUF1m~2_Z}WiJ35*rApilv zBq0wm>{ZlLYX{>dD}_3MRhbHts(gJu@FY7Yv;9&qSwZf>SfW z+4%|)nwoo!XHLVA>|aF2UQtjJ!Rcr)JnNr1KNh;FjUb+^^9$t4(r5)^v&vD-%sy6r z;;*QptFcr%dOfzfvJ_?Fu_XpgSVedxo~|g;$#iTrnu;$b6HBQIzL;EF-$=)!>Es5( zsG%7nh2V6vcU5<=_t+M7STcsBXV|3rj(Hr#k*+iG>(O+IIg1p22q`xNR;_Weq(;P! zXbifWP|%Guqwv1o21hcWcCeQ<3mCyZ)QrdvB%p!FTFO$g$_lj}G0(GJZ41=X+9mN( z12C9&e$a-n%?99`z&KpVe~lcj=5Q^C>p5J-;TpI&)XIFaQo~S2l9lHc3KIubfNQcJ zeCEsfik*>iOQfpOXKxghmOY!Ntn}>bI`Z+tT=B#}Q8)M;<|Rx;v$w49R{OkrxH%Wk z&lOwUCA|AU#m8X>^ghhUUenKP+Ma0aC5^oxF5DpefeQRD? z(mS5$21~lZvTiuzuPSxfTV$ zBg$N<$_*vCmD9Fm&Q|4`t#|j0mb~zDd@EQrw&w#Sqbn19YV0b^myJUi1w4z!R(9FY zXRPhiT_5*G>+F4lE9jR1mGLMn0b*<3RtW9de-Vv6KcEDdqMfslEdF;2PAd3Gp`IcI zKa-*BN}% Xzz`5&XRthV1o*|_#ReT_<)i)wMcu~O diff --git a/engine/engine_components/__pycache__/regles.cpython-313.pyc b/engine/engine_components/__pycache__/regles.cpython-313.pyc index 2fb38a86f1e5245aa5133278eec0338d34bee546..c9e9e82e2ba6f6c5cc7611cf68480b6067b3f387 100644 GIT binary patch delta 64 zcmaFM|CXQkGcPX}0}x0Zv&@v<$ji;n$TL}z-A|N*frs}3pWFhr6)YFHbuO^z{P^I? SG`WU7gEN|uQMX77s09EMh7bP$ delta 64 zcmaFM|CXQkGcPX}0}yPTV38@Xk(ZmDk!`XhyPqf<0}t;7KDh;KD_AaY>s(;b`SGEa SadHiN24^@Uqi&HFPzwMjwh)5= diff --git a/engine/engine_components/detail_writer.py b/engine/engine_components/detail_writer.py index cdc100d..69989ca 100644 --- a/engine/engine_components/detail_writer.py +++ b/engine/engine_components/detail_writer.py @@ -1,3 +1,5 @@ +from engine.logger import log_info, log_debug +import mysql.connector def insert_detail_record( cur, cnx, @@ -81,5 +83,16 @@ def insert_detail_record( tempstour_str ) - cur.execute(sql, data) - cnx.commit() + + try: + cur.execute(sql, data) + cnx.commit() + + except mysql.connector.IntegrityError as e: + # Duplicate key = lecture déjà traitée → on ignore et on continue + if e.errno == 1062: + log_info(f"[DUPLICATE IGNORED] lecture déjà enregistrée ({e.msg})") + return # on sort proprement, SANS échec + + # Sinon, vraie erreur → on relance + raise diff --git a/engine/engine_components/process_course.py b/engine/engine_components/process_course.py index 38f9c97..e5513b0 100644 --- a/engine/engine_components/process_course.py +++ b/engine/engine_components/process_course.py @@ -1,3 +1,4 @@ + # process_course.py from engine.engine_components.tp_config import load_tp_config_for_con, resolve_tp_for_lecture from engine.engine_components.course_config import load_course_config from engine.engine_components.engine_state import get_last_processed_read_id, update_last_processed_read_id @@ -13,11 +14,6 @@ from engine.engine_components.regles import resolve_priority LOCAL_TZ = ZoneInfo("America/Toronto") -# ------------------------------------------------- -# Mémoire locale pour la dernière lecture valide -# ------------------------------------------------- -_last_valid_read = {} # (tp_id, dossard) -> datetime locale - # ------------------------------------------------- # Conversion d'un timestamp Decimal en datetime UTC @@ -45,9 +41,6 @@ def _normalize_course_time(dt): # ------------------------------------------------- def process_course(cur, cnx, con_id: int): - # ------------------------------------------------- - # Lecture de la configuration TP + configuration course - # ------------------------------------------------- tp_config = load_tp_config_for_con(cur, con_id) course_cfg = load_course_config(cur, con_id) @@ -63,29 +56,22 @@ def process_course(cur, cnx, con_id: int): # ------------------------------------------------- # Pause # ------------------------------------------------- - - # Lire le statut cur.execute(f"SELECT process_status FROM `{engine_table}` LIMIT 1") row = cur.fetchone() status = (row["process_status"] or "").upper() - # Si une pause est demandée if status == "PAUSE_REQUEST": - # Le moteur se met officiellement en PAUSE cur.execute(f"UPDATE `{engine_table}` SET process_status = 'PAUSE'") cnx.commit() log_info(f"[PAUSE] con_id={con_id} — pause appliquée avant traitement.") return - # Si déjà en pause → sortir immédiatement if status == "PAUSE": log_info(f"[PAUSE] con_id={con_id} — course déjà en pause.") return - - # ------------------------------------------------- - # Lire dernier ID traité (mécanisme incremental) + # Lire dernier ID traité # ------------------------------------------------- last_processed = get_last_processed_read_id(cur, engine_table) if last_processed is None: @@ -109,8 +95,6 @@ def process_course(cur, cnx, con_id: int): # ------------------------------------------------- for lec in lectures: - - rcourse_id = int(lec["rcourse_id"]) chr_loc = lec["chrinfo_location"] timestamp = Decimal(lec["chrinfo_time"]) @@ -138,7 +122,7 @@ def process_course(cur, cnx, con_id: int): reason_ref_id = None # ------------------------------------------------- - # Vérifier que le BIB existe dans la table classement + # Vérifier BIB dans classement # ------------------------------------------------- tag = lec["chrinfo_tag"] coureur = get_classement_by_tag(cur, classement_table, tag) @@ -149,7 +133,7 @@ def process_course(cur, cnx, con_id: int): dossard = coureur["cla_info_dossard"] # ------------------------------------------------- - # Résoudre le timing point selon la config + # Résoudre timing point # ------------------------------------------------- tp_info = resolve_tp_for_lecture(tp_config, con_id, chr_loc) if not tp_info: @@ -162,42 +146,14 @@ def process_course(cur, cnx, con_id: int): det_id = tp_info["det_id"] detail_table = f"resultv2_{con_id}_detail" + # ------------------------------------------------- - # Règle de priorité (peut rejeter ou forcer tour) + # 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 + cur, cnx, detail_table, tp_id, dossard, timestamp, prio, det_id, tp_config ) - - # ------------------------------------------------- - # Gestion mémoire locale selon la règle de priorité - # ------------------------------------------------- - # Si la lecture courante est rejetée par priorité : - if status == 0 and reason_code in ("REPLACED_BY_HIGHER_PRIORITY", "LOWER_PRIORITY"): - # On doit retirer l'ancienne lecture valide de la mémoire - if (tp_id, dossard) in _last_valid_read: - del _last_valid_read[(tp_id, dossard)] - - # Si la lecture courante remplace une lecture existante : - elif reason_code == "REPLACED_BY_HIGHER_PRIORITY": - # La nouvelle lecture devient la base correcte pour les intervalles - _last_valid_read[(tp_id, dossard)] = dt_local - - - - - # ------------------------------------------------- - # Si la priorité a déjà rejeté la lecture, on n'applique pas les autres règles - # ------------------------------------------------- if status == 0: insert_detail_record( cur, cnx, con_id, @@ -214,14 +170,8 @@ def process_course(cur, cnx, con_id: int): ) continue - - - - - - # ------------------------------------------------- - # Règle d'intervalle minimum entre lectures + # Règle d'intervalle # ------------------------------------------------- interval_cfg = ( tp_config[tp_id]["config"] @@ -236,16 +186,15 @@ def process_course(cur, cnx, con_id: int): except: interval_sec = 0 - last_dt = _last_valid_read.get((tp_id, dossard)) + last_dt = get_last_valid_lecture_time(cur, detail_table, tp_id, dossard) if last_dt and interval_sec > 0: - delta = (dt_local - last_dt).total_seconds() + delta = (dt_utc - last_dt).total_seconds() if delta < interval_sec: status = 0 - reason_code = "SKIP_INTERVAL" + reason_code = f"SKIP_INTERVAL {delta:.1f}s < {interval_sec}s" reason_ref_id = det_id - log_debug(f"[SKIP INTERVAL] id={rcourse_id} {delta:.1f}s < {interval_sec}s") insert_detail_record( cur, cnx, con_id, @@ -262,26 +211,13 @@ def process_course(cur, cnx, con_id: int): ) continue - - # ------------------------------------------------- - # Calcul du tour + temps de tour + # Calcul tour (placeholder) # ------------------------------------------------- - tour, prev_ts = compute_tour(cur, detail_table, tp_id, dossard) + tour = 1 + tempstour = Decimal("0") + tempstour_str = "0" - 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) - - # ------------------------------------------------- - # Écriture dans la table detail - # ------------------------------------------------- insert_detail_record( cur, cnx, con_id, dossard, lec, @@ -296,26 +232,23 @@ def process_course(cur, cnx, con_id: int): tempstour_str=tempstour_str ) - # ------------------------------------------------- - # Mise à jour mémoire locale dernière lecture valide - # ------------------------------------------------- if status == 1: - _last_valid_read[(tp_id, dossard)] = dt_local log_info(f"[OK] id={rcourse_id} tag={tag} dossard={dossard} tp={tp_name} prio={prio}") # ------------------------------------------------- - # Mise à jour du pointeur de traitement + # Mise à jour du pointeur # ------------------------------------------------- update_last_processed_read_id(cur, cnx, engine_table, max_id) + # ------------------------------------------------- -# Recherche du dernier tour pour un coureur à un TP +# Dernière lecture valide # ------------------------------------------------- -def compute_tour(cur, detail_table, tp_id, dossard): +def get_last_valid_lecture_time(cur, detail_table, tp_id, dossard): cur.execute(f""" - SELECT tour, timestamp_read + SELECT timestamp_read FROM `{detail_table}` WHERE tp_id = %s AND cla_info_dossard = %s @@ -326,13 +259,8 @@ def compute_tour(cur, detail_table, tp_id, dossard): row = cur.fetchone() - if not row or row["tour"] is None: - return 1, None + if not row: + return None - last_tour = row["tour"] - prev_ts = Decimal(str(row["timestamp_read"])) - - return last_tour + 1, prev_ts - - - \ No newline at end of file + ts_decimal = Decimal(str(row["timestamp_read"])) + return ts_to_utc(ts_decimal) diff --git a/engine/engine_components/regles.py b/engine/engine_components/regles.py index 677d2d1..fdf6d36 100644 --- a/engine/engine_components/regles.py +++ b/engine/engine_components/regles.py @@ -72,6 +72,9 @@ def resolve_priority( if new_priority > old_priority: return 0, "LOWER_PRIORITY", det_id, None + + + # Nouvelle lecture plus prioritaire → remplacer if new_priority < old_priority: @@ -89,5 +92,6 @@ def resolve_priority( forced_tour = old_tour return 1, None, None, forced_tour + # Même priorité → rejet - return 0, "EQUAL_PRIORITY", det_id, None + return 0, "EQUAL_PRIORITY", det_id, None \ No newline at end of file