From 84be398b82d3be6b44c05f2ae6364f04c6b7cdb5 Mon Sep 17 00:00:00 2001 From: theo Date: Wed, 15 Apr 2026 17:28:20 +0200 Subject: [PATCH] Ajout du matching entre nouvelle correspondance et correspondance IGPDE --- corresp_IGPDE.csv | 101 ++++++++++++ corresp_IGPDE.ods | Bin 0 -> 46763 bytes docker-compose.yml | 4 +- requirements.txt | 1 + webapp.py | 384 ++++++++++++++++++--------------------------- 5 files changed, 262 insertions(+), 228 deletions(-) create mode 100644 corresp_IGPDE.csv create mode 100644 corresp_IGPDE.ods diff --git a/corresp_IGPDE.csv b/corresp_IGPDE.csv new file mode 100644 index 0000000..b45a647 --- /dev/null +++ b/corresp_IGPDE.csv @@ -0,0 +1,101 @@ +Nouvelle nomenclature,Nomenclature IGPDE +"700 – Management, Leadership et Compétences Managériales", +701 - Management d'équipe,Management +703 - Communication managériale,Communication > Communication managériale +704 - Gestion des conflits et des situations difficiles,Médiation +705 - Accompagnement du changement, +706 - Intelligence collective et émotionnelle, +707 - Évaluation et entretien professionnel, +, +"500 – Ressources Humaines, Carrière, Prépa concours", +501 – Recrutement, +502 - Gestion de carrière et mobilité,Ressources humaines > Mobilité et transitions professionnelles +"503 - Diversité, inclusion et laïcité, lutte contre les discriminations","Ressources humaines > Diversité, laïcité et égalité professionnelle" +504 - Dialogue social et relations professionnelles, +505 - Accueil et intégration, +506 - RH à l’Insee : outils et métiers,Ressources humaines +507 - Préparation aux concours et examens, +508 - Jury, +509 – Formation initiale, +509 – Formation initiale, +, +100 – Méthodologie et production statistique, +101 - Enquêtes entreprises, +"102 - Enquêtes ménages (yc PCS, ISCO)", +103 - Enquêtes ménages – formations spécifiques enquêteurs, +104 – Répertoire Sirene, +105 – Recensement de la population, +"106 – Conjoncture, études économiques", +107 – Comptabilité nationale, +108 - Autres outils et logiciels d'enquête, +109 - Méthodologie d’enquête , +110 – Communication avec les enquêtés, +, +"200 – Statistique, Analyse de Données et Data Science", +201 - Statistique descriptive et analyse, +"202 - Programmation statistique (R, Python pour les statisticiens)","Big data, IA, DevOps > Langages de programmation" +203 - Visualisation de données (QGIS et autres), +204 - Séries temporelles et prévision, +, +"300 – Informatique, Numérique et Systèmes d'Information", +301 - Développement et programmation informatique,Informatique > Langages et Développement +301 - Développement et programmation informatique,"Big data, IA, DevOps" +302 - Bases de données et SQL,Informatique > Bases de données +303 - Outils bureautiques,Bureautique +304 - Sécurité informatique,Informatique > Environnement informatique et sécurité +305 - Accessibilité numérique, +306 - Infrastructure et administration système,Informatique > Modélisation de projets SI +306 - Infrastructure et administration système,Informatique > Réseaux +306 - Infrastructure et administration système,Informatique > Systèmes d’exploitation – Poste de travail +306 - Infrastructure et administration système,Informatique > Systèmes d’exploitation – Serveurs +307 - Agilité et gestion de projet,Informatique > Gestion de projet SI +308 - Eco-conception et Green IT, +309 – Intelligence artificielle,"Big data, IA, DevOps > Intelligence Artificielle" +310 – Autres formations au numérique,"Numérique, innovation, créativité" +, +"600 – Santé, Sécurité et Prévention des Risques","Ressources humaines > Qualité de vie, santé au travail et sécurité au travail" +"600 – Santé, Sécurité et Prévention des Risques", +, +"400 – Connaissance de l'Insee, des Régions, de l’État, des entreprises", +401 - Présentation de l'Insee et de ses missions, +402 - Connaissance des régions et des territoires, +"403 – Action publique, connaissance de l’administration",Action publique – Environnement administratif +403 – Europe et international,Europe – International +404 – Economie d’entreprise,Connaître et accompagner les entreprises +,"Comptabilité, gestion et analyse financière des entreprises" +, +"800 – Communication, Rédaction et Techniques Éditoriales", +801 - Communication écrite,Communication > Communication écrite +"802 - Communication orale, prise de parole",Communication > Communication orale +803 – Communication visuelle et digitale,Communication > Communication digitale +804 - Rédaction administrative et technique,Communication > +805 - Pédagogie des adultes, +, +"900 – Juridique, Réglementaire et Déontologie", +901 - Droit du travail et statut de la fonction publique, +"902 - Commande publique, marchés",Commande publique et achats publics +903 – Gestion publique,Gestion publique +904 - Déontologie et éthique, +905 - Accessibilité et RGAA, +907 – Droit – autres,Droit +908 – Archivage,Gestion et recherche documentaire +, +1000 – Transition Écologique et Développement Durable,Développement durable +, +"1100 – Langues, Compétences Transversales", +1101 - Langues,Langues +1102 - Soft skills et développement personnel,Efficacité professionnelle - Intelligence relationnelle et développement du potentiel créatif +, +1200 – Autres Formations Spécifiques ou Transversales, +1201 - Formations transversales, +1203 – Economie générale,Economie générale +1203 - Formations techniques diverses,Immobilier +1201 - Formations transversales,Événements & séminaires +507 - Préparation aux concours et examens,Préparations aux concours et examens professionnels (agents MEF) +507 - Préparation aux concours et examens,Préparations aux concours et examens professionnels (agents MEF) > Préparations de catégorie A +507 - Préparation aux concours et examens,Préparations aux concours et examens professionnels (agents MEF) > Préparations de catégorie B +507 - Préparation aux concours et examens,Préparations aux concours et examens professionnels (agents MEF) > Préparations de catégorie C +507 - Préparation aux concours et examens,Préparations aux concours interministériels +507 - Préparation aux concours et examens,Qualifications informatiques (agents MEF) +507 - Préparation aux concours et examens,Qualifications informatiques (agents MEF) > Préparations de catégorie A - Qualifications informatiques +507 - Préparation aux concours et examens,Qualifications informatiques (agents MEF) > Préparations de catégorie B - Qualifications informatiques diff --git a/corresp_IGPDE.ods b/corresp_IGPDE.ods new file mode 100644 index 0000000000000000000000000000000000000000..27142850a7c56392403851a79ccd612e99fe8fde GIT binary patch literal 46763 zcmagF1CVA-vo89!ZQHhOYudJ^ZQGhQr`P%Q)dr*Q$u@uYYSsTXA3)9 z23K1XdOJfW3nzLzdsABzJ7X6cQ(I?xCwoUzLlY-+Q&VS!|Ie6zhxtE*`Ii&1v;Apd z=HmFD(VUnWobBwajSL+b{_j%Q{}(BHLt9hpf06rd+J0Kw89JN(n>O74qRr6Q*wote zugP}*Ci&m&baFOyc5(Xm`TaMI_KtRDj;2okKavFh#a0U&Lo-t+24M?l8$)}ie{s)$ zGuPhE-o^g!db<3J+<$ZHKO+C{Bq1Ro|FfO`TK9hl`Y-3?>|t%{MDK26eTlbjyFT*R zsZT_CVuV3C8A-Z6Hw68Za@CY^Nj#c&qRdDhHb+Prh8aWaMexq1o9h=ZaQfT1sv{Bm z{O4+|G3jik)6vFs+OFxt-J%UHoGsxf;*YA(t27qeo`GQ?X>|4e?XB)6`mpLFKhKDM z#8-QC13$F>U@ff9NCS3*0V#GFsEHNAT%#B?sh^l8lo@1%p{r4EESY_xiBsvUEWK(m z@0#p+>iuMerxQ)f=Z~wN&Y`1yzTZnnjFElXmR1*FP=O_I>rR>Roi!kW0WLv1Zy`l3 zx^pebeOW(>oBS;o0K<67?d<(EJ8;T`^DAlc3es|mzgJ8LMP!YXEn~EcqJeZ>Pn<2f z4FzwPGjiCv9*U?q4&RXlq8z4TP$Xe(vTErBFw)Z;+a`%=22x`8lfZ^tRLt704%^7X zg%fzVg-3^NC7jH!%z>9Wsaso-|A5X^oI z_i3j^(%yN)+)_iCR7t+~uA?Fk2oWNi)Q`cMwnwP2#mL0y6cvT=;zU?Pv%pV^M9q3| z$@l1N6BsLN6DISSk45g+ovER-66W`0dZQ*;NJ)DgWUhb~rOs?%alja>Z_six_v!;1 zU7!Z&+){F6*2)M)yTMX1k{z{}u0h%%ZIrzw{iKvntJt%WjPe;?6C&vo0VTQo-0-VR zFt8p>3qe*hwdbZ$96v=hElrCFbK2w*nP~e7>1_@+6I=HxLnXn_8<)eiUcSo#3bYie z0cswMDdsllo|Yt10VbQ8Sutngh85xfMkcmkwz&(=Xq3(DDsY{H#8PrIw1X#EhfdW6 zpqlEc+8yonU0tFae+SA|MRXZc>u0H5pW^=n zQ>N`;eYvvpBj|XqQf}eii!Xe~-|`2g#mou_2D?)%@P@#^g!g?fRc4O1N43}rB7<2$ z*L3Wu+PJLhD;zq67nH&3L98HdWSC*0i5Eqv^~MwviOvQFMh7jBp)oB)MgFz&Q#cTl zJp-&*&7T<>xl7hmCMbdcgM9Zqiu?~^Lw;z8Mj*=rPtLSdDQ2W#okjo{4s-I$q@Qr_gXZ4+K>NG?48s~2(RsLz zyK*7|s9zfOrL>u2(WZfPUz+hIGKf2kgLzCr3r5nMW+dpOHa+dQlwb!qU`@acM!1a2 z#S_wE){kQ32s|};oH9TNGN^*Iib6^B_n9*45%TK9Z^0e3yNJzBS6E4c=jnnyt`rmG zBWC=G=jkakZXcF;d8Gc{y8X{PM#S`yE9GH?V+L0se5z_MjdJy0QCg-mMlzNKnH$dv&G*pGPnB8AyI=CJ=Fo6W_o&{)b<2-rtLox^UhSr zN*DgbeZIs5-gGph2N(7s=M5&Dy_5qbWB!7HIY0&d&`KY4jF&lfJeBTWPF3%IH&>?S`M#qjK!^>x}lA5OBD*Xy<60VZId;)-qWQw?o}{0YiC)VRk0<9V)Wru zphszSCm6GYrmzlQB(V*jq%^fHQI;i?i97OCxgM&?*f@l;6$HGY=8IlbU_{Xi!icgA zi7=X{vE=mYBdJAvw6m*K@q8`LVsGq(ixG8S5zRG;KsNP>F@;yK8o^~l?ZA!iJ&I)X zSy9r!mN%Gdx=v+Mta`^;QS!61wMSMv6daeaOGj&4rrdvjzIVSyHA)@P`M;++t*GLO z_AiOe8w$9PRl1T1u$;^64-!t|_#hLuHgy8cLi@fhV!*sTs^T{g%H_99|5g>Uxjq@a zKOR^yGs+a87=j`lYE?9HZYE#u++d^2)Enk}caU98nB(ZAt5e_I#m~D9NZOK_&`~o! z+5*q?wMAm#b1e0m=w;iaYvFxXbUytA`+a zPP_J=G0}6**k{?+34N0=hUvZp=N9A8xR$OUg4!&aY{BLw>VCI2i#;iQxama3WFBkK zXz!n12I7;_ifLRTr^FiedUe!1;E#Tt8X8TSe6E{q&N@{jSKdCj>LH+{u{2SWfIv;20m|Zj*IhF0bZCeq+kK~)32utpq z%~$0elo^+0*A;oG8LA}ED3&J!DWel(+I}wWaf_1%W#;!PRjVpE(DbntcSStUK~g#~ zaP(t-PlroJFp9otPWprXSf8y6_+xdkc@uY6Mw{BVQ^-87IX@!s+P!FMiKm|wjKoxI z^o=ND3kLWfQcSk3=zqSTP}j@Pv=ysZ#`+P>&0j|i>?|dmu_?k|=3b>RdI-}Q)^(>a zt}4r?9Zt{hP%Nhz9YkW=vcoq$uIcLk6}y{vn@f$4@gbIX@m7goCAnmMZyxCQO- zExIXrCehs^jVqxNZ=Y4W+S)Zb!L1{N^{t~)d|a-Qb3#Or{Iq>h*3ZYqG}`OWC7hli zw%~Vi-Bx4eT6H^US}tkgeZ3R4&4@{j{JrM@?&ZZ z(-4B)!e$7!bW2)C2vi`ZgEE3kHlh(#SzevuNGoSR@G^D*4!})JrsyR-x}H1RZS>rK z$CeN}hUsuDVpcdQ&Gj5Z)|o0pT05T7Z@x_B3v-*!$%llG=5Xb3SM9W8AOafG0N>C= ziwbB@K6Y8)O+DTmZxb80Gj0Zs{GG$FuIJ0nSh#E_eI1`Zcg|u;Q{zb)T1&Of!u6wi zCUtbnwJJ3JLijW~P#@%*7?qpKzLvYr*+|~EcdR5 z8Wrq4vdeHmqC_4)JOV zn%^MjinMFt6Z_%VvuP{0PDN(Ucrg+j47-k^l9meIC~TW0!5HlP>F2}^9j7O$JqYd` zXjgTP3>!9}Bq2gEk z4K+S5`G}gsC1nqz3cEXxI}lG$1~wD6otIMbp} z?bJv8EuPUelHAA5DWKmkgILAnzrTW`+c-=zs;;2!-h3~=&>Ea)3VBx)A06gdSj(jp z$Zs%6D;oM~kJ5L;TLZ=lQLIXW1Uiq~gX; z_S-{_1L`gtY{e88ISPi3UZjc*xVuXYr>Sm%lS2zPgBnxrNHeR!el){1C&vZ* zhW1{Vg?2^m4q9dSGkNr;A&k;})au#p22$;b%Pe&YBB)D>CG9{e0fO!&i4KI1UbT}e zlcL#aU0|yLqRW7-i$YGdHXhy7>7vB!`X(}!Vhlqew@C}Rkt%NcE(plBhx~}!Vfpga zYC(&;uGU6*e+pOhzMf@XN@aQf_rVaAVNHQnk9aC8D&Hq3x15trj758W615;~o%gQ~ zLT|Pav$J*)y{W(yV2Z(FJ47nN^omH2)haDJom zp!6?p9QLc%=fYeD)7?qm`QhTGDzC5Qp_kOSG$OF>bb8#^YJA$1%o{`ooG!_5-i_;95KG_X~RtG;EDt{24X+*Au(?W9+U)_-0Hf z-K}#ry}FCnilQB^Rl4MeVH;D$^UMLbUN5k<>oi%LU#8jj-yjMy5Rhk(urj)E0068r z0PsJN^?wV>VE+bXHiotqKTVyS=^ageP9{#;1~DQAU-O2TPOS(sfPpzuj{p4iY{xWYTpHqzMAnt`s<0YUk&Q{3H=fdPx6`*4u5+)g zG|>>GY73NU4x!F>4>8FIyall$n*XL2Xr{D9+cM$h3aym%D4mHF;Lg7aXTRa|k zV{=XILH$%$bUx3G^LQrYi`?Wc&8>qrY*OkBYa)yf5R8VB9Se32!fpE@Gsu#1ugKN= z=W=h@pmRPbkJsRaCT4nND;&1yP;aNR=1M1M+dJ9Z^UB!MWq$B>n z`}m7~`WtJny1`Y{T5u}g>@DuN;f_F6qWT~AH9VWofdK&kK_LI;KA6AmGq$sJ{wM$O zpP+Ws5r^Z6Uw^i+RNAym)FIJGsy0iBbY0PiC+0+U68Dd7O+*3C3BX)>j7mM{n3aQ| zpKIbnhYX|N41*I15fuU-G5g5H+7;4tSo znzk-=pbZRWRi_;ux%G>InucMs1hs;*|Gtr;#Rxnm9X-c6`0ZYg3+< zKWFuI26Od`QxpA9s+Vdzi-4$Q>}OjM(I;%MW+h$o2ix899re>rX>K+wq|>2F$ICx9 z?Ql zbkA}FaB7kJ9u~oOalG>Kr8*i|J=?lk%s%3Y=1f?T;$=l(QJt6eRc2H=D_ibY#vtcH zD)lcjVVHkrDTr)DS!EaQi_vTMoD9Cjd9!VBCB=Jg1Et;llgKan}uOW3U_YSuLHMsbqB*fmumwP_=tq^icD+&r*qLiuRV>9-XE5H26B2T8icvpkV8fN)k?>xy zA{j?)?d)85HW}GKlWtm}Tc^t6*4NLRQ$#A;sTa7lv$8f#?Gx`!s0UY3jcy8~l5P58 zLB{rgKp`tDFE=R|Ul$>df3b@eX-X%Ew>GD6>4IN#?R8;o@noUxR*qWxzQT5L6Jt`v!}+uYug!UkXOWBDBp^j3 zqudWCLmC%T74j#Nvd|8ez#G3O0Q#O#N16#&MyhhP;7(fA-WI)tR)JJFgbzLs^4gj#l-$MJF7jL7C+M1{dq-ooLdkmsp zb%dwQJWYcffEZDZ^IUq0YU$Hs%yxAfriQ)Y!{cG(>Vay1xl8$x$zfhj&y)%u+^b0d z?WAm*P|+J}A91pSn#c-WUQyC!P$({W)j$po31l6Z0OB3@vcQk@Z6eHSlUsf zJHg;p?W%}kBnH}nV;K~HC!HP3+gOIQ^LZGlX^1FBV|a-=?3L|{hnJboCerw>09)caPjqWuuqS>J-w)??k zf64h)un7J2ajD;nLwle8xiY!KM$1gS=x++N*h_>NcPk09xMK#l;vTF`szNvTawL~F zn>HAwt-SfQZ=5gcjJW#i&iz4Vs&27t0W{J}q40fI52F?0SNu_C2z!T=OnR--lj`gX z?UMc$YQ4+e4ZGtDnrdhuq#8b`>}Rk=hz#<#n(T2XKbk14nelkli1zuVJ1SFdvXrME`Yf9Tzw}!qk$o=#kk6l5k0>ZJPSx4%aXmIgl%7uT0sk!&pZpt zConihfG|#XR}gpPhms_i2SC$sJJdn&1SElLT;Gjh(4slbHN>zl1_tGae?9SUOla@K zV$eMAMl4)ps?`@aDUKjFN8T|=Y#K=elW3CQEVXqc+%cWy4fcx$ZdEtn&dd?}%Ywb6 z04uCmwKIO{GFJOClg?+HSopmnvfZ7tsGRbOi}Q|6Zinq)8HtHZBj7f65O6Bds?1aA zkr%%|pV>C{CK!S7)oF0t_D=5j|5t0c3Iu>8F$Q$gei^3}>-BS08)euDKazj_e7Efox;b;t)G@ zlEOllq3vXac3x!kjL|ID1GizmWca^g!9lF=24$I5%@DSl96p1o?X5IqRCTBFV$OxFys@vqzs9R@e0#lbQM2BS# z!T;zd*uvc6E9K#b2LpGWR(88)lpXq&__8m(rDz|$5+tR*cS-J>hcUE;zFn<}yR)eV`3Hgh7JuV8?QK85)US)2m!0Wv=|Qcin0zra2CnRT zO63P~QTR4$_($8@N0|CBU%tzb=_04!$;vNVgy!#CZg@EsBDu|_pRN0|aiuP>Bvx(m zD=tOvsqGsRD_6}PzkqvG;dQQcDfzzm3dL5G0n4pt8;#^2kgUJRiN~$mjAW?DB8R1; zG)wttWJpWWnVj>{8mx?lm$DA)btG*xtU4Z-MKw7vl7*|b5UL3@oEZM7;#6S|zjsao z0mKYCt{sldl-1NG%(2n(y>OnysIPZ6m16TZy^-n)oeKX2M02b)Bwzb%M)Kde>|F=q8dOjRyM6 zJXHXH(43n?js?_Z9|3V@wMl+2X}yW=K5q`~x>>%yZ|fIq4Q(p3S6KHHsN2Qo+OHE; z!mNT%GzB>_S}9&2H7lcbcGQ|;x%v;*T7J&VC2xgG%O8zO1WX}0y{%d=G$hN_QsLLF z-^9bs&Kr1$)SrLKlO0!fclz%J<1{GL)`HR)E|B>AE=F;7lI!MDV{ZK#%SBN!vI}`i z8&cIm}bQBuoSO_YHV1=!Q)*ca3ocGWOdmv5vRdnZCj$Ptv}# zLqTli4PymFce2jDR7R?z`OCt-2(gFs*EDX^yNj4j#%KDP&%j(%T;Rj_0~3$s(=SLJmRS`(=YyZ*I+bk#*MVKOP_V zy{>9fQ71ZHm8-Tb@|^q{OJaZ9j-}~kNzv=j?w-g+Zg7(^qz3pE?ydRL`%=HJ&E^F+*2?gpgE<6C& zv1!)PdF*oUq{j4vjIMR^wL6=6Q=~^qT%^yYhMoncG>4Fb}uI8u-i-4pJcQ2 zxBI!+j=JPpZO%!oURDt~(W;io{KpN!FHqrg0@-Vqr>v?4meuH0o!YK*GH_Fh^E|D| z;OtZ3`wWYrOl+QjATBmru1#F#*-;#=eV*tGjg>{A&3hdw`8Z_x0GLH0MnKXET8scC69JOIEE_-}FV zf1m-T&W8U4z0t~Yu^Ei09nb27ZR*Au05WvLMSRb-vgGt+&q&;NI+yd6cqW;*YnUTr zj!j(8eZJ`q4;J>t&%=UzGyBTApBVGO14x*Lh0K%mz8ikxrr!G9P@2O2<>Dgrs2La3rOJtbg2l^%|JF?QF!G*XiJkVHUL7frshH zyCb7vK(D}`~BSp-Oins*grJ^q}XY`5fN#opy4Pbq3IbU zQjZv7%oDD*GM0Ze6(7jx`R1@~W$Mol5d8U5S?@RFIIL)OnuxiK*43u%eJ)V;4gAkaFWtZQH~h_Tg#2rUq3$w=vBg@+Mg4}`-R;%>;-fmKXKlTD)T&(+yy$OnFu z_NHxS9T2V5J!D13PRa3qH$B|U0yD=@wo>|h6(q$FI+R3?Jmi_At=%rqH4o_@J2MNx zSF|rc@k$Obg$a*PeMr(%bX+Mg3pon)NM-@YxMu+n?%M3n7Q#&GE55v4`G}gZHH}1% zFbdNUeLHpF`)`!p?;`3fUY#n{Xei`4l&K2;R*_a{rV>Wc#7a}tp9Rb)+}fF&QkV^` zeLiLXZVu!8u;`cQ`$pRG9Ana4jPoONK3tS`N~x4{7|{M4Etd!z}79G>LA&M8E7~g7vyW&(LAC04@}_M$GvRpT~5Llt+<_{)n0VEu)ph zH1e6RXJ)^;)r+VNw4Ve=DIt6n6vkS3O*^DL5?eZA_L{3IkqgG^9Tf~ef@<3xgk@UG zbOf?(@4a(bAFx<&?Ipj_-xd#NnFuY^2?88T^(Y&W@HO@Xzyrp$&L@vrD&tZG6fth? z*#+ht9;~F!*7VObJ=ahTBE~6+)Buh)a0cSrf~%l@@BIjEhAP^H%LJwv%VPsdJtl1e zW-l1-zX3-oQ6v@~I|7TdR20r7p{(a*g}ia`PzI!JB}rbgnWCnbqX4bi_=EM;nnUhQ zEH^?3QN@f3${MXbc_R&49`HGW5Mv)8F`C|ZD2#h%istC@*&+XC2HuVYB*~NMj?E-C zkov=gDMs1!)Um{zEw6=9tj(hgf+8mpFk({BgQa@N{)EMndpQns^J4PN`9=I^QBZQ7 z5?nuj0)}y?*?*Gd?-A^Ca3BM-QS6pxumZVbY*nhzF`Z@7%P<|RV}*Vv;l7828?GI( zr4F4oIGia+K{7`Z;2FCOMhq`6ez1z_1oCqz$|sz(y@`fJ9oMYdp@i50l9Cwz$sKo7 zt_*~}-wp^=tw*UywO11p4A0R*XH_*h@8Dr&{pPfAG5bc`pxr#+B_Og-edl!Im@Nyf zqfK-p+ysAuQb>MPE!|I|V$%hsW(WWfC8Cz-;hDLaDf2Al?4{X1w2=slg!x0T@Jr@RrHiH}F(?9{m+FqUJ? z63a6&3xMAQJ3pJX#NfSQ&wsC=4+MU?OimOKDIKkF`K7C-W9`)s8D(3n_B_@c5tCK= zR{c?rs?OptkNM?$CaG$t`gGn^S;j*0<#H&{UryASPZb;5W*uLY^r?a+@L=g_iWx;v z^)X!HaqZ4&&(mLWxOi~?r(+3~u?#H8&}kQu&lA_M@bD4i2ZZsll;+T~s~MLQkDASe z#YU;d@{?rb5E?FT%mcxdh<>ecrVLp}tIF$Q;+UQ4ce}18J+Ay&NBMlfsB*Yuigi1J9KUbs#p~p+GqE2OS%4m z`rY4UMQ5T_%}4GJR@wI#n>sq)&ZPX@$kV-WHU3AmH`c`G{lMJA$i6Lafrl{-jE7c>ouuX}Ie#<$sSrR=DKqm4tEYz5 z9-V2UQ@W7gVvhDEp;+Gycj;E%nSfV%U4(EvtdNJm`Tko76J(#2^$F(0}TTfofICA z5gmsN8J`h{hz5s(ofI9G3AmkoLhm0Pmz^ZoRMFVPEeIwNQzxtgGbhgPti<>4o`#;UyzUSU~WHAf2=jmx>U*hB&*CEcZ`Een|;QAqfQ$X(eGfE$JUh(u!IN!h*`u zBFb{oT5>{)@`{>@QtIj&g7U^|dN^B~{)OmyI9j>6xyhONt62x> zIEL%F#T(f9|8xsC^-gs04z>-KTRqg%G|(l+q^ULSQ z!`IhW!@YCn-}eE^UQ*Ku0DwXHrvU-dGylF>ga9c~Ar<$vi%u9F<>eFsc=Z4cP!BP| z2eVtX%4a14x-~5VmoY$}!cddnw`g#bUVdP{TkeQR^#)}gnrJpMerpt-Xz4FyKgtHZ zVl)*><=z-HKQ$#rB1%?LqV66t_nV4oyJ{anki5nvJuANU4NdcCi|ZAvj9L&=j`(Q zMGKR}gHS@lz8Hd1V>HNxBg^|62skGCf~3pHgn)N4-9)#~o8+Ql!U~p?5;#EuWaYE~ zuCqkm1B(c>@%(b!+0gP>0L8*%+9yST34I(rb)b$R3=WTGjzI>rt`PD{9i#OhTSP#O z%kEf6WdR}%3z=_|4c0>k1#+Fj*b?+^Ow=!=Az!FXP*YMEoi-hUA z(WNJZMfPwfrGN*hHU8Gx=ZgmMXLxl5?M5mjT#gAiv0yajL!3F0k6!GDm@lbui(5d| zNZ!IN3>=7HTt>h=2}%6usnD!lpp3EoWvhy#&@V94XnY$C<=573AKiCUKd z=gXvkl+FabAK|-uY;2?yUjSAuZVYtvhvq5DV8ph6RnG>O#{*mw!(8DvW3Pb?#&N1` z^Zdx$e8WMj3+V)0qQ|d={!F?@WW`%hi(kX0`{n;>{7~~jHqxYC8~I@%03?fQNXVit z^l-9v?xJ8?8c6q~VA`neRf^T2hC*r1Lf1_asK0E1QW}kyf8D?(Y5$m!zkDwH*MH;y zXfdOOKIn+QX-eeCGayGkwFBC5irgEaBk!N_@+Eb2ytxHJsIE!ARzsSC3Dd*6yEN%U zEVR~mB3;SO!I51bInTl_)*t6U?L^}g(=A-N=+GGN;azWgoeG1MVleY!r!|Lt z$p@f*c&h~YZgTjZ59_qofk*)VEnsIp5xgghi1zjA^Yty;y58-DW7^3dDWSINw95<6 zC6q$hV||cfOTXy{Pe(k(;(9j;z_zo|zkqjUDD09DI*py5*Tuam@cHA^(MG<-?=8{1 zQHk^yrp4znCX1RM!Dc3xM94-M7TkONfP<04W&fIr_NC9jmJf+U1{g6RMnz<7?AXl}5QLn`t>t1vmMeXXE)F+e$*UQdkEH-fg zqm+5zPah}H>%|2TqN8Y}J2(4PHk6NZo+=x7?-c`GsqnS!2T8oi#+!lIDu-?h5A&`w z|A4KhwoS0*4b|zThETu`G!`eepPEqu7I;ufmf9M{2 z+fCiN2V(HzCGFRqzN9+{c#Ax}RW`K!;`;=jYE;2MJXWsmB?qYdHtVAN>F?3jmmrQ> zw_pv7uD;`C)P%lSia}Bd+0krzXDl8^b4J33&UR|74ZSM9@OBQ`+(u_c&_b$bo`b8` zxz_a|;Km2O4pdEk?ftAa90~ISKX@zd*pH8IH#lROXs1Pk_L)=jG!bli?EC0_%hSgs z^yr}6p>&QI;12`v-$%fB3dtXWsJtK8=?#a5z2ZXHeP`h#pFrzF=)Xw*?&MdHNEkUb z4sS#yT3^=K!1I?f%#&oah2P}zGL-9(Ak5dKLZ6N?$Tf3H{Z_Z0!K_6#Pn60_YA*H_ zbH}!LGcM5)myX|xRHA~dFVO2cFa zaf)9i{%$DhEe35<`{nbC3c_D_whnFuZQ|XUG9Vqy6Ow$RbGQtXj=PiHE9~Udjqsp+ zSr6D<0!j9ZJUL)ooH_Iy$C_RE2l&K&&>a)CYba>l<)bBzShJjoa+2u~nw>nSu~)v+ zM(>tGJRXzrgXQ_&dJxHH5D-9AKIqdJ%LSBl4T(rsmfENbBdM0&4-}(&0&Z4@hy$TR z-*Ihb^qWn>V@9nly4nbC!1dd-D zB<|eq3?cNxauvC-Wf6#@O<{-gso7lL!0?0iw$9~4N7={n7cfa)buk%`n+Qmve{oC6 znfM^<7jlTKa6jXEF`1>pxB-f?xX(g)H2yGHN+_h1CT103uH|$0`K1G#HcW&%8WMB2 z7Z?Ttqd3CgwacDw@r+1Amn5PpWIwRY?-F71wP*Fo%(-WITggAm=mhZn&7Owz^6onp zwSI5{gak&5(Kq}*pGNklHJkMao8>i`PcBd&iCw2b^>AtrumYgD1Ro0cm%J7&#`mYA zAZI#$_)@;o&Df|u0ey1%JNgA6NL6F(k3dryR}ynz^y2)E)*vSAq;4qJS`lNfFl^s&X+ zNOAg4KMhvpHrWaZmiz4W(W&yq;AjHR25%1x(nSU@D~J&RUe+jxO=M{t{STWC)Qfwi zVRrwRfdaJ2Ob8RVAvh{k!|z?KaLdW_N6i_rs3t{C4<28b8QrLN(<>ztgGWWTD#eXZ z;Cs4g;*XTg0C^!UFw&(GSbs2gp%28X`+**iFIewC-vM0y!4XO>)E=(d-`CA|=fXV{I$nY>a79pV9C@@n}G*=)zz?{?OV}4Ph|A(Ec7WPz>e) zvfl@I+$-!Ubuf4IZ#6`7)%ORBv{1o_(*aU83ovhHz<|wqho*do7x{29qp4O@fzZnJ zkfeA+@h#k4Krx-RX7yqc2qHiOSK^Rpz3b{8n~XF{F(6(@ox!{Np!2d>!N0Z|L(JdB zs#uk(MA2BZ5XHs+OfavDMU;=?gV&2y*>)DVH9w6yGPc(wkC;#jDk2*d5Da3y4-mu@ zsEd21FzPMawzqu4Iwux}Ze;MJ>+AT<1&I42;KcWg1X+!ggk1xl`b;cRH5Ho&xoKBQ z*jY+W9g0MK_h<7ITfn{3p<%VC@O!zk4>95VsQ!f_EYZH$?iE>$*a?YJ$tFmK;dsUz zM~ubK*Yqy?MCXokt-dLZ7UNs+wfw3<{P88GO+DonDWS*pKT5ad@wvB9x~&S+Ls8#< zA6YmGg#r>?A?@C;hIph=_F?y5^Y~KNt)6;{ApIOQ5Yg!l)R@CuHj#N$IbCnKqq(N# z1l1&)xrwpIvvz=ne12hGwjd}~Q6$sAypD(7O8EgCDHlie09A77?(zqin)f4_A`5+} z{g|!e?I+(r+?k4*q)X}al2T3W=!fwLbXLT=pt*qtUd-n;dzc7RppA2U6V> z(f-iP0OC#J#Q_07z?dbHM^=`63G$QF?nSSPpAi#QrT3y|kq9KMAki-LXPfT8g6yv? zroF$HRSteJJsKL5s||BsU$(TT77U8DtgrQ!wmfOmT^fZY)L|w@7>){{h{|!;m%C`` zG_qm0&@2t4zsczsbH8QORxh`VK{QJxbRUE+0boxfYqte8VvuNFwFYVg7ofDta!!w3 zXZ!{xI95P%J{rrmibIDZMe#1EP8L*Sib z+<}j)Sbb`M?C&#d%W7nw)9`>qJCULDLh&CCR^{&&JTFlhAb;Q}6tCf(E*P=hsd;q) zNie({S;c~beHE|6{(R?+o{%IXh5Ur}Q+?|1H={_N$m#f;H&~pe2u>r`Oe`}d^x$uV zCfqf!3|QI30P0a*Dkp{(ykKkjlM+qlg=+EpUu09)#x}L` zV`#!7ta>~;#LlPge0Qbi06zXVn5N1WFARfM1whGQ3RacIV4Q%4$+MYAg&c?%mzy^h_2(CK?FQc%W+K@ zjHWxY6@BfF2wEgC)e-Eiig$w%pAC=NI_-Wx&9!(*RNXYhCG@FL5K`;!C5s*OQE>Sn zLTHL5Ir|;zhw;W~Fu4g+l zR-}S}Q~H1?(O`-nm1?|Hgy1bKGhOn)`+?M>Xs6o@RjBzUc&G~Ps4{H8!v04|n5cXs zVdIPyntVbrz+W~m)mRjg8hA@z8m(?@83U9MumBWiK;Vn$`wX$d{fIrg!haKTou&0G zUmANG?1X=MEe$E(^07QFIbwUv{L+IkbZNtxH_)5OhZU#}G%tVoL@}IrmNMP#UeL9^ zOE_BgvZWR_VT1z&2oQdXgbPea*Wx$%3%3xQ24?O*M^EYfk~+{HQNYXPi((A5Sg`2# zA|bWM?U5pQfRN0DKD5HF2Kw+IF0+FK5bXjOf?ysGZ^m)~xkW!8DZw25(2+~tuY#jV zX@CT2CJJ$Pcy|G_U7LX@ynNZv0A{NAI-kR8^q{AE&3X89&d+RRgd+T~5|Ei4&gfO6 z=J(4{*_%k&a$4xCnC-?xhVO6r8nGLYehrl zq2m~EGo(Bf%j|k=Z0y)8(o>jjYDG2!$)x$bvEDkr*${uu61A6A&a>;yWjRr&>_6Nj zRKygID5rt(+Ikkk5)$1wNPkVPMs4RKj})5$0YrYE+lqupQtO#{o&dwMIP&GRZf~+W zK-7VE-;@KUF(Cj$2A;fSX%mwVC(47xX2<~VJ)oXqeFT8`L-WTV>6YFL3s8@9X;sxO zH3%M?$=5Xdk`4}i6~p2J-y3k&6Te;SFxv|PH$3bb!ZiI$YVqq0yE1iYwYPg|~taIy#NzvUiH!55$c7`nL)C+PExC|{JEMo z!WHHH+VC`rFFau59zyaV7`Sj1XIRBBVjEf--z8yb)@-WIYrxZ%Wl%Dp7=K_DtF3+i zDkYZvW1a&5;QyrM?(rAskz)n#1q&FX?UxlWxZe3jGjuTWS(ZLMS}o;@{CvctX%3T@ zo7Te3Rgdfne36tyzD~mf+78AG%YJF8xvWvfO@Y>~BuQ_!B?LgDNigEkxLIe*J#QcV zQbLCq={GaWjSc7y6e9>yal|7~Kvj0 zq=5Gq@&lBk*pt~dy~eP+ZirtAUl#P-0|$D$MC8XdaeX7DyksB`dAo$vc>jhSaSi$V zU3n})VKij09kq+~2Nu695HKwczK~L&vkvtr@8OAy5X)yQ0Sn{S0fy5a687R3eHVVP zlWPrBgX<)ZzGxQt{sYq_+gC8{_Q2^4?5JAY4+RQ1w(KdUfc0^x%F7gd3Kd-efFbnK201Y7urI!fGw?jdfV3$!UuEf7_K|HkgPKt0I7{*A%uo~eUC z2Fyme!>viM%4!PIcg^j`sw+*z5m10UNFo|*B$nb!Du6Wfc3YvS&-$%1W$ zED&Qha8GISK0w49Hf82~gDpCEK-)YV7*sI!hDS-Jnq?Ot#%c?hlg$d4_yh_hKG?N8 zfhhi&+SCVC0RE|e_8^|1Ou-Uj$J9&{XA{@(nqXnIoKTD}&=u-kQu3gr&dgbfIY|jtA@rZuD zdeyg|xi45#3_rDUaqg-YPfBS?p?VBWTp;m3^S4LwI72Xq8@CLlJ2XFZd{xbZ_mwUR z9~zGiwZqA1j8<=qjt(6fBELKo4^1XuVztbmKj)@rXJgYN-Rl;t3br#{J73`?&6^u2((kKa;~DN={$Ezd$qXBtyNe6K|epX+ux!WLpClhdOZG@!OL^M4S9T z_Ny;IPSx}#F6}o%g=ETW{X|s_38-$*Zzm&si`JN-${*oltrVB>hUxRg7oZ*zHzMxB z_z7%A1h%Q%(-uoPP~xz@nV{ZYcT!?1{1zGfk$CLNMpV+u6^%|*w|d?cHJ}vYdbdp_ z7x4;uHQ@QENLy@gddAi-cCid@{K3&-Fbu#mjumo83@ zvY6KD$l)Hr#r?SmA?$Uza~o=_@J@RQJ%`PHxNW4kDnH+Tk{P#yEz7)s=RdWJguIp4MlBP&PH z?cZO{%+FI+$m!wFQ+353FFcfeQlsf_-nTXlDn&^-mzR%BxVVj?wj35a_JT3hoW8`wj%KoBUKLiZYWgipG}#cgt7$6gJ=W5F@WTMa zF(qc57r(htTPBp6BjkGaj!vGbEFV*H*xVlflz7S-=qR^pXSKTeY;r;IR$Rc-Kx@zP zO;Le!_Bf|k(@#I9XW&y{IutBX(F!L@fbj8D$Y#V-GD>ePsm$=cZQ14=w1JFa>dSVQ zhG0T~E~5tGbh_XwwU$m*WDNhqvJS=o?|~~HNc^~sj#%hcdLwCjf(uxGKpe63h9q%W zUeFO@4=pafvkYD~?8q01eIXQ!$uW17Swj$Bzl+CEnyMt1hXXIcBkW0VW`MGojY4QH z6WPoTr(t8?OoFq8**v_)u6oWw3wBafo|y=4CivgS8xz|D(`dU+&PwhnyedK1!Im`S ze5rlVTf)Y#;iCYRjl*(?%?!g6uO-*1YMR)Mnx}@s?C8Ow^yWV2rm}qTpE$vMuS_LL z`ZmE?6GQYdkW~ux`)tYJPNn>1v)v$Yd#1W^?sGD(H zW`H1j2L$R?75#3`gC)p&u_!(YO77kTpf*onmIaQzA-vliu?GS9b&`L?+4)|kOkxW7hUngiA~Z4!Ny~J=Flf1#r+p%7^w3D#2uI z6f|)@F~q_;{2wN!6^uc^GC}|cSs7JmulDQBD!2h_5F}JGCF#Sk@L*cP z7ylsu060>CJgLt4QoCdi#~q) zq?iEBEFY}&p5NBJx4rDmRms8VjLquI5y*GfS?a*f^ad$bv0gnWl2^A7C3PA6p!dD? zq!v+0w@{PR8jHcH2-0Nd1m3!=>C^z@`vhi+6&Z=ETXD&hm6oeBMO#6oDJvPtnl3dS zN;;&=xIjb|O}GP~*%zA7J{EGEqCOeMX1hA2IjF*AR#rR+k=33?=Skkvsl`Le4sk6V z7+dD9*OTWeD~e#l^h0FVhE+XaHM38e1Tndih8xRxV3k7{XF>8_KSRSVxmndS9Hjmw zTBI8ss`Pux!IOvO4%)D6@+=h5D+d&ROF8z?E}V$qhPCq+IsF6=sr!?G8hylW)Qv*Z zm!#%Odrr>*(g-hTh@n|4fN9`+Qw#fq)>16!*ICp_#YASHudJ5wRTIe|^rt`;)ml%B ztq(4qgS#QFO0%wq=|pT)MtwL*Z9*9egGYow`#s_~<^eOg2rF6Zw%aCij-UD0&J+BM z-)Xh>rT%5SCQ046`6NFOFOx5fRxxxCsHJ9izM5{keN8Sf=+hBn`Krvoft`OibOW*O z5X+!rYRO2f6ijEHX*iA5Ym1{)7dQRduWF<8_?v<-4ndER=Cz)Q9B+_r!=I50{Gk$v^-`9D|4#!EByKT(+&sJ@2 z{sAjye;5h=c1>=4I0&^jKX9b7?&RjFM8D8c%p*31y_Ze0P+#tH+95S<-P1a z6hEBy+}Cq>fXfe{Nf2xiR3;mM4=56j;S#Dm6Uy&(3fLm@U9nM)=#KQA6wkb)^kN2b zM8&4x4>K$EwQ6(N6`1}KTg7#LL?_N6SsrFhW&zkBVp6+X^o4 ztlx35WWzn#Tys^7Y$j4RT(mKYe@Xh2Hu5{G+Ar*9BFAk1Nw;vpNaFAN<%gn zg$Vtp*FAI{q%d&p|78IVYjN9?uAY97VF4$4eh@6ZtK|yUS+Ri!X!Kt2*O#%dNw{cV zc4xi>aRaif7ZoXESi_hFn&XllJr%ewHj|9x&dg0ArI*O=FHaslS~gs-#T1{t?1I!? z**~Zx3lav*_gM-Kc?Z68?}5!)%_E7qjRHrAhSRP{T>dTo=A)(Y;ghNZ@+vozds25&qUNp+F9CbUZnDj!O~J39d{DNb6IL8w0d%sYcCT>- z!09z{wL{J^#iBYC>DTS3)|Bj<0_7B0&fultgZD$mXLPO?fu0ddFX2!3(TL)n^0ik1 znWVTjVl$_o{OT`e*g+NzG;&lb=)i%)Li2)1-u)VOJ-WmC=>@{F>jn}FdO|I0f$X}C zdKHEow_DmtA2;_~lVF3`BpU z!>{#2f*tD%q;7>hK>j4nw+mo4ljn21nHR*-h4K6O$9z`7@N9tI9m1sEy2J- zJ_w7#@OQi4tdQRv)iV&mbe9c*T5U$)61r192RY@;0FHpcJ#+aEz2OZ(Tplz(YBes* z!BB82IuI+wPlg7xfL3*R&_y~p71OuZza<$l4IrrM!gB7z5{V>B+_<;2H@fw%qt1e! zH<1atoiqcR`s*D9+@I#hE`xI}jYM)kwb)o!jnbxUX;IB+McRsUJ->2{4XYkrQHd=| zo>{~Nro9Hz&6yxzpK6vQj;E_67ok|dgpe;btr$tAdA=QT1Trf=S&mFaF{ftz!91TXxg)0KcP4ZYP@WjnM|idze$qFrQ9|pz z{iP{75U?qc?dnwwd`rH9c_ILHubO`)YebZ(xOO{3)|oi&duua;NxL9;spZt5MV&-D zqcS>oFio20xY+&IaV%24uix2*aAWI`;}PurE!l^xzeB%esks;40#=79u#sV$eOq&hA4{?0cQ`;H$*>R{_#Q_?oYm? zC!i)eu!y!+jPMmWm@h}48{6&}a~B*b{WTAchJ)l@f*7Ix`ixnoZ#PN{W;_fk1(FU% zA5^x3|KLl@H|Hj-0Nf@49~|@jy-Jq1C%gTj%+wLt#m^GJ$y0m@AOD66d5|Fh63?L0 zU=|e>yQJKO!qE6mpS!bd&ex#7ZE*K8H`L#%KRuY#S(=GzB#3u?60D6<8#Lh6J)R=TO)9~v1%N96hbY~6uCB}I!tDa0-)_m9Qo!j$PV0hzcsOE-?C z{5>4I+3(%HrTVl#E4SSq$X|QbUQ?Fi?BD$So!jD6cB$yyNJ3!%^MYyb3f;}$ys5A9 zj{4cWWkDL9nN+U1&UiB%6fs#;kEbEjZsJ)&%x|sSQ7fwkXgEql^@%jy{IofKViqje zBO-%hu*;d#max#-MQ{5fpNrw45HvGn)G zPKqvV&t31gRYSb`M)O{oOy2i6OhsXp%bD!WQbrgZrQfs~E+9G5|G_!+tfJW|9uV91 zJweKS&A0XmEFR`iQwTyhZbn?psb$n!$4cKuXaM44-gBL|%m6kXzmIrLbYh2IN zG>aj{exM$@$&F;;nuoAH5M{A(VSH%>D;bFJW?1Gyk`SSLYcft&2z4Y}D8(MAJ z-thr;uwLJ~ClzW_IX)4Ew?HV-4<2yahKASR8h-M5Gv)Gw=%*tS?rfBWy4=HJky^h=gv+psi34t<~kFs{E(m?=zC)#y?rmU$gAq7zDad8 z4DpDM#xCZT1i*?R^b6h|Hh5?M27A0m#nse@zNc4>k!~y>HsfIq9tm1fLmb7I4HOKO z4AqmlV)+P>dOehMi!`Sz4wnLuJy1McQO*WX@XO9!mEPe7iiKan2LPdX+WWG1>9|3w zE%X3jlStf@#(`thM$@0MHW7#Aq`Z``k{)+xz`6+b1vt!&_ib2>e{Z;5Y?lm&5>vOe-G!1f1R$Dk&d~d;Itw{gN#GTGTndA3GgmtFL;%g?`$N8 z-KSWo!knyZqo@1qVtBwlU3&H|hpodeCXeQz{WC~sfA4-YxfZm?BT6WP3Yh$x&evPthK5$4Ye4T+hyD~{ zpiT+7oIVW}fn68T1g@+4D~cJ0!5NM(PxyrJ_q>X_);&4ca6SByQ05F+! z7k*$RiZ+^!2Hf>qZuq2s=fR@P=JU9}L3V)D^`RZj;LeK%Pt7Mi8;=Q)TzO_SN5ezx zaM|3g#p!3_v6}+@R~O#4PGQZ8WbV#ye#VDMNw`_dD7Gmpl*c>=DVf;bZwZ}>443pR zBDY;pfU*L8JpPJM0wXV`ma4w!%st!Q#?%b8zxM`1tjojdf3Sc!nDl#l$hUIXUOPGKkI&n86yzHmmblpoZ8{&=hHBSUr* zlsagAP|rxCgG}NQ8BtS-U}b_=A~CdUJ*gTZ6Exaz5h+uX`;#8^C3_^TN87x3c)uS0 zS9?E9s=6iMZV|(<^~hsm#8NGedD^t_3B~i@j5kSwatRMFf$UxYX5m}hzuJg##)ilR z(}sLjJsA?`;oTkmgncQJuaqd=E!F+%Y;{AXXi*uLf0g>vTAmkhHP`z>MS6=Z?R(sU6 zD~@fpE0P}!aDk!h8uY&ytkNA)pOt;5b0)XW#u-eQp+b8EmpFNa=!|u+bF}1B)p}Tz!pX#ekS*yBuY071?dRD?OYIJ1nlEw~XLgW*)X1eED{0Kx_ zPd|nR`sLE6qiktcD*ROT^oY#W<42-%?cgflwX)f2T}V;_GhU6K$X6L z*bzeq(o{Uf$di%QN<<+8N8}oq`d%~giK!rzJUuK;D&S!FGTl(&AZe;Jy|KcgDv~Bm zJP|?xh&i*P+awd8S1n7o^zbwNbl&Y$qJ#b0XU+C#HyPiZoS3K#N4IJ<P4Oj zB0Xq^KZ7(AFQU0qU<#R7ty@I({Xm0(HiacX6Dx#v)QcgY*cYoR>3a?x z_jeNB>^9TSCX>F60m!!8OI{V`9SDN|U>ZK_EJSqReNQxi?`&hZN7Uo8jk_e-0gIQ> z$H+F!|NbGp$lEdz1cSoOle&xmJG{+r?Jp=vJGUr{SjzraHrO<^5rbriZ$D1I_w?uF z&#b68$&KyC7AejBlY|bX6k+W9ahp?D^=L{fqSeEQP+EH-%_>6*T^Q%)30C z0%9c4?2Z?iqsSSZ;~a;sl-|Zoa~&M=G?1w!2R2d{w)2lGe}`KcT@$r^koh?FoNz%2 zf5+ddt^xJl_;Ps6N<4OMd7UlKt`kduYg&)X(f1pr6Du$M$G{O(Wz49r%yu&jegBtb z%GJkhGOd**&p4)qoH;WRO+E9Hp-`Y3(XzPmgGhPS5Oob7PAhMC*w#E}w#$f99$!so z+OuT^uGG=v2brKmG5?7g)Tt=mseheg0BqY5-HFQJUNxJ?kw|J{H(6{}$|;UsD=80? za(^}fZ~~9}g0bPf@oV~r$7ZB>O$7#aY8y=TUgBEI%6w+*$}Yv_EMK?sU;I6R71JZS zfg%Gz;GKtsD8JL}|L=pL_EE7{{`ZBdD;P2mAbj4@Y3NEVrsa+Q<0t7oejK7?9=odc zsWQcs8CeY!qtvLY5c}WR1twC$`K4xcVmjOnzjE(S9A`9of@&DkUyCgU^(JPl%8;`* zsQ|I-Fg{ix=tZ5_LaDNr=%+m4vCQIjQtQiMWs8Fh*dMQvgx7|Y+4a^wvK|d}J61Ed zck^g&k7jh=KhCl~S>=CJHLm1ozDIQ@t2^II0}${nBdIxqGI6Ijq?+uD8-| z^uTv^TRqCYc$~#8Fbr6%iPn8}^Sy6+@xLsUJ|WR|!%OxeCG7%7V39CqFue}?X0lF)S^(~-x*F0GX@$p( ziV9Qpc#{1aB=34H(4zc0a#P`-oU{v#9VRK5GhJk2N^SBpX-$xc-jf;nGwvXlmiMEp zj-NZeEfG?>6-tPx{Rm-RCKFM6qMFQF|Lr>n2Gmkkfq!FSixO2JwQA1|$G?-F9!kJUlJ;Sq)>9?@#Z=6C&QcJq+!WCJnJ@1xEL(Z<)%~ z4(K@WEx*~Fcs64(xOhJn{Sm*&qC`5iUj&;|Z#KJ;7ikQF#MJ2H*(k6tQDNpi*eH}v z@wT5?ldwk4gkbc`a+lR$Rk7Hz8kUiI*d+F}SA9F3V(Rw4Aj`j%dzTw^6@1^->WN2` zba%bw_s?6&4Rt{tTeV+qY|yiJ(_I1wK~q0V`>j**JWoFP#%v<~G)`Ngz5nP!-T%s# z_Rd(&bbQJ_C_T=^lFgder(jo3DGs!kN)!W8WL6myS}6QMBV*)kKbpoOFWUvh;qA8(kbV#s?r%Qy-wu=rJIfJa zFn`K$)pg5;s)LL1I3oO--UEBrR9~7S3pIDq9KE+CvqxgVj@=0o@|YfMInovYV@WLt zMLtZov03xS6UXbo7un97f=O`I3n*zt zemkikU|U4(R|oevU3MRf<=KZ?ZxRKUG+j-Q!#BSd-+*yAq|djavVkWArRkK_o`V?u zS~^E{r2}s7>32tYr$FD*Bc{^%%6lxh zxBQwM?r4qxRbyurk+fg%=?Z)woZTN9w~?sFj0c2K(FHL)1`L(NNR{~k#7hv+9U=OW z8A=SQV;tN`Hx_`r=QRI;9?+)(==VlmC)N)2n&zbQkKS0OwEdt)eOfLB(LiEp5qaQ# zRsFvmv)*awFHpb5Z|QqM7CA_q0|b0FmX0gj-JoTJ>Y@JG%VjEsuje{I`)*!mMJ*01&8eGij{O$=7a zSvNtu-mPidKF}sq>P3xDN?rD-+#nXCKeG1)RIox*pZ>ie1e(v+dFBdkjDpgzK5qRT zxH7{e#`nzkboawy-o~E(uAs3zUOt6xYFK%$hbmU95Sr&l9O20bh3WST2KpC9?%8CB z03EHo5SxL}ChDO3tb*)?&1%q&TPM8+`xV9ELN-4Jx{#{!nd!RtX^Q@jeWSw5eikmv^?fgMG^YwNRTQ3^QPE$sUK+=uROUFq38@&IOcAFIm^0 zdwLi6{?^huq%{f*I$rmv^C)Bg{B)ME$IB*KxMo~B0SAw;*e*6_!-IZO^vz$J(U%4% z;Pm&o;^%>=iNGkS>*K9DE$?Hax=C}XYyKu4c|(Z-Hr-siChbTmLfkgHLCbzkqc3FM zTt=l6s6(U;KD*Dji$~bx|8j=Zx&;2iN%z-qwvZChd0taoFTFo2$Al z;vz|Cd48qoFmA;Q`2qXbl?oNm6Wl2{lvFp7neMurEx6dRc zwj}pclxE*zX+*iM{;G7QEvpHJ!xG_%TV`h_$6%pVH|UxgioyJXb(4-v9rBYWHN|&Y z1DrJXO_`G>5QDNI^2~RiC>~!<&tQ7JwJ3-2{JL~p~ zF!Lw@(}pn9TC}Kkg=e$KgzlE#Ay8ZhZoFwt{Od4E!lM8c!iLCq2AiTlB^r(G%a59j zLN-Ru8UB#`JPHOV-aP^a(8sHV>=)V122ZKn5C9P*3A>)v>Smmglv$Rzir9G*lRhb?n@M!sMiR$fwRJ?pP6uuGP`}YGdyEUT>1sWDM6`%%e$d71 z^b;Rs=M5VkZ3}c=?xd5LiTKxLD$7x)kj_~!*)n5M0zzvQlT`A%dq{QZaV7MJaBxyh@1n*&cy z3<(8ztz{e?9j6D2N%&3zExd$xmgjx-k({)){l~C@u3-J-=`MZ-!+~XmYy;@?NR_jZ z?x5&Wm_aKm0wG=#Tu?XYmFBG%q@sg{e#^<1{2xlF^gcVo=xwWF%=)}|lv|t};DS1B z+s_pXbyJZJH36DE2-*VXgRHa=@6McPFLD<2^E9a1FV}njWIN`N$*%#XK6@g~HK-E2 zDqL`mR3=7~Q6n2{xIm)j)3ngLbK06?DZpo5_bu9xO|b8+%qc*{Qf?wbY*C<4YP*_r zovcful!?qI&wRj2o3kP4f4_(d6;sdeY$n)0z(DJ0vbAC#UO*a98Q+#YT8Rm}S$$G^ z>G&%NduXOnFE~nw#(7BId7xEVVw=WBMvsaVQyZ-4^5t|l+%Q3Jd*h1D0Q_E!ekXbz zfR^Na3{=GZ4~CcT1?m4k6`nu^@b3@JZC=X@Mc1ict^vbh(eB-}JtLinWUHh4JDk}l zNmSn9{Vm(;1B`Wc-AgKg`#;~tQ}Tl=6OS*9$vcRKvCa`dn^~gWe>p>MlUYa!Nh#er z{{!|etNq`5F`j;=FFB2Kq9$;Lf9Elrpi^W>x87S&7-XBYvo_A=y3JvsDy5y#jMM(|8%aKg^V83e2tVAy6UX#8 zWhbHBQli2Q9w1(ciBxDL|NFWq;dBNqCGk1UA)cq^ajtG(53$z6~c z=)I#O5ERpd+@xP5u$-8`;|%9EP6dq1H>C&cjmE?L;qpEP?VU)}`LXS}K8-@wvS|u% zB*74m6y@MvFSanGUqXW=1?t7rH1P8Oygr_|dqn;D>6nKssT^k>EAZI8qD9JPxTms! zF<*g=>76^HlPFu8(O5v)eaTjSp#TFQGM-1G^YDwR+h-K5$EU;Pi2`&`mCR3Zh<9Nh za8>mFApNqx8B0Z7S70OSgtMWT$CO_tc$-p$S7NnWR%;WLkZ0P;VA<}dzr6U&e3&9G zZ?(!b4&Qm~+*=;h{X{2GZ@z8tMl^?_E%NqvOM3R8eg5m~GvQcnZ)bAEWzb=cgBlHb ztbs6L4=mTensHMz06uHoy*V};M}z`Md4O*2T`=nV*L7Be(U){@v`V_lreN`j|2>pI zWc~FAr_+hSzyUUK5R&VK9rQ{IM;uyr$>rps{jV80o^B9rU-s!#rIRv4RAZ;ihpfPAzXg7=azxGhfa8`0fNZVR2s3Svt zcduyFX_Yr%nO`hzqlVnii5x~b$-p5rXtjh!)mO!~> zGhU-@pZw8IsS0F*mANSGEV-;js2sJzw`eV&)h`qxV;xSb`d}<#+frD@o;`$|T3gX_ z=tyNj#FiG3OqA|lk-#HiOl3JnJ+XZsiCe}y!m(*R_*=SfS6Xjn*SdS&F#6HjWaqUh zGIF_9;mg(30$WwEZ_B6uLe0@T($EPq+(YY*Y7Q`O%yS|lU0R)Zp0r7#11A1cz5W;4 zNcnHCueSH0x=CJbg!YLc(g9oqwrInKK;Ra}nviF|jEwIj?%OZs59ZrPWcTF~LKo%M z`^wtz2lDc74u4)tLH47H2O|0)E7c5H1bz`UvTG-vN3LLy!|2{Vs2)kzu%F=L8TOCu zK@K;nC(V}Qa}LW!w18yPOPniT_j*Ow0TJ8M>WA$sjhzQtZoC@z-*8ogV-0?PmG75+ zvPDgnujp&nIy85e*z=^1R@a@OVIOzg>43!~ifhjt$q4nnc~cxRy=T;<7#%aE*o#o% zvIlX(kgHFR?`BT_pDzGL{V?Dti!sv?=v7~vYYUo^XiCSKjpmc!76;*xtRJ(jMzRbt z@!D${>_ex2JmOv#ye;y+%E7g7gdtbBQ&~^oEEir~6u@LaIv2z(k}!K!tuvGSCaWQV#4%?h$xY0rxypl0P|-l3zrRqt=l#&8#t zC|A&+Eg|CJP0dtK(73#-r__?>0$-;>pTlw#rm>7nB3STHuM)`pyQMhfW~>>jj9;O( zV$F%CC|?@&Ene*%4Eck=MDcC`v!5{38wHh4wN@DzGfZRCOfBd43HpHp!?XO~VjV2R z>9uxe`MqF^ctG!NCoAc^1K{(t)z5s%tyzXyLrQomN`;p)XNg#ORr=vck`Nut6(A&= zF}}JxF&8kepFC&Y;Z5NbmbXj>shJQCcM84`j5L(UrP^)P-7pEz zvL@!8^#25?#H{anqf7kLim2+int1G&i?Pv#;_!b7=S6me$l2@X^g_06t(|v&g(IXJ zwS-Xk{uql~>;21HrN}?-Wiw2PfYRdCQuA{gAO>CoaY$v7 zJFO7h<(yL+(d$V)JE%>BK1Z5&x~cU9$@Vq&@Rgr_#38)n6yCa%{K!iIzz@PTOm~ir5V!Cr^+EJe7c+^Q$pi36QyB1$JK{#cYtfd2GDR8fr=K59J9 zz89oilTwG56u&%2Gi504-W!+>ktF#Y}QN!yv)0_0UFlVJk( z4{@P;S}?ID7wj7=rP}*Ahr@yTKBq9~Ciw9Vd-oWnenCS|dj6UAKqFt|8|Gdm81E`A ziOrNmq{lqSx~+UzZd}Ir90I>cta8wfm;FZ;R$VkLwk+sTNnFy?aTsz#3*4_a!on|z z1B*w3Hk2mA2O6$f@LX68^4Yb&3zb%k9qK9F1Z8EJZWbZK=%=hJ@pyylOzq z@-ps9dGA@Qsu*G)tf27mS9v|`AYz~py65Sk#G{8zJb?_<{e=)V_yQWa{*4p66(KB( z1^7AzMcqED0B0QRV}RoSX1(Xo%Xhc28|06K1N5kG-#EKhUns-88|~ZT#CU1)W}If=#)$gDsOb<^HH$Ds$|>Q1@q_vB^*bi`m3IoMDSPELXkaX-=!;CXDA zG=m4$4HVBTh@-r!-i@AfWVx-5!I=rN=6E)L`5+?hJSIv7K3#*L)fniQt6kSeb3`B& za~JQ7K(dn%zH?*PdK@S1=~PQs(l@PZ`d2V13hX|!2<5qpGu%{?d1XM>U1#|}3znwO zE{MD-m@@nEZTho%UGf(Q$F&%ZpMqWjB9zf2CY|~0QZ@N2izzk8>JXKahcZ980A&*B zH=F`Nb|jha-!25qRfYB4+E|y^XFOF@*_N4rS>@g8-}+U!CMkJ?N!W}6nU<5W{WTTB z<8f*HzSIjm4diRs##+6WI~0{6ySIdQ?NJC`M&pb_I0worNfsTU7s}Otk`3~&k-Rzv zq^~^W7HYTWDj78xWK6mF?1EZ+FV-mxNTBl{CtC#Py8dG*^?eSK8|CiCPt_rKhlIvvcZ5^ zc$rqA4X!0O1nH_l@MrBC3vNY%ZI}EWY)Awr!RKd0xlDV_2o6g^HIqlXUM|M#O5+x| z8tx79(bC{@zPy8QH+rL=LCDI#n9JbCe+-K#9=Fkq2Y!{~xmn!k3oGCWrF($bMsqXbKUv&NflbAy+yxpscUo z;!$5tbo<)=Vs{s1|HfsfJX{oq?pzv?+X&?)T-DbU>^GSiWPx!s{bOZ5c8ca>Cbej! zqy1=TxIj5NH}C4;zKwhrZInv!a!hn@oF}DtrxT;?bPQ$~O%pvMdPMx;`6;}98yHPjaU9G5g$ru$|6#dBALFV z=8Brl&!OMpv^jy5-JJeQVvAKG{~!}z`A(4;`nQMzJPT;5Z+;LDlTHqMkMVhLiT&!5 zpfC2u_O6BjEJ$P+>eW(|Z&un~b7yEhsSC99P5XCA>ClFuq5#s7!^|H#u1rnv(LB6!R@g}Df8p=cT{ELXTvmf^rD#7*U^Lo0y zYARhj!GEaBm#u&toYwxW>pxsMJk@BmEH@|~o|~G(@W3Ke>$;0(8s&)fO2P3kLoTk5 zK=BIujMxvm{OOyOE2?{yW#m(0@P8nuc;L0b*LbEeyT$SPEys+3Ey4P-g^!gsUz}(x z2Uc_Jo>k;YxEM|Ob06$!c9*k$iRs=CEE$Zi_I~3=dWZ*`UlXvO#TIhBaC(SqBV;I+ zEw%fvj`S9cYklak>;)~y!G&J{?APFGO|kpS{*5&A9AY5acYx$kZm{g6IF-&Umvt(! zgD}%p20l6r!8J4_5OO25e+{YUF$XE0gA)O}f&T zN$XYWh1aUVT&Ldom?2h&zTwg>b+SWSTsT$NxC z9E9M@M|<8x(IX$+!^FB+SF#8Z_Rgx8PXtZ#;_X{10;~hd-Q91ql_^|kRJTBFf@|);w74v8!-e87x@{ItE z@eOqiF5vCr!#l=QNj63Spnq)9^GT=D3IYR+jx{smTTFEosnsuiG>!CiPQe+XVsy7) zKJ(lI7e0`kj0r+#Ac7P(cXx(jW^en~+%T6Xc+h4utvMRbzeFsUCjrY7$gIb)1C)m& zWwC*Bcq;sdOpPE={mOS`Igxprm4MYv?p)V^OlH&M(wRf)^S`I50rQNxq|giL@Ar}8 zQ)7q{f+tL^^pP|_m*soEHtf&F+60pVGmPPn*A0S)%1gO^Gbm6%Zr3NIv@wvXLbwOen9g$fB-eO4M}lb`{%^7%=v-$msfoO`;qKY@!%h zw=fZO1VOV~RCSq8igIOfVB&!MLzj$;jL}+26fE&V&(ahcJnOf~s9W4Vj z9vuLYSmcCRM9%nL)PaXJy?WS12)!r?hfN>F_a{f`<=0W&wE2ptsR-t_QL{x}W%n7u~! zh&{DW+iZ@suK{Y69_l^yw~=W2gZp+y4xFu4kSeh6l?9%3!}hJ~SrNNuG0?YZ9sM)x ztB*Aj4LTk>H3|N%*C+?1s z@j;XZk|hX5A`%E3lu-Cp5|ieh6qu*#O)sXeF+?I2w_`8Bb`Qe;Bv^?GrGW z-r}Qvth6ozQ)N*)$;~4#H;V9ms3t(|DiaQifCpHHzwxC$OR44CeLs1N}rufD8k5w?zP-^))Gx(M>|}lpO)RawEDh{@)T2 zBHn>hQPTIM;tE}R2cI�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 literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml index de21978..39acb5e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,4 +4,6 @@ services: container_name: extracteur-pdf restart: unless-stopped ports: - - "5050:5000" # Choix du port à définir en fonction des services déjà existants \ No newline at end of file + - "5050:5000" + volumes: + - .:/app diff --git a/requirements.txt b/requirements.txt index 12d2ec2..119b1e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ pdfplumber pandas openpyxl xlsxwriter +odfpy diff --git a/webapp.py b/webapp.py index 1a5da79..59f2cf6 100644 --- a/webapp.py +++ b/webapp.py @@ -1,277 +1,207 @@ import os import io +import json +import re import pandas as pd import pdfplumber -from flask import Flask, request, render_template_string, Response, send_from_directory +import unicodedata +import traceback +from flask import Flask, request, render_template_string, Response, send_from_directory, redirect app = Flask(__name__) -# Dossier temporaire pour stocker le fichier généré à l'intérieur du conteneur +# Dossiers et fichiers TEMP_DIR = "/app/downloads" -if not os.path.exists(TEMP_DIR): - os.makedirs(TEMP_DIR) +if not os.path.exists(TEMP_DIR): os.makedirs(TEMP_DIR) -# --- DESIGN HTML & JAVASCRIPT --- -# Ce bloc est envoyé en premier au navigateur pour préparer l'interface de log -HTML_LAYOUT = """ - - - - - - Extracteur de formations - - - -
-

Analyse du catalogue en cours...

-

Chaque page du fichier est analysée pour extraire les fiches de formation.

- -
- -
- > Initialisation du moteur d'extraction...
-
+TEMP_DATA_FILE = os.path.join(TEMP_DIR, "temp_data.json") +TEMP_MISSING_FILE = os.path.join(TEMP_DIR, "temp_missing.json") +CSV_FILE = "/app/corresp_IGPDE.csv" +ODS_FILE = "/app/corresp_IGPDE.ods" +RESULT_FILE = os.path.join(TEMP_DIR, "resultat.xlsx") - -
+# --- GESTION DE LA BASE DE DONNÉES --- +def ensure_database(): + if os.path.exists(CSV_FILE): return + if os.path.exists(ODS_FILE): + try: + df = pd.read_excel(ODS_FILE, engine="odf") + df.to_csv(CSV_FILE, index=False, encoding='utf-8-sig') + return + except: pass + df_empty = pd.DataFrame(columns=["Nouvelle nomenclature", "Nomenclature IGPDE"]) + df_empty.to_csv(CSV_FILE, index=False, encoding='utf-8-sig') - +# --- DESIGN CSS --- +COMMON_STYLE = """ + """ -# --- PAGE D'ACCUEIL --- +def get_nav(active_page): + h = "active" if active_page=="home" else "" + c = "active" if active_page=="corresp" else "" + r = "active" if active_page=="result" else "" + return f'' + @app.route("/") def home(): - return render_template_string(""" - - - - - - Extracteur de formations - - - -
-
📄
-

Extracteur de formations

-

Sélectionnez le catalogue de formations (PDF) pour extraire les données vers Excel.

- -
-
- - -
- - -
-
- - - - - """) - -# --- MOTEUR D'EXTRACTION --- @app.route("/process", methods=["POST"]) def process(): + ensure_database() file = request.files['pdf_file'] if not file: return "Fichier manquant" - - file_content = file.read() # On garde le fichier en mémoire vive + file_content = file.read() def generate(): - yield HTML_LAYOUT # Envoie l'interface au navigateur + yield f'{COMMON_STYLE}

Analyse en cours...

' all_formations = [] - COLONNES_ADMISES = ["Référence", "Durée", "Type", "Publics éligibles", "Niveau", "Sessions", "Tarifs"] - try: with pdfplumber.open(io.BytesIO(file_content)) as pdf: total_pages = len(pdf.pages) - yield f"" - for i, page in enumerate(pdf.pages): page_num = i + 1 - percent = int((page_num / total_pages) * 100) - + percent = int((page_num / total_pages) * 85) try: - # Zone de détection (haut de page) check_zone = page.crop((page.width * 0.5, 0, page.width, 400)) - text = check_zone.extract_text() or "" - - if "Référence" in text: - # --- EXTRACTION DE LA FICHE --- - header = page.crop((0, 0, page.width, 100)) - titre_brut = header.extract_text().split('\n')[0].strip() - # Sécurisation du titre pour JS (on enlève les quotes) - titre_clean = titre_brut.replace("'", "").replace('"', '').strip()[:60] + if "Référence" in (check_zone.extract_text() or ""): + bandeaux = sorted([r for r in page.rects if (r['x1'] - r['x0']) > page.width * 0.7 and r['top'] < 300 and 15 < r['height'] < 200], key=lambda r: r['top']) + if len(bandeaux) >= 2: + titre_brut = " ".join((page.crop((0, max(0, bandeaux[0]['top'] - 2), page.width, min(page.height, bandeaux[0]['bottom'] + 2))).extract_text() or "").replace('\n', ' ').split()) + categorie_str = " ".join((page.crop((0, max(0, bandeaux[1]['top'] - 2), page.width, min(page.height, bandeaux[1]['bottom'] + 2))).extract_text() or "").replace('\n', ' ').split()) + else: + header = page.crop((0, 60, page.width, 250)) + lines = [l.strip() for l in (header.extract_text() or "").split('\n') if l.strip() and not (len(l) > 90 or l.endswith('.'))] + titre_brut, categorie_str = (" ".join(lines[:-1]), lines[-1]) if len(lines) >= 2 else (lines[0] if lines else "", "") + + chapitre, sous_chap = (categorie_str.split(">")[0].strip(), categorie_str.split(">")[1].strip()) if ">" in categorie_str else (categorie_str.strip(), "") + data = {'Nom_IGPDE_Key': f"{chapitre} > {sous_chap}" if sous_chap else chapitre, 'Chapitre': chapitre, 'Sous-chapitre': sous_chap, 'Titre': titre_brut, 'Page_Source': page_num} - data = {'Titre': titre_brut, 'Page_Source': page_num} - - # Extraction du tableau à droite - right_side = page.crop((page.width * 0.6, 180, page.width, 600)) - table = right_side.extract_table() + table = page.crop((page.width * 0.6, 180, page.width, 600)).extract_table() if table: for row in table: if len(row) >= 2 and row[0]: cle = row[0].replace('\n', ' ').strip() - for col in COLONNES_ADMISES: + for col in ["Référence", "Durée", "Type", "Publics éligibles", "Niveau", "Sessions", "Tarifs"]: if col.lower() in cle.lower(): data[col] = row[1].replace('\n', ' ').strip() break - all_formations.append(data) - yield f"" - - else: - # --- PAGE IGNORÉE --- - yield f"" - - except Exception as page_err: - yield f"" + t_log = titre_brut[:40].replace("'", " ").replace('"', ' ') + yield f"" + except Exception: pass - # --- FIN DE BOUCLE : SAUVEGARDE EXCEL --- - yield f"" - - if all_formations: - df = pd.DataFrame(all_formations) - output_path = os.path.join(TEMP_DIR, "resultat.xlsx") - # On utilise xlsxwriter pour la stabilité - df.to_excel(output_path, index=False, engine='xlsxwriter') - yield f"" - yield "" - else: - yield f"" + # --- MAPPING --- + mapping_dict = {} + df_map = pd.read_csv(CSV_FILE, encoding='utf-8-sig') + for _, row in df_map.dropna(subset=['Nomenclature IGPDE']).iterrows(): + mapping_dict[super_norm(str(row['Nomenclature IGPDE']))] = str(row['Nouvelle nomenclature']).strip() - except Exception as global_err: - yield f"" + missing = [] + seen_missing_norm = set() + for f in all_formations: + full_norm, chap_norm = super_norm(f['Nom_IGPDE_Key']), super_norm(f['Chapitre']) + if full_norm not in mapping_dict and chap_norm not in mapping_dict: + if full_norm not in seen_missing_norm: + missing.append(f['Nom_IGPDE_Key']); seen_missing_norm.add(full_norm) + with open(TEMP_DATA_FILE, "w", encoding="utf-8") as f: json.dump(all_formations, f) + if missing: + with open(TEMP_MISSING_FILE, "w", encoding="utf-8") as f: json.dump(missing, f) + yield "" + else: + yield "" + except Exception as e: + traceback.print_exc() + yield f"" yield "" - return Response(generate(), mimetype='text/html') -# --- TÉLÉCHARGEMENT --- -@app.route("/download") -def download(): - return send_from_directory(TEMP_DIR, "resultat.xlsx", as_attachment=True) +@app.route("/ask_mapping") +def ask_mapping(): + with open(TEMP_MISSING_FILE, "r", encoding="utf-8") as f: missing = json.load(f) + df_db = pd.read_csv(CSV_FILE, encoding='utf-8-sig') + known = sorted(df_db['Nouvelle nomenclature'].dropna().unique().tolist()) + options_html = "".join([f'