From 5d556faf86436683d6ad3fceca087f2d18418b03 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 16 Jun 2026 04:42:44 +0000 Subject: [PATCH] fix(roof): bill at-rafters insulation on RdSAP 10 Table 16/18 column (2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `u_roof` only implemented the joist column, so roofs lodged insulated at rafters (`roof_insulation_location == 1`) were mis-billed at the joist U on both the API and Summary paths — under-stating loss, over-rating SAP. RdSAP 10 §5.11.2 Table 16 (spec p.42-43) gives a distinct "insulation at rafters" column (2): the rafter cavity is shallower than a loft void, so the same depth yields a higher U (200 mm: rafters 0.29 vs joists 0.21). §5.11 Table 18 (p.45) likewise carries a rafters column (2) for unknown / as-built thickness (footnote (1): "The value from the table applies for unknown and as built") — band A-D = 2.30, E = 1.50, F = 0.68, diverging from the joist column's 100 mm-equivalent 0.40 default (footnote (4)). - add `_ROOF_RAFTERS_BY_THICKNESS` (Table 16 col 2) + `_ROOF_RAFTERS_BY_AGE` (Table 18 col 2) to rdsap_uvalues; `u_roof` selects them via a new `insulation_at_rafters` flag (ignored for flat / sloping-ceiling roofs). - `heat_transmission` derives the flag PER BUILDING PART from `roof_insulation_location` (gov-API int 1 / Summary "R Rafters"), which also fixes the multi-part dedup-roof-join problem: each part's own location now drives its U, replacing the unattributable joined `epc.roofs[]` description. Worksheet-validated to 1e-4: simulated case 41 (4-bp — Ext1 rafters 200mm → 0.29, Ext3 rafters As-Built band F → 0.68; roof total 24.8350) and case 42 (6 variants — rafters 50mm → 0.88, rafters unknown band C → 2.30, joists/none unchanged). Case 40 stays exact (roof 35.340, total 441.1606); worksheet harness 47/47. Corpus within-0.5 66.9% → 66.5% (gates 0.65/1.08 hold) — a spec-correct shift, NOT a regression: all 15 corpus rafter certs carry redacted (None) thickness yet lodge roof EER 2-4 (insulated), so the open API blanked a specified thickness and the spec's unknown-rafter 2.30 default correctly over-states them. Recovery needs a roof-EER→thickness inference on the API path (follow-up), not a change to the U-table. Co-Authored-By: Claude Opus 4.8 --- .../Summary_001431_case41_rafters.pdf | Bin 0 -> 96715 bytes .../Summary_001431_case42_50mm_rafters.pdf | Bin 0 -> 79563 bytes .../Summary_001431_case42_unknown_rafters.pdf | Bin 0 -> 78790 bytes .../tests/test_summary_pdf_mapper_chain.py | 66 ++++++++++++++++++ .../worksheet/heat_transmission.py | 35 +++++++++- domain/sap10_ml/rdsap_uvalues.py | 60 ++++++++++++++-- domain/sap10_ml/tests/test_rdsap_uvalues.py | 63 +++++++++++++++++ .../worksheet/test_heat_transmission.py | 38 ++++++++++ .../epc_client/test_sap_accuracy_corpus.py | 17 +++++ 9 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case41_rafters.pdf create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case42_50mm_rafters.pdf create mode 100644 backend/documents_parser/tests/fixtures/Summary_001431_case42_unknown_rafters.pdf diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_case41_rafters.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_case41_rafters.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1f21db8ce7efa149b043f2d63950bf6f3166cd1b GIT binary patch literal 96715 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 literal 0 HcmV?d00001 diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_case42_50mm_rafters.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_case42_50mm_rafters.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dbb3f4c8a2bde0a2543d5f79797d091b80d51ae6 GIT binary patch literal 79563 zcmeF)1ymf(z9{+#5}cq3Ay|S3_u%d>6KrsIcL*Nb-5r7q5F~hT_u#?ZU4q`>JA3bQ z&ffQZ@9zE9J?pJ=da^p*GhJO>GxP7R?kav$q;f(cGz_%NhzvyZL{_?{TwHWYF4hKg zf;#p(=2k{@aymu^c0`QOl?psO2A2BJB#4jC{zK9~lF$iSIat~gvCv7H=-a6?(m!rM z#Q4W{M2yV;_Kfx4X6%nM&A;Ww1bzA+a{DB!YigipPp9ahYyVgfaSLb(=s+g=_Qphv z^z?LM1|~+v_CyR!%+Ot$TiGgF>*yKK2^lz==ou)83eX9d*xSh&*a})%SX)^dK#Rmq zC!=El&4->&#Khd*z?M$LT*ux($Ux6Z-+)faz|shsF$*grI}eYYy{&z_FeN6;A zCDG{n9F&WJa@KEq&H)bS=IE<7d!cU!x;plpyFY332?$i8>c7R;wSOXj6ZjtKZRYd0 zTg__EvL+`?izb>fz-sFX%9FdU9#0t0h5euNP864Yy;N2)e4Esp>-!%b?aN&P z=SGT5Rt+SmE%>2<2?Cq6^7ZZzmMF2teLN0)%g+M1hgB~Wg0R3rjr(6Y@Kpv{z)^Mf zXDR)nxeqj6KMy+M1&Kb>=b_E&D`?iw`2=E0(ya%!G+P_Ec&u80CYP<3)@NtoU4Aoq z1#GsjK<19GGN{bfTNZY9JVL4+*s%)t_u@HDr*>;skH4pvde}=1Ic8_-dzkGGPE9V) zckgU$9QSuJs=Jcsdz^^s%@wrVfQP&wR!3nIlgo0M$0jWUk-REDI~-pzi+LU^ ziIZN%@6LW~y!jN-c`v6#X7}7qqa35Ah90et)IL0HGq(&i5T?|(osp1-rc6wI_NN1P zT*$TEM3{vPgWXlYki$ivB(=d9Y>1*po+aA1B=zp;I5PaLG-PC~^ zKaTr|aUWm*FMNqgaE}ZlrDA$#!%&~8z5+Ys@Su4guL;xnc_X5z${W`k&h9S7{Pdur z`|Y~pqp>y#eKRBSkaGqdYe&?U8*|eDg`2Hcp7R@>=b!dkPor9t=8;X7F=P@{7M#bJ z>ds8rd?WFRzK)q(ad>G#$TB4}LZ!d3UKccP&)_cSC77AL?!t6=-uQ9K8g%HMVxM1v z=n>tiM#8$D?ca1fiw-n{4-i^CQlz<1llle?$iV9 zU@3%c>}-JUHzl3~1O`WS8JpDox=MW$?P!N>@oS^cixZc;<^sAfGUiM8)0c1O7g(_! zcr!UEk9I)YC&6QjyLgT(-sDcL#bbf{iLNAXQmC6v7%J=Ste;qRuZBdiaG>?QoA}HGZK<(GAxN2-TeDK>7X14h^+qP0dgNJ_Ya7*+fz>ajE(gq4CG9Z}Vh#SGqn6 zD54~kOh!>4vF&ZDE}q(riL2H~K9k(|R-JyeR+1yS9akf#&#Z*7H7iQ+#5pD+MxtUU! z#z;&NZ;yoh13NQtmgLN}yM4ur`th+HHRhV8*CES<^fPA{1RPGiU||<_=9LEz-ex6z ztCEVw>mzs1xvftRXCL;2qUy=XIJ?Qda@ODP>t<~i%!z=f9m`lO9nKY&>DoddlauQi z?GM+DJtuuy*zgWBj&B=2B?cXr{fGwH^;4T0T==-*Nd#zM&y$K|liu6!^YInOfcjes zRX|LJCCbm@L5V`QoX4IIb~U}_S(VcdFr)Gh=~^US;nl{$pCq{;3jXma%u?K^5K!d= zi6ofijPYyRqUB`42SOZ_pXT~CxOA^9`_R&?Og)*ozH<3h8qIZ8Imi7{pGh&g@vsigwI6pVSQ>pwu*hEtQm8^l9M%RUS)_D->5tI<2N?7Yqyir^G^2hL5f0n=Q7X_ zg$bz?&=0n-!RtenYNuJYZa74UOq|BIU*E)1KqYmW zKAr5G`bObUuIq9>Ip6Ii_()dVOT-_`7<+x^OV5TETySx^U~FlG_42 zXuV&5-JTOmCm`Onme=8(eFlxV$yS}OjVzF|-E}_6TJvV^%nRz4XAPfSRTK1}r{d|@ zBm~IHv1Ze3eSbBwgAzQv$4|^~_@01oA-r)dT zeZKs4X4rkM;*f%&VH@?*)q3&up`lZCpjz70H2zs#L&dtFoq7N>aH6C65Ahjdi z53EV_xBfFT=}N>_uSJ7&22ZGT7Zi=F!TVA{95U?vcza>w+P*oBUf% z+ohVh-NjAE{WK#qZ@?wasoOjuK_EVzenCagGp03>jkkC6o-oW&eT3EhucwRX^O6$~ zAo*IfyDcw}NPJZcF}IH;2-AtJsJ#cd>dyKCspQVx$nxO~H$RGWKh%~Hqkt{CS1A!@ zuBvz~-o3OHr<0pOgB9dyR?1xyuX~b?0J%SOi~&;@3T{_3?7pa92$x~)g&%LN;Z;%k z_WOAJ8V-@!Q4@5$sXWGMoDi)hrZvIBUkj^Ca+(e%;d7}y9sz1p5E*Y>qc`c+tQ#zq8CgP(TLaW zD+@IWf3Z@0f8e8hI$obn)7WNyc9U@?83DRPzF{}^jCZnOw`Ofa z#w_zPv=Ys*y)R3`;Bm9wQ>z#z>LY92mtNX6gc`{fH{XyQSA|1+536mEhrh=RV)={$8AHVRwETyy52A?Xc12c$vCNNxHRu+}(AbpKgnO z6r(rQ9<6%7&Cns^v*X}-Pn0BC&TmFl2Rv`ei(WN|MA4WfH|at&&VA;SX~6y$2XBug z!QmjD-$=>oGXo{uaVNA~-c2g0G~kMY8vWwnc~KsDxf504^X)N-TVLs`@uU-~_{fs5 zRcF#njf;rXTFS9irr>@&mJ;Z9t=*=C}U-(di4>T6zqG+c%T- zMfxu1t9Z2_f}Z1NM-lZRP3WD>){P234o(7*odTsVa|9P^%x`*Ltv%n!KR!}Wb}OJH z?=N0=IL|Ij5BIctttG)b_JREOi%V3e=1bm?eqT?snu)%h;?+3R3wO>ld=GWvsL3{# ztIP}pdFRgb?~?C&Md01)S;FiR_mm1#&+HnSv3m>(q#R~1< zAb(#;nu8Oi6g+HqYir}0ph3U8o!482yT}Iq-H`e6v&{Xda97QB{W5I|FG0kd262Zz z|Bcr5JYR;xXk3`I3Qyu#K_~V#Bc6UjI@5mti#;WZ3>?F|kb6dF=(P+AC+ce2*wnex zP^|Jy#-?|`yA~PJ%w6{~cK>(9UJa_lo@TdI<_WEnzRh6+u`)Pu32z%U^Pa=OhVoq4 zHi_XTCriU4n{qbDCa7$%us-~Z4`>*frpaSNN{lZI-NFymJyNx|YEzfzP**2sbeH!i zE??J~<=uRuTKh^B)=ryEhw)jOdE+7s6(I&P>&CaDq=O(zWWrlF7Q8#yFVEor{PCLl zO^Vj!FGMWGE&}K^?aXXF^R3rMe8&-&lafFY(#SbKKKZ!?`pR9rd48gTMzu5N32uhC zkTxdx`Q!oJAB^zcm&u@XNKod>&9~-D&IW`Cv;0JObC!g!sVzO+o4&bOCLL$@o*5o(0e1^(z6C!*i6Kjps#PYaf`K8_W)8KGZjjbFhNP zuD?UCc`d}+n7(v0NgCV4NYWkhyy0W%N&sI|I3OJMJ(QM#?XOm&a7%X8xMJP5p?6bj zW7K&|X;Ha6Eh#(CNyAYi}W3OOTQr2WE-Oqzis5utZy&@8s+E7nrnAQ~vGmX&LYTZ8df`M~tI+@wm?Dl@Aji3M zH#nqN%2ntIy2U^iKQZKUp_;qEM@p1(xVEmR!?mF#XqRvvK!2IK4(UDFk#d<3O$hdU zg^%dMUy`|)-7aru%js(q_>h;4qe6mk$b`V_S@|=2hhJn=tDm4chj;auJ=o_K*`$@P z7dH|OG7@$Yqewc%11TXrzcB{$h!lSL$lr=&$+gYDN#1y4Z0{id9S6(l_CbE?h4(Mk z6Ouek&zIHkelO#&!Z+5IWgGEQ({e`KA~&N3DOgfz<92JWw&CzU3dv!yh(`G57Jh+Q znMhYO9?m*|p2)?;;lReG7qk@f1!Y~E&M(0qcO2f{(Hi7rn&gKFoMoLic&|mOXvJlx zpzJi! zf~R?GC^#O?i=$c|X_OAVrk0H>^fufw+aZ28O8h><%PCKZD|caXo^Q1Ja+vCC+$J-L zR8E9nZsK=Ua1@(4Yi_>vXpxjougABhtV<&*+JlBW{kL;GDai7dcMEMLpC9!mT<>v{+c z63)wInF1L9E<+=`>yZcfqcmp$OKSVesYpL(o=pG!E6MZ0NDj59(&D=46^$g=J}3qP zJj!3|v{yP#&7yO(SK_usmM*GTeCdTFm$G-=*>?WDBffm+;o)y}vbZLgaT5K4rNnWr*)B;iKb^ z)15fxYs{|bx}frSTL%gPO(82qRbQ`p^2+T>k>+5pI3Qx@M!1@Wi)Tkml-Lu!Rynr&VvPJ|y_mK&mEV(6|~`Fa$*M>#Ee&g~)5 ziwzc7wk3>a67=99S)chi_dd*SA+GzR#KJAXYKhvRWfpzZ38qh1}ce5YZuqCF|J7!OBfi zjf|zWyN%ZcwJRMWfyBdA!BM)s)XxT&DxtY`N{v@N8yoOw8|ow4NPM5wM!n+Os~3Ta z2@xYn5+IKmb-g`NUgwZ@-+$*edvoN@t{+Yyu!w{)mmkf)*_wLt^_O~?&fBTTBT(up z?258N49A=FM%_}kw?gKU!}Q+J8$i&L4(1&+Oh|O|n%ABS>&<-J;TdmhCQouggbZ;3 zA5TcTk}T^0j!0n1+bH6k#CEF?kiI$UAPohF`bCo=rSE75^%?SP-X&_?#q|s*&IoC5 zuo0)M(YvwBFy@)>w(=wj^!nQm4w+U3o{z0>HWm%i;rhK2hIo?u6F4eo$=ynIzzdh) ztuSo}@kN%foNP8377K!Dz?S;8usdX8Q2Lmj6w>C{hzP$HHyutapL*m-Wy0_@X*5F= zb+WIc__dYymXzwmuMZ>CDX z?3$W~HW#sratC=bXq%wA?MbZN0rxS2L$Vm(%hK&FA2?X*UeWc1pl*3gilg|aq@xJv zMD~%-tA08;^7*#TaEy~sxejkakf@`s7kFZkV+gCq8eE6#@Y(uzTPpFw4)*sCcw4y~ zq`tu;U@^!o+S*zl!Iv_XyV;3v!-V(>5^Xh9JYzPyfme5O9)7B)#k(kQk?c+-&Klf% zTM=Znezf2ddm2HDgUwtkP>aRjaipO#k=fi<{z=xX|Vsl$56)Qq07uiG+xT$9^}X%B_M#y~?Ti5)lnmB|+%|{lS|wVaD1#Qayg2T^?9bUQW->&i;{+hmkqGgK21MynnY=a(_%>En`J?W8Z$C zVozC0Mg|eaXL&g*&qkhgm@WCzChBwgoM`_ZL)bq15JBDdaqQf3a)yr|sjv_O0|Pg^ zgiyF)an*UL2<#0nR5veXR+!3GQ?j22Z0Xb(-&rUW;2=at<>+WKjcYr zm#S}0x_8S=CA2O4n4V7T0MFa``){&aebLMcW3C>nw~zifj5^4AXdg$p=U4)LBo$>5 zBS1t%yU0Xw+c-q^uDXNaCgrJX7sE$OGkSPw5{u@ygB7PI5;HOqSXKO}vF35|YU;08 zo7FhhQpSyFBufd}h3<>^gVnHlY#@O;Zwlpb>mKaI#W6h4$2}LrFQ-r zxa1dl>jHGI6~qhUX7l1k_1M2LH;!NO>p1b(Z%V*1)gBzdiCt5EEb3=9tTzA0{y&zi=*o@!oQyz;^pAansaPnn*ND%Ag!gjpzda=bRA z*uz6cgz;!)3qhV9*(uT z`V0%p-Q8X0B4zJ7jshgjPccX zwNF6J<>l6!Lmq4iBiW~?r;?JAmkpUs;p^+`@f#aztPRa&;lrDY^$tiC%h!e0_BI+= zpWMEHZ?LZ3$x=;M_&gPzO4b^u%AlvYDSEzJG_!7z){d;;4;ScM?-m*!x^P(eU8Afz zPgy|Y_KwyVqUUG`7izj@9l2v_rmn7*S*`tDL_{Q4Q-_$ht$Z(o$!$RYdLfD`(7inc zEh*u_2Yg2|ou~T!5b?6Zap9RWtb(fQr|gZ(h7Vk%C@kmH3ABrcKFy-gxZxA9`W6Q3 zzXX$l_a+D?+y)QNwbK@H)+TWuM^|Vs7O`YenYrI*4oMhig~e zHSBxQ$#1j%H`DEGwfi_fihq2z8w!_s*Z%IZp(K6-2UBo0@eSL76aV77BelU$>2Hio zwckdFv&!@9)!K^l^U017145=2P)k|Ja8CWGkzczsml%Z;zka?)f6>8X z^Qx>o_q0-Ev>B$E>?fxftYfPqEK4)Z#b>&gxg^n&gAf+P3(^zmnoS%EFeN;)e{&hZ zI~os8r2XxK_zT}L1fG{g4aGzEs<8r_k~*RW(IB53~7DDExY?#>ah=PMb{iA z3PIx`Rejys)o_OFbJj>$kXSuYXa85|t(gK~;+MM3Vg3G0SkfW1Y@Wqh*Aa4uEbW`) z4JZ~=4^w43;z97x0|q=vIg2}gvF}v$28Ns}GBp+0#mlsN1-Mg<(u&~Sf5GY0lc9M9F+a}7gUyjUsKlg6E$ zojz@Sjj-TmEEk52>o5&6#ru+jgM-jBWOu5%nTwi>fl#UCOgYrNGUS;Um`{!`;OzFy&4*JzS{O+dLy8lI#Ji}SKy1ds&{_ z!p0Kw(qHj7M)2fe-%JS!K7QC<=QDgGNnOokWKmketueo6;;6QRSNOyK2T_lqzStMp ztGyiAxBK`bu}92j4rie#q0M*KxsZh64R9A(MScD3?ASA-f>aQ?8p`>(1&St@XR+eo zDS-nd)r?(R`M3~6;U$@5v9*>)!Zi*R9A>bSzOMejKrjC%3H;QwwBoNNxiix{#->wB zQ(}xU$5dR*p$BVRoSf=PpRV?&$g^p+w)eM%JHp;che<#6ysuZA?G^9sQI!YV>~x6p z5-ze@&JCO)_e_q@KSz2Z;pXPLjMdA60M5(LU9z&0wW=OBI*!*d?XnD$4wXG}=E4d# zb#rmf#_Cn2Pbp;LiTh>is+QawZmDUJw)SEGwAD>+6Q%hOMkEPro+KSaaLG(wnJ*8=zT8z zCVglqAON?3{y9@10#9yUzC`$Ekns(@Zp|bsIO?g!%@isHt+^HqRM#~UW!_;Jeoc81T8cj{i*V#~DDtH-;?(E;`ou?X&k7xY${Jyx?znFAjbh^t{7WT@e z&P5F`c}8`Hq=FtC?JL$8ui$+Uguc7)jW3KH=_kTbL(s0bNiElsNQen=p_Q2Hb|DxG$i`khC63Y%_^jJS-{ z*!U#F$atWj=zEgVqTGs`J0bHE{8FKI(EX2(~e6l#mvmm>P{)#x{=0NPw(;xN2R>MEZ^-&EoK>#^=^u+$#-c zZkU=Lf6zs-(bU3xmBj(Re6d>p4!)>j$61Q=K8#ID#*>ft#QQXg%>u1y^{990yF?5M z1}4TaJvB8I>yRs&eq(Uxm#T`Yo!$L+oG%*%KFcX6NyU4Ig8FXeYP*V|6;gmJ9Ajgf zs&ZVM#1P4o;WvbD0!4B?H%h4IVLgSf=k_B;fZTyFr&kmn3*zsvr}pb;r>y4`wT1B5A@~G=h?7& zvMR+2wu#0E-{WJOa-{G#4)GWFUrKaF7;Bp!9f^FWo zl!oo|u)vq7K9IIkM|2hNmtLlY-ws|#`X~vx&Me8YPNgarp^0HEZAP$bia*35?4wQ1wwS{lZ+q?2)On=J{f%x4X74VzN}Q`W~IJorlrm1NNomO1qcPck4bvR;Bv#d7hMxzxN|KUm7bhmP`bOm95f|r&%Sub zLPnpmZDR7<#YL#kfU&i#@z<~56WfuLP|f`?&9E?(1D2LrgF6Xb$gOjPm)rLKooyH5 zwnOnH&m|W_Eqkqv|3TLG!s)SJ0(T|JDIsQigDD&{9Ir!>kGjVOZug4DpEi3MCq@qx zeod-CFn_j?*5M5Y2eYE%=;RQC-72Q%ZnwuiEgb*!!x(!G!D}31&B)mzrUa5W6Jag} z8kVpTB;qmn-JKmiRxwd4v|+#RS8KUz`92rv}(TZBInC8DJ&99YYULv)Y8-x-u*Gox9huqa(VtP z-{luC5r%i3K(w4>8?AeMT)cpoow|C|q+&6`4tM26F6HhbI`+p3@R6lUw8{yhKK5Q5 za#7?$x#xpkEhK*3vBMvAMSvszzDFI%I2S69ZCo z4u~ALb}4rjLv_4>UVWmTp`kD0U|~&<@1^9ed~a(LNDkY}B{He9nX`s{0aryy0dI$? zNJwYZ*xcza{Cgl%g(d}sdgov#K=FVL^0_ZK7#U7586@O0K{BnZrXcWPqBW)tm$gj1 zWFP>YX)*#qZ+-6Kif#GufQ_5jswDDa&fR)?J*2{92Epp9wv7%yJH_7ifwyQwsj-mY zv+0YvI*y6wz5CreeX}aB=(C!*n-o|03lGDSwzs|SJvA_CjBRvQ2QOZ}Zl$woSCg!I zDfYnyEItjZjOMGNpfONZ}XRO0%ot9HI6|}5>xfaKqM3tl%kadIORf89gwo8BG|x3bdJtG`r!yS1kx+! zfA+QaXohq)_=xeb9a%M`T@J=w5-XzHtu~>?t|#f ztJ<_?Nkq=iCnvT6N}}<>MN|bel>&n`+gcInoVW4oHvYLJ`P!O&Ej2cz6kOI@hld~x z?C6u=-Y}XIo8>#X{cG>%hU8c$JKP12-I^jCPIjvr7y|k~(13WA&6XWya6EiGaT<)Ty6zwM` zmBCT>Utdv88aZW`RAG5%?NA{Vy-(;I(7USP(o4Ks(JF0GpqRge21TT&y}9dn3a*2b zQ(%A}#xB;a$K969kXRmRWlWRsN^gA9>(?ENcHh1QAq2@>gwx6lfeFCuos+Xsr&l4=ef@u?1M->VPKx|i&?)I z?3xTa*xBA#Ha=Whks=QWCL<#wc}F7BX{Vv8to;3Fz)w>%vj%G(<79o$d+<{w6*UP7 z+8F408u0|NKd~Cubdd?-x<+)@!|90lev(=QvKx?OS;I)#OIbhraS8(7-P!Z&6&U&G zxT|8L6P11>=RwXuZM+QFa5f~06yo?TXEX>X53E)y!Nq^z>Ny#;PA)INeuKtX0V z(lw4vi{qwvm8zAfbyLCBs;yyO5?>ET;BUY2`?rgvU5MJnZ2Y+nah3>NKvHOEaB#4< zcZ0{qM2V>dO6ixQ7d#ngIMUKGD5$77xY#LpDIFRu#IMjlTXigbm!tSOK3N1=)SmT{ zTl#Hx|1`zJz5zWoARxD+V=%EqLq*0-DXB=azl8wl<5hm1xzYC z-7QLu4j5cLL*3x1_Z~;4O3d$Q-qQ8+fm%L|vNQIWWEPXRxOT=J_*6CKW`;BuRlNL3 z@=54PtgrWqlYQm~x8%26nfCXrv>-)kP0#&MF)q5P{`T?VZZ>9irAh??E_e&9#gqHazjXJR3DD#f8IS)5?7$E(UB`q^vx5=Gz7SAhrgb{Ykc1Pm# z0^RAWt)}NrFIAasDr~08z{2BYk!pdozNeedqC~HI!gkq~Na6-01TgK&zs1}l zfuYSopU1vqZPCQMzCXBT8JmU?KsuP1NRlZzN1so*Ao^~!yY`Y07mYRAqhXUCTI&&m zlyX&UEQP@YOquHjcO{z(l^gbV zl|{(S&rMILL~9ppH|)wsS<9&>IUXjRR#UaZJ5b|zRrsac+76TH_x2_97M8utT`*-W zS08#Cg8+F#NOySko!viaInrdZrc*fA?BfN=$anNND+Wa(rMD7>c0z**h9wyUws#t? zClkcixUyy~c%ZU9hyKiO-!S)G+ea^u;hsx?PpnLBgZ-20PCST05JR5b-jY6S=x_cg zviL*@t?BF&evIrN8D!q2{#-zUS+VR|p{gCvEC}fdWOarQeCdV=05Jo4A@*2uJ2^j$w$ZmN40JaRk~zeb^5#M2CM}t$L0( zVC_{nhoB@d+eX zk6I|%p4>=7TAcr?Z()*_`t)FEIrFIuCDn)67U$1ePoxS@5cw+FCwm9rF+(Wml2SVs z$j01sxV67?Gqeu$dM~T(Di9fgbig(?mIl`2p4=O&Ue&6*ogcT{&!E>e3zeK)jCFNQ z5P2LWhJ_{Qne{z_We9Ixn!LSI?3uZaf8;W@IVk_TVn|7>oBw>gl&Oy;JcH?1?7c02 zYO631`wK*el0|{{J16T%-odpbF51xAM`=)(lJ&9G-m!L0^6MYfgz01~P44a|_ZTCVta}$IZ~Abt)58_N$-8|Py!ua4ramlqP92Z;&PdA! zl^9ITs?AXR+7>{l`BCim-PyQ6IxgD}bX;NchBXj+5;T*f{vDYMZTQ>KWb|N8==H0e zSL(cACNiA;{%fzKWUWJJ%cbsV8kHeId&bQ`QcBB1$nPL>X00GfVollt7^ZP16`c6K zgw%dLq()Oyb7od%JfcIM=s*lGi!FP87n!fb<-U}xWG%D$)G*Og0c`O_9wAM#-zx9X zs50H5t!0Qvhzc<=ii!%_%?Bu3YY9voQK{{&E)mU{=kb<_o$(=GB0|2^lq=YjC;q^3 zCdIJgqiUjc7Nl5?6Z^HFZ(aE1$Imq^-QH^9nPBI*xG02=K^?n{OEBHtwdaA_j%WwJ z>U?WdwLl38DM*fa@5znm$NHJnAu4#Um2S0L_8Y3-TBtW)?d*I_3$yr~krB=H{O)dS z4i)-^qH{r;x02Qb$DC9^(B>kX$z4ZAur~&t>5hIp>xcJP;fy37+~r2EiO>*W%&wRO?aNcqg`nX;jo8P$;=Z9g?COI|hl z4C+i#Y+%bNtL7Eu+b}TN3P}i^pP#qlw9>b2UjM#|F69gft3z0Uy}GWh<$dwoXYrj1 zj_~*@67H-PxAg)1>-EKRrybl4Ty)Z;*C|Vy@5#y5*UM;reQCy6H#V}8{T4%j3O57~ z2M4by=gDt~iYXMw8;-lbzYV+Y3mO(PTH9PVqG!|wm(YBWuR5E)yuEEKiV8Xls5;r& z-iG5gFDKxyWj%eEOZKn)E6zW30!t!S?cBx zlk&9({_>xHfyfnMgzD8@Ruq*{1LIq_{QV(QyF4r1-uD@plkULqLm_PN3Z_@Tm1IL@ zWm}nH@kucbI>Nfa`isegu{@T~uYGdh$sgA;BE_P^}Y2e;GUOny5(>LI=M}C8|z(joCijRKVOz$R&uBOAl zKw9iaX!wDKW@ct&Oq|tmsm69tda!2MiR1DkyDzCvPm3|3Qhuupp0;efB?_2(y0*-d%ks5T1IAQXaL&8gyi1_ZPzl7dNXgJ zV44##7Oj9|W&)@#w`NIEds_Z`fojM5&OT^JpE6PTg37w z&C`G_0&EdrivU{$*do9d0k#ORMSv{=Y!P6K09ypuBES{_wg|9AfGq-S5nzh|TLjo5 zz!m|v2(U$fEdp#2V2c1-^grGfar|qYxBs*)V*QiOTfi0pwg|9AfGq-S5nzk50b2yv zBES{_wg|9AfGq-S5nziD4*MQT%fR+mt5LWmyJ}pqZrgw@0&EdrivU{$*do9d0k-JB zwJl5rB&TTm;}E02cwc2*5=EE&^~7fQtZJ1mGe77Xi2k zz(oKq0&o$4ivU~%;35DQ0k{ajMF1`Wa1nru09^Dx9v3k%{A<0p|FkY*|C8QZKobkTorUBvjWwNL-kx`^XX+NS|s1n43_7Xi8m&_#eQ0(23eivV2& z=psND0lEm#MSv~>bP=G709^#=B0v`bx(LukfGz@b5ul3zT?FVNKoVddcAv9q@|(6K=LY?Cj}eQwkpH~`vj|h%JHyAXziMA zljx(c;I`fkU(t4t2RQV5mn!w(o!e_eQg_;$^2hhJ)dw?P#t=`aMM{EZ3KLN3^zZU# zU55K#`efN-v~W9pi2PjoQRN1vNUe_84EHKA0e9GBH&R1^(2Q%bSS=(xCc6|YUDQxu z&H5fG-<|PE1iVmRcfhTw$IHACg~ELUnEW`Ueuh+@tOLSv7+h&%e3ATYw9r`>TO+d!L>FS3e2||)Wn)72-o2L7$zCoNn$QMzfc5NUn6%c z#HdI%rKBaI(3Hxka+jM$^Xb`Z*s%A*8F?;p{zOG_q=*beWH-wOG9ft!=0v)+9d-8y z@fsQ4f$|Jq{^_=Q;r6EimdW4SD-HjwU!Eryy_W3)stCw_jmb&kxI$D64C6SG(MeNA@eeE=UQ3!dIgVqm7%q^ozso* zCQq3-POobH?b9{aZ@Mz^oKmqLoy!e^8||zhlCIz71KYs$zjAJFp}}@DvN9;2K+J8F zFEhN>shuM#70aoaCOy6Nb9QIkvli@BEmt%aes+Cy|A4!)zwBMgy2q+(d0OglqCF7EMt zi$$`FM{{W92nE)7TwY)7o$fi7SZe1Ai$-zq2D8e>h{?r@KtFhq2sXhmR*^_{?OY+% z6shj{&d0BjY-(dlCtXuFPxMndKeRj_1DQAjnRtR(PtU5!!}HfFiR~ZvK&R-SYwu!hK&NEuV4(E4@W)O>=;;J>>Ozps%s8Gyhv!=xcx7?yuYYzs>)a9-7Bv zK7UNm{V~upu>7&J#~rdUGX60=F0(>~I~%lMkFPy$%tBB9N4m!yu|o^+M_TCXe{A=c zbbs3py3OM<2Q%~E@_x+kuZ4J=GqXb95HzpHH~6>0L-+C5`#;9Pzo-7YNc*RQ z?C*vn{Ue0_AA*eG?}Ci(Z}Ieh7+_=my@bkYX;COVI2 z%Ey!}94xeqY|!l4nHgxIXCrn^=+(ESJv6l)5#!?zUntNi7}!}k*yKpl9}fyu@H&V`uo=Rr@FZG*wBvIac)MUC>hEKnF9}{d_D)t6F`=#v+e{nfnLE z`-&_vc$n3l%d?+eGyW7&)ap3I5!`xjx@s47ac_K@(MTkICFDLO_lagl@qI6j$q2^R zlDAbs^(1sBe4ipM)FuC8m40@iCpm18mg&t4{uK(#d%|EApZ1nB?fM0W%l^+%1y513 z*`xg65ca$Yx$@jepDG1y%OH^Z#=Ly-Iw}6}$_In2*ZAX-4{U08CKw;yNAXi?V*>;i z$sm61Yht`%mbnVfp{N(ykSHfCsZ4kdny_F?w6^D#fr+Opb~he=%-oMT=4{beg$+Rp zmyiZ|qm!L2eHp!u=bfRKzt%OGpES5a$;lm zGkJhGwQTZy2>&Y!0q1Y=Xa|>Xf;(v@Of2~1B@cKPFrHT^ z4ctok3u&OgynG#uRKY{`Y#|<}kSUI^jdmJUydd`>z&>8&$EmQ2awH$s$$1(#*wmo8 z`B0ho7ikczwPp}ahyaKuAx?zO-P+S35-HLTGx~)WE|K=;Gq01O=W}>|_XK=DLh!~q z#N#*N6W_KzU~{cNU=gtCeD%Lws72D(U&zH~H(K?HTI@xqniN+yx@t}hwafb`{O*`P zBAor+u(3XmYb@fyzDgTZ#nHot05OjGTr>UFg-r0X=$VCyb!XTQI;JJCT2f7q4oA|q zKT-kbUDHr#`c>4smuz`x1!9p!M%W{RjcFm}o1Gs(ogINuIgJi(5&>TmCrmd?G?3x? zN|JBnDrv`f78mBPP&#}v_a~L7cx-QP%IfXQet`7HF5eWB$I?IRyne`gJ*A_MzM^fatlt4;bJx}XmP!d~j6 z?68grTX0#CwRyQxRdG;tuU<2Z^CW!`f_2wdk}s-AS?MwUz115Iqd|QttJSWOE`?BuE z7gB>Ta1z-M-qz|YFY;t_dkZCBd?k|ezQq!~1^X)pP!PvP8o#L&(abIxpBD|?Te+zi z$RwmF&@Ty96^M}Ro9*p&|LD-$Ks0GRY9yKc6)|-)8JQO|6Z3b{=j8&foC`Q0ve6Yhg!J&u?BiK10`O^v-^oiI=iBdSHPcd8}Xcvh|sS z8p9;pIPp^t`=Z5zYHOL=8m(w$r=@n{HedGo{V6{UqnMm1R*|r#GJ=FRAK-Uc>;{_6 z{ZXcLJWwb0n``q%`?=o{@Zc+q1o{U}F6EBk<(ZvM8hc7dI>Po1??!`~g# zguK(}*&5}XIqni8NV!)$ZQYI0pn&0-R&j}Od#9+ZA2;a-?6WUe~I{VJn`*LG)I{Z-4z+(b&53(Txeg<(+3UqSFt1P zWI28N{_7@~*7i*+BZv1p9?yIgXkiT0$!)Vn(4;0^vz}}2n6n;wq+2T~1gy;buTriu zEXr+dONWHQkkZ{Xzzp5O2m=BVLrRVa(w&lnbW2MLg46&bAl*n9bO}h8fJh5`xIO3F zTRrFe*!%tYTx+da*E8?4?t4A=JrjTG2Gx&iKo56wb5aYl8l8USSp3w9XDOxK^u9Uk ztEtwm;QnE5CclhN@(o4r?%H$uml}<_;D7B{WQZ}6Wg0{@q)uMoMD9MKR zaLV=pP;b1kMON8nk(q#4dri$7m@d9pG zXnnr_u^Q6Q2)*O3+1-Ec*vWA~elA^+zO^fGbth;xbKr8HDu6+A0T<-QVnafru!7+V zFD;!dw##%1VqtH~Wd*t1<%h()Pl$5;h9;dMIRtrp0DF^w$Z9sT7EPS+wgNQl+%
)GEfjO8sKrD9(imgyRDMdg+1{p&??@Es`%&s)ods|) z*3QUsJt`}uGAW7k!+S>hFuWYNUHCP08OfzC*vYZD>b`IbvsYl$`*y{&;7!LU$|`6& zsStyh9FsB%085vrcfMa|5HB@Fu*(-<+;f*scf^v4;n=e*$jXRJY^0P_w<4kyuavG- z9ELOuADEo7tBB&VwJAGVR>9c}s(;jfdvUdt%~Vf<*3Fqfs?rd!zd<3NHo_*}ptd1P z$d$;nWl=s?xmlCVmq2y)B@exeeH4;q4LMj|<|fxD5yIqAS(_{w=uFG`QQtch07)| zRAd*YpU7g&E26z%G(Mrd7sWA05{caLxbBrQu*g;l(s|q&IZxvb1>9zh(Ids)(Mja* zV9#xO`I3@uuWeR-5v4fngVh&=c3=b^&u^M>;~u3O-0@k=7#X1S_$Z# zQTkzPX_dN|Eum%)CbegM+0xc5T*LXu3=Q}U-msw+d~k9;YHL9{#U^1q+i=it0%p`x z(W9iTKC4HKcI4_{VxLL+$#yO>>Aj~e5&H$90DbU5vDMS{a+M#7Ksos;L zLze6+oKyn#v9Greayd3buMb<3Z<&7_V5|UHA!Vu^a>RF|b`_!!K|7fc`iOHrB3%Z@ zZo#d3OptWWaX2--uK0BwkaW zdW~x{BG}MDN9u-Pyf=Hm7FHrbkhei(fxSr-EW=*oJnr!YchU?a*z#31k?W+{{EbPo z8M#TfBi9?r)9+?7ji+5W7TShQ-kLSeXP(!rMyOLaZy?IoP>+VV#ck+4Ks+B7w=}@( z&o>b%&nBnydWlXV!))i^<6+W(?50GX&xx5(igh6kx>^2AvpMW#YuME41RT5yTQr$Y zxk1HnbBcWeBUo~M4Q4d%boN!3!Y-YR?3=KL>QO#?qKL8Hir(tXUg{wC;hg#^G@)vm zX>dm9k(68tBzt0Y%8&blOHnZeq>mhr#WtP2SE!5h<_lvD`)c!3vI#^8i>N{#;WFQ& z^o|=(ZH%WS3YL6~<cLr)w7b}IA{mO;(l<=4yF+$sd~4i1e%c|Vq~^R~MVzmt*<5d% zcP+T7A?T9jaTRt6mLm3Ral~NczQ^>%JM|nr-qbtHTW0#{ef3SM>HpRNx5jf^1ynLc`BE7ht0e21i16KbKI z;DXYZ?ihh+J)Ft?Li6e*r`pHy=pKqjpWL(j)!A&-ArNp;fD?JH?XiPRl8tS?=G{za zzP1QF&PC5f;no2(Xq3S8nQw1crs((fz`y`y#ggZ??GBm$bFN*zR%r@*!#%xlCF3$BU%Ds5;V`rPdKevn0( zD|ehT{%M(h8bX@1a4yr^0w1>}WG&}R-vh>7hN?wW`P*Vm-nZqrPr|FGl*C7MyFspr z3aCAA|2$*K1b&~~vE|8*OH4hWb2;ub&s{?aL}ehfiMjBRI=}1l z;d<*yh%Ba~z7p+;YnK3>hM6Uu=S$2bd*ALHw{P>{?XO!YP*phrFUJQ`m*DN*0wwED zjlvm9(8squR%srNZ{}u)*^-N(aW#o3I|Mq1iU(F7h{wnika6gWtE!oVRP+YF9DFhA zR9da2v!L}N}N?UIS=E~zq!7IRmyK;{Z|367kiQRg%8M2l_UJctT% z1b4;WphR1sA$Om@d<)LQoz=9YFL^|wG^(!$(24xIZ6M=q{{exdE+6f6fnqG@$g9b- z3&b2W_U4JrguRH-*nRr-dJLsxoltK(4&VVAcfl}~as~?CgftqU+9D&(5jR8``8)s? z+Y(zeq1R^RaK4L&BtWUjMk6%(HR|4XCHB#YtxUVg(nMuTRd|sn)`Qr#xiTJ|sR&O> zy}y@XH#uUdGjmNS&T)BerJi&G6=ZF6G69aB>E5sZ{{4ieXw z@#g_{%83#b>X&Py39lessFmudOn*k&t5Hg>>cCmJv<$N5@Q_1h&V`Z%vuJ z&Y~)E4Hn|QY~ChZ-=Y-cSLvyfofL4Hjl@0^XKVzE}y*h4pJc#S^0v~P*n ztnvNs@k0lb;gZ%z!~Oz$Z0~p1q2Jbd5!pd1>x5WE>6-q$h)v0|tCi*Nu%{u{z(O=Hx}=GcSlrsk z13hx$xOMU0du?!fS=zFC*jmY=#-_8qy>k1soIF-mBD)hjPNP~J%?(Zu$cFuJ4Bfd8o;~u`DtYCw%)=h+J)b#9#@T3X=yqi)o)P_k670G+UuqN^2>Je zimO$&Cp4G^IWwCzdK79XnKFqPGHj&!C2Stc*sCx86Wa7MnOB{d#URb6eAOfGYBTR= zgt$t-&Aa+`LkQBfO?ht}x{O4M*}Z;am9l!_Z*pXn%o52=S{ZNxqZjY@y@lIUZl~Z{ zD=T*<0+IcgP;-azJx7qD@;z0@SI_lR2{UJ}a~pNHOfa&SLQ^Qt`WKs!E~$!;kaER7 zerB4!-MKl6$Oq+`j+%jcp6J8q{vxHqh}@e7{I`zk4TP#B-Ai{L7Ku( zpeZntw-{|o`!c(txM3{-m>u#n|C8$#A>u|M(s$WAX?!bhTI~=_MC9O5bz9OfLM$7+ zS1ABS^m$lj*_#4vl~Rl}v6=)!GSE_T^673TCcao>`IbO-txAASECK%k+ zUS=G$vvD$`IXo?nF0}NP=OS$yxp}8a72mKC@FH0}Od_q+HC+1X(PNF@6EthhBV0n zT4}9rl`MrC6Iz6Q-*mThYTCC4r?0(zN6bDN)B{crx}caTw~arskV7OfP8ePF+x)Y`+N!DMOEgc1!cXI7vBvW|StIx4Tn^2d z9RwiN1ww_PcyA0)MW%Ql@!{HDpV|>h!Q)els&`dE2qQzs6Dl=cU;c!~5GS2a9LcP7 z1S{(V_FR#V^=2CI{5d*DFs<^UgEF3Qx3*A&HZIX*By!gOLjnDwpZ=2q`cVXbr+~l= zfBZMQ7C%IcLGXvTb!Bv95d63Hbfr!Gp*~&NABC-F5)|zqbAM`cBlH{0=sCaf#({58&ciq=aOz*B$KO50?mxlTOlC{?@*df%(uIYs z#CNhN!y%zBz3Uw(f}Sd`+s?wl459kZh&B$X@HHFZ%V(EZplom6&#Ye7-oz5^C+n3j zrrY^2;iVYGk&zNKy)3K|LFDN70$s$vva@GX2F~|f$oh5ejRaBKdfIPC(N3ZXcn6$jk8d08g! zN~jf5=e~R}<#jH^ZWU~mFx(EfucT^~EviUJPmi^ze}t%ZQxYqt9fsK_OSQLXZXArg zC?*_TN_zt58dOi8FfA3ThFR3m+-cZ^MLkI;c#Rz&{bFisZi`B$Gv>Jh@4*w{AVF`g z2@pY%b5$wmbCRRkIBmINDAr>__5f|WkTs$z?R=@5SjKvFFMJ4s$}6AH${ovUM1QNJ zXt_ikPoK<^KXS?Ks-91;BtR7u_$Dnil?VE^p$C>PF>|l0P}Lhx*PW5-61;-NCR>6D zvGrpWZS&Kd?%KdN`T1RE4{Dx0cJrC23vQanHDp=?hCXY-3DsOZIRRboY>_~f4OXA# zId0H05=gvyo5~Tc<+sn^$A$_e1ajYMYhQ?N>{y9?HwDTvmXfzcGu-P$mDyd1d^pr>+9pSsUfQSd>ag5UVM&u5N0D=c7! ziw1~5ndRbJqe*pCJ3Z_0eAmwX+y%Y(r})-n*FOq-GnaDk>R*_zn_$z&Q{j=S8})jzQF%K45A_;KoJIOhCgFhC(-wZ!T!&f z$kmDVOH2@W#S?yxiHLod(|?Ky0!6Oe3BSa|fI?RX;7{X(M8$wt=lD-Cpb$vt*S!b} z0EK_ui?FERl^@~fxgtQ2@D*wKX}v3d0r1!LL_vZgzs?l}fxo*bewr%^2K}}_QL*oC zqF=^|iCj65evW|z1i-(?1i`=N4GznR z=&!j4iGqI1DM(aI;EI_3ycduN=(o8b5a=)Yb9FYev9of%>vO3)}U0q#0^RMpeYJL={oRA0&11&QG0}(xum98ll7oC!ewE>-= zj=he#l@Xnsj*)>K5hHY^0uPUYr9LzX!sDyIN%}_;IzcN3OM4;~I!P0KJ9P$*#|?-W zA2Wr{nEo_l`O}Q;ai;m#+#Xl{O>UoLbxjTQ?CBI8bnPDtB5naK0UgLh-`<#rk)EDT z%)rFR*q(@ii5a>}b1Pd#YaKlUIw1o`6Fmb3Q2{yu6MH*316x5W3u`M&189-h>11>) zp!v|#iI|w%8`#o`nCsXZ2pQ;E=^M~V8CV)YGiG6BWar_rv$r+Su|W9bx339rrz9G6 zpN)LcU&i`<&)MGr%^YpjW-sJze^vt$dvuge6j}VIPkJ-|~w9&SB+Cg+NSjV8i}54t$mVW^iP!{aJFK zXwCzT=dXi~I6^tq@r`U;wLv)%z1l630<%}v$@F7B%qpowMcrS+K^IG5jyp8lKd zE0Edat8^-}_2z}09rxfW2X@SY{k=Gj)5+bM)#D#&CGPf8gN|7l`tD}C1CtZW^W8fe z8^?W}jOw4s^W0BF^=9*%Z@`0|5UZom@rh-*jAN7L{s>-`UmcFG8FI4?w8cD*mBdM} z;&x{~Hr#v)@4S~&BC~sLr%{GpT}_YLOKKk$x|vgo5&%=;)6Pi9LsKfIKJ&|gJ2v>* zZamaNhQaQtbG7yKRU-U(qdPA4kA`a6H&NYmS_2Z?Z&7f3WgPZq+hRipienPR$Rsat zn&iw?dhOXYe%zRUc{x1yz!ltSX>rI7ZX7~uFvYcQu>Fab0{ssbAA)cDw3|9Wqvg1l z80Yc*|H8MZ1oz0$lgp=eHVpNd>dLW#4-cC5@ftClpEn?gs=WDp!`a=Xn3onJkH9(>NAW9^92d}D6vuW+;V+GBpB^Ze6Z>uF@O(mayMGP+E>%7XJKQ|*~4 zn@!J@D4($&m=Ora{->g(wGxM^89Y|Br&A*`LWbpLzg`*DPMvpHwYwo zRN)tx6y9*Xgiy_T^q1Wa@6b>?R#y+^<5TcHok<`C6PKt@5*mNp`aVx~cctsSfGkQv z$z&7>65HOU>f)){7{6+b;4{gIYt`vfYb80N+xcwd^o7+h98@N{CrpZj+;HNu?(hI3 zt=y5hg_kZx5YlA7H%l$&^l;;PUV;Hjnvkjrp5J|g1QF8y{FRxxW|*5nd3L%4rXd1D z#LGQC@4(IsoGCed?Pg#8vTkg2M~%6<5jS|5kbe5?f`G%R2Q2Ku&b;#A&fBD z2DPw-4qP9qR5{JCb-zG>$i!-VpZhMB3@WbG92+sv+AK+ZYizR`EGgH}9;s}A`RQcm z)F%>~a$T46$vKCrQt2%kjqeUN$uW&XBJPo?wVmC{3T_MVfc1Xe zb$fOUoq%}PT5g9|))_S7CR%mAHLyTRch~tOYs{OtGcKr`pVfc)tQxNeJrz&IB*H^h zjx`&X<;pAGFoKT|z6od8j%~!Et0gJe?CL$##Lz3`3EA@1xZ8vIc_+0{@ecc6)aA)v zXN2D8C=MzZ8n#g{U9A^g9~wGU1*oM?PT`+*R=RYS;?28_>h*^6+*WisZlY}8R1ZE- z-GN;SPJ4xvB(eI?%f5%1v|*s%w^=4NyDMwO~%5 zz4e=(PE#Vb!W9kF891TRT~IWx0`E%&a>%gv;q8TzbN6aln2#0@`4D+JZt`z6ZkK50 zbQd)q_t6Z~ya5+Gr)={C2ZH!?`UDj{&Y0FjHs0RNd%!S9_7Yb0;Z7CO=O)F&L-Mp} zcbi`#lK7|?Vr(Bv5T+4ZQF{$=)t>bRP|2OUlI6WH-25og{ZLa%j10EuUZsSezN+N4 zc=yUyoK9{U6;_a^NhxPdy!J^NJmmh+F&a!=Ah=z{u=}!ZAxws~2X3sjnpZ{X`|sni z>lX;jj+&t3P32KeTtP#I5a17W^v%LG)p_c7T!%ljd!JDtd_>~SE z&-#jbs|gw0wSUN)65{&Mo8Z1ana*L|cNOq7JiXZ)(v9L)wxvOXL@k&mpc1d!R}^Rz z%&}7Zc;KUaI#!oP)6iyqc9VW484kKcx?wl=h;y=GJDSu#;m z?R^qt4Y3-oXrs5wnk_UE8CLdma*O0Z8IbY2C%;3w8H63mGwj0ALKz$kX69<9r)PqD zqsl8tt#GLS5iZltxA@4Ep`sUiuvGrtw3G-N2No?UB+7NS^mt{WJC21gMcRhsRdoGH zZ=_FLhG#0Vv%9_+_E68%S%0#Sf5UZg){H1&SXaWk`g{rJb1=U?Q6+Xm6?SC*mc>_)>wj+Uj0h7X_Z zO+vaZt&S;7W*dlesT-~0K`mV>dtY^ys5x|&4gHje+v|izTZ&LmPIQDFLk{L0Vik9n zj@mOLq1Qp^Mey*xbDwW_e=kTXk2Xdk;=XayZ1D-c!g|F*HB5BN$8g(HW=ic*4G+@7rgSSVL;4l!+ zZ^R_^>HcEw*b`bVuSS&=8gO}jwSH01yeN;n+=;62`Sz&9t&jB8SmFs)Ttso`sxxVZ z#>Fej{=&RHHLq~D=++rsGBE!xTK=B-5pInECEe3C>Je%qF|l-rTE=?lAWhakDB{J^Yd>))WK`Ms}iWa>h;h8`XM_RU0Hp}x!c zDqanU;OFtPqwqSBMzl_5>js6EgOdOxrvT~8Y{7+U^P8Wq*Pd_W9UmzuyXI4p_Z6)> zoM#oJg?ZTFYDw^pejxw-@)E_V>5@0N&&R{8dc1e1Xf+n)!j1C`-(8(Ja-xmpDkB|U z-nldFhvd5+5jfX6mQcHdJ*9$_GrNY-ay|CDzBG1SRMxBf?~_(I%tdN(JgBtR`PT+S+`M*P!3s&h071S!4tMuFrV&Mdp4)xU2fQZkaZjmmqvrgSbPV|3>S2 zo-f^DBsNr9g(qP&zZ2`45l=rpjcLE{<(?8nI=10m@I9k5v@L_oiL#nHI(hCi7^6I$ zzUk%vu33gOW7n;e-S1tIXT9o>huLkVd3@`HPg7`rj0|>c{M!c2+~+S~LwGK18^v&v zlBD5~OgZai<5f0TSRa1H`PUCm(d4orCd3tlY~hFK9;w<}wW-TARy2-m2m96W{ z@NPa)t$D2qYp2bo!}u)KykQZBiVz)%b>n+s;z1xK65*{Y3*H^<*Jp75{0Eo%O|sU+ z90I0d7Xh?QJ3Uj!eCzoU-*MRGq&Pr?G-B45PkwfRzG4?|o}VbcLG8?WoSPvwxQz*J zKB-@~g%Qr{G6|Fh3Cwu4`PO{N*?ZH9p*( zp0mgD4r(~`l!7`GfaRcne~yrbQ_a-@*~$;Y0`HTduP*22<=+k5`7lA^90p!gogasx zQ1fxD5{}!--?e4vRMpr6aa|_{Gf)r;vFiTt}UhspPZ1w!svHYUNY{7wHnjhwl@oh33 z>P|nA8hR+gMwcZNN)O%^r4**o+*YU`e5k-L1LWeu4i{NDh-lG~6$z;493^c$%W| zQ04*jL@qA=0&Hx0K}#{8U)r_l{0jVW$KmZAtwDB%NnWtPS>}1Y*II;%R%}*s&O>qY zyR@yf)d)Mp`W)Db&V zHGb)saa1F;6vgNrRSE(t8C)ckMue_m-lscEESgk8>zx=Yd?QttLsZ{lHcynzJE8`(gBNBLW= z_DaX8SyZ<6O6;}>(*RM0S;d)i=S*^rC|LI-u!n`>i|5IWkP_5f#6vbV5nMDoSS zk)yh{Wk`HQF>hp6P@Ze_h*;VY4NM7qO()caJT6$~MM-}hNV?d;C*fG(C(;#;)4*H8O zU!P2u@Q|gL`wD=RY+tNkox|5*&NM9)CBTg=%MDU8F?3h1d^-x-qnr{w=XRIq!2$~` z+Y&}G3A*!;tWW=%eV^*#(8T8<7WZ}UOFaH?i5(BdS5Vvy_ygqU9bIzs>?)R7dXn?F z-Y1;g2oXkz+XSxh4|kHnR4_Lrai{PW&1KiSGSVT6Z#Sz$r|;*_4*Y20jH94y{^OFh z=M``c`TET1NxGMNpWj|3`#fi=g;+&n$!ZN_{*K3Q5OQm$LqLNV7O!I+2Prp7H87Uc z>^5BI*Q|7i1P~8Z21V-jP(K@3s(|L!DK%F4Y_#9KZLpVUBjJ5!8}*7$k6t(m21JY` zQGh&p9tT*$qhiAO489Yhx;WEVed_2MJ zO0ul|*dhVNZzGAb6WXnULHgz>12hyI>KBcMls+RJ)MrRDxtA!l7uVCESR=%}fd=f- z2Cs%L!{}!|+sYHj(Dt|Q3nW?whp1_ZdZ*r_mL`II9^DiemMi6iNvC=LvEh;PNJ9l@*I6g|H*wY{yEKD^hF1Cx@< ze%Yd3k!n-yG{I832-|Hh%Bj3Z<$MN<3SRJ)kpW)y@Km9|4FB~M6a1QVjFHVQ)b zO{?Ic<1!DP9u~FAM_q2GcH!-N&`2LFT5D=7d_Ti+2fZFF$4(Lc^0LW~_o2($jA=a2 zJL3tk3N4hQ`z_=VI7fcp(8iT}iqJwaEQ#W>tYP5Vnhp}D1Ba~mhnZuju^4DUyB2hL z`^dRlUkYaTbJ{-MvM~Dy_5>W0cv}kQ4B-lV)m;h?h zkI5jnXlrYI1XsdT=4vOt4HN7mNVHX7{*2k|22S0{dFZL07Vo0KMUoqpIBQVPZF!*8 z`q6@S%xO3+HWqV@Kn*5?`;msqct%rOwV#6^cB#kb)>i%({ZA9L%Vhl+t_%if<`FPB z^M}`_=fQv1zzLXJet0T|QvU=2+Qt3nZs0$6b~FEb-P6#X>VK_!n&F>xPqQ(z(Eq3I zX*l~gld2%p*ul(~w?2X>_x%q{b`P$XcP}EpK7&QT zq>#(t^ZLhyHlM)6T=n20CQeO61XMisyJ=N!6-4S)PR&;cs3cR9+B94>cAixhP+nF>&(6;Nk&%ayIjw_faBHk@w?=Y*RAMcCMRsG~exG7bSxQC* z0mgfIIWyNro^^;V>Cz_hOWLex-yTEgKKmd+?T<06oHBBTj~}Tp5ds1NHoJt7xnXhC zd8r8O4KGwTFQ!+RN>`J!o=$dReM9taBP;vy<0U!!hZh>lQ%6cleLZUg4nG78(~%V{ zd1=Z|nb#)HMu%lUacya>XJ?gD@TEG&&c+S`9mc0G&F;*mGvHdO-&=pkmE zW<(=dLeMUBU&tS%hWXP55}@X!UJyHz8#|)M{++pD?2=!{iN9`B;ssO9!4VwZo~mq)y%`St7D6E`PU2jlKW`jp z)9G4;VvnSMV4&wsQN4WT6xQ`*)9T`tCzb%A3&?rW^n662?w2IYLXnf>wIRhG9ufkK zdt>A3>gq{@H+2IOaj)rRRq?gU$d~U8pPP*;jB|YmSYyNApA0x%TT<*{TdS*2v#{LV z-DON{Z51F@ZU6mdiUvIWx%^S9MqO1k_Yk zX1zJ+&K5tMb$WU#DJgkbpV1h$zP=u}v7yFV-&7hlw7FR4fLOkKU0`i*qk;L!^(*)W z^Xi=})l|9nQ_;yJtud-}dYYTU=evc|>lUf)ND6*00-WnyL&8E94l90Wlvd>`3uxTl z(HcYa91UNDn66nz?3kLVtE**HY5x!r5y{ciA?9r>+e>G1?bp9vh@=W|YfnZ^jDPS3 z-;qq^s{S}cxa@FTc;*bNpsM;QYvZ#10~aYW%QD)5*9M-Q(tN%Gj>De^K`%GGQOYT>VshIo%!OE;xn4Gi(i6A z%)pG)H?{9}Yirj7_iJH;K)v#PrQEJN_6oYAj@SsbO<|x*^n&rZU+>XgcJSD|E-lMB ztq>V$f@vcA#VH2s*y;$&(nNFdh3-`jNtEOOgazS(^hCOP6Pp4|35Vp@R7&uU#+?&! zfBPWr!e0nwmk0Pz>aJfU4_RX<+WDBZ? z$$;)Kt zw8A|1*->R;JviRFYAPW)7Auxf0UIf z$&5=B*UKVXyM)OsNY-Eh$Ir2uQL5jLZstfMART_M+*;wefvJ(m$fkt_86GWm_JSO; zMZ}renHGFTu(%sZz*rgm2i@~UIqC(c>^q_H2*QvkhfYsH>7M8yu?uC{q_}(6A*J&P zsuyiHH`j&f@=OfOC&w2r?Dovfhf+RT7)cgEisA>wyQrN&Y|;+MTY|TtY`O(j8HIWL zPWHBr*1yI_N5&>XL%t@YH*;S2x^}elx?YDo9~g|45jA{EW5HHOz2JSulIv2{(11j; zZETu}nN)==dtGP{k8|H9oGB!5K`_Z0Vv#^t|==WK6cwb*HsVLvQEYEF0L$P_u zTpYF$9C_$>Q$m7|AGX)|4BtpnS1}n`loWGo%FzoQ5!~aPFKP5G_=v#5l^wf^A>7>%67-RG? z6&G{J!5SAQr@GRotNlsxELyGY{cYin(09_I(oa3^>(pj?#Cv|K%7bloI>dPi7g;T5 z`_GVmPK?bzM|>jT>iT&Zvxfy9oST=kWMw65RW)XG9H(R2Wf>|RB75Y_g&Ab(>f)S* z*`rFIT)@N=J7@b@EvYHYQqv-J?PWjC&+rS_z@m~&6AKf+1eA%wB0GzVm-YS@8ASp0 zFOIc)<&502$jVZFA`y_Feo*JPB)HmMTXJ3>T@R`_S!v7K>;F*wYpkB|lPZ{uJv#+- z>87h^)NZZ658vMFSyAGq!BH5I@`v)Vw!$w z;qP<4j+TP${XtI3y zylZ%<8#NShxgp97y}+=%IuRtP3obNZFDW{mGxJ^JH(6h%GHV|d->hn#%lq_`viV_3!n}M0Q*4ZJ{(D01!wxw(vq#M*=Dh`#GW8rf?} zFxBZGr~TO822&IBbv9&}a$W|bJNtKf=P5>GW9h#=ek|_wEhZiqo$j)ghQ4;Gby34h znpT}ADW?ZV`G_?H`UWI(n!m#I6QhJkg@(kQF|`Ymw0Hg#t3cAAo%}Tg6Ql+$jkk}; zwlBTP%dgCKir=Z@A!1?Xi6A0+rF6HKAJAtNG%en8!BmJ4w;pU_ouDuekxtaU2Hgd> z9l!>bpKNklMqGE_8Iq%XBy?BVyyQ<@rc$9K<$&!XjLfJn;sg__w)F`=YZa2XBU-SM zWZFJ+Jwj-E4GI4s$QhC!Scn9%mL^qtKPZq^Abk=PnMONA{m#8TnN9bnjJS-{=-33q z@K}JL=zEfq!kqG(J0b4Dbxc|9i_fCF~^Sa7Iv1>aMp{vJ9-1@Q4*}G;Y4r^91 z*-om??oKcIh)>QcFLl;1iqXK+Ra?d|H)FbRqFvPNij_`ZKUGxNPWnc$3cRmCdI{+d zF>vv7KI(EX2(mG2kdPK1pB#x^#xjmhkB6*%xN2R>K>UFy&EoK>+WXc`+%pwsc8HoD zf51ht!PLThmBj(BY_UrJ4z93#$61Q=K9o&L#)FUd#OpMY%>uP?^{8j*heR|oItKbN zJvB8I>)>Zp{f3~Bua)JMJG=YuIA1jge34U7l8W;R0rlR@)^rs?FG&8MUobYbsVc|D zN(_=b8G1wbCO{;|W22aQ9@azndUk&ktJ20*J$c9DD2+NLv?#k3>m#+T-$9Cz#?%m@ z?e9H{WC$Ns&SfgkL)CIRBA&QbIx`xKiJ2+GcRQu#VD6Tp+h>?LzM!v%-p_{AlT;~I zuuL>Q_#7YGlp%(_afrLP|5~gw%vc=>;h7{_9N`2s8v-R5npFr{k3hW!*JX%QNMjjb97Z2i26pIV;OO@h5su%;`7Q z;&oY*KkbeKe)V%`fQgkJ0U0F{zp$`yWNc)9<2Moob98G#Vd)D<3+<7cAlT-OOG)TH z4+~te>H}#zb$C}kf5~NP*zLfDq_>jL=jkO`*2xs*LR2yIrOj}5P4S0VBy4Q#L0FjK zx2aAv5UO74r8$fhjGr>K);wPg^>){muI61xF%dLVwh2lPFjk$ie@?`yyk-znVbt;D zee#i%lx#!aC|kPql@BXbnOTX?nQ2Lrxzf+it>KqbiCZ-}p;(a~MkhD}XnI&hzNW8t zw#QOaV#O@MoL0AB&@vw6_Bp<>qr3YqMWw5=pY}joKmPvu#s2Kc?^nmfFk$t$W)?by zA0g1jXj_qkgM(L=$3jz7rmX(EjWJt1ycngM8`$RTccdJ+HZV-&S5*W41Ty zIDayY=W2B!f+-0JUtqW$R&vhhX}yKME>y-SZKu=+fSfy<7I^6K&^AX!RwuTM^_@Gm zqZe09R?GXkee@f=sr-ck-bW|CV{o})-HWRBH{7`vjZ90*%P-koUk;oUwP#;EWFezZ z-ZnA$?cyTTYrxoA+Aud4bYeT49HO}&su>!Je8AFNV{j*-3%PX;_jKLfzq9Q^*mfwo z3KyEzPYy+qDEQBI%sQW6Ae;y_Kz5| zb}4ffLvg%-wmwnLP|+5#F)^pc_L6f~ezdg-B!%wf5Sdik%v!^~d{IeB0cVGyNJwYZ z(A4QC{JTFxg(ex9dgoxrU-5tq@})N@2NiBaFGDOWD2v#NG2T3>Qx_4K5~ca(xi^3ED;|f3UHc+bK+o^Y zsK_bYIWVT9z6u}1GqO)}NQ4dEr!H^y?zq4;W zPgBb*)GmZ|F17Xb-*eL-D@X`~=VQSfuzu49o4bq5)DKucPfHukYhvFw<~)erysk-Y zl0@MAa<puOu25R7jOiQz0-=y{#3V#(5jJZsV6jlBccN+gxo!O2K8lb$AFu$BH@$ z>ItPev01*8+rRdDZb*)KvcsMK_*hef!^v({1HHe$|IFUW>FA@sZ`-)Cvg=-ad>qX4 z=c#TUo?46E2qQIObJJgM#a3+*pY(jueydaO*3ZMjGCnpxHa^Y5#pNbCLeYM5QV|q+ z|Lrx^gppHLaV4f#<_;BN;rsZ`e!Z(|F1>`i6|ItH1&aAgXi!9W*qeVIOU7|7|b-&w^85GMUt%zHPU~Vxk_Am%6u?d!GBu!9I}G0|w?9znJyQfv$!_`2E{O(k@u-VkYifhd5K@g@0m5NKjCamsh>} z#(1%*1#-#PqnAACsMyldGRP<>*f?0pc*z|a&BU+KzF2iE{g9*hH8xQQS=65Klw103 zcmFim-M$_z#os@tqhlbUSVKj|RVlGhv#*%|@#9rquDQ}nNmv_)Tx~naM$4{_t=%n3 zjSd(bJwx4~$@lI@rb^83Xx`HG@qwB@jj%KRG|4C;Z~ojFd*EH!kdqPIR9OD%7s)4~ zCow)=D^B(qA6%2(a%I@xv(kbTr8PbFL&Uh~Ci~jQhPv69*_A332)N)ZFc+I&XB5>2 znz*XhM{&8rTCWq0=QZfSey7Ym%HllS++l$9NftLxd)_8>&RRUL;1P!3G20!E%MFM% z7##ZepePg zCod;0z5=yfu-&jL4|y%Sj^ucVbV^Ot4(~vX<8{H;GHW{wrr+C_&|6scQa8cmwH$ru zZ45l*2_fC#)em;R#N`N+iRw<_9J7xXB*Q<@-mDlDij>?+7}^O9#2Xf;6WHEqd_EZ` zzQ&O?YsLeW?m6^jeE*KI|G9nS0_nwb3Gj)Pscn#7V(p1LQ7}UAv)fzJhYkJBmO_h9 zgwQLUeSFLC{*gh(UCOToM3@!Jt`+L_c-_ge5}FUWRwWI^_(6!6Q9~Lk3PGDhtEPlc zT54BazKN8~eM`BRLU-z(AoRe!ZOU1_%Q%hXRc*5S>S3=qrZ+6G9X9xNxZbsnQ|t*p z%9!tw^vRUYB2ja!7H+R-5@{oSWqbn3(W4eh zvL`pvkQV2^>Rp(ir9M3vT+VnZLrL`^rrG(6))T3M69m5U_KBW;IE-Kly2O-@1+r0B z9d7L(+zhS#JzmRdy9z``ARVxcjirJ0mw=-zFW}$+Ui?O!05h9PR z#IUdgJ+r=tw+!aZO_jG-ia9ga@rzi-G6&`TPz)}ParK*zlQQ+Tgkv!MhPAilM{N}f zVt5C2aKHXC=e%?&$P5`V6nmE zjM_BC+_nIGbxV=&4`<_i>DVk^&~drV8`c2mNzhD^`gcSQwBv6_lhK_yzQ?y_Ua9kf znaFVF$J}0V@mhz_mP_r^R4PM)_Vk+7F+hbE;1j9%Y7+X$r@($$swYr0$AdUJVKgezg6C&Qf0V7 zd&>|I5fq}M6%`ek>EXI=2M<<}afZcml)bdYmwY$W{0z>ZzUC7AB+n)3i{N7RG4TAylF zEl_-XGNNPddvYWC(LN@1hzcIA(yey$etp$j3-zX}ot=K1j^_4$doqNp(9t>wu0WE&tL2cQ zaTutZX%3$g)(nhVAe1GRkeoQzdbPxCZC!XYTsDn6T{<{DtvcM&_DiFp_;rK#fX*bv z29}(%YHnek4FjXCkc80r`FSgLD}CGM_3x{w63*bzTKE;%tLv&7-j~n47vHI13y-ZL z;>>7qTOYvTt}mWD?ci+SpphoxCNF8eCnsB9FQu9L+JwGtY-A<-J(>XJ#UR{^7jT+# z9{h$V7(%hUVL1Ey+pz0Cpdm4%was-SdPZ$`Mpmu05e`#b}4(j6FnD1Z%G!SL*}lB}<&Xe%`= zIw`_NgI_mTf06l1FcIIk;-lR*(YuPGsp&8GW|iFVN|%a_Z?|Ng?>z`&rxYML)yP@;BJlc*EgJX-Tih2CBEjrQXk znp#+KYc0wPrygNai0L^xR!1*R&y0?CSw!Em$8eE#k+b8=gpU%E=KQZ7K>oucN&g2p zlEif1e|&iRD31OsY!Tx>d3X!hBES~?ALT}XEdp#2V2c1-1lS_L76G;hutk6^0&Edr zivU{$*do9d0k#ORMSv{=Y!P6K{%^EJtp8s3^uKJ2nEpxkG+>JWTLjo5z!m|v2(U$f zEdp#2V2c1-1lS_L76G;hutk6^0&EdrivU{$*do9d0k#ORMSv{=Y!P6K09ypuBES~? zkGDmS!x;Tn*dpeC^6(b0MSv{=Y!NVS5io9%9WZVYFm4erZV@nU5io8MFm4erZV@nU z(ck@r|HqfWxJAIYMZma4z_>-gxJAIYMZma4z_>;Kt;a25|M$A5|7Bam@=v;_0b2yv zBES{_wg|9AfGq-S5nzh|TLjo5z!m|v2(U$fEdp#2V2c1-1lS_L76G;hutk6^0&Edr zivU{$*do9d0k-IWye;DR_a5FdG1CcJnOoT^TI=W;&S04@S>5rB&TTm;}E02cwc2*5=EE&^~7 zfQtZJ1mGe77Xi2kz(oKq0&vm)cwEH5@b5jn{g-tS`#*Vl3+N(17Xi8m&_#eQ0(23e zi@<;`0(23eivV2&=psND0lEm#MIIHuvUd1IMzs0}sB0v`bx(Luk zfG+y)t&14{z5eNcSr>8qlm2Ny7Xi8m&_#eQ0(23eivV2&=psND0lEm#MSv~>bP=G7 z09^#=B0v`bx(LukfGz@b5ul3zT?FVNKoCp)`7{xO@XmoFv~$src`Q8}3hl)|f!$SoGh0o@|B&2;m0;r`+N{(_x=)RzLISUPx=__b=H2~W-av#>60bxQJ1CV;Cr8M< zRNT2n)~!y#y-sCtt!?LYBdpOwCYIB)N`L!w?elkCnK(|Vn2*k720;yWRuIY0KjZ`2 zz;$!kH@ClUZ*PWI2IS+3xsCEM^ZPOFJ7_Uyw`MJJO+KZFX=RIO+#BT9PK#C!m>JJs=oZ>i;=(p-Wb}rU(qo|5mMH z`B!Td!(VEZh`Elv!GEq%EUX-Vtx=!+grkOJG59gNA4spw>MhQ-i5N_|Bhte_?H7?mXe!@yNCMcX=}| zLw&EjGwsovxt%^ld@1>;asyMSR!eM#bCnQ}Gi0(Gp`k!%#`fnQPe4_e1HqE^>ZEg|Vav3`Ar%%LX#R*$3uCx-}iO_Xlwr z>0SZy44!^zwt8Xqx|e4tzj1=fF6aIiq0RE2LtDhk(q72GPS4iF+TO~R?(yXxgVpid zn&_B+5U?`Wr-KHkksT2e;~y&p1gxCZX_(nKh-lc@n28vendylb=;>KCd3gR(d078( z4|IwSy7n&C26Rfc4hBk(3xDiHgq}`7$Ijq!r*wa_Lt#tkrNzY3hz?|8$!}?A^4HfQ zCbo9=g2pz`;Po z#KiQc3HlxjJM*8?Lf`w#c7NIC|7-r&^w2yW^Z8?f?vH_b5jf*v|Ok-~q(0oy;u!GEOwyGZ+&gN*$jGyi`JGKRkkGP=LU)Bk3Cv2p_gPdG+y3sz4{gK*aDE?$83zeUjBRHPEw%t`~=X zFn_ETXCQj~Y@ll&fBt+D{v%&#Q|2)v6LWh5=$ZW=n-~mi>YNgWb+Y1GTEur)?~9IheUy(BGG5iowCG?p&Vz@|^agh@@7>CJyJ;d(&03sEc#s z-GoXaF&Cfnl-xUt9ogr-I0hpaUrXLr1;vBVjqrW4v{09P%PRfM!q2480a~UvFZov} zEbj?}SiIYt&$R0n94`C5MCLz5&SH=BeF4AcMaY%wM*37Ka9akR+$Z|gOWee`!z*ue zvL54)OWv?4-5FqfIB&&IDGl`y97Kb-wQmV=hMDFn*oUH?sDq-Mu%t3!*{H&T%~9GO zTLvZ`pRu~}@S|s2W|^}@V-z+7DO`f<rg zeF}UMqs4c`nty>&aAfr2G4qIlGr2HD9KJ_!vi%Kyms%ZGEIXT!a@2&8=WB!P6=12X;`aF*D#IANXu9JAq0DqIXvv4p>W#_?9 zCfsLSSyqxh)Rr(FiBB&_dn<)e`g)}3G}!024;>88I>PY-e^9!!3wuj(7k#nZ*w^so zrZcRiBI60cUoV+eWK`-bR$-D|)uop+9G?q_a)`Y6eR0qgj^O3SZv1sr-k^KE<5cYB z6)DMfj~|?Rg2W!HR=CcTG0MD#@V%T%X*^Gic)S>GU!C`tGYdF|81l6bMIZDfkOvNN zjFE3R`6JLyH`j6rS)N@?I(1`aUi9IWln|nNH?F%83up5B7wg1(G`!que^+-)PnFJP z724ubnBa^|MSK|5Zla3Qw8>9Th&>lz@oiQNr|P&N@#VvI;qXQQe)w*;)Qx@x8rD&y zyl`%pWzf6q^=xuZbnB9SHj8s#C8|k+HOBqXE@gS{9se6CRbI=G&1HtrFpl+1R2!1= zp6j2M>1w$2)zVdak zsH_)_)E6DyLU5u$GRC3q=4+&;8*d!$+4J?7kXQ!EEgNxFPL4#`d|DTr4m{dxb8#hQ zuTzcaWus^IB)k~(bLs^MrJG~;#=+==mSWj{;rKJKCizU1m1#=!-y+}$1%oPstLhT? zvg8&kweH(Dr_Q$O3BFzvhQrK}L>?XNzch@9X!~%q^IRd@1Lmi-pLzXJ(Moys4gR9c z6Os@%Ip!xydePi7jiJvO(?VEDKJkfDcU>EPe4i0cdp_HUJ-9t_Az875;>IrIj3!!h zmlUyUZp%E{RZu=TzG~~UuH9%DHXUD5w*0YpD6?+tiO=Q4n{E-8zP#9?2rH8PoGYm>Tn5Se&onl^QE?Q0cazQAV-{I5i^a0| zakUj#mhYSEO;E06nTGsE=#sHzm8@cG1}4{$AHLjwB*tHPr|WiZ{xD)Z zj_6AuD9}Zg;`cP0(YA#b2jvARb68J-t*eg93=F;|g#E0(`Ez-SX(x$MpFA4Vz8l{6;czmY{2pH#Z(3~&q*#fJ*J zp+$lL>y|d>0Pc>r_V|h2d8I_dZ|tk{KO37IT%pqy$HoK2Rq{b=pY=nqvio3oeNTh> z2p_(|>)U`!olo?)E5ZPjl0xYgr$uw-_n8-@NEn%mB5tdM1z}}=S1*Fq^`%ly9GwlI zh(dG0ILUX%UU^l%y;Z9yB$mUHkDep5^<_08=Ld3 zA_Ei$lWpBl=kVrp7L^atQTTBA)l9ZyLu@Y0z)NtB4N&MAuK?cciPSdFX+t&znTM#3 zZX=R@f95R_OyB61cxq4o0uccvTVAHE`R;;9bY!!*J>h}m#!T4Lzt%?rH$K<%Pq6)8 zT;T6u`!&)27q){Ke&+zcV*4Ne*!X|2T?8!fCu}b^oN~Ly_NQZd^TjzZ)_hk+oH}RM z03}0+D-$xKfBAVDdo+Q3V{@15&o|E|?szU}BEpIV`c!BsZ3ocD;n8?Msh(#~NJ5X9 zwX)ZQcMI6##yx!ZP3u6QGpoY^_DK)R_k4ZPlkAn@Fm@hc# zK999PP2w>8DP8$S5(gS7qyZ-iJ;n&~HnPvJ8eBw0bAl3;kSk)yd6g4K0e}*VIEP^4 z>xS3N5j_*pu5e@46()bPPpU|j^-rXHY|rBR*#@YiD(I8}y<`&lClYe8i)J(TC{sfOU`f`z4NSU~5j_GW$G zex?k^1<$T~(l^Jt-V(Fu`m%A=y#^o=*blZ6nm_2)5RoPqF$n$Uh-6j|ccs9ERJXGD zCfCiqx!q+xbgNU*121XJ-k{AeP}x9CzkN2vUS?6=CI!oDDp4lutE1*b6{E*fMkg+_ zxAo>P{b>xKJM@CnwHbQ~LPEYmR2YT~aylFf zU$J?V=pKX%l?bheGA^?nab(ESZWH--eUG-KTr0CjCK9|Hx(8qUpi`el9Q>Agg5ZPH zI@>~{lY2xVwuUe+O&lsp+GC>e!WvMluEqgzdh&3V#}Xg6u{V}OiFnt_wH_m2W+jUY z#1cWk+;AdNi68-MR3m%qw03N@qE+zlUb`FoG)zN!=Pazw=iP{A!4hWuZp+}gujAY+R!W7%i1qI=X^00 z8dLr(&+l>$dDeN4@>tTxPG5Gh5exY#`I*IzpATCnB)Jle#_zEN*;Z+4qI!NT!J3Mk z!wb@V>;m%9dnU}olR2!#>+$v=0JE`zSH`s1y`}-!002asx~(y>^%}Gtwq02VUv+db zJaPe`xOpBARIWldxl5DBtcFvxj*B1kw&X$hApJOdx96f$33n@KA6?Lxllt91l#d^1 zxdn9$j9=BSO|&uj)fXm&7Ijwro1stkeEMwP^w1*0;8M*%ECq;o2rh8Bs|E1+Pk$K~1zgD57~bW$o(I`(Vuc z*UteK;-mrJRL;~lyowvR)&NGZM(&~8KKMWD3)t>jeo4JWur~g@z{Tyvf^1hhxtlayU_$0dh zbWSl0MiikMzJV9ETpC>m{!2Ra&PVC`YksEmg<>%C^zAb|ekpHkVMT9jg^`??1`0VD z!a);&%+QQFIT!VKsVx0H$viRpo%=<)AHEa%tBlZvt9y=RQI>53UzCZ(P*sTGUu6${ zG@0vTf<+MEC(ZPW4q8V9%fcdDv8=~thNZQi$hy0GISu58v8nk>MC^sQ9}VYniI$$0 z0h$V?5F*vzi=NKT)F{ToB^QdaDqk4}+BnDd5uHYdJDAyxg+nO^W|gjYQ1fAV1ox1_ zF^%oWTWO?0+L81TRlo0S;~0WP4pTAg=-yEqgPN#c+KA^6^!;eKs=HRZ+Chbfr2m7b z)JaB128f-nT7a^nA0D$sBJTtZw$to4aOza{yP)ISMv1v%C8k9zMnwq~_PGowQZ=~> zFA3!5&xc$!L^w*0;c_&6CX*FGjJ)tBgty|_aZ?z$Z&gWpN#RKG!fmIW99s~@_ z8*bh|As@eM0)IRkKbseo2LC1>o65FHRC<_g z=UN}qR>mtAo6<5>Z1-hQ30;}Yq)AarR~kR-ludezIWn`rOIQYXT?xHvZENuY=^N2M zN*dvl>7zHdQJn?OcZ!R&AQn3CuP6iPPa-1Pj;8bXY2Tja!Tnv1wGp)X!up3F$Xyt_g*#mcBX*vO~U!oTgz`Lak z+}@-_pbnbzi!lhH%^6RUm_Ow%h?=Bx<3#rsIom!X>VYpw6<~0l(lsn8V3&py&u*M@ zsucQCc4Fm_YfNLUyj1)S9!Mh_f`s(az0g!mp@F~p;$nleL(NH~;L{%0=tR`|0Jr!J zd`Dn+Y&9_7X7nGZmROV=gjiL2a3#3mo>l5+L_kT3=d%5*aIss%zUQ6mdmC_7A{ruU z10C*6m^-kW6Z7cFr|?oeXp(NB0%^@ZDal$kwKXSqX^q@*k3*7ntt6Zkc&W)lsLZ6& zp>5yY&|8aFOCjlq5sY=dnHEEtZA`jl*EONsc8pN#nkB#jH^C+5qYy^nY{VE6PI7>; zq-~3`B?n1c8aNyA0#h|VgyykzKfs%_P>?@E>!AG_}hc)(%2R+Eyj zJkGVT+e&9*+|d$g2|j+r=TS0@w3$$n!f?@-r#toN2GXlp!03G{FqWSL^yGP7%nNTF zZl?D2)Z}k?*+blMNv*cBS9(aGFeLS|2pBwH#E{Mk2BBv zP6nThT~}0D#WbwCH?-n&>8NWtTvKO3^Q8+b2Xs^5K?Ug(k6&W^Q+>hqo0+H$P(m1uaEgiHOQyjhV!JV&Azc?mG8-cimBv>c;EwO@2 zR^sZV200UN-I(F;fZX!FWg?5_FS{p?olS?!+MI?1fqSf9cGpxd)_5XvLloBt?!3;_ z2;_;_0MB)Ocl_EykmFm0r?2NrJ!YbnInsa!wb{60-r@72}l zo+R(Hm{u1{!?PdcCf|1VmPThtzrGv6*55F9jxqaeXxn|jM=Pn`Msxf1uI)qkm~y;g zYs=-ReyhTZs1?1#USIu+{jJnh59?eX)llZT>6x6-!^X<{;q!M)eESj? z(WYKWzU{(z1JQWN3m+M5$TrUk^MDQ%TtD3~{7fw~9-1Tfk!TUecOR|OzFh^H9$Tj} zM>CPsJUcZ2h@t{$uv@AeWjz|Cq|XJFq-K-qA2NPn50O*QR(5$?qMuHXJ%h((+|xSF z$X2eJMsbc>YN>Nims1L>lI!DRqV3zAo1=)ftI}}M2;TESA4U%pEC)v%sM4omF?mf) zEa9jK-FeiOTc^K35$paiD1gkGn$>D8_-R9H(K!*6zkB3AVA&0$@tWf4@uZzgE(JSe5@B%!Gh{E~I0%r`)oDzZKG(R$|G-j8YwHIeQ~W4*`4X zJyWwLY3 z+)s29UM**YIT4?Ph^b%%Ks3OSs%Zwq1Gn4d4?Mh)eKs$5m!S}~?^i71o7K5~NIY?6O)jo_H_Zj8H0Vlivfpjuw@ z81r}|ufvF0;ps~D&go?LqjTy*z2rkwIaG-#=h|ok1cD{QnZPl&&+0|KHxzwKw%2+^0X>+5etb z{6MhapA@P}J(SnALN#;1Wn=#;nf+SLPOH@3cGViw%!-8oaUvC^WiX|<)&W2G#BKqS zXpkCj=y{k!+H%A~krab)l6U6Ne6c-77skEK(kEdDSqGa%m(1A7K8tJ` z7k-vsA356gHIQH=bSa%fahu4X?+g0NvVQC)FSUPlWphmmpMRv89cmW1dPa^`%Dk-+ zRthq?->XTu2MzFO$Fo4XU9@GL1cn?Y!H7?I3CSsz~E*WJ&IJaaqk&ftkx zkPN2MdJ)-~7fYRA9`X&RAw^okI_1-M;|>&7TO^X36myZO zn7ZZRQ=_{+drEc;Sp>EszKe%@`=sJUGYe1UXcJ^A`msEZscrUO~rC@9X>|=t)*UhX>?# zsw;k2*^DO!HGyD{hh}Idng%#>UzRLdq@TJ!Yp>X$R}vuX1!yC{9o9c05ISxSP6B$-$7&DR%6G(zkjVvQBS! z2aKE5?Y)M&u7*aCpQ#H|)rYroyVKwF<%8}{M3xO)aU&mSc#SKc%Qc1bUuxG@Q$9+y zoO&`Xve3~^_e3uAPLLqmGYznqcEotum&_L-?PO1k;-v@CFE=qduv(m-nI9+wHaiFI zoPVAh03htNaCuBI#==%=7zI}f4QFZamqFy%9!#(FRTU74V&grR0CnKBjRb(Lrt#RB9`-7-p{DZ_=jQf_D?)ianQ!;<-HTgczkntIPdIm@*s_? z0l`d>$z$j8%Eq0@uE%r0RCf#FpQe+&42p2H)#SDso}nIwoR>y-3$K=hDHDzEzTVOt zE|s@1JV?>LPs)H9X~GMZHLHO{%1R%R ziCzrD(i_!9G^UGV)us(-i|J|BO@mgVC!eV+UOK1ml{pp-0Uh{E@cg&g8?DGdSqD;M z_zKKd7oH84BG2a>O4_66+GRQi3M$A{XvEhzaPRmp4tTqarDr2#)`kL~w!scQ6PE3W5X~Y#9ECU1R=l2g8#;VuIIu z*NqrI=$aAy8WR-xP5=KT#t#y_?nAf{69EZa)0i4B;G5Tl0D;0c$3cJse;p?P6uEg{t~-f@Zr*1ISOD_Z82B&u9wG$%4Uv9X zGen3Va`SmWgoLmArEZLa06~A6>)~c$?`Z9YD=v=9r)BSF{o8xRr|Il`-5~uNX~cy> Zaos&E+&q4N>#lnxz+hY!7J0P?{{usX`0oG! literal 0 HcmV?d00001 diff --git a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py index e37db750..2104cdaa 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -99,6 +99,9 @@ _SUMMARY_000565_PDF = _FIXTURES / "Summary_000565.pdf" # cert 000565 (5-bp Elmh _SUMMARY_001431_CASE20_PDF = _FIXTURES / "Summary_001431_case20.pdf" # sim case 20 (storage heaters + RR type-2 + wrapped "Double between 2002 and 2021" glazing) _SUMMARY_001431_TOPFLOOR_PDF = _FIXTURES / "Summary_001431_topfloor_flat.pdf" # gas-boiler-upgrade recommendation "after" — top-floor flat, PS sloping roof; exercises the Date-Built age-band + flat-position layout regressions _SUMMARY_001431_LPG_PDF = _FIXTURES / "Summary_001431_lpg_boiler.pdf" # lpg-boiler recommendation "before" — §14 SAP code 115, §15 "Bottled gas"; exercises the bottled-LPG main-fuel mapping +_SUMMARY_001431_CASE41_RAFTERS_PDF = _FIXTURES / "Summary_001431_case41_rafters.pdf" # sim case 41 — 4-bp roof: Main joists 200mm, Ext1 rafters 200mm, Ext2 joists unknown, Ext3 rafters As Built (RdSAP 10 §5.11.2 Table 16 col 2 + Table 18 col 2) +_SUMMARY_001431_CASE42_50MM_RAFTERS_PDF = _FIXTURES / "Summary_001431_case42_50mm_rafters.pdf" # sim case 42 — single-bp roof: rafters 50mm (Table 16 col 2 → 0.88) +_SUMMARY_001431_CASE42_UNKNOWN_RAFTERS_PDF = _FIXTURES / "Summary_001431_case42_unknown_rafters.pdf" # sim case 42 — single-bp roof: rafters unknown thickness (Table 18 col 2 band C → 2.30) # GOV.UK EPB API JSON for cert 001479 — the API-path counterpart of the # Summary_001479.pdf fixture. Together they drive the API ≡ Summary @@ -178,6 +181,69 @@ def test_summary_001431_case20_fabric_heat_loss_matches_worksheet_line_33() -> N assert abs(ht.fabric_heat_loss_w_per_k - 285.9847) <= 1e-4 +def test_summary_001431_case41_roof_drives_rafters_column_per_part() -> None: + # Arrange — sim case 41's 4 building parts exercise both RdSAP 10 roof + # columns per-part (RdSAP 10 §5.11.2 Table 16 + §5.11 Table 18, + # PDF p.42-45). The P960 §3 line (30) "External roof" A×U per part: + # Main joists 200mm → 0.21 × 59.5 = 12.4950 (Table 16 col 1) + # Ext1 rafters 200mm → 0.29 × 10.0 = 2.9000 (Table 16 col 2) + # Ext2 joists unknown→ 0.40 × 10.0 = 4.0000 (Table 18 col 1, band E) + # Ext3 rafters AsBlt → 0.68 × 8.0 = 5.4400 (Table 18 col 2, band F) + # Total (sum of (30)) = 24.8350 W/K. Before the rafters column the two + # rafter parts were mis-billed at the joists U (Ext1 0.21, Ext3 0.40). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_001431_CASE41_RAFTERS_PDF) + epc = EpcPropertyDataMapper.from_elmhurst_site_notes( + ElmhurstSiteNotesExtractor(pages).extract() + ) + + # Act + ht = heat_transmission_section_from_cert(epc) + + # Assert + assert abs(ht.roof_w_per_k - 24.8350) <= 1e-4 + + +def test_summary_001431_case42_rafters_50mm_uses_table16_column_2() -> None: + # Arrange — sim case 42's single-bp roof lodged "R Rafters" + 50 mm. + # RdSAP 10 §5.11.2 Table 16 (PDF p.43) column (2) "insulation at + # rafters" 50 mm → U=0.88 (vs the joists column (1) 0.68). P960 §3 (30) + # = 0.88 × 59.5 = 52.3600 W/K. + pages = _summary_pdf_to_textract_style_pages( + _SUMMARY_001431_CASE42_50MM_RAFTERS_PDF + ) + epc = EpcPropertyDataMapper.from_elmhurst_site_notes( + ElmhurstSiteNotesExtractor(pages).extract() + ) + + # Act + ht = heat_transmission_section_from_cert(epc) + + # Assert + assert abs(ht.roof_w_per_k - 52.3600) <= 1e-4 + + +def test_summary_001431_case42_rafters_unknown_uses_table18_column_2() -> None: + # Arrange — sim case 42's single-bp roof lodged "R Rafters" with an + # unknown thickness, age band C. RdSAP 10 §5.11 Table 18 (PDF p.45) + # column (2) "insulation at rafters" applies "for unknown and as built" + # (footnote 1) → band A-D = 2.30 (NOT the joists column (1) 100 mm + # default 0.40, which only applies to the "between joists or unknown" + # column). Worksheet-confirmed by the case-42 variant set. P960 §3 (30) + # = 2.30 × 59.5 = 136.8500 W/K. + pages = _summary_pdf_to_textract_style_pages( + _SUMMARY_001431_CASE42_UNKNOWN_RAFTERS_PDF + ) + epc = EpcPropertyDataMapper.from_elmhurst_site_notes( + ElmhurstSiteNotesExtractor(pages).extract() + ) + + # Act + ht = heat_transmission_section_from_cert(epc) + + # Assert + assert abs(ht.roof_w_per_k - 136.8500) <= 1e-4 + + def test_summary_001431_topfloor_extracts_main_property_age_band() -> None: # Arrange — the gas-boiler-upgrade recommendation "after" Summary # renders "3.0 Date Built:" glued to its "Main Property" row header diff --git a/domain/sap10_calculator/worksheet/heat_transmission.py b/domain/sap10_calculator/worksheet/heat_transmission.py index ebdb2b52..6084addc 100644 --- a/domain/sap10_calculator/worksheet/heat_transmission.py +++ b/domain/sap10_calculator/worksheet/heat_transmission.py @@ -41,7 +41,7 @@ from __future__ import annotations from dataclasses import dataclass from decimal import ROUND_HALF_UP, Decimal -from typing import Any, Final, Optional +from typing import Any, Final, Optional, Union from datatypes.epc.domain.epc_property_data import ( EpcPropertyData, @@ -349,6 +349,27 @@ def _joined_descriptions(elements: list[Any]) -> Optional[str]: return " | ".join(parts) +def _roof_insulation_at_rafters(location: Optional[Union[int, str]]) -> bool: + """True when a building part's roof insulation sits at the rafters + (the sloping side of the roof) rather than between the ceiling joists. + + `roof_insulation_location` is the authoritative per-part signal — it + carries the gov-EPC API integer code (1 = Rafters, per the empirically + watertight single-roof corpus map) on the API path and the stripped + Elmhurst Summary label ("Rafters" from "R Rafters") on the Summary + path. Both resolve here so `u_roof` selects the RdSAP 10 §5.11.2 + Table 16 column (2) / Table 18 rafters column instead of the loft- + joist column (1). The flat deduplicated `epc.roofs[]` description list + cannot give this per-part — 190/329 multi-part certs have + len(roofs) != len(parts) — so the per-part location is the only + reliable discriminator (worksheet-validated by simulated case 41).""" + if location is None: + return False + if isinstance(location, int): + return location == 1 + return "rafter" in location.strip().lower() + + def _joined_main_roof_descriptions(roofs: list[Any]) -> Optional[str]: """Join roof descriptions for the MAIN (non-RR) roof U-value, dropping "Roof room(s)" entries. @@ -873,7 +894,17 @@ def heat_transmission_from_cert( # col (1) per the cohort, so only the literal "sloping ceiling" # string triggers the col (3) age-band default in `u_roof`. is_pitched_sloping_ceiling = "sloping ceiling" in roof_type_lower - ur = u_roof(country=country, age_band=age_band, insulation_thickness_mm=roof_thickness, description=effective_roof_description, is_flat_roof=is_flat_roof, is_sloping_ceiling=is_sloping_ceiling, is_pitched_sloping_ceiling=is_pitched_sloping_ceiling) + # RdSAP 10 §5.11.2 Table 16 column (2) / Table 18 rafters column — + # a roof lodged insulated AT RAFTERS (per-part + # `roof_insulation_location` == 1 / "R Rafters") sits on the + # shallower sloping side, so the same insulation depth yields a + # higher U than the loft-joist column (1). Driven per-part because + # the deduplicated `epc.roofs[]` description list cannot attribute + # a location to each building part. + insulation_at_rafters = _roof_insulation_at_rafters( + getattr(part, "roof_insulation_location", None) + ) + ur = u_roof(country=country, age_band=age_band, insulation_thickness_mm=roof_thickness, description=effective_roof_description, is_flat_roof=is_flat_roof, is_sloping_ceiling=is_sloping_ceiling, is_pitched_sloping_ceiling=is_pitched_sloping_ceiling, insulation_at_rafters=insulation_at_rafters) # RdSAP 10 §5.1 — a lodged/known roof U-value (the assessor's RdSAP # output, surfaced by the gov-EPC API as `roof_u_value`) is used # directly in place of the §5.11 construction-default cascade. The gov diff --git a/domain/sap10_ml/rdsap_uvalues.py b/domain/sap10_ml/rdsap_uvalues.py index ea1188db..b2e76b8f 100644 --- a/domain/sap10_ml/rdsap_uvalues.py +++ b/domain/sap10_ml/rdsap_uvalues.py @@ -747,6 +747,34 @@ _ROOF_BY_AGE: Final[dict[str, float]] = { "K": 0.16, "L": 0.16, "M": 0.15, } +# Table 16 column (2): insulation AT RAFTERS (sloping side of the roof, +# rather than between the ceiling joists). RdSAP 10 §5.11.2 Table 16 +# (PDF p.42-43). The rafter cavity is shallower than a loft void, so the +# same insulation depth yields a HIGHER U than the column (1) joist row +# (e.g. 200 mm: rafters 0.29 vs joists 0.21). Thickness mm -> U. +_ROOF_RAFTERS_BY_THICKNESS: Final[list[tuple[int, float]]] = [ + (0, 2.30), (12, 1.75), (25, 1.30), (50, 0.88), (75, 0.67), + (100, 0.54), (125, 0.45), (150, 0.39), (175, 0.32), (200, 0.29), + (225, 0.25), (250, 0.23), (270, 0.21), (300, 0.19), (350, 0.16), + (400, 0.14), +] + +# Table 18 rafters column: pitched-roof "insulation at rafters" default U +# by age band when the thickness cannot be determined. RdSAP 10 §5.11 +# Table 18 (PDF p.45). Identical to the joist column (1) for bands A-G +# (2.30 → 0.40), then diverges higher (H 0.35 vs 0.30, I 0.35 vs 0.26, +# J/K 0.20 vs 0.16, L 0.18 vs 0.16). Unlike the loft-joist default this +# does NOT collapse to the optimistic 0.40 "assume modern retrofit" floor +# at old bands — a rafter cavity cannot be topped up from the loft, so an +# unknown-thickness rafter roof keeps the as-built age-band U (band F +# 0.68, band E 1.50, A-D 2.30). Worksheet-validated by simulated case 41 +# Ext3 (band F, R Rafters, As Built → P960 §3 (30) U=0.68). +_ROOF_RAFTERS_BY_AGE: Final[dict[str, float]] = { + "A": 2.30, "B": 2.30, "C": 2.30, "D": 2.30, "E": 1.50, + "F": 0.68, "G": 0.40, "H": 0.35, "I": 0.35, "J": 0.20, + "K": 0.20, "L": 0.18, "M": 0.18, +} + # Table 18 column (3): flat-roof default U by age band when thickness unknown. # RdSAP 10 §5.11 Table 18 page 45 — the pitched-roof column (1) defaults # bottom out at 0.40 because "between joists insulation" is the implicit @@ -793,6 +821,7 @@ def u_roof( is_flat_roof: bool = False, is_sloping_ceiling: bool = False, is_pitched_sloping_ceiling: bool = False, + insulation_at_rafters: bool = False, ) -> float: """RdSAP10 roof U-value in W/m^2K, never null. @@ -829,7 +858,27 @@ def u_roof( (code 5) are deliberately excluded — they stay on column (1) per the cohort evidence above. Worksheet-validated by simulated case 15 (the 7536 replica): Ext1 band L → 0.18, Ext2 band F → 0.68. + + `insulation_at_rafters` selects the RdSAP 10 §5.11.2 Table 16 column + (2) thickness ladder and the Table 18 rafters age-band column instead + of the loft-joist column (1). A roof lodged insulated AT RAFTERS + (`roof_insulation_location == 1` on the API path, "R Rafters" on the + Summary path) sits on the sloping side of the roof — a shallower + cavity than a loft void, so the same insulation depth yields a higher + U (200 mm: 0.29 vs the joists 0.21). Ignored for flat / sloping- + ceiling roofs (the rafter distinction is a pitched-with-loft concept). + Worksheet-validated by simulated case 41 Ext1 (band C, R Rafters, + 200 mm → 0.29) and Ext3 (band F, R Rafters, As Built → 0.68). """ + # RdSAP 10 §5.11.2 Table 16 / §5.11 Table 18 — pick the rafters + # column when the insulation sits at the rafters rather than the + # loft joists. Flat / sloping-ceiling geometries keep their own + # dedicated tables (rafters is meaningless there). + use_rafters = insulation_at_rafters and not (is_flat_roof or is_sloping_ceiling) + roof_by_thickness = ( + _ROOF_RAFTERS_BY_THICKNESS if use_rafters else _ROOF_BY_THICKNESS + ) + roof_by_age = _ROOF_RAFTERS_BY_AGE if use_rafters else _ROOF_BY_AGE measured = _measured_u_from_description(description) if measured is not None: # Full-SAP cert lodges a measured roof U-value in the description @@ -852,7 +901,7 @@ def u_roof( # genuine "no insulation" lodgement, which keeps 2.30 (below). The # discriminator is the deterministic "Unknown" text RdSAP renders # for an undetermined-thickness observation. - table_18 = _FLAT_ROOF_BY_AGE if is_flat_roof else _ROOF_BY_AGE + table_18 = _FLAT_ROOF_BY_AGE if is_flat_roof else roof_by_age return table_18.get(age_band.upper(), 0.4) if ( is_sloping_ceiling @@ -877,9 +926,10 @@ def u_roof( # uninsulated 2.30 W/m²K. return 0.68 # Table 16 row 50, "Insulation at joists at ceiling level" if insulation_thickness_mm is not None: - # nearest tabulated thickness <= supplied - u = _ROOF_BY_THICKNESS[0][1] - for t, val in _ROOF_BY_THICKNESS: + # nearest tabulated thickness <= supplied (Table 16 column (1) + # joists or column (2) rafters per `insulation_at_rafters`) + u = roof_by_thickness[0][1] + for t, val in roof_by_thickness: if insulation_thickness_mm >= t: u = val return u @@ -923,7 +973,7 @@ def u_roof( return _FLAT_ROOF_BY_AGE.get(age_band.upper(), 0.4) if is_flat_roof: return _FLAT_ROOF_BY_AGE.get(age_band.upper(), 0.4) - return _ROOF_BY_AGE.get(age_band.upper(), 0.4) + return roof_by_age.get(age_band.upper(), 0.4) # RdSAP10 Table 17 — U-values for rooms in roof where insulation thickness diff --git a/domain/sap10_ml/tests/test_rdsap_uvalues.py b/domain/sap10_ml/tests/test_rdsap_uvalues.py index 1018ce30..8d4e3612 100644 --- a/domain/sap10_ml/tests/test_rdsap_uvalues.py +++ b/domain/sap10_ml/tests/test_rdsap_uvalues.py @@ -961,6 +961,69 @@ def test_u_roof_with_explicit_insulation_thickness_uses_table16() -> None: assert result == pytest.approx(0.21, abs=0.001) +def test_u_roof_at_rafters_explicit_thickness_uses_table16_column_2() -> None: + # Arrange — RdSAP 10 §5.11.2 Table 16 (PDF p.42-43) column (2) + # "insulation at rafters". A roof lodged insulated AT RAFTERS + # (roof_insulation_location == 1, "R Rafters" on the Summary path) + # takes the rafters thickness ladder, NOT the column (1) joist row: + # at 200 mm the rafters U is 0.29 W/m²K vs the joists 0.21 — a ~38% + # heat-loss understatement when the joists column is mis-used. The + # joists column (1) stays 0.21 for the same thickness. + + # Act + at_rafters = u_roof( + country=Country.ENG, age_band="C", insulation_thickness_mm=200, + insulation_at_rafters=True, + ) + at_joists = u_roof( + country=Country.ENG, age_band="C", insulation_thickness_mm=200, + insulation_at_rafters=False, + ) + + # Assert + assert abs(at_rafters - 0.29) <= 0.001 + assert abs(at_joists - 0.21) <= 0.001 + + +def test_u_roof_at_rafters_thickness_ladder_matches_table16_column_2() -> None: + # Arrange — RdSAP 10 §5.11.2 Table 16 (PDF p.42-43) column (2) rows: + # 50 mm → 0.88, 100 mm → 0.54, 150 mm → 0.39, 270 mm → 0.21. Each is + # higher than the joists column (1) value at the same thickness (the + # rafter cavity is shallower so the same insulation depth yields a + # higher U). + + # Act / Assert + assert abs(u_roof(country=Country.ENG, age_band="C", insulation_thickness_mm=50, insulation_at_rafters=True) - 0.88) <= 0.001 + assert abs(u_roof(country=Country.ENG, age_band="C", insulation_thickness_mm=100, insulation_at_rafters=True) - 0.54) <= 0.001 + assert abs(u_roof(country=Country.ENG, age_band="C", insulation_thickness_mm=150, insulation_at_rafters=True) - 0.39) <= 0.001 + assert abs(u_roof(country=Country.ENG, age_band="C", insulation_thickness_mm=270, insulation_at_rafters=True) - 0.21) <= 0.001 + + +def test_u_roof_at_rafters_unknown_thickness_uses_table18_rafters_age_band() -> None: + # Arrange — RdSAP 10 §5.11 Table 18 (PDF p.45) rafters age-band + # column. A rafter-insulated roof with no determinable thickness + # ("R Rafters" + "As Built" → thickness None) takes the rafters + # age-band default. Band F → 0.68 (== the joists value at F), band H + # → 0.35 (vs joists 0.30), band J → 0.20 (vs joists 0.16). Unlike a + # loft-joist roof the rafter cavity cannot be topped up, so the + # optimistic 0.40 "assume modern retrofit" joist floor does NOT apply + # at old bands — band C stays 2.30 (vs the joists-unknown 0.40). + # Worksheet-validated by simulated case 41 Ext3 (band F, R Rafters, + # As Built → P960 §3 (30) U=0.68). + + # Act + band_f = u_roof(country=Country.ENG, age_band="F", insulation_thickness_mm=None, insulation_at_rafters=True) + band_h = u_roof(country=Country.ENG, age_band="H", insulation_thickness_mm=None, insulation_at_rafters=True) + band_j = u_roof(country=Country.ENG, age_band="J", insulation_thickness_mm=None, insulation_at_rafters=True) + band_c = u_roof(country=Country.ENG, age_band="C", insulation_thickness_mm=None, insulation_at_rafters=True) + + # Assert + assert abs(band_f - 0.68) <= 0.001 + assert abs(band_h - 0.35) <= 0.001 + assert abs(band_j - 0.20) <= 0.001 + assert abs(band_c - 2.30) <= 0.001 + + def test_u_roof_unknown_age_band_falls_back_to_mid_range() -> None: # Arrange — nothing known. diff --git a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py index 3153f1bc..16e362f9 100644 --- a/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py +++ b/tests/domain/sap10_calculator/worksheet/test_heat_transmission.py @@ -151,6 +151,44 @@ def test_roof_insulated_assumed_with_ni_thickness_uses_50mm_per_section_5_11_4() assert result.roof_w_per_k == pytest.approx(68.0, abs=2.0) +def test_roof_insulation_location_rafters_drives_table16_column_2_api_int_path() -> None: + # Arrange — the gov-EPC API lodges roof_insulation_location as an + # integer (1 = Rafters per the empirically watertight corpus map). A + # roof insulated AT RAFTERS with 200 mm takes RdSAP 10 §5.11.2 Table 16 + # (PDF p.43) column (2) → U=0.29, NOT the joists column (1) 0.21 — the + # rafter cavity is shallower so the same depth yields a higher U. The + # per-part location is the authoritative signal (the deduplicated + # epc.roofs[] list cannot attribute a location per building part). + # Geometry: 100 m² plan → roof area 100 m². rafters: 0.29 × 100 = 29 + # W/K (vs the joists 0.21 × 100 = 21 W/K). + main = make_building_part( + construction_age_band="C", + wall_construction=3, + wall_insulation_type=4, + party_wall_construction=1, + roof_construction=4, + floor_dimensions=[ + make_floor_dimension( + total_floor_area_m2=100.0, room_height_m=2.5, + party_wall_length_m=0.0, heat_loss_perimeter_m=40.0, floor=0, + ), + ], + ) + main.roof_insulation_location = 1 # gov-API int: Rafters + main.roof_insulation_thickness = "200mm" + epc = make_minimal_sap10_epc( + total_floor_area_m2=100.0, + country_code="ENG", + sap_building_parts=[main], + ) + + # Act + result = heat_transmission_from_cert(epc) + + # Assert + assert abs(result.roof_w_per_k - 29.0) <= 1e-4 + + def test_lodged_roof_u_value_overrides_construction_default() -> None: # Arrange — RdSAP 10 §5.1: where an element's U-value is known from the # assessment (documentary evidence / the lodged RdSAP output) it is used diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index 13e9f6e6..fbf10705 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -67,6 +67,23 @@ _CORPUS = Path( # energy were 5% high; actual SAP bias is +0.145). # So closing demand over-estimates lifts BOTH the SAP gauge and PE/CO2; there is # no one-slice factor fix. RATCHET any ceiling up when a slice tightens it. +# +# RAFTERS ROOF U-TABLE (RdSAP 10 §5.11.2 Table 16 col 2 + §5.11 Table 18 col 2): +# this slice billed roofs lodged insulated AT RAFTERS (roof_insulation_location +# == 1) on the spec rafters column instead of the joists column. Within-0.5 went +# 66.9% -> 66.5% (MAE 1.039 -> 1.064) — a SPEC-CORRECT move, NOT a regression to +# chase. The calculator is worksheet-validated to 1e-4 on simulated case 41 +# (4-bp: measured rafters 200mm -> 0.29; rafters As-Built band F -> 0.68) and +# case 42 (6 variants: rafters 50mm -> 0.88; rafters unknown band C -> 2.30 per +# Table 18 footnote 1 "applies for unknown and as built"). The dip is a gov +# open-data REDACTION artifact: all 15 corpus rafter certs carry NO thickness +# (blanked to None) yet lodge roof energy_efficiency_rating 2-4 (insulated), +# proving they had a SPECIFIED thickness the open API redacted. With the +# thickness gone the spec's unknown-rafter default (2.30) correctly fires but +# over-states those certs' real (insulated) roof. Recovering them needs a +# roof-EER -> assumed-thickness inference on the API path (future slice), NOT a +# change to the spec-correct U-table. Do NOT revert the rafters column to "fix" +# the gauge. _MIN_WITHIN_HALF_SAP = 0.65 _MAX_SAP_MAE = 1.08 _MAX_CO2_MAE_TONNES = 0.35 # t CO2 / yr vs co2_emissions_current