From 075d80ad401daebf45d850180990d5a99a570c55 Mon Sep 17 00:00:00 2001 From: Thanakorn Date: Tue, 18 Aug 2026 12:38:15 +0700 Subject: [PATCH] SDLC docs package delivery script --- scripts/build-sdlc-delivery.sh | 32 ++++ scripts/sdlc-delivery/assets/LeelawadeeUI.ttf | Bin 0 -> 95756 bytes .../assets/document-control.html | 7 + scripts/sdlc-delivery/assets/footer.html | 4 + scripts/sdlc-delivery/assets/header.html | 8 + scripts/sdlc-delivery/assets/preview.html | 37 ++++ .../sdlc-delivery/assets/sdlc-delivery.css | 130 +++++++++++++ scripts/sdlc-delivery/package-lock.json | 54 ++++++ scripts/sdlc-delivery/package.json | 8 + scripts/sdlc-delivery/render-sdlc.mjs | 175 ++++++++++++++++++ scripts/sdlc-delivery/verify-content.mjs | 91 +++++++++ scripts/verify-sdlc-delivery.sh | 28 +++ 12 files changed, 574 insertions(+) create mode 100755 scripts/build-sdlc-delivery.sh create mode 100644 scripts/sdlc-delivery/assets/LeelawadeeUI.ttf create mode 100644 scripts/sdlc-delivery/assets/document-control.html create mode 100644 scripts/sdlc-delivery/assets/footer.html create mode 100644 scripts/sdlc-delivery/assets/header.html create mode 100644 scripts/sdlc-delivery/assets/preview.html create mode 100644 scripts/sdlc-delivery/assets/sdlc-delivery.css create mode 100644 scripts/sdlc-delivery/package-lock.json create mode 100644 scripts/sdlc-delivery/package.json create mode 100644 scripts/sdlc-delivery/render-sdlc.mjs create mode 100644 scripts/sdlc-delivery/verify-content.mjs create mode 100755 scripts/verify-sdlc-delivery.sh diff --git a/scripts/build-sdlc-delivery.sh b/scripts/build-sdlc-delivery.sh new file mode 100755 index 0000000..b1ad39d --- /dev/null +++ b/scripts/build-sdlc-delivery.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +TOOL_DIR="$SCRIPT_DIR/sdlc-delivery" +SOURCE_DIR="$ROOT_DIR/sdlc" +DELIVERY_ROOT="$ROOT_DIR/sdlc-delivery" +STAGING_DIR="$ROOT_DIR/.sdlc-delivery.staging" + +if [[ ! -d "$TOOL_DIR/node_modules/playwright" ]]; then + echo "Installing the local PDF renderer..." + npm install --prefix "$TOOL_DIR" + npx --prefix "$TOOL_DIR" playwright install chromium +fi + +rm -rf "$STAGING_DIR" +mkdir -p "$STAGING_DIR" + +while IFS= read -r -d '' source; do + relative="${source#"$ROOT_DIR/sdlc/"}" + destination="$STAGING_DIR/${relative%.md}.pdf" + echo "Rendering ${relative%.md}.pdf" + node "$TOOL_DIR/render-sdlc.mjs" "$source" "$destination" +done < <(find "$SOURCE_DIR" -type f -name '*.md' -print0 | sort -z) + +echo "Verifying staged delivery package..." +"$SCRIPT_DIR/verify-sdlc-delivery.sh" "$STAGING_DIR" + +rm -rf "$DELIVERY_ROOT" +mv "$STAGING_DIR" "$DELIVERY_ROOT" +echo "Delivery and verification passed: $DELIVERY_ROOT" diff --git a/scripts/sdlc-delivery/assets/LeelawadeeUI.ttf b/scripts/sdlc-delivery/assets/LeelawadeeUI.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ae5a1260d35b0e826c2440f1f6fd436190a69948 GIT binary patch literal 95756 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%0Ac-FJvb{xq-4DYSQO?RuQJ_YnwqT|Nj2 z%1vRBEbHJ{j>>HY9)KL8#w02}lB}x4146rKs2M5_N(!&)l-Ho4coV#AR9kK3%xRNiP4`{ix?@>G$(buA&0jwu zUwH2HHKj8$N>%$dhunptv7o@nHai?~V_k?RLOc`-?8+^4?Q*+w4FipwVVPP4 zFS{Ad8NO5}X3c5pDKIv zMDejPR~=k_Y~|L+$E}=}(3W3z0lqHs&COSBeqzS9g>~BTF{@`J`HnfaUodoH`oiMT z3sO9I4L5$b;P#g48EeODf1e~72en=XDZ-L63E<-BWdlHie!Nt%%fy{n zD6HVRhq11@Bzy#(aKeBjlWJG>5pckn^v3kQ2tK1%+C{PDz=6BEX3swSU}sV*lXjxhwm>qJwbSK{fm1c$(-#5qDMhPj15Ueb2>s;>P2W48C|>b z^UdL%0j1i=BS!vn0Cmak#qK|m8&Di8fGD^j2x3Mw#G|9HI&{&qbGGfAxTZC6=;7Q{ zYDln3xT)*=+{oyh3-_-X#vfU9@2bZ72WD5aT->zfcDyJq3NLNf&KAV_^X~*&=YvuW zypON}q_^<$<>qK~U_7|lT)YcwMk*ax--rHJneBhou&Z^6x|0yt>I{>qD-wUnz*0x7 zh95b|_{uO5~ zI%DjWZG#e1$g*LEMEnM*yP!QBh7K$kg(_j{PN!ldW}ieaBf->xwZk$PVgp?+bSS7? z@tf$Zg-d6TQZX-g&f@mT!LBOt2KkJ-dtd)fJFWfm%5~hr`Ipb`jBkfFo6-H5uBT#b z5F5uvrHX1d8yh=Zve#wA9}8`+sF+Z=nR(^pt|3EgWjphOqjo{1kYj>A0d3!-#2|7+ z5IPNMe>e4Q@<#jmC*B@t2lb#=(}Sk_7uPcihtF%6wQR`J?Ng_1Us}KH?4fgq7mm36 zzH=AcySnM2vT;j>!QF_LjbGe2bkT(JWYMsxRaMhc#YL%U)iu)^3iu6m7qzzp2HZXG z%&X@Ps-1iF#CdlQ2(+|cR5$z9^M?*S|JJ3$myR#%H-2ej{i5cI_=F|G^yIW*Ma9FW zRS#%Q;n+(0dp2b0M2{@hq{30Q+2~i*f$`1$%AJrSxp-F&-bv|I89ExeB!MYVebBBw zNlTRvAS8Ly}HR-0^nDbxSYXIA`bLT50A-Gj}Z?+I0DE+n4_4!uoyj@r#C@ zv0xn9ykuD2{BdzH_Gj(b8|PR5dU*5B54S!x?6TeS7N@TmIqf&!nI~_WQNS>vZqHlvx*RMDtB1Pmj)zp!gDFw8uCpgX$LbQHld$W@$n^HD%nc)d zJTYuoqN1^}9Jc^KjlNE?c!4O0Rs!BEb@~Ie-fqQ zT}54x898s@BKa@~()QC+#QP}-pKj=S6(fP4(}NFh(2$N3Jv0v>Bn~H%edyt!hyI8j zVFNx_G*d`u&7Hhf;q5!LJkYbg`%AG35*Nc*sv;F|IyyZPD^C}?BVUP?50;iXgB#4| z8fVrVVT^?NfFt;UNWH5`nGTh;L>{|@fs$V=C_z6QM7j*aL4(52G})8mXN*r4rKTi) zyL#vD@mJ3uJmAckXAbaLMwJ&t@%Xhrb_M*p(B!hzxico8HLJFAY<2LebX)0}mrfje z@ti4>XAGLy0q=uaQ+%!!l&>jzxeID`&aKgck1e` zeD{~n-FKYF$JXXd>w3rh(&q>9(V>}IoA#`BOxxJ9dMtmG&*RJZac!yg_?OyCqW#2! zmu~w0g*Sl_&Ck1_*A1BS8JCI(c=_F8<#=z!Axc(B+zNXREAPMxTc~@dOrEBU!G1=p zSt3E6NzVj_on|=X!#H5JBdX8b@T6NTE{Q1yQFa}?x$BPW9~X)rxcz_dHo4jM0ZmW9 z&R;Lg%WE7G>-se${}B9(1gLRf6rT^9nQIJebvk))D|4w^xo8Sn*W~8PYrNj5Gq@H# zLN}ATJM@zU(C4`Hi6{dW7<)ZZOmynQ^Jw;Xh~IqhFB>l2`hfPs(Np(#+@_uU<44=? z*7nG|9@(~Pf3f6zZ2N(aWI?OCYUL|kySh$qS*5`O&hGvcJbDP8&)}5Rt47e9z3#1o z+d`8G&Pof%`>k7zm`Ac${OTH6_ob5<{*c#Jo7rRRpu~D)LAT^27Rl;C(w5s#YA*z;soEpj3GI}2v<+MXPV&$muH~mx6Ywzt zA88HVK{OFCcC|#$7W_j1mTI?o`mTKyCZAV=uR+{plY{6)7@vZcg z9e^n)YYiDj>tND&L05x%p%~JM+cQP(72XER)SJ4lLP;5Jk-ZGEF)62iYan1`I=AvI z35%e!%#-1l-|xkHG;FQc8_seK93w|eXC3wQ>S}T=hL z5L~@RAkPh)M0J`fxgvLj3LDPZ>CH>fiPXLbvw!z@8F*rHIdW>NH4q zp;Q$d_L}V~$BUo_mr=xvSoqR_S=Lz$rBX*cZqO%}qA?E5p|=gXlqb1#*XLbt<{PBF zdnJ272)xp@Q@EkX)`f1+HQN^!F6e5`%F=GsB*TVUCCDnij)wOhNl0~;lAVWC0x)rR zS75J@l6Re=-c3E~Rpcw85vA;jh#{LTScnl$u#oU72?y2&ds&c3HY8D651mfTeZ??v z#jqQ*S~2JxqMBpcD>;xTmtXx2z2O=N`(^Eh7VQQ(5c1>e57J$)@{0on_#Q$u#4mbT ze5!8i@h39DlO7#+j5;FLveSxD?~wzX&h(w@K!`(q-{P#Z5nchnf0Y zMuvuVig=hn{^qChz34wQp9&4*+REJ4t@!91bZ^k{(=){&k}Lr0QT=KZ^36Lw z?$SQ_>WTCNk7{WluM4weAHMK{c3SwTYya(Ad5+#{ZMFOYT3yO&St1qiu(GWdscI_= zep#63VFt&Vnwr7=*FYbkqAmTKJ2)#{4l z*Iv81Kg);nvyZ*_?6cYvoBr|C#@yPeXXJ_a*vnn?k*vCT8|1Zj z-whalxckmsy8vYFu3LI=JPaIjFgIS4%JJuI#b_aSt3~8yzo~GIljjIdiIwG<)|!E* zdcI{_OV%d?;KR4Pdk$*T9Tcg&W@pJ#yJmj(3t`D%7SwiE$)BI&g6{CM~(Yc+n}ji z9-j63oGYFgv~23YvgV7%P3TxSWb(BK78JBEoHh7JUS-sE(bBY7+G#+`qT>7}{3ozK!q=wTP-d9Q#AigP%v=kVY57H&y6@Ph>}(x| zHf_nDwRhb3hK@&j>rdLu$9R<+pMwbTA$Rgh6`!W^^CGHO3JgDU?8qO{f*VucZFAkqnfe0y0Msl*HxFrliF@$4G&2s8lI&BFvp_H z27Z}H0A$-5BE+F&o$Vxf+nHWSECefSkI{IZL(TP4B(-TJd$r^r_%J0`H0N+QSf^KX zeJR+*EnO?D;|m4~o6urRlS5nddrYNG4H0Xb8h)s!7?AtYVa_FJb|7X@sT;yD04>7 zRr=IelXS*vqI|cu^}YA_`P#wDh?BlG)AN*5T)$gd^778#;a#!hYmar_^k=?k_xBfGykz>S z+wVAV*VQ~f5$E#Sy=~gBwil?A&K-Boed~$RJF^oCohDUc z*7bovz9P6bfHl>+2LnL|MY`52ijhb-X~xs#RcX-N`@9v;4f#ZA_ikzBXMg_S)H}~F zz2}O~-`=+I`Ukdemrr!Hy`Y`^MC;a`7sgzF#lDZ8zwfDMh#zghY%VbKvg(wz5_G5- z$8`!meNce+S#6z0bU%x_9xgW~add$6@muXIef(CyY6f7?rdyE=D1*edOYeGD`<54c zv+>*ufA#C5Puza>rHN5I_itT1S-rRUe;(cc@^oN;nkDro41_S)#JzTJCqC4`oHpW4 zet?*h;Y|hufIzlaX9~#;f-TtA703O{?$!>r?xV9m&ukz6S(c7#)p#49kNLr|>%GX!7FW+o;x3hu>+U1JV^yQ0jNiy zBkSlmM$0P35uHMgIo4_O`5oTePPZy~gFcaMFqulE$a=M>Q~5J8(<8t-Iz?(!e?${; z_OMaK9Fad+^Gh~%ee~pYzxy>`C0+8xA3po>FE6!&nfF|N^Zoa1>iAtt*9)U}xAASu zpXSbwxy%RbxvuM_n^ryW-ZS?ddz$1LtAE5FAYD#Yobqt5$!nFsLJY7WR&J|t14ajZ zx|TjXaH4}N`e=o^SHvm)u=~pukRR#NzwjBtM4ozVYu6$4q{UBZ=Kz_jx=-;R<#)&z z!1^GcGLMBE_7fh{Trm~2igUXwRqKWzX2*J;zn*NMDa)Ttt71ls)&S^p&rYmA7&^4P ze?vp;yz-d4AyzfDp=h@()mKyu8B$R`^mOQ2%XZlmX`I(6bTgrQu$IRcpnfZOQ=7_j z&%=j+^dV}enB$wPrr=0`2!%l|LP1?FdPzYqf=CcGMKtfGPlCe<>N};($3FkW$i}ga zAMS4Mp(=h_`zo@tKVKkz&3lRJgR~V$Te(;EnjKgqZDCf(>VB&cYp_U-)#~yHgRKw5 zdPcHP;NlM-YxQ8P#270>wERu`2M>Mych0ph-n;FeX75O!mFfD7C`qJ3JS>vkfloko2OqD30_$sc zFu_f9WN6_K+g_FaUlbNxfp7o#8E0?5I-6A072EIMv;Kzrv@&7ivoJk)TOCU3Sm)XY z-hF1@(LZN+EgXVtsj{;D2CvbfYj-T?HVbyxT$fqdKy!Ui-&if9uPq~yCo`feDPTg6 z4TMASNsF!pvtu_*PZfwWqDSuQ`j^CBTe^}wMYa(3-2vn)*oaiAT z8!)g-Rwt`_EFVs>-a((1qiJR*{MuyAAbp;c7N`vpt{i-h_H8=7n`2^TtGFoTyyxtUQV zzBzNMfWvWtm zuU}1JT~`$6TCA?TT4bkB7Y!<@!TM2sJSlDi1#TKo;#-G;DhS^H7}uJFST6+`61ccc z+sj-2aMT@=o>HrwI8M8 zHRY>^wrI;AfrV2Q*}-EH`*EiBl_NPSc^a)2pV1VcYB^XtT%R$NiI;!lHyv)#P zbo_rCRf@~I?h;y0KPt@bdRO#`ubh5-OHv$$*CsfdiBToZRtJDVo>Oi~DHf)T_f%RZ zTg1R*7IDeefEQ1%yq{y1u&(Y!^$1)bAkaYTIq4AvJ+>=!ItP^0KyuY0FBFXjkBzYR z`GvT4?ABX(^7b7&Mzv}OC0@-VYuX3rpCb@8_rpI) zDR~&dxiMFQskYB=z;qRsfn}Cu(WG!{`*tl4&rOiJ5wxp1?ZZ-qR@RaVxa9z7cTIMS z%49iWH8Ihp_#EQr%_iPp;wCKqK^u-#t1mkLqYa>2m9eI&#yMb64d!{#APJvU^9$;& z+Kbc1bavjst8e9Bo1fEO*Ity?3T?}H^tJv{<*U5tDKtfUPW$VN`m>l<_yOr&{Er?; z72bft3v0O;Kw2W$nVe6Gh9^BX&lAk_ggJ$;2D0wKJ??@EC*g3{Ur*9-7_T6ae^&yv zjYS}&u%Ubg4wCLQ+_?6cXI`ug9r&Y0WG@GTPk zd8Be&C}xEK$$&3#e9X^gp?xBT!{4UN`9Z}nR1|XLVivEd;RrM%fS&JCusjcaKgOn| z;yD73@R%q?Bw;JQ)WV#9wy25^ebg0v+U|MUXv~Qzs)P^P6lI6==pC5afDNFh*oPaA z)%C0b?s>FHE4O=|Xws5qeVw~OcMtR@i^c!(l+!tX#mv^_ZOf`t7gjbl9_{lMAt21S3#iN>TBU(fYpq&bN);_CwYEyJZ*ghs(${Kh zTdA}!@7q?HC*SX!XC|46xb*$5|8;$TG5eWk&biNh?)7)yhgti$`X@03DJ!(Av^|RL zh8KCRB*cbvp-^D2i8EHaSQl}R(Avno*CElqAR}XS1==2&iZhw4jMG`>-D;&0o8Zz< zz4}fV%OmH10QMaDqI=`4d__TcYkuMo}# zi^`pM+@LB3gy+@I>O$<=E>|y4QEGR_wJg zR=_rC3OT-=)zEFmE)&im8Jd{90HbT@=(pG>*?Dv{Q*0_Q1lZ!-(KLS{`yEEtihRVb zz-VhsjSyzY!z%+k=g(mxJfB8TWgp-bO_^7?*a7kifG}KEsXD;1j{H~9=nF_4WX6f} zM}HPbe`qy>w?xMTIIo_tX=eVTM~?*xR2;3x=z;*}`TS_j@U!1Uy%VJQkKhJ<98>yB zw59;(&%OY&r%#vOm>Dl)b}wZzr{?{bDpH2wJXb--(^E72ilf8t@D{D&qVMpRn6jL{ zL;2`;kU59PzfD&wp3FbRR6g-NN=DuTyu3u{r?Hk0vuv1`EyKKAo#EwuCeAX<$om|+ z3?rk#V}_AKPVY{zapWXr;xkz$z6cNdKF`Ah#QM2-%{RW8<>A|RsN9d89u>M7;ob+m ztnOrP7zdOi5u|tMz9R?+8FdG8aNhhy?dxvoH@>$+m7KJE7DOE^O(69;UFA4jXwV^;KDO= zzW^a?@v5Rx3XJP3FVQXvS4cMvvwKNk3h!IC?j_GG+E@SRT=*53iO)3;U&Hwc;z@Vo0Yh zG8>iGMxLK1U8vno*E%<|)-f23)65Qi7E_uGN_`IbL7UOz$D#w3xU*K1L7@w<#@w}v zqsdyur@zSnvPSuUw-^+7wB>W7=~~6nyHP7j*UF*ZsR+Y4R%Bsut&kJrnE&CPmL}m7 zRL87<_D@F(eO|%mK>J_FhxT_+*qMmBg1Zb)hlGf^r!QQ80sr$Kr8V5&*Re6-1c~Ua z<#%vN=|j~2Z;TZi-a^^JTQS@SqwF%jrPRK~$Fxe;%GxVIT;wKPVvgLLM3oPa3ap6kB?@yD>#zp&Q6R)K?w=e*SQNcKQhn9XC>2Yu_+v}9Zhij451-$9 z?b78}U2~~$WXp{={jyc{?2j$1<@9gaP~i1fxSEG2$S-_SIB7EXFZZcr65Gw9qL$(R zikwIhvY5qIlk|W>QKZ!$D3owT00yJ`sfKZIGLyn~3*D@jUaF0pq`v%Fq>D}ww~=g^ z#{*ltemEXW0L^-WzXur5!~gZdb-($^Pk!_LZ!B51{zJA@c3 z^_&y`s;&r zy(o}TvqSod5E@JJ;TD)y#zx_WtRv)z92>!@T9!JUH#pd&0`) z8*iU`8GCTjhFyo7?w`~*rhej-YhFAwZN_ce4u|h=-_tmM>YAq?Vz))t_FUdEZdK26 z(El*IpSzFW13tFI(7gdEWM!9|N9u*)ZsTRQDGDO*oIR8N1yWlu6R~4pd=j}N|HK2};k&l4zJKl7 zJNB(%|0<1sdWwATH}2p3jl1x4lI1ZWb{_!hs?@Das$w?3YM-fEw+~599pWy2bTf4q zDIq)5JBx~BJ3Yo-CIX=VZ-7(4Ei~zYbW50_XAt}n7$!IIZ%FhJkquyO#9~vqS)wPK z!k(JE`0|@xK6>T;b(j3{@Y_!xc`11nd(FE)T#k7EUDqX_G2LJ~vT5xW;g!#xlgvNy z(R250`*}z@S@_lu&JkIjso>#c#ASxlJvj89Y^(GcDly7oK`w~Zl)8E=DoSi_w;|k% zj**5}n4cEDB9nxQBR}{1i|sZv5k~9Fm)&mO{4?cC+Mgk;c%CM02^pJcx{bP?;xF+# za*T=pDa}RCPET-CQ=Zk#Rr&Xe0V^-UEv?*7T@%^o_wk&gcvdA)R(a2;OvS79r=lfH7-XZt>U zSXj?LecM$_Z?Q@A4{(!&rw`Aa1eVF8NF}p4W~$UxhG%FUZpV8L-r)!smLd&Gj-AIB zn2|#t@A^u%veI1obMz}6MHp=O04ioPWIEL}K#t+S*;9a(eIZS-(-%p+_h@)1NXgh`rw_bz78?I)6Ev0`3D=UPxie2#3R3&3aQhJW+hk9(_=*jJ;cci zc?*l1FO7QhOpVIIYtWa6El~6;l}4S$&~JBBGZGA(sV%vX8KDhqEAAY(17f9$!*mngl`QVa* zjy<BiWT z(i=q%M=pmkJYT3h!D~82+W(a!?b$o{rAzK|Q$7Lier{-|in1n9`0K$NhKDl0_ zcn;u53QAeW z?Vo!8{p>e@mwoWYska^jJn&`Vf4HgFKmB{*k4t%TI=u5000nnG)%oKW*$Q?J`@oLD zjw3G%KO$H`$*U`bW5U1QOY)`*_uq^%1`{hGYn{%VmbLJ|##&uWh#3=e0RmX8lG>lu zYRi1jN{fRgrqE&t{)KZEmi$FQd7AJwI4N5uhy`VoJtCu`ZZ{G@Kh^v^z7tgtiJOYq zcYfT1Z+!doTlamOJp9&?*WSAS{ehjwFW-67s_zS*>^i<~=Z&i$1MdFp>kkk9QTT+_ zzy9#IK4Rm-g}qxg+%0^{%92;EyAvFiJs0TA#B|4kITfyM`J-|$zFhkpu6kf_CliWX zNx7>=Dv6vzijK+^)VS87_~OhL)VqrHBEs68!WwqpPL@`VaJLC-*}HeKD22UnE>5gu zaSpEl@lwE)KZPVorko{Kz^I;68pw57h(Vr&O?Q$MdNjc@qUHXeK0p*KIb9>gEv2L~ zMPQG%u>YA9?w1HZ6;{WG-{Vx~J>(3DS5piWT1??RBn*9zGbivix$luJR>{7HSg=Ch zgF7lr-Otv`*yZ~KciM!N5N`svgyXyxF(a!2wN7!lJ9U0XGui zasjLhw>TAc;j$2w5!xR=7`u$*5G##Fsm97vK%^DGkFOPU(%aH^Y)d}8mz$JU9>@Mog?%TH{t{k{hv|%2q*8;m;1*~X=gkHyYd>Jt z^+{PO0dWfre?Z|rpe0g+TG13opAi_ahU~<&j2cj=N=kunodU;@^F~syMo0>j&Ol5U z_(c+j@bPMg zNm!Y82#0?uy!0Q}+|G6oZiotD$n^+YYVO$29)9xGpMLM?e{eVL*>>G+yRW@| zH`cifQRnYcpSLsSb=Wwafv8J~s>rrgB5|p(|$T&l$6h1!Pj{r0EsM$e zaRDWf%mq;mfr61E(o8{BLl2#IM2Jq#X#pw-W0GJU|Xm77SK`+An&nhp~i! zJ(2UCMTg1jhmnP{+37I<*gZekv+I4~?;9n1|9;P->^4^R=A-xC{qjfSBv+jK`MVEt z@hw*zF&=*9)|*$)-*PyEAp7vcZ{5gN*mP|9HHVh~75~{sjeG7peACMvg^KBmC(ipW zC{r`^h$Jalk+Fdm#_CuF3>-#T082+=gy83Q67y##Ztu*PJ}hoclHjgT8Dq5U)9Kv% zAXJI{L+zf7nZrhYQA@Y^7eK~{ol>mLtQ@ui_BcdrAS%zSjUt30WBb5>kR%^mD*XAQ z6W8yZzHbHVJa_E6LE*ppZhGK{wOpqlnUgs5J&Em!TVA^${AR_$sfTZVgCI<%Eo5I!zt#KNa|v=Db_>(_Hq^J08qLa>mDFgG5=~uhiDupC5>0@Kut%gcj#Oll z%5jR@qs73e*$jq^nbJ&>#3QAe+{Vw9XfA8LNQowQm&8QXfqXl*qGp3 zQ2$G6fMcIkOJq2^A29+dYHNVAH~x@)7Hq&jhh|vmlPFau2`_loFj?;2b>S^ysyq_M zE3Sp*FdrB?1ylPtx_D5>!+@L0VHilzPV3iF;D;H2r#dKD7WrIwA5qxsIOUie!2v_X zvy%D3wHt-!A3~E&BRf_4@uw#4uUDnlNQ>|ei-bQT4oK=S`eKz@KoOJ@ zgU(cKikW!eJq&6muFEs-!}TQGJ_oPcS3m$@)Z8YA4h9E&$pJmEpac#Y0W_5`nOqLE zBhO`Wct#_lY>hMp%O9&AbEnOH0rm5lL;Bz+T zb7scNdexaUF{3`HeheL9cs0pMnl<_!b(tBdV|Ou09+!u6lE<)`AV`=<80ITYBJ0kG zU$G|Abn>F;b5ZeD0`IqN5R4=;Fgm+lTyv#qrZ@@bgFl8|lymr{B=VDS$ zv0WuQSAZ5|ipW4TeZrx8yVgO_e=kvEo7Nz}AtX4$qYnzNv*Cw?Q!|f{Z;lARd>QB_ z7?Q$c;#abg`8n?siW!kV#f6>cViIncjCs1R;L{4TI8DM&_Gd@2d z{Oa3mmGB?mo-4fm5L+j_`Vi+39s&A$w(yYn6$k6Kp}$I}C@k0zsJta%hhBDlRd#` zxdAo>(bR~On)Co77_Haa?E&{zg?Vqxq+?8o88}gUrr)dMy?dobn;1DD8fX~1lCdkP z)e5uK16z8ey4Vth0V~D~0(u-1lE{zN$_?pi-8D;R!qvW;y_W5sKX(JiJth{=B8Z?k zxb{QgL)QBHE6`slQ=X^NhyewKWRVlat7$`sPwfZ^v(M#{SpBAweRf?zd5%#*EepZ~gd3VNE25SJ8qMMS=UEaS z5t~^86T&lMLa;d~O}Pg)#19TUy62{Mguic)>=b_gAz=B=b8K*}WDBc0`I`s1#1&V4 zM4#xgmH>58mGXmGU&66E>LJ{$Dil!0|$^^572u&Cwn}G z3X>-|$rIakvJx35iwrosgKiY0Lm+qG`nzs_W^EF6=t39!9QnZ0*5PRf z|2I!Nc-xh058QU;`kbdB%fp{%*V3oiVujq}G59f0Y=0C@t;m$fgv9BvYlo)+@{l>O z8|bTmTh6`;0m$4}Y#4qkVy%9}Y(Olq1<^GP>JVbF673u2!spa6HPa<~BgSaeXzqne zk*$Wpu0jQG{S{5tAd*UI;%IBHHyoKPL7Fzk$jA=TVvso6BR_H{E5@``t!!hD(4a&` zMf;eld6e!t4 zdY~YSR3sb==?`PT(A*X|ae}yxRAwr9Nla+%724Prd)W@*K6v`exKrsU_X>9!pF7ge zr+-Yc4iv#h^P~&$7Qk#k_lS5Ce{mt;BdGTqfjMEk0W(`du~C>PWY}(IAH_y-P{|i! zxED-HUX%+`VTG5mq@!33s)~NdpmG)~$ZZ#vfmw1#FC5v!FAyvBu$wD@aQGkWW&rU{ zyQf%kASU1Lv=>`eFAtzv$ajFVyB$cGhwRQ`kHmG0 zvJh7%7ic6(#JS%+fmWDARK4~FsrG`aNI#KdPf%@+cSJ@fz*Qbr3$Z)#rC}JjA%{4V zS@Fq$HX8i@#P?HsFZ8l3vGhiDmaC-t z^+DluM!yn%&wD-9sgnNT*F&Mw*Ng0c#wx!$LR#4!$!bpQM*YyIl3Qq%O9?WV^I`=> zmzpD5-^XQ{k{2)bZKUMIwzN1gM7g&q*SU~oF zt_CKz$DA?0q9DIK=G}|cXXKa6eEbE~8gzxX(G~icsXz2F){kA8wYl@wX7}&%`GQ}z zuu;pxF}v8JMwLe7FNCemDU=vh^z&KOsJe+P8o>xqW(Devrc02Om|lPax&qjMk^)IV zAq-n1uJVn^aC3GS01I-1MDqe|eu(aax)U@i3qB{-VT80Hq_II!l#96?Qgui%3$)&m z;uZ*U;lc$jCOpMXTFZ{1@P&24Ny2R#Sb^>5-h+LGzLM~Z;2!bnLTW+5Eyypi$+kvf zg)Y)N5DkpT6Xp&%+4;<`!Fh%s67dX~PNg#>H255Y48&;eW;&X7 z=oR&osGeka1r!rLQ-d_Cm~Osx%jE~MPoPhclb)=~MbgKpRVemk?Lbej>ta3|?}$xr z(Tty7=N#_OTEu^@Lu(1?(Avf~K09{e%kDO|t&KKoeIXE-LYuXcNY6;KRw75}Pat?s z&sMQ%tL`;q8EK(%j)j>U4kDdfvnqqc`g8yGg}?t?N7s25R_x~Q;k)jB?Ad$o`R?)C zmaJVfuz29@Qpb7O?GmYiaH$`JwUxI72{x%XMfY=m&|G#IK}iy&byNM1loQ2n)zy=BwAGbfN| zu0^qU4UlKZEk211zwCL2zWpezak{j%rL3&ES@J@2d1+H4()KtGs7c??{#EA0Yb{Jg z%x*dvt5$ATzNY-0QldPeJ;@jTBqlqd{|R}b<822Pg)bYahcH^siiuO!IcM{g3+8RQ zW^Q$CLv2@=WdCKe=C55lbLIv%+F89JR$UXrjH5z7dn0f-2Bz((@&!QWPQ=j1qK5&u z$$t_fb@4GhK*Zekf*0Moh^|A&18l)`ygnde*%6jK1&lD+tUNG#6xd+)MuJ=q7ZKF& z+PN!-m<&k$sa5@Cy)n@LM%eqrF9Q;h0YkDnXus_;Nta{;qIEnc-!3CeMS^exVyEG^ zq2Ecft6?YY#{a(Lmz&Nl`Gt%jSOW4vxj@)}+c>eMkclcr#=TG|d0$E9O_(A52Fy(l zG9#H_EU|yt>B(`$j^euBh2Ok=`grvkb^rKlwv#8Vz%2L63bTC9^V@Ov6XA0tUg$LV zsI`&X|HiJjHXlALDL{KB@;4k2K6_3!PQle57;{4(J!s}THuR<1u zXJouO6LFvx3@jDwEs9TIXySi@O^}HF>;X`aqsT0(516HZDxjy*z4t!I_6Wzg80WiW z{}S$Z>6>3U@Unm_eh@$;^}`<{Mih=&OjeCvqBWb*)Dwr^B0&^nmsXAAhIfJWO?&yA zB3;Jw?K<(z8qM)hWcOlJ18evCSQraw`HF2aYkZFwOWNTz!naR zG;uV)fj(_OeA<;FRu;{1>DXbcEI4kYW=IbCgEBkZF9K*EFOhf2B}n;Y!F`ncAO>lL zkSQd(`)=+Pe#So;ekPvdM0|^Mgd-8SSj?+avwHy(U?hkm74DVtdh8~O9TlW~6Q`Ew zNsl8->z%U#>>(N8* zsIVR_*c|r?R^+Wn8#&Bt$>^y_Dy z{MB1euzRrgU>wO3Y7>m1oN@;oNYN&!hHhm>=~fy6ps4MI;%MB=EAmRzVnYakwdV55 zaXg#!!YV{W9@!rCUa4kpp_KH)LffFN27^Va5!+!AKO4><;u82u+vO3SF;f^zUQetC z-~Z?{;lp!JKlc4&2pU6FNe{gL-l>iTn1lZtNhfWX7o-RmseJ-Ad-K>fG_U zN@n?s7j4~vd=6)j+>LfeJmbPic`gRwj4}raax#Ezqn;0Pn06hcQ8^LiVo0DbY9Vu2 zvQB~$eMbLO0YNBm3Oi}eIzYF8@dJ zJ8TuRjqBmI!M9N|9ZyM0^hG@)^2;Wb^U6OmrSgzcqd3LKc(zN*@_d1c_-gNDae!oR z&}8dWQ!|Zm!V#3$2LNB+CMbUNJUjMTu4e-`y>aW3G`W{;72fGi=_AmTSPr>CDuXx12i6-oW*2`gq0VjV%*b?CagN_MR8-Bh{=sxF~lA-nRruN3~t~2bGF9 zt=DhMfYt4K?H^1bpe^`6xXbJ+^(m|KRhi7I(j$jJ%5>83#Sj`Jb0O_gsjCXfV$6Vf zeeixWHD(kQK#MMkZM9?Nrv8P$o!S4BJ&P93xqf3}?++fj<7RHv9leudw_lmq(tT@m z%aT-sr2ODF?#5}`24vnD>_U(^hjSBRj&nbz=jd;euJn%>`B4tQ@D5r%Mn*n#k&*vG zh3zp$`8dz%$xu3W2gd#dD-YtPZ~9c8gaGBtza*WRv8CczP>GRV!B5BOfNl3A`)dy4 zXn!P1{Y-lw=@nNI%Q)l117rLmp2{)H0CLnw9s)=t1blHFC&L{zCTo#Gf{26UIj_d~ zoWZO$SQxuTfhL%lKNi@Z4a5opU4cNE`&q;rK?I=Zeg)QCO}A}kN=kQ+OfXcpdlS>mQuw=BG+w-=w*-LPZrtqX6SI#qb`iU+>6 zY2%?I?9wIVPfHRTuiLTqrX@E|nR4@zn>K8{`U;L4+_-U&d|+J>@asd6Qlw)aI?WZJ z^`RDL0;R@so-1X5>(ukyDV{fJWf204#^n%E0M2ReKEMY_XDf940a%KK)xneAu^xRL z_t3gs!WMSpee3=zDRlgXWq8x8uP)@u(|=^|b(bR#HMWGDO8wAz$)oTl9HeuL1=^#? zVp22v9HdQ%oq*xdv_6W4DrWuesdFY zQubeK*0NjXtahxMaP7jnwzbLm=^MHAx9khF^!TncZ(JbUML02lnYCbMO{h8Rk5#zQ zXh6!8*QLfC8j~vbC#C2ZqDdCH<0?(OsHoYDsZL^=6OqBr1hVPCvx9hq5fO$bb{n}Z z2frj$6l60=>vb|;E14tl0tnm;E%&gg(&(u zcFn6%UAp#_DRYxW<2TO}_O4pV+iPcaOxR$PniYYLDu<)0BTzNI%F4>BN_0+hVP%QV zWiI4iYuL1COli-ht?nmp6W;HefL@P=nx#`~*dw<+TxyM$7nKD$j=VO|5pj51r}HIM zg=Uwoq_WTqgG!{#42cT&#!Y3ej@6cif^w-$#(3g|y12{LVU3$i{Y*!qvVL$vg28=J zQeDzz)-u&CsY$Y6DK8BXUqy{Hc_=jLP*Odx#|DYAj>-72N zTv8H=cqk+@&i5v6LkRUqZ^=ZQocn)q3NN(m>}x`^Cs<3I`nWc)Y;j_MjDCmZ-&z zlhnCHh1>(TslaV5xRlOtX|0FGrpn9ZuB1U{l{Y7P1|ta=v6i^i+N%zDWpTMT zuBO30!c`)(od>g_NQL}JxQ#dpxW@z0)NGqa+V^z$f&_~-b8IqPR$ph$w5=r9DVQwm z_jLXebCR_>ES*A zFYA7bHB`@@w>&m*$C8>?enC!I)2hBm)r7g_W7bqw&mCK8t?Vk#l2LF>zpHH84?7ixgZma5ez&}b@XG=99-6gQiv%L85} z&U)i$&Oy{}4vp}wo%AfXk(RY$|0hZtp8bFTABT@M zX%MTAo&h*AOt+*NSGNqGaiaL5>Do8Izc}VL-8&?H0Q!oETyi(osN+m;r^(6y(sz~k zfoL}@N=<|d&}Wuh!bLWa&pZ0;{r2)=o!4UZ8ZUazb0tIVx>XB2$z;irHJ4YIYx-K2 zEGiv0zebc2@8?QZT##of4q1XF?jU#hMb3Q1C~46oFR5<#^HVM`wR5|xm2)d6)D#U& zsqSqkku*RQQ`sSWm@6o7Du|i14f|<@g@m4g8jZBjm?}EtOG$NwwgfX+k|>B5YBgwo zlZF}sQjACf5Ei7&Ui5)-iF({P=Oz8d8VPZS*_RDwMbtflgZ-0KEhVROaqc4d0iqVj zp}%m5{l!y46%zk=Q80I!98N65-wt_3?wsq5RapIwRQOO$${#~ZJ-^>lK)x2pl3JZ5 zWKC2Jx)Y_Pg%&e_>h*;rmU5QbK<7!u5S?qu<$7T%(2$iOr;#f7>}5r$5+XrAiqwNr zwV+WomL!_^GVUMVo()rLRxTR|PJVv#`tNLL+V@|E8Bww-dv$j>x~Nk42p1z%R+e}w zf6d}#7QN!Q0ZE_iSQf-HbS10QdIiPYDnCV^Nod%gWTf zE~E7mn=fuus|P0ZCfWvPCMIN!vzK6*!59N;WlXep-#7)^gPLiuu|eBB`;3SXa9m>) zLDDb`DON8HL?GMTXRU9rR?`X*=e|;_}19uX>J$G z6c^iid_Wn-hSlRdF)q+ebIdW!tEh{7$y2|{Yr^Hl)Fjo zWRrs8CJkIf>hrR%A?k^p{Z(6aF?r83*9@JLp;(9M!%f(0nA@K!{XlhI#iA0mAqd}!zMf#})r012jK z#SeA1%nfL}HsZ3>6V2-zcFZoWok#gKBg($t-F^Ag39H73WPe?=c9hUrHt?pVbItx` zTOV8zu3Ww95`Qx3T(oX|<%n_?T5=Bz`%+ffbL+cTPYPxoy+EGm`Ax1zDEyb)73nDD zr(R&H=JeF&>E<&zCujSx#)q-WTqA6wd*wv#wu!l#NjT@sTO5nG zwOxBzSz_^EP2#c{bK;jxi?6#Zf7{5e(GOAK13P^7rSr82b&?$vH!ibpQd~s}J&$>vEfX}*h^(K%ne>5?BP3PKC!Otv|vue!dcuCJlU9vqAuBc|etidsdYSQppV)^3^7F}5u}e^z{K zV%^}1#1w2;8d?dRETEpE)O~?f&`gS85k8EBUxhu5ou+|ooLPD%vs+6V3{yXZm~~QL zFbMfMVjUsmK$IBL4QRD7Y)XkcIqU?I*hH;0@A$H`Bmzbi*?SpRrtoP>DxHOHhojVG z;LJzbZ|jzoL?=c{8p~|OO?@?TlUXlOl)B=z&8iu@tH@sVtQsH0VP2Z`7m3=(!Ct)R z9ZhV_{^i#_xT4}hf3ElP949R_6=`g4vp4*cnTt%Gu?;>$pury)6SlG($CkBg3==EN zy(>R0K3Mhl2$gfi(l zCcmJ2O?U$)pp}BzA=0S9aT}3q)+%`c&{{fu&PM(JiVtw* z_LxwSx45eDzwLG5qM}fldd3=k`IKhwlu3~Z5u1dYw5UiMtQ#LO&*`r8+s%CM*W`!K z&TraLyZG*ME!o0rM>Le$1n2^~JYr`SH?u@wJJE1_u*M z3CmDd4x0K5ggRk>cWP_ToC0@5NTimA&hUu@9Es4t-@|F71IgC`q65yN4fokI1LQBX z0FMe0sBvIG;58FD0fwRckr5J5+KtxwJon114-k&aFdUx=AZ6o}_4T4#nbj%g>@A$I z2C}Y7#tkL%DXW4A)AAShN+51{=T#f$$#D@~fx=hQ=IxrcY>}oEeO{C+3pG-G(C=1l z*)4VHZDnq~&RywhjiryvT$&|Ly8|p5)<;`AT+4P3RCs&VPw8LZ?H*-Kn1gl2CG|nG zqiKF;!^#CyoVL!5^FpiET;kRQ{lzl5+aFN7maJaxZS5?P&Cb(Sx?;1%<+7NIg%x#m zm2IU~d1Y6Hd2;uJITB9qH(0Bza%ZHgvax+0uP_vs+bpGSs>m}+QvMoeVPSE7>3`PsRGJh88rhbPwS5&b1)y3yy3_D1 zt57POr4eCMj+RTmZZ0l1<0BKTeI4-v5=Gk)8z1v~f$s6Oy5cgMDsH!T8sdcoM&tC> z&O}+w;KW30Yehq9XLsdUvu& zDKtLuWno(q&Z*DcI)3L!%yx($Fo(M;FFeXmvuiHgM8n`;7`GK;IKqk?YGXi=WsIu4s2z!>UEICbpv22lc=HK`lSxf8F}jtPdK`H*$@{ zIOu^;Z%j5vW_HZvX3kt&o1Cw+XoD4NQf6I+?b1{UpNbtSNKLA!h=+ALTbpf7+#GbB zU9sZwJ|@vUs7UlRx5fJ!<1_n4+3_T?C2lReFW7fq;C+osX276F_>;tc#Auip{>e(+ zuQt}RJb-;=Z^=C?+(Q2)t5#PgNib;95)ulkT)%FSGx;?Qbk0HILAv5H$9*}6CRo}L zaml&6(;Flu4ZYP<21rO~uClggPSw{o%!QJmyCi4{6`Obx!N}%NNXBkztX!>NQez;x z6bf@VS)vOxm>Iq^p&sND&CNyrcpxyT$kY%wSI3Kn&k{Ak&f_!f?w|QdPqHYv*CcBs za$ok05cG(cArP%@@hpV$R2gS&@6WPu#e@X|b392Lgsy8Iz6=Q!-;}FoPv0^n0B)bP z&Xr4cc7|$BkI0YXOB+`%n$P`k^IqLCrC@V7mK%uAGkNi~eJCs7hBcm4iY3{m?@>4C9<0>ya!S_P9-_9M)v#!|O z?FAZP9aQ-}*xxSP`Wt5CIyr~N95&-VrcDQDzpYqVwa*t#79+$gD`Vnn^*C8m+@_4n zvfzaD$sE&rfSBI223^eT<91DCCBD)OHfJnq{vHwrNuN)$t74OiI#>5MY+Nz>3vJVe zHn&3GGOpXT`QY+OR!rx=Aw1SM;oiClVFR%nubXhq{OBmVG0-r^nl(Ualmq;|54#Eb z4iWZ+vBv5Y4-M7=wWc%Mf)$ff+iiPnT#2n3UwGTtR7pk99G`4+ogF`ZN`1H@-jPrY zGKu<;ZBD-fT}HGRN%;{^ghUX@hUcGg6 zC7LLoUD9Ln^bL5QKToMu&D6%6C6rd>I+M0K=`;fbQ5$f^Ba|QkN)V#bsc&{M3b45# zkWuT_=dG5Tlns#;j#^>rJuO`LpN$pP9Yn`p>Q+d5lqqHce)K zALNdY(!PS35v#K3D|mEi(Q&Y|ic4W}OH;)iVXcTm3X!ZD z-Hxc9q?#!bAe$&1pr$dkn_wQ3P9P1W$0U-b#7_}%JEFVm4e8lrZp7;6Y-RFZ;Vt3K zdq)Mi_(eHkhIfUfd^?&}9TpajBvWayO3)EIhOUk7%$KZ<|9^IElD9rx@r7$cO_SsY z*tZ&{li*yusro~0sS5PLw&DWKVwqN7ESFiW#d;m2>)4R1INp+|8LUl&h#4#@4tyqC zChi&Hpc#1*fCNF8$c(eXBkUOr$|u1Z)|BRQ^alH>BgY;8%I>!R_x zy73N@z5?@{3r&;zaU}tWf5<^FOV1W<_~|(;AQf{A97_vzBltF z!Rhpk_vHu)c%Icm=lC~qR)FS*HeM8K;#1K>ttqw6uJt%$DVx!`J+ddll|-uXg^%>4 zLe8SN(`K7eUJb3&gw&BY9+}*Iwj4G)wD3rFFQbo#GkO$BFzS$ef!m)Sq%#iZ>)6n! zlQQhx=jG*=<=SS;hO=_{>PgUkaqf-%Yp&|PKPUUfF}Fh>@25IpJn19kCeVw{(o`r^ z<)#|Jtcz-^lC5>|Xtbxbw6uyzIG_-`omKJitt8!9d!D#{V&5(ivq^tGOY!J=`XDq) z4S-RsNT!+1#KIeaaw2erpa^?oE<1cWN;CNDn#|^=IzKx<)-5K!yS=X5k*74= zd0CBcOVB8pCNcV(%Dy?G=Gj1GY*mq5?yMRcAsQb1GY)5J9Qs>Ni&eX9Nv1>B!F6=Z z@>Ca}|D{d*DE^E0E>O z`gfP+`ge{m_U}mS-~6?lJb+q9i=%(T$;;2k{;!-otWG%sbN&)HkK{#i&KW3kEqLj9 z@DjnNj*pef_j$eJYY5J%rXpFdQ>WSwjZaxwCLUzjUT15;Qf9;`8a$s0PF^?hZ=)nn?V?-eZax^45G+&m z@?`^MlYe?S0e+>Q$Q3^6rb{N3KdE8!{J|65o2FGPo|YsqFiY8(&_f;V1O_Jfa*07s zRwf%vwAJEVZAJLx3$zvC6XRE^tc&nSegyAxr!E&`;mc3e{eP~qM(59PKP~vgEHR=( zR2_UGSD#|Bf>ZEGjn510C?!^50 zHn|5EhI)c7jj60qF1I1CvPfQ21^97QF8nx+9PS%uMFk@wwNY!>JRD{udAc0 z77m$uF;5bLHS2?!JDlPF`e0;&-rjea)hYXydr~x-^n_evy}Nbcnp%syH}VB|?k@x; zlkj7Or^~f@ZgkIL5;tqUtkhg#RJns;bD=te?*3ZvWZ)`5=>yQ&9l$p&U^d0NG*zma z6fafqsu_porBspDj#jR<)mLUujXg9u<us?u?Yf36h#9k&z{FlARIv=yMV$pkidBZxq(+s9Xroxk3o#wR`{`_dY*OXING={Gy)jofu}8A-7|sWHOQC^d zg*m-TAM|w>|Ky?`ao>ciH9Bb0EcS)9P`6;b8sb~dl6XxKYDUr~tMT@3Cfl``|0 z*|F-mj*3RJl%1aP>!dXJuGQ7B6?3a+H9Bf$xkG)`@tT^2DmKoT5)%eH5-P&WwW2q3 z=G2)}pd|=PC`))5@nRCHK)pgfXHtxBHXBD8nsQkYg4B*i3Jc7V1No*1Jdy_ldD&f~ z@x7k;m#(bTm-?(lwc|<`tTwqk>Ylo!+GtS}&tJZLS~Slg=FXRRiu~acqnCR;U$Lft zTfJ)4(o(gjyuQTWTxOm!uBayLSJ!Un5{&F$N@@c(xpVH?E#J7jg73=VT#3V5;&4?P z^br!Bgug6ZAg!Uiy9RQ(Z4717@wzg7$>P9DfZ+A1#Wjc{G|9B#CQD-B;Ic$XI9M*1 z)zskH1w<4s?`&*}&rD1P?z;2y5e1u$;3`DH3|1xIB7#c**EB*s5?~nRD&Y@AbO47= zK@?vZaTo=^&W}HgGG?q*6{ULLj5-pPFsNBpZ8UvFRD!S0l_aBL6x@e-_Kr5v(&;%` zHOYKI!zHcVoz*;F-PLXV(#XZ1bM}WstJV$uo&O#zLy~ptiUsRa$Tgi8g2STmL^xOPA#b%xcd>k%0$pp!`ZNiuiVoFgIAn~=R8_H%)snPT!U8%_psYK+C*jf}Z zMz|WnR+>-Y_uKj^^}0~3Iyht6q+)HugqiGtFCZ{?#SPo8;`y#eFX`XXA1a@lSh)A6 z(NLeyS?N|t)9>aQX#4|ev#qeuW-OE!>Qxr6wJ?3t*p1U8kr|uDCG#jhan`^bc9qFj zU~@Zby30z()i`^*?cIR~9DakNGjJx)mSbUAF#K`}gA7%D%x2xE*SmPHGWk2)66!^% zeB8@%ev{)zERXAe&ypH#;;3}I0gMze$Ka;0NbmYG&?w@U;8H6j=0xBGD9bNTO5C&7 zY@4{|nqb?MP(!(?rpOmTb?vKfodUxL zwbX05B>y4$RFpE^K;BP{g((&|m{Qnmozojr6%$d~P}7NvlY2UAmC6~$GA7aM8yxRS zj8PdQdRRCB_=;EozYXN%;my-jb9A}zWgg8ynp)25v*+( zr0eazI7s)uzG?(yP5L^?ilL9Czo7K0iMa`x4)G-^R6A+ zETE%7y7v4_WGBCJvBHa^mtw0Jg;$muzH*|t*bzKOydWb}64Uo77b8n7L9G*GM0CZh zq(K#0*o0#`oafEDJlrU+W|ZfW-aa%WY$eZ^-o*u3bmd}X>{wD1cA-Wxai~FhKRVf1 znJGU)+4o0JDm6X&{kVJLM9i%<^`M8!eq1k)*wA`rH}fcSn)w6`JqdF$`r|kOXbFzb z2z`;7|h^ zrGGcH^bSZSnu>I?owrxay0%~F9254mRXK-e_YR%iWus>I!_4H+!~p-a+6z1I=9zAo5wLU5xJ@rc-Y3sk(%xCt! ze|vV~3{UBLjr2p*&-;-p+wdc%c<3Wsj;QI;rPi0SrB(ZF#UhQvbQwkaZMcU1qEt2~ z0%2Pi*O4&ABE3tp^oG5GTZcue!zCniN7Qy8m-r*Kr;y6S`#^L5qBLq4-jljZD9IKXYqcc@S(b(3wi|$~J#z}OgD3&oH;ea;!okusQl`xCV1FL|^r>N7sQdn0 z++^TOuggwlMcxb>YsQqQHuG9f>-?^_GqV!@kT=<*-tNoB zSIz6|sv#}?gzpJI8@hC8C-TLGkMjjGT%ks9QIiw2Sq5fa%zd*S+D&8gnv7n%=C`5x z`jFA<&}toCnC{H+p{0_DtOI?G-HZ>Fp;gSPSc_9#a%V*#;0gQhL_1;L<+*c-ZG(+F zXk%>_M(t6$Dx^F*vvR!6#wf2ZEoBVXI~>&k59!rJJNl3f!iP0_%1RU=KqW819769S zC`bH0dbXK1iSSU~A>Cf+rGc4meD!QyKo7?U0RSE!aE3zo@i^Ok-F-_h@9Mf@&)hk? zu72`5+#mR_>u*l~c5rZz9kXP~g5}Z!-#((8w&2cd=FbmGbc+`3JGSXEC2sDz|Gnb$ zKMHrVo73kw9k*Htu)qEF+%-C((Rw{><@<*I!TvXF5iKN%CuZ<#O%yXwY)3ppkcz^KU7_i8e80+2q0Y75bw#KVP={c^P>aBl3zL3|EsldKm5O0>4mQ( z1<95CgD;XV!_M>p`=bsAld(xP+uQavbR}z!v7Iryk?xeMrDd9zR9Pz$sGRdon-C9+ zbsN|*Vt9#LhcpRpD8K~;qv;YFy@lCu=P)FmMxltcM&X2+;oR{rTYm4fqF7hV6`sDO zH#BYP1ZTKQqvZ7B4*w8ajAKSrlnfyk~%^ppOX?P7>S$h z9!ey8{DCm8t5`g-arV?i$KdS5MB;XfWw!&=o1yJdNGyZ6+Ya3+9M*hZ8ZN{A9PKXN zDTVkq`EYSAl}kS1pP5SBQRn4rm*3`6QufPCDei^2qi8zw;=NQZT6;V6_AvMLGc8X! zQxRh-&_XC`>7*2$Jf2dsJzj6=@1GtF&=x^J&{6Z22n$jpHkxp>>QyF}i`IWA1V}^_ z^*`2PbjpF_(nxS-h%ApP+RW#*PYdlfqe3S$`ym+XeP&sS)TX>(SVGN@ames!}O& zfrO(nRylX_D0=3SN&0R_Pfy2|>b|C8cVl1m`9Pb=G_K??feM>kVXN@pQa7#9wQ<#_ zC-Z_6z~h-vjU(F8XdIAZMm60hL6X3+xm^rfAw^wuARY|1+hp~X2@QY;wh^|5 z<0!&mfwzYNC&~p1Df+TP=1iB7QI*O&ZoXI#8dA(~>n)L)v0zGN_Sk-t%iY{Z4-}Nv z)Y(RQ&7gr#b@z^Sv+dvhHtkaL*;2^?Q)S4>3*XPt5X67DM>2V6i!6q2juV;Zo`KGv z%5)B$cuHsO>FjL9ot`HiS897?&*0Y+m9Mv!qN87xF4}Sj1v8*5cp_D^aG|cwGubs0Gt_k3DN|$OkvzhPCkIRq)ko<;27$X zMnALSRGx{6BHlp)c0P|-CyBTx(X04vu0b2 z&a3(B+^xajx;Ejot2ST>`P`El!riS6F1^8F(WChsJ|y2|>;1ll)=DqjpVFFAlP^-F z57);6_2a{avHpSbDz&4u$X%zaY$%VGE0>yVHr#Y>BbL@jlG#JO(u0t>p_rA~uI1f) zgzw|m^Sk*+`P2L-Xhq8Zh@H%E#9SV5oPuWybeizdvSrePxP}Bfc^*&LEBzay3U$ne zC$-YuQcik|?R%;srn2-@)Z*J%fzz#xXgjpLHfSX~b)us*Om@Mz$4;FEwd zU^N9J!HCNAV|F4u%rbmdF;4Ucb`cafc0#NoBZg`18Dv#pCzHTaez*bU2y!BCq8cgs z+co47IsL9wUlgzvReFp@e}ga3QRxVUR&VVza+`ULr}dKCFI`gAcAv*;^^gx~v&w2z z8@*LUwhEugTG3fvHO1MwXmfMr;)T=N{c|-v8^wVhLW9>qgOl*uqKE(`pE9Sas|zDg z<mbuF!WqoDq%XXJN zT6VhZlQLyl9k}vwo@bQgYfB4b1G_$6QQ={ZgFJf11M+wYc`|k>{YoeVg2A1`Elww4 zHjzqg5|GH0V`fV>Pz7aeM8qwJWVBZy3TI_4*|#=U zwqP!ZST(mK+F`u(Qe*2?;KnRRZU!x%2S?(%t)mQAK={QhRk-C5d7pf}d^c`mJuUx4 zu8f(Q!*Qy_hYorFuceC(v7 z1vWdpB+!@?J)zd|C`4Lpkl*9}>Ddha4jRz46xv?-|gTPNm z6YTCOh$|eG-L>8+6B=I&`%1#$5?@$4S!Qy2jN@la?+L^rMI6WVj<1~7=9fq%t9+%U zK5rQis=`*#>}ly;pqZQLK)i#U_7opeso~)rW2b?&irht!qQ0W_MZ1e0EjnHFNs+Rs z@HiGkx++d$(9-n`Io%oGlh}zFCqEy1RK*=Eb-BvQT&~jJ+sj)#o|drP9wuMQ?UM7P z#Gd>y_qP}Y855Co4t-1J25SG#r>u+(^QuK1!{b^bzJlBuCf3m0!^AxP9(XI|I5tme zJ*>*3AP1;X3(3)^<(u7*AB(k*YENrF(aOYMuGdO-XMQ8mqFsHDwlcvKIPuR92{ic! zCVwO&HxRrO#rPsD5+6izBiIzij`5;k&dO;TyBw)6vslel*gP@f|A@7-wY)6gZg*I^ z+`+DD=ae3Gkwp`1pByySYGoFiP0?9z_EuV@I*HnDGCK;DD-{ZTNtLs?F+KK29Od6Y(K zr4L9?OFu!oX#BW&qQ>-? zh&_PD8*37k_pfyTi7~mk?|b)qZuigZHM7^OS+l0knT_cU4*`Fl9s{V|PE3a}Y3J+P zHokQI!#*=_NoL&C4I@XqGc_)5>N_JwZkQTZ6_7bKw(pe8fPl;?ePgF)1{|{J9&Be9 z*xkaSd!U_NaCZx_9^qqSTf@`)eWwl!3>-GKZ`*4K-L-3LXuOTOQ%G*PFY}Q0=Jea| zP}bV+6MIMM3ALtfB)IL#yQ88#>bb$IXlOd#K)J$IZqEQdNU{Y&7zQy zqBnDM-Yg1*Olgp5F=UDm5`;mKCc-kqI64SUP#R(7L>Z>h3TfHB)-9~ow0n2Ym>R>? z@|qq!0v+-5r2&C$GE=pDmnvllPvXOcPMUEDrK4@l6w6I*t0f&WfEJvCNt@h1BO{`4!N8RHh2f6DF-~C# z9v!=Ghox5D5&Q$hZt5t{gxOg^{<*IX&whJiRPPCI=cLtS^)#H>b?)f?6H+|flP3;{ z%89gd`?zBlP9nU9LH+Bp3plSkTbS}5zC0Ke>Ea#`8XyS>koS&HrU|CxVd+A=7#W`w ze=+`cyevN6RTErmgtFN?(#o~gIwN|($dMiqO8(JUUSl}+&Q+*4Zw6d z{!omUqcCa96CgCvCe|By_6xQ5b+W*x2Q9k?JE{kUs;qk#4}20hu(-GNl9X6+ZS25K zCaSJZ{?jzhmbON6MQ1YuY}Ih^v9|VcG|h+&%ZhOkyIVLqbhB`9kT`{mO7bzWhyA;& zOzflbqMux#?G>8DY>o8vqaJYy32~yGjh~BoL_k1edWOT%Vt z0w5D6&BH-o3w*lL(_w$mQ{z?OBMoRx3}EBXcWd^*+;NCno1(oX;{McGLp2_WWzhkN z0V-WxRbZljba8^4`+%~Tgm_D@2*;4vSNnxJMR;21>Ko}wt-N5lk=|A~Q_W80O+Wa> zS)t0v@|D9gmY4Q$4Csk-K?59nlq}0gTT~WrSZ(pz)UZ*BUgKuY9Oso-8155?Pv>G^ zfVrhtPZugYq9AI2HH-sGJ5!C6+j2{mE}I9-&K0I(&Nf`U8h$TavOAzY;EMpMS3r2I z%q@7I7#*qVBJaFRNH7F>Kh_Hsfv|TA*vjs>Ilu8zzJH zEo(vA&KpMERG;Wz>qTq*j%~s1XGW6NC4HRw7WBz1rX6ff5nfhOrP8@)s!wQ+$~Y`` zfU|2(-bklmuMAfYTykdm;_6PiOLX;ohP{*-R#7;lm$P$^WG}P8&|nK=7khJ2qh0<) zCWyHKnNcpoQ=KEdEX})zIO(~VxcBn5Rw&F3YIIGEl=_DH0V8V$PI!0X0BcoNjJd|x zz|*={ZvS9213Qo40fo`w8KE{ho$%!Y=YZb!o%sYx)|1MU+0;ooN=cQ=iEsI?I`| z>n}6Q=tFHk8|Ox%{-F7fKj(yYLHwU&XKN4A+=Qmr+qz_JKT4w>n&hZBwe;_@=bvS> zdtzB%H6~S@Q-|+t&(0^sC=pb&yrt6=QdMT}zt1q2f0X%yYDOp7c@LnXTl{$T(>#{o zq(hF5%AOLvp|!FJ_{C0wFmjKuEP74wvVOEur-zlge*K)}riQgn`^DaZ74o^Wyw=;^ z)KqB|i(h2)rhL@%^RwzcTd5pi{Q`bzP2~%B+pb`0L2wp2)nOi>p%Kqft7;NC`rLpK$JiyaEE-O$K-KU48o_%*8AGO&&a~C^HrCo94h-HPr zVZ|$Rl3p$dxAu#6cfp_&E1&U!j1aJNk4cLK<#hVClkX86TPR*te)(hIQq}61KO3O zKeZ1JWv{ii50fK(nVyq9Jb4o4h zHL1i3n?`(-#$lI0Z!pIeA8>dj$S%n=m-f#AiULy)!~P>vijXqCHbv`#yJN zc5L@0k>>8rhK5#h$7aFY$XsHs^f8xeg0wd8W@XfB^%_HWb4QuA5@wIHXEHN=lp0@| zS{-h6(>gnxjGpHEXmPK##-OP`?)RYM&Z?)11GUB|9v*I3rn56sE;O)o^^I_{(D=AJ+bRvsjr3$! z)*bBP;u+`=+}F*}%D7`E=uaj;H*a-i4;#+_3)lV;&KAxd&K7>@FFFfqQonv$i8ETD0Iw{kd7uVp?yuV8m!$(v5P1wQf0Lizdq7&>0Eo! zUbGin45bSBY=w(LGFz&UD%>n=X@`>$?{FgfHTEM5#&HcDH#%VwQTlH-JKe6Wo%e6{ zJN^BhClTMid8GJ7I!_1@vLe0I8ecyJCXPD!;Ob@=BCYia>=`Ht40Ojwk=<(z6@E$| z8$TZ#A3v#S2pW^!f)M6>)3miR?24Io9}8@QGWLV1FmV{RY-*<^TMxLw;xuN@=v*PH z)>_+o?H(>|7nm1AOoB{Icq3ep;up&qx&8B1FHR|)(It4$#N^TQl2tB)=NAT+dwG=w z#taQGGxJU9(XSvbz+!Fb?0F8^J$pnNNQ|bw&^z$=hY#;Z&l?+)IAiUg;p?WvT3fGz zd1O@f46n!t9J;K2^pm3xpF9#}Ww~)ZaYkyskRcl?OT<^T46$R!4s#7YLJ;z`FV-N_ zaoT5rP%2EIuX%{3jt&9-e!i})e@$Bdc5VIJ=CAY2a~r+w@3a0o=32gO{?5p|W zQ-Hg(sc|MtHw#r@U)RGixpsDgHFvw>5Jx-fZebpdK|UT9oej;5@hM}vWwoM* zwY8_($x7qYRppAWNI07tIC%Kkcmz8b2H3@OewZ&oqdriK@=8K&u#-%oKdYzF5UjVU87(8h={F@Ss%; zwB;D(b*wS8EjiXPWUcEMJ)|AlX~mNOX=hU_V-x#sojY5)SXgVE%yf*p8U-f@J9N`jeT-3*N#G?V?5^FKS@o{&_0GqDD;XqDD;XqDE}%q6YQ`Vo@Xh4~rUC(du4pUDUuy{zm6L z{!bP)-j(Z!?lv~=K2FveXS1-Nk(ohZGKrOQS3NOPs%z{JHl&w+Cli|?nKqVwT?`Ch zYX)6pO;Eg@3(h3$Zt5E37@B4|aG>SDP%{r*r=ID4syNc4bBv{%pW%Q3hCU zad*djzJWEZDN(5yfGIcwY&Yv1Ywkq$YHX+w7bv#k)AmGDV9k@3+FYUVI29F?lRI7qRltl_N=iu&E zJwv_L+F4uJ+gn)M$*uIf>_YnV39$`uF&8CbM2J&wUt5_>mS$;dYiX%cVO`@O^tNA~ z0=>1aYb4RSh7qr8kaazOT_cv)HDdpRb&dHd?XsV0x2ZjSZ@|G6vv~MxZz?4`=EnMVns7TqH+{LW zsfohh!`RBnM0QQ8G&M3dQ|ct^C^}m@o4fhxNexWV>36eK%4LZXsYEn0>}-TDSfZES zcAmH)&V$U_9^*#GSNUNe?^f#`9X-&owx5xiA%1~}V}HW%-5&a)Pb7WOCpl863B=1KUC!NT%o+S-`D@)&{h=k)^U9M?8pDv8LKA$y|YoJ zI&B9_{hyv}%HNC=c8E!`A0#>gJ~bem#4Tc_B3lsU^U+EwJEdZ;>7bTRfN3pwz6vG|N?vR#waEG?+ z0fx8a0wb^<=_I7H?~s-+;Lc!oX3KYQzXA!JAVWTIcS}#;9(M0#_x_fnpuEp~t}>rS zb~oW&XCbI%EifFqaz+@!<1IUY^^A?6xL}=L0(5CP1$1X5nOy{bcIPq{Fit?;xCo2c z{cg)=aBpSzHpW9tIm7O=jOT!^f+4iziXSczI<@==B&o0l+j1Hhfwx?RY^LWhE`nxU zg-slOGgG!OzTa{m^yAQ;E956U!Sqv%SHZ^(not70kQZ(kb;*FCkl77-(FJC+Zw})G z>=btsHnV#R;|a!7!0wPaA85?z)G`@vFX*Z}_Cj3+1_=hhsFqzo+C9@9@>7@`#tD$D zyRfLGKX4Os*v#}TjK^DOdqX|r38tK4ybR9n(9CDR5O!xW?q_TQdO@lcKx0NH)>{0^uljpyp0N+nK(f zV>}M~@fPYC&#>=V#&e8UnST>P`k)5T039Hq4{R?C=mI`%zr=gd7s~+H2QEFeS|ao)>+1LjGr_A^Ne3GQr+SsG%}wi@bQH; zt_2!1IzfBBu*Um9D&06Y64v62bX^Bh8TUmQp)fg&n>fs7c5h*%((Q}V-5*#F%k+iS zQTS7gRJwf;`*n==8Z$bzWW()_cl{9ibYLiQ#}Ba|14cn(ez3h=z`l&JjPZ=g zjH$3UKgdRWau^HwtgK-8A}*T`Q0+cv5e)66^vDklNcv6PGOwNxai4Gh^vnI zY~oaG=6JU-Zs#z^nO^^73qqb?`YE74>)fB~Qh(H?S3x0d`?I$Fq3!!{leYa?+y1O= zf38dY1=6;^K-%_4TR!C1v_ zO=6tPIE8U4<6`Epm2n&6cBa42{Le7uEaN%GMo1Wl@^uI}1ZfQ9b~X_8%vreiGo9MU zKzj9Geq%c41zTVL0=v~vdkdl z)NjCWv`Rs+Pol)Mcmew|#xlmW)PWMu?xdDPxCb&OGbM%Hsq7xa?lhKUFuSSV2tv9@ zlA$bfCcB4mDzZ6b4r4A$Sio4wl8j~?!&t;vj6OyXehp4?mbO>{%Q)V#jOC0KjFqgD z@i_M`2sLmia3af7#lDjmCo@iAoXR+j!%SzK!8nug6W2{617R{#QrMl!?m_IP9&(5!8jgz2ti(F04H)>RZO46IGJ$@<5b3J z9A-M>491y^l-D89VH9vN%T~v+uWlI!3iUHXkk_Qi&76uYj9WS6HpcDDpZcF6$b|}E zJyT9_$Wx4GIOJKzbBxsE3gJ;hDDrC_$|v7!yM-@)T0hXD?lMn0mFD)6Na%) z2;9^Pgu#Y?0=hGLVI4dSHcS+M#vpv}ISjoNq7*Pr;I|gDdlU27%I_M zVai#?bHH%qND|N(rz>`sE6grgi0CNm|4-Kp#z#O^fo%)?QRC=B%w!nubKjx>G(cLB#+$Z?Hk9K%=y zeTECgs4>D(bCM*b%(IMfEMqxi1tXQ9aFig5rHUz&7$-AMVVuf1jl)c5oWVGgaS>|i zaFm_Tfpr|?YLu67wuEq$9nvB7F~VU3gj+e}HpcDDnQFywlpS4QJtOrx!eI%7XE@|p z#&e8RcEVA1CZo^lg&d4v%}1amJq`B+?39aO3yk1aH3F7KVVdCEgGcDm$fK(`9U%^V zjA*n|*MT%WH4rO{KtUV58fZiPkDdL3jOrL-+#KnQjt3WD)aoqcg6Lw)XAWqoL;rFn6 zFT1G>#zFJjfc1<`%=sp`#Y0ynj zNV0Ggn8KEm!u(U1e+u(YVg4!1KZW_HF#i~5J0+{3t+k@%-D|5Ru@ zALqRo!tw_}+tY!@Eonf~_8`{wAZYt4+@$S6Jf0nd+;fGSMzw>4q?Sj(fs8c59fW=_ zVH)NQ2En4jfJ2yD2IEksWU_l0^PxN%gghdoaqb|2#<_z88s`q;vF#wt`I4K)wu1y3 z+YS!C1wJd20CL+)3aR#;J@{Qqmza(WyiX zhD99$DuF||bsoZHb_jC+J5XZKf)9aIw}G+jj%Q3}B&qOSYz{e^aSUS-V=-e1V=0F( zV;sv^&RD@%#W;y^GUF7+sf=~Zfm+xhkeuY9xv3%C_sD?E>rh|NTula~{SDX`^;HI> zC5&fEGGi)JO4(h;?y-#Jj1`Pk>^q5ZGUF7+sf;vxlK~khmgA`XG9be)oCOyHJq%?n z4duKW%6T^w?H$ocOGA-sgp_MTIoEJrAG;~nhQiVaDc6Q_t_|f}8wyJ!3guTO=3;Gt zN=BNY%0#MX0v#YjCS3J#oL2x#*-d)Lggk_#hfG*L zVHM*f#>tFR7^gCB;&`{S`!H(#Olad3U_Ed+%Jpf?0Me{%Hrk6~AkDyJV+Lj$kY-}D z(RSntIUFX3!{l(79G^JgPG;XJ z>^p^hr?Br7_MO7MQ`vVa`%Y!wsq8zIeHTG;17R^szL+In%#tr=$rrQatC+(o=CF!6 ztYQwUn8Rx5XN9mDdOiSbgnKRAb;1UoC*Od1?B#INJoX0Ez!5;2$KJqm>F*-;WZ_-J z`x!8l-4TqL$j5gDYEj-5n&95ZVKyRPK7*TP&o*+r8#%^J;I9!jG5<~APi~s4-^_Ws z8SfUuP5Hi=^K>)k>1NK;EgWVGhuOkmws4p&9A*oL*~^pQ;Rdu3w7@`4G?xE%+YhwuhzK!`$|;RC~aU!qcd856iZPx$S|TQ$Zow_OfhyS+>34 zoB;}@bT9MV3;S6PcMNi5FLU0@a_(h0(I4WFsgPkW_|F3_LSJ+*OS>2OmR!|z{6~ThuH#;BbFV)ad0aJ(tP-Fgx3Ir zAJ-Dp{61RGA+W{nQ^~|jv+@`}F zinr>)4eKJ{76Bwa^~|RpQbhkSyw5g(deZ{T&e2A*AS;Mw&Cfo9hmP{ULJX?DE< zmQQY)U2nihhxpL!dIRz>3P>vm4LrNvAkgf3gFv(E4Fb)sHwZMl-XPHIdIPMBWTV;j z28@z+372`6@-oj-UWS}kL7`d7%RHXCj8d=zZknaM%(IlVI+P49G;48{M`2fa6n2%z zAXoXVt2_$3%A>FwD5ukf8z`rQv?_OlbKwSZ;WXSdhkOHLUZT^g+zph(JwWR1+`w3p zD6}efgJ+U&K%RMU(;UJL%pnk+R^@JRJ~kq*B%u*;MF6QhHXv}CSiTWupC~kzZ$u41ZW_xs zqCS}nq_KP>kL4SAEZ@ju`9>bgH=@T#anV@55#tC#8p}8GSiX_R@{Q=95KkJ*H==(+ zNMrd%^iK$BEZ@ju`9>bgH=^%EbQ;SyqVGgVWBEp$ElHBlSiVu9v3#RIWBEqtxhIgu z@{K%}Z^W#Q72Grr)(HKR4rwgk$Yc3N9?LhvGATTbsqR)=1%LEZ@krUn7s@o47C6#C^FYuF0E_Yco;nQD3f!>)R%-Z=1NjZQ{OM6W6*J zIiP-P;##+f`*Ka(murHqh)#XEChp5MabK>9`*Kat57DVF*983#QeUnKDW!O+FV_US z)dx~vu8I3{&AbNG%xgf+Y*EdyrTaoNY-u`>YVT%P@-ZN-0X0MNT|im`YKG*5vn>_x#$>ZOf=&jLPG;{JBu5Q?6Aqd_QZ`y0Y z|9pX3w+JinZFNhqn>oDIEfvi16=iK0S%)xkG~b$2ISTq#B?LL4Rp2gn84jdx0LCo9m1QzZKOMs>E<2i-QYIR zUBq-N4x`nNO@}ac9o$YFUaL1Rb|0j7bvwwc+kwA+2fBW%{2Nr**d3@!D9kA@ttic} zRP`w>FDot2sw^xm@mBRJDpDmE7L2N_P$lP8 zRZ^H&;r*0<#kjJvqQbmfReotnrMGHG={Qw!R+Va8MIN|-Gm%x5rK+6rysXMRPgQPV zMOjf+m8UAJBv(~dUI^bD1j)nStO`|GUU_k0WhK~USFsdDg*ka8#0wr3s?zc{M?S^i z`Da;{4Fm;thuEV zN{ULeIz|fZWocEFS5C<(MfL#4RhEsbRORMTGD*Xu@`}p-s8JY8NmXlb$`E7+^fRh3 zyAT@orb$Yn99`Zlp$M*Qp-SwGN@TQ9g3Vne9sE*+N`xsvM=%TFbEPY!+ocDkhjG6r z-7npQ!KDh7m>P>Ifp8@Vg&5qIl5rPcT9~Ni&yCj$yq@=F8s02N3|cu< zSQ__+{DIF6t3oJ}DGPTc-qOZSS}Vs#ja8gR@)-r5&rf{;LY6|#vi8t9crL~>X|)jV zRUrOV zxpE; zSt`U?4nK;C-qOaI*ZQ2rIZy^3WUqw?L;OfOSS_$k`y_P^d`jBo(!N3YM;1sSJ5usN z(WZeUA!~ig>v`HC8A}oGIK)!P(zjWuHeM|kis#uBP|8%uC(=_6P#e#)IwRXASt)c5 zbTkfeY3={dx+e`4aq8U=9=~=)NSbcb>g9PkqLtxU$^NNspK_->EJ4XrK^tVdR4OP{ zZRPrTDbS|rd8rNqpEetylxWjJ`9vjza*aw_VQX4*L7BiQF9n?};CU(4+8&kIr&dMz z-ev`*{8k%f9G=RcYqC7j_jpc?)~?Bhsbm)+L`QDV=CHOdE8+a;$i1zEXmeU?KV$>h zxV1Knqg>(ozbMabWskO{sXEl|RKFLtKR4j1W6ht3C7Gx7JJ;}k@JE_twla;ZpRBJe z0vOWnOPoren?&Tqc+L%KHF`nM<WKimN#ddp&j7zKBM#&SYsnk;%DNU1pE4_uEk66nBW%06+vWc>nWvgZT zWuMBf$lsA4ke^ei6#j~M#cIVK#m9;(iibJ|I@LP0I-7JYb-i_?bTf6w>%OF0r@K%0 zW8F)-zjZR`gq|tlmUPPP^bV(4RQhsCWy)Z^yZY6gw+IUIHwZL1gmcl6Hp5AR2#RQk z@cegM>)(B?e~+~OJ=yyAOzYo^t$%N8|KiK6{JxO`G$mraSi~x@h~H39PAfo@K=;TM zG%;f92#BasG{}Ki2^0kj+%C8&G!!Xz1xmz7u@BzV=Bt3GNTde2z`2bxP>|c&e3o-Q zcLu+waSvv0I-p5#+Du#AQSDSI##24ov z#4W(uNQP=yjfg5n#B6>WRf^(OBrL&m2JRK@bWxZCH^scXohA_?QQWi|B>q~yQtWbe z$Bd*_gUC&$w`ovB8ps`i`2SjK6rwF$N4HiBk)Y@)uC@DaVKdrB$mfEr$%~OPf~Ggf^rUvw%^;L_CwVOlzf+ zRFq>Z_3Y}aFYewHicOpw*NOHlh zoj@_b9okN%`plbXTz|kHsi4p@(VQ;fJ}4LQhJL$`h`xu&^~W#mG>LFSl;LRy?jPEz zQsEq~TX?4W<77Ks6zI)axT((3#wQWh@tb57&FxevYU_pk=F|FvqUV)4U4-7f_} z=hHvjqRzJh-4dOPpr1#WO}LlfUV*y~_d3w8;ckZeBXsSGa8JW?JnkgW4&gqI`;6eF zldp(WMCvR=|F%}=4V~3Gs}-S7FP(RYqO(P37q0zuDb~@YbA&JQTaCUlUpgP^e5e?$ zn2$?~GKCSY-xO}Ry!q0G?7*Mm#udX?e+6C1d<|C6HJq+@Coi5C6h2rU@l}e^?P&mMXp0*$UWSyvYbh(#HGu`rk zo0Hk-CsE#EpCj@p8+z~Zhg+hs6|}b#=%_aow-FAtpH2zxNw_Oo1Ev1?x^hQ9F( zUxMrs?i;vo;lA7I#$UWA%0|i7BDTM_HLCqE?#8rPHBcYzBzkp{a&U=!=8nNM0O|pj zYE!ElQV~spau2DsZoDfGMZH5AFk?As1w7+~I1cpf8j4htssclwFH!oev%J2CKhmR z#d$4-P>#Gpt>nZAfDJ1dbs6mlr8wyqxRMc;jxdgl+Zkao@coK$IichSrt4CS(xdDS zWjAacl#PVq2kh2Lf>WpP7RnSj+Y+|)e}aBkOB$i*OBA`AP87ntB+Ug5W;{UHqQ~KD z2yqjKF9;>q2&HQnLzupu#%mIkT4>;&co*+ZBBas;{Fy_>5>F9jinK(&B5DhSEamXQ zjCzbUgfeUPWxK*29|}2=5v2p}e8ylxSrp?-jKdgNHk`->Ux`)&%uV_V`z|3=2&^|1 zr?-@`g~N{|OL{Vi+>bvbgrkvM<%J?GVe8vle zEtTxfV0Rv)B~yAZu4l?Z#sbDPrr%?CCF^GaV?JXZ;gk7{7Z@{`zL2qiDeD znZx)g<7mdkgyKBHC##v#pWR2;y^b-Bd2-sNW~4nqcw){7TS5pW%9{F5^Oocz=HwKY zq0fejjh;}dXzze2zC!0DcxvbOP&-K2FN$aWct#Gdioy)&mHxie`YzfO)JDXW`tTA_ z2ff#hbr6k1bV2WfQcm@Y7l*Tky9@J2<;mfxpMt8aLyPik?S~Un!H0UBPvbG?zRe&p zO-vUDi$lZ=%<@v~T5e#7u-boa_#??JDxq8?x2!_z_Jq4rD&}_S_UxZkSpow7K~{bj z@7rv_vnf*rn|11Ho7IX=?lrMB4?2rFlDcY}S3z2UI!&Xe?xc`=7)T|SazUM?PVay{4ixLvwll0Tp-I;OG>)_$T7>y>n)j#a#U5(=4$NexsXCj|-YBiy zuB`a)jgi`bh&m=Z$$7cOr6suS+mb$oWwclxl@hIrPDu>a_y_m#>f_%# z#LHJ57^rbpJ8Dy8{oE9#X!j7Pt3`*78Wz!_Rf~oK=t@a7Ih%_{+~(dhcbWQa+b(%Q=D9KOR?47Hdq`_a4y~DX zt8skpOApMyNZO`z&tvVWSj+LwW7g0A*i3ONEJ^3H%by=pb-7`1VQ|CScN~9CaNf6K zfc*06Pj`;=UHIFRz5chBo4B5s(m1^2(BtZLpH!Rt{H4XgJx0X`Dy&9yGR^C@cJs!@ z13jhg_A$#%3|{!fJLu?P^}p;Z=dQ1fk;}jN^5SFVJ>!Z)+r*5l2gkBz?H|9r?4rx6 z!()qU2mUMl=H~m7z`iH?rzzfClQ7eA>6hE(dq3U3`DgFR)~~#Lb;N6S)1102G27;E zw9{~@M2d2>rdsR-jmXtDP^pc9%v@%g+c@A(xce`&I*qJUn8oOC8UO2@P_`i(N127% ze1@r`|HE&RW6G2_BYz+N`%aH-#{za5s#EEGJ6VFdpE|CtZ(VdvpHY>SWuZPkIpsy( z#ci8nys;U@r)*3iQGHr>eN_0gXCv7qnnVc_4WBy|INXo_0jE` zmFvDaE4%fe>z&_^jIhyr@AL4psqcQ773Q+G-?7NYZ>WEs`>^`g-D1HBOTTfF1)b*{ zJk=!TFTXrJJlZC+^UlF(_A{>x*Zp-y!hy~~i9uCIFIfi6|JC*7?ANcJFL4>uCpM{e z%iWMy7u|pPz~{=}fBU31^nu3C>civn3#`Y_ja{+pg7w5xb9yfCa{pd;L(^|wH7nBG z4tZ89?g=t%6; zZVMVOwTHGGJGC2tD!H^2JExF$h53a!*m|VuHLh}0X?bB~71dJeAho~RPvaZlrw&0a zh2ys9(N9f}|9{ozzpH58T2-|D$M0enyH6S8-L2u^rOWlN4RlP}{OJXYL?^>rXV;(Y zzqwMaGH%lOB6X=*+!Cwai?=M#RJ(jDjJYxSVDnrZ!v_Yk<+ta4YBTpHxm#fE z`DpU>IW~>g6W6Rd>X`D;E5Aj5*6Gaft!K9NmaYDEUD2xrU%7oBld`Sm%vHA-Z`VyV z0|q7QUy*wLK6>Fob;*l&hN$2CZQ7U1cHXdGHtpd#lRLV5Q;L&!MK63SR*3JLZ|vGV z|D9!5K3B|)U;XQ>^~QZoJ5|3m>*k<|Ps9~ANxHKIBX!Kpy+1g{ynoy)^{uV86MJbU zeEj;4VK2P8Doe84rt|hk4_@CPe(KOKwdGg&u@6*wZ57Q%R5a_=hE(uOM6pFCS7So9 zr9;j0+=_+T2pdD049#eb+DOr&2`yT zXOlphpBCK6{^Enb&NWy?HSc!@PASrQZb`a2Co0GDuk|$mc4W= zQyLX`;ip}jCw%v5)xZRCyLaW-p~dyTg-MZ@ z(%;`Yc!hO?jaakk{fWQKJJTE{-nw*Xfl_|*mDo$SQ_L<5*tq!0^;brJG2`g9CBG3&Ecj4Q}udJKA%!< ze@p$*`xEmrhl*X5rUvJ}HC=u${Js3aJH33a|2n(oEv?-+j`TJFe-?f;O z-Tmj)uXp!XOt8#86=7R!SAARWgy;L8_1Ss#*XF6aFRy*4GGJd~{aD8?F5~sWlV2X2 z5!1)?z|Ni95(-Yf)w^XzmHmvBX6pPKy}JyzJh{@r{!E{rJbrrrZtTaN=fC!y(ci^A z)@ejWW7^N_et7N8k3vfi&UCF*7~dLifB5z4qpqoYwvUdOyJ~#au98(I>khx$_jZ@k z$1nL7?RfIzz>_aKp2|P?rp=38awQR7TZb&zcg6nd?rk6C?3$P=Ki|tcY15KzZ%^E~ zv#xfWneY$P zc5F56kLsB({8t2greoXMLzn(V5B=YERA2S}_UmgsmVaYqm^&ijoxusFVGkzFdK7M~ z3H^EQ2k~CL-i{r1q07pjH|D$fe=*g4(B%FJFa2`lUpqceHk-F+r|!OaNqQHi|311? z-S-8u+A0Xdfgh~S+`i&Zr%UV=?5)N&eOkIaOA?Wl23PZ-E+b^YQ57((`H_~rg_i&n_)f9 zj=FW|lh`vgo?E^*us?d_rDM0OUL9g`|HH5u8!Q?rICw+Zm+pC*b zS-pII+B%~oqeBfRt#f~S(fjw~ee=za%$gjyb;SPJQAH9%uZ!CZtXB7MZfifLLsOHr zJyB=siMCaKQfd3}t-r3=p*Csn?{*Cea@bw`&PU@UOaHoa{M^24X;}sLk73;~YZx``nap_vftNbS zOC8LuK~|dr&>9Ro7W4y|>-@tu)qeiP+DqSj%&%HG1c)IS|>WgLbzBh60Cr{t? zlH+5se0iGM@U2ba>szDUoIF(bHmSqZ4`hy`4l8UE)yfFTiiz886zdZsR(021Vu8sjUvG zh0KCgNgleZE(G80obr8jdD7CG-#3{|7`LLNioLUBRzZWI1X_iF5bZ!BkRLT_di;NHwW=k5p16?E#DxSC7btTZ zZ~syme?jS~F#|FUPW{ad}Q+{gTsk@W69MX;qTZvXUS`nDH>#(dc2YrWgS zAH1g6b~5A((Nxp>^SPa?a$lUbWTaEk)|GK@{4{F#xgTFo*`f42w)LA09$O}L+WJ-P zkdH=M%74lqf5SJ~rHjw?jk-yn?Tp$x?Cay+(s3I{-8o%+Cp2@F`Q4cP7lU&*mE;CY zTvwN4==FKzs}C>#pwsz_%&NEJx?k^nsLo`x-9%13O z=VMp5VCUr_@qwRB*}iy-wd48?`OR5&qZ{1f-W)OKlJl_7?fdmeKE6A>r<3&d*-1lv zzHq!;Hq3Bf%!Hi}g@yy0B-JCnJ7Tu;pw;<7{jP_sGW^Lg?tsOdKmyRx*IT>G4>vuh_`+5_%b?c`V z%((Va_QVl8d}e)>{zm5E32tuxx>4$e;m78)iq%J>IE*{rR{1_*5=^Q1Wo1 zYOtrtu#rnw^cdhb>)UN}y8f^-@ow$512J_)%ga?Z=`sN5@5qFLDIJyUsNL*x1rnzN2` z{nZ&1z)nUpssrj0>iX5h{qq42iZ9A7#v~ZNep)P5s;eYvq4-qyIGu}fAbsT13t z(b)C=yT;CkMo&Bw^f&w~)id8B;Z?Gk%hWTMsAn!}SCY3B-9&ZIw&*0Hx!>PKM{R3v zX-)-Zw+gZKxGJZt!h2L@u{yGyfkf?Z>#MTipRE)Kw9R`2cJk3AGi}GO!V_)I*Z%mT z7?aNYGY*x_GZiYgz6HP3yuJKlYL%t;=U-PAIJ~Y`Yuu2t__f}(Q_ofDFFcwz!rQaQ z!(-)Vi(hzhsOJskM`4HiZdh}t@VlJD4gqg387%&lQLy)F;cdE><%iV2Fd zitXdA3w)MA1^P2Q#G!}+!eWboA>p%a;AIBBvEQ&b$W?JHa!!yzyR!ubawV(8J zFXi^0zrXYG+F?gm&3Qe2Y+7P$^pW1DULQYHIU{z=9UNT#sGcK zr_W}X@^61prok+A(=k&6>CSXu; zG%9o*ep|K5WrovVmh9B3veJU`tg=y6J{=1p78PXGqA=i`^=oR2FE8A$?!4*L-Nr8l z)JJYOb$?^9t*ubf91TVc(`bzna75e zGXri-d;8wNyo~O1Rve1*`D}8ut6|EbzN5eN3K`wyXY=`^U+_F*aBtt~9Y@x@I%J4J zobHBXpO=;%-P|SX?qk*NE}F4b_xdEa z++9^IaYOe6-)L{kT!p4uVg`~io96ugx5?S(FT8cI$zf`Xr&3EbdhHK7A}q2!J#vj9 zFOmhT12n%s?^}&0~eBXP+V%;g5Kt;hw$M}dZR*j6R4nO+Iva>ab zZ%B_jO>PptJl^Z{i)E#A?`-JQGcUcDt88D`?FaUQx*z;vw(9XON$*AY77n&d(I1l) z@U70C8xb}^-yIJ(o-OmW9rInvqIqVE9;fWvw9Ina7@b*jS{^r_b?VxrT(!%^d+?m1 z7mvPtWy8_29$g>rxE5qN+Bs@M&PrRo?O#s6ad7S!&oMTme=6|*YH-ua2VY!2(RINc z_bST|0%xcGl+)*wkt}USk!kT}n+s{p>J + + Document{{documentType}}Release{{release}} + Project{{projectName}}Project code{{projectCode}} + Project period{{projectPeriod}} + + diff --git a/scripts/sdlc-delivery/assets/footer.html b/scripts/sdlc-delivery/assets/footer.html new file mode 100644 index 0000000..080ca4c --- /dev/null +++ b/scripts/sdlc-delivery/assets/footer.html @@ -0,0 +1,4 @@ +
+ ISO/IEC 29110-4-1:2018 + {{projectCode}} +
diff --git a/scripts/sdlc-delivery/assets/header.html b/scripts/sdlc-delivery/assets/header.html new file mode 100644 index 0000000..46b96ed --- /dev/null +++ b/scripts/sdlc-delivery/assets/header.html @@ -0,0 +1,8 @@ +
+ +
+

B.R.N. ENTERPRISE CO., LTD.

+

1011 Supalai Grand Tower, 7th Floor, Unit 6-7, Rama 3 Road, Chongnonsi, Yannawa, Bangkok 10120
Tel. (+66) 2687 0250   Fax. (+66) 2687 0255

+
+
{{documentTitle}}
+
diff --git a/scripts/sdlc-delivery/assets/preview.html b/scripts/sdlc-delivery/assets/preview.html new file mode 100644 index 0000000..caba88c --- /dev/null +++ b/scripts/sdlc-delivery/assets/preview.html @@ -0,0 +1,37 @@ + + + + + SDLC delivery theme preview + + + +
+
+ +
+

B.R.N. ENTERPRISE CO., LTD.

+

1011 Supalai Grand Tower, 7th Floor, Unit 6-7, Rama 3 Road, Chongnonsi, Yannawa, Bangkok 10120
Tel. (+66) 2687 0250   Fax. (+66) 2687 0255

+
+
Software Project Plan
+
+ + + + + + + +
DocumentSoftware Project PlanRelease05/01/26 V1.0 Final
ProjectBRN WMSProject code200-WMS-26-001-00
Project period05/01/26–24/08/26
+ +

Project overview

+

This preview demonstrates the shared A4 header, document-control table, headings, and standard data tables used by every generated delivery PDF.

+ + + + +
PhasePeriodPrimary output
Planning12/01/26–18/02/26Project Plan
+
+
ISO/IEC 29110-4-1:2018200-WMS-26-001-00 · 1 / 1
+ + diff --git a/scripts/sdlc-delivery/assets/sdlc-delivery.css b/scripts/sdlc-delivery/assets/sdlc-delivery.css new file mode 100644 index 0000000..521062c --- /dev/null +++ b/scripts/sdlc-delivery/assets/sdlc-delivery.css @@ -0,0 +1,130 @@ +@page { + size: A4; + margin: 18mm 16mm 28mm; +} + +:root { + --brn-blue: #0789c9; + --brn-red: #bd0e2d; + --brn-ink: #1f2933; + --brn-muted: #52606d; + --brn-line: #aab7c4; + --brn-pale-blue: #eaf6fc; +} + +* { box-sizing: border-box; } + +html, body { + color: var(--brn-ink); + font-family: "BRN Thai", Arial, sans-serif; + font-size: 10.5pt; + line-height: 1.45; +} + +body { margin: 0; } + +.delivery-document { width: 100%; } + +.document-header { + align-items: center; + border-bottom: 1.5pt solid var(--brn-blue); + display: flex; + gap: 9mm; + margin: 0 0 7mm; + min-height: 22mm; + padding: 0 0 3mm; +} + +.document-header__logo { + flex: 0 0 auto; + height: 16mm; + object-fit: contain; + width: 16mm; +} + +.document-header__company { flex: 1; min-width: 0; } + +.document-header__company-name { + font-size: 13pt; + font-weight: 700; + letter-spacing: .03em; + margin: 0; +} + +.document-header__address { + color: var(--brn-muted); + font-size: 7.5pt; + line-height: 1.35; + margin: 1mm 0 0; +} + +.document-header__title { + background: var(--brn-blue); + color: #fff; + font-size: 15pt; + font-weight: 500; + flex: 0 1 72mm; + overflow-wrap: anywhere; + min-width: 52mm; + padding: 4mm 6mm; + text-align: center; +} + +.document-control { + border-collapse: collapse; + margin: 0 0 7mm; + page-break-inside: avoid; + width: 100%; +} + +.document-control th, +.document-control td { + border: .5pt solid var(--brn-line); + padding: 2.2mm 3mm; + text-align: left; + vertical-align: top; +} + +.document-control th { + background: var(--brn-pale-blue); + font-weight: 700; + width: 26%; +} + +h1, h2, h3 { break-after: avoid; color: var(--brn-ink); } +h1 { font-size: 18pt; margin: 0 0 5mm; } +h2 { border-left: 4pt solid var(--brn-blue); font-size: 14pt; margin: 9mm 0 4mm; padding-left: 3mm; } +h3 { color: var(--brn-blue); font-size: 11.5pt; margin: 6mm 0 3mm; } + +p { margin: 0 0 3.5mm; } + +table:not(.document-control) { + border-collapse: collapse; + border: .75pt solid #718096; + font-size: 9pt; + margin: 0 0 5mm; + table-layout: fixed; + width: 100%; +} + +table:not(.document-control) th, +table:not(.document-control) td { + border: .5pt solid var(--brn-line); + overflow-wrap: anywhere; + padding: 2mm; + vertical-align: top; + word-break: normal; +} + +table:not(.document-control) th { background: var(--brn-pale-blue); font-weight: 700; } +thead { display: table-header-group; } +tr { break-inside: avoid; } + +.approval-block { break-inside: avoid; margin-top: 9mm; } +.approval-block__person { margin: 5mm 0; min-height: 21mm; } +.approval-block__line { border-bottom: .5pt solid var(--brn-ink); display: inline-block; min-width: 70mm; } +.approval-fields { + line-height: 1.62; + margin: 0 0 8mm; + padding: 2.5mm 0 4mm; +} diff --git a/scripts/sdlc-delivery/package-lock.json b/scripts/sdlc-delivery/package-lock.json new file mode 100644 index 0000000..a6cea6f --- /dev/null +++ b/scripts/sdlc-delivery/package-lock.json @@ -0,0 +1,54 @@ +{ + "name": "brn-wms-sdlc-delivery", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "brn-wms-sdlc-delivery", + "dependencies": { + "playwright": "1.50.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", + "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", + "dependencies": { + "playwright-core": "1.50.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", + "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/scripts/sdlc-delivery/package.json b/scripts/sdlc-delivery/package.json new file mode 100644 index 0000000..b426e48 --- /dev/null +++ b/scripts/sdlc-delivery/package.json @@ -0,0 +1,8 @@ +{ + "name": "brn-wms-sdlc-delivery", + "private": true, + "type": "module", + "dependencies": { + "playwright": "1.50.0" + } +} diff --git a/scripts/sdlc-delivery/render-sdlc.mjs b/scripts/sdlc-delivery/render-sdlc.mjs new file mode 100644 index 0000000..e1235eb --- /dev/null +++ b/scripts/sdlc-delivery/render-sdlc.mjs @@ -0,0 +1,175 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { chromium } from 'playwright'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '../..'); +const assets = path.join(here, 'assets'); + +const escapeHtml = (value) => value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); + +const renderInline = (value) => escapeHtml(value) + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/`([^`]+)`/g, '$1'); + +const applyTemplate = (template, values) => template.replace(/{{(\w+)}}/g, (_, key) => values[key] ?? ''); + +function isTableLine(line) { + return /^\|.*\|\s*$/.test(line.trim()); +} + +function tableCells(line) { + return line.trim().slice(1, -1).split('|').map((cell) => cell.trim()); +} + +function isSeparator(cells) { + return cells.every((cell) => /^:?-{3,}:?$/.test(cell)); +} + +function renderTable(lines, className = '') { + const rows = lines.map(tableCells); + const header = rows[0]; + const data = isSeparator(rows[1] ?? []) ? rows.slice(2) : rows.slice(1); + const cells = (tag, row) => row.map((cell) => `<${tag}>${renderInline(cell)}`).join(''); + return `${cells('th', header)}${data.map((row) => `${cells('td', row)}`).join('')}`; +} + +function renderParagraph(lines) { + return lines.map((line, index) => { + const trimmed = line.trimEnd(); + const hasHardBreak = /\s{2,}$/.test(line); + const isApprovalField = /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(trimmed); + const separator = hasHardBreak || isApprovalField ? '
' : (index < lines.length - 1 ? ' ' : ''); + return `${renderInline(trimmed)}${separator}`; + }).join(''); +} + +function markdownToHtml(markdown) { + const lines = markdown.replaceAll('\r\n', '\n').split('\n'); + const output = []; + let index = 0; + + while (index < lines.length) { + const line = lines[index]; + if (!line.trim()) { index += 1; continue; } + + if (isTableLine(line)) { + const table = []; + while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]); + output.push(renderTable(table)); + continue; + } + + const heading = line.match(/^(#{1,3})\s+(.+)$/); + if (heading) { + const level = heading[1].length; + output.push(`${renderInline(heading[2])}`); + index += 1; + continue; + } + + if (/^[-*]\s+/.test(line)) { + const items = []; + while (index < lines.length && /^[-*]\s+/.test(lines[index])) items.push(`
  • ${renderInline(lines[index++].replace(/^[-*]\s+/, ''))}
  • `); + output.push(`
      ${items.join('')}
    `); + continue; + } + + const paragraph = []; + while (index < lines.length && lines[index].trim() && !isTableLine(lines[index]) && !/^(#{1,3})\s+/.test(lines[index]) && !/^[-*]\s+/.test(lines[index])) paragraph.push(lines[index++]); + const approvalClass = paragraph.some((line) => /^(Name|Role|Signature|Date|Position|Company|Project roles|Decision):/.test(line.trimEnd())) ? ' class="approval-fields"' : ''; + output.push(`${renderParagraph(paragraph)}

    `); + } + + return output.join('\n'); +} + +function extractDocument(markdown) { + const titleMatch = markdown.match(/^#\s+(.+)\n+/m); + const title = titleMatch?.[1] ?? 'BRN WMS'; + const afterTitle = markdown.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0)); + const lines = afterTitle.split('\n'); + const metadata = {}; + let bodyStart = 0; + + for (let index = 0; index < lines.length; index += 1) { + if (!isTableLine(lines[index])) continue; + + const table = []; + while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]); + const candidate = {}; + const dataStart = isSeparator(tableCells(table[1] ?? '')) ? 2 : 1; + for (const row of table.slice(dataStart).map(tableCells)) { + if (row.length >= 2) candidate[row[0]] = row[1]; + } + + if (candidate.Document && candidate.Project) { + Object.assign(metadata, candidate); + bodyStart = index; + break; + } + + index -= 1; + } + + return { title, metadata, body: lines.slice(bodyStart).join('\n') }; +} + +async function main() { + const [source, destination] = process.argv.slice(2); + if (!source || !destination) throw new Error('Usage: render-sdlc.mjs SOURCE.md DESTINATION.pdf'); + + const [markdown, css, headerTemplate, footerTemplate, logo, thaiFont] = await Promise.all([ + fs.readFile(source, 'utf8'), + fs.readFile(path.join(assets, 'sdlc-delivery.css'), 'utf8'), + fs.readFile(path.join(assets, 'header.html'), 'utf8'), + fs.readFile(path.join(assets, 'footer.html'), 'utf8'), + fs.readFile(path.join(root, 'app/assets/images/logo.svg')), + fs.readFile(path.join(assets, 'LeelawadeeUI.ttf')) + ]); + const { title, metadata, body } = extractDocument(markdown); + const hasWideTable = markdown.split('\n').some((line) => isTableLine(line) && tableCells(line).length >= 8); + const isLandscape = metadata.Document === 'Work Schedule' || title === 'Work Schedule' || hasWideTable; + const values = { + logoPath: `data:image/svg+xml;base64,${logo.toString('base64')}`, + documentTitle: title, + documentType: metadata.Document ?? title, + release: metadata.Release ?? '', + projectName: metadata.Project ?? 'BRN WMS', + projectCode: metadata['Project code'] ?? '200-WMS-26-001-00', + projectPeriod: metadata['Project period'] ?? '05/01/26–24/08/26', + pageNumber: '', + totalPages: '' + }; + const control = await fs.readFile(path.join(assets, 'document-control.html'), 'utf8'); + const pdfFooter = applyTemplate(footerTemplate, values); + const fontFace = `@font-face { font-family: "BRN Thai"; src: url(data:font/ttf;base64,${thaiFont.toString('base64')}) format("truetype"); font-weight: 400 700; }`; + const pageLayout = isLandscape + ? '@page { size: A4 landscape; margin: 18mm 16mm 28mm; }' + : ''; + const html = `
    ${applyTemplate(headerTemplate, values)}${applyTemplate(control, values)}${markdownToHtml(body)}
    `; + + await fs.mkdir(path.dirname(destination), { recursive: true }); + const browser = await chromium.launch({ headless: true }); + try { + const page = await browser.newPage(); + await page.setContent(html, { waitUntil: 'networkidle' }); + await page.pdf({ + path: destination, + format: 'A4', + printBackground: true, + preferCSSPageSize: true, + displayHeaderFooter: true, + headerTemplate: '
    ', + footerTemplate: pdfFooter + }); + } finally { + await browser.close(); + } +} + +main().catch((error) => { console.error(error); process.exit(1); }); diff --git a/scripts/sdlc-delivery/verify-content.mjs b/scripts/sdlc-delivery/verify-content.mjs new file mode 100644 index 0000000..ae16976 --- /dev/null +++ b/scripts/sdlc-delivery/verify-content.mjs @@ -0,0 +1,91 @@ +import { execFile } from 'node:child_process'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); + +function isTableLine(line) { + return /^\|.*\|\s*$/.test(line.trim()); +} + +function tableCells(line) { + return line.trim().slice(1, -1).split('|').map((cell) => cell.trim()); +} + +function isSeparator(cells) { + return cells.every((cell) => /^:?-{3,}:?$/.test(cell)); +} + +function extractDocumentContent(markdown) { + const titleMatch = markdown.match(/^#\s+.+\n+/m); + const afterTitle = markdown.slice((titleMatch?.index ?? 0) + (titleMatch?.[0].length ?? 0)); + const lines = afterTitle.split('\n'); + + for (let index = 0; index < lines.length; index += 1) { + if (!isTableLine(lines[index])) continue; + const table = []; + while (index < lines.length && isTableLine(lines[index])) table.push(lines[index++]); + const dataStart = isSeparator(tableCells(table[1] ?? '')) ? 2 : 1; + const fields = Object.fromEntries(table.slice(dataStart).map(tableCells).filter((row) => row.length >= 2)); + if (fields.Document && fields.Project) return { body: lines.slice(index).join('\n'), fields }; + index -= 1; + } + + return { body: afterTitle, fields: {} }; +} + +function words(text) { + return new Set((text.normalize('NFC').toLocaleLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])); +} + +function compact(text) { + return text.normalize('NFC').toLocaleLowerCase().replace(/[^\p{L}\p{N}]/gu, ''); +} + +async function markdownFiles(directory) { + const entries = await fs.readdir(directory, { withFileTypes: true }); + const results = await Promise.all(entries.map(async (entry) => { + const target = path.join(directory, entry.name); + if (entry.isDirectory()) return markdownFiles(target); + return entry.isFile() && entry.name.endsWith('.md') ? [target] : []; + })); + return results.flat(); +} + +async function pdfText(pdf) { + const { stdout } = await execFileAsync('pdftotext', [pdf, '-'], { maxBuffer: 32 * 1024 * 1024 }); + return stdout; +} + +async function main() { + const [sourceRoot, deliveryRoot] = process.argv.slice(2); + if (!sourceRoot || !deliveryRoot) throw new Error('Usage: verify-content.mjs SOURCE_DIRECTORY DELIVERY_DIRECTORY'); + + const sources = (await markdownFiles(sourceRoot)).sort(); + const failures = []; + + for (const source of sources) { + const relative = path.relative(sourceRoot, source); + const pdf = path.join(deliveryRoot, relative.replace(/\.md$/, '.pdf')); + const [markdown, extracted] = await Promise.all([fs.readFile(source, 'utf8'), pdfText(pdf)]); + const { body, fields } = extractDocumentContent(markdown); + const controlText = ['Document', 'Project', 'Project code', 'Project period', 'Release'] + .map((field) => fields[field] ?? '') + .join('\n'); + const expected = words(`${body}\n${controlText}`); + const actual = compact(extracted); + const missing = [...expected].filter((word) => !actual.includes(word)); + if (missing.length) failures.push(`${relative}: missing ${missing.slice(0, 12).join(', ')}${missing.length > 12 ? ', …' : ''}`); + } + + if (failures.length) { + console.error(`Content coverage failed for ${failures.length}/${sources.length} PDFs:`); + failures.forEach((failure) => console.error(`- ${failure}`)); + process.exit(1); + } + + console.log(`Content coverage passed: ${sources.length}/${sources.length} PDFs.`); +} + +main().catch((error) => { console.error(error); process.exit(1); }); diff --git a/scripts/verify-sdlc-delivery.sh b/scripts/verify-sdlc-delivery.sh new file mode 100755 index 0000000..9c113a9 --- /dev/null +++ b/scripts/verify-sdlc-delivery.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +SOURCE_DIR="$ROOT_DIR/sdlc" +TOOL_DIR="$SCRIPT_DIR/sdlc-delivery" +PACKAGE_DIR="$(realpath -m "${1:-$ROOT_DIR/sdlc-delivery}")" + +[[ -d "$PACKAGE_DIR" ]] || { echo "Delivery package not found: $PACKAGE_DIR" >&2; exit 2; } + +expected="$(find "$SOURCE_DIR" -type f -name '*.md' | wc -l | tr -d ' ')" +produced="$(find "$PACKAGE_DIR" -type f -name '*.pdf' | wc -l | tr -d ' ')" +[[ "$expected" == "$produced" ]] || { echo "Expected $expected PDFs, found $produced." >&2; exit 1; } + +node "$TOOL_DIR/verify-content.mjs" "$SOURCE_DIR" "$PACKAGE_DIR" + +format_errors=0 +while IFS= read -r -d '' pdf; do + page_size="$(pdfinfo "$pdf" | awk -F': *' '/^Page size/ {print $2}')" + case "$page_size" in + '594.96 x 841.92 pts (A4)'|'841.92 x 594.96 pts (A4)') ;; + *) echo "Non-A4 PDF: $pdf ($page_size)" >&2; format_errors=$((format_errors + 1));; + esac +done < <(find "$PACKAGE_DIR" -type f -name '*.pdf' -print0 | sort -z) + +[[ "$format_errors" == 0 ]] || exit 1 +echo "Format verification passed: $produced PDFs use A4 portrait or landscape."