From f85cd667950e6420c7feda35d87f0c3c8a797ca7 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 12 Dec 2025 09:52:22 -0500 Subject: [PATCH] implentation tour pas fini --- a faire | 15 ++ .../__pycache__/detail_writer.cpython-313.pyc | Bin 2742 -> 2785 bytes .../process_course.cpython-313.pyc | Bin 7028 -> 5746 bytes .../__pycache__/regles.cpython-313.pyc | Bin 2029 -> 5986 bytes engine/engine_components/detail_writer.py | 2 +- engine/engine_components/process_course.py | 236 ++++++++++-------- engine/engine_components/regles.py | 126 +++++++++- 7 files changed, 268 insertions(+), 111 deletions(-) create mode 100644 a faire diff --git a/a faire b/a faire new file mode 100644 index 0000000..af7601d --- /dev/null +++ b/a faire @@ -0,0 +1,15 @@ +Notes pour correction du moteur Python + +Les priorités ne fonctionnent pas. +La logique actuelle doit être revue. + +La validation des intervalles est mal faite. +Elle doit être basée directement sur l’heure pour obtenir un rapprochement plus uniforme. + +Les tours ne fonctionnent pas. +Le calcul du tour et du temps de tour doit être revu complètement. + +Concept général à corriger : +Lorsqu’une entrée est jugée « bonne », on doit l’écrire immédiatement dans la base de données, +avant d’appliquer n’importe quelle autre validation. +Parce que ces lectures doivent être conservées quoi qu’il arrive. \ No newline at end of file diff --git a/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc b/engine/engine_components/__pycache__/detail_writer.cpython-313.pyc index ea657168285afa4986cde3b4a8a558a3aacec554..650ea1f184fa84c8e8c64c43a2d320c972173413 100644 GIT binary patch delta 267 zcmdlc`cRbjGcPX}0}$N$YL&TlBQFypqt0dl##~0LP=+8rkOV4FVNhg<7J!Kb3Bgzh zI!I*lLMB;8vB~?HWcejw;=ux;VnI@yUo+)1GxAN2W^?D_%toL*~bw$x)mh(z?!!42K!zT!fhq%WJyuF(2V& z1d)8KF2a-da#|TG0JYy@&de*h#SAq17E4NIa!C=$+?5QUfh0qb2}l8lO>TZlX-=wL Ok?mv+t}YfEpdbJat~oUT delta 242 zcmaDTx=ob#GcPX}0}%ARu*{sfk(Y^)QEamSV=kjqI)frZG#^L`1cC%0G>Du$lSyXs zNhWSx5ri0w70hoH!N8Cx$G{LR4i^&$6$_Hs%*dS2%*Z>rkj;JbVYY>gTqZzyW*{#1 z-kit5$;i^n^W(|nR!$FTB_}h6LuPU=9L$GBG+mgPk1#QUNM=?Sj>#`LtqkRXnr<;? y=9S!HE-op$#gdYlTv7xwZ6(8JAjwc<3{t>hlbfGXnv-f*WHULKtBb`NCVD_OteXEsTjrjFaXw3rr^(AG^+msO)W)H3$Ab#ZpXcnxie zNI#8`P}}{9kdTZJD5!jdMo5+TiNsHZ067ZK)vDp~&xhLvsX~G>vrPg*tap#kotblI z=FYvdvG?JKUgF1I29)cmA zQ;3IQI0~jcF~HGT0=ohLMmU!rz$oXQ9vC-p9zVEbgs}@=)_0r86AVje?#e}>@>;1T z$^v|&Tra_9U0?~NNQH(_uUT%CWU17YE68A)v(>UJl&XSUuGLEoxhyvur8xmgO5PAw zzA%czS`QRv>*b079cXrq#6nUtRAp^PRmN_#Ui4p=D&@NJCD{o1N}>d3%JtfuP^w5x z*bqu`ZAO3*qz))e)41UgP;Qw9h>_)QO=k@x@#4zzQ1810V+MeWi$RVS9b7`}=YYtZ zGVuW}sjQfHKK~m4e27b#kQXtGSXyTh#0GU1MU2(i0AfQri*Y1xZ~$&NLyGYttNdk+ z&7*+h-$;%Y6MT@1A9xdhDha>@adHGrLGocN<#u8`J&fQ#54USK$QqtD{%`;2=yt!D zM4L(R$-zw@WfxGMSbX!lvGc9-6jQ~WTXXP$kMm(Z$b0z|AItVUZEvFGiD_|AWcf5l zAD9B5>;|AX`Y#oMbP0ZtXR|w=wnp)WK0q6Ca06Jexy4VA&N;m$b63eO(ik1)dr z5svc_gjqi702#~H5_M-^0bm7DB;Qu=J%iQzGUPwwiet*#wxs#WAu&d&j8{$ z!HA+cbF@aHe1;Ti(8!tcW$0BB6q~~3rI8l|8KQ^)=)-`c#tvy@r3n#;m*If&K6Q=A zDFJ$zMQ04Op2|gtGZxhX%1o5>GqW&=r6CLlFg&A3bRw6*bR2vs5Is2&rBd zAl!$WZJUK7mtU<5nt9WzRh94P(5Q2>oVE&4v%<=DRU!j!8;v34Orc$@l5_Q0L}aAU z*XUh7>pE_TsJ=jJLiPGuFR1=>>x3GNzH{;yU#Jt_z3LllO(?fLQwDchIqyr)`&EDV zL7&ZSX_>nTU~^v=ezXRl={Y-mqxI12UanBmPYtzUqonE8&cYA=H z>DoXr)GGYs40H`7oxC4UwclDg{Neo4<<<0aYw^R^3mX=YD-bH>R;i#$c~z=Ur5K&h z`yM&XImfCk+d8#STpaH911i!USaZhjQ>^Og)8DA-38|h4dN=GnwCkY@&`fs(P`(BF zy{q@B{)PSR=pANd-wJzoyz|nTyH`3>=Q^cI=UlbJ2=}P-4=XvVEyNom-+iuFUu5akNBJtQ}u0+)4dlJp8!(IziA$HJ-Rx_@iM%q5axS7wm>_ z?8buIG|9%bnDRNpEKAHKBCjWs2aSPV40=ixFvAxZJca=u(5L7M#ypHb>peIlRnaZ` xEWD~X0@<;3kY5KA>ma)hj{a(Oqt&fhQ`dSQQYIqU^}S3IM~F7}JJ5;l=5HqBm4N^N delta 2706 zcmZuzT}&I<6}~fLgYB`$|A5Vp#|CW7UqXN+BxLhLC?SL->tXWeY#hfn)TtRq*OT3d zjbtiSDyvjm?@m_oLm#NLE3wi_MdF3G<`K23WY=9~)&yxz`qp3Bma1zsJ%ac74LNP=EO(^u@?+R{#l?QHrn-!mR@5 zMYv6{0&N#;w>w5?!R|%(b=W63V2xjJhFu$7)M+ZeWIel>!s$f(&|DU0B{`eeH=;TYqWZS=uY{ud|L027+pBd;d4xo&t~dZkt1}Q1-mHVUx{O3e ziBsB$80zTFHvsmcT8PM6#QcU+M*=-YN38oZ*#BUNw7`r}T_~)(oIZ2Tio#TtmY0CC zi|GW;=2D5QlnfiN4K%7uPR3OxEy>tIrj~h}U6j*{shk{Ne1JJk;pwN{BrLBSw`DN+f!#DqBK{Gp5=4foV^fu zvame0ZMGI1PZx>{m8M|X9{eh_9F+XMX3Ffz z=j=q8ohV%uOF-FcCEc~xumRF8Tlt25e*}ABs`-sQfQY+Ex!RDFiCa|zd^6cZJd-2$ z&htF(fUoi$As$djLw-O#8VU%ENOMRC4>M9{Gz_ZRxIu}XVu#o!xPM`&bV z&%)bO=s%1`;S^;2D1Sh?ZXCWcNc0b$X)t<7Qvf2OJugSWD>9^z80y)BD1HI>LGbV? zcz6zQlNcgt5E)YZ`qsW4u}2J^Z~za^EZ8MZI3)H6hpk8$5`z$rBg1;BMGQ!uXrEFs zg!}5}+6HOA6poUs)P^v8;90LQEcI#ffjeSY>=pY2O$`F&CxJ@B6;IK#kU7zMmLOq7 zbi#I{q8I2(1z?SZOA&dTvjY(Lp>BXGn0RVMKeayB77!XYKj zEmQ9(f9LwBBMN72v2_tE-GrPbCG`P6YEe{zOWp$KGFC)bXxPjAZ4 zu5I0X&Cn-JWy7(Rh5}oBZ*8V#KxRkb-6tov&Ae)J7K5s-x#(1F?!pHIxr$4wtx0We zSKC5rN1y8UsNN2>Be2tKws1RMWO3Kb$kJ3umCbF%8)b9wZx-7}Ldo8}iYs|b@BZx2 zx}#zlsWl=a5pVIVnE%Q)*J#G#-}VJo$JeRfo?bs*?i{c9P8H0%2IL%EORSGSb8j_P z99LHwU$KtDve9bK7xVRaQASFD|?$6IJn`N6e^>%!X4 zHeH*UJDYd5hDsf?1y<#Q1c#-~0Zi4%u3E@9B^}u-dtH zX*2k>?(7e>EJa!Ts*nHF{I4A8On>m$XS?V>b?Ih%^@;uJ0LutLi+g9U@qP}o&!B`v z4qC4!pb>!*Dab*3n7Pq8Lf1E{Q-Xa-TZ~F@s~3{pQ$0*ZGB;FU6E?2p?6w;w^>)= zR)ytDIv(?|&E>Ga8c0bCX(=^GK8n%JZ6sXxDet?$)y{)SyWXp#q{?7H`QGiNexaC~ uMu)y-oh4Uyg$*tncFa2JI<@1{Q*E`TDTX>ntqQwH1Fvs4I4Hli-+uwhE^_7o diff --git a/engine/engine_components/__pycache__/regles.cpython-313.pyc b/engine/engine_components/__pycache__/regles.cpython-313.pyc index c9e9e82e2ba6f6c5cc7611cf68480b6067b3f387..8f94770c07a430355207cdbede6846a213b24d86 100644 GIT binary patch literal 5986 zcmcIoU2NOd6(&Ve5=H%5mR;NVp`F-{64|k1r)v{Cn}3wlKT$H#Y#cbLqR6zOMy5Pc zN>X(wTnxpMwLn}f9b8}!+QR@BC^n&cSoRj67|=(03Q&#kS_3OG^i4>NVd&G&rASJ0 zoV@h{yu82XoO|!N-#O<#bGaNCq$ka%Q=hhB*uPQ5NEW^DYz_*aU>wHbGuSjP;nNn$ z!lJrmhL|QLQY#ZP)HEeg(^km}b+R3kY#ar-owGttb2i8woE>t8qak-D9YN-M#3E=> z?deQ1mJ*jTDve5u!u4D-h^w|qA(2{+r_qSTcvi@!mIc+0@_a@V60|NL`q|+l;CuEB zj)a~66$KRzM|l)lb|=mfA)f0;+0>v+@o%He@mSSd4dY{P2RgR-Q7b z@Qz{9SOZYRqD*bBQ97v?M22S!`4_7^8P0(O6rDW7Fv^#(NMS;OR4X zc4A1KM8`oa^4TbH5L?27O%c_a$cRg+q)J!+vFwWKN~DFQSXNkGiCw>bQ$mNUT5rYE zIbks0xgZI0CVfkYtw^bil*+zymAxKM+{~@SZl}b>%YlxDDIASmj80vMPR(7;2kJPpEPruwY%UDz{A+EU zXcM{cT*EljW(q$Ldae&PP|k?4L}pQd)%8Zh7iY&N!jrM_%dt16&cVi+0`$IcvIJ`I z{MNE;b>Gd!HtxNI6jXJGf6kB1nmmFwl}HL%l}N#gd1=v9dXY!;Ca@2F0lRW^P-EvS@s;HphORn;X5w`1l}sZLZilvB0BR9J&b z!z5kPiNsP;bu}ohN=TX8sx^~dL}Q)kZ&I+Mns#u^QbtM$)r#zeMW8dNPwKUaQ&)Id zkmM_JR#>?a7q3ia61inT%*t0TrsG*4usn6)%0(&jwvd1}q~(HiB6jOUY+zvMiXbLa zqM-jk+~pP65j0m4l4(KiUwKFM?!JD~Q4j=SwtN|`-6~dLF}i8f{>u6*oA$16Y3Duv z-G%k861~4#j;&uW(VZ0=M%gP)EYPxg{u`U4Vj<`QTl)?ch{D((tc5$9>_BPX;N9~( zc8qc>-oavD@u1=!drXd39GE?@j+bn0WltAWDEqHx-k+&pmVjs5({Wd#Q)$K+_x;el(1-o26aRH!P7l!8_P4CPyXil)dbaFu zQCdTr{=U^&D6ZZ5`1I-<6*p#YF4?-u9o*loP_YLq zcFgHg+y@HQ4N_r33K{xeC2Yaz1Lb}De{=o^8JUDHd=ov`mp$Y}h)C3fHnRMh$z1ehO!r@(Ys4Wq->8NPXZ{t!=|)Xkh|tD`{jHkeECB+Mmib9tL# zgyNH6L@3UxnVB;fHh-=Uo`;pMF$jlD*Tv(Eo+m^fU&a$jv$z1+#Bpg%wEhzjUNi2{2{11=rNjtsJy z96J!y+UfwTzFx&7N8YzE6`2c1-yEC0TD1V9KhUfW38Q&83qT`CV6<-mF)-y;d1{7=YMu^q1l8J&ghLhA-y>@BuE?Eh-tS8m09=`lI~U0|T- ze-u{cu9gCCtwz2hU5dA-@Sf81YBBYQe)z89o_S2pKCuPXLmR$Q`_ZzmebaaBF12O% zDb3vlVPm4OP{fs{6OZjf+l;rw94xmQ&1CUPG4iNW2~6HSyX6d&n?f6xHl~V~9`X;* z0XR(J9p#SB^&^Gg#@UV6Dg^F3xkC^xH{AOGAdTYhQJg)xK(`C2joZasF{!xEJSKly zX4;>@;;@8%KT#P&c%a>R^5u6P-P^i55AL4n?rLBn8khwxSHFo74hX(7GoKIdp`LWo79w43i-g z(xF9z6$yeMOv~<~-oHV%iftie7%X-dTOZPodLOlY6;$TlQn;AnzV?`mmr2`hx5I|q z4jSB!CU85}k1BNcj-RG_cbWl?%GR#JNTDAUMP6~AeoT&h!??>H|94E&gP}E9={UC88Y=nwN=&~( z_DipV^a<1aLj^5XgNY|lsCNzOqFyu7H7=8g)goTr>=p6)*4jnpabq4b{Izzj)(&T6 zj1eshNG2^sk>9v0bzwZdfY4t{QKwO={W;6)mZ+yzH%h#PBVNY=2*^n4XX73Wo*gXN zJmk!BDj{XS4rj#0puMIrQZr1IkTj}mNy;pnnkFIOQHfkOq1q&2B^^%)K>~=$s;x>z zR;emYw6$QSs`iP_%HXKSV45@m?SF(%z6u$r6YYKAUyJ^>wM4hAj&HerYh52ESI=!Z zJ!?%5lIzJ*OV6gS=kwlTuyk}}(>qdfj;@Bc82{Hy=RcUvLht7{{+#|Sy~&*Znwi*S zCX|c30y*=hLcUo&auV1W$vpBepyd-3;MXgJJ_E7tRO<< znt}jdKrt{osXhO&!X0ReLwyAeOAG8uT!x?-M(Zycqwt;)za=DekGcAY0hINSpc(@7 zLkwW5p!sTYr>2DPV)DWllDgnRd%b9U0%_pW_`31Jrw`<*Yew-E*ayQDwnMqnX7pz8Vk~0xVk%-%UXsl(^HFzn1QlI zEFc1;ZY9HKAjwdq1|)8A*yQG?l;)(`6=?&xj6ht>2P8f)GcqzhXVCr35zWY`TciaP F0su|*Bs2g3 diff --git a/engine/engine_components/detail_writer.py b/engine/engine_components/detail_writer.py index 69989ca..951ad89 100644 --- a/engine/engine_components/detail_writer.py +++ b/engine/engine_components/detail_writer.py @@ -87,7 +87,7 @@ def insert_detail_record( try: cur.execute(sql, data) cnx.commit() - + return cur.lastrowid except mysql.connector.IntegrityError as e: # Duplicate key = lecture déjà traitée → on ignore et on continue if e.errno == 1062: diff --git a/engine/engine_components/process_course.py b/engine/engine_components/process_course.py index e5513b0..5fbb42d 100644 --- a/engine/engine_components/process_course.py +++ b/engine/engine_components/process_course.py @@ -11,19 +11,15 @@ from zoneinfo import ZoneInfo from decimal import Decimal from engine.utils import format_temps_decimal from engine.engine_components.regles import resolve_priority +from engine.engine_components.regles import check_interval_rule +from engine.engine_components.regles import get_last_valid_lecture_time +from engine.engine_components.regles import ts_to_utc +from engine.engine_components.regles import update_detail_status_zero LOCAL_TZ = ZoneInfo("America/Toronto") -# ------------------------------------------------- -# 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) - + # ------------------------------------------------- # Normalisation du temps de course (start/end) @@ -128,9 +124,12 @@ def process_course(cur, cnx, con_id: int): coureur = get_classement_by_tag(cur, classement_table, tag) if not coureur: - continue - - dossard = coureur["cla_info_dossard"] + dossard = "???" + status = 0 + reason_code = "UNKNOWN_BIB" + reason_ref_id = None + else: + dossard = coureur["cla_info_dossard"] # ------------------------------------------------- # Résoudre timing point @@ -148,77 +147,9 @@ def process_course(cur, cnx, con_id: int): detail_table = f"resultv2_{con_id}_detail" # ------------------------------------------------- - # Règle de priorité + # 🔥 INSÉRER IMMÉDIATEMENT DANS DETAILS 🔥 # ------------------------------------------------- - status, reason_code, reason_ref_id, forced_tour = resolve_priority( - cur, cnx, detail_table, tp_id, dossard, timestamp, prio, det_id, tp_config - ) - - if status == 0: - insert_detail_record( - cur, cnx, con_id, - dossard, lec, - tp_id, tp_name, - chr_loc, prio, controller, - dt_utc, - status=status, - reason_code=reason_code, - reason_ref_id=reason_ref_id, - tour=None, - tempstour=None, - tempstour_str=None - ) - continue - - # ------------------------------------------------- - # Règle d'intervalle - # ------------------------------------------------- - interval_cfg = ( - tp_config[tp_id]["config"] - .get("config_tp_interval", {}) - .get("clef_temp_minimum_read_entre", []) - ) - - interval_sec = 0 - if interval_cfg: - try: - interval_sec = int(interval_cfg[0]["value1"]) - except: - interval_sec = 0 - - last_dt = get_last_valid_lecture_time(cur, detail_table, tp_id, dossard) - - if last_dt and interval_sec > 0: - delta = (dt_utc - last_dt).total_seconds() - - if delta < interval_sec: - status = 0 - reason_code = f"SKIP_INTERVAL {delta:.1f}s < {interval_sec}s" - reason_ref_id = det_id - - insert_detail_record( - cur, cnx, con_id, - dossard, lec, - tp_id, tp_name, - chr_loc, prio, controller, - dt_utc, - status=status, - reason_code=reason_code, - reason_ref_id=reason_ref_id, - tour=None, - tempstour=None, - tempstour_str=None - ) - continue - - # ------------------------------------------------- - # Calcul tour (placeholder) - # ------------------------------------------------- - tour = 1 - tempstour = Decimal("0") - tempstour_str = "0" - - insert_detail_record( + detail_id=insert_detail_record( cur, cnx, con_id, dossard, lec, tp_id, tp_name, @@ -227,14 +158,122 @@ def process_course(cur, cnx, con_id: int): status=status, reason_code=reason_code, reason_ref_id=reason_ref_id, - tour=tour, - tempstour=tempstour, - tempstour_str=tempstour_str + tour=None, + tempstour=None, + tempstour_str=None ) - if status == 1: - log_info(f"[OK] id={rcourse_id} tag={tag} dossard={dossard} tp={tp_name} prio={prio}") + # 👉 Après ceci, TU CONTINUES TA LOGIQUE (priorité, intervalle, tours) + # mais la donnée brute est déjà sauvée. + + # on traite pas les dossard ??? + if dossard != "???": + # ------------------------------------------------- + # 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 + #) + + + + # ------------------------------------------------- + # Règle d'intervalle + # ------------------------------------------------- + interval_cfg = ( + tp_config[tp_id]["config"] + .get("config_tp_interval", {}) + .get("clef_temp_minimum_read_entre", []) + ) + + interval_sec = 0 + if interval_cfg: + try: + interval_sec = int(interval_cfg[0]["value1"]) + except: + interval_sec = 0 + + # Appel de la règle d’intervalle + status_interval, rc_interval, rr_interval = check_interval_rule( + cur, + detail_table, + tp_id, + dossard, + timestamp, + interval_sec, + det_id + + ) + + # Si la règle d’intervalle rejette + if status_interval == 0: + update_detail_status_zero( + cur, + cnx, + detail_table, + detail_id, # <-- l'ID que tu as obtenu juste après insert_detail_record + rc_interval, # reason_code retourné par la règle + rr_interval # reason_ref_id retourné par la règle + ) + # Et on arrête le traitement de cette lecture + continue + + + + # ------------------------------------------------- + # Calcul du tour + temps de tour + # ------------------------------------------------- + + # Appel de la règle tour + status_tour = check_tour_rule( + cur, + detail_table, + tp_id, + dossard, + timestamp, + det_id + + ) + + # Si la règle d’tour rejette + if status_interval == 0: + update_detail_status_zero( + cur, + cnx, + detail_table, + detail_id, # <-- l'ID que tu as obtenu juste après insert_detail_record + rc_interval, # reason_code retourné par la règle + rr_interval # reason_ref_id retourné par la règle + ) + # Et on arrête le traitement de cette lecture + continue + + + + + + + #tour, prev_ts = compute_tour(cur, detail_table, tp_id, dossard) + + #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) + + + + + if status == 1: + log_info(f"[OK] id={rcourse_id} tag={tag} dossard={dossard} tp={tp_name} prio={prio}") + else: + log_info(f"[dossard ???] id={rcourse_id} ") + # ------------------------------------------------- # Mise à jour du pointeur # ------------------------------------------------- @@ -242,25 +281,4 @@ def process_course(cur, cnx, con_id: int): -# ------------------------------------------------- -# Dernière lecture valide -# ------------------------------------------------- -def get_last_valid_lecture_time(cur, detail_table, tp_id, dossard): - - cur.execute(f""" - SELECT timestamp_read - FROM `{detail_table}` - WHERE tp_id = %s - AND cla_info_dossard = %s - AND status = 1 - ORDER BY detail_id DESC - LIMIT 1 - """, (tp_id, dossard)) - - row = cur.fetchone() - - if not row: - return None - - ts_decimal = Decimal(str(row["timestamp_read"])) - return ts_to_utc(ts_decimal) + \ No newline at end of file diff --git a/engine/engine_components/regles.py b/engine/engine_components/regles.py index fdf6d36..7c8c72d 100644 --- a/engine/engine_components/regles.py +++ b/engine/engine_components/regles.py @@ -1,5 +1,7 @@ # regles.py from engine.logger import log_info, log_debug +from decimal import Decimal +from datetime import datetime, timezone # ------------------------------------------------- # Règle de priorité : remplace, rejette, ou force tour # ------------------------------------------------- @@ -94,4 +96,126 @@ def resolve_priority( # Même priorité → rejet - return 0, "EQUAL_PRIORITY", det_id, None \ No newline at end of file + 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 = 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 + 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)) + log_debug( + f"SELECT timestamp_read FROM `{detail_table}` " + f"WHERE tp_id = {tp_id} AND cla_info_dossard = '{dossard}' " + f"AND status = 1 AND timestamp_read < {current_ts} " + f"ORDER BY timestamp_read DESC LIMIT 1;" +) + row = cur.fetchone() + log_debug(f"[get_last_valid_lecture_time] result={row}") + if not row: + return None + + return Decimal(str(row["timestamp_read"])) + + +# ------------------------------------------------- +# 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 + + ): + + + cur.execute(f""" + SELECT tour, timestamp_read + FROM `{detail_table}` + WHERE tp_id = %s + AND cla_info_dossard = %s + AND status = 1 + ORDER BY detail_id DESC + LIMIT 1 + """, (tp_id, dossard)) + + row = cur.fetchone() + + if not row or row["tour"] is None: + return 1, None + + last_tour = row["tour"] + prev_ts = Decimal(str(row["timestamp_read"])) + + return last_tour + 1, prev_ts + + +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() \ No newline at end of file