From be41b0ecd68cb014f77fa79b241ccf4d065aa415 Mon Sep 17 00:00:00 2001 From: YX Date: Thu, 14 May 2026 19:21:45 +0800 Subject: [PATCH 1/2] feat: add revenue usage reconciliation --- revenue-usage-reconciliation/README.md | 44 +++++ revenue-usage-reconciliation/docs/demo.gif | Bin 0 -> 54215 bytes .../docs/issue-20-requirement-map.md | 20 ++ revenue-usage-reconciliation/package.json | 13 ++ revenue-usage-reconciliation/scripts/demo.js | 69 +++++++ .../src/reconciliation.js | 185 ++++++++++++++++++ .../test/reconciliation.test.js | 91 +++++++++ 7 files changed, 422 insertions(+) create mode 100644 revenue-usage-reconciliation/README.md create mode 100644 revenue-usage-reconciliation/docs/demo.gif create mode 100644 revenue-usage-reconciliation/docs/issue-20-requirement-map.md create mode 100644 revenue-usage-reconciliation/package.json create mode 100644 revenue-usage-reconciliation/scripts/demo.js create mode 100644 revenue-usage-reconciliation/src/reconciliation.js create mode 100644 revenue-usage-reconciliation/test/reconciliation.test.js diff --git a/revenue-usage-reconciliation/README.md b/revenue-usage-reconciliation/README.md new file mode 100644 index 0000000..87a5cfa --- /dev/null +++ b/revenue-usage-reconciliation/README.md @@ -0,0 +1,44 @@ +# Revenue Usage Reconciliation + +This module adds a focused revenue-ops validation layer for SCIBASE revenue infrastructure. +It reconciles plan entitlements, compute usage, top-ups, invoice totals, and anonymized analytics licensing exports so reviewers can catch undercharges, overcharges, refund risk, and unsafe licensing exports before closing a billing cycle. + +It is intentionally dependency-free and can be run with stock Node.js. + +## What It Covers + +- Tiered plan quotas and included subscription charges. +- AI compute usage meters by capability. +- Top-up credit reconciliation before overage calculation. +- Invoice comparison against expected charges. +- Undercharge and overcharge anomaly detection. +- Anonymized licensing export safety checks. +- Revenue health reports with high-risk account lists and audit hashes. +- Entitlement regression matrix for month-over-month review. + +## Demo + +```bash +npm run demo +``` + +The demo prints a report with one clean lab account, one undercharged institutional account, and one unsafe licensing export. + +## Verification + +```bash +npm run check +npm test +npm run demo +``` + +## Files + +- `src/reconciliation.js` - core reconciliation, anomaly, health report, and regression logic. +- `test/reconciliation.test.js` - focused tests for clean billing, undercharges, licensing risk, aggregation, and regressions. +- `scripts/demo.js` - CLI demo with sample subscription, usage, invoice, and licensing data. +- `docs/issue-20-requirement-map.md` - mapping from issue requirements to implementation evidence. + +## AI-Assisted Disclosure + +This contribution was produced with AI assistance and manually verified before submission. diff --git a/revenue-usage-reconciliation/docs/demo.gif b/revenue-usage-reconciliation/docs/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..a18c06fd37fea9feb066e06cb4622deaea64c219 GIT binary patch literal 54215 zcmXV%WmMG9+s41Z1`X08vBWOjUCWYEN{0wY35ZCEfYfj4MnnWukZuJ;LVB0(QbNk5 zL!@ItU?2bIJm-97=GDBK`#Ljo%{6y*wdEBYvPejYV*!9bAl%&CT;C9`udlDJ{&Ds9 zAAkSx_ww@Z#s9d(UtHqx_;dWwo;-+}hdL z+Fsk(T3z2l^&XXy3qa@4!fR|8Q6DP-pKTwtE26)sN}y?da@l{|C0W4b$C-8LsR)Z|Uf6Y42=m z>-^S=ZD_{Ue`~L6Y^iN%sjX}J^7UJFT~pPU#!p`wE2`@&tLn->e=V*2Qc&5FUs7FA zRFPL)nNw8#v9Ka1zbw0;{6k*phmS??K9;=uSe%tz`1XB4dRFe6cll}m`iz|9%(7SM z*-2^d6JBM;rewq(u-KQO z(f(m^FG6Dcf+C*>ML!P=w?hwj1SEL+hkN>kdHA7?{o0@T20!%;arX&v_Y8RA6XNC- z=<4$im^ig@b{G zv%a~LzS$!KV;emaTV11v+J@HJ_a0~&SgPw=qIAquv`vs2MvDJXyRV?CFRN-GtD+~Z zq9>)SE2E?%`42^HF$HZ&c`Z?SEipMwF&T9cSq)(s4Pj|@L1{G+DHOk?D!+uPpg59G z9ElKD;TKbeizy>S6nR9HxP=vY1?9Pg6u5*GV1n{I0&<)JaxjDp8;=AFrzjJ<5W^io zdRBp3EPPx9iu+n-rW$HST9TsTBt!rJa9w}^&wr!vzXtqICIBe`U?*kMsma4elF|#> z^w;Ef#nSTVW$Sz?=t*Rea$o5GQrPzjrjfv=TU#{nhTpWpW}vosC{x^NFkAO)$;b!! z=Q|4nUrWbwQIX{AdUa(Jg*tBpZ3pYhzn2;n>gDLwS4>q}eRW?PtgoD@w(m+{*Khdr z^Q+tU3frNE&-0DmtAjcEja7>+fyXEUh3tmEeOc{G<Rh&Go-$YMh2X-fL<2Gym=R@1>EJ#{FNI zND9vTt=|q;``-vX8f|Sl-W)H~&%NK)eENI#>(gJOZ7t{fxUNJ3r(t{R#qrkn%12}E zZGX=XR)=y8JKC@R;*Wp-8tdpF5C8~24h>>*!-bO~%5V{svRk-FT5bO2CRBV@I`|oU*=EVqBl)l}2v0YYHQn6F;C)mw~dP5Rjp^_ zJF!7iJHKnXnV#-`=|?;jstF*g-2FPPE%c}Ehea^r>kEaae;OA2fB$L3c_#d^nTvV4 z*R)kqx!3HJa>uf1w^wMt^<=uGvc((wbie)T?Du|0!piTAc7WOA087fZP{q6!lz-4g zdso<}jrgn9K{t!*r^DWEi`MBLwpih#e%?2OH8!x!Pe+5ojl-pV?!~)D!_qSzU$ewZ z;m0G&=ex)K3WfE@W17s*>ac3LGA9!Ta)11qbwAXfd^fXvCR1meDRVky6Y%Hs^Mg0_ zr!&r(&l(1(azCHVdHlzEX64itidKSUprDcZe-o>Vf*QohsUZ&?|Z-JNA<@Tq>kKQ}hpZ6|z z>-H&vEox_~{_Yv&UR8CqLdCAIjZSa>9`y57dmLJ_S6>~E9}l*gkKXmVKCQj{D0VO# zuz!6t*YP4mm5{z4p2?j4=%zKHEVnmnjX;u8bU60P&E(|9M$-CNu zVx7$oA_59xF6DLjfEx`oXOb0ZsbuGWys7#WHo2_?kGQVfwwmbRv}K4=RwvtzcnHbr zQufMb7oDVc90%{*Ez7L#+meSd_d^`#b~Jl9Jq6$DWbnX#G}KRk7xU2$gybgGOrYy<_Mu_EKMMb6E#@glzl&|Pvz{jKGf(DFHjTX z0HR}iYmdzw7EcN$t)-=c6_E2}4IDkIeUnSMo?(@bl=piA{O13KL}7{{twb!VvwbRg z#-vj2WHAzPQ8EYeuO44KIWB4>lq>9O-?wO7EOc*`DFIeejG{|XF=AW zWFyKGeR`WG`j1#m0cW+)t}QyIAARJaL|k_mM*3Kq{jdrLkp){@M42c-lQhwjB0`|s zyKHjva#qH2Dsw^8nFneWvVKK&EAASF?uH3F^ngnbpY>xs(+{$*^T6ypIfHImT*Np^ zE0J8zQPNkK(Qy43`E3>Dief$cx2$9=-@)XGQ(f*)if`lv#H(1X#1jNccZP(ujj;|bv&d;^L!}Uwp0bM|5T!=uQ{By2H<(XH1~B9E9F7|@T? zU)^z!knGh?BeF z2l(_sB$18Ysj6)J8tJH7Xccafr8b;Qiq~kqa@wTwQCqPdVRiE9rT;}|Td_~3QuvhU zullc_2SlDpL~Oa#R}zSxiVXbaJef{w8)+*>c?sZlI0CUmx-VHePCO6<_@0h`*C{oGfm&#aI>Q^%Kw8G+2;Cu^8&%)OkBZ6Kh!c9_sZ@}R3dct z&ZcFb%88r2N_`0TK^j!My_m~pDT+5Ya=fLENFlZJPJ#KaF(QEK^g)h((t#U^;=^{y z)bf*3!?rQ=iuMMk9};k)WfzqW14_nSahBJb2f;{i0OsNs%CeS3)9!KC9* zBii%fuT#hODuh4JXn-*}>61d{@27;rOYzUDe{JV(RGT?n*Pa_+<-iG-^Im~s!~h{& zD{SwKrn?W`%lr~oZ27#_SsVu!&O=^e;Gx>as~8fS0%-p!%^r^2A7(vX1E0x5ij2df zamIY8V1oSvhlucEP+>?JQyfNO7+8K0wV!$AYDfF zWggNK17FBPzMFxU7Dq0X!WHl|R4fQSonS%va4Pd~CzEJP7R0@{mv(f~1aIeHDDYtR zK?XR+**Ea1o0VA^Qi&8$i9@q7+i{w{^onp?U$wUF1c}VTp|p_q_A%3tXFH}8c3_}@ za7y`*n`-B~=|j5Ub*Z~|&=H|9#DoPh=^Ol2fAIQqD(Nz$+_(RFN`1O}^>#E8)ZG%Y zPiau%-WRE10LJg24}P@ykiIS~84rJ_6a1mfBqlEIQWnuJkN8#=y3>_7+@pn^ebAMa zKZm6BC7<;r6j~*$YeWI50R$tm z!$>lQ3z~p)YZS}thmwK%{D_+}#7=+8&T5K-dYa$ni$LZyQ~5W13JB2$$u|TjAR~aF zyr7|O@}vEkd?fkO$VYkhlm=r2hKUoYU>*@@$(I zNXq@hyS*1Kg{Ce7ph7?fA=T5J`z;EFfCl))H)SQ00sobIg%9}eXkBq_Rr`!tgXh_8S&=@) zla8z|zyUZQz8z>XQ40U`!o=Iche{@AKI)a#TPip?;*p*)-OJRYCOF-+(eoxf$j{uT zP4HVa&IIu!TXoR)mthVyHVt54Ql^Bd4DJ$S;`YAyAwkdBRuB=LVEEU&48WCIP0@ra z7BZ%nG1QkeGFrW9hCgkljAOO>NDlZU7(OR2Zr~|a*C>8Q2PB}hDp<)+O5v869I9fn z^>4)B3S^VL<;_8q-JDS?ifl*YpFn`3+j&>#@b)<)tTnPLA;YWLma3TMKl=|?FF)H5 zJGFoiy5cc-X&T#_1Y1XFuB*CZJ$(4m=1*p&RZr#Pf+}6}z)0;XgmcxFV&VRK+cEcQ z2lGJ7yx`S5IHB2iY|s`_^toZ6Vk_;#ePb0MkK`?k>Uf;ymnPNQ@l3lkqM%d?>>ABw zDpe5-Y{yOf8lb*PrRhCDj#(mu#^F!$C@%XJppAkE z!J%4Fimy9TU$tfvmMY*60Ll^&_4@;26U=8-`PbD}ukF-9LaYcX9E4lQU<^fir^fMj z5nS4cYzC)&d_WmvZ*F_^RZXc*E4U5-Y9H&@A`)v|CmX!}-t*OOY}0JO`q8+4Xt-v5 zZ|&Hqu3xA97al>;82uaW0#KeUJuUol1p6dJ_p|wj?H1qL^bXq+C5eInO0{{K3va5A-k^<# z9dCHrtTo!sS~^JM+I92WukK)4Ry%ZvFvKC4Dh8|l=o65g$M)h*N2t-RcAWYZ(e)1#!;W6|1!TJ7{E z>9S$#UHIJ-NYd*V(z~hOi+1XDTj;$P*`t`>&?vs()8}76jjPD$f zUdHBr0tQ=$hZlxNuZG9jMkbXEG&#HU5((_Mpu+a*K9^NLPoc; zM|WCBcNa#-uLfcrho!=?YPiv}kTHDr*k$Y3)xy}#)fm7&POLJ1&IXF%9+Tw4o*Il( zE{;=QkJGYG(5Xx?*iJA#8-d*8CuOInY#nF5o`A7Wa;r@8+D;-uCzrA(&;|V|UHu*_ z6a4JoB~`vl6KucBhJKg79u~Zo6t-0mZ9|CXd{{wl$q_6#l3O;9KL(kbcUf*cGpw(?9+}a(-YZKfwfbj6I0G@(;kb{p4Zd7&!z(+ zrbY6ny+daLb7q3tW_E;U{MBcK9cDsQW}|FpWA4p{J(E)zQBci67+sIl(f&-kM-glL zGb8k8*1d`Ndwl3Nx`;m`Z`kMFk@UR)TxH{C75*PBDvKQce~d0{MP zajtFgF8iVnL2{AbcyT&paZO}23r(X@GtyQ=HJwe0KUmtork;Y4iFl8ykCShpsO!bS zBcF&f(Nw$kl#b)S2<$i#+{mC?a1C|@K6$w7LF2s z09_AREzTo>c|(>DsCe>*L(w#?7!tt)=&3ywEwJf(f73r@b25oy(}?0Mm1JZWN6Rr4 zLO|0P?5!oG%oyz!2o)FmTD9}x-LMyF+2?Ny+10BrWs7#9HfNe0qEc` zXDg0BSL*Q6xjHOCkWpu02&;9?|&K`UoX49!EJSO!*B4SsbT&ng?jt zkgpt2PvD?*HN*C3njGA)Hku|0GpvH9NzVgVVKnuaVH=|jkk6F0Rs#gkO;RA zUxz_QLnvo4!{KNeoxI_;aSB@_%3Tz7p*YEq&2l;LYj=bwMVx9M8RXB5$g zq#L*y9&97C+9MfjUzliLA4}R18l{}Sq3X+9`GPtgHP{=r0pn>Ywnr&f&@?CFARAg5 zp;2<0yy1)X{Sglmy!XM-Fa%#iPL3wQ17yRyc#?g5YR7OI=g4Fp6=Me}9<@3YayTkX z8CZR;0f5$OR^sx8^)SDOvWYhA&xO3!N?u(KQXZ;%@9eXa)W4#cTN+m0KSqh2tQgS< zR#ED!F5e?8?S_&IaZsIyosQ@Jc+i1(C`MlvXw{b}@`o zbDL&iF^lv{R;yY|rQ8!(&IfqiE*6{mK#)uCf=awLFedvN4D_*O*Y zY^4Y5GMc0EAB5fd#BH+pUM@$@#gY4TMkHkkRat(!M zQe@4JgIrk}ygw7~gnJ0)VoR0mrwJ(&k9+GYsiPlGAXaw6Xj0Zf;E?89aCQR*`S6eG zHIqBAs#X&~n1fmnlqW`79T*ANYXs*#L9g7>Yo{dNLj*&A&OcB|7L;oUx?5MSizX%8-b8 zt4TAQVP(5cOq_{ank)T+Lc1whI8w~Q}Z<=`Eb{^^0D_MIroclF&~v;`!Vwzkr7 zNoJ$3?^8d1rLFj+IMXG_%B4D)&f1+&TGH%3{wZ2<(1Yk$w!yP;%48u*c<}&cdhlhX zGQhf@g;Q^1ieh!QL8+e%xJc-I6v>Z;QbcC!#zsZrwT*}{;9q|+8SYqDzNkQKX;K1T z+YF3-VOoe^JnHsy=R(o{?ub!NHWg($F>DcWs!x8wulV!J$NA6&|GdcixD9X8vZF}@ z;*bYwJP1{_&PI*GpsSC`)!5j)4xuq%1LZTrD3DhRNuzYjBJh*mIgWcvEVbj|Vcrz|ZO z+2>LZ4O||#Ze>=P6P_ofkiVv~I81)79C^zIuM?lHMq*Rv#2SX-`Fp_K7%803xaJls zb&19(uQh`SKa;m z+m`VIZ*9=qYC>qeivwKg!Jx%-?R{krY5EqcQFn>zpipTc>2%p*lerGHRt5)8CO=?9%an5Za(G?c)Pi{FDlzQ!6p{8uJq_~dgkuV z2y5_s@XR?vs{IV?jRq^*x8s=IFBhw={topJ=w`7rG2%f~AtE5Yz;cQb$4H)52kHR% zMtXmXXpvn5N`1pF4sD+Vb7d=9IsI-P7nk_A0V|9%v%PXv?umsZw^{zP?0rxukrJ)! z%w1~PptDZ!c@_EBsSMoIZy4j7{;JrCzs+jEfL+urNBFU5z|SGs0^ir4Le@7VZa;ja z{rvsZLZP^r*nl&{E910zU1sLz=u^+Hua_uY&j(+Q2K*}p@M4P3b5J)H5I<{#up8K3P| zck7wvS~a)&%15EgR-CtIIw1Zv(JCH}%=5E-+Wxg~+dN$4=6{aG_}3MyJaf04pPT9R zZ>Vp3<{20L8KUOkBOGO$~2>F`x-NGoIYi{86S zN11&g8^<3`TP=StzlVlyX{iRu1+)+6@`di0cLWU?-fX{h4c&dL`nRejY^$a;bnit6 zAvkybX5*Vz^0#Q!kQL@%_`bVgM{heqHspR?j6u&^LaTjo`cI}Cdc)3E6Mp*}=B`d< zmYnsd-q3uV|4RiWI9C~loi(cV{YI*GeCK?3+Vbn>T+F3Pgr4wx?AIqe6ay?EeOEhd zk2@~uF(5P48W(C*stxf@6=Z`UD?&L>bPVugEE6&0YZ&UJ0*a_QD4Wm^DG3^BEIls= zZ3q*TK@I*B#Sn#MNw2&=gfR(LyWNdt1JN;dQ!;s|@iwXT(DJbJsV4 zq<+CpzFiE@C+s~Z^-dNM{_ak)^iEzg^|75gk>kpa!A^M)M&bGxh`)G@MuDb&x8^+tjw;$ao&7m>ab+ncXe%td9@Wh`kYnE6^<%P2#qXO~ zojQuLyNjh8TNdGsd%N(nXK^C&k#qMQUEXc9W*cd)t~4pNeava_VMwwiA`4oh2wuBI zks6zfPmBzQMd8`)a72v!u%~&YXtw@{BEFI@+C8wIc9d2W<}U1=4#9_4#8vnxoO0dA-En}du8`@x|#bX8b35<0+AW66&uR7851%?!A zOEdbJ4c8J)wjfMd1Cisv%dEIapj6ce-Irt#nKi771xswjc(RbQ(}KP1%k33NzrPH( zGJ-yp2T^*z8QC4q$LP1M>32wz3T;LEWyMIj^?w__{o{5t!BZ#JMVyt1tA_~<_OF3x zjswq>duX2w)f?REUgKm(zopfMWf2coQx2JB57nCi)GYse5B*|kVb-o1wZyvG z!Pmk`dOKjBRPt@3m`M3(v3N*AzJAN@Xrssd2ZssQYxlvp2*vl*7CNQ)Y{MPcaIhq4 z_7f7PKDFx>f z|Igy!;vW}N8&{@#wIv^KxqOdKAYvseg^tx|P9bJHE}AgF?kSycT@)T99vx|qcISuC z5{*}kH z#fe?zq15~)A%QXc@>6DSvxy#?AlKho2SU4zbz$B zndl}DRgfoAyJwCB$RK6W?r!wD=GrG{E`q*@LA?1ZT`ZLF?ar;7RJ#1w|IC5cxW1dL zJE70`!tUjmqDbo*GF8lpf>F0qS$fv2Qa0>gKW>}JOSRAogwofT-1=#rN@W!kYI!^V zwHN%8H>2g4(O8?frQbcvPV|_yLfpfA=0~ro?Ii}0<`gO;=1r}ZX|L%Ozr3N#w~nl_ z#*U{9DaR?5#de0zMv612aiuqDF{m%o$VMPqcP zx=mfrln^+J=~=unXyIJq&4(AGRp9ruJ1pe*7_+>0)zRf&&Czuu7J1mYt#Ruj@Qj45 zb*Z$eOn2Jz7wRvj202X7oaFEG1>O^0sw@W8zl+4&ro);R_GgBw#f4pvFHG5DYJgz* zocnF*CPpj~YNWB{b}?8xefJM1U!dp|%CVgDwIyKclVB z)8}41UV6B~{)>p?*NAD}z#B^w;OC)zVZUWm%^lKsf%ysh)-C#mDVFp!ySw>zU1V&z z!r5I|2L%&gd?3*-5Xvl1%1zGbuejXYbcZAFPUKdUCE4=qySK>rpHVD5^-n&GKbRao zwFSE+pyZ>yXdPy5InYhNBfeQAL~Z5#dTk6@5`~80d_wE!3+>D4G$}>xxh1)IO}LH4 z<6s2|M5j)XHC$|y`PpR7dr_RIce!eCd!`hgRwGD}HxaFPr0?zAwk$JSMi{XqEXV|Q z-xIc{Ldr0gM{SZ5#u&qO0En=Vb_o26)N#JZ&$YU~qiaE=YH#LqdyQ?5D>BacZWa+a zF5C(OU1#YzQpi1d!1;K}5iIZg$gZ6BS-h?2nzgSpjE-9wS-@+}E$c5-9J*TaVr~IX zJXnSkj=!tazF~I>r<;HW3>0s9XIiDDJQ#czz+t3VTL>Dl51J^<;ExEvrx_WeB{9$l z?KlIDqHU|9$HKiaOpghXu;Oi8Q8j<8lsk`H6K|;Yq+bHDe-^a`9dl3$bui)<0@&Ur zeQRt}xb0mi>~br_e_;tkmevb>_r9udZjdY;|5{ zHG8i7V=>)}kvpqa0@i-uDJW%{_ko4(* zV)H<3=)ia3fSUP`|F8E0%EMcCeZZXV1@{dLW9Y~X4q0Mr=zew4XCB^Ru4i=hf!6r2 zogb1G`pEAda`GJ!Q5;FJ9dTP8{h9V*GCYFE9vv$liH02UHy-W2_l18t652dkA3Wk* zI1*)kzIb^=U{c0N+&y0N#IO^RqzsScGOKws)n&!pUqt%I_Z?%>julFenI1JNjUA(0 zD^)g+k()luv3hEjev)z>$GqN}v3jUSC%SToy{CQ}uap8J+wX0j+&@1tgq|8PpBnR> znsCYm%>U6iR$X#BwFr2Tr2hi>@YJ&O)VlFSc;XBB52p_|Pwmc6_4`h238xNxXO8D0 zcU`@8srZW}ZDMJ>waAAxVGcK6Cag;Ph!nt;gn>H}m=Iu|J&T+~@AO z-%P9T{+H*L=K-++-A(~4&(8xJ&x1Q!UUGg3>N^iRKd<{9K(TuMA0IxVx*2U)6E263 zcEuNJ;0f0z_?S$5d~Q>$S9M$|K4~T}?Q0;X=~*)LB6VNIx%A)_^TnIH7n#5DTb=my zfQ!tr#&YN@Fi8y|~qLgSNSY1hALYVq-EiXH_(+HFCk&wByfrrE7mxehv6b8|JP$d2Jb_q6Ay6PS4=zg@}7b*~OwU=@AQ$0yW_4Fr28xHr|c-Iq7@zi|`s(+PWN@ zykfc2ljjkFE9-Q;$sp~s&`jFu_~c!5^LH@xruRk)zDez7NYUQ=ybK&{M13%3-m0V5 zMf%_59N`2?3MPiq3A-KSMZf?iNsp}quI@OBwm={IL+;)rRyp6@twWyvRBjcMbhjht zZWyU0b`G{wGID9u$1KW0J()WGqE`#xf;hE2Th8u}rT)dke`< zj=}woN~NswK5BSbAd&jjb-aLVWqM@35mo-lmL}>m4`ch*JOfTpuI6O>2Z@6G`!mry zCCsG7CXJ%BO72tXB2Lp+E&mIof_=}a?XE6|-aW9G-nYXto{5u$qs)FpQjn0J_zqKi zDRUa(Vxe{J7F=niPv)efU zHz!?*owym}@a2=tSE4@fWZ49fh_TzCXs%ar-1kcW+BNBT8t#sGQ^PldlxCU)MN{$u zfC@>~jgsjh)%@@bf|YRTNe@bh$8{|~qHKZ$#sy5Z3-i_1 z%$eOX?}d>Sr{A*>FcPzLQ83EC$02BxZhGI{s5Fhbn-m$wkHbo@56>h@4olx2LfLdn z!G&l@c`2nb;V(TO@dYXVBY`|$M-*F(QwHXG3XRp;>&VDu!A<52 zcouG2>Qjs$Ey;JBsSbq*t>Y`_Mu=7<)Rf=p6>a)TVG=#7@@p!j%B=nX6${JqumT&u z(U1}^sWcerw`)cvYvzr@Qbu1^;@U3u=BrV(`UYQm0?4CkI?2U%YY4;-$m;le0SW_g zs4$M6f|BlA9vRYxt*Go}8*cRo0~Ue}w;YZ>6zj$B3{`hXe)o_XRiv@4Ydzu6^y5CT zNcMwFltq#t@O6hDR3?lLv5XrT`2c7Nw;D$Ui(5XsvsWt_ffBsaBKIrw@3q{Qe!UOV zwPUPd^2rm_ADQa49_SA-v&)aqG+5fI8&Z;BOp!U#A%;e z^5;dUbizOkgXyhKoko-_hcR3=ez&7{KCP}QHnp7JwDgmYi>ChhR=WFRa2hMxqQ)~$ zPtEJ?*G@N9-4{l?T|yJpY{XLCK6gNF^{VX*6#ZY&4!BJ54sfYI11-akv zc;a+4jRmbU8Qv|rrQuxpGf)*y9RdUK$TMI?MtP$(*dS7*bUo(dyvaO+t5=-O|WYA)Kmvb(USZZnKVaWqy&g6yF9BqjzlNRSIevYg5eZhxJS&yT(B@m`(EEAAB= zWIWDPy@0rXRRc!u0}FcigId*`_OA33Ri%gId9{J5ECs1C> zj(P=pjEO#R-6fahp^8w>W3MF{WGCmQc3T)^15JDY(U^8e>u_+)N5t(8o20-cmI+x?iqru_XJ0|3Yh4Mf~S?^1daRk zEU7^m24=wR+NOKps5ke1@aQO&p4 z*j3mT{pux?CMlk@TcACkC8wK~9wY%qenJWOl6PkVow0^cVzYehTS??K0{36IYq^W+ zl}O9Pfzacz??NSl$ZK}gtn|n->CB0=;W`A1MslGq)X|@X;*?aZ-y-=y}Y!XF9MwTC4w;?aN zs5p(Nf@urDf;bYDxnjU|Jd#$TA4T&`CoZ4$P5fZsJS9ISGHAmdHW=5d1e0KRBj^NW znhobIpoh$LO2>A~i2A<$pd5ZB`wi3-Ion!+o>$~_P)b3 z>yq5~6G!sA>+WKVh0$)xpBj!E@whvLEUO;mk1vR-Th5G14{|^PzU==Vr;$(IqHu*Y zkS`dyo#i_cMgDrjFC!F7PAFTa8XJfmh?Pp}W~8;DWFWQg$%WzS>1}moU+U25KcggF z1!0nS?<165+$*;-<^eqmfCMd39Dr_uEt;a=eW-JWQM~InmwhVyz12nhdy}pPQYTmI z#FdO+gTvkhoW^gSB7B$c-q&|il^(?o5pnN^x!R>mq&^qiGOg?8yE%DiVlct@HR{cD zbAt5RL_{Rp_CxZm2#LAZREC(1Y?OG9+>!*3#mspzJ}OeSY5d+m5-ZY1dy)0e3Go>O6_tWMzgzAO4{GyRzWb-=Ltrr^qjC<4H#ai(gDDALgw8`PK z%@XyO5B4Ze^6A^QoQxF^5B83VpMy5C(l3Ue+@#U#qB$m5BEZ_1nCIzErH))!%9O(t zb7qQNN4}Pc4(^k>uWf%kf00dcmHU-^zvjLl%lo77{xQSj3f%?DM1_LYxHj<3Gq3%@ zIhtyG&mEOL_Uz!r?XYv|4T9}Hm)R{S>9UOP!HTYPd9++ykI{bQBA=hc4~kK6gH9<`4 zApbmQnEkmqEXf82aY=>TrywoKBd@|;#^=>WJtC7_B%209YxAgMbzC8UgW z)RcD1Clg>giyzD-`K(p>H#rU(R$RAN;{HYWoWJC=FL7~ju(OL=uu=bFYhGsB$cIs{ zu*NJ~CpmD+S}?hDamg0&AXiWQGWN7@Z;F{VO^T3cHD ziWo5uoQ#JG!6U6*D1Dzt#-dQhjz`v|P}Y-2&c9GDj7L7EP(Fo6A)`>?BadQ9p<)$} zQbVCqJCAZNp-|bP5FWuP1z8oDE=2C}sGb$7-teFxMJQ@sHKrmp4qkO8Ubdyxp>-#9 zB(J7+k>-6~EsG*8J6>&PEjc@Xan@H))L1Dpyx}#16dO^)jbn=L?>TF?Ivb0^O=XKsk#MuoHDm7L z#ZzZ9JGg~Qv4tnxQhm+Lq?m;Nv1JPUK}PX|kB?2m;N}T%>xSZo?Qol3xK;(+q_)^* z32wJhY`3@mJCxgMkn0ho#GV@Az~u7irg(X_*g+KGBwONi!)v=&# zROU)NZxG%PKFcG7)Mbe`2cIv()rgKyoVC;!$>*nCsw?R#qFCx@$LH@-YIzBN+8h1C zoi8w^G;oLKMb%0`3SUr3X%I1IfWK?mgY}@^(vZQNdBl2MC4h1q;}aqKiOk$Gc-Xn+OaG)%+2Ax8=F6W zTt->^QgK`wSNuo*#0IwqC0mSDWr?Hw$=dKF(fp+8vXs3tqop#M4gOb<@>IRz7*E0$ z1iP7vC{G(Ke$@+06%|O=e&WJZ{(3Yz-A*9musli;dJ@Oy>jCvfsY7y+CPVZx&l>FP8bl#N9?>~ z6};lAC_q+ZO77snf<+b;#e+GyH&4v%1WWw|;~!U)#K4P#1j{ojRAL0fQYy-;+{^VU zP80+ydn;m_D-N&~pOyrpCf#>`3Ra!%pm*HY4lAmuE5nGNE|Lj-LHrJ4tz0Xws6|%3 zldPmv6sog$nxR*@Yg}3HDU|M5Im9K@81vLOsFE|GvPoO0%w6!?N1>J#L{kJ|uDPMI zwSA+-Vx@KIY1;Jf_#UD5vr5+;AtCq54r-4GVvj2_VJt%UKh{qof}grF_`7a6JCVXY zC9d5oPCYIjRVBZ3k1Bh^JZ$Vd_#=b|M1}iS%KI`t4W)(I_gkp)BAO*w?>S)5c7P(fY-uB;{c#vO>i6_Q|^SfZp`=o(X}GxFiKxK z`s{o88B)I@U?ZvlBL2QYBy5G!Q10Xh0f3n(&`*3Us>#Qou8~g6W<|^;-F+k*bu0?n zDFL(nixI`4rbkhdtr&F>S~#7Ec(;>LIEoZq<$fcoXVZBgYj(&g=F448MqH(l6akmU zXu!~-m~f3E6uFV;cXuxh=gzYol;jrXHmD0M9z`s5pixwsI6#Y%OjqNh?Gp4L0`Oi8 zd|gYd5;T9kHFSIqt`sUVqp-iGO;j2mtbA^4Wd)D`#@-|nvv&_a;8Rj;oy$b zAs7@Pf{%bli2{KD45Kb?5HThwL9ZmiK!Rc4JmmQGdA{@Wf;JJ5s)rh>7;b#0P&yGK^B3wzK6HE}%+fvryTHG9D^Yy-sk(x!uj8x zu&+*1ry&NSYCKjd_0u51@H%%=c%W1sVt|kc_%{q>EnMsy8bx2HR{h6d=H#AgEurC= zwwK@C|D*vxoag^7v=?feej01iJaK3WzX)~0a}A+GK^z8bpe|0U4k0Cy@UAjDt}|>w zi7IV~>YItlbyI})hP34QaWN_4yT2Y&}Kz}c_VMJs3FBPDYK=$p6{O#15ZB4x}P_&EubXr zB{YQ9Bx^8N8N{5kXX-X6Pp3wIDb0C=46j_b&mF8O)O+5yF@OXGpc?~M&*-EnkoU7M_{3plR{laPVlxF%=1A|h@>;%4{B$d_dAMUC1VyLq_ zg;@THPEC^G-$d*q!@y`7%yvS9u8|so^pBEMHzcH!JMNppSG>ASCr7u$CNYXxhv3h> zAo`p2-wJ!D=6ZAU`}|WNod%I6pTZRNg$7CE5*>z%w@dH@yXKE)oK@p)C0?lgKhEwv z8Vdj6`~S?CVa8y_zB9J$Ldu%PzV8Z!Mkum`WC@uWV~l-l*%>8!vSo=hwxX0ZTavA! zvZk_Bb5EbX*L8jGbN#OSckcTf$6wAF$6@fE_w(@rDyR{TyW+BK6myzGRewela0ZC` z>#0nt(w}k^)xgLT9a+*0T!Wg$cf%!~ZOk8|<|cGV7<`j>qgi5uFL|KKrA82#2N@s% zs$4yV(tVk22l^?va`{uj0PLL;V5GK&r+&mGa}7ghJ=I~rFJpB8A5JW~LDfW^r~f!W z8Sj;Tt>(ea+n*67N>axLXtuBIDr+#00m%$sl8y#PYLCXI(0A~s*g!#BjC89Sj6`8 zHXT4$^l*4Hz5n9{0J!%~0J5pFd%)u3RHYgJqunhZ>At3;8@bws$EY|Z)b)$f2Gg86 zJrdXo20$1uaM}VqN86v%CJ0-e;6*X0`E7H^wCOSch!^$Vyz87g-v90dfmP$-t?(b= zTexR_QA-n4gYq_swt%Vf0RV*eBG^muKc4?`PNqsY;|&(I4bZ)^i)e^4PRZvKfZU@r z*!%GAlf&jGdISpj(8!{}P$(eF%YoyI zOiqe9);h(sJTH5g8DLr&mFWB>0x|qpr`lETj$Q6#-S;k(!GE%CSz;n7F z)OVuNE^8{f16X9!P0F!1pim&1mG~Zg!@HDH8?d(&r~8*2VK~5C3N&BMg`IlfIdW{@ zEaJwJ*7--<+*C_2<4Mp z;^8jy&uw(6zu`gXwn>fnybI6{EK1XBM~YKHtC0cA$p^cfqoM?&Qh!awRa4kMGGG|= zLwDYDh(ix)6f%!k4-or%_^u!*bsW`$DNc?Wuv>*TseksL95WGhvl=s#UBng4b1r|<2HJ%XD98<6kkvF z^2Iy8KI_72HSXXc^)T56KlYx?ap~pRX}8FY*G|q30cU4C1Y^EUduCXjoAu5Qn3^?@ zlR7u&YjxuLtY72Hb8iAVHqK4^RI@s{zN-5+HGh4~%IWQmS#Fm3;3X+~yT5*adLe4( z<@9Ly$_6d`b>ufz=S3V$X=V|>|INw61gJZ+lpvPoyzIv5KC`STSmL~rs<}DisV3j; z>@}(SduBDu&f4WY*(7k*I>#f;r_wwDX^;Wj!DHmYtL@_;kNxbMA9bQ}_8V<4wQkzBG>+Ykg^)6}kVVWhw1K zVA^!xn?R@LshgYh6WhYKUFw||g1Ugm=ePUVZCrN-_^-|HJQGWI-5rvBFu(gkWyJOC zOU{ay4&|z-v@8MzX=|3`!RTS>+O$sIE4HD zQsVK2{gn(Gn)|`~{A&vbAIs9+57(<7EF6Ar9C1I|=-65~+Uh}E{JAr9{N2y5V>TCm zeVe`Z?$?i{^ozd_)*rn4eY7)jk@o9g>m7{-X#Q5}J z)?_?)%OXT2PmeW_94EZD$fU=o&z?pmNU1Hs?DF)v>&fwop-XTNJ_G(&Wa7z|CD!0P z1K~|_!s)#w1dh*8j4gv?q_)hSk!L8Sl#yr^y3AR|XC!N#k#x3YnY%I1NHH)Y*==u` zw};PIB`qVxTWy7ZEYJ94eMV|P=!)PHpNZzHjI=5}eB+4t1m%i86e zpRLc#xgYvo-h&oewSPA7VvzZ`op zJUTQy_U}s1e-b@C14BLigVg^)>7fig?dtCR1NA)a`3v>5J$mx6qr0X3apS{Bbfu@h zsqOxQhjgN+`VZ05bg!nNysG|A#eKTZLn*JJ`#eQuRdk$(&hiu#m**9i-!8n9olnWk zqrd%;dEyF7$vH)-betz6KZTr!P0JxAXVHBgVsaLp=!s9vz>&yUV)|ub(k(&?UFo@n zPm02)M&S}8V@ct$NfC6SCnn(!(GwLP8i@~%j0+COT1DOBhzLT21^!if{vmo|=s3^S zzzCmfkyipE={S#<-wp4p*FF7i(0v}d%!9ccJ( z$CQrqSh*Nl{Fl#j+Q{y-fz7`YJtzM+pT|U5^M50H{`x#hYI<~^NAAD*Jkm=^ZUJc(OES=Tvl zFF#3P|A*d@Gy}-kT`iYU_BO~;_AIfKZLY8)iRdv8M}94{Fi1w$+s!r~dwGZ5t(#zK zi|Hl6bEk&&K2spce8Tn*lfFreKfvT%y??k>Q&x(Z&@!xT-FQ_eyd?gMe{a#YI&iw_ zto*4<4skZz0+(i*1@xP*J?2pxK3TuqMK<`(J=pI5;%-Lr=1bS?O3&f~->F~Wxz~G! zqSbkS3OXw<73Q!fr@ZBESs$$OeMhHvDnEAIiRU<(sui&F8e)LfVRQIl`jL3+M_A#} z_TJ|iBl9gYCk^-cnkT9>(Y$EQ^9)dFppS(ALU+(@ORmXDEgticJ%%?-qc)TPfOC5j z+nF8c2__&S5jV;srgP_|?$;T6u(=AU=~Yy=O!#dJARGO67osejeDg~OQz`8D`yZ3H z0FJmM^~1faboKi3#w9PASwNo16-;xwc3lM7J^?IXO7)Vr46)MvHQa0|%C`mqFa+&i zdZqgV?J&sbm$;8e+<46UE>%{l!z;_i*yT;CChHYwhNwtlzL+oI`iSlfS1;A)-a&bG zq5bd3xza$6!?)f8&S*UB*>Z|0{lmGvU@KMVQF zI})?y&Iyk&hmkckQP1N}v565z_|*k$$>q4SHC(OMQvCq|O5d)SYN%PgsD7x%k7d@k z_HalFPhWNQ! zzsoZ?hKYvJC-l4dVpR3bV|gCLD#2dLjzQZU6<*=Kxipjk!+{SgJ*W$=qu0W9g8 zha2KMu-`LYLV}AX;}v@j(>ia3&X7QZ(NzZWUK2FgdxFVC=TX=!u_n+drTgE28;pa z08Rbc3u(5ZvMyMJK4lQy!}9?p2{i}V#vxCbD%gtYAEaP2bDNG>HNY}1^cP0wqqc6W z@ZDj4`N3n`&f206^C*6bl*#r?D>g zHdAd;CkSqxry_2%X${yDksfL+O>76y6r7^o!kIdBMU1c)To$*acbSyQD$>ubE*^JE zErZBPS%pJ8o`PJL2VxB`#lWf!%(beJg4yh1J_;m(*};4LqZU#D9LxCL_7s&0L!|R{ z7#m~NDWOKl$=m&w#}eX(c5KOB#CD39>P$MS!iyKG*?WoYdOHV4y~>qfSBohRWDIyw z4I9J^2dk=LG$c8okpv4l2OYrZu4rrjJ*6BUvVF;AUTeGe8v+af9+7g<7a9cK+vj9j z(JG>Cp+j0%`2e{9keC8GQ4U~GZ*gb!A``C9z>8v_<35osqxb%P8ii&}ASX*E`VU@v znm8O%vs%PtCJh+eO+WEdk{=d|0Z*UHv@*YUd|=0`PLA(+*W0VY=5`SMq6YKYmR;2c zEtVEun)fo*vU8ywMQ9Ej@^nQ%KNO~CvGKGz7X-LWqrCT>@lwp$uYdUZ5RLRo*k#KlzvNDwmsl!mEAn|iZMxpvp4fhDF|@i zbQf_ft~IFS0NA zT+DMuAeObV3R_EWi}ZB>89`snV;nu}EEW&aA9A#zt047smS12KxXypwysCE_?=y{%f7eDuc6`=t~=G*Dk*ekyhw(4l^H zRgZdzY!tZT@7QPTY4UsuzYXaA!71Zv&$7V%V;^UG3o&)m2j#n(S9dGN)X4M%2tr$(!kQTcji)bbsiCef#e3%H@_ z+JMhPvr?SpQlca1WpAwklHr7()S<4&ughY4ZIw6grC{?N&7Co6 z_Ea2LMvpzc~1HF0nuldtIKWIK_5DV3D^Ap7LLtQk1MYd2+OdN}_azAvLoGxGv zm}ll!w_DWMyE8HWK_mSgf(B&fRsg_ebS2D@%4`IM6)1BAPNv_;PhrdX{?%tCZyI=2-Pw!NXHoq&Lt|C(7+z#iCwjPK zfs%?|{Cx&>>!8(5^c)5?cO;>9ORq?^26v&5D>?vf@=!r zf!q4uwP(F>@4Ai?@^JzO!q^YcfTPe)TAStJRaeLm+$IRMgLEw-U&n+aC!LTo8fqf@ zoHO`SbaBQDD`P=6+}K5Y-46Nabn^_(SE)xSOo9-cqn|KIKax9j+LY_cUG)R3ZFQI( z$7<~L-Vp*z!$)JNI}exs_@Goukj^ZH;vcTW30ij1@p1BBAJ+j zurefzrEn{zkT7UMh!0E#_ebnlp&))!5OcQ7XBV&9 z#Un2aHkN&D-@JtiX(fU!+MRQK_=PCKDlk5 z-ya{r)|f<|NxI$y=?;=!qQYd+5O!|8r7`rJ43WplZ`MPofhX zX`B0L+kEM}8tHTG=)H*a{k-(U_VhA4;?Mnb06!TdoBmsa%os_A=9BY*Y0R@^wgWPf zKZ8RvgUcm@=kNXdjtrsM43UEj6o00;W~QV|=CR03nfy$-j!cEwOr?WNW&SKx%`7#S ztW%L$8npZ@t&S|6*(}|IEHoXn(abh<$u^G6HqFmA@5r{C&9*+sw&l;U*UWKr$vGF9 z6|A=$35)dkspfb5h-_LEsb z!SMH#!e$uzfGjc=i!em8cQ&x90}HPQv3>zqtwRg@hqB7^i!s3k)k6if>je@X#m8v4 z!iV|1tNDc=&59l$6wmGgHl0!#S?%GP8{Xf8!OpOBBMOCXJ9-)GBC^)T*ZBz8>io*c?A zrZ5NqKyr`nn0ip4SBp#2fSZbQMhZRY2i zWe+>x?=UQEVsN-}8Q14xK0j#HqXNf<`fs1=*#L!93d;%&1KmS|Wj{ZVhc`ih&21g*IGm-X}K)Ef(*iAArgl9Xsz6$xU=Xl{vq7^?bp{0)WyX z5`kf*+X=@wA=9U0Q_;?HW0+#k#(UXx08X`0C-{n zl@0>oDW&J-yXr1HZhS+=T&T|;y5r^d0MrQHG%vKoB7DGYd*ln8T9ADJ^UXrmmi(ey zop8j@5)R8AQ8Z#101HNYiZ{~l+cU;5GOu9~4wMc-1*GtoUZg)%#q!aHFH|0jsHPOE zU=c|es5TZ+NiM9~ZhW>*Jz#Qb}uYy064t7*5B2zo>TYW*gvrxX!#Y*44));eHg(ISLe(1v}$qf^K?UqRL>%N#BxQwv@qnZz2w zFVPQc4%^1bg{5^JwXVqf{(Th}*kD))JFcCGq4ODokc|5s$t9f)&zlbbI{+(Z`o|wI zb0&4cFej2N3sWtdKPneKODR~W9@h1PCe__dRY1z15x3E;J6Kp_|bgCL!Juxslh-`X(S!jI?&L2u4+9s;&U;Y8b^+Q^~)#q*%x zPJYW#7-kfzh`fSj{@75ka30A=VcjRglTb{KXy`uqc_)Ci!)&Y|c^pzU-lR|jqe9xg zSUjSoFm<>LJ*g|~M6$N<19yYqL@H!qwzykzApY|Rptzx`leJlPa&WkG*|(hbr4;B! z|18`8Mp5}~U-QR{PQGqXy7=HtoV`72_&%lo4% zD0m^dWa0DM1xPS(nacbf1zk>D)$U%!xFO*Si-`IHPYgX-Wp%m*k{~lF);~O7ziO#8 zSfn}4&^1CFZJ2@;dQ$7zc9^%B+uox`_fc{?k&Jb+h~$Qu0NUZlx*=FY2Oz7rV2cbM z2!L`US!-tNuReyyZX#1k*1*EDt+I^`{E$zJ#UrT4YQt=GFlHrpWM>1*OX_;7*{7+x zg38U6_(DLWNOQDq*~CU^%d3L6h1G~x^>wqKxA^P6Q9;XQpDd$3xhWxY(uS|PF)D5r zd!vv&-35JYA7_2*JuT$JM9M4|nO8^IUN$fx7Vj-Cg4(ETV`L`jsIu!w)~#(8Ps$7a z`OPhnEx2-_I2AfUWn0~5wj@^`Co{hbVjJ0px-0htJm1<@+NxLD`R$IpBT{%8!~AZW zZDJHE5%kdv1^-B94Z%)nZhRUV1+|T`X)!#2H0}t~Xgh`8lc)=uA4@hrZ+^BcE>oqm zJR@_>iC@?1H+2iZ`+!NU!>?+HTEnYQ*pqEm+BTO0@*;|5gThL|LibS5~@x}^Lu)AS^$mvhA4kHZTU)R4m%n#iUxCU0|SEC-eYHi zkN?Oq`w@lUi7e$!72UZI*|AVPC7igwxxar3ci{76|NGE^Q#B9|VEu$<>!dss)%<>^ zBX=(UFjwWhqCb1j{OrH@^J(nQ!P1}4 zpZpwt_wyC)=cwqf@iV_BFaDZ}{WVkiYwpRfdD^>Q3$$O0qQ95V{9e8I`$O#SwbI|8 zp8Wpu?)N6`_qHf)_Y7_CBJF!DZNHRu_=NWJ9ql)rB8GXkOZr4sVO;{B z-Lhd4x1xK0p52OZn(*mcV1D~m(@ZI=lIVT2_s?x)=*Ob`4j(KFlwB6QTpT`Hmz<9I zy+?AWGN%}&iWn7ey|=9}FV>A0I7`2;b+*!dpx~_hP>oyLEr{Sb|K57Lon{&Ty=`8`j-fQHVOIdn+-6VxvsG+1cl*O%ElDL1# zZGchJ=G^=NWr-BK&0gm3vWK3i%DsIunO%{ls|cBW6j0T1eC-wC^yl}Vc^kyWnPbvL zL^_)Ep1xl^&(7riBjkN1w|=gYL-@c5taIyJcf)?)RCS-I0_}WNrLw5p+M3tdRUrVp?VEaKjGr@BwQ4yTM>X|1Qz!4C6z*Obr1BI##EPv{WR!ZFH7U$O8p^Kd9H53#jqUG3`q^!3#KII`r5Z@7&`A zj|3rm=e$6r_D$6u?y!12Fhl?MPZm^v8d7_OCwO(1F`EauGGT*;uqtQ@81H;2zPu{K zGWgWCM|v_~@sZku)e0V-cj5V3h2SX;)6g;F3u|ykyK9!iEs}@!IcJ?XWzITis%2R+ zm{vrZ2wK^j`ZL)-GP{!R!IYF^XCju9Wv#t4%-M)h&-u(8l^uzym;2P=4QwxpKX<;L z<-WyqN1ZZQ`0AujK-0B^*Aq!@Uk2KqXJH)H8MM3Wp!Y=xq>uTq#hd*|q2*n1k!a|p z0bF|+f2GKwzEQ=Sw_yiQ^K93%jNBHPA-l)h7xy(BqqA`>Uimv_9rn1JDCx`5$fXAR zcHNvv$C7KOz!`6`c9(^s*Tg_+g*1b+6;sJO{I9+HL*Kt92eaS~*9vjBwzI46MxFpl zjPJN0apm0wS539MHCF>UkryuCTe=Ds)#aGM7KAus{Whv0iSUJ?RVxfjq*wh%ZUYiY zek*zCRR0c6oHxR7Mreq`BZut;Y)G)bgJF@xiaZ$6Skbx`H;HGP|pBtSt_kBqRP;{3*gdL4L#C7FG>@sR`S1HB@#S`NxK75s1< zs7*DYUp=591iujlnA(Th~DewjzJGn@|yU)+0)}5F3 zXc8#ustm653QGom(HF>wMC*j@JUxP$pe%fzPHyYPA2+|N1biF+s(~_U`xulFELM7O zOzY&Kb7<^G3S6Jt9Ac%9%i5n1RYuE^Q{^V_{=)UYEMRBCNiJP^$L>2ks%e9t;Vez^ zU7ysEL#k$af=P|CA3_}r3~f5~My*~J!{10Mb3@sufHngLZmY^?OI^+q zMy-ZJJD+n`sHw1caQhh!&f)xvB)9|206Z>wytDO}JcEi^u8qvtMC4)A)9!)w(wXw) z7bQ4v(0iJ-_oau{j8C-g0$Gl(m+fd)-WoSkmLm1nP86tcf?*BZ`khb0EHS9QlN)HC zZR((n@9i6!NzY)xsHudR)3mJC>oL@0s9&;IvTe zW!~>=18i!_L=`{1CyAjhoZA^Jf{s+iEa+>#Zs--wiTqR3evGHVjUbjUs0vb69eOaF ziRZi2SaRUrUD*`A(_d#tuuq@yE$aKK?@3Wp>3zn?Dx`bK;FJeB6UM#i!9jmH^mRW?;ED>q zOW*U_>*1!v*CXDNdskz-P6)|ko#J0DC9^)s7kt1Ax#8-M8VR}>QNY0aa9N^JNYu0n z-R;|MS#;+*Dzb_riV;8P$$sFe>8hdoFvsFDusd%~I}?z5tfB8po+%5zo{Ff*r+Eb$~)aM%s^dfL+D9-)cpn%Es}uycbt)eJy_S z{j=wpBk(Zr!3bZU!-ZnLiM#IK$ko;$Ln_I4a-G}Kboif=M{R{ozyiH>awO7EqVVP8 z9LulQ5c%M3GV;0}PNN$KYWFEUS}4_7hg|v@j=Feo-3;WSk>F2eP(DOagO_45CG*kzumZRt3EBl?org&rP+9hjMf4pRy#jz-0)_^&SiDl_vDcWcSLJ z_sX^O%J=swO!g|S_A2f59tZa+bM>i6_Nl7%oiOZEbL^v?^y)hm+DEr`G_w0N%loui z`n3D|bSC>wulDKg^_>Ctqq+L^B>VN%`V9>G4ITT9y!wqp`%MV_rrG^w<^AR@{TBWG zmXrNftNqq{{WjnMTdn~+$pL${0SChYN5_G)UIXVs2b>54bT7xHeBgY`z=i$+*U165 z)dBatfs5d$7_O%tl20$GJ@qUakT85|#M0}n)8BdhseX2k4@*C%KkVwBfuEtC|D-{H zD=ZZyQTvGh6d5*5hRXJkc(SDol%FN$!E;rh`Y>1|A&KV;G;|nxZ5WmUHAxqImPchn zL{d|#(9p&dY&j`hCp%&_InsZy(z%CBHAz{krztOD;rsR5Nl;Z)hR=C0G0kTkn2bqM9tGNLgN31>R01?@&op1djN~&!kTeriF_YXe`&HPR^OICk zPTFJ!C<2AIdttZw6Viswg2i~-HF86D5SL7WF1AeQ4gh|JcL0;?lh0#YhIq;RBJO8y z)DZ7mz?3{_`G=+pV5bgVL zZnjLmnkG4F|PIC9|LMnezsvfcC zpkWAEhyw`Vl!fqUp`L#Diw;>_kkDX;kZ28D!5rLA#a^c$>MIN4(SncF!0jFUZT5n# z^!F+j27?7l*yJ`U$WX7QW|b^PP~K1na!daAwz~F#AMt zu^3aip3-aOqMu)qlf+001zop5#LF{6$En4(jS0tQvevuuzefwk-?9GUTTm$m+IAG! zi7v8TDtgomomm6(jJ9z%LRD*&qRExr8f9GP%XsGaqDVwJK#4D3N&BD(2Q#HTqKvPA z!hBO%5psuv11df)MiV)&{uWF0v&|2?$xVktz~kU(nR<>+)B#Qe7lcwSE>lsgq6xL> zyriu4aD#6sE7Ols$0=Z+3H5;lCp+P(U}3ddtKEZfDD%Y8%=q5AJ#@&CzlLHKP&}Pj ze7cdza}CH!WiY@bIAVdE05yHS7P-gvpH;y6#ia|wcgUY7Q5^UaFUy2Kl~LB1lj{H` z)Xt=}XVt#rz5K@l3zFR^O!I?xcbkvll<#>yM;mjLKf;y=+$_OSj-{_M7*v69nD`JR zm!rMZx!u7>lzSHA5`l`1ndABQdo*!*P8Xh+8@y(X1WTxh*Qny*+_j)~XD2 z&90|;YNgYR3(|sKvj%Rke7gfz3W2Qew7Hlwg)U;dgMiU|+O(=ETpnm-#YwB?-i2O~ z=sZaS$_x(by5+&L|KdCe1~~G<-f70mRrQbn<6{W*#A( zZ|bLLq3?@*o&)m%8#a=|9>wV-N~eURxgCrSL2K|)JyjblJ*QK8H!8zWB!AnnZYVI? z7DWrH!a1L3*SgS=liz1wjibzgn$CxdL+e$~%~ate&Kh5SI0wy7TWWH6IRo)AO<}?& z(A-V2?;hnlYbjNqlzn!7{Bv)@xwotvNz+cq1ZHxL%xKos&HJ`Xxq}V&d*tfkRS8Sp z7s#$kkAmvI{Gx-Mqu2mbK?@eGnK$i0?rQ@HGHdXM&*#>CYM0Fb>!z-_eMP*CDvodA z73E!D`^Cyy*GuGNx&%G{kY(WpYWJ=C_-Vz(9RAg_?zqwVsmGb4vmDaquZWbhCuB(R zUsX_?O{q90E=uy8hfefVZCM$3SF95=HhavPp1O#0q?QCyn@eN!BVUD{Y&rgd*$ zVz`~`kH!qYoFd`nmzWS`3N zDQvw8*QH?<#Ag6X&cfO!2w_nz?SriWQSpHm0Q)$&<$|q&Oq-!bTj&LcSM7jc41^9D zfhi|1^qs7pJ-L8twSlYQp!@+0t=1(to6BPDc`A+$C(l`6FPv(%)gIL`v+ZBG-{JiJ zTv%Z@@`UP%6KW18jLflCo9_cVy^P`>S`v|gA6ogVKB#IpzaF~$&4}fc^`iQB#Fq>& zb6qtr#*=z|o_OSYU#0gB6>aHNrfj1C&I1h56nnW?z@b5_j&tq5B{ac9gFh`Sp58QVlKar*j>OueZDXoJ!!I$ zHzmjbyBiJLd#WGh1r_W>U(-p1%5J3(n(_4Osd#P02W|b()yt*0z1<}pwQl`v)d$&- zeto#+vv=(iU^@-DT@VqZtQG`PxyI@k6j=9kh8m==v&#Bo8^N@L(a^K6I@4eOa63P~o?+TU^X}O!VL&rdzXjjCh7QD9lcbG}$CC+mm@voKFoQ+#c`1@cJ3$Xc3bldyUmImfQ?9^J&!;D4Qv_w)WO};H;Je) z&8jHk3mvcFhLDKOs52VM#-gdAbBaBLE|#2)pc+^JM=S)VIorfa?#+< z$}=R$iLdvL?cKgq8TA6?b|y*7yl}2op?%e3me~l_dn#&rm?{khKjyB)uQKE1=4biF zrMAJQx(rt$xqLpM5J|)^8Lv;F7z1)X_|?raGW#^`kw=}{Pmm!k#$OrEUo&1YZ`6k>6(y5E)oNjupeL0Rc8P?vtJdN!?BpO7i9E2;|_TC)Mz=#<H(2tYnz*DC0Nll&DMYZK11;6#pB=* z!ZpkFdOy=nn>^{!ZsJ~4He-BMeo5P+61=AS=b^m(Q+~fLm1lHRQe{vqSx0q9s_;uN zuZ1^sZ+%Fy^QY>t%8O{#pc8MRPYeTr|7r!&|NmNn|MQCP-wEISy|4cs3jF7a@6OK7 z-wEGMdL{5@zjyQV-wEH(e{z98KXQRTt-#{KyMJbT-~E~G z{kKqnKHfVyHTyRXn41_Kqc8VP{8t?C@;~-_>2bi@!ACD%{)q$rmH|W0U;U30zJC*e z|C;a}c--@}>z@_h?(UwyE547q`{;eZbo-t5u0M0W?T?2tpKTj;p}ea^SM`T;!`xLbL@tm1w{X*T6v-JfpYZgpvSZSkEy)4qk2iu_V~ zJ@9AOH@CR_@2>CPdLSd0Le44vv+J8xNUsMH>C?U`IrMITkeri7O87JFOH7ZYM+1a3 zdN)9y_HFZ5y@^ev@A^jEO1Ke|a6Kyi`b|OzJsJqdUi;UgZ>-<-Sig`c`kb%dwFuwf z7@wf1e=7#UJbkZw_+F>C0{`0b^$v3Trxo~T$oHCyhyTBF0Zf3?MZZ=jM2iE=$=&DQ zy8(y)zUb@pUyHt$ju`r=uZ5kfsjVwL8Zff{GwN$#aZbId!u9xbtNypKQq1Dyw-jZ zaL=(aP8?gP)@y0|1mQEp$(yewCsVatcn0)dc*fUD4HX!@QC1kKI# zftsW*_v!(t8gx5Z{j({KxN}*EWh`LKPkux$3>vxN_U!bAz0r$^rjJh`DyO|xb1#1^ zw^t9h`kBAF-YWlYyzZw>h~Hf6>?Nh@!%v^5sJxt(=n;t%ks_~Q+i$g-Ka4xMFeU6? zSlpb=h+BCYzm|{ z=F+=}@`6A!{hJY|+WaDM#?6})_B_cl9NP|h3a*){x<5x( zhEcrW%c~i*!ST_|W2ay<|KxH0VeH?%3U0(Wc}6TloRF#_J70S^<-aw#N0mTVM$ha` zqMX>?w6u*&98{^F#eq)ZM>5jCcn_cfO8p?$gbb z1aN$eDj$SD-q|vLZwkORvq|x*406N!L-Yg~evY=P-F&gpX28j+)Dg@9hUteOLt*;m zZs4t|Bfu^Y`s(+@2m=U)v5I=dQU)MwtQ+9bDQV|lBH>=RFIBWr`gQ-RG;3*q>Un>Lth6Ol~VAdDCk>pvhI~23>Fqw_2<$*SaFP580KgW56HbPX=6|) zX(K>NSKUCtm(JO)ixig}n%8kU_Ed=A#N)u{>OQlJLprRHUuc6R8XJ({?s57TfX%V3 zfb((1n5h#23-|~?^2CwsK53ke7CN*qU&3v z*`lUDGW-BBbf5*$c5LCShN`rnr$&dt8C2zM5C0GnNVqB6q@@phy>WC;1P3L<`lQtd zlbl6>^6Ab%F<>4=Jz87`ki9K>PR5k^bdx@Q)kCV@8Oq<<15*n^02HG=SZFZSzSv15 zK-e(X-4NQs!u&FE zsS!9fss;6YCV)Rpy%;VI61sld_26`nmtrj^-thb&@52b9uP}x@ULd}zY^ihJd`TxR zNsz-LyhMRM&=v4F=uXRMCXKqP&j@U?24T3@^;peyBZmMfAyO65t7pVzGt4#(bAoV} z#-e-qG|sXg2Lu(N7aGX-V1_o!em2_2dXv5j>7inx^&CasXMjf$ViX@!i8+UEyeWei zx11^C2kM>c?`zWm`w}K#j`sW_J=E#NXAQ{w1c;%X_=GbIPF$unUQk-})Qze{%Y7v= zBPCN1xJiDfcxq17m}WXs1Q2t(HXej36`8FxJk;!Uy{YE9Tmv_Z<(K9dY zz_ni{PEzB-Cui9`jT!t4qRs4EQTpDO-hM!L=cOi6_e52T7bVIMIIsR*0cgh%e2{l$ zV%y8)EMg2b-e8gE7cp|?ZWsr4j=}uZ06E5eymSzAwI9vv8#~{x1Que`tVxR`!=<+~ zelG>&TP~v{$jo59kf`$=#zfS!`=2wiqwEsw!JOIj*XkMkGTcv9HYAn#HibaC2U;r| zkKd@^hv1(kSB*(1SzWj$IyO6EeXmReh(p&bvBzM-P(pyhtD+w!!|=x9$M{fZD!zf- zR0-VjW_>otWXS=i@df*QvcZ0F94ro3#E{=fG1rI4JeML)Kwvv(#?kB-!a}V?zttLd z9SLtQ7%@*27=w*IW`y6|=}J4hB>@%~`vEz2OGKq2`r)j|kt$cJJ%b%!(Q${-3D`{qlDzAblbzH zHGvyt2>*+7mqJtY=1e!w&7Z&m_)n6ioYTR@je2Raw}>cr)3KFl=s|Cb^4Oo4a6X3G2Mn%hm8*;zZ+ z##+>XJZOvMA>m>n03|H!4O(gs#hPrZ5YmVf9)xko;CX$lY|F4LYgptUM_j8#=#(IC zAX;C8VCYORjv$!k5zN~OmNNwFeS$4tyuC)eqjUVZhH@lBj&BsK|Ww7L=xhKO=cM$fr0eZxUy z0gyojt?`&^HWI322qYZ!m5KvTiNjX_=*pFiWdI-pD*_SyekQJk*^I+jjxkffN#ZNG zW-i)yUbK;gSt6lhS!w`Yq=Jf>A&6OQ#hG%DpNQB2HXzByt8K6l@A#N591N3`vTr!W z!i8xuEVW$9{8O5=T}kv48!F<;VscUsxr#fjq>9CWAoDD|K@xfjCmoaIZkrU_l7yR2 zIxz-YVF_CSB%lO&$4>V%fCJ$ov!IL>;$>_h_!TJQmlq5u%m|yxFmqv^B*4D&q{uAn zZ(f={7+4{up~o^=4fteR`U%_g-Ad^mF`!MdlC}*=$ET7A*Q5q<*h^Gc9Yv&xg*71H zl3Ca!DnuRx|IS9=PQxDdq)-g-1CC@A*tI$A#w-JN$oXU!i*_1hjI};;`reIf8nAjxrD=w}Thy892 zp5vYd`e7@z0B&Avj(s$Xg*{Bbfi&4rWZoBFgcT92LCPD5krWEU%>vYMirAbNri2Sw zF9oNPvyJK?Yo$p4VKh+7Qq4{;F2ieNW4GF|Tvh-xAXWaR<^jC zjs629SrBli+Kc6S3IScw=32}k73R~;+(kt4aab+^K4B|XkAr;_paZBNeG>9xE#?&= z!*c~(K*dZDk%1k|GhV1qWb7yixt+^&>$&X_3vw2qra0(|TKEbHwyuBsNa*d8-6e;R zxA7~*xf{diZ6MSf3ms1dW6FRPe#&|mutE|+w=f4;nBxRyd@N2K1pE~49c86Cvapey zOc^qERRib7!p8qBzqblJXtb5Oo0v|3i6INWhDb^HCrx-^DZ^sxnMg=s`de0U)B>=> zjiu9AB&%DsEfnLDCHj+Z0`w!%)U;-DW#?)!1Ycsl3RtcJWB!#ucqL>^bdjCDghR!} zf(*PcKvH@+XhJt%*Q%v9{7* zqjbq1=Q0JFH4CdE@u5lGwY4h*6p{+s?u9MoGLcktCpT@42m2)~l->)(l(Wm* zS?PBv;IbZOjsQ>{Nz-Lv)3`WmEnVxJI*lyseltn91Et)C%yyHjCN)h&K)5v9PHE$QB&zG78Smet3N3q340>FYlNZ z?;gALKJ-96VzQMOUJy;Mvc-1~68I1~kmw5v){w*8--}e_Fk4vIOaVli0OhhUa-6Ic z0{S2oe29Zyp@^(8(C=`fayYOK3mHCAv%+Zr*q}icHkJxervQ5^?j;{+NRMk+>#9#g zHCX1PHnSQJalp=kCucd#IviGs!;Fc6pB!c$+5n;Up2Jpf3gw2ym|oZ=!9BYePyhwX zW|fxh^*I_=X03nSlG+dv@9G}S`?M?~Z{LzEsxZLD1pI>=)o52YJ0=t4M?A__4@P*%hO1x&11F#rOXY^aA( z%eyibFZ6jBD|MpQ+1{{wg(9jv+oATp#G?{k6A$uXVasyUsCM^{2ylJ~RCXN3)fT!^ zTa&YbniX^e@pIN(pj@u-Bhj|^%(J=5Yh5sBchM~)xYpd>qfxjfqaMr(Y5>=PA!3`U zY0^YYI~9b-VamDBJhYiOf?T*)=bE}GJMLxX8Ppb1HIpoIWdygA%!Q(@fj};@sbX=m zIa;F-{5}f^ho{Bk&frY^4d5=r-MrQALiytNTpVNe(QUEfcTeGt@t{fEy=i1R#;?D2 zEvtKjgZ6kTJmH~yIgCd<{BdsY4Q}hD+S2q{oZrgJb;E3c4egw5%SEKG@%lWdpiRWr zOk9V_dma%gIvS7rF7_6!`&M;EcvhRP0pLX{N*8Ipi=T0Z^a@Za z*JNSm4HRBun__vrilmi^O!HXu;02yfo_LU+|JQ5 z99yJC1LeQ@F)rYB$}sM+T?@~@rHYh)@J7cD`+VaQZ?7K|`;G(a+Mx`Z-k=Re0E)#c&5BzW|BHOHWCn@5bs~m zacxb56Wv+!6v&$0;QrepOfpc0!|eN-R?%o*l%1I^0QU8zuk-pctp=KL*qnneF<|rz z4g%=19SJyCBHt(ti+b7o1?X=ZyBts%_ zHI^v!A_dT8Ab5(%g68v0Q%s>e{DLeZNfyaqrF*hJukk=U3O1Sw zQ3C<;&oVgx1{GoJMQU~{}3WTgk zpJ%_BL)qW1da+U~8KHq&WV=AXvu_BWn0(QZces0kDkFtAX$Py%aPM^_R$KMFS?3Ki z`OrB~2Af=Su(nS|3!OJIu+D&f#AIX((tPInB)Qh=InTRBncn0JJdo1Ax@Mu33+&<<{q9)Fo zkNhHR^Kbx$T`V*A$!_ZBJZPaIxBAqxZ#D5a3j%7AjSixwi+3{(sK_xKb`Xd7MVnja ziBx)FWnqFn-vFy6~X_Cj=*PTyKmB_$pI zxahe<@MjtIf)8mFUDxTVR4?aSd#{CruX*-GCa|z`_S+Bhd0W8+<7I0e z`fFL|PYu3G<7{}Zk2KRM`_U#Oup$>WMZx^kQBdMSx>+CIm@p67PoHJ154i+>^jsfx z;D0?IS${6WYyWmo_;ycYJEoB7%_Mu9LD5_Gxc|EqNbNfCw-wMk36&LBvPeSNraujF zDzr=0IkEjM#aC`$P*c}aZNIotb+kU(IC%2#B$uU-a8otgdCKXI{l8j)>E7Zk|7rzh zj#i&&Qr^9KWY*>W=@%_SA~=pT-CdZDv{O7pd|aCNVCrh?$TWjx|X zAH@OcRtrzt{lm|KYiWOap-0g(?ebxr)0M%tZdyG@bPq z(xY+HwNq~Q&ix2=i^VT*)iC>UwBdBQ@~F-JO5N}T_OsG$1@tZ#U3d`8kb#%IrPQhL zXbbXWp7o$H6uikP%@G`sIQ3Ymd+257k0|ACYnJuy+Aq1%hZq*;8N zb^R1MG!Flc#+FVk+ye}KOLh01>phVbJ)>8E$JwR?@f{b&F%&y8YbP$8;KOutON)B(LcBqLR z-OzP8RLD{Tb+Y&S6)9*7udlQ75m8Vj#k&k~C8f@Yu*wKkU9U9EPj4EH7l#DYTlO;? zRT-5^ikFEtTgR0k>PpnSxiy5*@guhheQgP4F8Hfe;yrbJkCCQSRF!PVHsv6 zXQhJ&YzRBc&g~I3dtwugztZv|!;3Fpxw#9#^BoK=V$iyk5HC0cwWa!#(ne@kr<8&z zeQ-X=Qd+o3p@PF4k(2}<;VaH~nJQn;qCxQkZh8u@saM_m3&JRPgAzxh;66R(^*Yra z1=E;^`&BUM?Rj6JqORSEz@xJ%=glvm-Yb(%op+=ce3dtQZ~gR$M zLU?aA0y0$o;{?Sh74%YDX*hm z?cb|7!gooT#;M&hn?uIF=rop_`|TeCUd$Y4=Hj8|mAQ zGc!1NyLou@C@qfb{mdHo>1^s-6~%#bxLu{nTSVuE9B$$<;;t_D141?|;$Q7K_4$kb znN1+C9df#Gbsh>SP`Pg&zXr&NM0UIN{)mmAWudR~#B7Rd&FhllX}NowFs@*|mv|`M z$mQPel8cHF%F%vBN1FrHrkrs;F_O?kGyTF2l~7!<$HXTwfMjNDg;zV+PJvlzJCmd0 zS-KSj#n#$N(_j|UtVCHJ4_Z*!o)Cj7@;zQmjNZcrnnOzY1gFVqN56MCwYmo(d!J9) zNEM9~kVx?G$t~;__X*S{?>nl~5BJf|YtYC_ia8`e^M|ge_7neJ+4Gj^y;Xdt&r;^ zl{YqL0z_&9nZi|3DAiYfLIIo*Djy-bi37ffFEm$HY=7TJut6zPXf6{fUl` zi3$Ca0=%JX^GT7bWDyZcSEnW3HeS(}kGudquc0;u!B`|G>OGrRSteL*Q2`AVhnPYQ zK?}A&gWg2QJ$Ripx1ec1icXIY-sMjUmvcW!@SNPkQ;7rJ+q)&Lcl)pRO5#7WdbMAm z{jQpS*m6;^3AA~b^X=xvKl7}850v`7(vWrp#`Bhe?6=TrcEU+`q^gnG4!jcqXBkf6 zwTUtT^H1UtHP*!NEtM(H;WjV*np9f0D9=r&6LL1N%5UE*RYy0?DskgOaV><(E^9f; z)kHtRO9}YAnP5;FHEz0JJ^Dub?n@OOqJViQlVSb6>5zA=$v6@nV?0ypsiu0?N45Z;BvO?&*Z5EvEx&JD6I7u+a48aq|E8IDPMG1t zryA6I-_)SWx4t-#j5R~}m%=4^n{7g%mPCdQDe5W@M&@qn-pPa}4^M?ENXf zn-#o7gS_BEvTwRV9A6Lr_J$NqHI*$f-Rw@6G6bY#qA9fxm4d)0CJjg~nKXUKF=F&p zY7~h=P4rWM@u&&5Bg|;Wf_$kni-|Y7LELd34#kSceI7wc!1TrbeM&nieV2>K z=RZ$QNeG^e8RFy6V~OS|*53u3(Du%-x1Bd8I&Us?-hy^fCA-2^yCQaX(JZ>?&Rvl{ zT~WbZ(ey4xYFA82*G6nzS6q8n{M)XCiLS(jE+({_CE1;%+MT?+JH?_q)ww&(r#n5k zJA>YxncAII(w$w`E!-T*ecPQk(Vf50T>$N2OZF71_7v^ziF(>uY#|ov+|$PByrV4^ zNVjX@c9cQIPFNr--lHntTjrAJ);1zdV{|25#_mGK1!&A}Km3EIX%9f+3XTjPZq)tv zx;h3xy?ks03h@*gvu7o?u?Sf`YnQRyQwxo~w+3#k1M1oV#WJl&^7;=z2Y0yw4JEzD zln_0s$=WP)^g6_|OuT4$hL4Dq}#>7k@V3b*$*sL#U|=&ekP z|uzgcQj_nfNiw1%UaPV&yvc^8s$>9z7ShGLJS;y{*s&o#P{p2DTB=+Ncx z{R1XH$7I#k-@+~dfN~b1oUN>yV)C<6tdDRcRRU1j{pzO^Yq=*P|SwI5e?*a?Ul{zXjDBv6Q@ zvf4>yny#8bDBU&`YIz>OS(Ye>t660~()__qcG_VAQ8iUA(dMXZyUy4+ds=BzW3+WW zLaS9-$7!qMk=u^`j8i>DKI6(-(~tLs(l3ve91>Ul9FfYhyrbiG=S=7wU$>(k54F+A z`@L<$@7w_ZPeaQ{Jv-@e`o^JD-MfcL_{-u0)}`g;;HvrvMi`YGd4rx}SC*zJ=}v-^ z2|${9a^7@VK`3-v2t6#B$R>?Mf*wcfM_5xLHT&&pIF}4@$znsE6M;npsqboD!t@09MCfr<@F**01!Rqw2E#|t2u576%WcWQak7|h#&c82AYoDvAzp?%J) z?EapBeZcuodmJR9()bgWfqSxd0z_MPtx))>FsPf%M^!7TTD58#WxR1WV14Kj=f=k< zU?Q5}bL2YoAcb%L=0589Lob6d|0jErxf8pN>K&xvZ~ojX3$EWgcVyu9n8BSi!>!Ux z5b=PzJJz8r|KRmwfnS)2=654|%3G zG**rn+U^1#^s+Bi*T?nNYq~zorFrY53$J`r&o>)rxlP)yxyFeU`5T8HlR_T zTCt3JMAAJ+cX&m9f*+kO$&_$xkhu8gONRQvd%1UZ+wSMIW~^2u#*8|vBQzJ{_RYYzoY{wEv-cxs z!Ub-AgRTaL{7bC-?@0Gmrk*IjBJt3|RLaM6qbbPh3g(0DyuUGctDcW}5XF4Vn1SU- zU1op}{4gg;T9`pa8C+9^g_avu)akhv#kClVr)}zmG(SeP?=oa{d}jAPi>5c`yP&bH zH=*Mf+V=LD@7!%p>is4a&_bJRSDkRY67YFrRRXImgk@A=fk@KWnWPmS$5R~>+IT?uQHc{;_kk+D|fEn%Ix7q{(ssE zEc|b#ckkk1>qX~di$~51ncl@?(Tgq_i>`l}-o@jci|z$+4n)8{6GA6a_W+_aLJJK? z_dW(3UaqB+VU8|<$1SL*3E)!$U`Tn^6uMr-m}?0A0Ew}Wf5nQcsZ}GYL}cjJU!n2o z4lZ@p0Nt9=ams}8{stZ3K_Y!=hsdx<^i?e$+9U zVk`yP{&4mF*VV_KK;@3>kF9^xN+W$u80Zjs|Bof%QVQKBEV_UpU;q0lJjag6I7R}V zUk#&^ueT>KPVQZ;#ICj7g6i-WYZo?&o$eB4Sp#v!^f4@yB^uA&-T_6&jadX-laN&fj9;U=}{jn~+ z8A}A84mo~z<2ob-^uVCWArpGpgdv<*VMYJ3W^K^A=!aaw#Y+E>SutzpEhQ>*4q{Q?9_Er?^w%1#dkBvV-}7{#-+;^I43nprm}dL z9`Q}FIWss+%ph5r%QpMV^ahW|MTn@__9;1&OWAjQ#CDT=Uvcd3sBVpXnCkU5MuE0- z|LSD2QtY{iE9JW=--=z&P#V?49?1 zAQ?%K|81jts&kjb0R{CCLUx>pNq57V?>y&u-mwQ4wxn9NA7(n^BJEg-7Q^-*jScnj zODdy9u*z8t?f||TzGnD z;SkQq@|Vb6#bVx_^mcQ7+F(Y!!cA-O(TtBbEyjlOgC!CI`6{`Sj(D1Z)TCUbRJL9R z*_82%ab9H1iYvve6xg_mD~U@+iJ+}hFGwVGg{yCdJdCx>>%Rk0-sxuJy*XoIsf)&_ z((O%@wC^vl@D=9RYU;{LXnKO$&o`VoxZ7tBag%c zh#R#|l7j5%)xNW^#ctKRCyM$=f^w}LrVH=lzLN}Oh^<@W$#nz1r^o1p&t{ggs@qhv zM?P*1|6%H^Dv=XOjEPnCzvr5}+=Vwq|J=u|uRs+oqKbB%G84G7sa zddejEQgzhNExSIa9lvStCF{V}$}f4>{}@ab#u*z{g#HIU*!c5D_yB%=ZB2Nr{T3d-*Z$vohNY!nf0KsAU;pkI z78if6{CE4{yWrxbN>hklR^Yxa^}AY2vh$~8YccDAbj{R{!iHO z4}vfUZ7DzA}E({Ubqr|KKMpJF!fyUt$?5#;pZOw%@`=xgzACs#n7`t_#oiQ zne!p10z!oF!70CB@3VoPXM;S>1bUpl_!m1kbzYb*xSssmF#Pon{ss*GLh0a``&l7* zaM<1dKi+}Uv6Djbz`^CT{jvWd4m!VJGT9P#=K9~|~L;B?}kqq{Bn|EdR82alNx z;RD+v!iK@v%1Njm?6V*nnjJDE9Ta8^LiJ#ek?k%c+uerNJNMb>8vYOXVEbMRA$+hy z&rDm-TxcI??KaccMbac1tLvIbTC zSvrN9dZEGPZ{v)-r0R$dD~`n&VgJl#O;!x29ud8x*ZX#=qe5xyW?DtcoP|HEA%FDv z?n(#7{+}oK2QvL%w_-=K-8W~2q)mAbJ2yq-t<=OfG_ZL{h<0W+MQGANnY_rEmk|0doG%IttmLdG}@g7=&ert z6jLy3m{6ob#6w)7M9&K$pTY$noc4WD1pfR(zf)X!eC@@gtvX(Ul86YLkL3gPch|kZ5@}4>GpQ)VPK$c}cNbqH~Mcxsz|yE_D0Fd*C64((3dgRgK%Q zG8!Ul1GJ$rQE74XDj1W!@RsJ5;x6028=$C?^hFm>MOYUR-ga20tSz3amD54SnjBlL zM`iopzouX&bd4lxFBeT%S$^fIvI^KdHrDcwQ5Rm0VfMDu8nl3PG37_0VoPvpMUFMu z%BdIc-g3NNQ#n`GyIoGEb=5&RgA*&Lyg%Q5+jNr&jU^5|q`~0C1$CC9)|N}$r*gV1 zro&e;c9J;!n%E_#Xp+3n^R-!g1W>d377LY&`T8bX{UM?!hxl7HsO#47T0TkoZKvwC zJ!E!=0Sq0%DiAsNb7-fK6rgpLDxmDNjXwv$;P4VY+_0#2ti{@vc7OK^w*LC==AnfR zHh8l(h;xs0Rb>KhwvEz&RgkBXfrp}($2^9PMd(~n2ZY#c$^OOly(q}2m1c*joA$Qn z$FgHs8a+2vsXL7WA0#8!^&67qU@H|#8gQM8{u$BkXHE_w0nQ*pSRNvl%f=;{ozM=0+_N-YYcWeu|Ejp{U@Y>6llhEf9C(MWgXu;h0av)* zb`27qe;b)>PKS=$Z722EIn8I7nLc+YT0S68@S!!+X(}@!mL{p;jO{#&t$X{%tew?y z(sp>zpI$i9w#x#n|25`d?8&=Mn^b`EitOcI1ta@N%C;fUZ4M>W1N=#9PnLs1W-(tE(8TkNIf zw^Cmt`6~7PRBxA*bXBG6?j(-U)Di{pMD{At#v6go#z>%bv>K zyv>fZmX~|1$-bZha88Dse4v^l=K{GG$~l5A*GSF#At$zeKnv{E?kj&sc0}%+B|h^0 z-O>Am{oJ7jBjgiLn1m9*;DX!PG33ZYDwob{m`$%LY>bT)FI7#Mtg`G!@=9_xCgI4G zRc7Gyo!kwevc$dA`Ocn)M2Byw);$q8GgZq>c71dn8$vY~!Uy698`^E>9C5>~UsccA zSCHHgNQ}Hsp?XVp4zzXT)^hqSY1hvtJTIkEFUL6dwO+FdRIFr4xI}?=mwwL7@zmVs zx9(A~*FG{xTfdn@uu9>kb`YxRpJJB(z4Pp)(=3D%abl=(k?<1zwl7=Ip<_W~xAf~9)w`!m1nMj^8C6%1 zep_0IQHz%Dc*KU_qD6@}4g;;<36i0Nn~p)S5hpj~zTHza8y|yVZ9ro0d6on^f9Ggd z%keixwwo|!XZL+_eHHf1)zt85GM09-inQLV=}ZCZaX+;~Wa}J|H@+HuA3n{B%{JZL z5+O<$y8&_SuwJ86FRfY1T9c__UFldnhz&ZVPuHz=gkj}5&>(({u_^~|Pm6dH{U^Ad z&-?feC*WP4+gslD+5zv{J+FgSNNRW}Os^aO%6l>8UIvWzD@a&U~^f-P;xNp&`vqL>Dbqbel9~&z!(s9}dcE^SeB0rOpD*&$e0K^dcz=mvjdn|5>~@R1xGw zL}nAFOYw<^RBrq{3dXv8w{H%U;$yAvedZZ+?lswz?PZ3qn_QqL+SLlgSL1s}DEC zQ%yU=2{IAN`VnU1;i@4K>X{Lmyl~QEp`9B+T#nc(L)+~_+p~OaR|w6po@OK?XTYP8 zWa$5Yj~PN;#Dpy(TaH1t)Ry7fCny3#>m|n&Sdxjnx z)EBQr@&3eqM#8rc=@*$y3neBtP;?w5s&g-4QXl_CpA|Wlz!$zVQO{g-Nrd^av`v}s z)sq&11VKG($%ZL=jp^AR??Zxh>PyNXkk_eLP*x%ifmiX91!NO7u1V~WRaKEqd1sRZ z@lM&h5tE`BlOj3Hvh$Aj0U-$n@ydQIwQI>l+Z1+fa$0{fvMf~toR+SeVl2xV(q~Pm zC$8C~NolZ7xTcva#kX@IGF-5^ZK}aNa3T@?orTWeKzc%u%fyTks_g>;=0`0$7bF4+ zMBnJ73kbs=85o;|h=@sOV4*y?s96%GPymU@0_o%a4)OOa48vOq2@)D#)*OJzkhq@Q0&mv2aIBB4$Rm-pGQ->q5s2Jj&n#4=xGoPk&bWn`D- zlw3JZ788RnLCXkT{zOvS$Cw4dBA@R9pi=OdEun+3@-c2XI3&YY>K6 zE4T-)Iged)ixJsuzHs_0p%q~$P|tjAo87|@8(|r?yXHPB~zRSr(K8#1=ywZy#%nciqs`T4}g5!709I2Q+KoY$CAR+3~4888gFD zD?IkgObK-=wweRM*Fv0!M0-L~dtJ~xis)t{cn7Y~h=5nt%;XxB-wrL$q+*kWszes* z6jD@{AUaIN8nNS>gj4+>5qYv`mlxI&hU|S;`AV*8Q3Jh90@Q0E(Zmdk@`^jL<B?_z1+B4S?f#WrZg;}3-C1Ve8Hlan`B!7f z=w&{zlMg<{uD!^*yM#j@oBT;s1SP?D7T|){`wJo5g+slqh~$7w_tja>q#4LoF0(}z|lv{m`AZJkP#J&MRxsRBTIY~pp|*yNos4k2p4#CG6$rrS!>HV zBWyW)c=c{spaWia4Z5Lh6taPc-3LlIOvPqp&$?>$WJn`Z>DVj47*WZ(p|5xPjD7=Ho3PR+9$vTxr#dF zq^NF81KZr1fYnTHCTfP75JJTsXMtQf#li28Y{4@l!;C5x=rk1@D#*MhNa#_2cG>P3 zn*S8EQ{yi^fCS&=w1@+@_8EzO|z=Wz};uo-Gp zSVyXXXJ>r^p|2UThbBRDT-3(CXP4OV!IYvpQdPJr4&}~SCN#4JoD>#FJYWZcl7#9j zM$02+DPY|kSOpP{;J`leo*terb}s(zT&`$l8o>Wl#0y{M?htY3z~7@r zKnW4<%{!mQR|$9hxy??ssVVR81i5xMfH2dgo$`IpvsRvmYqtVQZE8jtb!-^SEqjvv zprp5TXc;76p}6&3!MUKl0j+P{GCbVwN+7!+f!$g0*qpV*d0zK6w{CAL*0mPC=miO- zVs~&5gXBD~G6@&?aszMzo-d-Np;Z|xx=cYB*dp0fQSpA%91$FWKpn|R_~iaFFEmXi zzB9tMCp!k4On&wo!p&<;-AAovW+7sp#v>@NmO-j{6(y}kT=4#+A)6X+E=r^^RiDHS zee%j+>Xn_`E3VC}j5rZCxgTeHySO5rTwC7&!fJsMj%`q}XTc%6Zfcv+Bl|Jroqn_)zpgtm&#)rsqM8*#QkolqT z{k+Jk_?MhYEicFoBKFVmq2r?i1zy$SRf!B89=)lG8v-7$MJ^G6(5B(oZ^H;&PuPo= z3+SOsCtlqd9i~%j2Wx9}xnOJ@7Js1e)KFCZy~gSN*k9Eygq!`;zF)CiBm0;t?kaBQ_o%;K++X-B?@o`)xcurEL>3mLStve@ z46$qcBKRO&+3E2e`+6d?vno+^u5D*N=$Xd&bk}{g?S(rpG###u&!u9&5W#W=11pSl z15-#G0i8CCJV?cDsYT9l(Mkl7G68y$FP6X+UFIMlx?gy{aIOIJk^oz|-q1br*;C=m zp9HM2agusINEwYZCL=#?2x$Ng@Kzlcoj)0O0WzHlt)*gk!uOF_UG8(ACqQ5BWetxn zPsYBSyqzV)B2YC?;)@ifbToTJJ`Ax*U#}#7U3_|nVLYAuEDgbdR**1r9I@*H&<6xY z*9$d^LtiJ<^wdw?`0#?2oAn4Y75^dejxmb+@_v8fj6n=!k%Vc*AWv*T9=jmzdSRfe z>xCu0htQHbfS`Z&i2M2>W%RxH^jz}n1*YlpTusQ_?UhkO$L|grY38Nh*FJpz^->(< zfPX6+`PoAdSIWdF#tRhYB|Yb*Z_UdT%*!>;%YU3#*qA3M{!ljgq3Zc#>#ZN^1wS;K ze`tUFv3=tQQSs-_4U?a{J%8@G^;5s#r(yF?qmMt0H-3^77tBl+EIb#iZY|gpEZ8+K z9Q?R&Xk&q_xaefE=94|>7r);hi)$yrWVj$5l9-!E z>Rl|p^^D_heBf___iC95{h6oaGakf;Bv!=)-&br(d-dzKX9q4YHNtb* zuI0C<<66v)^|%esj&}}gZ3PmQSmX;&F&V;#D>V{lC>hrpGWuN_6vaC4W@T4stZ&lD z2)0culovWZm`^w)s71MjL_TvSCgpL0gcupUS3i4c`-oaNg7g`($xNa#uuxN6K9jwv zN_Vue3>>MF`uJ+!7kWUKiu2>ky_58V6pMlStK3x!g8u!9Zm7$p+Z9nToUt++R_-%( z-@C2II%T_B#GCQAWo`(k#YU;h!D{c$OObx*+Aep!J3>FdlfLk;nBn&EgMVWNUDnjd zrtkc+K*1KP+<*#VfIc|fZYMGRX&ZaG0g@&qsS|`aGF1Ld5JrPt2_KUCs0o+8A+pI>cK-C8 zLoq=e(TwSdviA?hOJ*L;x;F$3^qw#o)aiTlZRpu!X1?I&AGFBEjwl&215B>@Ggl7F z(b|*AkK~2;GIToyG1_21ff6)Q3S%&9^b+9ceknxude|gh(e_L|A2`Du=7;+W0xe+J z=e!^28ItZLf*+}y(uT=vwYS}xM}I9Oet2} zBVvCeaw0CXSw_dIzfoF$Ji*+}7G9vColL2?t^5gG;Ph;@ukEEcfJZJDEqIrk3A&Y4 z?Q}Yv50lL@t>tYdScTe($G4Q?$oEO8ZW-7maDnr)aNIFF;P3(ZnsfJL_4DxOhQVf<<|nIvOwOH|ntf~Fw|^u3+2Idn z-&J<&R0s)IZT|-fuemqcj`p6dz#8M6P6E>UPIbGwV3Nlr3yYE7vWG~S0nf&XJNIqF z_+jECIQjz#E%P!yWd8HBbB&&Q4TZZ7l{FOR1eoV)8j@lfDzhH@okQgw&ykMJe{fV~ zvYzWW8&qPN6;f`{D6#GrCj23&VmN1YZ?ox>KN&O2OY*`eaMLLl1M1iIZ$AG==EM`P z@Vrh)!GU&VQAKS=@Y#qeuka9fnDdHe%G{oP1C%OVJEJq&q=tO<>_@m3MA3|XTWM(| z!eG{ebSpNs?@`3aN}pQ=KXxj8(V(2c3_Gmrd6+`Debb;;3r`;sB%a-~5l3_gkn>>< zM@8>2J6(HDF4&_8B-!++0e#+{1K$};25+$#C!UnC36Q}@NXkd-l)7Y zX`j@acR2NUKJ*NmS}5k{YaYe`OCIBNZaYH^$39hx+i&J>v-&uHV~j5QQ-+S($q`A# zpCokgU!%6xiX7vAf?;c687H}5;+XltYE;ZFQW8T+dj8PagP-I^^jQS)WQNtm=LP8L65SdpMTK<>0;9t-P51mDLw#U7MX$(MOVHX} zrUj0>KkRmlk2ODSUJ?W+YkO_F5AN8fInf)w@QSRAN1K+^=B9>kw8~oBktF#DvxSDM67gfvbf6cVZct_L#rcx6w@LfO1dErEFsR;^v z*OGwB76cP)>wT^E%Gnk`j+AblL>|>G?=KhqPWDn689EeV^y=Y}f|P~4AECb-TA`Ap z;3IF{=lxr6?YUTUOEkc1NYkbD&p>sA#_YhLIIm-Tpk^Z`GQbD%#-;n{QO9UK@@W$U z@A};0y>!tkpM9I%UJnjbvu|wjwmvZQDt++btrOqQo!*y5eL48(amV=!m!FJ0`ZoBu zS@Z%Wa^~rV*ie0^<%LUWPu#!k8ESYHc_H}rz7x|&hZ=`EE?jx|*1f~s4pEm< zZyTM~JT~03b^BbJSp@&Xw9WPyZulWVmrO~JD{4ZqOnx~geQlV%& z!3t&P*CV-ob)Y7F@xrOR(u3a{Q*w=(m-c2Y9f zM@8zO7VWal_%|r^xaR)}It#`0Q+Y^4CQ=bl#9Ja1*@~S(p%b3?BmyMOg%|&NA=6O2 z0Ij*mg$h!Elr|(_2ONt=9Jpc-geZg|p!m#TK-cY)dunNUvvYf$r=hG+*gm$4WO>Ol%w zRbyId{aOeGMM9m5KDE3R7z?+pbTVP{G;V6rlM=9iEfqoqDj0-4@KOKuBDp?EONxD~ zXk;TC>4-jba_c$xk3p(O?K*}mQNGUZ8K)R$uo73nPa#@y;K$iqC0({`Ssn8ML;}(lZmJnt3tZ6^ULlkZSprZ%psC1~9`q3%2#HtO9zgk(hFE}gNQVw+h#0_!N@$B& zxs-GOg(+wV_gg{ui!@L$0(jUGaQPS9IF@ly7P){yB&?Jvw3YvFA(aCJ8dd^@Nq7em z5W0T3qzsBcJOqh5^sN>m18qw+r8}C2aGKG<2b}>LI}nT1;k`i|7DH?Zok;+cL5!6l z2$Y!$OVq-j2^vBn8vR)ZoWKK6*n-m2A9v^qY*3hn7y;{Xijk3y$Z@};$+t^$6;L1t zunI$3d_;AMM3I@h+&B!Ak(#eyn)A2-ahR3~0Ebif9XmX$p39d>@*v;}C}CT@4it@s zs2xL`mW_hLh5#Dg+eT7^#F-(8$w3FixCzOjoRn$D4@sRoAQaZopPLGaJHUrhG{Ver z3TzODsc6MZSezAc9J?u*E7TZ3(n7AtAAIlv6DlK%=tlqPm=?x4NXP+&V%&<|xx|z> z15h}FXRM@Xq`*3)Mm|Kn{}2$Q>wsZHEN$G136g|)fuT?k3rW~L>nKN>^s9leAN(Pd zpu`7QV7|^cps!G%dlUSpA;_s91yGja3;>Y%2CQty#kQm5pilG^*A<(cQP^cLEn@In!)B$#gh79nchAPXiz)VGA%qYT5 zD%#CPDkMny3S@|ecYp+}p(m5vCw}py(_6{FAT?7XgTge-gDIn_NGD;uCW{yYBZ`YL z;7PHVCbT<@YbvH=Y7J$Yy<_4E{VcmMhz|M;C#K9tQq(?QYKYk!h!KDvP(Y<>5+-5- z1(X1VqWMkq0fh)a26?CuRlo-{XqO93&y-ou2Q`kvkWgB(rNLPPjsk z?qrWXL^L%!%&F+Apc<;L@B(3Qhiu>ko#V&zwAb8;BJUhi_N`Jh{*W z=+UnViIhT9mYNE&(ki%YQhVSfwE`j0lSb_;z0M*!-OAC~QUrW}5*4G5>gj_9YqQXb zfO&uhBB+26U;}h82k#lNJ{f@*5QcY10S7Cy(h@taP{a~bk9Fh{2otl?+JHfrKXS;0 zs-xApn1yu+g$;*_Vacn3dU?rP-Rb*_*}LoYmQ#<=LL~*`Ed4pcUGoCEB7j+M`9< zq*dCby|7J~3i{!SPS6Fq=z~tEi%|&4xCjPMs0*iU2&lE%xR_eH-~eD~2t~+RVY!8x z6k9~#0E=CNeQSs^Fk7zhT2SDFxNL~3oeDc>2nmQty1;~Bh>N&Ag1N0*Q0Rm|;1925 zg{f!+6o4JBKm~?)go{0exDbS}t&2w(g}vomzl9)SnTpS)i_GPU&25OqbcWfhL^_RH%zlWQ7Ts0N1Sr zP?!NwD1_ThUKId^=mmx8tzOB=1W;gJyx?B%INZ9ZguD%24xm9$(B22;3O#_rsW@V~h+$A*LcD0={YZgeV1kCA;jyhtP+*8n zxZGBaiz%jyQ6OR_28I6zmIO?wH#fM2)pZCx7~83MVkS)EHEv_P(Bg);xEy}tIBtkJ zjtfs{1S1Y#t`K7~8DLNl1ihV#u-yPS{;duMg+XTDyI|qCAcY%9;)bXM(`^VzrewuU zlu+gh8jcG|VBEsBi%jraR!D=Xpk1zD<%U4wE8dS;-j7xpj3fBr!N6s%h}*hgAXyQyvVBCX83+XS$&1xs8iaqzjQ2 zuJ@5$x;WezX5rn93&K0!aZ%hPOj*CLL+`54313_w~z6%?6m8!-IlAemFwu>@IX{qK4s`f2x=89320Iq0+pPp&5 z1%+ThViiu{;niup2wZ!{I3bof{@4LVj*Ci&gXwLEz-H+F_+G^3k7AyR$BoD*ZU`oT z=BZ%p{kZI@=#@R@ifNvjy4Y;~h-_=F3(qEuRzc>&7VNqp>$RQ=)7~vlo@Cbr1qtYc zOlTDq5Cs2S0N}Wu3e;`jxCm?;mhDEMZF;^7761i2Smp^h1-t&r@%8HB-QMnn<+{kq zx-{%n9%K}d1j22I?6&S~?(Xkyh+AkC5~u{OQ)f@!1WK++`VsH#-s$Q#>_G-&U}#l) zhUB}z?}pIt{m=nVZiG7F3jTI#yx3?^m~Xmh?Ws^{1P3lpnA%YQ>xQ@k)J23nzGMKW z;ZZp0hS&s8rtoA2YtFq36^P@& + account.anomalies.map((anomaly) => ({ + accountId: account.accountId, + type: anomaly.type, + severity: anomaly.severity, + amountCents: anomaly.amountCents || 0 + })) + ) +}, null, 2)); diff --git a/revenue-usage-reconciliation/src/reconciliation.js b/revenue-usage-reconciliation/src/reconciliation.js new file mode 100644 index 0000000..0064f5e --- /dev/null +++ b/revenue-usage-reconciliation/src/reconciliation.js @@ -0,0 +1,185 @@ +"use strict"; + +const crypto = require("crypto"); + +function money(cents) { + return Math.round(cents); +} + +function hashPayload(value) { + return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); +} + +function normalizeAccount(account) { + if (!account || !account.id || !account.plan) { + throw new Error("account requires id and plan"); + } + + return { + id: account.id, + plan: { + name: account.plan.name, + monthlyQuotaUnits: account.plan.monthlyQuotaUnits || 0, + includedCents: money(account.plan.includedCents || 0), + overageCentsPerUnit: money(account.plan.overageCentsPerUnit || 0) + }, + topUps: Array.isArray(account.topUps) ? account.topUps : [], + invoices: Array.isArray(account.invoices) ? account.invoices : [], + usageEvents: Array.isArray(account.usageEvents) ? account.usageEvents : [], + licensingExports: Array.isArray(account.licensingExports) ? account.licensingExports : [] + }; +} + +function summarizeUsage(usageEvents) { + return usageEvents.reduce((summary, event) => { + const units = Number(event.units || 0); + summary.totalUnits += units; + summary.byCapability[event.capability] = (summary.byCapability[event.capability] || 0) + units; + if (event.billable === false) { + summary.nonBillableUnits += units; + } + return summary; + }, { + totalUnits: 0, + nonBillableUnits: 0, + byCapability: {} + }); +} + +function summarizeCredits(topUps) { + return topUps.reduce((total, topUp) => total + Number(topUp.units || 0), 0); +} + +function expectedChargeCents(account, usageSummary) { + const includedUnits = account.plan.monthlyQuotaUnits + summarizeCredits(account.topUps); + const billableUnits = Math.max(0, usageSummary.totalUnits - usageSummary.nonBillableUnits); + const overageUnits = Math.max(0, billableUnits - includedUnits); + return { + includedUnits, + billableUnits, + overageUnits, + expectedCents: money(account.plan.includedCents + (overageUnits * account.plan.overageCentsPerUnit)) + }; +} + +function actualInvoiceCents(invoices) { + return money(invoices.reduce((total, invoice) => total + Number(invoice.amountCents || 0), 0)); +} + +function inspectLicensingExports(exports) { + return exports.map((item) => { + const fields = Array.isArray(item.fields) ? item.fields : []; + const privateFields = fields.filter((field) => /email|name|orcid|private|raw/i.test(field)); + return { + id: item.id, + dataset: item.dataset, + anonymized: item.anonymized === true, + privateFields, + safeForLicensing: item.anonymized === true && privateFields.length === 0 + }; + }); +} + +function reconcileAccount(accountInput) { + const account = normalizeAccount(accountInput); + const usageSummary = summarizeUsage(account.usageEvents); + const expected = expectedChargeCents(account, usageSummary); + const actualCents = actualInvoiceCents(account.invoices); + const deltaCents = money(actualCents - expected.expectedCents); + const licensing = inspectLicensingExports(account.licensingExports); + const anomalies = []; + + if (deltaCents < 0) { + anomalies.push({ + type: "undercharge", + severity: Math.abs(deltaCents) > 5000 ? "high" : "medium", + amountCents: Math.abs(deltaCents), + message: "Invoice total is lower than expected entitlement and overage charge." + }); + } + + if (deltaCents > 0) { + anomalies.push({ + type: "overcharge", + severity: deltaCents > 5000 ? "high" : "medium", + amountCents: deltaCents, + message: "Invoice total is higher than expected entitlement and overage charge." + }); + } + + for (const exportCheck of licensing) { + if (!exportCheck.safeForLicensing) { + anomalies.push({ + type: "licensing-export-risk", + severity: "high", + exportId: exportCheck.id, + message: "Licensing export is not safely anonymized for institutional analytics." + }); + } + } + + return { + accountId: account.id, + planName: account.plan.name, + usageSummary, + expected, + actualCents, + deltaCents, + licensing, + anomalies + }; +} + +function buildRevenueHealthReport(accounts) { + const reconciliations = accounts.map(reconcileAccount); + const totals = reconciliations.reduce((summary, item) => { + summary.expectedCents += item.expected.expectedCents; + summary.actualCents += item.actualCents; + summary.deltaCents += item.deltaCents; + summary.anomalyCount += item.anomalies.length; + return summary; + }, { + expectedCents: 0, + actualCents: 0, + deltaCents: 0, + anomalyCount: 0 + }); + + const highRiskAccounts = reconciliations + .filter((item) => item.anomalies.some((anomaly) => anomaly.severity === "high")) + .map((item) => item.accountId); + + return { + status: totals.anomalyCount === 0 ? "ready-for-close" : "needs-revenue-ops-review", + totals, + highRiskAccounts, + reconciliations, + auditHash: hashPayload(reconciliations) + }; +} + +function buildEntitlementRegressionMatrix(previousReport, currentReport) { + const previousByAccount = new Map(previousReport.reconciliations.map((item) => [item.accountId, item])); + return currentReport.reconciliations.map((current) => { + const previous = previousByAccount.get(current.accountId); + const previousStatus = previous + ? (previous.anomalies.length === 0 ? "clean" : "review") + : "new-account"; + const currentStatus = current.anomalies.length === 0 ? "clean" : "review"; + return { + accountId: current.accountId, + previousStatus, + currentStatus, + regressed: previousStatus === "clean" && currentStatus === "review", + deltaCents: current.deltaCents - (previous ? previous.deltaCents : 0) + }; + }); +} + +module.exports = { + normalizeAccount, + summarizeUsage, + reconcileAccount, + buildRevenueHealthReport, + buildEntitlementRegressionMatrix +}; diff --git a/revenue-usage-reconciliation/test/reconciliation.test.js b/revenue-usage-reconciliation/test/reconciliation.test.js new file mode 100644 index 0000000..d940068 --- /dev/null +++ b/revenue-usage-reconciliation/test/reconciliation.test.js @@ -0,0 +1,91 @@ +"use strict"; + +const assert = require("assert"); +const { + reconcileAccount, + buildRevenueHealthReport, + buildEntitlementRegressionMatrix +} = require("../src/reconciliation"); + +function cleanAccount() { + return { + id: "lab-clean", + plan: { name: "Lab Pro", monthlyQuotaUnits: 100, includedCents: 10000, overageCentsPerUnit: 25 }, + topUps: [{ units: 50 }], + usageEvents: [ + { capability: "ai-review", units: 125 }, + { capability: "literature-scan", units: 25 } + ], + invoices: [{ amountCents: 10000 }], + licensingExports: [{ id: "lic-1", dataset: "topic-trends", anonymized: true, fields: ["topic", "score"] }] + }; +} + +function testCleanAccountHasNoAnomalies() { + const result = reconcileAccount(cleanAccount()); + assert.strictEqual(result.expected.expectedCents, 10000); + assert.strictEqual(result.deltaCents, 0); + assert.deepStrictEqual(result.anomalies, []); +} + +function testUnderchargeDetectedForOverQuotaUsage() { + const account = cleanAccount(); + account.usageEvents.push({ capability: "reproducibility-run", units: 20 }); + const result = reconcileAccount(account); + assert.strictEqual(result.expected.overageUnits, 20); + assert.strictEqual(result.anomalies[0].type, "undercharge"); + assert.strictEqual(result.anomalies[0].amountCents, 500); +} + +function testLicensingExportRiskDetected() { + const account = cleanAccount(); + account.licensingExports = [{ id: "lic-risk", dataset: "grant-map", anonymized: false, fields: ["orcid", "raw_email"] }]; + const result = reconcileAccount(account); + assert.strictEqual(result.anomalies[0].type, "licensing-export-risk"); + assert.strictEqual(result.licensing[0].safeForLicensing, false); +} + +function testRevenueHealthReportAggregatesRisk() { + const risky = cleanAccount(); + risky.id = "risky"; + risky.invoices = [{ amountCents: 20000 }]; + const report = buildRevenueHealthReport([cleanAccount(), risky]); + assert.strictEqual(report.status, "needs-revenue-ops-review"); + assert.strictEqual(report.totals.anomalyCount, 1); + assert.deepStrictEqual(report.highRiskAccounts, ["risky"]); + assert.ok(report.auditHash.length >= 32); +} + +function testEntitlementRegressionMatrixFlagsNewReviewState() { + const previous = buildRevenueHealthReport([cleanAccount()]); + const currentAccount = cleanAccount(); + currentAccount.invoices = [{ amountCents: 8000 }]; + const current = buildRevenueHealthReport([currentAccount]); + const matrix = buildEntitlementRegressionMatrix(previous, current); + assert.strictEqual(matrix[0].previousStatus, "clean"); + assert.strictEqual(matrix[0].currentStatus, "review"); + assert.strictEqual(matrix[0].regressed, true); +} + +function testEntitlementRegressionMatrixMarksNewAccounts() { + const previous = buildRevenueHealthReport([]); + const current = buildRevenueHealthReport([cleanAccount()]); + const matrix = buildEntitlementRegressionMatrix(previous, current); + assert.strictEqual(matrix[0].previousStatus, "new-account"); + assert.strictEqual(matrix[0].currentStatus, "clean"); +} + +const tests = [ + testCleanAccountHasNoAnomalies, + testUnderchargeDetectedForOverQuotaUsage, + testLicensingExportRiskDetected, + testRevenueHealthReportAggregatesRisk, + testEntitlementRegressionMatrixFlagsNewReviewState, + testEntitlementRegressionMatrixMarksNewAccounts +]; + +for (const test of tests) { + test(); +} + +console.log(`${tests.length} revenue reconciliation tests passed`); From 0bf3692c247c6108bb40e9ae2af808658d9be3b1 Mon Sep 17 00:00:00 2001 From: YX Date: Fri, 15 May 2026 07:06:09 +0800 Subject: [PATCH 2/2] test: cover revenue overcharge reconciliation --- revenue-usage-reconciliation/README.md | 2 ++ .../docs/demo-transcript.md | 24 +++++++++++++++++++ .../test/reconciliation.test.js | 10 ++++++++ 3 files changed, 36 insertions(+) create mode 100644 revenue-usage-reconciliation/docs/demo-transcript.md diff --git a/revenue-usage-reconciliation/README.md b/revenue-usage-reconciliation/README.md index 87a5cfa..3a35d68 100644 --- a/revenue-usage-reconciliation/README.md +++ b/revenue-usage-reconciliation/README.md @@ -23,6 +23,7 @@ npm run demo ``` The demo prints a report with one clean lab account, one undercharged institutional account, and one unsafe licensing export. +Text-only demo evidence is included in `docs/demo-transcript.md` for reviewers who prefer not to inspect the GIF. ## Verification @@ -38,6 +39,7 @@ npm run demo - `test/reconciliation.test.js` - focused tests for clean billing, undercharges, licensing risk, aggregation, and regressions. - `scripts/demo.js` - CLI demo with sample subscription, usage, invoice, and licensing data. - `docs/issue-20-requirement-map.md` - mapping from issue requirements to implementation evidence. +- `docs/demo-transcript.md` - text-only reviewer evidence for the demo scenario. ## AI-Assisted Disclosure diff --git a/revenue-usage-reconciliation/docs/demo-transcript.md b/revenue-usage-reconciliation/docs/demo-transcript.md new file mode 100644 index 0000000..b61504c --- /dev/null +++ b/revenue-usage-reconciliation/docs/demo-transcript.md @@ -0,0 +1,24 @@ +# Revenue Usage Reconciliation Demo Transcript + +This transcript gives reviewers a text-only demo artifact for issue #20 in addition to the GIF. + +## Command + +```bash +npm run demo +``` + +## Expected Highlights + +- Report status is `needs-revenue-ops-review`. +- Total expected revenue is `313000` cents. +- Total actual invoice revenue is `309800` cents. +- Delta is `-3200` cents. +- Two anomalies are reported. +- `institute-undercharged` is flagged for a medium-severity undercharge. +- `agency-export-risk` is flagged for a high-severity unsafe licensing export. +- The regression matrix keeps `lab-clean` clean and marks new risky accounts as review-required. + +## Reviewer Value + +The demo proves this module can catch revenue leakage, refund-risk style billing mismatches, and unsafe analytics licensing exports before a billing cycle is closed. diff --git a/revenue-usage-reconciliation/test/reconciliation.test.js b/revenue-usage-reconciliation/test/reconciliation.test.js index d940068..71f4788 100644 --- a/revenue-usage-reconciliation/test/reconciliation.test.js +++ b/revenue-usage-reconciliation/test/reconciliation.test.js @@ -37,6 +37,15 @@ function testUnderchargeDetectedForOverQuotaUsage() { assert.strictEqual(result.anomalies[0].amountCents, 500); } +function testOverchargeDetectedForRefundRisk() { + const account = cleanAccount(); + account.invoices = [{ amountCents: 11250 }]; + const result = reconcileAccount(account); + assert.strictEqual(result.deltaCents, 1250); + assert.strictEqual(result.anomalies[0].type, "overcharge"); + assert.strictEqual(result.anomalies[0].amountCents, 1250); +} + function testLicensingExportRiskDetected() { const account = cleanAccount(); account.licensingExports = [{ id: "lic-risk", dataset: "grant-map", anonymized: false, fields: ["orcid", "raw_email"] }]; @@ -78,6 +87,7 @@ function testEntitlementRegressionMatrixMarksNewAccounts() { const tests = [ testCleanAccountHasNoAnomalies, testUnderchargeDetectedForOverQuotaUsage, + testOverchargeDetectedForRefundRisk, testLicensingExportRiskDetected, testRevenueHealthReportAggregatesRisk, testEntitlementRegressionMatrixFlagsNewReviewState,