From 44557f3d0c2a2949a2b263fa4527ad773e6ef782 Mon Sep 17 00:00:00 2001 From: colaftc Date: Mon, 26 Jan 2026 21:27:29 +0800 Subject: [PATCH] feat: added print-template(test) and sync_mdy_plate_order beat task setting --- .tmp_docx/document.xml | 2 + .tmp_docx/mock-rendered-2pages.docx | Bin 0 -> 22982 bytes .tmp_docx/mock-rendered-3pages.docx | Bin 0 -> 28276 bytes .tmp_docx/mock-rendered.docx | Bin 0 -> 18720 bytes .tmp_docx/rendered-demo.docx | Bin 0 -> 17831 bytes .tmp_docx/rendered-pages-test.docx | Bin 0 -> 21301 bytes .tmp_docx/rendered-test.docx | Bin 0 -> 17833 bytes docs/2026-01-26_summary.md | 58 +++ env.example | 3 + flower/settings.py | 19 + print-template.docx | Bin 0 -> 20807 bytes printing/docx_template.py | 319 +++++++++++++++ .../commands/render_docx_template.py | 78 ++++ .../management/commands/render_mock_docx.py | 96 +++++ .../commands/render_printing_order_docx.py | 110 ++++++ printing/print_docx_services.py | 366 ++++++++++++++++++ printing/test_docx_pagination.py | 109 ++++++ printing/test_docx_template.py | 55 +++ pyproject.toml | 1 + uv.lock | 59 +++ 20 files changed, 1275 insertions(+) create mode 100644 .tmp_docx/document.xml create mode 100644 .tmp_docx/mock-rendered-2pages.docx create mode 100644 .tmp_docx/mock-rendered-3pages.docx create mode 100644 .tmp_docx/mock-rendered.docx create mode 100644 .tmp_docx/rendered-demo.docx create mode 100644 .tmp_docx/rendered-pages-test.docx create mode 100644 .tmp_docx/rendered-test.docx create mode 100644 docs/2026-01-26_summary.md create mode 100644 print-template.docx create mode 100644 printing/docx_template.py create mode 100644 printing/management/commands/render_docx_template.py create mode 100644 printing/management/commands/render_mock_docx.py create mode 100644 printing/management/commands/render_printing_order_docx.py create mode 100644 printing/print_docx_services.py create mode 100644 printing/test_docx_pagination.py create mode 100644 printing/test_docx_template.py diff --git a/.tmp_docx/document.xml b/.tmp_docx/document.xml new file mode 100644 index 0000000..1d92cb8 --- /dev/null +++ b/.tmp_docx/document.xml @@ -0,0 +1,2 @@ + +#{打印-销售单(8*9).印花销售单} 第#{打印-销售单(8*9).当前页码}页 共 #{打印-销售单(8*9).总页数}页 #{打印-销售单(8*9).客户名称}#{打印-销售单(8*9).仓库名称}#{打印-销售单(8*9).日期(订单日期)}#{打印-销售单(8*9).产品名称1}#{打印-销售单(8*9).幅宽1}#{打印-销售单(8*9).规格1}#{打印-销售单(8*9).销售数量1}#{打印-销售单(8*9).销售数量2}#{打印-销售单(8*9).销售数量3}#{打印-销售单(8*9).销售数量4}#{打印-销售单(8*9).销售数量5}#{打印-销售单(8*9).销售数量6}#{打印-销售单(8*9).销售数量7}#{打印-销售单(8*9).销售数量8}#{打印-销售单(8*9).单位1}#{打印-销售单(8*9).数量和8}#{打印-销售单(8*9).产品名称9}#{打印-销售单(8*9).幅宽9}#{打印-销售单(8*9).规格9}#{打印-销售单(8*9).销售数量9}#{打印-销售单(8*9).销售数量10}#{打印-销售单(8*9).销售数量11}#{打印-销售单(8*9).销售数量12}#{打印-销售单(8*9).销售数量13}#{打印-销售单(8*9).销售数量14}#{打印-销售单(8*9).销售数量15}#{打印-销售单(8*9).销售数量16}#{打印-销售单(8*9).单位9}#{打印-销售单(8*9).数量和16}#{打印-销售单(8*9).产品名称17}#{打印-销售单(8*9).幅宽17}#{打印-销售单(8*9).规格17}#{打印-销售单(8*9).销售数量17}#{打印-销售单(8*9).销售数量18}#{打印-销售单(8*9).销售数量19}#{打印-销售单(8*9).销售数量20}#{打印-销售单(8*9).销售数量21}#{打印-销售单(8*9).销售数量22}#{打印-销售单(8*9).销售数量23}#{打印-销售单(8*9).销售数量24}#{打印-销售单(8*9).单位17}#{打印-销售单(8*9).数量和24}#{打印-销售单(8*9).产品名称25}#{打印-销售单(8*9).幅宽25}#{打印-销售单(8*9).规格25}#{打印-销售单(8*9).销售数量25}#{打印-销售单(8*9).销售数量26}#{打印-销售单(8*9).销售数量27}#{打印-销售单(8*9).销售数量28}#{打印-销售单(8*9).销售数量29}#{打印-销售单(8*9).销售数量30}#{打印-销售单(8*9).销售数量31}#{打印-销售单(8*9).销售数量32}#{打印-销售单(8*9).单位25}#{打印-销售单(8*9).数量和32}#{打印-销售单(8*9).产品名称33}#{打印-销售单(8*9).幅宽33}#{打印-销售单(8*9).规格33}#{打印-销售单(8*9).销售数量33}#{打印-销售单(8*9).销售数量34}#{打印-销售单(8*9).销售数量35}#{打印-销售单(8*9).销售数量36}#{打印-销售单(8*9).销售数量37}#{打印-销售单(8*9).销售数量38}#{打印-销售单(8*9).销售数量39}#{打印-销售单(8*9).销售数量40}#{打印-销售单(8*9).单位33}#{打印-销售单(8*9).数量和40}#{打印-销售单(8*9).产品名称41}#{打印-销售单(8*9).幅宽41}#{打印-销售单(8*9).规格41}#{打印-销售单(8*9).销售数量41}#{打印-销售单(8*9).销售数量42}#{打印-销售单(8*9).销售数量43}#{打印-销售单(8*9).销售数量44}#{打印-销售单(8*9).销售数量45}#{打印-销售单(8*9).销售数量46}#{打印-销售单(8*9).销售数量47}#{打印-销售单(8*9).销售数量48}#{打印-销售单(8*9).单位41}#{打印-销售单(8*9).数量和48}#{打印-销售单(8*9).产品名称49}#{打印-销售单(8*9).幅宽49}#{打印-销售单(8*9).规格49}#{打印-销售单(8*9).销售数量49}#{打印-销售单(8*9).销售数量50}#{打印-销售单(8*9).销售数量51}#{打印-销售单(8*9).销售数量52}#{打印-销售单(8*9).销售数量53}#{打印-销售单(8*9).销售数量54}#{打印-销售单(8*9).销售数量55}#{打印-销售单(8*9).销售数量56}#{打印-销售单(8*9).单位49}#{打印-销售单(8*9).数量和56}#{打印-销售单(8*9).产品名称57}#{打印-销售单(8*9).幅宽57}#{打印-销售单(8*9).规格57}#{打印-销售单(8*9).销售数量57}#{打印-销售单(8*9).销售数量58}#{打印-销售单(8*9).销售数量59}#{打印-销售单(8*9).销售数量60}#{打印-销售单(8*9).销售数量61}#{打印-销售单(8*9).销售数量62}#{打印-销售单(8*9).销售数量63}#{打印-销售单(8*9).销售数量64}#{打印-销售单(8*9).单位57}#{打印-销售单(8*9).数量和64}#{打印-销售单(8*9).产品名称65}#{打印-销售单(8*9).幅宽65}#{打印-销售单(8*9).规格65}#{打印-销售单(8*9).销售数量65}#{打印-销售单(8*9).销售数量66}#{打印-销售单(8*9).销售数量67}#{打印-销售单(8*9).销售数量68}#{打印-销售单(8*9).销售数量69}#{打印-销售单(8*9).销售数量70}#{打印-销售单(8*9).销售数量71}#{打印-销售单(8*9).销售数量72}#{打印-销售单(8*9).单位65}#{打印-销售单(8*9).数量和72} #{打印-销售单(8*9).匹数}#{打印-销售单(8*9).总数量} #{备注} #{打印人}#Relation.Repeat[start]##Relation.Repeat[end]# \ No newline at end of file diff --git a/.tmp_docx/mock-rendered-2pages.docx b/.tmp_docx/mock-rendered-2pages.docx new file mode 100644 index 0000000000000000000000000000000000000000..5169c5fba90df094e7a7389e75bd3332040f6066 GIT binary patch literal 22982 zcmZ_#b6{QF+QtjVZfx6)%_fa)HMVWrwr#Vq8oP1hG`7uerG1{g_xqi5{>Vz!Tr+cx zd5?QsW8A-M4mn9sFjOEQASj@lQZCKFI$mN$U?3nAaKI1X=(n#n){aKjjyg(iwnh$G zbgovGO^IVNeGG`g_nyLu(&&_SV<1Fxjj;HV2l<}9bre-WI#*JX&-YR@Lm+5^RF=eK z4(Gg!1;Yg+%S)Ev(9#!9jFk|=5Zd5YFCBE+#xc9naYW)ZMW67G^g-361~Cej$U%%P z@6`5T^Id7c%E39pltVY&W&(FeFD=p|DCaXW16r`*s)Jy%dlbd3MELO6zLKOQ*N3qB zoQ3Z|)9x*;sr;%x(i}96itl%*yLv(?T1BFEcE;9B4NajP9*`I0e#EZ5=DjwKpN^F_ z%BMI~BXg9SMJ0w=P~9lOEyGG5q}+@wGLQtfBKp)8i3=SDhs)(Nrb5(vc<~yoaX->e zreJGA;EHqo^}$0-1GjQNe}!d_`pZuD%F+>o-OR%}{-*?!tNAzmb1z}|=}t1l&|d*Q z8+tjbI(}^EZv|gcRw5|640NAyv$gUgmPmwMN}~9I@jWP=a zUgN9D1e0J|Rc`)p_HmYe-MNnPq#oS@96^)0u{}HI5->(ENCP<2IehV|^om{P=$@jG zC!#pY*e@b?T7^}O!6)Z}ox#ZgP3DF$bbAkQ#(N5WbgM=uFP86?GO+$7h_Yhrav z6g(k#E6%kKh^Gq%n<%m`5~xf&@Jqw@saeiByXCq?d{zw{8++&CO`;HxaLa>{4T+L2 zd%%a^N?Ul6nDCGl1+OKmAzdPS^DCFtA`={R<^1tp$iW7K^?}7f`@C-5m zL-x`E0ln2h03iXw<6>iPNN;Fk;A92J(~kr_%{sTi6?Na=*wA5=)5hfR)qkxvCsFvR zwRVbm!4f!_4^N}dpN2|Tgqlr@ye2Y8ch6p@jD*Y*Hc+uW!d@W_lp>7OPi)@m#2UU?fV~+ zFFYP)^M}>1}QnT$;Jp z^eQ;Mx?EhDn!NsKt?2tbIaW~-a9fdedbjyD;x}xsIQik!}6_u;k92WqsK<*7@e_q2{^LUEj8Lrn9o9PBqyz zi*H?RWo-q&K{q)u-fQNLF?o8|PA@B4XOAiONI@?PlHP^;BHTUsEb~U1 zO&G58Z1ZSua@#ob&T;^u20wG>DFNHg%j0H1vsR|+Q|DUNkjcu6 zw~0rS$LWRUK-4SCe!BKffi#Y1w=SKQ)wyGI=EOOCaOY_wuO&gY3k!A@y|=x0r^k!; zZ+oY`n8~K^i#p!763vS?HCAX=G&?iY)jAa&0+U*_znT_ST-!bIs;#7T)(>R6TUb0j zDrT`eJv%#{erNJEbMbaui*$3e%(wE4KX;xsbFl|wOxg`iqCq}qs8-|iYcM!rx-8b* z!Q{e45F`$T+pKePe4p)_p^8b`e`|o<-|1cS@&3IUZae)O=Uv<`zv^yMKxj?o^7S`@ zj~XtWHPvJh+dTC8I#qhRU4hpFPK`4=d3PWBI}cU$SAqAV_VwTGiHn96FgBgd$|4!6 z@8yc9Db3pyqKnJNi`A&*C)O8Nq5&>+nJDuZ+^4rPkID1xi3H0IoR=ClYMK}O_D4tQ zd@0N}&X5^6S^VD7`)>qOzgH`U`s=^nYCNB=pIyi)r?A^NvcWs;a)0~AZ+cH~d;|t9 zGhp{a7NO<>nxDT#3f8BC3upM~#OqDM$D!cmO5#$(6LA(N%#M%qdR!0h5L`6N0XEvc zqTjzyMNL6e*$|yIt#v5pq?dw&c6fMxjQiC4c|K~(lgT;K=&KF>XW{e#o58ps*ZL{Gg}RAwnyi1HTlTk(Ke8av9&X~(V#+a zd4`wC6^@blec#)5qEUIu6$}P8RdlsexMerrD6Sm^8UpF!37S0cJ{2KiDUPzvCzRJ=S5S6n!Y8D7wyBSjSucQy&>&NhryJ#yX z=q8^x*zqmDSBOC#X9&-9-oJalUcA4Zi}L%NPX}~Y&{=7&LJc7hyzE})R3hv}OY>#B zz;A7xZH5wz6Reo4=IAp6B#ti%Y+i8yUd<+U&SRE^e{0* zCMO_x3&QATN^ z3zt6UpGCBtztmwU-><#T*E|&Xyh&S}cY1u5_qIz{sM%U|n~*;eF2g8VS4?pa3Rily zettsWdZh``(zfGASa}8Y&QXr3MxIJg%M*R?;^o4?G-s_<|Ajf za^d4@wctad_M5kjkRn1^$PLE1xL~YwgFd#boYDO9kuuoU#T*W`tbY1O)PsiOyR?H> z!j#3%!jt9$N{9`+a&X`S!--@4YBv&cQl~t@(CAo|!U6h6cM{sW13m&uuir|4vx%xP zvDGCDv}9y zsNIb-6k*YJLfeyNm(dc27yhG$@X zZly^kW@~7JQ1HPoAfq@H^f6}ZSzMGMxrK&mCr+AKO<#4upDHT(nNx8l0_sydQ{jC-x(0E%sXo>T~Ae|EVQp|uis-Pw!$EtiWu&0H~B}R3jq<5G3cVWvW z+d1nruK5Z%lT!UWdskrHf)m>=F26jGHO*qL8#`I^f*3_H9(i6i*VpO!3)}lk))mMH z$BLx3pAiPny&2uAUt#9S39J_0UVSpmL^>lGD~I@C^Wb8lbhzeNzz8!ho_f+R)HaHj z`zb2zcsRd8TS!MaGu=|pZ-VkJC9zMed+0>-w+r7wl#hfC$SRmueJA!Xs#UsyvdB+p zClyCY`HaVtY>ivMGT&YauRqKh&c`*{Rvqfv;do#Qg$U=1s8BEr;a#4csDg6Xw_Iq4 zi)934QC?1h%JG1!u(`<$%qOX57=N&f6TW-rh}MYz!ca+WtN!cDDH!ltdkM4 zl~zK6nN)!9D&al!Glrs(0e-|=us|j_p{wd^I5X^j9`O#78 zLVd8G8befX{gFAVPFu&CoqJU7gcqgLI+Ms6f_kW{NYQD+dGJNl>2?uKPN{IzQ9r9! zLQ*hXvpOl6638IePiYIYrB%YM92wenR&lNLGB`{c+Y}K7cZd9o^`S$;0h#Br?AKvD zR`+?bR*|?*xV6PV7I_AzWGenhN?PC+wOqL$M=QyD%neB5%Amqn)#s65a6RFvY%kJ) za)Q2-N0FLGfhjkWHi?X@gRbf2a?|Qy^mW8&MZVI4DXU&tzOSn)^#%)Z7xoAo2Q|Wv zEe;sh#W4*KJNlQ?5LIF4m~xXYf4PShc6nq%+Ni9F7;lVCo4#=JtAX{@!ah4DE;3RN zhd~l9j!7O}6t0fse{uEPFGvz~u%--L_9*vLl4GOO%$OU%LSI5rT7zPv(__D6afKQm z6ow%v3xvJ!%RUj2wAudA8W01!rw1b7p=P06$v_Y+%~&9)~UsFE`x zFbU>67ogF-ZPR^@_EE$*e==^lM8y!=SqWK53)(CX=-Bt)^Mja)B|aAmK$TYqHj*7@ zL#CAJf2}PE^^) zRG_a-POXYvTU^qHJOjvDuqse<)TMvo8yU`x_H9g0L|D#A+1x4}?%2Gmi>iT`*_Bd< z{8J_?4^!}x*dQZZjIJwKUqLVqK9w>ys*swcZ0un{5fhIBjmP=B>&u9VNAo#)ly=_o zPMY1>L_70DX<8@bu5Y0d_BTD<7ipz{j>{!(+}O!@1h(5zVF-shFe7$<(0E^KraMJgaWdU^2b(2nrmY@6R>-rs9|; zDPj3$6}8fYM5ESA1ghl?K_M$KesIx(l4N6^H(vU(@&c~2-hGc|iYNDRB@OyQCp8EW z+9|B5+Jk`nBMGsHL7Twe`y(;~3A@Yo1?`?`p*9Pe;U#v-dsrc@sN+Stbn~_jR5V3% zcERb00w4LSDZxIadGuSfJJn2NB2HOuPhsI(byTk+MyPo#yEh5oeSj}^|Z+m>qD9}21IBRs!c!j$S64lf{2bV{|z0w#4%~zCbPi&XVDQ9crrrv z;ZEOGxA^|Bm@n4u#6EAU}F2_E-vRpHkB_@qffXqB0H#}|gucr*a6LNwzj|xgvFAotOPZQ{G)lTG zK{^Y}8g9Ai%L=UK@LSO{yHz+~(09HcO8@+;D~;M> z$F))=l{0(^Na5jtp(m&jM&8$EP9#<0dxjByc4b7|r9MlA#F*lc)awXStjLgM-g1~| zA2y{Qphx>BsE(tRk^HUSBA4%dynW71HhLVXW8;!Sk&T)9fIqY~{3uHa10TCs(IidR z)L21}@mf&Mc~_bQyBlF&!J^3sbG(S*9T}>(Pa{b`C0z3b5oX-Q*F0g&bxi|-W(biOO?DiVY;B93=GBmb@A9=`!vf1#vY_^VllNpdd zkHuz=c@h7)%9IjqQz)>7J0Jv~fsg+N2cSDe(m@IEdiwcL8XmRg2K5i{A-V0TAjfLC49rfp{;IvkkRR3a*K@oCt9p;&W zZHj-pQr}vU6!wtjzFM&Ghbq{o)YJT3R@AvXTTdT|v`C)%Ja-%aoyXcyBNVc^Q$Pq= zSfO0Xd^23r!%})yi-gQOY?gZF_Al+{SB8DPNz-Czj17e%F>n5uYebrk4pZ5 z6?-z9Frx5jlN`u;pl~ZJ=pB&XpCMZ~zYqOY-D{5UtY`)SK{+Q9HHhGaf73eAu|+k7nj%HPJ$?gdIyEnG1AN&ZS%B`*pw7U|F!12{@7(7`l+(wQ z5pS07kmEN6Xgc}y^L>{f+y7{Vv7+EZs0XO5s(*RKUfD>sL&fP~_$}dor@E!Y@OYkt z@jgo!V?~Ax^OmK=Ei-+^$ym&3gD2Cn@nYQZVh!MQ>BDi%>WfCWa_qn*r5 zhWm)}HgHrIyV;H8KTO7icRmMz17stqtir;vGwwgJokT_;;e-uguj%gjmynl7zcusPRY{t^R{9E)R zQWy`;0YqC&!M8&(HH$bCfN(2^I4ZRU^8l6vz{f$g@M8)>hU+l&wWTq+09ifvaYt!{ zFa3yo6~+t~tt3*K8`dGJB=sB{|n!Rtvi8*Gy5@-vw@5&LW3~65^6hM?KUWTpV`ftR<{s`~qPT9)sU#DUK zMJ@#Yp9~F392ZLfUAR$Fl@5dI`KzL}-6INCt-SzE zC}iKK%nzEIWa0?#kq3(=E^$HC%Y;mP7qd=_S{+x}khkk1xLXNfzf!`CnayvS+|>q9 z9@@W@hj%2UC1WZjxF!ja`{$yFg*|VHkCIkqpNo2?{3mYqVCC8G5do0hwF~%;qwIMZ zj`L%wP31;{29qk{gBk;swKxDn$PPRr=rLDHEuMH-tu%3Ny`0jbNx~nj?YPHCfm9+>P zHs#g7`+E__55R=Z8*3cny-U14ms$U4&534XcPG&iE8AE}=hurPzNtQ~#_#N_uyJ4H zejWSPIgQi4cKiFNQI>wAjf=r0*&flWtV7p~?f&%`h11GUJ0 zNw~T5TID=xYb!U#oJA|!9jl&qpp!mnmi5fbYmnC}c}LLnJ>TJbUqat($8M%_FV?`Rd8<2};7vhW1k+nZHamDZ~mbVD-Leyu#My zUvKeOnxvvO8o$dwc*N-wbv>JlpUSyw7HB%_2*sqLHhlroUFoB>sM=pda#io!z1*Sbw_jwDIPB%b>qo ze}CfN=zP3jcOh7R*FRr4?VPfS;eS1SNNA7ozM5Tl>E@iTzI9*xzVd335`5~t#BOOV z)t+SS(eAp~?OBw9$~h+R#_+gSdE`&t-P+cla{ZXM8}^>R_47)w(_<)F{$Fw zvUHs7)6g($hVZU0H!1fv#a}AGXH|$yyxRhqJ;nQzR?DdkhobDLHcZ)?9}6RKl!Ie` zTL;f^A!OF@b+pK(7!-yosB&4|aWJmZlHdJxLgw)hRlR3Wx3iP^blbTal_XPzER5KY z!h~yTp_k!kGoJTMx5jxH#|Y!pk+r3L^;ntL8CCgf2TgmQ86=-q!oV!>ns&1T1B;<7 z{-)1R%_RI%_jZa2#BbS`wfWXx5tB}~H>%+^?d=04eI*q5)}0DXo@ek&2URKeKYl0C z7V&emK(7D7t=9R>POX%)`ZVwSK4S?MQ*+U{Q8k!9OHcP{W1TCozqhTkscMTZO}nOJ zW5v6or37BFwET7`T4gMw?O1ip&%+H?WwB+^XMa+YXnZuGKZHw-;6+Ha%qpQ8B$-%tp zA6X9R5NcNiFZG!ySIQecwTtGrzy2`uG0{eVd%&A^`S5#&CVXakhS*3TAo-xvsD4hY>PW;^}5TcFwAk> z<*0hfj$P<;zxaeYf4A0_AMe*p93zx-od@!!-N5NXQk3LvDx(uAfK7a+pTV)Yxhd8XQ z0JTWV@R$%}+)$(Os&26fr!fR5ed;+c31zCLAs(mB*H{ytNEf?oAv5Gr?3z{`XtSn8 zm(|!Qu+8~Pku~F9`h2)vai&I@DC>)z*<=JFuN zXLi}+T)X0Fi={|=p!GVvU@k&|UZS&v?iL1Z-Ys^gai!}mK<0f^FGY2As;F!M>;8cH zk|zw!{pS3&QQR+21(f@CmLr(&X<*}gt-aah`3$fSpm3R>5G1R`wvMAR{DD1Z3yRMd z%u2796{VaG`_WwWz6n$l`0wO$Qd$G4k*%2nYF$tRbJfG@RjDxg-*Ln9axv5F(=a1G z?k8)Ap_e$I$rOMywMQyC9_T~MePCHv_n;&P$rqT!PzC5`MNwju$1O zd(;4!c0+CE$ha5>&{syM>(pNk$-#^^2m^9*k+Ia@MLt}JHSMrnabqdOP6gPe-aV)Y znkx1FuZ<0+ncCUS297|={aj9XUqH&U(_}eUpX2lS8LmpusagS*02KcoOr^$Hux##< zP-HF^E-0_wt~Jatc8Xv?F0HYibgG5!0m=x#O*t6tkxatirt5$|xM{M_RH9_s;5|X)#;g97<}+HkG~aUNYlaJe>d_m( z9oJ0>QuY~ar@*s*0)|pmIxlRy_<6jdl<^SA8cktBsaI7tEmN#rgzcG2(SB7N=YKSz zn1Ya^QSo`Z8ZJ2;Sx?&3K84h5tG+0pUm;3$cuma-O0Zh~#!NohQn-J+r~n)j2x8Nm z$H_;$BJAeTa)u*1lIc&aNkS;>x=*W%C-bKe|A(!*vjZ*cSYupp{)GkqFxA8YV9)CK z!@)(_NsIE&q!Fwis3pqLZJY$c9#FcEFw>OTf7}uED6s~CGuz>+eBlg?smdGE&c+1;JOV&@!LtHDD9W`&Q-A{NW8H}d zYgRzf(0ZBHFCzN^06%L2plTFptSRUkJ?h%-n2_gd>poT(rF4nxPyZ$OacAA{%;uF# z@F`3YLYah&`9n9;3hD=;NtYo6G3TC*3LT*HciHBIXo&`YacU2&JD~Puq&OkZhCuM+ z++(0gk~VQfd|mSp=m2iQYJK_Ch`g{K|hkF|Annbp@3?0FGIdS3vuL6 zxpgb37P336pL$;xQO6qe|5ibXqGt?YiE=&ej|JJyWm8}E;=ojG0&f=wf$8MmgB--`E!sVO7S ztEG>&;3AI0KG2onfbO(?GIa1ooJ0!{!NEv%huPiwbNwUw=PI0)Z;&LZS>7f;4|UEn z+ka_Tsf+wN4Lm6v_`|iU^gA!phWY|2%d2f$O zbsSd~78`Uy=!3}o8B_Z~p73KP;cjWAnr0Vi*#I2o4#3^R#s7n2P5$86-^?F4HmGhI zy}E#$7f%lYVRx+1uR&X6kO60eBRB=ABXZMd&X%5}NTR|I%3>Fq$*WOGTg2Sx<@eAw zH$fFox&sjL+`1y!v4A=TRnnjerkd*q0WA%*B!@lDA21V9TM+(3Vijw^j8xZIwrlup zx}2tk3sj4&tG;uru{w)peEnqu&D;oEhQHtrX%-mD>{s|vaMK=$JS)dr0QfPm1x)Q7 zG4q1K3gibwRWa1^0y>Tbfvg0Cq@OM(O860wULume8AYA#AphZpMwu-_edqyz>tqY3 zB18WROwupMB~5@V0O+Gw2>Vn|=-<$$)AZGA_CW*AFK53jokAD+QCv z%hd4?BE!@^oiv1mp9=C(H}~REZAaDvu>)`#PlKTZ!~bY`ez%rCO{0{-!whNnzVQ$ALP@$!wlKIr zgvbY&H`;#u{wVY6*8&t04zy3jlmKC0@;D#c2RjMD`f=L z&J~x{q;?jDMLec#+0S$szTkVZqojHz#pWKw@t^e1Ym`BTRRdTW_HeWE?_dB=0DNaM zfmFs5%wF*Wp8tcx{iYaQ=teh3rBwh_7&7N8z?vcNF*(IcsOznk_~4a6%@qT4kUu{; z5{Nb{s3)$V$}$4Xu4x61G^oE`SO!o_)FRImI662Wp@N!3_E-Np4Ic?_TjoR#SqXqU zoaAD}Qn5gP>>U(pv+A%MQlBzo&60ttrK6L;znpCN-;Jhsg^)7PG07RdfzZAjeB8Ij7jxJ*0*~^iD-@bl zz6}CQi@Ab{yyJ|fRYfMD&^;+U)^J#meKRasp8Tje_^%yN(j~q`veRzxj_hzJzfdb@@P){(U zNq*MP%>Y1@>?INY+|>WfkBw!vGrjQ{xku!%pVQ$i67fGY2aH*bd}fZ6ko{w7RE*N5 zl={!e@EAx`R-+L7Z$D}e@7J!DNSW&oVU2#Y{{M*>&lD95snOv*2m!*L{zUj(>9>ra z*i*^+|G>J6wmJUK0IXZ`7wfwJ#kx5FtSftV=Hu@0zJGu3eJ9PlT4KGP_1Bv?Q>2w} zJJW>m{W(XtS^{FdWk#+I;*f(}b}5J^j{5LL#<`%#mb!IL!)oaOMr(+?J^^Kn_g&D4 zNFq}$jVlrn(4?I-RsiO^UGIf}w^t-=9!;HVh5R6ea}QQY0D1_pgLKcaxqCDFW$*yv z?1s+jt$?=k*ZoqFqRv*sbogL}wB|RK(Ek}0_@Z^QNaSMw$BwA{fVs7+0O?=~V~fh} zA^+FBu<{~L+f+OFu%L=uYdCH$C^ zL~+=p7laDni=hu+Wo`Ty{&*X?w}KiWyIf>}F)i#Dwq&IM+pT=M%xPY*avQWwhq7%f zCG5dit3)YrC(RJQTG2Mk|5&L{s{~aRCBr`WuMND*wX5>avhk0iOxR|*U$mzg&*7?r z?{XC}8?^kGWh@0urtU^^;0P@;SwsXak;HulmVHE|?Ii>lDb0(2E%~OOKf?4GvxRva z7Ia4}wu6qr-0JKpNU*NK^ovLq(vnD$&BOhzj(Q=wR%cg)hOJsdJB)0t0KqA@7EQKL z6G2w^vqC(kWt5NCWBO3ykx7w~rT+|i^=uR9sS8crKl3G~?q8z);jit>6w3q5;Xn2T zKz%->_K!oPNdGxyxDvn@=N^WzV*gGgAhjx7$O{^V3xK>u!~DGYUDrT<&U{mP0V!7G()v;1~8tJG!kx!s@jh<^q2mV&Fsh2`40HUEV93;K~#}i z!dX_81F=jRYZ;MA+&=`QLTrvlH7xs5O+7mzL#AhX1q zXM4NHXd-o~CLANhhu{+62!xJZKcxe02dfi^GB(g!59E=*rnrFA%vs3k6 zt*$YLiwTio?T?XW4;s*Qpk7vu-miiSJ#7 z_@I0CAoDCi@#g`!Bl67WLsN$W_N$=8r_QQJ6m%-V^X0RnY3`1vVwcX@?v8n!&bk#X zf@+!Po2#k{PzMvLkm;0B*kogejc&fU*}cII|79HhWjweI+VgAGb{rIW#}#gyTWu;j z_@`2l+1;`V>pj%_E+v`8mit?*`$qM6*u2+>$t8Fj|E=S(jMv_arV%hK_J)v`dFSqy zV9naS`+<^l8mPmr^z)|07f*iqa|6ns{Fz8sCrvy-IzwA@TG(_`#}tky6q7ooVvN>s zd-M!Gevc8eQ#!U~$B`nfTq`mPXlwB!ys)_^ zivGUlwaRwF-frl$dE5P6W%GW_|7Q2kvPQp#`~Ms7Ce5U%O?j(OjM~1;r)+n>nO&wJ z*t|Zkvv=daPhfbQ`@H6yuX{dZv?HAR)VUnrt?xGv<-9q+jI4?BKbl#*ZDg7+x_4Tv zoW3i3c5r~GY4 z=S0g3L57*AE2qz5ahEHExrZmK4<}K2ODS8qnl0p#ydp%65pb5veNdfh&bDThy(Bht zraseInMaGW7IUG-Qa?EJ3s^4Kfqu%y*yz0i@)_Zb=Vhe(%EpeVTWFj3NZ2a-Z zO^Ka(HI-5Y^~H%EmF;-R?bC_R{Ko&?3ZU@|$Fu&`PexAaP-2Ah*!T#KzQ(WLcJBYGX)(xC)_FA7C}oC_v`@8B#$ zSSPqnrXvpN61JJtukanu@7)~TZ{0rbt}g!r{`Fomlf;V@x>#(r~ zFx2}jit$0FeQ;cS?r$fyyy8i;k)Y2-X`!S-WBJ1r)fVw&D03$jnc}HZN$8Ef-#Tr< z=g(Ciwey8!VEMMO=-H}6E}`blZs^pN(yszLXand(7M^Rs<%iZft%Cr+|cx6wNRNMgfOMuu> zDZpU?MI0P#szrCuZD3*2f~>R=)}A^^4{RZGmR9Ij0N)Q#o_;<}{7BE8WY`3<`Cv45 zSUWVCvdtx9oO!mItYNn}+U84#ayPeH^U(`%smgJ<5GcgWOBT!mYC%EN&GLDq8qZ`a z0X(c#p_BCfy}D-7=wqnX28irK6;f=WYC68xj+Yr=e;_D0pyBV${!k)Tga_qvpKmzyy=2t&UyM&kEurVu{qxkB30j{R(m$J`RJNC~_*CAHqjU?J8fnUNrV zV6G<#qdH(Y{@Dk8(Ax~Ia2x%D&Y*xU@1+*L9}H)|LRGTONb4 zF@!DJ%#iy&!`?&#|u}HCifYAO;aYs`lD&R^5at%VKMDs|;avon_Bxf^zN9{C0ylSHjtgoYqo{xlO( z{ux=UO6kcWSwWuq65}Xsggm>yuLvxx+^}e^?lo(BYX8CW&QLa|6-&`EG>A0QDf6f- zJb>Za?TgwkyJ~zmzGd53r5=#n%-_+>12dcrx4$7v^c00T2E|sGTcPnN6sVURwGNN1 zV@q89)9`WZkOF3;k=p!xNlw{|t*aEw+87b{2qEg6JM+4IZ$ z7iw_~bJEtv2{9L$xRK!ZgQJ2MO|7S17wz9GTD&VZPT2R~n1CL-w9TBrk53z2+}GhO ze6K_!0F+;~2K^%V{Rzf|<6@*T%rh&`uomA3k6Q;E-<4<4O)?BEs z=INVA57>*~62bntTF4^QSrBZDnA>hC^r~$$SaCPt;z~oDIlgp};5&~61UdZfzR@kLedeg1rz##qjmWoW-N7v}lW+G!oJKA^5zbufJ6d zguA?VMz`C{FMbnE&X%$Ug<+~)%(aWCAB-k3^nhs)vE(EOW4A`qwHY6S+9vZ89??P! zepqfPYBEl3-^iS#^ja%)Vx6_IuWZ5#fdDz0wQIiDn4dP<#5x)hTax*?)!~;of~8J4cIEzJ z)T2*2cY()L8k&FI{X0u*1g_Cs8_AO$u zwDq|XO(MZJh11IkdC*W8*$FmG<#k?6iRM^;`g`rK(yq`T3q#>W0{@!~=$bg@ndeWG z@H0HScN9xn9>rt+>YC0I^&QRzR%vvx4Bx-S1=f8cfx582Pj^_eUQyI)m@&2Lh|+AY zSpK437ZmL{G0%HC7*e<$d9x zOJcu!O@>*nWp9;+h4Q+V{pn~s2B!<>h%3vFF6u*dmRq&6ey*qB&Bk7e^c!zf+&rSJ zP?}uO-KKIgAW-6Yo^)WO!98suJMn7cBDIGF3;Fyn2~frq;Z%E^JRaGzj0j!ohvT| zb)^<*!@gCqWZ1Z3)1GTAwx}9cCHc@_{QlH=?eWycl_${#`*VT+|Eg{C8_LZj91u`9 zEzp1W7CIO?I+|IVIQ-fCs6*q^6b4o_fkuHz8xvA!gVjLH4ZMcbe$Ak6>t@d?X)o-t} z^H3e!bXSMx@Rw#yJd^GFC$P~T8we4iVf6Ag$Z80$9ZQzO0u{URz2CX5D&OjVZm-ju zp7$ZSNXBuPw3j68W$dO<)`w^}HRR#r`=TF9uUZd!Cmq#!Yo5Qoy@3F~C9YqIxcg4Im67@A<9{H!#d*xJ)$W~-QS z**0e9C@7EzD|~a}U$+d&+i;FJPfrKz{*`6LyCsYwPwpakXR@=Cdo1zmc@$zw|LJI$ z;nuKh=9u|wbzx=q;d}O4G?>|2K?z(iZSMjTW5#*q60RU%VVdtI@ov%}(gv`u>44;#q&h>b9n zn&s-pYBT=TDEjthloA>J^APvf?~ewa8__Q9Uo&%EGRPM*rfrtnV81H*cs6LUd01Pq zOOMnXg?K;jKhn2@U-sB&+l0k|;$436qabfxqEKVfRjol%8syc|NiFu zc)5G-_55`INYLHosz4$IFSq%IhmmS?_xr%Nf_B| zl76+*h%ccL3h0yxShI{og{ET-prLX}RfU3~x8WI~v8GMo2Ry`*W2iIIs8&4JKyl+( z=W*8IZf1gCLQ9pV|46lAMQ2Bs{3{_8Y|_Eq1_98yS$Z<`Vk@rh)2yTmX5Q zTWu*29do2{A`F)#?MN`~fb(;Pe(g6W;hiA_s{w6v5=&Bn&!V6n$`olEz$}Ulm80lhQPebd+4hB1kxeRz7O^g^h9v5sYUD>yp~o7M~AkMK~Z9v4u`d1WF(f zly6s1Sc_?xTN9In=F?;!+2?(%3tZJi_!i-|GFrE}gTm!_xgeEUVTmk1jFyq4a-L1# z=14XeLhRP4CF6kDazkU@`V5L=C1BeS55*D7X>&drYt~RJe4*MI5c~_%K;uwuo?wYU zFneO6b)n!!88%p&IALaSB}$5`OkRuw?M)+5utI_9IPriNMYzWhk}fow#_wXs3XD~p z^`>E~rVuQMiF8dEzBc~KG7oSagldgUM!6ZA%KE@YYQ0{7>MeE zJC>hBE5uJ}Md6TU#6}@a3CzIPGJ8rAZz$V%H9u2J82H)nzFWGM2V~-IZ~-@5r2VO)Xe8r!<6JqBMO#-?%^qy3v9}~6U=Vl zyBZ~oSk4W*mLqp<3z-(rf*~&Wrl{24Oc`grPF_J=-z2N8evtEJ-cR5jfoL5&aa)+s zAFTA7Sb9BXy=md=A-YKDeYryDC8OjZTD-W3B5$apWZdN@O`zeRm!N#SRqkqly;!sa zrczPZ4?MK4;SymiGBPe%XnC_8s}Yt41wXIQY{6?72JLvTee6t8ncajh^C$y2CTzY5 z5;9s5iaplx8X(dt;83l-8l^Z?CZOdbMW~5i!F1_~(nJxi{0ifP+KI$`EJ0!J`_-}|XPRJ51rBr&rLiMB#U)sdc3I*bd z3~Ax^T*;Wlg$tX;(dbJTN&8V`fxjlwPS(QZ3sIRRio9kjI0CESG6g;75RGrc+jF4mNlAiZx~201*mbsv%D7V z=NCl`y00Lfv;NXW{JJ&CiSU?56OCJm2}q(%qrrVHtaD>==J5Tvb+Mx>oVq|uV%;xJkg^vG3FR;4P`uE?){6o zv|WQ_jtYt_qwmtJmq-06rxVLdeFZ6sycgVW_=FUf<6zdLxUkDO!>#Zt!nwnK;Y1V% zhD|06Ljn)P$Yj!L{4^gSljjK)$%;xsRz6O5H%G@>E~%xYPNZ`DjUpN66D}W4(SNK@ zU8YBiDk;qAKE{?*HWvUKfxXw5{{w?an>pkz&tK4fO5l!t)piERb_j|l)y0&RI!g-P zlvS9sEBC%Lc%~(uaxe0?wuMpxjsXdp;4oLR^gVfe9ivz<7rKEW3-EFe`4wUtYt%}o z#uuhZQgXQvOWYuX9bcQlI7vKr9V6;;oKhs=(4WNllc5|*jzQkC$w-2*tOLGd+6DlMER>lK~g?%ZaXh%ca%#Tz+ zlsCkh4;uWFV#DMWKL=Z8fh8RueD)I`G!3J+Nx)k>IN*&RnUm;UwSZy0BO<&*KRTjG zMUY_mk*fnnTcAx}Kppk}YvjDcns~ZBPVc>UBm@Km1wlZ1l`b6#AfR*z(t8o4h!iPO z0|WtSBE2aD0!WkIMKLHH3@wC?!i~S{`|6u}@AKU3KiQrAoIN|UJF`3I>{pw!nk?8@ zMYCv@b*|G}h%*mQvet@ZZTyC0rkfUsV$j@)JWBIbxdwdBoTr>kH3Pp%T3P1cVniD= z2o<6=i!)T;)K8j0K%Ie=62<09Y~=&d%AilKgC^B@6m<|7#Ng7dq9SYW__kZQlC~$e zHe=q^758P{5@w)X85L*yX`Q4rqaj=`?jEDY&F5!;g#9MJ=@5dXt+n(PJ|)&kJ!Pb{ zet_s%SeH?qSbL?CrsEUR9-%P?@r{oP-?iS7me)5}GqM_F+5McBW%uRsAoCj-C|jUi zHUL`|NH>*hB3}yHY1l{ERxm|zP-<;N3o)?g*b&aOOUmpE4)dg@mB+xIuN06}(N}z& zX4)~v3!wS{95k7GloL#mmD18clV@tsC*}%DnJobrDYK^wp;J=ybuuAjMsd;~#yxIF z1I>f(~KbALa)$SqiA2q#&cahOl4`8?wl+K`XnC}|B248(J9>rF0*&CkEP_72Xm3z^xO zuef)(>GQ5s_HlrCSo9Q!0&APhd1wHa&F;3M4VBbX*_{!;cI#b5V@TMydrm`}xe^M` zk`6u=aVhLvWQWlZ>u!nG_4_}bMsLp~_ewQE3HrLP39Z?9<%c1cFk~aN%Yk%~r{{)RP)6Y)oYVh#aIMB~@Ui*iio|WA|8)v|t zngq4V-z!U1{63rpRM_^q1Ix!JM@EMwqi&=Lc5MNW`TZgUJpKpGuwv!_)dHDj*OR`b=OGg=y1N6WW6jjdOP?{NDvv-!aW z6n`EK#{gfXJeT=QpI;+$6MxZWQc&x?T8`f774%5x(1jH_RKO!#JAG`f96bQw96t8l z`N(S`k{bt_lE%g>UcaYzv ziRDMVb&C_yuNIG9)H=Pzllghxba+s1%piwiCEqw^UDb~#D!!|DgL^6tQG4a(kK(P( z`6zpp?_2DCckk|ZTQ0)KZS$NTM!6;#bj{@I+r7BjI;lu@H{r!~S_@U9qew8hNz4M1 zC!NcWkSGQ=;s^D^0%9s_he#R`yXz`jQctCwsl=SAyyiF_&T%Z2-|q49iF?sRlce$- z717~N@|G#$^LTq_N{?^@!vb4yU+E%6b2$D9zF_T6%`iqXx{-Ml?AE%M{+LQ;X-QN>MHHg9Xy@% z)m_^XuHwo=%eRH5x8*XYd;*2PIz5Y8JI8D|#%$R2IkC|3*Bjsh(E;+*Zm1L*jMCX14L{@hTGhh3 zluaRJ6LHKH2OLZ55g)VSbN+haLkkN@7En!~V{_Mb;YF*Q$DkN^oSD8&_!a^?jbhVq zblA1=2pl@5e8j~g;HQG_8-fC+sZGYMZx6!6n>Pa}WlIqe%J24IJ6&(-vUr%~liZdpq(( z(bWNpY-Jg8&HNSs$N|N<2hrsubS>Z6Xs)I#_uVHxq$QYwR3A?IUhiu{{3N`NkJwL!i;sc4uz z>G=I<){0D=2TlCECzbZN*3_21jXO;n&S7p76Ed?!%SS@p?e`dcqb+NNHb?>mq-vb2@w@^Z2~sIG z&n8Z;m9ADf(-qBJ6F(sqEM3dF(Iq9vKh;Mem=7!$hf#2gq<2h_LQzBuoth{WJ|t)oOh~AgT=hd#7m|3>cI+yX;87=%72yr3oKL{U7`efW85QEgB4m~R`@W-cNUgTklN#C`F|Zd zQtc6jij0#=9|FPtv6n>Xgd$q7+YneVV3NYP|I0B#S`UFN%vn9dc0XkR;~(&sf4|Q~ z#4w;^ZzuRY7|u|b%fBmjtA=RUrrK`&$ySo=Ddn@~rIt=`j(@CnR!5pADh73WKk?}D zX|MW-wVB}P0$R~2GCTJi^TA@4>yJfdLi*HU?|HPN8=TJ8LzBY~&=&&}<0clBoUb8x zgD?)|d)bN>x*>iZmSJ*~*AU_0kBLo^5A6?;eAfVt-_X8p0y5JLihEuSOWa44dCkNd zuJ?nFmlhcGBxcH0S9VT>6~8ah6E@>3Y}VJmIJzg}vKe+xU&M>_Sgl(r`2tIRST|6j zY9o-edrPfl?yW=K>*1?ns2snp9=WqOl!`2K*&~wfy>jslX$=yzLB+5bQ&x0^+pXu9jY)tI%lC=;9wR^*0W|xQY9yZK&o_q`Z*KkEygf*1R_Jhh!#Sv~D zL7~AuP|hmf#pZjqVb013>v?c~7~vYz5S5gG(&0PTfDnmu;ILKIrPa77jQ3M!oL_Iy zLP5^6F7^-T_`5H_9`FE|BRn%%O)Ym`NA}@7$@qPVQcJc?NDA>ah02b>z%a?ySI`~t zj7uiQ#2qJECh~k>)Ww4if9nc6YWN%P(D7@d^n}nQU^v5(ZoO8a9`l=#>}1#mw-jwd zmxY+ySO6->fK zg;D$*(Ov4`+~^=birTwx^exERjSdED*{h=w`#y|1g#jm1u}Y>Ta?D2SPb|o6(cb-@ z#%dHov=f$;4?Z?0C~tiZ=1IKIE-nux-_UDVgTIFaFeBhQUkrX2+gxHj9Wvik9n{4r zL)u~D?Z0IAFu4Z?-X5MlH|#vT{|U8+rRn|Bg^W4Q0}S$4_S`kPcmrfq{2s5Z$q+dQ zI%;f+;~bKFB~Qd`JH{hTwZsN=p(iuhk_#XEDi@NMbg%vZ+GTE7As5HX5%|))H^yS6 zYa2`lC4l;wcv+Z{?b}jHvM>{~PDPs%RqK^>N#oACDGkz<`aIsu<0k<%6KJ_lOPlbH zUgNEB({50L)N8cjq}zr@1EmKmipvROgRh)f0-f-U6m#nFJ!4-IkPdX;m zjmV6)gC%cCR06@(VjAwJS~Bm;fPDnZPbq%Ne8rPO4l+1dk@39|cb~B!F5%SCs>+qb z&m;e~j$A^o9xneBQGJ}jQ~YJ;C-=T+j~7dFYYK1LD_NcprHQ7~(;M+O{`0|n0(8#j zimCb#Ox4r;UG)zrPs`Ljw^Llm)(ti zzMit^5=yGhXZ3C*g!gecRP0N~bJhJP3eIk`E~#h<*EIWRhG=al47qk7_pgX4-*pzzf_k&!l zoU5`K6qMviD1_pKvjg|Lgc3%0MAJvrQ?rZ=xFt*?+fe5L3w9#cEZpv7Oz=eScPS%X z?LqMz0%N8%3F=u}4kJDYdE`}{o#VuD%{1{c6-$Qh!6 z%Vt5wez3Y@`URGdYS3boM=JdtOh|xU|I4@<4PmKU8$LZ1PY*7Y-EsInC(F#XI8p3C z(8sg)-V+ZJ#3#CPGI}AioM%NE4u__j!HI=YdeI>WijV&*3g#*$b| z4UJrFw*BA-&CgT6GY*)yrpNuMc+07s>-!0-fetPn1OET5c#CPre|`~|hW%glZ?O$v z%eDSDj)N0{8;vpXmv}2Sh%IFL2Q0>1Q2#^L6dT9R3;Tn=5c`e)A6a79max;P{#dHM z`PJu6fOM&CmC{E?Eg^7)%HV2m}bTd@fDDa$XVzNC*fFSl}=4sm6P2 zD+ePh2OULM8zXxy1{X_<>eznicBU6XmmWf~QZK1&`=N-(tB?sKwlh6Gl~We`>zqnT z++0eIc0*waQd^La+aK{xW%XqB&dylCB1#=QG8e%K!D+)<-u__FHjdbkiXs*(&3;C( zs}HRz+4U-Gh62ji;zD%`In#v}rU3SBuu{;n>!{y4*@=157pjTm6yMs{C?)<#X)Ow3 zmco1l3-3u2;wu8#ybnXx5$QH(7L=zeb~U>`hDCSSm!Do^WY43~I61x6Obkk(>*;f$|#d^N0r<`b_|mQX;Nh=7cU(23AxcX&p2f4hB384$_+50PZQFXkct<1SMn>6p%o9AYBnQTX|773okn4C`B z>mPXvp^h|?zX+Q4^ev}s*fTPKe-=keNkB8d2spYAiIk9DuqX@?t!xbXnJjt-`JUzG zbMkW*1-Lp~0%ap7Yz8{P;Lri;1_^&xNNZ{L zHikHvG)I-2^lWxPw0wq=ClG(msqE^-!KA@5hRm%vCd)eNOwVOvs#E$#fo?XRW#!wY z%_FgD5jc31*{;yaSPAD%$Q_NmTAnx-d~^lDGl>#-=g`)SWM!Y&oX`?RQ^T5$k%!96 z_nQ)8gq{PN(ElBt3pAm1>-P{4t}sXtXh3+Jt?djM4Xq6vErC1*C+I=yku~0w>(SCu zvwlVsg6yZw-*=>WS_3H{+h?^_FS0r485lya>Y}luH1+F7`&^B%9Q8Y8$0nUVv0H9T zGFVgzlqk|Vl}yatGCxZ=Exbo|Hg*!TO^|S{itjiZ@Is`Nc-A|!wePWIp9cJCI&*LK zwl`_8&*GR~+C6HzrN2kNQO>Pt7+IR1Z9F}#xxaGnOuA7v@t$5-*tfcLc4ze78(h4v z@p_1RIC0Oiw!YvfIr`-kn0kuRaI-{kVEMyiW!I+m^5FdZF08OI_3&PTTLahk-s|D? zI6Lfq$nD{Dqd08wI*)%^^Hpf&bwi_ocUDcaPLtO5;o|n8PGXwX)~}gs_r`_Oz=Rv$ z)Wfpdt>wOJX=j^*>qT|fi^fBnz0t{w$>YJ(`2oDtoQG5YJ>Lau8JTh?JlS)~r8Jl~ zo}AZT1Y#~zZ^hW}V0N7~34eX>$G&d3QK`8g;4qk}jN|{|<`9i5W7ZLtdVt)W@Tt(+ z=~;zsaC2#O;uZe|S+YBpSs&j5bwYx{s#%s6Ltkt5=ECtQ3x38{u z7Uc(;Hx}cpoou!7f0Z8b^36?haL<`HOob0CEl$hWm+T1f%~ai4zZ~c+TxeVjuJqr# zX}_1A#CM7q=6)TfP2+t!+I6^deDkMV20R z`d>}@opoE^ifjabZ*UhuJGkicCVV))*;qM^U)3~INM9aotf))oXy|S}nLXTGcb;BN z&)smO+R=F^^uA@7V4EpvpWC)p8F_iG;u&&!deOGNoqgCQ%;9>Tbw}9bAuztQ z@5tdcp)q+R@A+|fqa)$E{nUp)L1Aj@S6zy$xwE%<tSW^ zx2H={LB9rvdvbR@Qscn^|2`_g-YyEFbf;~XOhoB14nKdrB(nDpKHQ$`eb4({UGvK8 z3-RCTUN6RQgKhaZ&j$1k&V90m>R?6cl=NHmXzB8^`jUmDj1NyJ-G3pXO0d|SA2c3* zy_q$3SV7rI+uWS@s1;sHUDVV&e!IcdFdFqdPH*=3^>9C| z!tZ@k80f8~GyClanlvHbb^jRu%yc?8`)?O77tB}AA|h?~x8(;`cZieSZVl$%9<~?7 zUM`OGKbRYJ9epQrRkGc9J4YjLl){M4P!KJmu-iK*z+$){j*btl;^q|25G#1Z@Z& zv`clGczV;X;d{5TM>%ZgL8f_tdQm8^d9!7ib{YCrH<}}lxW}HLtKpldXM9@hX0^8U zQP?TTuvjIRrm3O#p-Xil3I4D8sZsTEdwzUN%L|c=@z$@+yxwia3wQU`dwVjq_}3h6 zzWsA|BhDp9Rw$I|g3YLzd`y@93w!PNodvOR8}<)gS@)OO#e>Da`Dboh&+k^83zOHX z7Y2jnof_Pi_EgR%avO-#5nJ}wit7hAt-PnWpX5<5pE94doiRgdLbe6)2i-(C(s!vk*LSvZKnts<4O@g9C z?Rl!v0I1^~IOrd`vMCT?JKr&fxo2y_?~!2w(JxxG>18#RJMz^}Vi7hW#t?!8z8}PF zPpu3V{YfjpEJbOEsX$}G4%g`*OMFk!mMM2R!FR@RN#Lk6?kRVf%72l$+Z5P*1^cj@ z?lr;ba^oeUyhNw4tCZ64l~cTt>O6D2!69FTsx--Yj^nn>lN}AC`w2PZlnR4wHuc#0}ZzSrmHw+45F| zc|1bRwcEIo^NL=8)2b{WnRW=zLh5T`P6#unJ*-5T2U>`!W>lp*GghYsC+2$rW|lP; zwwI0Qm}74v^n6@>t3S$0j%icpW!^G|6nMk4&0io$P*9Aph+_&c#<=qJCSg8L+u|$N z^FHb&sSs3WZx~5lg*Cm4J!^fq8*F{`a5WbwlW5IXh-p!mAW8W;TQ}B64v8;8{#dv- zt9W6r@oW%VF&e&8K{CHHg|$NoHhoMPDch7elCY zT)?u7w{2D2eA6}5>1FX)|Cv*0_}7qW!#ZQpA_r-qyjra7ThTcF^xCis!$E~IkiLog zYuC`wQtE4@%C4&AYG~dW{C2DUq&Uc}7|BVd@uWrAFylN_h;50jmZbYiT{Z z6OvSMoCKXgucI@V6xmCdv_*d3%Av%G(Mp`A>_tb&zKlH&#US0{lrRbne;ZS1Z5uSLo) zx0QD72OUDsH0e`tEVY00&{tKOrN@)EN$jr)9bi-sPx3da94i_TCxe1q2R0DB{FAzm z&h;L|Y_bK0jNHuykqx97PF;V++`vCvF)Wbt4aT!DYttLO=68APwHMQCe=wED{@bt; zmGE{>k~PUF)Ry-`4Z+AozpXDLha6(XZwj8>ue%F%f`U&kY;6WdT+`gFDn?Bf1V^~t zTT*X|x_Iw!J7QVDA^TpK&MR8pq;c&a?k+Ac#sK zxnCo46OR5{y?q&3;d*9>;yt9hD3BCIQVgW(a zQ5TeGXAkAu6;s!dPryZIziw%KG2h2HyX;~WCxZK}8nXWip`c`SW+kvD&*vEi88a-Z zI^(x$17=G&qwh?_3DYRzvX18rC?m$ID)jb@QJ!=NY9+<&^@Rz+S8(>jJm`G=IWjXo z#LrP&b$eLNiiw{muw0UeMPZK^Tz|~AaO#-7I^!pN*tga&tW>yPkz{=xUX)oeQ(iZy z)QSlqsNnnKJlkY?KjmoPiz2(k+pBjnPFZm^Gt8N6?A(4(ItQ*eB(y4t_FOV#-@7mr zwHiyZ&S_OfrZ@GUkC5sD!2xARJV*F%MbDAq|Av#OekiAEJxkD|lsqP&R|Pl4i4 z!5LPhfwXcfz^z_nDwl$OCivNR9;S!>y`FrdkIaoJVx4E9idM8a=1dEuIa1k6t`Px! za%_Q#i88oCQZwMwRx_v~_Q@}ru4I#j{|@}i*`xu9@z;n2RzhZBiIs5mYww&jn-~K8 zBWq!9)SiNRHw*+d*h%uU? zW$p^Qys)<<-0G2=4XLf3o_DyUiD7 zWe|5sA@d1*6N)?dTr1Ab4fEk_Fd%B;g2l+>!@k0IG^94 zj><~oQ&6a2K4z>sb#`tE!UDCvKW8yrE?S9V9&iJZG4c4UprJ&lTe*BwG?HB|pH3qh z6QMP7`8J8V_NjIBA5*J)#d%ET68tt>r2?56*S3()le> zDkY)C%h)qG`*Ii~4j<@fU7L4pKP<^UMwpBFy%8jzFmkO{B++3%)FYLn1o^ z=!=L*CH#z*auSlK(aRo8i*6tnOPp6vv-HNWrkS`hoZTie8KSOQQa-^f(d=>SC^caG zUrBx%4&4&qg60Dc1NBygL2k&sL@4+o#Fix5_EW)jdf%_RG@=swb>aDPhS1>8XAOr` zF_=uGlpXz`;L{C|@H}!_OiDvp`+SCfDXNf&R>0t}8SpIY_sM>9$+o6Dw!VXym zloF)Tz)ODU;>Y|K*z>On?{5jhf?eZ$=_#wI1#3ET9maG^UZHSeSoeK)yF{B}HJL48 zRV~^?Xh@F%)}A_1qIzDlPn_B@I*Z~%@Yl7RaMgD;|St~s>H1^cEbW>f+ z`N$vGdNi8RkT&JF`ruP(2Fp{DGxvpQtr?8ye`GT;Vj#|}sTb?>_Gi;-K$l^FLnn6{ zN^Nrtf=Krt<&vVml9Y8Ulsol%j|jw=k;pkJN9;9EkCcofTU~F-Iz(O8GraYjcZYu5 zTlQC$K;X1c))ea|W9JQPsG@*cCSYv@soiXH!SG7KRKRlr!$Q*OiNnG z1yqb-pf2)ZELS2Xz4C8~)&&wQ`Xg4u4g2z*(EZW-XXAebl( zxxk3IFwy_PqcTUKja9`auH6ACM}aBTtZpaP&a?-^SC390CqlW;p|2>6zg8uD_PO`{ z^0J9eivvw`RNOH&YuyS2g48oRp>t_7r037Z^3Noh-mrqo5?zU@8g_HgikAZI%PSX| zQ8wihy@UOhZHvXrNkoTXgoA(}u6*2DtfXnY~b8y!%#Dpk4!GwMSu+-;CdP;n*z*6Sn<9u zfZWIP+fO_0np@0gkEEM`IP_l*@7L-xq_9oNm==|FB(0SY0w4IP;x@!$)`|PI6NmtJ zEJn5>V%03k*u7TbZ-{+4m1*>FB$gRqovCYqtOa%?*5>%xNEHUer%vrm-N5d3Y;!wF z3GaLu7iQ((Fus!Tz%v#aKw6kLpPe}DvZ4w1>E}4nd^QPCdNCNI>Lh{(U^4p1kUWSe z8{9Vi_p3e_IOi`Z71OgH)v@SEGQwtpV~)J4lEf5w-}Z3gCi{ zYu5|YUC$3hNg|@B=qY{8p~~X>a=Knl6Uh9hf@t?_)ehts#_y7>FS7!7PVCpp-EgGT z<3M-{`AqDK)-&DZ9q_z?hi%5uDvVdFoB(FqE90L^ku1+gZ`TVokfX~ETbUo9EJdA` z&Ax^bjK&MjYKy*Bivo;{nb@JQ6(pJduLS~$QbO|`Mikm4tjVjR?(G=+9mHe}xrDD& zY7&;?jAF)AHuuA+l$StTb(L^?>KeuB_1EjtZlGh#>u*^y74|`S99@`KW-{s+E0r5< z@cp{2K+RCv-Bs7JCi|12#b@JJZ@MvIA45y$SF}Z>RscB<(=r%BBI6nke~%7S4v;Bt zZF%1Fb+|g2m}GlO)^ys)#wNhQ1*I@5N6;zV>V{ZvKrh#LDrMA{bo&N9~L`IV-#5ig21D>FJiyzon%DE(+rozZ=5T9AsieL^++kfKTkMy(caKEkLC3| zHVC5V@wo&E)r2W9HK;se01W)ds z9%Bj}#*1f)Vm}{i5p6~PQc}*0p=Wciyk&K(R-@!MjxDN8|M4M~w;?TZFySQq+#yUu zqRPMXmOrxd{+a9*{{(o_*Cpl$w7<8=!WtF8k5`WO}RH(vvg|57mk#y1vz%i;+f4Sdi=UlN~1%oL)1Y0D&+V} zA7wJK&{0)1iZew|!zJuhT+Qzbp2KTQx!J?-NpD=K-c^sk`)Y4*a+6m%gZt~k-50kJ zxzxefZp+viS4Ewzc5Z0yl-fo`l-IsSYw!GGk&2w}5=|vghY_}k-`N9ThY6@|3+ASt z7(;gsRn;?u{_^6!_ZPQ_{g@fv2iMoV+&}I`9)zuJTpReWht3zmR<5gLN*C+-n4IIy zUU-(*>#Ffx9&u3E&CaH%$U2|FPhAEs6(62WIdM>CP9Ge4mOhxd+Wr)&PGXR0M44f* zJ(AdP@O9Tox0gxI^?$+jV>{*Gih`=S`3KuHVG|#SV9oeKwssajb}ABi^1jI>zs@J| zu<*C`jeOz}Ws)Zf`E?+xU|M6m#I!rz;V4$kDfyjW_}##7mvsa1Ds$Ta# z5(|cJ`X+umMHlSHOQNazVc-_;!2~;4GNXv@m15I!G2XYk1&9flTzqNUYOqMF4wab& zn)}o3i-dk2N@M2WF!t!$r1WBp#e}nukfJ$x zO6%u3$CS#I9Up%c&&l2nJUI{bH)+F`uQ0yoDB_{U2L!PQ?LKR!J_3l<1RKF|bCuge zNXgU7p?=E2HweyIdU~ zW)VK@waW1EdORFnr8cWG!SPgYwcu=>%@ZMibJAezg`!Md@mVBhWBGVcuD=zE#f?GC zFV=<3_S=PZ$aU|e+^547GC1fack{D~#ZBtb#rKBx;`>91rGta>`u2xvok7|Aqb1XK ze3swPN!DuN(}sC}GH9JUaSY2(*kbI>d$BO&#o9UyWqUa`*D$ASo~tn2+i+b(%J*E8 zQr}oIng>SS>lUhbMjEx1)isYSBs&GVtMs+X<|@m~a8a6E&kT(O?QQS!9eydjs83e1 zK6Kzk{V{(umD+H5{_(i)e5I;1y{UH<P8v=r|B3U| zmOkUn%3VyeyjQb(ZKXlnQcl36yK*M|YVS-oy8fQThvb-K zQ$=rnH~t9CC&n~o2#X0@meLKZQ`chQo@#3OPh0VlqA?zf-`^TV5=sB zr=mN|9ss~?$VnBkQySExe*;B!Vm`>B~gvPzcL$Lv% z-CncDkkM{>DKskP7)j^HZr7+SX4JT^iBwow-_WBsm=B3Q>1Qg2+VJ%#ibQog7>63< zm~d|3LeC{y^N>;{YOb?z>b#HK;t6#Yi@K%@Gvb_{FYTye+BdiAF@ZY1b}6)~+@8|t zTF!e@4t=$ISA)9vu+f_OoNMB{bC*9_2(xkg^f;ZW=#LE>^)@buBIu7dTo9(9&Ia- zz5*EAwlsE6H`Nay*8vl_46a>tyPmR=3SHZDhM830??L*t_UvegZsY*(^9$7%-x%on znuto%vGhUp&2H1I$>IjzYuc1L9f5@!OOEHeO~{Xg)&J`Mp0F;hgJQ<4Zn_9B;8emJ zew=!h+|~@$iKv(^n`ZvZ@?L{iKticjpU!7AV7Q*(za4D7y8$M!VMk5QPE#HfiX??A z@gPBlB7tWdt1AYYXXukNAB_AzfiHq>Uek6{*Bq+zjZ$bYh*s5SHd1fny|_FcmrcId6))Prb#V@}xOlg(Ht9FKI zkuyD$CQLxOW**W6JQJEIBqjI*FlemQ`zINO&!d=m&1S%e_3qYuX6qwX0dic%74}Ij zWrZ6XhPY9<2ylV5G!{@BR#(fn%XpUtY<$MkkDGvumNkq9-k7~V5uK~cm4>P? z4%RL1T9XxjjYkF9L^xT6PX_s*Xg1d<_!#}hMegRqLFKv35f+bK7qZfk50zs ziM!{tH@c>2r2Wid($BWUm@gjShhZy!lZ11yOl7vDq@o0<>p4OA(zW5K<_%kc6yHoy z`y(HrGYH))9~Pu)$G}iVWuu2gP*FVkmUH|Qms>IU%8xDF#DvPWIDbUiSitDD$5%?A z2aWRC9UO~&{)&4Kf30M{6q9^4>E}9wROR>vEqrj?>(a=E@}w7jnpxXpnKUy#-+k7o0by=e zgZziK?$Oz{`b}u`jX?2D^*ZeBQ8Jnq{Ao7B9E93yBDimRq(05h z29FyJe4YnDN$Hiju*nKMZh#Th{WC0OHwOSlKHv z!D|0hSU?EpwB@kH;rXzc*k1I9rcOw)-rugBh}7gSzLUby`&B=ldXBnfxK~4&{C@i0 zq&inOs7PLKXon|i8bbzX+4k$mBj6#3DTt;AdS9qQ68)6Z8k-Md<$sSHa{Yu@mBmEU z4Q-pCJTWs`n7F4C&)htDgy(b zEZzo9QHyt_3qapf{WWFZ@gKp$oGPQ;MwEIC81zJ`|3ke5HDX+Yxk!L!^LLJQH8RN) zq0vUztHo18K_<3E{GJ0e60ke%&#gaCqJcZzujhj5-5WjnXC0y-Jtx(MfKMGgY_y={&w87Ciw zgr=mkc+-5!g090(%va@rX$^V5_B5Xuii2WDpD?Q_2XIA6T}3tlVr_oa>Sn|pUp&D zHD5R5J#Z(eQj6?$4M>R!o{>FuO^!W9Jqi{zWMCpiS|0un24ihAd09yWbW9t`KKljS z0AB$>HdNVnhBX`o)xvi`E`BpAOzkX;*F$^z$X9F1``lwFi8y#C!qgv#nC$*QU}Xx5 zDAiLDIp7y~Z~sA9z>YRqoOx(XQB)u4Tk95p9MfN&ax$b-zcegB%5F6QI7Re!`%OA= za(Etij1<2b8sN=|$u~l8b^fS-T zf&zIjv(r>qb5e*MxzE)%U&W`Q&?@O5qiOwkGEa5N9C9qJl2 zwoI!_!A6&YvUq+BUMGnUxWJsmYID16iL5>62)EJrMVa9s5GnLmp6rHQ)lNjIyylU7Vk=AIKFP5i5SiyufDAvADB zrB07Js{G;k+I0*)^3d{Cwsb`#;b-e}Qvdhc%PyDgS*MNn2T1gDR?s3OX)rr>-^`8&N zwv}lv{RfXRS$zQU!7nEG>+Cx)B|!W%2Q&CZP*;u#xLd0KF7Qdn32G)tU|Okc;L~B? z1G60ty)N@)zR}nh4Vs#M1gJI#4Jo??g&Oh?4>P!8P_s*3+t%BprdP*P(jU z$c&}({7g!gX#x?kB=d9yC+sk2Hbx^U;o4OnBaRrShWjL1vV&xKZwH_BF_sK;J|hYa zJcx{ZfyG@)lW|hoMA=jmpA1m1U*`a_0JzXxokh%93!aCJ?8!S%a(8KG%#OzlX6z+b z&nmEdh%Z(}`(^^=D+~a=DeUiFwr?rM1OQd=O`V!?Ok^&0F^1y8NAmCIqJL{kHq(5M zsocxuoR?h1E#)mt|Hui(-(DjAe9TUZLPA-|zYoV* z$+tVYy;8D{G5#+t1K7kPh&XyY190e`h%R`%-L(5 z3KXotYbYDwv;W_v<&v7(E)F4(Hxej$GMmwCI?Sj_DX*`T99yamd~CsD!$Al%Z~%K zBSe8OQ0;x3x#|%L_$0B!$4xsO`VQvpUiR-^7^!O^V)BCUjOsm}ZP@BCyo zK$E4l{3lT3glSK7aVy+K|DnYB7G@To8&80!y~54ceS!Ia=E@GXBA@^@vg8H8Yvv9` zJ=b)zcJZ-%>t}fRYi)J+g3IM*44AvTQhzH)*Tp()Z}Keu$9L^o>Bv{K&ZGT)xi4t( z%ZG~a$`utbEfgZyk0;U=DFm(h{h!CTR)c^**5j*{=;4S-0`eh@?i1xTgabxuOlq-o zW$zyGth7gs7gKvrrR`BC!d3;Q&pn>3b90RJ|fg|Tm8hVN* zm(<6rIz4k}mUU2J#5ftO583viSV3D<}hDWQ)i2oEVa78K-%6#Tgy;U0=Iak2L zC?94t3!Lf608N2ryL$AK`lx=zW75>}-u=I7v4vyOw`jfBeqap;mG7U4+#}qz4F7Kx zU7m$h3X%_anDOE)>^8EjgT*oMOuqU|fK2sF06ZIUES&Oq3i4w#Svv}ds*b_o2Pk`ny3X?|M?;Zf= zyPux;uJeEK-H!jscMT=y2TdgZPkcB3|G;+-%;H%AzH9vdn(xZ~Kk(i0|F8Hi_WwJ+ ztL4ZlF@Gb^xv;!AwKYGsX-e#e%E)EH-Gtb8#AyBOBCu43qNf(bi&^3XzrnWdQ{Bv{ zZB{~*b7R&t)r7fu6KqWQKKOnmG}5GW1KkV%OM`=Ht%n$Yh5^6*y`XDnEpuXX%Ps6C zV|;q^vOEV)n4M!&>NlDOas`-|D;>gnTmmFo8Ys}C*R)+BLth@s()8qig6=Ype={VT z&>MI-9d|bK)ZcA*KXfnQ|2WG!fhUyOcg!V}$LZDOJssZlU#u;?z37kV_V%v1t%AWY zE|8N=so!s8%{_P>cQST+|L$$&PJfuvs3Ex9UCe?{$D41mKR!u4 z32Y|twmB*!e5hi5V9TAA0KA2%hs(2_dd{-v&oq+9tc+?J6q@$Z8)s$uQ#Po)6K}1w zodsf+G<8<`f8YK*?I_J9j6MDq$rS|&2D?mD%t_VmvLq*^Rl?8yvhjzb7#K)ELn~7c7J81y&=Nu zwiKTK_pjr-hBfDVvsUcm@e*j0lwo^cy2x3}2{Pmqhk&uB>$fM#kG|v!+YciB2mrsN zOx8|+G$a2WA=DYU7Ek3;SJ80hC{cF(;F!HDQ_X+1GE#14DYIY@zjYr|VZwEN54fAl zC{p;n*7#{;s_s-B8ya!D3)L7by`?TS?z_yZONM%gA~#6-wfOoy8*hvsu8&of5^Ah2 z*drXVtdV%0m8nNM#byS$K`xAp9-S)$R!<>^D@T0d_N?+c9ouP-(}T^rinbQE4pw^IVBF5a64)Pviy;~69|nN2{w=ag)g&5YwMy0 zXLD6AXIRDSAkKTu9LhwTQGXwStZImYpcEP8v34`ibA}TD&No0HM9^j13;OiDbHCZkF2b@*bv?aiO#wv1uGmM>B9P$b0Gt>xW!0Tp1hw%26pYX2Y_9*@T zFUHV^*t1f?`->HKc%7!pU)j3-jfPw!RIpi?q4LLdu|6E)1eOQdN2%6bU!!@b()7jY zH&cj9X1|Ve4kP0lL?iZo;H1-|Df>!J1cX-+g|XIlRPW`>5Yqx|HBoB%3vIxIYof;) zQx|0e5ul&6$t<=^_r+k5AOt|o#7g>MJ^-Q3X+)E84=c*TN_87!EDqy(2AluL~T^9N3-gvTRM{4*+S z{ZF|1wj%WNB-PcTw=@$x{3^Z{nWSKI4y(}^I9PnD5T*Ts#aW~FIoN<|Pi?9`B~FdF zg%0>HEv*s*W=|!sahi$injNwJFHRp|<9=ft1;OsOGO$L~74J&r)6H`XQnt)i20otk zZJB1ID&17kp=s+AGMc65d49j&Y4pjZL7o}-FkrwSb4)wV<5l|nM#hKsnH|HN28KGe zy>al+eh?K+CI$5*pF@szlSIIy_+mF5GwqN<8@3_M+ z?!A-*L>vADcRn)@yHg(f38FdP@R01PxbhRPxx*4ec+S<9ldV zlh*!0nB8Gin@5*Bdr$)L0s*#3aSMzy z^(-H8Cb;F?rOr}ruRjde9#u>hjW>VC0qO9MRWz;!W%ZOH6V$AIgqjtKbpiPY|IF36qv}IBpPV>1lO{8XgphRthhyu? z|9C~M*g~R#&uX(EYEHj=f0w2qHVS8I*)j??hPXJnJg^q%w!;9kX|U7dKvi;12t+U0 zL;n2>;E#Yceb0_;nPe<65DD?ez~oz(OM;kgG`3~aS^*!=Tego=Jy{Vb0dkM}CK|fU z48R^?9(~K(<&~>q8Fj;szy45`)&KZIQ})ZCg?YZ5h1Z6V~-uL)5Y<3$$W(l-@z1=&sZ(c_!Df}=l#HOK}-$CwUt6QEa?GUMY zF9b%wJW55x{Q#ybOM)m2J10>gPkYpRV20)<+flW63LM35Kp57jpjn(ap4(m!g(O|;+x0iiO>S4@$2F7FK)t2@w zygsClcD}SqC!FELwoDjs_8Dv3eUtvnD!XWoq|zWJe<^a9-5JFFZR9jSi68Vi2?0iP z+>%!NX9U@UxYOb+;Ocw9jGwIkUltrIdAc4&EOBV?j(n089sb&H>cnhw(akyMzX3j8 z>0%Abadnx|nocP*xaSl7-#%A{3w1H=8AZ30BZLdt(3#a4M9Igsf9hs&dxSa$QR@E- zqlMZ(deL7SAIYg5X^&O=*e!vIb( zi*A>Ybo%fY8$1F^rbj^OG6^}eO8pN`Ry;&bUTHM|T5E$r`Hj~V`%l$+_K{HfGYU-T z-HANrCio=)&DsGj1&DvW1hD1r5#G=KXfx%hpf0e2nakV$YcZ_|d$O2f1Ksl7*|<_a zCipFYSuarGgp14AaXD(0||qwPa2LQj+$zm zt~U4;0L7_2FOMhFPX^Tc44!d2zeGD%%qeyx7V9;E+!jdYqcMvf^Pr%t(>d9PJ;I+} zC(c-f9+@ePg4Ou|hM(Fst2WUIq7!^Hn|1|0Vt`mSuq~OQ#Uy@)Y@NDm4G2NDCBgHg z&_5n#b+FY`SsBt?K4a>?9QluAvgnaaVn;`&=2JdtB(5ds`9e@O08i*-6^l2QY7Og# z22(dvJBegON_>=RAP%b_^YdAjzqKSOn2wcN7a(cpx1xJv7TDjR<{>VrZO*BKtzh551*f}8>%e4SsUgj@ODVveKsD8Cj3-5YNdqiT9xg@ z1NvrT$*NiGvEQJB;V-+E_&^TWDhlQB2r0h;W=5K{9p&d!f2-+S2jV6f`_X@MN9KGN zvz7Ws?s%ZlSP=OK-LuJftX6doz^hH50ipUwoQjL4i-9be17bx!0@dsA_bI@>9*r2T z^k{sXjdIk2`lA;GdLRL~hTEz_QxCQf1G4H~ ztw**WLK-}Dg7PEYFOBVz#{PQapZVmFR1yDO)kBVa9OqMt>5;8|NSo~vKO4&Wk3&+2 zy5@g5Bxgi-=CMnTYmq|=3J-Clonj$z^T9F`yZj@VEM^CT$@XP8@c^ycg;Vd;^KcH8 z&Ct+)*-uL~hfrLr?I;^1U686r<@Us$sbBd%Dnz9hnCxh(8Vnv~D2x5TB~ERs^M3q` z!k^$I3IHbov9eg3M(__)Pr?#PJH?Z^B>#88W!;iC=2KZ_<(-)5btp`K_wLWu>Lvo} z3{jH6wBVV!q{4rQW!0-mb;EzX!17o$2%os*J200#hW{6r^nUTkC9&LKl&0>52$lMK zN?uV)R8PPGbBn0OBJLmtm&{5MUv7kXfo0TDJLeb1DI;wZqF)~8bcaM<2g|91Vfzaea8_{%8Q9l(szK_H}$rel^} z)j!Yfc26Gu=yI}9`KL1ynl@}NQ$QBkBnY!;LZWOhIn)fgcj%_%Vw@e#=E;7F2ZrY% z3lf0G{|W@Fh$|Xz2EhSogPaa_p>6{%R3)Cu5Spp7Q^tQpQZ6a^UM5~auw>-If=z$| z2O#>~Ui$Z<#cR`$K%qYGV4rc-iR7MrRO?r1y8S^9U>X1y%mOXaO!2 zp~6K-g2)kuNndvh$iV*Io}5SH>XQ@oGBAp=I-HT0Y^r%6I(y1+H76g zU`R!*OTDvbAiXd;+`20FryOEnVX?9tnQM1;UEvQl?}#|7X4MYSnKR78^UOdU(Z`^9l&DM(tb;;n=3hYbGME~PrnH&Y%fiK zT1w|`nra-CfNwmku4+Qu^5UAE-H#BY&KB@?zxOi=`PuB`dAE+Q4ZNJ4#ZmiXfzWg7 z`moE!b2eAST8l7|TY@dLNmZ+iF8$^b7lYH%a)>ss;kPgFj{cGIbJXr*~2KO^; zs=293iC-1nd7jAI!!j?ZoHbr#GcC!yxxp6y_1wgd$DKZ*>_&AyTcUkh;u;bE26XnS zkG3nU6^tpFyvuKIVH4_mKgJfG9gr!_(1S?I5GdKzrx4M1X7#ZnCv}*W9kg!)&rxE0 zhW20i4?EBBnBy!}`7+*bj2tjN-0zj3&I#ZUimI&$F6f3=#h)_l9ZoI3olSF91?<#;sW zD}!pa#Cdf!bk(jC!C!kh5#c=xdPq_@$o6`e1uxC8TE?As9Z)=IT2nE%K)rN6tbLGc zG-MacFX_cm+VHj4x}@}Y!^7*N>+00lb8#1W(bS$#0sxxCj>R6F<)uOkfbAXwJgH{9 zeHgSgCL^g>>~VeMU%9tY>uGalrJU8)C-*vdCF)h!VwJLP5DC60PpYbOLxeaSZu}lB z!t=T~K3-W6;lH2qP4{rSx?ecq_O8}NaWR|v%sRAHC-74A;JaBgtTb2q^|`$F+2D2Z z;C`!~a;F<6aDYWCd82BNte_1n%_cDkNk-8QBf&Y9HRbb_%!bM zgDYy|g{MOF=AdSqohQr8S5_p0zVfu?f zO)=@zuTi#FoJno^uEZF}P;r#b)$04qWP_mwE$?u(cp~%h5J}-FV&8_;>s1bP#I%n_ zit79A{o|As`MTV#PGl_WL58Px#Bn*5DANKY4vN0^lNe&KFvGRFU9O#zL+0dpRmgTU zae6S5DPwdWrhWO^pm{p@GzmgI*5i?5$R`4@IFN0zr1O_&jBzK}OH+GXqv&c*>U4dzgSmgNMZ zYdsfuJG%a#Rnge1!sZEnmmkTjmXhC)Xp@eLlU%V@kOot4zuEiQj=0@g11odf64P<05PYlgSuQ4~S9_k=kzwY$?I4#@Bpe;b)Blmn@#QMKe ze5B;4gBw^niVOz=0_#tTJA5>G*^0pgU-uVy37kb3yZK z8FG+To(m7i)>x9~qQ}XpR|7U8jz%dP>$ycJ{0yb6tEu1h}?nzN1oT^`AeH9&qGX z70a8}Gr!m*f-84&H+@6GCmi8@cd+B^oLY==7a!HBJ3#^ZZy=bfUtn6 zk5s+NO@Mdk0m{Zi^`9pH4--9BKbwjh%H|9Luxlad&rj zx8S{R)4+M+#U`{D5?O54EA&gATVQje@H7v=20yu5c%Y?P%`i)A?03;!!ij z^qpHx3X!-bkJ+exT?BYc!nJvsg}Z)D6rybpXPjqX>bY~Rlycli;NZkL8OYL_hahQ_ z@g4J)XaQCsJUGt~ONJ>Mj+c{Q%R7x-r)`=r;SyF}>#IO+AX^OV_FW-)u9#2YM@JFi z&a2V0uCYx;#_6vm6eXkwEB%0$nuUJWW_b};y)vMjKZ z9~KF36^%{iSH>jZ9du6iDg(xPT)z}1@-F}mexPsG3@O`Z+qU>|bD8(OfQM}plg(-f zlI7hKww-1%`OA-VylHC)_*Ou(zR%E$cC$?+1}m;#p#O!N@8fS(vZRWXz$bT0T>X4I`> zxB-;kH4c;_KDo@I*Cz93Dw^o!`AQ&zM2>yki0~5t33aHg$XS^~WaOUIAZ%vKUMlQ| zI+7z+q(qdQ&J2wXX70ySAMYM8foRiaHJ~EkVs$DGW1clj}NiUEGUuGiKBydkZpfaFM z3-4UhFB$rlj0OV@JjTEs9;Wu`Z1EgT`U#^ou&+AEjE zfpuXY-Nxobjt3)(wqh>VRK*4(ujw_{V2HpUw<>{o&#_<{Kn2+lLxj2H-Mwi+t$1?3 zPfezEo?TCsp6%OpLzm-`c6Y!GuE z^iE@iH3T%(oNxvt=HRZ8l(T%bMX~PfysdhHcoKpIeH7!afUs}Q4Cj0Tr=d}bE%FZz zlFwVS{;gAqR;eTuRT{Xyz7ZWg6%Lj=z{ct?>fUFJXS?vh683#6RSu=v9v+|10PmC* zSR(bScrqQmh*(bb7Fu*Hs!~3%|N4RY<-+&AO)y`f4S8~2>_2KKdDZ}%J~c_#!(b^5M-G5zg7q#a z5BKh(`j|i1^&;w`jX6>=%xp! ztKVxYE+;u+D>3fPWoiBk1|lDT)_kJRO^xZ&@mW^arcf`^~f9zPY8s^^ z^i)jXw`?y>+RfZaV*p1OHP+{okpVr+BwJvVRVwC9!u zAAez?I!w`%yV$y2ME-_H^l3)MpjE8u8`3L^Si00s@@Fn*XSKW1pzb>tOunn~1%`^*w74I({zy((#L6x0ll9Hy(D{#PV zHTm*{8`vjZ1)L7``-l3b>P6h>Q`(Z@^Gs7_+=^lM?@5$3P26kEUC%4EiD5i=RBCI^ z%h9dRjN9-g+ez+dF6{;!BKC5%jE}!g=GK?5z$d$7s8x-2y?t_0i{sa-rz-q%DS>q=z1s;9VY}=eiO<57&kDkB%7$y^ip6(HoK4HOtj^ z)z)IGvFz>5*rh5arx8AnO?Re#8y`K}CA0E8GwBvGryQ5tkR>%g_|+To`a0P2DG%2i zMg%>)w_3@{u9>ahmM;QsL{BkVH7cNm*nyS`Q6UOQ|XXfUcr4LGf0NQTxJFilaEw$~gAI7Z3^G2u~9n zqP(pE;-E6EDK!~wZQN)K`xK9%QUzbe{JjRb7~5^w;|30_?Ahf0kSU0|-d_;@Hujqu zlt=vO0_a1fDccHM+puB+Ajy6kYLRIQm>TE?cexa8v}}5&uURr}0|dqKAfaLU6oF-W zZb{Bi;k3pn%)`_w9stDzZpDb5I4{Ez28{4m!ZRidCo)mY73q+8j21RI83c(?Xwe;j zv?14!kO2WTGwOIBE$bfPFA|+(v=-^Mau)B|{i5YWwJ_}&X@%?{{FdRA3gPdN%`v=4 z6hy7DOBNyV6=oJ9;7s}>El8&bU(I2=DH~B{hc_U5GO5~WD6(Hxp%x$^;c$ge7#Awa zb*bk6dN)W(ha@2&ijM&VY}=vA zR>~i<$f96Q%ZE?s3+Rzt%kVBeN-UODvs z9TN4Rl(k_IT6^@$h#4)RD~5}4k|kl@u~VkYqMrv9C59&=)HWuIx~9j^a}h?dbYqm8 zfonx6;MIQHbQ_g}I#=`kd7_2$+Ku$c!lb=r^#)iZ{R*K1ZK6Qe&IJTPUaZ+SZHZR7 zQH+XV^nhR-Sa$(%)V!42^H5aF7BW~rB`K(mRm6RLI4=o~>m%SXE5^VHwKd7_)4f_K z&R4EBMVb`0S*{=RuU|`e%-v z1a};Kd9Q8z0meQ(tv;p`5@$@^h(vu1BzG)@mNLfokWyfpdhs@S+#-pDVlx($T`yX0 zdFi6YZ@BDb3pD-Ms*sY&EaSB(1yYRG$uf^w8g7u5Ni-Eq10=0_A5(vJ)Q~MEHewwZJcKN&Jzem#0LpNcXbzr9&jyGhXw9NP ztL0oSb)~Ig)yTuhngBQKEbWa#nZiRj3NscAkNH^xi6+EtbY1;idX*8d6zZe=R(Ni$ zEJ-cwkd%pqVl(zkE+nyF2-Zs6KIX>&C?IzlvJiNpVelHc2SI znPBB&v3E78$!G+JV-1_hNtm*YBjJ6XZ)QgmY|#2i>f%SzVm=odnh;UX&00|s6HtZ% zPNx}YEmPGOWMEk$^KSfRGc+vY)>Uj|AFZ+Wg+Gp{#M$7t)t3v4-vpNk8hM4O92OSa zeY{S0SRM&xm`biF3k1*?2h96iib`oNCm^jU2@;hHMA?&8MhQhO5=WPWMvf;9!9xzj zsbsO}P1+38stbc;vSU-Q-X3N6*x(T^m)0^cB{RDHz*bE75TzbP-+yGnRQ{d?M^Re9 zXOuUkd^Q9!nrOG7pckLgh&$psKN#RLA#u&O>NE}E^c8{mlc&`irfelNt2fdDoq0E3 z!lzpj8FpiS7};ti5t~xu0)_-rm2c=0>p11Y1@TNZc_5d&>CQ3Q-o&na(G%wyr=e4e zupaZ3xK)d6W5Kgiyy z4r)HqF~vxYav3Po=F@;+w+?b<%5n=ck}4HKR=$N&Ne9x$vW$S*Y!0=d-fqY>@7D*X z#7C-YvWDAb!=xPTvj)izT17HBCXue~?~5c3&non++9JKVrlh#WJ3M6mgs#BTn`aEe zQfS0p$P}wUT8-qR36Lw8CtU1yVIs|f;;l76*qvnOO|_K=qKxQTB8SKomC1H3>Qa^w zNv1$I@=FL_K8-273_|%Aa)l|9pTX1G8NhC@N}fVpaiX%JPzm6Y&4^Yt6iQu2M~2$7 zX?c0tuNwi|GI0ebas}Kh`fd9y>SBBv(^53jFL!mk{J82}${-zFS+0-wRMF>+?z6ry zu|Ic{n`y-frNvktipyC9Bla^*;BfCRh#tzfAeMpajc^Io(o7#` zg^1nA9Fg3IhDz5lx76(PbNL&~W=;S&=yDHeo<6_5S z3PS?#(eDD>PI$C-lYw8V8rSuN{+};D_d%8iXNV9GWA+db=>I*{aXa3i#&lf{Q zkF7S$Y)f3>41U7JL_6S9mUh>cceLO3dBGXve%LRx`=?L|1AeXf8VTAqo$S<~YBt zaceE*Z9>?d7`_bGGZ!;ZAs(G)<+Jx0!h6jFqefkKHgkb86Pd7I<5r7tjaFTElGCX7 z5}sL{b}wi58)}nUn|GJx^V^^MH#RM64UBqawI2_bs`Ysjq&jQrgtYO?6{Otcu{1Wv z_AW%eSWfI!2Aq();S;%c42eBne+#2YivK9MjGbL0$OZkwc$!hZPbyvcj^bj>Z}iCk zIfub9Kp}ZzvFu`qiuBu!+sPvxRY7c4?)Z3%URMNGN)}^D)ze1T^WDbt@6qHtn?{?P zqcn%{MZ+d)wq~%NqRi3vS$L6g7p21JWctGKr&`MvD8a{v#;eORO&sAfLgf7$!X4Ep zxIz^%cJdj}S}jH1@4}x4DPc!`2iYNOg{|W`VOjt74Z$ITe-Hk~zzNvBI>KNzWdr==i{Q6HMzpf|^E-$}i@UqZVQK{X0yJzHTa-@E~b9Wa#L495_5mS`-!lfIv6YZ3qsq&X?^V zY)u}(B-JYo_!8Pl*^OP@bIfDIEit+!z*Gl}nc%H$peVmvgckKWi>&&H@xuadC$gN+ ze4@Ol!XIwL*t*OziVP7-IdGW5x}dZHgh+dktT5sqmGKtDLb)}&WzEwAy80x%6{Jb_ z&Qn4nh&t1}B!MSA{T_vSg=$kV$eD^pU-~oVEvqKN6K~nNH&Hka@^3Q6SyukumibL8 z6gyV7-l0E#?3ojSc6Uk0Mpp!cQ)gVgST-ypLv|ltHr%{yICZ_%Q}j?CA_HPkWl6CE z^3}$%Os~I1l5JEq6K;M&;WG}rAp_A&B=&Pp7|>d6Jh@_a_{K3vMq?4#D_B!s03O25 za44<;9N(`1 zJ%J0TR7%vE{B5VE1m_F3u9en*uBY2va?|We4u&&otw`)&MZIT`l*};;suCog2b-Zw1&HGko0Zs=~2U>AA zm=jyLXbp=sP82y~-he{gPw;WI{MCSaC63fx?pt`qlHGLn9zJ2ZnL!lBY??Ce4is|M zyh@wW{Q|aw5 zp{4ML+~B@7QQ<|X09oi%nK0YD);A=xr4Sa2zkaz!(LwkBsHGE~eJY$d%T(GA|N7=E z|2lWJA3kbzs_FL^n)6}_A5#0#H5bm31Yr{U(eH@`J(PBen^;{zaTKfK(SaYdAN~9XES}4^;-a9C47ECI) z+9>uPKuFUi&yfePC!>uOY%XW@U~<8=fbM)+4<|lr_ZhnCqI+!l=Dpu+&AND)5+kUu|jvKkxiS; z-FE37;RXLp7%*Uu3H(ql&2jN|JUVwORlG9QA95z=ga|GMg6Fsy@<9G-)Br3U8nnx-FZUfTr|JNJWsiq>^jeW1C=i)|8l^n4WIP!>4J zuE3@R)K>BaeXQj+vO z_&;D5P#-z&)NL29cEp*~4HZioGsK?0k!oISG0&>_M$dVc?%vZceE%6$j9~H87_a?+ za8!L_JrAaLVMmq{Hj{gMNL-Z-w|?#NNQZ*!tbi~P9&uOkt}l+1&Nn-JYm+EPV|-I5 z+I2c+NDcxPS#j-i@6tQ&HQP>aKOff|(kizfO*-%Vtt8frR_wCez8O?K8%%O^P%ou|+y}hZbJHVxh=(ttRFdo*yF5EAx>Dn$q%v0&g-liuCJlK? z^ob5K;^CP^D?3NL{CV4oa!$BYp7C&FPAoC;kh5t*p5AwQ7?qo%cYSv8n`9oxhRxZe z3Bb8l0?X$s<9{Ce6xXp&&WBmwqsM7G;d$n*hRCBIgzUiG&9^sgz#1bjcFw>>B&i4% zMljSvOYOz()(frXwr_SB5)-l$9fwZmI7Ro#4E1(Lsa5%`s)zJN<8q{yxH@$0+!a?b z-}81RT>NdZFuulKupZL83zx4>no=lCm{a^$cyiRnv0{8E4!(q%U`B;|3u|KS!}{? zvZzMrD*IJ5o<7syiXBEN$l(Mn0pz!c{95_wYl` z+lr51mAHO5$f834Ho?f-&+=(f|5 zYlx+Tp`F6>U=T-uII^7g*Mc0=CnA%KLMn`_dVNN=>}V$`xFL1bM`bEPwn<_#H#3!) zCl&c>$C{kKqV0%7IiP1DZ-^k48je!hFE?Fy&!S=I3o*zeeKGk@4#~#K0pAY6!zz15Jm#X)Dsd|imSN(e@r$0uC>_iFGt(TfVWj_7@a#8(B zgIa85GmAQEJ)mu~NS3gpL~M(_0!A%(&Owx>HE134rT!3*!@1h|QSv-k)XH|=I^lvx zqR_5%%sE@M>1N42Ba}gz9^Td|JBOpFd2TFZ9jEm;@&`K)NE?Tvmmw{M?5P~=8&l{&`bSMDT=t>Jf7y4+O`k5s-*Vy$Q! z<&`$H=x{iM?b_|I!z&CdbTntQQ>>GW9;1d#WcrWgugo^u9pW{Oaw2PfUJ@a;Wbb ziti99N}lDH@SLm7Uva$G(aKoW8_97IyhKWy`(%U8?ii=;J!V!%4M#t!z-VM9wWM@gsOj4~FVM9KOy|Mg4c6dvurU(gz z1O30}=Xz}NvnV1fA`g| k>DPV8KlFQW1~DU8PNZ}lTA-rBr-?wY6x+CzW_MKkU2B^$3# znl^mBA*1Z6mNENhI8g;3dW?VbYGkM~V8^=PrH(pZbfOU|pJf`Wvjo5Npj;XzFFJ)p22_p&hUE?&1{C8u+usxpYs2N< zPa@{YWRtdl)x6UI%bEOn26D*j==(lpObeLNx6YOQf?VwCV}rQJLAclIx7xdWQr~M7 z3VdN~{Q_Y$-ocu{k*M>w4Iw`WQI_5}###l!>HuwoH0sG+#La|pjs5C9|4??bIFplf z{ueOCf3d>t4gT{M$i@a32nZbTzrK^HjWZ*|`)gIAoD4WKa_HG>WW0?0noUKxWJ5>z zWTxZ`s0hnbU?viaitSrRKDAbvH4>lM<#>{LD7`w5KqSW~Td%=vdwEKiK_Q;7`RvH9 zqgx3CGX#_|yu~b`R8>aB4r^>zQP?9%0(HEg`0eO{o^?H+R~{`T{7!Mq5_p>NsSDT> zEWx3e?ytk=B$7PJ3|tkay%aEsbkcsNPzb0iiZk;_ zf`{z#cvy%T#3^`lc3VvkOk++v>MyX_IQ*4^Md*?BmEJA>B+TDMF2}i?-|pOr#Hbi5gq8=_W2@j=yWITe@Ucjo;8s_&&|qEbyu0!5?Ltb*!CitLL<3Bjr5x zU-_TkOzoI8JNhKMhXM*h#y2iY7h39_Iq`29&Q1_;!yc4;9S%z8_T8b@f`1o@-&xdn zJ4{+_dl%Yuey74VpKH_WTp5H4-gI&7aBkpVOviM0`O#>{c!o|y=<+0SV|Zcy_PDOp z;lI_=tew1Gfu#LZlY`CXoBmSJIqpd%AXTiM z$GxQ|V2{P_!V}!1b~YL#Cqu{2n}y+Yr6j+~YjrkS5l}%@5&TV`2!qiavf9|s`LeL^ zw)avZtwlaCiehw_ue_Q2%81?hI#8x>33r&<-}x5q#iIm$q~uuFzkb*={}bozT|C}&<^{x~j_1JX&8_ZQ@8sFA8_(~BQ~SynLq+0yKHk5b z?0-w}01EqeDi(Ow{Cqog@6`s2)MoXoY>0U#u#T6{*zMD5dR}g?cXnQ{#{j=i-1P8t ze`wn4FHP}y&-8SpJ@s|F`7wsSvJ~;-&5b?$?1hdb$Rf7RXf+h=d9+HU*&fbE;q<9@_iTa3VWZP<0s7L8ZWmx z#6)f}S03PA)Ea!eJ3Kxz^fr<(k5f@F;?D=_InHu5Z&Ei)S902Xp>S1&G)oy7&DLF7 z!obHg**V5@c2!^@^Md++dO6LI>}wT%tP& z!BD}Mb;$er34<&+d4yUrADh~2OmE0Br>HDWg@U?YrjNQT#KaQ1PmuYDM!bbMHR09> zU!`5Z;s&Y*i-_LTkDR~q4$73;?rFg`=Qc9WAh8BTB!0pJ_v|M!jT0cYAt$wUE?7b5WsqUQSQW6-5(a!G4A+Yb{HS9-tNx;NoW4b z73(&rho7_BG@L`cP!7dP-M18L6a%$7SAINHwfp<7?_ivGYMf2nzf^j;VD$0AVt8Fs zolJf2QniNTxk2zc+3UOy>*RabPDMo*;Z4Q`_2R(;7>6+4RXkbQ$8BZC_6!&%CIGPG@qImZk7|)l8N9vhAKLVASzom~SuwKIBr00xM39oxfp@3TubXYvmeyWzJ)y zXoyQkXec$d#~iF*Csd}AV>Uk^L`lB2(->0rwNDF-MR7DE@;45LJa^?!Vdf)RaWiG< zDDN8JAk+=HlFAYWLE!r;d17IV$5935Px7&0BTb&p|%(>c&{ zTDTz{oRxVyAHv?x+X6Uv)y~a&-nQ*`AGqTu>U_P}mO5)Ix$_fpFPXu#Gxh4#D+){O zuaV$}%ZEpj^Z6ZKPmx9aSk;g6bt6F~lc~Gmg=Kk&TrJ`Q`udOwfS27zSP(d;!9t3e^-9R7XWs_fwNwz+YFb)q}CdsBC4vNEgGg zrLT&q`x2LTC@q=$dW|r-Oja_8i>Oue4PeZ_wl-vAwyC;#Dj!r{5jr1jNYhIP-0N58 zsry5AS=IlbOq5hI@xZw@o)2otIiAnF5wPUkl59!*6S|PZOO_~mrx(U;;B4*VZe0n{ zDv#$|!`jTRXEbq1LN35mlg5z+2<=8>E#4P-MZgF*X&`u{Rx}EM>)!s9g51)fCe1hH)BM&-+EkPxiIXG zEU}s&C?}QHxaBNW`EdL|G0}I*lnFG=Yd)C`f!bxW&uD0n`Ph-z~`TbtDfDRf(_OAOt zltc-6TQ=)U+6UMLN=L&;-)o4^>SE;Zi8c2-vy`&u^be8fj(MaEAdp`5bW+6kfMq>7 zI&$5M5nz|}k0CKgGi7XSHre`veW~Aj)Q=YH`}g>qF`?-&>VG4v>cSj=C~)4sOCEe4 z!k}uaH_S{afp&AU5HUvMz_%_ke5zsl6Ti11P!yLDcLbHiy$Y z(3Zj5BK&`cz1)8Nq(+D`G>{mVfCPZTyR2&|L99Ra42t1C&;z$eT7l00b2yHI@d3Pn zoT4j6B^S%?)4QsLtic+0hilnp2F-*NUD337{`LU0Jb+YI9c6#{C!x8N_*6vqRnPa( z3$%u9K}>cO*WAd+0d^AZ@8CE}slBCpK#r3=Zj1o6_A19Cd5Cv^@6I0z5|QfYjd}Z0 zkt2bTQx(&z9R41G|A@L*kr!r$T!sr(goGI#Rm9fQh?=fw)-bXKS*SQ}HzNPrDnX}v z^VfkKKZmpJmx*}7Vb&7%j8C2^gv0%**2d?Y5JA8T@+F7K+x~?3H7#-=X&H;Kf^N#8 zC5dDT{w?hCFD;8~XGaRtr?Yf&XEiM z;uh%Loh+hdlOf!~)i&>7`aTZT2wBIy2be`i;=G?}c)j#M89cHPd1zL0MbD5O)j7t@W;VhC$`!3pz!t7mIW)ib z6Z{41WClo8kz7a;qGkOb7wy|vevdLl9kech8L5;kgbvVEU@W23{xsih1y;FRXEaP} z14~lMO&@aqAA{gYmXh7)6iH6_|H+X0OZnd=U*$Jw@eHJ0xKz>sX52$;Cm`vM$au*#XMjZX6=^vnq zK>kwLyM^<@3zvY?2Lr7LU@?~9+WQAsJ+uvgav}wb_z#<)^32K*kOg2ao_E;D0I9ak zlBVQzyGD7>5rUxq*vJ+$$+)ozDFqd0-fWDdI5F1D0h^~1~8k7OQP>& z|C2LCm4tgi;}8;SQQyfUyP;Sm*?P~Yor~=Z`joa-%u30y1@tNO;sw;?k-`O~9~IDx zJW!WHSffMtnSI%SpH5EiXU<}NyFm?xz-_zPf z*cmZJxH)jrp?^tB5k9!dx!uB=QgA4Z>=2Vo?8gOwLF1KyF+#OYBJL-fh8a4QQVxRf z!ea?4HBvhw4TC?`uF)DlnI>xdNWOEh#6m#TrIH30)>e78BEvy@T_TUSi8MX}wg zqp7Z$j0Pb5mEzvRUM#+te(=0f=g$-k$1Tv~FTVzBY2t<@8>&SI&bQ_^v_Steb2i17 zn0H)lmsA8A3wZ5c6JRwo13-KtjwMU&pCrdauk|Naf?9*s&NGCIg(HvvXCDZa350Wh z*-A|>KtDpLGV%*1X#f2G7t}f_$virb6j3512WH;?R zzq0w%g)GYIk1c=WFA*rGHTO%M11~f5v)1_Tri!2CYuL#@IFd9;U|q$bIB!*@!?L?X zJET$ZHRV9%^K^SYUuY~WC%zpZk{8GQm~qa@`ulv?%Y2N_fx%qaXx!&FcHFlGI|6`t zwp>jv(&g)z&)&oG&k%!-`Ti6&>&7PY?C&(Y3`t)3fVKDMEnj-m7#uzC`-C@wi?%u* zZ+H*)Zt#@mpwsvX02ymaI+A6-Y#FgM;89vRV($oTe$NrDWqQ8ltr2witXjg~75jR5 z9v*fn)9kE1XlWWswO-%Jpm6~*MqtZ1I=y+Ze<(Ly0iR8ZqGx-Z&7H!D?c~KRTeb35 zeWotU(kuLaWWM^nScx(uw)4<%S?8|rDOk;)$;YuCxqoak|Gsveu{;K@>1FLqd<2~T zQ_)VWmw?_W?tB|OfrxLxHOo4*x6vw;T5@g-#B?w*e~m({=x&BuglD zkEvYAorhm9yM-6O{55>!m<-+07flaW)6{ZZpO13m-YzBj7|%K%?18OVJ*yMtKA+FL z`M$(Fz4_g}cEl;Hw>w`>WnU<~89%SDG5VbsrchQtO$=D9s563ZP8Bd(Gqrri`dtw` zaLjV--o_Do_=2|(dgHgm)tE8kSEM39n{lV#J{c1!G-3n7{9vbbBa~!+s=HzRz3hHI zL+YFG9OcVri+H6i!DS+^No zvoZvi{U5TeX(Vw`;&)IU^i3?}n_J&@F6Pq-U(c==a&!G&f4>ktj|^!G2>9OhTp+zI z4}8fg5xC%ShYDb2kW>lICjvAzj!)W8gIDjPl9Cx$f!loZ92C!ph$o`hO3C1D`?RZh zbRjFW*&YxwkUjkvns)!XOy}9gZ{y07rZ?p6>3eS;>_l@h(b+`m;m1%HkRTl%Qi#Bt z&0ee9I?OW`R%#W`5t@`suzO<^|0?Yle|IHMCXk7<4Ry11>rgUZGhg+jo;efq8EWm+ zp@T@and`1v-`ke^Azpd#@DknEgSaA^V9k$?kz$9M;m_B>v(t}Dde=k2P@T%{XZTp^VG3}BP5Pz0`c@iac7-;=SG4rnqXzRXdUgnS zVLP@^b1ByN^G^$yAHP~!)VMTwwt1-}d+0P(?7b2FyP$NbLtpR!EF_hHgMi@wT~PX~ z==Q(MN`DpLlE$ohn2VCWkQt=R1fM{7+v}a>;zUqarS@wg+XM|F-mJYMMEA0x zJ#|YY3frJJN?h}d7akfW*EtZ{cvkd-;kLBi^r;lMmDDJMYyrlZo3&K?13}Mc9HETOdh$FN-cI*!-N)x z0FrUL!$!l`gOQ?V_4cfHkHIL7CrFX{9doe4t!E6^wHo?Md{(> zq9X-^RMpmr6lk-@6C0Za2VOE!!DrbEUy_fRWmL_ zIuwVbnKTz)s?BgrRHx}Z?V6V6+f20S_$C0-UzyLxNh>a4hIiy}H#tyYax6?$wT^p6 zW~u7S40yWT$XT|-O)i~CQi>bk7llY&RD+%8(WD?DCRz;oJ^SXzttmbmu)PW;mxtvWDj~K&^KQ=mwS5FS`D8= z%2tlThe0E6T(Dsm(h3WsZTFt=! z(*8UR8Uz9j4>H))90VZIhwF*BaQgc&;!be zeq^#S5qneIIxXbRg52&`CL5_9*|aq2J!?H#1kFC%(PR(Ies?pZ@=fedVq5gl47?er z9?P<@8R-d((9+6e1CQx02M3}Lol02DjbThW{%=11y7*l;=&pwTVrSckp+d-gThuwP zF@ev)t>LrM&c!k##|F$F{5fl_sK=(yieU10Wt+d7+AG}$mmv3L#(XWi-NY8AQo}Vy z7Kbrj?ICUbf%{e_Av*JqD`UU4`S=mDtffn&?$3o8+88csE~G!8?*@7I~NzF zpZ2S|(+{F;|9UjPXk%`CNq&pDWAjuW#vZpX*kVz!k3mm}Fh~h$bjtqU5}&F*>*aw2 z0U^f$0m1lN;w~1Zwx*1KUYY-tnl-iK&^XZjYG&UI#=O&4DVHh-3)XbKTe00e?*n6Qhl^eRaT-DT0V7Hz``H zd&%CK*t_?+HBrd@j-%od{)IftHS4e}GMMSgQ&3~kv6>K`f7u~kwF@jS>vt?`-!xal z&2Oj@LlrU3e#sTq?=S>Z%Ct)^x(7#g@g*KX>4bQWD8bWmD6N43WG5WOc2&xjt<1=~ z#2@P1ye-*C`NiY>Urr9(-LtFE&V0-LnJH!vb0d8PGN;*wYP(x9UK3Q7iogiKPB^^^ z0*DAUGKYoGzfFS^lqCA%yB{_3a1@mH&e!6Zg~o&~SlCUxEI7SY zwD?v0I_B7WWdXVWp=ap|d34g~?zIkY9dIcTb*5LLUW0ia`t}Ixw<6;Yd;bXbOQRd< z!Mv&kW#7xT)6=Z(U98@g+#dmUIDT8e7rwyMlMcP2AchvG_t`lGt?%2yltL&=N5Dl z`aB(vtGl(cUHCs=FKzwmYy?k%@wvQt5g=Okce=YO5m>G3YKDZDy9bS^6UDkfY5i&3 z!88KlL9I8jNepJBWz(Pt7ChS@bXrjw@PiW)7IPJmLHs;pQrG}ICwr9|c|ERI6b0^F z7#%CXJ8Oo3q0+iFe%w^t@Tc(8wvo|hH3amE<(aQcyAL5R#t(C_5Oart%cpPlsk6qsD;w+Pn8(bNdakGzOJbKCSs_ra1m-RbCRlb zM@CakwTq=c!oOIXh%FEWU1!47B(P3DAy6Yu^X}YIE$Mm}j|6GzxQ*4fyBXW2GsH7B zX(xo#A&^0zJKbeCui34r=r&AS*tW;$v{ft%YSw*;bs3xEJLwPmwiSK7rXbKCaZ9DL z27y~2yH%l?_ZCx6E?iDHNF8P>^6+R}P%D_+>s6C!nP<~orDgqjUDxSkxDAWToomR0 z?dK2L15LIYjniK4$Iy+&ZmNuHKXm+jlI(E0Jn$Vrxg`iV=^SqcC`$jXo~WZ#wONtI z{k*kOfnXA}8C4|BE*GzN&J6Q>0<*4OiZ$GCCj82+S>Nwdi598t`|-riyD-Q{-X zJDPy%4kgbEnu}eiAR*fx`6{~-H8;1)3tDz;2}4_x{)t%VkKv#Jz-#`E6@ z9ar9ut=#$2t#Fg`0{?c~=2x`qad;4rPI{34Eb4JKb#bw@Gk5;84oz#tX+<2>_g9U> zL7zhXE=T_2 zkNWy+`gR$P-!wDL8-<&{>x|WmpY}WYdOFsn|N2znbyc9xK@iydQd=(Z=zQ-`R~{C( za`VuxUv*TM4H$9rX+e%oZ|GrpnOkz(XF!t4s#$)Rw0~T2ME!njZ@=Q{e1AsG>@)Pk z8~8w}xuO`)oktITEYW4ZZA`aq9baAhWo8b#ormG_;0*D?62Lp&ws#B{>-`HUN+N<$ z$sSb$>7{+iW>BbNN2$As$F}mdesXJ_(c-KJ#a$+W)4Z)DX*Y8xjk-Qe573ZLNEm>5 zB)4kU@0W5|=cjY_`uYk^*8MmU_^*jl`rh;*+vg>}MI5C6?g+OM>d_A&NNwW=Qw4LON=+vge#Ahp6OY?Gm zI=8-jQ-8WUhEN5t`Pm~Wx;TETaJJIt;aw)C9KzT)|fe&xnh28HjK;HuNk?IL+0 z8NshCZ0z8oOh%n=txAQGC!7Q6*WKHz+r!1qneWr%-2+kQ4}W+6?0UZ!c6xpPoQ8!n z|J{Xyg)n{pH_g|t{Iv@QV{4u23nwQN>gsVrdk2I9tIzi^t88&Jlj3fD(FpcMVg^oF&N>uVl zE;M|KK@dm#n=&McJPsv=u+#5iVw`bUg&eY8jgu(B@F-Li>QW|D6fBMso;iYZm! zgu`zlGsEL80FnE=q%tGu({kvxyjLIzqc~>?c9EWz!h+$Ys#A*MYHAo!$hIkNgC)}5 zH2HgtlF`=NkSC2ysM)j0y}?r;bwAHRe64IZl}U~`)47laOH#I_S+*esxWYwxt;qPM zi6Lsh8(k$+)DW|&Xa};yTl=t!;=_4{s8YC=saQprLwM5wQz(b2Rcyk<6By;gHiGQb zOUMws12`A7W)6h>C@W&Y@yIOjNw!eY8CgFHIeWONAQJrp1JaL)17lM!3Q zTFdA?XZOD?Cn|nXn-P=F4#a91N-5{v0Bw$Dhb6}S9N1NDYhQ1} zR086g&{Tp{@Usg1Lm1f)47$c9$s=XvDz18qh*gUZY{tN>KFCekc&NC>PfFu#_0B@@>Q7Q!7@C_$rDEjm9Ra zApC+(0gnc@3|Bfys3J@kZ@FR!AnR5*r-A=+m^f-VFXBp((xWwON-7(cv`||`wYQl% z!ET+hg0vo>pr^T?D>xS@bcaN;j+?wCMjQlDsx6sOk6mx^?d1SdJp8U)IsAfIrXM3w zN?e66+(jngVuLQkq~BLqDbY4>HP}fqRvKHiDB>pp#*e`gF&qjCZUq=6%Wc~swg%-u z-|!saD_ADIM2J1yED8CYB*8hfK0I^wfFx;o-6)kVyF@K8IWRo1U3z8VKUVX7*`j#! z+Vr#tzleECX!X&H_!NQ#SVe~4Iu>9DdVI{jYfZGsjiizJLM0r84(ZBOA2~1T@-`UR zvISSKostw-_mR(aeJC&K152#%OIEaw1H$(tpRbRKA?O1vtuo{(46`hOJThjj+3BLp z>LG>7I0xbhOhq;!CKIUjfkug5II|ISmgxyL_|>KhjtjtaS8?Z{Xtk^~;khm8r72V$%S3O~lq~>kG#?W_3^|H< z!jq)1;fz3x1wo-yD#MZoyNs+WU5c&J2rq?r$$jRXTPsac{BlS@OHaHRb0Ha=STG1_ zA!r-@yAQPAx)|bC_TgCLW^xjW z1YkI{$NkgnNP-n&FMeJ8a9VU_p{@}w+1#uJ2_7~{i167oHHCSq(tOODmuzZ{qR%=5Uui#x!F}I$_7x}}&BAeLTbi3u@AnK{)^3nidsv^I6 zuWNo$mE{E3HCb-lGOkElf{I9Ct7xxV%Q#Q_5z8L8EYY8w-A7k?668 z-R1`gJ531Pa;!Q`gE$O8(|vWf_(Yp6i)isljO$0>^Q~HqN`w(6oM-Wrz!s?{H)Ng@9uB#6 zkTX-7TbPklAsxIj8bT@-Kovtj9Nubms0Q}=mt^yPLr_Y5gp$g~P@8Osl%xHRfl~bz z5w!M61Z(^Ie2GJ|(%q}pu%B*8h;K0u59z)lNwfXT(}$oh)MG59jgcm(hV@brmMoaZ zS?qG6CCCC}uhoIxon&NBwU*L^AJ#C33zqy=D$%*9K~jn;3}&p;QY@X?=qp299Ey@yjd^o(rKptn1)l$vVoGqK$Sg zC!kdFD38Wi!X((B3^N!XL25sQ79B6$7;>(Io%@nzlq4as^hw($sZA#!8^E2t@^#S%wO{vu=?;^uiOxJ z5FOsUX6A08`$6x_nXbgtEYCUmG-R*g7qxnMc!1fq^~L3eW@pRqe(%IT%a}V|DO0~W zaQkJHBeY$eG>D&PqZJ>_oYTHpZr+?N_HoO1YQ8u@K)NxeS*d6J} zOrvRSprT=5!1B3CpW*4r>|zb(ZDevCx=o>8&+Tf*Z3(iktwv$_4eESl9iP#oob%*PX|J3e0a%7|4>sZ1-|EzlX;X#w(SS_Aph? zRzCHT1cAfRL~{NUw-kXw6eVxcVx6-rSeyh=`{6x1(QO&rvq#cMsh^beCPUutoeOww zPT0(c-x70ok=szNiWH49zEYMLahvjw`A1^bBihezE8km+dK%%hC5A44=$?!2D;JE) zv+&xh1o2#RL#|QMn9W=u$%Mn{RlZlHS))+Wm}J)bc@0G;NU@i*JBrYx*y`D7{`UEJ z|IVsqt&v8nv^Mr&sal&oLG(vW9giATnY5^j6sq#(*xnUihxx=_h2JTW3l^?x`=G$f z?dTWsr1)6wWwh)XZWi$0`qMO0J)-Gy55!kvJ|nL>a5>cWe$vSki=|hCqy(dPE~hV) zqy;fqx#Qz4TAg92DOohdRjJ+7@F&~WWQ=W*J_FLo(qo;R)S50 z50CL&<>fDbYX9z>G{~|r3bu+-?4HY1FixUsofd$Vk4j=d(GpR<4YYw%{j!effNJ^d z9gL0(WmrE-%`CiobA9VQh^Mm%$Ih>m1p3yp}|Mw4y)7ggZihkaVF{cb~9X zu?mfBanaUkMo+NUHWHUTEQ0fUTtrmGBL6nS+=(dTFr6s-R_^;@Sl_bLJdzLwOfF!E z*s`FcF&u{CAX$3YH!|aMI6cYM?3N{4clga$-mO3tLIZapp+K@s)8YiS^z=t~vK7)z znLtNUa&56nl>3h=(68*J>z=rw=y1=3G_xN$f40nTlET}3WcWGw{>GLrA#isWpKxS_ zi#K(~*@OO&CC1;V(uL@ z9Dz08e1Seww2JA6k6`o`d^iSKzu^h#yKAm}NyFjeH;Bl`PWz>)G5fxXGWWNP1qoQ0 z<{Y(Rk65K{xRa)9hTD3grbl&eiSIX6Bb2&aLfxQ1%w$yGOuI1j10ErLLJTLs_jiyEL8x_APv^r>3~)3$|`)e|D&2xP)le((XgM8=3CAp`d?U zud8lTo1EGtIKs7oB>1@gkz1=Bc%Db|zQu)~!@<;nYTO;l#1;l(<6?~ian6{haG}Om zsJL3rYT-v&rqo^5dnlUX-E_uo4qnQcet4Q}@>14zc%qNVKW1QZE8wdbRR8&3tj#I*7 zu?_&xM5AOoWhk?+6;rMKzV6-mu6Lf=nL9@K1CN)ydU6zs(t-ua&${Xq>L z;5F0SksQf=a_#x_$QPkbLjP<3-zVmaj&CB6I{~`~Bif#?1N=Y%yuJlJXB5XdCHDW* zwF5=%fM*4Ub0m*ww*P4_2GacE!@BW4^|~S^2eA9^9fL%%r%nToD&oJpA3q4}ANhO# z{%0&7mE3=vpL+G!bfV{HQl2q?s`)UgUu`@YbR5I;fDqBN+1zE5?iMPY_9M~Z;>*Q@ zZuIS2SfALKo*K>g`jvor;MdHjz#`~0tLNWXkZ1{`PAgYt)}5#Z_LBInr2SvonTep7 z*jH*GD%VteDDqmbXc<|bd{b0!vl;?S-GH@8xRJ{d=tBw3Nk(Y~RZTTn&+ypl_UOyC z634knrM&^0yUlfUIkve{k?qradV#}DG{`1!{)77Zq+4}v%LCshv;wjt`<=S&qSf{| zqq@N&5q;{IvrnSUi!G*E<)c)cmxtS>ooBgtaWU~ai*~)yMb$@buq=%t?e(#Y9Vpf*=|GvV=_Lf!E$6xVd*u$5- z^;^y@4>!FsW2DYXC!a~WaWu%BO>#ffocr35aiS#U*U>Up7cwGdN(qIGG7RI}PP zI}Hl(*zk{or!$=)d1Z!py297WS1M@1b|_yD*Wy)&tX(=|$mF}<&x8toF5<;f-V4%# zHMnvba8MD2r$w34Mlf7xiV`|r_9BVa#bf2mf!mjB*zN4eaK~EfJY7|MR?&a^ui4@G zK$W!*@JreVz+xow-?KvtQ$rI|r$05wqU0&NASU$Cvpe(ZcSci}&l7=CuT6wVN4~As>)CS&pG(ud8cXSSS1=b`xqe4{JeiG?XP z*sK!nLR2<|LIU?GqgN*DM3x57NPvtgZt8kw2=ah*btzu(Z`?bl+DI>*vYEB##ko z<_!Ete&Z8S&iL`$@KnCYU*<_!(axM_&qS}pkD6~|3oScL|1i7yuZ1KD?udxq|IL({ zy}ir-ov9!!R)+PDj@|)UQEW|oa@qzzUJ$8`%V*2%xW>b(!}+1NB%J7B|~A6BMx<1;Cahdk+ix3(zi@OivV{fCme1JsXiOi?GWS5 zr6I=0a1#5@6jqlskf{mBXK9B9=)agcmHCIMuvFLnE(~!Pvu>f&0cxHHLFA)i>fBAO zebz$hp6SDF2<&W7xpjyXsUj0Bc>g{^)~rx@!!tgH5eiI%WsILKv9~1)aJe_{OnqoO z!C>qeuSfaA6a$c{#sqA*Xr3^>5|ag-7uVTE_vU-S2S%dZAJSgha5C2TUK{blO%i{Y zI=bx~`i(b-;mT)shkd#xZ0VKZ5#2QXl(R!R5@ifr!t(wVa^Nx_MJIa) zXGUXtr$4J#d=us5fy;cp++~ptQ&#lKE5BBfV+b5ZN=u>#E^6L30>TMK*uU|8z%rO1 z;Y8fTCAUO_RXyhQoA|}+ofV^Af1T5N|0r9(;ipl(y?lC-#dd2&zJ4 zqA)4$o)YqYh^;XA7w%tR97n_G<~pzxm8`aNT2Msf+| zzZ~Y(AF(2rLHrih^Ogx$Y(j-LC1Z}+{7rXDt{EZJa#T>(4%s zNJ3za8snzcm@rl23gNi`2(F0paV&|~5@twHRp;a7dnj@y6npCM`eYf1ZKJvY2k9PH zsGV>pl^K^`{7;5Z6a=FR@k~T7n6mLN$NNzh`@U518X-g0I)i#2@l02=g1J@a9~7n0 zPM3;m)^OIQgZek-8=K_7ZF+iMPv4yF$_iN*;%o60lK zQD62nXtpol5Pn|e~H+H!3Uw1Q<6}- zgxEnK>l6<%_aUz+*MN=*Y@MGZLSA;&V-uSlY4a znDZLn5tYp8UG$`6B5R4THug^>OfI^Q|7SZR{~l_T-*eIRzwV|9PtulOiwrK^v+=v| zJ&Eddkq?{mCknd(XW4}P?_6*ZU|p8In^~Ao@dzVd^7pz=vJzL{8nd;XsJ#B`aM8uY zZsUswXHGpj>zeGZMeeCzLtj?)ENu2|slLloeL|LJLHgddd0!uW=r{Y;?JKnI{*?Z9 zGbx`(FXWCD%I_*`ki9-nL58jD%6s>7;wk^d7yiqCd{SfmshIoM9ABr_Eu618vGn7y z$!5koXHQ%HMB`@7u0x-FtGt%)cAj;%qRM8g=DgdxH8Qu)UUJv<_2X&lZ{>R5I9|}k z5odn-topWulcn?K&T)0SpmaMNZaVs!EVI0wct{r^|CPF)#EL1y=1)1n3p|4Ct zm}DsjH3?;%BD#L`WnT#WGKx_B=!?M6jX+<^fH0y<4Qd4P$_8}p=%axM?d`@e?eMWd zbj|3EMTF+1wouJTO-FRy=nWWzZhw2IZlvZ6x^DDZ6`}iw19E`^tz*%(qt^@w?Zr+= o+M)FXx^DFH3!$6a1xYueGz;)%1)h2YY*quI4RGb}Iad%50NF0#bpQYW literal 0 HcmV?d00001 diff --git a/.tmp_docx/rendered-demo.docx b/.tmp_docx/rendered-demo.docx new file mode 100644 index 0000000000000000000000000000000000000000..8734bcaafd447387b478acdaa18dc9e056211a8f GIT binary patch literal 17831 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 IZ1$f14IX`mX8-^I literal 0 HcmV?d00001 diff --git a/.tmp_docx/rendered-test.docx b/.tmp_docx/rendered-test.docx new file mode 100644 index 0000000000000000000000000000000000000000..2ac34317e64d91ed257f31ce0270b7dd544c7408 GIT binary patch literal 17833 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 --out --context-json '{...}'` + - `printing/management/commands/render_printing_order_docx.py` + - 从 DB 选取 PrintingOrder 渲染(当前环境无真实数据时会提示无法执行) + - `printing/management/commands/render_mock_docx.py` + - 用法(多页 mock):`uv run python manage.py render_mock_docx --pages 3 --out .tmp_docx/mock-rendered-3pages.docx` +- 新增单测(锁住行为:不残留占位符、支持分页): + - `printing/test_docx_template.py` + - `printing/test_docx_pagination.py` + - `printing/test_docx_pagination.py`(按页抽取 `w:t` 文本做断言,避免被 XML 标签分割干扰) + +产物(示例输出): +- `.tmp_docx/mock-rendered-3pages.docx`(多页 mock 套版产物,便于打开核对页码与汇总) + +依赖: +- `python-docx`(通过 `uv add python-docx` 引入;同时安装 `lxml`) + +#### 2) 核对:明道云“开版暂存”同步(command / task / beat) + +现状确认: +- command:`api_v1/management/commands/sync_mdy_plate_orders.py` + - 底层调用:`api_v1.mdy_plate_order_sync.sync_mdy_plate_orders_to_staging(...)` + - 支持参数:`--request-interval-seconds`(用于限流,默认 0.02,约 50 qps 建议值) + - 支持游标:`--use-checkpoint/--skip-checkpoint`(DataSync) +- Celery task:`api_v1/tasks.py::sync_mdy_plate_orders` + - 已存在,但当前 `flower/settings.py::CELERY_BEAT_SCHEDULE` **未配置该 task 的定时触发** + +待决策(需用户确认后再实施): +- 是否新增 Celery Beat:每天 02:00-08:00 持续运作(轮询),并通过 `request_interval_seconds` + 每次处理上限(page/max_records/related)控制不超过明道云 QPS 限制。 + +更新(已实施): +- 已新增 Celery Beat:`flower/settings.py::CELERY_BEAT_SCHEDULE['mdy_plate_order_staging_sync']` + - 时间窗:02:00-07:59(`crontab(minute='*/2', hour='2-7')`) + - 任务:`api_v1.tasks.sync_mdy_plate_orders` + - 限流参数:`.env` `MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS`(默认 0.05) diff --git a/env.example b/env.example index 4a4ea08..1eb0501 100644 --- a/env.example +++ b/env.example @@ -50,6 +50,9 @@ MDY_PRODUCT_CATEGORY_ID=1 MDY_SYNC_PAGE_SIZE=100 MDY_SYNC_MAX_PAGES=2 MDY_SYNC_MAX_RECORDS=200 +# 明道云开版暂存同步:每次请求之间的最小间隔(用于限流) +# - 0.02 ~ 50 qps(理论值),建议生产环境更保守(例如 0.05/0.1) +MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS=0.05 ############################ # 腾讯云 TIIA(建议必填:生产环境请务必使用真实值) diff --git a/flower/settings.py b/flower/settings.py index ecbdda9..290265a 100644 --- a/flower/settings.py +++ b/flower/settings.py @@ -63,6 +63,9 @@ MDY_PRODUCT_CATEGORY_ID = env.int('MDY_PRODUCT_CATEGORY_ID', default=1) MDY_SYNC_PAGE_SIZE = env.int('MDY_SYNC_PAGE_SIZE', default=100) MDY_SYNC_MAX_PAGES = env.int('MDY_SYNC_MAX_PAGES', default=2) MDY_SYNC_MAX_RECORDS = env.int('MDY_SYNC_MAX_RECORDS', default=200) +# 明道云“开版暂存”同步:每次请求之间的最小间隔(用于限流) +# - 0.02 ~ 50 qps(理论值,实际应更保守以预留重试/关联查询) +MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS = env.float('MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS', default=0.05) # Tencent Cloud (TIIA) 配置:用于通过 ImageUrl 上传图片到图库(CreateImage) # 注意:不要把 secret 写死在代码里,建议通过 .env / 环境变量注入。 @@ -530,4 +533,20 @@ CELERY_BEAT_SCHEDULE = { 'max_records': MDY_SYNC_MAX_RECORDS, }, }, + # 明道云开版:同步到“暂存表” + # - 仅在 02:00-08:00 时间窗内持续运行(每 N 分钟触发一次) + # - 通过 request_interval_seconds 控制单次任务对明道云 API 的请求节奏,避免超 QPS + 'mdy_plate_order_staging_sync': { + 'task': 'api_v1.tasks.sync_mdy_plate_orders', + # 02:00 - 07:59(不包含 08:xx) + 'schedule': crontab(minute='*/2', hour='2-7'), + 'kwargs': { + 'page_size': max(1, int(MDY_SYNC_PAGE_SIZE)), + 'max_pages': max(1, int(MDY_SYNC_MAX_PAGES)), + 'max_records': max(1, int(MDY_SYNC_MAX_RECORDS)), + 'with_related': True, + 'max_related_per_type': 5, + 'request_interval_seconds': float(MDY_PLATE_ORDER_SYNC_REQUEST_INTERVAL_SECONDS), + }, + }, } diff --git a/print-template.docx b/print-template.docx new file mode 100644 index 0000000000000000000000000000000000000000..99fd5b5615792ccb870c22b621bb57e13b640108 GIT binary patch literal 20807 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 str: + # In real templates, tokens may be split with newlines/spaces across runs. + # Normalizing by removing all whitespace makes keys stable for mapping. + return re.sub(r"\s+", "", (s or "")).strip() + + +def _normalize_context(context: Mapping[str, Any]) -> dict[str, str]: + """ + Normalize context keys by stripping whitespace, and stringify values. + """ + m: dict[str, str] = {} + for k, v in (context or {}).items(): + kk = _normalize_placeholder_inner(str(k)) + m[kk] = "" if v is None else str(v) + return m + + +@dataclass(frozen=True) +class DocxRenderStats: + paragraphs_touched: int + placeholders_replaced_or_cleared: int + + +def _iter_paragraphs_in_table(table: Table) -> Iterable[Paragraph]: + for row in table.rows: + for cell in row.cells: + yield from _iter_paragraphs_in_cell(cell) + + +def _iter_paragraphs_in_cell(cell: _Cell) -> Iterable[Paragraph]: + for p in cell.paragraphs: + yield p + for t in cell.tables: + yield from _iter_paragraphs_in_table(t) + + +def _iter_all_paragraphs_in_doc(d: _DocumentType) -> Iterable[Paragraph]: + for p in d.paragraphs: + yield p + for t in d.tables: + yield from _iter_paragraphs_in_table(t) + + # headers / footers may also contain placeholders + for sec in d.sections: + for p in sec.header.paragraphs: + yield p + for t in sec.header.tables: + yield from _iter_paragraphs_in_table(t) + for p in sec.footer.paragraphs: + yield p + for t in sec.footer.tables: + yield from _iter_paragraphs_in_table(t) + + +def _replace_placeholders_in_paragraphs( + *, + paragraphs: Iterable[Paragraph], + context: Mapping[str, Any], + clear_unresolved: bool, +) -> DocxRenderStats: + ctx = _normalize_context(context) + paragraphs_touched = 0 + placeholders_done = 0 + + def replace_in_text(text: str) -> tuple[str, int]: + changed = 0 + + def repl_hash(m: re.Match[str]) -> str: + nonlocal changed + raw_inner = m.group(1) + key = _normalize_placeholder_inner(raw_inner) + if key in ctx: + changed += 1 + return ctx[key] + if clear_unresolved: + changed += 1 + return "" + return m.group(0) + + def repl_brace(m: re.Match[str]) -> str: + nonlocal changed + raw_inner = m.group(1) + key = _normalize_placeholder_inner(raw_inner) + if key in ctx: + changed += 1 + return ctx[key] + if clear_unresolved: + changed += 1 + return "" + return m.group(0) + + new_text = _RE_HASH_PLACEHOLDER.sub(repl_hash, text) + new_text = _RE_BRACE_PLACEHOLDER.sub(repl_brace, new_text) + return new_text, changed + + def write_back_runs_keep_styles(runs, new_text: str) -> None: + """ + Best-effort to keep run styles: + - Keep the same number of runs + - Distribute new text by original run lengths (last run gets the remainder) + """ + orig_lens = [len(r.text or "") for r in runs] + total_len = sum(orig_lens) + if total_len <= 0: + runs[0].text = new_text + for r in runs[1:]: + r.text = "" + return + + # If new text is shorter, later runs become empty; if longer, last run grows. + pos = 0 + for idx, r in enumerate(runs): + if idx == len(runs) - 1: + r.text = new_text[pos:] + pos = len(new_text) + else: + n = orig_lens[idx] + r.text = new_text[pos : pos + n] + pos += n + # Any remaining runs after pos are already set (some may become ""). + + def replace_in_paragraph(p: Paragraph) -> int: + nonlocal paragraphs_touched, placeholders_done + runs = list(p.runs) + if not runs: + return 0 + text = "".join(r.text or "" for r in runs) + # quick filter to skip most paragraphs + if "#{" not in text and "}" not in text and "{" not in text: + return 0 + + new_text, changed = replace_in_text(text) + if changed <= 0 or new_text == text: + return 0 + + write_back_runs_keep_styles(runs, new_text) + paragraphs_touched += 1 + placeholders_done += changed + return changed + + for p in paragraphs: + replace_in_paragraph(p) + + return DocxRenderStats( + paragraphs_touched=int(paragraphs_touched), + placeholders_replaced_or_cleared=int(placeholders_done), + ) + + +def extract_docx_placeholders(*, template_path: str) -> set[str]: + """ + Extract placeholder keys from a .docx file (document + header/footer parts if present). + + Returned keys are normalized (all whitespace removed) so they can be used as mapping keys. + """ + import zipfile + import xml.etree.ElementTree as ET + + ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} + keys: set[str] = set() + + def collect_from_xml(xml_text: str) -> None: + try: + root = ET.fromstring(xml_text) + except Exception: + return + texts = [(el.text or "") for el in root.findall(".//w:t", ns)] + full = "".join(texts) + for inner in _RE_HASH_PLACEHOLDER.findall(full): + k = _normalize_placeholder_inner(inner) + if k: + keys.add(k) + for inner in _RE_BRACE_PLACEHOLDER.findall(full): + k = _normalize_placeholder_inner(inner) + if k: + keys.add(k) + + with zipfile.ZipFile(template_path, "r") as zf: + for name in zf.namelist(): + if not name.startswith("word/"): + continue + if not name.endswith(".xml"): + continue + # Focus on common content parts; still safe if more exist. + if not ( + name == "word/document.xml" + or name.startswith("word/header") + or name.startswith("word/footer") + ): + continue + try: + xml = zf.read(name).decode("utf-8", errors="replace") + except Exception: + continue + collect_from_xml(xml) + + return keys + + +def render_docx_template( + *, + template_path: str, + output_path: str, + context: Mapping[str, Any], + clear_unresolved: bool = True, +) -> DocxRenderStats: + """ + Render a .docx template with placeholder replacement. + + - Replace `#{...}` and `{...}` tokens using `context` (key = inner token). + - If a token is not found in context: + - cleared to empty string when `clear_unresolved=True` + - kept as-is when `clear_unresolved=False` + + Notes: + - This function intentionally handles tokens split across runs by replacing on + concatenated text and writing back to runs (first run gets full text; others cleared). + """ + from docx import Document + + doc: _DocumentType = Document(template_path) + stats = _replace_placeholders_in_paragraphs( + paragraphs=_iter_all_paragraphs_in_doc(doc), + context=context, + clear_unresolved=bool(clear_unresolved), + ) + + doc.save(output_path) + return stats + + +def render_docx_template_pages( + *, + template_path: str, + output_path: str, + pages_contexts: list[Mapping[str, Any]], + clear_unresolved: bool = True, +) -> DocxRenderStats: + """ + Render multiple pages from a single-page docx template by duplicating the template + table(s) for each page and filling placeholders per-page. + + Assumption (true for this repo's `print-template.docx`): + - The whole page layout is in the document body tables. + - Each page uses the same placeholder names; we fill them page-by-page. + """ + if not pages_contexts: + raise ValueError("pages_contexts 不能为空") + + from docx import Document + + doc: _DocumentType = Document(template_path) + template_tables = list(doc.tables) + if not template_tables: + raise ValueError("模板 docx 不包含任何 table,无法按页复制") + + pages_tables: list[list[Table]] = [] + # page 1 uses existing tables + pages_tables.append(list(template_tables)) + + # subsequent pages: page break + deep copy template tables + for _i in range(2, len(pages_contexts) + 1): + doc.add_page_break() + new_tables: list[Table] = [] + for t in template_tables: + new_tbl_el = copy.deepcopy(t._tbl) # pylint: disable=protected-access + doc._body._body.append(new_tbl_el) # type: ignore[attr-defined] # pylint: disable=protected-access + new_tables.append(Table(new_tbl_el, doc._body)) # type: ignore[arg-type] + pages_tables.append(new_tables) + + total_stats = DocxRenderStats(paragraphs_touched=0, placeholders_replaced_or_cleared=0) + + for page_idx, (ctx, tbls) in enumerate(zip(pages_contexts, pages_tables), start=1): + page_paragraphs: list[Paragraph] = [] + for t in tbls: + page_paragraphs.extend(list(_iter_paragraphs_in_table(t))) + st = _replace_placeholders_in_paragraphs( + paragraphs=page_paragraphs, + context=ctx, + clear_unresolved=bool(clear_unresolved), + ) + total_stats = DocxRenderStats( + paragraphs_touched=total_stats.paragraphs_touched + st.paragraphs_touched, + placeholders_replaced_or_cleared=total_stats.placeholders_replaced_or_cleared + st.placeholders_replaced_or_cleared, + ) + + doc.save(output_path) + return total_stats + diff --git a/printing/management/commands/render_docx_template.py b/printing/management/commands/render_docx_template.py new file mode 100644 index 0000000..244894b --- /dev/null +++ b/printing/management/commands/render_docx_template.py @@ -0,0 +1,78 @@ +import json +from pathlib import Path + +from django.core.management.base import BaseCommand, CommandError + + +class Command(BaseCommand): + help = "Render a .docx template by replacing placeholders like #{...} / {...} using a JSON context mapping." + + def add_arguments(self, parser): + parser.add_argument( + "template", + type=str, + help="模板文件路径(.docx),例如 /home/f/coding/flower/print-template.docx", + ) + parser.add_argument( + "--out", + type=str, + default="rendered.docx", + help="输出 docx 路径(默认 rendered.docx)", + ) + parser.add_argument( + "--context-json", + type=str, + default="{}", + help="JSON 字符串,键为占位符内部文本(例如 打印-销售单(8*9).客户名称)", + ) + parser.add_argument( + "--context-json-file", + type=str, + default=None, + help="JSON 文件路径(优先级高于 --context-json)", + ) + parser.add_argument( + "--keep-unresolved", + action="store_true", + help="不清空未命中的占位符(默认会清空)", + ) + + def handle(self, *args, **options): + template = str(options["template"]) + out = str(options["out"]) + context_json = str(options["context_json"] or "{}") + context_json_file = options.get("context_json_file") + keep_unresolved = bool(options.get("keep_unresolved")) + + tp = Path(template) + if not tp.exists(): + raise CommandError(f"模板文件不存在:{template}") + if tp.suffix.lower() != ".docx": + raise CommandError("template 必须是 .docx 文件") + + if context_json_file: + jp = Path(str(context_json_file)) + if not jp.exists(): + raise CommandError(f"JSON 文件不存在:{context_json_file}") + context = json.loads(jp.read_text("utf-8")) + else: + context = json.loads(context_json) + + if not isinstance(context, dict): + raise CommandError("context 必须是 JSON object(字典)") + + from printing.docx_template import render_docx_template + + stats = render_docx_template( + template_path=str(tp), + output_path=str(out), + context=context, + clear_unresolved=(not keep_unresolved), + ) + + self.stdout.write( + self.style.SUCCESS( + f"渲染完成:out={out} paragraphs_touched={stats.paragraphs_touched} placeholders={stats.placeholders_replaced_or_cleared}" + ) + ) + diff --git a/printing/management/commands/render_mock_docx.py b/printing/management/commands/render_mock_docx.py new file mode 100644 index 0000000..8196878 --- /dev/null +++ b/printing/management/commands/render_mock_docx.py @@ -0,0 +1,96 @@ +from pathlib import Path + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError + + +class Command(BaseCommand): + help = "Render a .docx template with realistic mock data (no database required)." + + def add_arguments(self, parser): + parser.add_argument( + "--template", + type=str, + default=str(Path(settings.BASE_DIR) / "print-template.docx"), + help="docx 模板路径(默认 BASE_DIR/print-template.docx)", + ) + parser.add_argument( + "--out", + type=str, + default="", + help="输出 docx 路径(默认 .tmp_docx/mock-rendered.docx)", + ) + parser.add_argument( + "--seed", + type=int, + default=20260126, + help="随机种子(默认 20260126),便于复现同一份拟真数据", + ) + parser.add_argument( + "--pages", + type=int, + default=1, + help="生成页数(每页固定 9 行明细),默认 1", + ) + parser.add_argument( + "--printer", + type=str, + default="系统自动生成", + help="填充 {打印人}", + ) + parser.add_argument( + "--keep-unresolved", + action="store_true", + help="不清空未命中的占位符(默认会清空)", + ) + + def handle(self, *args, **options): + template = str(options.get("template") or "") + out = str(options.get("out") or "") + seed = int(options.get("seed") or 0) + pages = int(options.get("pages") or 1) + printer = str(options.get("printer") or "") + keep_unresolved = bool(options.get("keep_unresolved")) + + tp = Path(template) + if not tp.exists(): + raise CommandError(f"模板文件不存在:{template}") + if tp.suffix.lower() != ".docx": + raise CommandError("template 必须是 .docx 文件") + + if not out: + out = str(Path(settings.BASE_DIR) / ".tmp_docx" / "mock-rendered.docx") + Path(out).parent.mkdir(parents=True, exist_ok=True) + + from printing.docx_template import extract_docx_placeholders, render_docx_template_pages + from printing.print_docx_services import build_debug_context_summary, build_mock_sales_docx_pages + + placeholders = extract_docx_placeholders(template_path=str(tp)) + page_contexts = build_mock_sales_docx_pages( + placeholders=placeholders, + pages=pages, + printer_label=printer, + seed=seed, + ) + # stats: aggregate all page contexts + ctx_stats = build_debug_context_summary({k: v for d in page_contexts for k, v in d.items()}) + + stats = render_docx_template_pages( + template_path=str(tp), + output_path=str(out), + pages_contexts=page_contexts, + clear_unresolved=(not keep_unresolved), + ) + + self.stdout.write( + self.style.SUCCESS( + "渲染完成:" + f" out={out}" + f" seed={seed}" + f" pages={pages}" + f" paragraphs_touched={stats.paragraphs_touched}" + f" placeholders_replaced_or_cleared={stats.placeholders_replaced_or_cleared}" + f" context_filled={ctx_stats['context_filled_keys']}/{ctx_stats['context_total_keys']}" + ) + ) + diff --git a/printing/management/commands/render_printing_order_docx.py b/printing/management/commands/render_printing_order_docx.py new file mode 100644 index 0000000..63d39ec --- /dev/null +++ b/printing/management/commands/render_printing_order_docx.py @@ -0,0 +1,110 @@ +from pathlib import Path + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.db.models import Count + + +class Command(BaseCommand): + help = "Render a PrintingOrder into a .docx template using real DB data (pick the most 'full' order by default)." + + def add_arguments(self, parser): + parser.add_argument( + "--order-id", + type=int, + default=None, + help="指定 PrintingOrder.id;不传则自动选择 PrintingJob 明细最多的一单", + ) + parser.add_argument( + "--template", + type=str, + default=str(Path(settings.BASE_DIR) / "print-template.docx"), + help="docx 模板路径(默认 BASE_DIR/print-template.docx)", + ) + parser.add_argument( + "--out", + type=str, + default="", + help="输出 docx 路径(默认 .tmp_docx/printing-order-{id}.docx)", + ) + parser.add_argument( + "--printer", + type=str, + default="系统自动生成", + help="填充 {打印人}", + ) + parser.add_argument( + "--keep-unresolved", + action="store_true", + help="不清空未命中的占位符(默认会清空)", + ) + + def handle(self, *args, **options): + order_id = options.get("order_id") + template = str(options.get("template") or "") + out = str(options.get("out") or "") + printer = str(options.get("printer") or "") + keep_unresolved = bool(options.get("keep_unresolved")) + + tp = Path(template) + if not tp.exists(): + raise CommandError(f"模板文件不存在:{template}") + if tp.suffix.lower() != ".docx": + raise CommandError("template 必须是 .docx 文件") + + from printing.models import PrintingOrder + + qs = PrintingOrder.objects.all() + if order_id: + order = ( + qs.select_related("customer", "merchant", "process") + .prefetch_related("printing_jobs__product") + .filter(id=int(order_id)) + .first() + ) + if not order: + raise CommandError(f"PrintingOrder 不存在:id={order_id}") + else: + order = ( + qs.annotate(job_count=Count("printing_jobs")) + .filter(job_count__gt=0) + .order_by("-job_count", "-id") + .select_related("customer", "merchant", "process") + .prefetch_related("printing_jobs__product") + .first() + ) + if not order: + raise CommandError("未找到包含 printing_jobs 的 PrintingOrder(无法做“饱满”渲染测试)") + + # output path default + if not out: + out = str(Path(settings.BASE_DIR) / ".tmp_docx" / f"printing-order-{order.id}.docx") + Path(out).parent.mkdir(parents=True, exist_ok=True) + + from printing.docx_template import extract_docx_placeholders, render_docx_template + from printing.print_docx_services import build_debug_context_summary, build_printing_order_docx_context + + placeholders = extract_docx_placeholders(template_path=str(tp)) + ctx = build_printing_order_docx_context(order=order, placeholders=placeholders, printer_label=printer) + ctx_stats = build_debug_context_summary(ctx) + + stats = render_docx_template( + template_path=str(tp), + output_path=str(out), + context=ctx, + clear_unresolved=(not keep_unresolved), + ) + + job_count = order.printing_jobs.count() + self.stdout.write( + self.style.SUCCESS( + "渲染完成:" + f" order_id={order.id}" + f" job_count={job_count}" + f" out={out}" + f" paragraphs_touched={stats.paragraphs_touched}" + f" placeholders_replaced_or_cleared={stats.placeholders_replaced_or_cleared}" + f" context_filled={ctx_stats['context_filled_keys']}/{ctx_stats['context_total_keys']}" + ) + ) + diff --git a/printing/print_docx_services.py b/printing/print_docx_services.py new file mode 100644 index 0000000..ad70d3e --- /dev/null +++ b/printing/print_docx_services.py @@ -0,0 +1,366 @@ +""" +PrintingOrder -> docx template context mapping. + +This is intentionally separated from `printing/services.py` (WeCom logic) to keep concerns clear. +""" + +from __future__ import annotations + +import re +from collections.abc import Mapping +from datetime import datetime +from decimal import Decimal +from typing import Any + +from django.utils import timezone + +from printing.models import PrintingOrder + + +def _safe_str(v: Any) -> str: + if v is None: + return "" + return str(v) + + +def _fmt_date(dt: datetime | None) -> str: + if not dt: + return "" + try: + return timezone.localtime(dt).strftime("%Y-%m-%d") + except Exception: + return str(dt) + + +def _pick_warehouse_name(order: PrintingOrder) -> str: + """ + PrintingOrder 模型本身没有 warehouse 字段,这里做一个“尽量饱满”的推断: + - 优先取 order.merchant.warehouses.first().name + - 否则空字符串 + """ + m = getattr(order, "merchant", None) + if not m: + return "" + try: + wh = m.warehouses.order_by("id").first() + return _safe_str(getattr(wh, "name", "")).strip() + except Exception: + return "" + + +def _detect_max_line_index(placeholders: set[str]) -> int: + """ + Detect max line index from placeholders like: + 打印-销售单(8*9).产品名称17 + 打印-销售单(8*9).销售数量32 + """ + max_n = 0 + for k in placeholders or set(): + m = re.search(r"(产品名称|幅宽|规格|销售数量|单位)(\d+)$", k) + if not m: + continue + try: + n = int(m.group(2)) + except Exception: + continue + max_n = max(max_n, n) + return max_n + + +def build_printing_order_docx_context( + *, + order: PrintingOrder, + placeholders: set[str] | None = None, + printer_label: str = "", +) -> dict[str, str]: + """ + Build context mapping for this repo's sales/printing docx template placeholders. + + This function tries to be "data rich" for testing: + - Fill header fields: 客户名称/仓库名称/日期/页码 + - Fill line items from `PrintingOrder.printing_jobs` + - Fill per-8-line subtotal: 数量和8/16/24/... + - Fill brace placeholders: 备注/打印人 + """ + prefix = "打印-销售单(8*9)." + ph = placeholders or set() + max_line = _detect_max_line_index(ph) or 32 + + customer_name = _safe_str(getattr(getattr(order, "customer", None), "name", "")) + warehouse_name = _pick_warehouse_name(order) + order_date = _fmt_date(getattr(order, "created_at", None)) + human_id = _safe_str(getattr(order, "human_id", "")) or _safe_str(getattr(order, "id", "")) + + # remarks: try best-effort + remark_parts = [] + for attr in ("description", "printing_warn", "production_warn", "rolling_warn"): + v = _safe_str(getattr(order, attr, "")).strip() + if v: + remark_parts.append(v) + remark = "\n".join(remark_parts[:3]) # avoid overlong + + ctx: dict[str, str] = { + # main title + f"{prefix}印花销售单": f"印花销售单 {human_id}".strip(), + f"{prefix}客户名称": customer_name, + f"{prefix}仓库名称": warehouse_name, + f"{prefix}日期(订单日期)": order_date, + f"{prefix}当前页码": "1", + f"{prefix}总页数": "1", + # brace placeholders (non-prefixed) + "备注": remark, + "打印人": (printer_label or "").strip(), + } + + # Fill line items from jobs + jobs = list( + order.printing_jobs.select_related("product").order_by("id") + if getattr(order, "id", None) + else [] + ) + + # For "尽可能饱满" testing: if few jobs, still fill what we can; unresolved will be cleared by renderer. + def _job_product_name(job) -> str: + p = getattr(job, "product", None) + name = _safe_str(getattr(p, "name", "")).strip() + if name: + return name + return _safe_str(getattr(job, "id", "")).strip() + + def _job_spec(job) -> str: + parts = [] + size = _safe_str(getattr(job, "size", "")).strip() + if size: + parts.append(size) + pieces = getattr(job, "pieces", None) + if pieces not in (None, ""): + parts.append(f"{pieces}件") + return " / ".join(parts) + + def _job_width(job) -> str: + # prefer order.width for consistent printing layout + w = _safe_str(getattr(order, "width", "")).strip() + if w: + return w + # fallback to product width_size if exists + p = getattr(job, "product", None) + w2 = getattr(p, "width_size", None) + return _safe_str(w2) + + qty_by_index: dict[int, Decimal] = {} + + for i in range(1, max_line + 1): + job = jobs[i - 1] if i - 1 < len(jobs) else None + if job is None: + continue + + qty = getattr(job, "quantity", None) + try: + qty_dec = Decimal(str(qty)) if qty is not None else Decimal("0") + except Exception: + qty_dec = Decimal("0") + qty_by_index[i] = qty_dec + + ctx[f"{prefix}产品名称{i}"] = _job_product_name(job) + ctx[f"{prefix}幅宽{i}"] = _job_width(job) + ctx[f"{prefix}规格{i}"] = _job_spec(job) + ctx[f"{prefix}销售数量{i}"] = _safe_str(qty) + ctx[f"{prefix}单位{i}"] = _safe_str(getattr(job, "unit", "")).strip() + + # Subtotals: 数量和8/16/24/... if template has them (or just fill anyway) + for end in range(8, max_line + 1, 8): + s = sum((qty_by_index.get(i, Decimal("0")) for i in range(end - 7, end + 1)), Decimal("0")) + ctx[f"{prefix}数量和{end}"] = _safe_str(s) + + # If template uses SalesOrder naming but we feed PrintingOrder, at least fill known placeholders. + # Any placeholder not covered here will be cleared by renderer (when clear_unresolved=True). + return ctx + + +def build_debug_context_summary(context: Mapping[str, str]) -> dict[str, int]: + """ + Lightweight stats for logging/command output. + """ + filled = sum(1 for _k, v in (context or {}).items() if str(v or "").strip()) + total = len(context or {}) + return {"context_total_keys": int(total), "context_filled_keys": int(filled)} + + +def build_mock_sales_docx_context( + *, + placeholders: set[str] | None = None, + printer_label: str = "系统自动生成", + seed: int = 20260126, +) -> dict[str, str]: + """ + Build a "realistic enough" mock context for quickly testing docx template rendering, + without requiring any DB data. + + Notes: + - Fills as many indexed line placeholders as detected (default 32). + - Also fills per-8-line subtotal placeholders. + - Any missing placeholders will be cleared by renderer when clear_unresolved=True. + """ + import random + + r = random.Random(int(seed)) + prefix = "打印-销售单(8*9)." + ph = placeholders or set() + max_line = _detect_max_line_index(ph) or 32 + + customer_names = ["宇文服饰", "星河面料", "青山印染", "江南布业", "云图设计"] + warehouse_names = ["主仓库", "广州仓", "苏州仓", "上海仓", "临时仓"] + crafts = ["活性印花", "分散印花", "数码直喷", "拔染", "涂料印花"] + widths = ["145cm", "150cm", "160cm", "180cm"] + specs = ["40S*40S", "32S", "21S", "涤棉65/35", "全棉"] + units = ["米", "码"] + + now = timezone.localtime(timezone.now()) + order_date = now.strftime("%Y-%m-%d") + human_id = f"{now.strftime('%Y%m%d')}{r.randint(100000, 999999)}" + + ctx: dict[str, str] = { + f"{prefix}印花销售单": f"印花销售单 {human_id}", + f"{prefix}当前页码": "1", + f"{prefix}总页数": "1", + f"{prefix}客户名称": r.choice(customer_names), + f"{prefix}仓库名称": r.choice(warehouse_names), + f"{prefix}日期(订单日期)": order_date, + "备注": ( + f"工艺:{r.choice(crafts)}\n" + f"交期:{(now + timezone.timedelta(days=r.randint(3, 10))).strftime('%Y-%m-%d')}\n" + f"注意:色差控制、走货前复核数量" + ), + "打印人": (printer_label or "").strip(), + } + + # Fill line items 1..N + from decimal import Decimal + + qty_by_index: dict[int, Decimal] = {} + for i in range(1, max_line + 1): + color = r.choice(["蓝", "红", "黑", "绿", "紫", "灰"]) + craft = r.choice(crafts) + name = f"花型{i:02d}-{craft}-{color}" + width = r.choice(widths) + spec = f"{r.choice(specs)} / {r.randint(45, 120)}gsm" + qty = Decimal(str(r.randint(8, 120))) + Decimal(str(r.choice([0, 0, 0, 0.5]))) + unit = r.choice(units) + + ctx[f"{prefix}产品名称{i}"] = name + ctx[f"{prefix}幅宽{i}"] = width + ctx[f"{prefix}规格{i}"] = spec + ctx[f"{prefix}销售数量{i}"] = _safe_str(qty) + ctx[f"{prefix}单位{i}"] = unit + qty_by_index[i] = qty + + # Subtotals: 数量和8/16/... + for end in range(8, max_line + 1, 8): + s = sum((qty_by_index.get(i, Decimal("0")) for i in range(end - 7, end + 1)), Decimal("0")) + ctx[f"{prefix}数量和{end}"] = _safe_str(s) + + return ctx + + +def build_mock_sales_docx_pages( + *, + placeholders: set[str] | None = None, + pages: int = 1, + printer_label: str = "系统自动生成", + seed: int = 20260126, +) -> list[dict[str, str]]: + """ + Build per-page contexts for the fixed-size template: + - 9 rows of items per page + - each row has 8 quantity cells => 72 quantity cells per page + + Page-level summary fields (must be per-page): + - `匹数`: count of non-empty quantity cells + - `总数量`: sum of values in non-empty quantity cells + """ + import random + + if pages <= 0: + return [] + + r = random.Random(int(seed)) + prefix = "打印-销售单(8*9)." + + customer_names = ["宇文服饰", "星河面料", "青山印染", "江南布业", "云图设计"] + warehouse_names = ["主仓库", "广州仓", "苏州仓", "上海仓", "临时仓"] + crafts = ["活性印花", "分散印花", "数码直喷", "拔染", "涂料印花"] + widths = ["145cm", "150cm", "160cm", "180cm"] + specs = ["40S*40S", "32S", "21S", "涤棉65/35", "全棉"] + units = ["米", "码"] + + now = timezone.localtime(timezone.now()) + order_date = now.strftime("%Y-%m-%d") + human_id = f"{now.strftime('%Y%m%d')}{r.randint(100000, 999999)}" + + # Template rows indices for product fields: 1,9,17,...,65 (9 rows) + row_start_indices = [1 + 8 * i for i in range(9)] + + from decimal import Decimal + + result: list[dict[str, str]] = [] + for page_no in range(1, int(pages) + 1): + ctx: dict[str, str] = { + f"{prefix}印花销售单": f"印花销售单 {human_id}", + f"{prefix}当前页码": str(page_no), + f"{prefix}总页数": str(pages), + f"{prefix}客户名称": r.choice(customer_names), + f"{prefix}仓库名称": r.choice(warehouse_names), + f"{prefix}日期(订单日期)": order_date, + "备注": ( + f"工艺:{r.choice(crafts)}\n" + f"交期:{(now + timezone.timedelta(days=r.randint(3, 10))).strftime('%Y-%m-%d')}\n" + f"注意:色差控制、走货前复核数量" + ), + "打印人": (printer_label or "").strip(), + } + + bolts = 0 + total_qty = Decimal("0") + + for row_i, start_idx in enumerate(row_start_indices, start=1): + color = r.choice(["蓝", "红", "黑", "绿", "紫", "灰"]) + craft = r.choice(crafts) + name = f"花型{page_no:02d}-{row_i:02d}-{craft}-{color}" + width = r.choice(widths) + spec = f"{r.choice(specs)} / {r.randint(45, 120)}gsm" + unit = r.choice(units) + + ctx[f"{prefix}产品名称{start_idx}"] = name + ctx[f"{prefix}幅宽{start_idx}"] = width + ctx[f"{prefix}规格{start_idx}"] = spec + ctx[f"{prefix}单位{start_idx}"] = unit + + row_sum = Decimal("0") + # 8 quantity cells + for j in range(8): + idx = start_idx + j + # make some cells blank to simulate real data + if r.random() < 0.18: + v = "" + else: + v = Decimal(str(r.randint(8, 120))) + Decimal(str(r.choice([0, 0, 0, 0.5]))) + ctx[f"{prefix}销售数量{idx}"] = _safe_str(v) + if str(v).strip() != "": + bolts += 1 + try: + vv = Decimal(str(v)) + except Exception: + vv = Decimal("0") + total_qty += vv + row_sum += vv + + # row subtotal placeholder: 数量和8/16/.../72 (end index) + row_end = start_idx + 7 + ctx[f"{prefix}数量和{row_end}"] = _safe_str(row_sum) + + ctx[f"{prefix}匹数"] = _safe_str(bolts) + ctx[f"{prefix}总数量"] = _safe_str(total_qty) + result.append(ctx) + + return result + diff --git a/printing/test_docx_pagination.py b/printing/test_docx_pagination.py new file mode 100644 index 0000000..46b0ae4 --- /dev/null +++ b/printing/test_docx_pagination.py @@ -0,0 +1,109 @@ +import re +import zipfile +from pathlib import Path + +from django.conf import settings +from django.test import SimpleTestCase + + +class DocxPaginationTest(SimpleTestCase): + def _read_document_xml(self, docx_path: str) -> str: + with zipfile.ZipFile(docx_path, "r") as zf: + return zf.read("word/document.xml").decode("utf-8", errors="replace") + + def _extract_pages_text(self, docx_path: str) -> list[str]: + """ + Extract per-page plain text by walking body elements and splitting on page breaks. + + Important: we extract w:t texts, so assertions won't be broken by XML tag boundaries. + """ + import xml.etree.ElementTree as ET + + xml = self._read_document_xml(docx_path) + ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} + root = ET.fromstring(xml) + + body = root.find("w:body", ns) + if body is None: + return [] + + pages: list[list[str]] = [[]] + + def has_page_break(el) -> bool: + for br in el.findall(".//w:br", ns): + if br.attrib.get(f"{{{ns['w']}}}type") == "page": + return True + return False + + def collect_text(el) -> str: + ts = [(t.text or "") for t in el.findall(".//w:t", ns)] + return "".join(ts) + + for child in list(body): + text = collect_text(child) + if text: + pages[-1].append(text) + if has_page_break(child): + pages.append([]) + + return ["".join(p) for p in pages if "".join(p).strip()] + + def test_render_pages_per_page_totals(self): + template = str(Path(settings.BASE_DIR) / "print-template.docx") + out = str(Path(settings.BASE_DIR) / ".tmp_docx" / "rendered-pages-test.docx") + Path(out).parent.mkdir(parents=True, exist_ok=True) + + from printing.docx_template import render_docx_template_pages + + prefix = "打印-销售单(8*9)." + + # Page 1: two non-empty qty cells => 匹数=2, 总数量=111.11 + page1 = { + f"{prefix}当前页码": "1", + f"{prefix}总页数": "2", + f"{prefix}匹数": "2", + f"{prefix}总数量": "111.11", + # Put a couple of quantities too (indices exist 1..72) + f"{prefix}销售数量1": "11.11", + f"{prefix}销售数量2": "100", + # Fill one product line (index 1 exists for product fields) + f"{prefix}产品名称1": "P1-产品", + f"{prefix}幅宽1": "150cm", + f"{prefix}规格1": "规格A", + f"{prefix}单位1": "米", + } + + # Page 2: one non-empty qty cell => 匹数=1, 总数量=222.22 + page2 = { + f"{prefix}当前页码": "2", + f"{prefix}总页数": "2", + f"{prefix}匹数": "1", + f"{prefix}总数量": "222.22", + f"{prefix}销售数量1": "222.22", + f"{prefix}产品名称1": "P2-产品", + f"{prefix}幅宽1": "160cm", + f"{prefix}规格1": "规格B", + f"{prefix}单位1": "码", + } + + render_docx_template_pages( + template_path=template, + output_path=out, + pages_contexts=[page1, page2], + clear_unresolved=True, + ) + + pages_text = self._extract_pages_text(out) + self.assertGreaterEqual(len(pages_text), 2, msg="未生成两页文本(可能分页符缺失)") + + p1, p2 = pages_text[0], pages_text[1] + self.assertIn("P1-产品", p1) + self.assertIn("111.11", p1) + self.assertIn("P2-产品", p2) + self.assertIn("222.22", p2) + + # No raw placeholders should remain (check on plain text concatenation) + all_text = "".join(pages_text) + self.assertIsNone(re.search(r"#\{.{1,300}?\}", all_text, flags=re.S)) + self.assertIsNone(re.search(r"(? str: + with zipfile.ZipFile(docx_path, "r") as zf: + xml = zf.read("word/document.xml").decode("utf-8", errors="replace") + # Extract only text to avoid false positives in tags/attrs. + import xml.etree.ElementTree as ET + + ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} + root = ET.fromstring(xml) + texts = [(el.text or "") for el in root.findall(".//w:t", ns)] + return "".join(texts) + + def test_render_replaces_and_clears_placeholders(self): + template_path = str(Path(settings.BASE_DIR) / "print-template.docx") + self.assertTrue(Path(template_path).exists()) + + from printing.docx_template import render_docx_template + + out_path = str(Path(settings.BASE_DIR) / ".tmp_docx" / "rendered-test.docx") + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + + render_docx_template( + template_path=template_path, + output_path=out_path, + context={ + # hash placeholder inner key + "打印-销售单(8*9).客户名称": "客户A", + # plain brace placeholder inner key + "备注": "这是备注", + }, + clear_unresolved=True, + ) + + text = self._extract_doc_text(out_path) + self.assertIn("客户A", text) + self.assertIn("这是备注", text) + + # No raw placeholders should remain when clear_unresolved=True + self.assertIsNone( + re.search(r"#\{.{1,300}?\}", text, flags=re.S), + msg="仍残留 #{...} 占位符", + ) + self.assertIsNone( + re.search(r"(?=5.0.0", "aiohttp>=3.13.2", "tencentcloud-sdk-python>=3.0.0", + "python-docx>=1.2.0", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 6049ce5..b31d7fe 100644 --- a/uv.lock +++ b/uv.lock @@ -426,6 +426,7 @@ dependencies = [ { name = "markdown" }, { name = "pillow" }, { name = "psycopg", extra = ["binary"] }, + { name = "python-docx" }, { name = "redis" }, { name = "tencentcloud-sdk-python" }, { name = "uvicorn" }, @@ -455,6 +456,7 @@ requires-dist = [ { name = "markdown", specifier = ">=3.10" }, { name = "pillow", specifier = ">=12.0.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.12" }, + { name = "python-docx", specifier = ">=1.2.0" }, { name = "redis", specifier = ">=5.0.0" }, { name = "tencentcloud-sdk-python", specifier = ">=3.0.0" }, { name = "uvicorn", specifier = ">=0.38.0" }, @@ -583,6 +585,50 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, ] +[[package]] +name = "lxml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, +] + [[package]] name = "markdown" version = "3.10" @@ -863,6 +909,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3"