From 5cae4a0ce5e14c60775114b35df6ca2ba8566ae6 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 3 Jul 2026 12:25:07 +0000 Subject: [PATCH 01/23] fix(rdsap): RdSAP 10 Table 13 200/280mm solid-brick wall boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _u_brick_thin_wall_age_a_to_e's `<= 280` check put an uninsulated solid- brick wall of exactly 280mm in the "200 to 280mm -> 1.7" row; RdSAP 10 §5.7 Table 13 (spec PDF p.41) shares 280 as an unlabelled edge between that row and "280 to 420mm -> 1.4", so the table text alone doesn't say which row owns it. Found by building cert 100031768368 (280mm exactly, band C) in Elmhurst and comparing every worksheet line to the calculator: volume/ACH/floor/ doors matched exactly, but the wall didn't (1.70 vs Elmhurst's 1.40) despite an identical input crosswalk. Initially reverted a first attempt at this fix when it appeared to regress an already-pinned cert (217091901, band A, also nominally 280mm, previously "confirmed" at U=1.70) — but that cert's build script never actually set a wall thickness field, so its shared Elmhurst assessment had silently inherited a stale 260mm from an earlier build. Fixed that build script and rebuilt with the correct 280mm entry: Elmhurst's worksheet now also gives U=1.40, matching 100031768368 and confirming the fix rather than contradicting it. Both certs move to (near-)exact lodged matches: 100031768368 59.12 -> 61.21 (lodged 61, within 0.5); 217091901 60.82 -> 61.59 (lodged 62, exact). Corpus gauge 77.8% -> 78.6% within-0.5, MAE 0.636 -> 0.627 (14 corpus certs lodge exactly 280mm solid brick). TDD'd, 46 pins + full suite green (2310 passed). Co-Authored-By: Claude Fable 5 --- .../uprn_100031768368/elmhurst_summary.pdf | Bin 0 -> 65549 bytes .../uprn_100031768368/elmhurst_worksheet.pdf | Bin 0 -> 46083 bytes .../uprn_100031768368/epc.json | 452 ++++++++++++++++++ .../uprn_217091901/elmhurst_summary.pdf | Bin 79974 -> 65549 bytes .../uprn_217091901/elmhurst_worksheet.pdf | Bin 43333 -> 46083 bytes domain/sap10_ml/rdsap_uvalues.py | 13 +- domain/sap10_ml/tests/test_rdsap_uvalues.py | 24 + scripts/corpus_1000/worklist.md | 402 ++++++++-------- scripts/hyde/build_100031768368.py | 252 ++++++++++ scripts/hyde/build_217091901.py | 9 + .../test_real_cert_sap_accuracy.py | 78 ++- .../epc_client/test_sap_accuracy_corpus.py | 18 +- 12 files changed, 1024 insertions(+), 224 deletions(-) create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_summary.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_worksheet.pdf create mode 100644 backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/epc.json create mode 100644 scripts/hyde/build_100031768368.py diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_summary.pdf new file mode 100644 index 0000000000000000000000000000000000000000..db84a0784e3cb4da8814de761b4744185194813e GIT binary patch literal 65549 zcmeFa1yo#Jwx}CHf)g~sU4wgYcL`R(3U`OXEqHKuP4FOv1h?S9Ex5b827SeU`tNhP z`@Vjs`;B+T8~0W*HrwWsz4u&e%{^CrH7S+EBp6s2*%4Vtm`Q96Ecp1CRNd^1nMCzL z`c}54OiKEu#*QSckdw*+0>(B*5D~1kc^m_q>Rl?%|Ik9 zZ0wM-tZW@r?DP$dnZ%5p%?*u}B}JG-%t4My#tx#k)^@fw#*n&jGb!j>L-Ju}k}$Ud z89OjZSm}d|#f%MYjf|P(jBQLI8FO&5atjDJf*g$Xtr0&49_S!As!GN_ly2Xc;HLHiQ=+vS8D1egekqb{{Q9EvC^%;_u#g1VXDGr+en7mN)R5L zB(lZ$p}`Z(5hK-ffX72%^GO8v==&?>5G-Iw)4?|$0`-AbU`##eJgr|c_mRQp*I{R( zD9I=0JhWLOWu1mOzhF#Rrj6j%7CU1%uQhAH*DUNS6HT-t7CP+mBIQ z4@#<3jxQax%Q0$dn9=$uL6H$#xn-!qFs1$-ti%EgWl~zRznu6J!fqTVBCHiy9Iw09 z+Rk35AY3$iy~U|(s$s;B?P1a#l;I~t#SKt!+MjDrh!`x1Pm!XMy~J%%vQqB@ackGz zT77yoGXKaI)@5UT#0_j7MsKosYuDuP6E7X|50(H-;PAYMK3Mz5NgpZh)A9eoLv*71 zR2XR$)4Q7{Mr;ig*kMP9EeCkbn659I5GB>|-EVn&x>fQsLy8`D>Q9cx+GUI^O{v2! zSoH0jQCn}VEP|A8x8Ha#Y<69I+;2OJX;oc7HebO|NK#*P9b>CMx8U-RCLsAXW`51% zqYI|Wmdy&6|IB$)(6TdwyONh=Y5BGr)9q!``zbrXk!KnxzXZ`Mwo8*%sOSEs9eYFa zQQN8GvGk&}lTK3=FGJXt~(}eL_)a!??LqkFbNKV6L(A0jA%ycrsuZ z9E}xhO3$0`T3cwxyIf1(n*3g!y5+SLFhx+YU%{WfCR|wL#C8(Q=A}K}1?-%LjxFus zIj{OsyR;RL1s^24li{b)x0th3HaOZnv*}q2i{aow>w7zgHWtqVrr8j&gWXUAt~Gz( zd+bdZBeZOr;N`J?S5VGAa`$vz{6(L18~yDs(s~*1=8Byx9<3&kRPSnnLi3_qzE@zn zIq!k;hml|C6~urkBd@FSjyxK4dB8aJ~CKL&;2tyd+i86i`v{wDNIu|ri8CoQvRW%CGd;v z%#A0g;#I@=*sdmfP4nBZ6=LR@^GhNgmtLT_8#nvvqnBWds*!C;Mbpi(r}y0U$H((` z`(iN-)Kt7ZRNr_T9u5q?>=evN0H>YHIBc9QlvkMA!@!f18(AHXH%&iJ`*gA4oo1W~ z8$Tw8oRs~D1vvK8TNz*adEm(eX=5)?N_?e!038Sk6~_VkTMN|zY$hdYFA@RCVt2eJ z-j9woz2#pjrypTPKRjmYlKDhdn}vRqM4G-kp%)FGjsBDKIcRJR z`&dmU?qN0O%c#|N6STx8gTWuavFY7=TwGpuagPtu6vDff0e&b?$fW^(a77H>9I02k z%yRX>A%YbWw5#T;q|yK-^*ZCD#=2Xj8H8r`YhkiVO&u|6##kRuchCG|aA-FSc%NPH zXsDLmp)&;R;*gy%IHkNjws8Jky=YfBm{P4AI6z;xeObwG4IH#PXt?RfiDwd#?q1L9 z^!<7c(YVPr{clYi;Ih39A=z517XIu@`qmeXpWHQ)3?W_dbbJZ|c=bf5c}1zB5}y@# zjQCAF+hKe&0YfuY*?!OPu{NGrIbX~{sMZSv6c(J)MZ-H9fNRMAaFZSJkgGDJY+}+* zzkIz>d~;;tQXQBaa)j285jM%Pxm(8R0Zeu@C?xpm&y*0DSwdOndt}CBsyW>%uJ>#sqI_I z5dFbZI)gcR${K1T}x3uC>)z$*@0(kMoxBcJofDPHs~L-2`*z zM20w%)Rx|Nkgxu{FPKj0!hijnATs8`Dg?!6x=AGzb&ni?i#8x7aWA(el#Hr@8#;0_$w#M%?)DCPrQrXVz z_;s$xRANNU$Hg;-2X#;(@PYas`F03)INzijR~L0?D2$!2m6@3h9$!OHlwSGB=so-w z$AFS!3zo`0oT0K0jajqoE094n%i*Cc1w+8Y zZeO!vn52&kyf`3_<6+cJKW>rmtXNxP_TBz>43Mgb`j6?m(%%DbD91UnE*LhkvW8Po z?&f=vepUC~WC~<)ngjxTQNvo^8nqWA$zRE|9=S5%A7J_5fi>9P1?$LpDqXV4apIo=#~ zR+tj)TdycvbFhjsv~Trw#8n{hw>pF}9egjx(BF$g03WZ&6^|Uf*q;J-U)i3}n9ntl z=FvCXCINnQtM7l-U#92LUoi<(CGBVs8~ag=c6zEW?i_x&;FO@Uw|w03B?dAMLI%O3 zhpq#my@UM(#loKac6gJm^Sfcwt?@D~^^#0GqlEjLz5s((qZn3Sx_w5ipxdD%))yzC z3*IOxio8{(-wy@w%ZuJLO2jZ&rZyXZwJ-b@QW=1OmxqMMvcO1yz;C2ft(k!m{)AIT zKHp~bbOvBWL5)#y=z^rc2c=UD@r#`?nLB^^>+zIRy2R*`h&5NrZ0*a}v;#%?`b!N^rgdCEI;e`MUk_}Fna^D8`d5R!0bh^bhliTPv9 zLl3P6XR2%7J`&^!-4ck09dv5E)}+cPFqn{%t&T45bs7aY$;)u|(8KQkE*8W}*dc7s zT+Zvv+zNm_a72{dQhQ`KbO>rP)T!$4ADzBbtYyYPxWk`pC^B-pSi`FY5dA!PaU9hk z(Tv{3Zr7yzX*(F&1Do1p&#_IOxoAsBQ`6tK9Y90l&)cwU9P8VMbGb6nn-|ET; zj=iJ){pt$UrR7R6tl!_;vSy-hw|Ff9_0p5~oWM(qG-k4$<2pMF;e%^eW}Pf~uLQhD z14o2o^1fv^~TQEaM+?99_AmY%JcA|lmdqx?;Pyi zleC%lcJg}5aF@7%zZellZ9h)ANIiN3bAEScv06f#-=V@hT_#`vbKDK$XgXCv-dp9 zxC6iNN|xO_u@R&eW?M(rC7SVuiB zebyHlR!vJVbi^3QoSRieDTg7n$i#OZ9C-JzpI^ZL^Uqs){50Lkc|&v?N%9GI}mR=)>G1bLAf1f-p%zljgbW1V2kcSUVg1Lh69Q4_0{Jt5iTH zI3)Y^7NOO$t1&UctS|}QoDK0?dK)j#<|+@Hl#{Q$r-&9!b_DSEhOXW#yJ%66E(L8k z7~9F{;Q}!ew}!71yj>894LqR2SX;@Tdg^KSlxz&_il;p`)&*}Id_rYMn! z53IyIE(n83XIZ5Sjb4T~EronKIGESp>qLqQjxT&4y>)zgKuice6FHRLt}P^acu@^I zhw-AC<)5&bKv?EBUfLuGllMcfT&Qcc8v!Cyq;ZSXI9Hy=M>Na1%0ENyFp#BBO@!R& z<}L}4lI5Ik>>8MG?P-ZRWLyW(U#D+?drx=e+$JQGLcQM*Ai4>cWG{W~_~7Wk>u(?Y znD-S&oebfK4MEVm^4Hg0VTn=Qexm9e!L<|aP`^85^ERPg+-NlLNW^KJ3gwglxP|^RR__Z#`z_wC zvXEd2*I&maYDwUI5a2kX6LS?i)hD1)SMG{nBW7=y{ye;$K6-b$HZTh-k#3ZcrUaw2 zT3KW@n~#jvl-MKE?`)TiLx)amqYHC&;s{B0^mxWt&1Iq$Pv^u$bUaoNN25I2G!tS| zD<%~BnrvI{l9G>-Qe^qK128jaFX`(|t?WVkeW!i3-R~uG0Xfv~eXG}7?L4!L&Cy#;*pXlx zB&oElJa_GyP0N+NZd^y~q>shFF(V6SV(je*cBP~ugk2~3P?8oiX5dhcEL0qiuds^x zQoC2e#*t)jTI6#Uv2Fuh<|j{5#SL`_k%zVVL%VPaBK+Lb&s)s8M3V2o+YT(ZUcJ@} zKZWnpg_OUFtYMc3*5py#8M`0~yNRE1+?KK=_Lc}S2`3m#oCr02t?0g_^CGMVgaD?H zRb#4e*1ZLl_T(sYuveWBv2&x`Eh44A#>$j{NZzWPs@WIP_vMlcaTFcw2MtB#_acy; zJ71}653~muxj8j}hUMwC_%UKj_C8>Op*O-OrRy&%;hhSM*t1d%uJ+vt zuhaZrvekoajN z8wN14LLFqF;nBKmHlg(&?W8|Pp3S>Lt-rjP0VJ3r?GHBLlr{M_b(_S!@ZV9JM1k1f z0dUBS>hMBbeY5ds;7<3tN*K~f{*S^@Qn2(hup!&UEH1&r?RTBvB{(I*Z@h`fkfiV+ zN%M7$ky7O}d&ER3S0E8e)i?v!_-T57>FD{{Ex)7CR{~N}Dt_8#T$O8A=`zPwy^P%J zD9-)BYSJ2rEdQtksWlPOHNhUORmBOtRX1tRzem|MImjNb+a;x)tAik$)}_Y#ULe#PnOtJ7k{v zp{boO?+oz=&4?_j+lscaM_U#^nh6-b8Wd@TsmWoi3vn$N2=-I+x4jb08Q^tzdSzkv z7wru`A@j2l%^iL#^4VZHDmckCZZK}Tl3MvYq2bJT5z8BT0mfX!ZrWYs$&ekQ>h@>x zj)(jw2u`U|La$4Aw*BB>>3bzN7DIYIVA32XKBpW-Kqmo3zx@8o#aYO|ZH8x@jLv;{ z3yee`bF(NAj~qu_J=W+xTu;E&zt>uc7jbxSa46Wu=OkAJkATIZwB+DmcMM<3R_@^_ zy#o{GFG{lASn-10@)lmp#dY|(p|0SP$YrW0oit}??_EWR?Z)wy>i7smTTidh6O}S-hz?!nFZkA892f7E052m zP#d2iLVVkQaRdLw%l+i7{x4qc|E_x);;H`E+|w-o#68W$&cXaQ?rC@s{#3$5ru9s` ze5FSdPl(G@Gp|CrQB>Tt@;^xchYzo8zO$)jeFy=NGVuSTsu6LcagF(e59T zlBX3`%*LyOgouX6eLthYuZ~2&#;fxh5e-#6N$llI(F$t|=>_UWYFX!o_gF;e^O;Tj z929g%Aq{Ph?3q4(99Wf24dvqrU(2^xSXo*5$yU#C=C`jKq?Y4<2&PO}a<+_Ak6(N( z53VRLXXfVSe$Ohv%AVQDHnct7zgH`JFebB}wW_#z06L)ASCdmvK!ovIS^1J@|ABLu zEA`4g=2PaJWdA-(!~ypZQGMMwc5XQ}%lr3qSct*F!CT#8DEzRvT7q;$Ad^dtt;?BJ zwz9Rfug|Btu)iVswNsVX)xDzTeg~($GJUM7+TXiQEuq} zf@5Id7PswZoi7pW)ng5y*!RPzgPcd81loP)638c|=nE+VSVFR!N)or7N7C@RClqe- z1AXmM(Y0_98OU+3AxQmefl)jUbnuK<#W1u!J^`+s42-90->B5BByo6Ch z?ke`C@hf3{7vY928927u!(({7eGSE2kR>j{HewMhZpwU7kYFNU%jHI$W}j?eaIhD@ zxbef6Y3!S+mbIm8A8Zj~H-PJu#l@&{!!KEw#bOud8xxv+JY+-|ujb~pwYAeGKl&y% z(mso;>XI9`(N9%P?yaVkW_kWZoC#4Br-LpxHZ=PbtCNtB$kov&6>Klx&tmf!FuGZcp$qoxNJC3Wdh`R{lTGJo)EyySbviG; zaD`RY(D?Xu^Q!S3A0-OM1$`3Z(ve?_Bt&n7L~N@fU;~$7((v#@U?T1D@Z7r`kZQt4 z&g=rc+ELFcCEK+GKDj5IH*cM5w)s)ISZD$Gc7M2c*WbWWNKRJG2HsA0aMd2*{3!nM z$#E!BfxLtKs<9+-69-dtEg7Hd&_#HO{8)1^T)v8xt+r}}^hPPVo=!B zB5ElI70y`zJ+dXYiP}Y0;7f%7clgFoSdgnwQfg9mi(bhq;HV{#mHxJ#e6PNKBjlhS zHWbkJp}&mZV;7`sFy@SdSl4~4oNmd5SXEE#>It$PQ$TJewVMK?U=$|2)n-}`#>s^Lty=j@QM!10EX zu7PjR+pGjQoWgu)ICT8VfLO7uO@uwi0qsuD$yt}jddKltgH-r=;^FmEZ?hQ2gl^8ui&pk zg*-pUXUFJ%J9}Coj{@|Cee>$Y=Le@pV`5qs7Zn6_xw#8-sg{st=VpHpumYt$$%4nr znco>)EGf}1y5!u8O+*ui$2xWSh|2fIg-TtjDW)bqcnqsvOwz%%-`?I7Wqn{{VLv^& zgmc`tvKmf*Z*3}D3@%O@lJ2H=0dUDXp=^uZMQ|Au+GZE!3%h_Eob7&1jE#;@MudM( z&T8eo4Djge5cIeSe>pgmpde{N$Y9OYK)>jB&XMO<-PD9kwqs`T1uL~0MIk`rM}p1{ z<6WfFnfdtmnBaR5w@y))Q1S2coT&c(K1xZU2StIq!ln|d()mOjQ+Vo#Dhpzw_wRN# zgiP>d>8sgHtxHSzwHNlyoi%sy3V#ItAo*!xB=uSGdOt^z@PJ?>{+RvT={y`IyygBT z7o0S_3GAk-XlR(79eZI~kPblCM7g-IM$zH(E>;;lBXR<#TXO5EofKjyzowEcw$s&4 zy1}7?!whvXGB6q#=oS7bLy(@4QT(kWcV>Fm%wkG)N{Th^gpQ9r{BWI*msd;mIvU4-J~Lz0$ouH9i3CcRQs8iI+HS<_6A@e@>1syhM5?P<--cK@Njeeiq)&ZoL0ytkTCDyu9?~r zX`^GEvHofR_h-~4Y)EnG7jtX#z+}|PqGCtu%U6v-*4f3ujc_M=eM+XDU#ZH|eBKsn^R`Cvi2_w?IYJuJ@ez`J-I6EJ#H~ z1$PL3rMPm8wYQDotZ$$bOOg==_KWY&SD!|f(G6dA;PC|gsK2_r6cLe%RIU1^-}z<- znKI9(^#}fQ{p>x}6*U#|f>yWG55#8OH!+SEXMNJuCSZ%KD|5F^uEgQr)W9Xx(jV~G zBc1UlC2C&^scv)AYWS$Il!Z3JSt4V1CIKj3AJkmoF3f z31D{1Y)~`F$vULq$7jO0m;=hXOLW^XJ3@bhzP+<4z|vy#>Sg!Ha1UAp(n?dTB}So1 zMNJAo)&N*!29Ca#_p?xl5xr2koKUnfEAgBo<}zuz^y`M#y-8PVmT9_lep4LwIz|TW zb~(3%p`=|zB*drJ6*dHQ=-T~uKu(dq300Ma9e;2(ajs7-`T60S7Ai(g1qYZW5n(%N z86alea}QVs2A)a|ps_E&8CA*4I4_Sig;Z}S3n^ejOgm>i6{aQ&;Bt`A*JNRCwZVk~ zQz6J=dJiHuyht}4AJ6*jUAMH~zm#%ldbY<^7V*Zd-c1uPbw*=`tb!RB>o3(55)hom zYxNo{P>L2T7ZILt&ekDL*3tD-suEe7aq8DJOsFQLHh%sRI|0nsS-=!Bk3+wjFL^pQ5r4Q%=^u0o;eRAHs%IoNn>kMBnt>n^2>^ zC-zd`x)M%Vp;M=&&E;<^>XKwD*g@XcLpUCt0+WW!pLTI7V!L1CDwp${St~ zQiKe)lc!Xr7!t`Wls^rP$z&X+C->?|<1+ZEAgv%bHa^KRG9D}{NkLXxlv{CoPofMM zm!hSf2)dB%*)8aG-B5ol@#tV9_V7MU+&J++CvRorvE!6d?4s-H>GEZc{^+XyN`D=* z1RXe2y=?|_J8l3k*-g)_^4;b0$I42Fseous5sFIWSKxtgW4A!p<8CM8P$c@=q&g&d4yTVbes`YIJ{d4`!}QDqgKjEK7S>j498U1%OVvjA z@I@87u5!E&5nOT#-a>+>zGpF9)@aRZ$GyvSGI1yvm>4I_^z?L`L+)rsO`+kRzgK+U z-8&%XecdGTNl95%F3~p}(04mm+g%J9kb>OdSex24)DjY8hRB``;}hctOXPZQme4Q2 zdW+x89c*ELxA)LW+x0%qq)(41&S}GbPwxsjt*mPX~9zEsM;FF|D*Ws1y*hV;Pa8+i(#!)4VqPKbM1HjCnxshNRjwXiI)$b zOY}!rYhnOGj*gDauDSX3MbhFH7wi$(9Gw~!ME2jj)nh8%<;XXmg-;0IY{7sbD&VHR zh4y78NwrAPODCh^%LiApH)4&UwdL@xYD&+7NGM2o1IK^(Uey-Nc;NzH{~R7<<77re zL5(3ODk>TsA6?k|jZDKH*H&0m1_%DZcMjs2y~>EZ z8@!bDQx$WcSytqnN>?jFlfqcuisIIheoR2d!ND1Vg&85taA5$`_0ccSW3FQURH(NT z_-tahx4wM6;6{mssFS`!RCb8D=92SsGEw~vi zG$P*|cfiAoSG~Q3ZOtL4#d`en!!V>;*^IOO9@eUOd62c2c2HL!tv5(4N zWjLJ-9Yzpt6G!iBqNuI?^>9tvBF-TPNBcN{ifd5xxSd5_pUbhgPT1jCKF%WJNcqeCu!QrnW~ z%Q;WG>5Z@o^BDx&Z+iCn!rV0bJBPlKjiqK{qA#W|>+5+YUiKdJ?Doy7zhVB;%-^iC zDqMIJnXdM*!WktTZ)bAGFztvY+UE1X<21;S%MzUXIORAkf?OHqzg zR9GIp9dEXE+Mp#NGA+sQ&1Zk%^j8wD0Jq*{got6_xoNRWlxs%1ZZ0RB+voD{-vgM@ z8Er)^NMGw4>I*+F{^pSF7CE|ZId_L9u>)>4Ke@P7ki?F)U?5`mxn1WOs!X$R1yPD=48i{X32pOT+>f!< z)vljECnp<`1nK+w_!s!koj@U!-Y_sPgr)3W4R%jP9PaLHu9zJyugXyeg;G&bk&%;0 zbUA8ksHxTc3i@SXY1wEeV3um+{Q!Kfs;(&`!x#tYrx8yP`;)72Etc58?&~D?KY5)I zDJJP9zS;)C%E>s%q*xJKMmPLcL?yBot(BQv{O8N%RN2WH2$}kY z0IeTKxmkajXBShqx_2cU`h9Q8%?@iRs(Ag2?4#JTcz@qj7f|*)k5odwY|sNIBS1x7 z$NL~$ijQfkzhiv3hl`zCwNjag58fJUsr5~EaeaunhgM@Op9ien2FXNzlRj(}ZQk)$ z-lMHu7I438N$ZTyU24~y^~*{DafDsVy^+Mc-~{85u`z>O+v&NpD-Cx03j3)tp!j%M zv}Q1)|Jl}y7|ENT5qn(AWC;T@BAAZlRdIJnK!`i&(^wtWHbdOohr=6=v1u3)q{E4c z6orxt^o6ubk~-79_1DC>Xq>TLja$r+QI8a$nyYSaBMu~D%ib`)FWFkGe9y>Gp;xjD z!yy{ZsrH-;7x31R0*%jZ2O(M`B?a=jNK3J&h>)9~o0(LJ)*;$q(w&d8p3^{fGE6zG zso{ussLAuD@N>DHBPQGLoh!&IEO(iwXxe(N5#%)n0sM@Z>FBzSJ1}J>+I+I6OFY-| z{UzB*9XkH1agjvnos5a2*kF=LNfwdAy|(-51nCW~qGc-{plsi%Kf9_5^T54h^b#5F zr3~=Y*1{n)Fs1&~izEy&?8V(3<>RK&){i3VkHnCX4wUp`+}4k~ItBXFuxK+`dVa&VNh4oW^kJnI!gpd)Jb? zcAtF~BdF2h_}LqzGHy5`vJ*MH11SKg1SXmQUO{kow@1nCrM-`YIW=FkR& z>w*giM4;>H>V2!9WF&(O@`#`BCS-jE&NZYLO9fGzYRgLtU-vCeGSZ(N4y|NASD>YP z7vJjoN%xst;VGg}MaN|C06bb=OWdZhd#ev9Y0IkK(FtL=AJT%DL@}+Z*OC4 zH}1{9x#m-?vDfu}+w&Y^*DO}@^0C&}H-kUmsIn|BLwZ&U1e-9yyo?VZ)%bHO{lMrI zY%4&1ol00qyhq?dqMU`F4Lpm*H|+iGKziE<0QW0Ir;;TRirv!eb<|IMd)zZEj|LmDFhb`{5$ z>EVjs)IENSKK-X@Q|}hNr%uLuXXF(_N{pvwHD_q%cSI0seiR4PxtbNoCwvV6oK)E3 za|S~?K}%Ws-_f}c$KS3Fs~3AxZ$RyWYS$$@iOFo;{C-KvdZ*a7TmAD4IuoLftlNQ< zwARP4-yziOx*@crI*bJ{Y~yU|IEj5p>HUUCO%@hb?40a)Bu4_V!5Bae2k!iCDu0=) z135+6T6V3eVUp(}*wRY^Vmeg6)hW>EvOOW*GDIXq<+wN%6=l7aLzM0HBsQLy^bU8o zsFv)DM4RNU#IVm%VO2Hd%J${SKX6HnCni*Y1X;u=X<`44i3P1n&wT@-bTP;2l z>Y9)cgYZ72bB}cyrl+U&B3REE?Qp){zeYnBkd%~$&!z=d$9t=Kr`LMW zSWRfH)pEVN`z<5F`cqa`EZ@uf`>{Dx$OA>^gSfX+)MVzmgfj z_xq0p4V+_neczr9zg6yR8v<4$%iz`X$T2t#Hq5q0&5P@V#4ZvmlFGx*_N|-W z*RiF%VG;ERtFYHM)wP1JUivMOtK*1|uOZ>i>hjwi!oS^Ex^UUW-NZ$wOnIBOtV2Of zwXspgF#ov)W5dkUR2b^wr&6Q&CLFdC>RM?VTMseyh?F`$32Ue32;g*ix*J+j%cBj7^6*I~gD< zD%uSfgumD&8rD#>%q4Qwg=J%qLrN*s5j-Eb@CuPH$`sY7r>rO@s|Louex>d)Td({} zCdmH`~qHF52uuv8U5SzSXV3?U18I$I8UaoN%lpm~FapAdo&+Sj? z7m}G#)>UHwnc3;JjTPx8w;)LyIXv9OAmRHlARholQ)9}{U?*RP__ul7X5#U z8$sD3C|d+&i=b=~lr4g?MNqZ~$`(P{A}Ct~Ws9I}5tJ>0vPDp~2+9^g*&--g1Z9i< z|Hu|`{(J7}f0->}`zP*cC|d+&i=b=~lr4g?MNqZ~$`(P{A}Ct~Ws9I}5tJ>0vPDp~ z2+9^g*&--g1Z9h$Y!Q?#g0e+Wwg}1=LD?cGTLfi`pllJ8E&4y6E#mt3CU5^`wut?o zOx{A-A}Ct~Ws9J3i=c6f9HDWGpmB?!af_gFi=c6fpmB?!af_gFi~jBx{-1WCaf_gF zi=c6fpmB?!af_gFi=c6fpmB@-w~kxH{qMP_|7Es_0vPDp~2+9^g*&--g1Z9h$Y!Q?# zg0e+Ww&?$OwutB7o4oy(*&@z=GIK@Qh=t|9KYPoHfGyTU72`4wVfPf>&!C2oK@!|gA;o%__NWVyUK>s%>|>2$CyQvqa%s-+9*=g9;#8I?~ZY@RJV++ROX zo!^|)&1D!CNJ+%-NX5KYOA`R33o57ZOU3X&Qbe>{Y@IDWJU%=;(%#%%S50Q>gI@=6iS#CNtzZ(>gS58qzK5y@y2#K?wqeaJo?P9%;((R{l2@q z9a$axkVMLFny)at-ldl#DHqSHlOaF7{cCo2+`AU&Qms@p7I}Vi{P2joda&YK>7tax ztD7y=G+m*YC8Crh7+L@M;U4Ms{*GjwMEr8>pDb3;ZHcGhS?* z`vG#zaxubn?rx?wC+v##G%_Js@hK(q>F3C{%{KT}f*_fTMsTSn zN<-u!&kb_fKNmVJ3$)eK1wSP7$;9xerwf_ns!VT9Km>HMWK>cG1VcE~lH@9ez!k$H zWqMi4N(oQbEfviz9m}JeBNklab#-&Of41*jVxyNUE*Zlk7|N*_C#4iG0lD!KQCy-C zoD$L8dbwg6X>vUaT~CjZYHD*zKU2pbPx51?Fr+^3gV}h4*#ts4J|y!SYUbPqfL z_A2wxOjT1$R~Ohf_<2M`_O<=3%{6s|K9ueomSlzp4ZtgB7lrK54pxrIRG zBd!BQ*k*UaEZ}7E2ajSaVk`6bg~x`fZR@I+9^_@4?PP0P3Yvc{ z`N}>oxqMIQrebTtK{r1a;Gs`HDcYE^T_GjfCtCeWNq{>_ch6#n%m{@8x9xuTnz3g* z$f@7ARP`s`1xOE^zT459KYpO6HJJ4}j&wpZS{5)bEdOn)RfXxYx-^xWndq(b~$ymV8UanqiS~UrT}VMU4e^oD@j;o~+NJ;KllS zg6=H5UgwP{7akbHx2OTLQ??m~8DDe~ooG-1?g%gQ&M zGUy1I2r(uz+DeVYVkhix`wbX59SGDb>&7vvlJv+(WQE_s7qO|oFlZq5@%*y3Jj48! zeoTOV$3R+iyWPII6V0WXvP=@5RvA<7 zcDrOXJ$nNiK{1?_=cW`$Qj|c6$U;JOyJD;mmUC!DVo=*z|8SV7o#h++fyE~<)4?zj zWN>wk`WrX2{A&Jxq1kNzBbv>^3{`EYYX1+bHv8YIwuG$>NX*#L(81geWb456wEKrG zBkW+VZ}m>Z*2;(p((jr&lCZJVsvor{Nrfs2csgoT}*nS_OznNvqV;Ll-! z;~z_4QgJc>x!D;rsX90rt3GY~QHTUHlZd{f@l&Bp|D`~28;H?lZez*>Ft-u5aWwzy zzJ$4hBS_Rt-{Gk?a{7PSW?^N8NE(669JN@vxJjNCHVzIF4i3&g7H)1H5>8Go5*8j7 z5;iur|5zZ$IJnvWLl$!E&vbvL`F~pfRUVSZQ$Bwzkn&iVSvdYE?5RL5R@Oh3r)^G% z10kS;R;c5L;{-1JxuK&pA>HJe&pVC3LpX&Iu{>!5C zhd%tL?m%<_^5B*KQx7=*Q4ju8`tLODUskfe3q|^e3jNA$I8T%1t- z`k&V?h%Q4KC@1qD>!$|x^bOqqcYjaX{5LJ+&lUkG9q>6{OF7>SdLgBv?pTo|*!r z0!$wj4J?ceL6Gy(kel|$X=xUczcfk#$mh=v`ageaL&Dq&WDM!`|IxB$;rScWIzv;| zagH0KWe;%9qQa4QvTDhsbwZARG#%2T%o&`)KY~GFFCT*{)t$3cRdAm)P9k1uEK9Z? zT5Qm9ePel!6p`o??g@99WnlO`Rv6uFjX7K-xpdg}jrK;)lBA%7ej|eFa+LV;??!yL zs)`xoL~>fvWB<*o%y}}H2bn0GIUTgeH}H7iLf zqjQILKlUgFgdn_4lmp}1tPuJFN$RDY^2M{6mR+y)V5^x3ng~Wc5`!ajddH?dt~>e0)oVMKAx&fk3wHFe!k)c9@;K zEDR`VA;pkItZsM_0B33Q!dvTk)Y!K|(Z$WPJm4J2eMInE=XO0l&+8LPmz8)@mWxWE zhP*0Agv<9@`SzSH`dGO(3%c#r=?ulLJ7(I55`DQJ>y=Ig##y;mf#sjK!1_D)o-SfC4y3 z$;%7<)K+eZh+HSyG+usHzERxZ{-!?dVNpHCci+)0ZEK5$=+@q(z&u`1j&Hup8vM2; z)~L!FrM;|zX)4ZF1?En+$t0W!`P4Ygx1&7#<+I9xP}$cOt9hfYDw{g+>R3eDYM{%| zonu?TmIwiz#c2v7VlrCLE{^u>J&xzx+A3H(C_yO~jcO6;%oCJ(f#p`)5M>jaXld+t`4*- zb1q%NGLnSJxqS;K*3m&8@_|BhbU5-DWtGJfiGYBFWB2y+xrNbG z;F8g>n%lt10+NusFcKx&Cj*n%ty8l#q40t`br@o{?auhJgveKKzJ|>^+Xf=lRNb@v zTJS{*K37=XuGM|1ZF*K!#7ULO6PT&Qpq_n^>M~GaA&xR-sDnCjlo?+?nnte3=g$2# z8wn{Xqj{|-H=I}n1Ay<;l z7|il~sY`k?FDxt*#sh&bc0XM58zDaDXKz*#KdP!;y=)!4bfj0UvUtXd$$Ijol2VKp zLasWTpdY>l<*;XZ@Lq8Vvqtv2?M1)L)eqiQxZgg1Rn$!|7vZiQw^{#I;*{lbiuBb0 zT2`35)#K>4EBa^tp!QG{KM%v9cw65`pw=7U7vX2)?T1|(;&FCXjRvE{4{Y2oI*PSr zx?L2ERP^7xvAH0FzbT2^u(0FPFN!~1FD6IX!#lM%ViNjRJZM-q7}nMzzU=iDM)`o| zj!?J64vYdv1UH=hdD|;gwku{&OH5^>yP&(vK?R*WhROJqeI0N*Du|K*Ag%&XPMgOR@rsBf- z6yjFTQ6;PxkH6T(9f2-%6;qR?egY`r$=brTkDF=lt=nZAFiwwx1qsFeTt; zfhW)G)S?wc2kWY`ob+tYhfW;PcU*;DR@Pizijo?jC1$hE@fyw@1k8LCPJmrEiv*i$ zBzO}wLjan8iPxp3jOQ*h60lj&x z&|sO63atIOQlWxs!RYAT__y$lT|blzcJqyYS{jgFYU9qeyHf{$oWK4HT>|-Kro(yu z6J2t5fcR6F@B#n7Z2F5X`QyERQvBa_2?Pw~|BL09p{M*ym$dBYEJ5`7wCFJJwH3J& z)-ntCdy45P!fZcmp!Uaf6TqyWut%NI@7epV@IMSi=&_(QIEQpDo!H&GrkeM^rNKzp zl%R7Ha$>_VTMW$=V)(E-kEPOMd;EPgD{oUM#M5Ny#|U8~+Whs$>*5uswMHiVj_cdY zeU$d)wl!-X3O7GK(={8J3}A$qR0ej>M`N+V=GuE@LC0EnI(Z*3D0E2(7Ms|Z2N}ap zLYfAb#n8_>Mx2CSM-xg6d+4%+Ssj9mKQKJd%00{?yQNhoE2qw#joCQ6$fQ@20eF^- zH(puXqojRBXXoD*%EVeFWpAPWD6OKR6?8_uyW++10)!wmQ77kXVyep5vtL8ISnY+7 zCUP!n!Po~Rd>etLHE|X&c3F5$8KT+5$m~MeXn$K6=kc~v}D!`I-iLjiSX0m*5SjIA#+BUZPZIKi&ivS@iDbkwg>do{CkylGZBSUqr z{PX}hH5^>I!ay(wPFGUYC*z+FrEe1s&xe#KtMw_QuT(V(a5+%WTQ4n9&>g0qJ)ig_ zvc$Xp<|~P9vIG%wQhcuBa0xMEGMGN7^;T?}7knET*U_iwtQ)t^4YSvz8F6Rqb>?!n zkp+Mqy2F>oaSZX~B-|IvSn&6?;_1J{k2thaO3x9RB`^SMd$n^)t;niH42l)<*Bp|$ zEF{OhpLf~5>BFu9oy<);Yyn2mQWr8QD06){c3u>5ytuM7(j{az#^ktU?x!D**P8ud zIE7;%F*(gsc+fgWdAq<*XNxL!gN*4w4_yVUv5?*>9?C;f=r^4zr9a9=y?7dC1A@Rx z)C_tt1zB>@g7|JQbEzhXmAl_VieUTh-&_p{ISdrt8EfTarY#x+WX_(+lDZdL=JIaH zC%?`xgk}oM8}y0RFNVu(B${cvkVDB5&%MgryFb-W7(J$KtK!azz*~9cXhzO%SIyoT zdW#)_8jL}=^CMO8V_?@$7V#_y>yJDK-e|na7hg5yw6mO9CnM1oE>UqAZNy@ItSJnJ z=KY(E>{8}w+M(MsfJHP1MpC^-ZXzwG_S5~AXbje3#z7AW#Pqd)hE zE+AsUAyom%#l-(?Jftk1BH**QaT?G~M2e^w5^?mt3fBlvTyv8zfu86Ic~OtO>D}Hd zif{m6-NrxBU=-RNMlgK3iRe-b+Z0OuJ{gna@p||3sC)^%(1^DzE!s#rMe>*MX*Qx$ z5bqnNan*=7Mp)@C4U}`~0X8xev%yHJoKB>O+he3K6O4yq}h1ahQNt5({PUn7kqYX5~041IUYRfl~?v(l@zV zhZSJgA{w9W>{?*w1WUj31;W6F&dvT<9(5(C^HyM3pc>yjsL!7ibb`ftQC7g3`(DN; z-iQsUpoJq*8sCv(V^>EM8L98E+v2)29t<}I$DODYn!k%1=TzUrsYR^A)=s}a>7~`O zHcXNrt*r z`O}iZy)VgE`szvjg0NPUHdki+%^_{Adn+l2 z&7fWiajJD$IF_)kXWZoT3FnzxkykTB=NjCu4XAL>7&m|ZqE7?(-yz?bIt3RaC>IL2 zhkry|vFK38vT_vTsUho9M56V%(rwo2F`MlQ&H^&!i-PrzhJ-BW+yMeA-0`TxMj`SK zmHSMKk&;yevn^vpPwQ;+ewGJ~j7H{oyk}TR^T!C7-%qo5Y-}GAJVN+Xw{z1h*WsWS z%)c9IapyZFB=$Ozs`y%<_`w4OVV*rITf(gnN3ghOuH=g1`>3VHZ@<0p&9<=2unnQt z`>7#^o<7W~e$Dir@|f+^Ovy0B@l+ERYk1y5ON*AOdECItXi65i~JbAOac$Ij`@Glh(~ z__!yQ$qMOU*w=7oZe=BBx<^>9RU1T~*LSc<2nAe}avqEI<#RudNYviL>R1R5meG{{ z9vJc^&v1B;dye?PTF?u9BcVZtl!>;4;6C{IYw*NL=^nKh<~CRP04cT9n{B|gh`n-% zzYYT_5hb+zW~9FMB?D`++DPKhEqB`s>}Y~%i~f51h#sU8of{6Ja9-SuHknl2IkdU& zlf*5C+q&`f855V=nn#W62*?^(j+>~rj&C0LHY?+p$IuORIVfI<#f93){M%1kQ%Rb7 z%$19efmYwhpHhh)4N&U7!-5IrwZCq4@#K_A>R$;UQLvB^G)iUXP%9R9AV15pNA2*7 zT?R5rn(2h)6d7MNp_U{>ea#H?6PWPvi87^Qo~qc?hpk32b3Yq?z(^-BZ$Kz{1PRoe4cWz`XRp4O zpSs*qtS0e_q1E!x05$vx$2> zSngK$m?GQ+T@%+~p4@r(FrK zx#$im$zxtc&sBlx>k_xUOxoZ)vYFq*s%^Q%b+Q}>K9Ga5ls20TE&21wFH0%Sluxxu zUPd5Al|Gyz-Xd2w6DVJU%Gqw#4?C;_w7x+eg?< zA2ijEV_O<7^(9t{cUfE0`ccf@tw)J=#kfTEd*sU7cN7^iVlFfqxsPt*(s-wA52SGj z2?fvYBX%M%n{(b1^p=|rSk&Ck^bt}47qor=lB1AeP7tp8>BOS6AeBi=5nXSlIaxCD z9LAZ$$L`tv`FX-6P17VEYRuQ94jvbb?{#6ZouJEPCsK)?b@R!p>}X0NDFkEVta7mH z%&qj)cb0sT*7y?V0P|)fN zouwfWrMmRJQ^%*fYi71`@$Q$y0gCS?)xNYFCSP;XulD=q9^bcr1TWOCc{daT7S^O8 zu>-F9G9xypHV|Xw{JT=YJ5ZH4{mgt%WdpOw{k8tmCN!KzeVU?sxu*-9FUXm}mZL+y zY^)wu#}pF_>zhrNZ^;c-#43U$vgn}SUeFD+DWw;!nsh|WkB(C>jP9j6FD}7ZTXxbT z&Qk(BDr8zz-F9@Qt@|W+KOYXMB#{orPBw1&GQU(h(u{5XwnL)IbaOdpo5(^VzDgFz zk|C+tUTzxrVzs-hOOss@fq%G#FTT%C;@LhnzeFAd!Oi}fAJ^F{eA3>~P&{*sC!sQb z^>6CSZ!O#ZroQ}n{Qghs3l#WgI|6psN&V#|!0+O2z&~nodM@LA)@9ZjtvSOVcf<1sHD=8mUZRkq&S0@uV7DP$e7IU z@#kIVT@TRyKn<9?E;BbcuA@!0_W2U(Z6Y;2{%=KM1AJ zW)MOHeT+rDuNs?_UO!w!Ztp#Bk1Sg7e*w!(U62a&qQkgc>v~X?cn%Y(>9whgmFu)A zTF&a(FQQh)89qeI*()2nQu}%uj6CGV3NJaps*MYfxgcmSlcx3}gg^=cyOUyCADzv|ND(@}35`LBmJN@zT}mPk2578i{1es@q0Kdn}G z98j=?e#Zy@zMK3%<{<#(75IG~0#NWj)i literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_worksheet.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/elmhurst_worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d419fcf3ea747de68da58407dd40bf731171cfd3 GIT binary patch literal 46083 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 literal 0 HcmV?d00001 diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/epc.json b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/epc.json new file mode 100644 index 000000000..161c1649d --- /dev/null +++ b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_100031768368/epc.json @@ -0,0 +1,452 @@ +{ + "uprn": 100031768368, + "roofs": [ + { + "description": "Pitched, 100 mm loft insulation", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "walls": [ + { + "description": "Solid brick, as built, no insulation (assumed)", + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + } + ], + "floors": [ + { + "description": "Solid, no insulation (assumed)", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 1, + "window": { + "description": "Fully double glazed", + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + }, + "addendum": { + "addendum_numbers": [ + 15 + ] + }, + "lighting": { + "description": "Below average lighting efficiency", + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "postcode": "ST16 3TS", + "hot_water": { + "description": "From main system", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "STAFFORD", + "psv_count": 0, + "built_form": 3, + "created_at": "2026-05-14 07:52:27", + "door_count": 1, + "region_code": 6, + "report_type": 2, + "sap_heating": { + "number_baths": 1, + "cylinder_size": 1, + "shower_outlets": [ + { + "shower_wwhrs": 1, + "shower_outlet_type": 2 + } + ], + "water_heating_code": 901, + "water_heating_fuel": 26, + "secondary_fuel_type": 26, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "Y", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 18250 + } + ], + "immersion_heating_type": "NA", + "secondary_heating_type": 605, + "has_fixed_air_conditioning": "false" + }, + "sap_version": 10.2, + "sap_windows": [ + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 3, + "window_type": 1, + "glazing_type": 3, + "window_width": 1.1, + "window_height": 1.02, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 3, + "window_type": 1, + "glazing_type": 3, + "window_width": 1.64, + "window_height": 1.01, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 7, + "window_type": 1, + "glazing_type": 3, + "window_width": 0.96, + "window_height": 0.96, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 7, + "window_type": 1, + "glazing_type": 3, + "window_width": 0.99, + "window_height": 0.96, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 7, + "window_type": 1, + "glazing_type": 3, + "window_width": 1.48, + "window_height": 0.97, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 3, + "window_type": 1, + "glazing_type": 3, + "window_width": 0.55, + "window_height": 1.19, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 3, + "window_type": 1, + "glazing_type": 3, + "window_width": 0.55, + "window_height": 1.19, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 3, + "window_type": 1, + "glazing_type": 3, + "window_width": 1.1, + "window_height": 1.3, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 7, + "window_type": 1, + "glazing_type": 2, + "window_width": 1.36, + "window_height": 2.08, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + }, + { + "pvc_frame": "true", + "glazing_gap": 6, + "orientation": 7, + "window_type": 1, + "glazing_type": 3, + "window_width": 0.9, + "window_height": 2.03, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 1, + "permanent_shutters_present": "N", + "permanent_shutters_insulated": "N" + } + ], + "schema_type": "RdSAP-Schema-21.0.1", + "uprn_source": "Energy Assessor", + "country_code": "ENG", + "main_heating": [ + { + "description": "Boiler and radiators, mains gas", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "air_tightness": { + "description": "(not tested)", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "dwelling_type": "End-terrace house", + "language_code": 1, + "pressure_test": 4, + "property_type": 0, + "address_line_1": "21 Greenway", + "assessment_type": "RdSAP", + "completion_date": "2026-05-14", + "inspection_date": "2026-05-13", + "extensions_count": 0, + "measurement_type": 1, + "open_flues_count": 0, + "total_floor_area": 76, + "transaction_type": 1, + "conservatory_type": 2, + "has_draught_lobby": "false", + "heated_room_count": 4, + "other_flues_count": 1, + "registration_date": "2026-05-14", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "pv_diverter": "false", + "pv_connection": 0, + "pv_battery_count": 0, + "photovoltaic_supply": { + "none_or_no_details": { + "percent_roof_area": 0 + } + }, + "wind_turbines_count": 0, + "gas_smart_meter_present": "true", + "is_dwelling_export_capable": "false", + "wind_turbines_terrain_type": 2, + "electricity_smart_meter_present": "true", + "is_hydro_output_connected_to_dwelling_meter": "false" + }, + "secondary_heating": { + "description": "Room heaters, mains gas", + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "closed_flues_count": 0, + "extract_fans_count": 1, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 280, + "floor_heat_loss": 7, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": { + "value": 2.45, + "quantity": "metres" + }, + "floor_insulation": 1, + "total_floor_area": { + "value": 37.75, + "quantity": "square metres" + }, + "party_wall_length": { + "value": 5, + "quantity": "metres" + }, + "floor_construction": 1, + "heat_loss_perimeter": { + "value": 20.1, + "quantity": "metres" + } + }, + { + "floor": 1, + "room_height": { + "value": 2.45, + "quantity": "metres" + }, + "total_floor_area": { + "value": 37.75, + "quantity": "square metres" + }, + "party_wall_length": { + "value": 5, + "quantity": "metres" + }, + "heat_loss_perimeter": { + "value": 20.1, + "quantity": "metres" + } + } + ], + "wall_insulation_type": 4, + "construction_age_band": "C", + "party_wall_construction": 1, + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "100mm", + "wall_insulation_thickness": "NI" + } + ], + "boilers_flues_count": 0, + "open_chimneys_count": 0, + "solar_water_heating": "N", + "habitable_room_count": 4, + "heating_cost_current": { + "value": 1188, + "currency": "GBP" + }, + "insulated_door_count": 0, + "co2_emissions_current": 3.4, + "energy_rating_average": 60, + "energy_rating_current": 61, + "lighting_cost_current": { + "value": 55, + "currency": "GBP" + }, + "main_heating_controls": [ + { + "description": "Programmer, room thermostat and TRVs", + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "blocked_chimneys_count": 0, + "has_hot_water_cylinder": "false", + "heating_cost_potential": { + "value": 739, + "currency": "GBP" + }, + "hot_water_cost_current": { + "value": 267, + "currency": "GBP" + }, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 371, + "indicative_cost": "\u00a37,500 - \u00a311,000", + "improvement_type": "Q", + "improvement_details": { + "improvement_number": 7 + }, + "improvement_category": 5, + "energy_performance_rating": 70, + "environmental_impact_rating": 73 + }, + { + "sequence": 2, + "typical_saving": 77, + "indicative_cost": "\u00a35,000 - \u00a310,000", + "improvement_type": "W2", + "improvement_details": { + "improvement_number": 58 + }, + "improvement_category": 5, + "energy_performance_rating": 71, + "environmental_impact_rating": 75 + }, + { + "sequence": 3, + "typical_saving": 222, + "indicative_cost": "\u00a38,000 - \u00a310,000", + "improvement_type": "U", + "improvement_details": { + "improvement_number": 34 + }, + "improvement_category": 5, + "energy_performance_rating": 76, + "environmental_impact_rating": 76 + } + ], + "co2_emissions_potential": 2, + "energy_rating_potential": 76, + "lighting_cost_potential": { + "value": 55, + "currency": "GBP" + }, + "schema_version_original": "LIG-21.0.1", + "flueless_gas_fires_count": 0, + "hot_water_cost_potential": { + "value": 267, + "currency": "GBP" + }, + "renewable_heat_incentive": { + "water_heating": 1847.12, + "space_heating_existing_dwelling": 11154.61 + }, + "draughtproofed_door_count": 1, + "energy_consumption_current": 252, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "4.3.74", + "energy_consumption_potential": 145, + "environmental_impact_current": 61, + "cfl_fixed_lighting_bulbs_count": 2, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 76, + "led_fixed_lighting_bulbs_count": 10, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 45, + "incandescent_fixed_lighting_bulbs_count": 1 +} \ No newline at end of file diff --git a/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_217091901/elmhurst_summary.pdf b/backend/epc_api/json_samples/real_life_examples/RdSAP-Schema-21.0.1/uprn_217091901/elmhurst_summary.pdf index bf5e1e38263ee164b96224bf04b600cede8742eb..db84a0784e3cb4da8814de761b4744185194813e 100644 GIT binary patch delta 7597 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 delta 7664 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 delta 40761 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 Lv6z@-RAv7Mb3MT$ diff --git a/domain/sap10_ml/rdsap_uvalues.py b/domain/sap10_ml/rdsap_uvalues.py index be1ab2f62..41b366cd0 100644 --- a/domain/sap10_ml/rdsap_uvalues.py +++ b/domain/sap10_ml/rdsap_uvalues.py @@ -260,10 +260,21 @@ def _u_brick_thin_wall_age_a_to_e(wall_thickness_mm: int) -> float: 200 to 280 mm 1.7 280 to 420 mm 1.4 More than 420 mm 1.1 + + The 200/280 boundary is shared verbatim by adjacent rows in the spec + PDF (Table 13, p.41), which doesn't say which row owns the shared + edge. Elmhurst-validated on cert 100031768368 (280mm exactly, no + insulation, band C, Dry-lining No): its worksheet (29a) computes the + wall at U=1.40 — the upper row. A second cert (217091901, band A, + also nominally 280mm) initially looked like a counterexample at + U=1.70, but its Elmhurst build never actually set a wall thickness + (silently inherited a stale 260mm from an earlier build on the + shared assessment) — not a real second data point. Only the 280 + edge is evidenced here; 200 and 420 are left as originally coded. """ if wall_thickness_mm <= 200: return 2.5 - if wall_thickness_mm <= 280: + if wall_thickness_mm < 280: return 1.7 if wall_thickness_mm <= 420: return 1.4 diff --git a/domain/sap10_ml/tests/test_rdsap_uvalues.py b/domain/sap10_ml/tests/test_rdsap_uvalues.py index 95993be78..f09a453ed 100644 --- a/domain/sap10_ml/tests/test_rdsap_uvalues.py +++ b/domain/sap10_ml/tests/test_rdsap_uvalues.py @@ -32,6 +32,7 @@ from domain.sap10_ml.rdsap_uvalues import ( WALL_SYSTEM_BUILT, WALL_TIMBER_FRAME, _insulation_bucket, + _u_brick_thin_wall_age_a_to_e, # pyright: ignore[reportPrivateUsage] thermal_bridging_y, u_door, u_exposed_floor, @@ -140,6 +141,29 @@ def test_u_wall_solid_brick_present_insulation_thin_known_thickness_not_uninsula assert result == pytest.approx(0.55, abs=0.001) +def test_u_brick_thin_wall_age_a_to_e_280mm_boundary_takes_upper_bucket() -> None: + # Arrange — RdSAP 10 §5.7 Table 13 (PDF p.41) gives four thickness bands + # for an uninsulated solid-brick wall: "up to 200mm" -> 2.5, "200 to + # 280mm" -> 1.7, "280 to 420mm" -> 1.4, "more than 420mm" -> 1.1. The + # 200/280 bands share 280 as an edge verbatim in the spec table, so the + # table text alone doesn't say which band owns it. Elmhurst-validated + # on cert 100031768368 (solid brick, band C, wall_thickness_mm=280 + # exactly, no insulation, Dry-lining No): its worksheet (29a) computes + # the wall at U=1.40 — the UPPER band — not 1.70. A second cert + # (217091901, band A, cert wall_thickness=280 too) initially looked + # like Elmhurst evidence for the opposite (U=1.70), but its build + # script never actually set a wall thickness field — the assessment + # had silently carried over a stale 260mm from an earlier build, so + # it isn't a real counter-example (fixed in build_217091901.py). + # + # Only the 280 edge is evidenced here — the 200 and 420 edges are left + # as originally coded pending their own Elmhurst confirmation. + + # Act / Assert + assert _u_brick_thin_wall_age_a_to_e(279) == pytest.approx(1.7) + assert _u_brick_thin_wall_age_a_to_e(280) == pytest.approx(1.4) + + def test_insulation_bucket_present_thin_thickness_rounds_up_to_50_not_zero() -> None: # Direct guard on the bucket: present insulation never maps to bucket 0. assert _insulation_bucket(10, True) == 50 diff --git a/scripts/corpus_1000/worklist.md b/scripts/corpus_1000/worklist.md index 66fc7797b..e1ed89ced 100644 --- a/scripts/corpus_1000/worklist.md +++ b/scripts/corpus_1000/worklist.md @@ -1,10 +1,10 @@ # RdSAP-21.0.1 corpus campaign ledger -**Gauge:** 1000 computed / 0 skipped · SAP within-0.5 = **77.8%** · MAE = **0.636** (floors in test_sap_accuracy_corpus.py) +**Gauge:** 1000 computed / 0 skipped · SAP within-0.5 = **78.6%** · MAE = **0.627** (floors in test_sap_accuracy_corpus.py) Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail engine bug · ⛔ unbuildable. Line: `uprn · lodged L / eng E / Δ · signature`. Rebuild/re-rank: `PYTHONPATH=. python scripts/corpus_1000/build_worklist.py` (preserves statuses & `<-` notes). -## Clusters (certs with |Δ| ≥ 0.5, ranked by summed |Δ|) — 222 certs in 216 clusters +## Clusters (certs with |Δ| ≥ 0.5, ranked by summed |Δ|) — 214 certs in 208 clusters ### C001 · Σ|Δ| 23.9 · 1 certs · `hpcdb100053/elec | solid brick | (another dwelling | sec:room heaters` - ⚠ `4510053280` · L 51 / E 74.94 / Δ +23.94 <- lodged software billed out-of-range-PSR (2.031 > 2.0) PCDB 100053 as 100% direct electric; engine follows SAP10.2 N2 extension (305%), Elmhurst-validated on case 56. Pinned engine 75 in test_real_cert_sap_accuracy. @@ -114,553 +114,529 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en ### C035 · Σ|Δ| 3.1 · 1 certs · `h633/heatnet | timber frame | roof room(s) | RR,ext1,sec:room heaters,whw909` - [ ] `22077224` · L 37 / E 33.90 / Δ -3.10 -### C036 · Σ|Δ| 3.0 · 1 certs · `hpcdb18498/gas | solid brick | pitched 250 | RR,ext2` -- [ ] `10011740578` · L 70 / E 67.00 / Δ -3.00 - -### C037 · Σ|Δ| 2.9 · 1 certs · `h402/elec | cavity wall | pitched 250 | sec:portable electric,whw903` +### C036 · Σ|Δ| 2.9 · 1 certs · `h402/elec | cavity wall | pitched 250 | sec:portable electric,whw903` - [ ] `100110680822` · L 79 / E 76.07 / Δ -2.93 -### C038 · Σ|Δ| 2.9 · 1 certs · `h301/coal-anth | solid brick | pitched 250` +### C037 · Σ|Δ| 2.9 · 1 certs · `h301/coal-anth | solid brick | pitched 250` - [ ] `452032915` · L 75 / E 72.09 / Δ -2.91 -### C039 · Σ|Δ| 2.9 · 1 certs · `h301/coal-anth | cavity wall | (another dwelling | whw909` +### C038 · Σ|Δ| 2.9 · 1 certs · `h301/coal-anth | cavity wall | (another dwelling | whw909` - [ ] `100071306639` · L 73 / E 70.14 / Δ -2.86 -### C040 · Σ|Δ| 2.9 · 1 certs · `hpcdb18435/gas+2mains | cavity wall | pitched 100 | ext3,sec:room heaters,whw903` +### C039 · Σ|Δ| 2.9 · 1 certs · `hpcdb18435/gas+2mains | cavity wall | pitched 100 | ext3,sec:room heaters,whw903` - [ ] `100061346049` · L 55 / E 52.15 / Δ -2.85 -### C041 · Σ|Δ| 2.7 · 1 certs · `hpcdb8443/fuel28 | cavity wall | pitched 100 | RR,ext2,sec:room heaters` +### C040 · Σ|Δ| 2.7 · 1 certs · `hpcdb8443/fuel28 | cavity wall | pitched 100 | RR,ext2,sec:room heaters` - [ ] `10013008238` · L 41 / E 38.26 / Δ -2.74 -### C042 · Σ|Δ| 2.7 · 3 certs · `h691/elec | cavity wall | (another dwelling | whw903` +### C041 · Σ|Δ| 2.7 · 3 certs · `h691/elec | cavity wall | (another dwelling | whw903` - [ ] `10033187652` · L 70 / E 71.21 / Δ +1.21 <- non-electric cost (secondary fuel/cylinder/tariff); spec-faithful vs lodged - [ ] `200002730` · L 65 / E 64.13 / Δ -0.87 - [ ] `10023328117` · L 78 / E 78.64 / Δ +0.64 -### C043 · Σ|Δ| 2.7 · 1 certs · `h633/heatnet | cavity wall | pitched 200 | RR,sec:room heaters,whw903` +### C042 · Σ|Δ| 2.7 · 1 certs · `h633/heatnet | cavity wall | pitched 200 | RR,sec:room heaters,whw903` - [ ] `10004873253` · L 65 / E 62.29 / Δ -2.71 -### C044 · Σ|Δ| 2.7 · 1 certs · `h691/elec | cavity wall | (another dwelling | mv1,whw903` +### C043 · Σ|Δ| 2.7 · 1 certs · `h691/elec | cavity wall | (another dwelling | mv1,whw903` - [ ] `10090793715` · L 73 / E 75.71 / Δ +2.71 -### C045 · Σ|Δ| 2.7 · 1 certs · `hpcdb17964/gas+2mains | cavity wall | pitched 100 | ext1,sec:room heaters` +### C044 · Σ|Δ| 2.7 · 1 certs · `hpcdb17964/gas+2mains | cavity wall | pitched 100 | ext1,sec:room heaters` - [ ] `100120745600` · L 65 / E 67.69 / Δ +2.69 -### C046 · Σ|Δ| 2.7 · 1 certs · `hpcdb17757/gas | granite or | pitched no | RR,ext1` +### C045 · Σ|Δ| 2.7 · 1 certs · `hpcdb17757/gas | granite or | pitched no | RR,ext1` - [ ] `100051178830` · L 63 / E 65.66 / Δ +2.66 -### C047 · Σ|Δ| 2.6 · 1 certs · `hpcdb10321/gas | sandstone as | pitched 100 | ext2` +### C046 · Σ|Δ| 2.6 · 1 certs · `hpcdb10321/gas | sandstone as | pitched 100 | ext2` - [ ] `100120018282` · L 64 / E 61.36 / Δ -2.64 -### C048 · Σ|Δ| 2.6 · 1 certs · `hpcdb17968/gas | cavity wall | pitched 300 | ext2,sec:room heaters` +### C047 · Σ|Δ| 2.6 · 1 certs · `hpcdb17968/gas | cavity wall | pitched 300 | ext2,sec:room heaters` - [ ] `83035979` · L 65 / E 67.60 / Δ +2.60 -### C049 · Σ|Δ| 2.5 · 1 certs · `h524/elec | solid brick | (another dwelling | consv2,sec:room heaters,whw909` +### C048 · Σ|Δ| 2.5 · 1 certs · `h524/elec | solid brick | (another dwelling | consv2,sec:room heaters,whw909` - [ ] `10014073761` · L 75 / E 72.52 / Δ -2.48 -### C050 · Σ|Δ| 2.5 · 1 certs · `hpcdb10328/gas | solid brick | pitched insulated | RR` +### C049 · Σ|Δ| 2.5 · 1 certs · `hpcdb10328/gas | solid brick | pitched insulated | RR` - [ ] `100050964834` · L 60 / E 62.47 / Δ +2.47 -### C051 · Σ|Δ| 2.4 · 1 certs · `h694/elec | cavity wall | (another dwelling | whw941` +### C050 · Σ|Δ| 2.4 · 1 certs · `h694/elec | cavity wall | (another dwelling | whw941` - [ ] `100120752995` · L 71 / E 73.42 / Δ +2.42 -### C052 · Σ|Δ| 2.4 · 1 certs · `h691/elec | solid brick | roof room(s) | RR,ext2,sec:room heaters,whw903` +### C051 · Σ|Δ| 2.4 · 1 certs · `h691/elec | solid brick | roof room(s) | RR,ext2,sec:room heaters,whw903` - [ ] `100062174575` · L 46 / E 48.40 / Δ +2.40 -### C053 · Σ|Δ| 2.3 · 1 certs · `hpcdb15753/gas | solid brick | pitched 150 | RR` +### C052 · Σ|Δ| 2.3 · 1 certs · `hpcdb15753/gas | solid brick | pitched 150 | RR` - [ ] `100000034023` · L 73 / E 70.71 / Δ -2.29 -### C054 · Σ|Δ| 2.2 · 1 certs · `h506/gas | cavity wall | flat limited | sec:room heaters` +### C053 · Σ|Δ| 2.2 · 1 certs · `h506/gas | cavity wall | flat limited | sec:room heaters` - [ ] `100020377671` · L 62 / E 64.23 / Δ +2.23 -### C055 · Σ|Δ| 2.2 · 1 certs · `h104/gas | solid brick | pitched insulated | RR,ext1` +### C054 · Σ|Δ| 2.2 · 1 certs · `h104/gas | solid brick | pitched insulated | RR,ext1` - [ ] `22027335` · L 76 / E 73.77 / Δ -2.23 -### C056 · Σ|Δ| 2.1 · 1 certs · `hpcdb18833/gas | solid brick | pitched insulated | RR,ext1,sec:room heaters` +### C055 · Σ|Δ| 2.1 · 1 certs · `hpcdb18833/gas | solid brick | pitched insulated | RR,ext1,sec:room heaters` - [ ] `48056153` · L 65 / E 62.89 / Δ -2.11 -### C057 · Σ|Δ| 2.0 · 1 certs · `hpcdb19008/gas | solid brick | pitched insulated | sec:room heaters` +### C056 · Σ|Δ| 2.0 · 1 certs · `hpcdb19008/gas | solid brick | pitched insulated | sec:room heaters` - [ ] `100021924710` · L 63 / E 65.00 / Δ +2.00 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C058 · Σ|Δ| 1.9 · 1 certs · `hpcdb18774/gas | solid brick | pitched 100 | RR,ext1` +### C057 · Σ|Δ| 1.9 · 1 certs · `hpcdb18774/gas | solid brick | pitched 100 | RR,ext1` - [ ] `100031270375` · L 69 / E 70.93 / Δ +1.93 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C059 · Σ|Δ| 1.9 · 1 certs · `hpcdb106417/elec | cavity wall | pitched 250 | RR,ext2,sec:room heaters` +### C058 · Σ|Δ| 1.9 · 1 certs · `hpcdb106417/elec | cavity wall | pitched 250 | RR,ext2,sec:room heaters` - [ ] `10001118522` · L 80 / E 78.07 / Δ -1.93 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C060 · Σ|Δ| 1.9 · 1 certs · `h192/elec | cavity wall | (another dwelling` +### C059 · Σ|Δ| 1.9 · 1 certs · `h192/elec | cavity wall | (another dwelling` - [ ] `10010234711` · L 81 / E 79.08 / Δ -1.92 <- electric other (per-cert cost noise) -### C061 · Σ|Δ| 1.9 · 1 certs · `hpcdb18250/gas | solid brick | pitched 100 | consv2,sec:room heaters` -- [ ] `100031768368` · L 61 / E 59.12 / Δ -1.88 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) - -### C062 · Σ|Δ| 1.8 · 1 certs · `h301/elec10hr | cavity wall | (another dwelling` +### C060 · Σ|Δ| 1.8 · 1 certs · `h301/elec10hr | cavity wall | (another dwelling` - [ ] `4510729926` · L 73 / E 71.15 / Δ -1.85 <- electric + fabric/demand mismatch (per-cert; not a coherent cluster) -### C063 · Σ|Δ| 1.8 · 1 certs · `hpcdb17138/fuel28 | granite or | pitched 200 | ext1,sec:room heaters` +### C061 · Σ|Δ| 1.8 · 1 certs · `hpcdb17138/fuel28 | granite or | pitched 200 | ext1,sec:room heaters` - [ ] `100091329112` · L 49 / E 50.82 / Δ +1.82 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C064 · Σ|Δ| 1.8 · 1 certs · `h691/elec | timber frame | pitched insulated | RR,ext1,sec:portable electric,whw908` +### C062 · Σ|Δ| 1.8 · 1 certs · `h691/elec | timber frame | pitched insulated | RR,ext1,sec:portable electric,whw908` - [ ] `10011797347` · L 47 / E 48.77 / Δ +1.77 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C065 · Σ|Δ| 1.8 · 1 certs · `hpcdb17570/gas+2mains | solid brick | (another dwelling | RR,ext3,sec:room heaters` -- [ ] `100022668387` · L 68 / E 66.25 / Δ -1.75 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) - -### C066 · Σ|Δ| 1.7 · 1 certs · `hpcdb10354/gas+2mains | sandstone as | pitched 300 | ext1` +### C063 · Σ|Δ| 1.7 · 1 certs · `hpcdb10354/gas+2mains | sandstone as | pitched 300 | ext1` - [ ] `100100892216` · L 69 / E 67.28 / Δ -1.72 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C067 · Σ|Δ| 1.7 · 1 certs · `hpcdb8119/gas | cavity wall | pitched insulated | RR,ext1` +### C064 · Σ|Δ| 1.7 · 1 certs · `hpcdb8119/gas | cavity wall | pitched insulated | RR,ext1` - [ ] `100060878876` · L 49 / E 50.71 / Δ +1.71 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C068 · Σ|Δ| 1.7 · 1 certs · `h402/elec | solid brick | (another dwelling | sec:portable electric,whw903` +### C065 · Σ|Δ| 1.7 · 1 certs · `h402/elec | solid brick | (another dwelling | sec:portable electric,whw903` - [ ] `12126160` · L 69 / E 70.71 / Δ +1.71 <- electric + fabric/demand mismatch (per-cert; not a coherent cluster) -### C069 · Σ|Δ| 1.7 · 1 certs · `h401/elec | solid brick | (another dwelling | sec:portable electric,whw950` +### C066 · Σ|Δ| 1.7 · 1 certs · `h401/elec | solid brick | (another dwelling | sec:portable electric,whw950` - [ ] `22032926` · L 61 / E 62.71 / Δ +1.71 <- electric + fabric/demand mismatch (per-cert; not a coherent cluster) -### C070 · Σ|Δ| 1.7 · 1 certs · `hpcdb18560/gas | cavity wall | pitched insulated | RR,ext1` +### C067 · Σ|Δ| 1.7 · 1 certs · `hpcdb18560/gas | cavity wall | pitched insulated | RR,ext1` - [ ] `100010942905` · L 72 / E 73.70 / Δ +1.70 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C071 · Σ|Δ| 1.6 · 1 certs · `hpcdb18119/gas | solid brick | pitched 100 | ext1` -- [ ] `100030307976` · L 71 / E 69.37 / Δ -1.63 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) +### C068 · Σ|Δ| 1.7 · 1 certs · `hpcdb18498/gas | solid brick | pitched 250 | RR,ext2` +- [ ] `10011740578` · L 70 / E 68.30 / Δ -1.70 -### C072 · Σ|Δ| 1.6 · 1 certs · `hpcdb18909/gas | cavity wall | pitched 100 | whw903` +### C069 · Σ|Δ| 1.6 · 1 certs · `hpcdb18909/gas | cavity wall | pitched 100 | whw903` - [ ] `100061404488` · L 70 / E 68.38 / Δ -1.62 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C073 · Σ|Δ| 1.6 · 1 certs · `h402/elec | system built | (another dwelling | sec:room heaters,whw903` +### C070 · Σ|Δ| 1.6 · 1 certs · `h402/elec | system built | (another dwelling | sec:room heaters,whw903` - [ ] `21067296` · L 67 / E 68.60 / Δ +1.60 <- electric + fabric/demand mismatch (per-cert; not a coherent cluster) -### C074 · Σ|Δ| 1.6 · 1 certs · `hpcdb10164/gas | solid brick | pitched insulated | sec:room heaters` +### C071 · Σ|Δ| 1.6 · 1 certs · `hpcdb10164/gas | solid brick | pitched insulated | sec:room heaters` - [ ] `5870047440` · L 58 / E 59.58 / Δ +1.58 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C075 · Σ|Δ| 1.6 · 1 certs · `hpcdb8051/gas | cavity wall | (same dwelling | ext2` +### C072 · Σ|Δ| 1.6 · 1 certs · `hpcdb8051/gas | cavity wall | (same dwelling | ext2` - [ ] `100070450818` · L 65 / E 63.43 / Δ -1.57 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C076 · Σ|Δ| 1.6 · 1 certs · `hpcdb17741/gas | cavity wall | pitched 225` +### C073 · Σ|Δ| 1.6 · 1 certs · `hpcdb17741/gas | cavity wall | pitched 225` - [ ] `100050881708` · L 70 / E 71.55 / Δ +1.55 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C077 · Σ|Δ| 1.5 · 1 certs · `h301/elec | system built | (another dwelling | whw950` +### C074 · Σ|Δ| 1.5 · 1 certs · `h301/elec | system built | (another dwelling | whw950` - [ ] `10010254955` · L 79 / E 77.46 / Δ -1.54 <- electric other (per-cert cost noise) -### C078 · Σ|Δ| 1.5 · 1 certs · `hpcdb16839/gas | cavity wall | pitched 400+ | ext1,mv2,sec:room heaters` +### C075 · Σ|Δ| 1.5 · 1 certs · `hpcdb16839/gas | cavity wall | pitched 400+ | ext1,mv2,sec:room heaters` - [ ] `3455011834` · L 69 / E 67.48 / Δ -1.52 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C079 · Σ|Δ| 1.5 · 1 certs · `hpcdb15502/gas | granite or | pitched 150 | RR,ext1,sec:room heaters` +### C076 · Σ|Δ| 1.5 · 1 certs · `hpcdb15502/gas | granite or | pitched 150 | RR,ext1,sec:room heaters` - [ ] `100100274789` · L 60 / E 61.51 / Δ +1.51 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C080 · Σ|Δ| 1.5 · 1 certs · `hpcdb16453/gas | cavity wall | pitched 300 | consv2,ext1` +### C077 · Σ|Δ| 1.5 · 1 certs · `hpcdb16453/gas | cavity wall | pitched 300 | consv2,ext1` - [ ] `100050852755` · L 65 / E 66.49 / Δ +1.49 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C081 · Σ|Δ| 1.5 · 1 certs · `hpcdb17679/gas | solid brick | pitched 125 | ext1` -- [ ] `100021326422` · L 70 / E 68.51 / Δ -1.49 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) - -### C082 · Σ|Δ| 1.5 · 1 certs · `hpcdb15029/gas | solid brick | pitched insulated | RR,ext1` +### C078 · Σ|Δ| 1.5 · 1 certs · `hpcdb15029/gas | solid brick | pitched insulated | RR,ext1` - [ ] `100050988345` · L 61 / E 59.51 / Δ -1.49 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C083 · Σ|Δ| 1.5 · 1 certs · `hpcdb17998/gas | solid brick | pitched insulated | RR,consv2` +### C079 · Σ|Δ| 1.5 · 1 certs · `hpcdb17998/gas | solid brick | pitched insulated | RR,consv2` - [ ] `100030285758` · L 54 / E 55.48 / Δ +1.48 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C084 · Σ|Δ| 1.4 · 1 certs · `hpcdb17978/gas | solid brick | flat no | RR,ext1` +### C080 · Σ|Δ| 1.4 · 1 certs · `hpcdb17978/gas | solid brick | flat no | RR,ext1` - [ ] `line80` · L 72 / E 70.56 / Δ -1.44 -### C085 · Σ|Δ| 1.4 · 1 certs · `hpcdb10356/gas | solid brick | pitched 150 | RR,ext1,sec:room heaters` +### C081 · Σ|Δ| 1.4 · 1 certs · `hpcdb10356/gas | solid brick | pitched 150 | RR,ext1,sec:room heaters` - [ ] `100080518673` · L 57 / E 58.42 / Δ +1.42 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C086 · Σ|Δ| 1.4 · 1 certs · `hpcdb18561/gas | solid brick | pitched 250 | ext2` +### C082 · Σ|Δ| 1.4 · 1 certs · `hpcdb18561/gas | solid brick | pitched 250 | ext2` - [ ] `235025284` · L 69 / E 67.59 / Δ -1.41 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C087 · Σ|Δ| 1.4 · 1 certs · `hpcdb18767/gas | sandstone as | (another dwelling` +### C083 · Σ|Δ| 1.4 · 1 certs · `hpcdb18767/gas | sandstone as | (another dwelling` - [ ] `250063088` · L 73 / E 71.60 / Δ -1.40 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C088 · Σ|Δ| 1.4 · 1 certs · `hpcdb16210/gas | cavity wall | pitched 300 | sec:room heaters` +### C084 · Σ|Δ| 1.4 · 1 certs · `hpcdb16210/gas | cavity wall | pitched 300 | sec:room heaters` - [ ] `100110097281` · L 64 / E 65.37 / Δ +1.37 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C089 · Σ|Δ| 1.4 · 1 certs · `h691/elec | solid brick | (another dwelling | whw903` +### C085 · Σ|Δ| 1.4 · 1 certs · `h691/elec | solid brick | (another dwelling | whw903` - [ ] `10091630692` · L 58 / E 56.63 / Δ -1.37 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C090 · Σ|Δ| 1.3 · 1 certs · `hpcdb19227/fuel28 | solid brick | pitched 100 | RR,ext3,sec:room heaters` +### C086 · Σ|Δ| 1.3 · 1 certs · `hpcdb19227/fuel28 | solid brick | pitched 100 | RR,ext3,sec:room heaters` - [ ] `100012368622` · L 39 / E 40.32 / Δ +1.32 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C091 · Σ|Δ| 1.3 · 1 certs · `hpcdb19007/gas | solid brick | pitched insulated | RR,ext2,sec:room heaters` +### C087 · Σ|Δ| 1.3 · 1 certs · `hpcdb19007/gas | solid brick | pitched insulated | RR,ext2,sec:room heaters` - [ ] `90077194` · L 65 / E 63.69 / Δ -1.31 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C092 · Σ|Δ| 1.3 · 1 certs · `hpcdb19083/gas | cavity wall | pitched 75 | RR,consv2` +### C088 · Σ|Δ| 1.3 · 1 certs · `hpcdb19083/gas | cavity wall | pitched 75 | RR,consv2` - [ ] `100031461986` · L 70 / E 71.29 / Δ +1.29 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C093 · Σ|Δ| 1.3 · 1 certs · `h691/elec | granite or | pitched insulated | PV,ext3,whw903` +### C089 · Σ|Δ| 1.3 · 1 certs · `h691/elec | granite or | pitched insulated | PV,ext3,whw903` - [ ] `30095054` · L 46 / E 44.74 / Δ -1.26 <- non-electric cost (secondary fuel/cylinder/tariff); spec-faithful vs lodged -### C094 · Σ|Δ| 1.3 · 1 certs · `hpcdb18335/gas | cavity wall | pitched 100 | RR,ext1,sec:room heaters` +### C090 · Σ|Δ| 1.3 · 1 certs · `hpcdb18335/gas | cavity wall | pitched 100 | RR,ext1,sec:room heaters` - [ ] `100100055589` · L 58 / E 59.26 / Δ +1.26 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C095 · Σ|Δ| 1.2 · 1 certs · `h409/elec | solid brick | (another dwelling | consv2,sec:portable electric,whw909` +### C091 · Σ|Δ| 1.2 · 1 certs · `h409/elec | solid brick | (another dwelling | consv2,sec:portable electric,whw909` - [ ] `74061136` · L 73 / E 71.75 / Δ -1.25 <- electric storage cat7 E7: over-rate = off-peak rate/fraction too generous; needs Elmhurst tariff arbitration -### C096 · Σ|Δ| 1.2 · 1 certs · `hpcdb15101/gas | cavity wall | pitched 270 | consv3,sec:room heaters` +### C092 · Σ|Δ| 1.2 · 1 certs · `hpcdb15101/gas | cavity wall | pitched 270 | consv3,sec:room heaters` - [ ] `100091435353` · L 65 / E 66.23 / Δ +1.23 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C097 · Σ|Δ| 1.2 · 2 certs · `h301/coal-anth | system built | (another dwelling` +### C093 · Σ|Δ| 1.2 · 2 certs · `h301/coal-anth | system built | (another dwelling` - [ ] `10091070164` · L 69 / E 69.60 / Δ +0.60 - [ ] `10090927185` · L 79 / E 79.60 / Δ +0.60 -### C098 · Σ|Δ| 1.2 · 1 certs · `hpcdb19110/gas | cavity wall | pitched no | ext3` +### C094 · Σ|Δ| 1.2 · 1 certs · `hpcdb19110/gas | cavity wall | pitched no | ext3` - [ ] `100100614412` · L 53 / E 51.81 / Δ -1.19 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C099 · Σ|Δ| 1.2 · 1 certs · `hpcdb18658/gas | solid brick | flat no` -- [ ] `217091901` · L 62 / E 60.82 / Δ -1.18 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) - -### C100 · Σ|Δ| 1.2 · 1 certs · `hpcdb17115/gas | sandstone as | (another dwelling | ext2,sec:room heaters` +### C095 · Σ|Δ| 1.2 · 1 certs · `hpcdb17115/gas | sandstone as | (another dwelling | ext2,sec:room heaters` - [x] `line768` · L 57 / E 55.84 / Δ -1.16 -### C101 · Σ|Δ| 1.2 · 1 certs · `h102/gas | solid brick | (same dwelling | ext1,sec:portable electric` +### C096 · Σ|Δ| 1.2 · 1 certs · `h102/gas | solid brick | (same dwelling | ext1,sec:portable electric` - [ ] `217061895` · L 61 / E 59.84 / Δ -1.16 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C102 · Σ|Δ| 1.1 · 1 certs · `hpcdb15708/gas | cavity wall | pitched insulated | RR,sec:room heaters` +### C097 · Σ|Δ| 1.1 · 1 certs · `hpcdb15708/gas | cavity wall | pitched insulated | RR,sec:room heaters` - [ ] `10002349227` · L 76 / E 77.14 / Δ +1.14 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C103 · Σ|Δ| 1.1 · 1 certs · `hpcdb15451/gas | timber frame | pitched insulated | RR,ext1,sec:room heaters` +### C098 · Σ|Δ| 1.1 · 1 certs · `hpcdb18119/gas | solid brick | pitched 100 | ext1` +- [ ] `100030307976` · L 71 / E 69.86 / Δ -1.14 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) + +### C099 · Σ|Δ| 1.1 · 1 certs · `hpcdb15451/gas | timber frame | pitched insulated | RR,ext1,sec:room heaters` - [ ] `200001634005` · L 66 / E 67.11 / Δ +1.11 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C104 · Σ|Δ| 1.1 · 1 certs · `hpcdb16407/gas | cavity wall | pitched 250 | ext1,sec:room heaters` +### C100 · Σ|Δ| 1.1 · 1 certs · `hpcdb16407/gas | cavity wall | pitched 250 | ext1,sec:room heaters` - [ ] `100071059081` · L 63 / E 64.09 / Δ +1.09 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C105 · Σ|Δ| 1.1 · 1 certs · `hpcdb17973/gas | solid brick | (another dwelling` +### C101 · Σ|Δ| 1.1 · 1 certs · `hpcdb17973/gas | solid brick | (another dwelling` - [ ] `5117280` · L 76 / E 77.08 / Δ +1.08 <- 🔧 thin-internal-insulation bucket-0 fix: present insulation + known <25mm thickness was mapped to uninsulated (U 1.7); now routes to 50mm row like unknown-thickness. Δ-4.44 -> +1.08. -### C106 · Σ|Δ| 1.1 · 1 certs · `hpcdb19007/gas+2mains | cavity wall | pitched 350 | ext2,mv5,sec:room heaters,whw914` +### C102 · Σ|Δ| 1.1 · 1 certs · `hpcdb19007/gas+2mains | cavity wall | pitched 350 | ext2,mv5,sec:room heaters,whw914` - [ ] `100011820199` · L 55 / E 56.06 / Δ +1.06 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C107 · Σ|Δ| 1.1 · 1 certs · `h691/elec | system built | (another dwelling | whw903` +### C103 · Σ|Δ| 1.1 · 1 certs · `h691/elec | system built | (another dwelling | whw903` - [ ] `90214753` · L 83 / E 81.95 / Δ -1.05 <- non-electric cost (secondary fuel/cylinder/tariff); spec-faithful vs lodged -### C108 · Σ|Δ| 1.0 · 1 certs · `h102/gas | cavity wall | pitched insulated | consv2` +### C104 · Σ|Δ| 1.1 · 1 certs · `hpcdb17570/gas+2mains | solid brick | (another dwelling | RR,ext3,sec:room heaters` +- [ ] `100022668387` · L 68 / E 66.95 / Δ -1.05 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) + +### C105 · Σ|Δ| 1.0 · 1 certs · `h102/gas | cavity wall | pitched insulated | consv2` - [ ] `100051051866` · L 70 / E 71.04 / Δ +1.04 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C109 · Σ|Δ| 1.0 · 1 certs · `hpcdb18737/gas | cavity wall | pitched insulated | PV,sec:room heaters` +### C106 · Σ|Δ| 1.0 · 1 certs · `hpcdb18737/gas | cavity wall | pitched insulated | PV,sec:room heaters` - [ ] `10013836852` · L 100 / E 98.97 / Δ -1.03 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C110 · Σ|Δ| 1.0 · 1 certs · `hpcdb17985/gas | solid brick | (another dwelling` +### C107 · Σ|Δ| 1.0 · 1 certs · `hpcdb17985/gas | solid brick | (another dwelling` - [ ] `line899` · L 73 / E 71.97 / Δ -1.03 -### C111 · Σ|Δ| 1.0 · 1 certs · `hpcdb18524/gas+2mains | cavity wall | pitched 400+ | ext4` +### C108 · Σ|Δ| 1.0 · 1 certs · `hpcdb18524/gas+2mains | cavity wall | pitched 400+ | ext4` - [ ] `40037847` · L 81 / E 79.98 / Δ -1.02 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C112 · Σ|Δ| 1.0 · 1 certs · `hpcdb19012/gas | cavity wall | pitched insulated` +### C109 · Σ|Δ| 1.0 · 1 certs · `hpcdb19012/gas | cavity wall | pitched insulated` - [ ] `10070056075` · L 80 / E 78.99 / Δ -1.01 <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) -### C113 · Σ|Δ| 1.0 · 1 certs · `hpcdb16843/gas+2mains | cavity wall | pitched insulated | PV,RR,ext2` +### C110 · Σ|Δ| 1.0 · 1 certs · `hpcdb16843/gas+2mains | cavity wall | pitched insulated | PV,RR,ext2` - [ ] `200131078` · L 86 / E 85.00 / Δ -1.00 <- room-in-roof surface geometry (spec-mandated, worksheet-validated; see PRs #1440/#1441) -### C114 · Σ|Δ| 1.0 · 1 certs · `hpcdb17489/gas | cavity wall | pitched 270 | PV,consv2,ext1,sec:room heaters` +### C111 · Σ|Δ| 1.0 · 1 certs · `hpcdb17489/gas | cavity wall | pitched 270 | PV,consv2,ext1,sec:room heaters` - [ ] `10000245212` · L 101 / E 100.00 / Δ -1.00 <- non-electric cost (secondary fuel/cylinder/tariff); spec-faithful vs lodged -### C115 · Σ|Δ| 1.0 · 1 certs · `h191/elec | solid brick | (another dwelling | ext2,whw909` -- [ ] `15129162` · L 62 / E 61.00 / Δ -1.00 - -### C116 · Σ|Δ| 1.0 · 1 certs · `h301/coal-anth | cavity wall | pitched 270` +### C112 · Σ|Δ| 1.0 · 1 certs · `h301/coal-anth | cavity wall | pitched 270` - [ ] `23032873` · L 73 / E 72.00 / Δ -1.00 -### C117 · Σ|Δ| 1.0 · 1 certs · `hpcdb16930/gas | cavity wall | pitched unknown | RR,ext1,sec:room heaters` +### C113 · Σ|Δ| 1.0 · 1 certs · `hpcdb16930/gas | cavity wall | pitched unknown | RR,ext1,sec:room heaters` - [ ] `45019020` · L 62 / E 61.01 / Δ -0.99 -### C118 · Σ|Δ| 1.0 · 1 certs · `hpcdb17939/gas | solid brick | (another dwelling` +### C114 · Σ|Δ| 1.0 · 1 certs · `hpcdb17939/gas | solid brick | (another dwelling` - [ ] `42065028` · L 59 / E 58.02 / Δ -0.98 -### C119 · Σ|Δ| 1.0 · 1 certs · `hpcdb10071/gas | cavity wall | pitched 150 | RR` +### C115 · Σ|Δ| 1.0 · 1 certs · `hpcdb10071/gas | cavity wall | pitched 150 | RR` - [ ] `10032928147` · L 77 / E 76.04 / Δ -0.96 -### C120 · Σ|Δ| 1.0 · 1 certs · `hpcdb17045/gas | cavity wall | pitched 300` +### C116 · Σ|Δ| 1.0 · 1 certs · `hpcdb17045/gas | cavity wall | pitched 300` - [ ] `10010678937` · L 78 / E 77.04 / Δ -0.96 -### C121 · Σ|Δ| 1.0 · 1 certs · `hpcdb15247/gas | cavity wall | pitched insulated | RR` +### C117 · Σ|Δ| 1.0 · 1 certs · `hpcdb15247/gas | cavity wall | pitched insulated | RR` - [ ] `100011010151` · L 62 / E 62.96 / Δ +0.96 -### C122 · Σ|Δ| 1.0 · 1 certs · `hpcdb16406/gas | solid brick | pitched insulated | RR,consv2,ext2,sec:room heaters` -- [ ] `100050477371` · L 65 / E 64.04 / Δ -0.96 - -### C123 · Σ|Δ| 0.9 · 1 certs · `hpcdb17529/fuel28 | cavity wall | pitched insulated | RR,ext2,sec:room heaters` +### C118 · Σ|Δ| 0.9 · 1 certs · `hpcdb17529/fuel28 | cavity wall | pitched insulated | RR,ext2,sec:room heaters` - [ ] `10002945522` · L 61 / E 61.94 / Δ +0.94 -### C124 · Σ|Δ| 0.9 · 1 certs · `h691/elec+2mains | cob as | pitched 300 | whw903` +### C119 · Σ|Δ| 0.9 · 1 certs · `h691/elec+2mains | cob as | pitched 300 | whw903` - [ ] `10032957680` · L 45 / E 45.94 / Δ +0.94 -### C125 · Σ|Δ| 0.9 · 1 certs · `hpcdb18204/gas | solid brick | pitched 400+ | PV,ext2` +### C120 · Σ|Δ| 0.9 · 1 certs · `hpcdb18204/gas | solid brick | pitched 400+ | PV,ext2` - [ ] `2465057465` · L 81 / E 81.93 / Δ +0.93 -### C126 · Σ|Δ| 0.9 · 1 certs · `hpcdb19019/gas | solid brick | pitched 100 | ext3,sec:room heaters` +### C121 · Σ|Δ| 0.9 · 1 certs · `hpcdb19019/gas | solid brick | pitched 100 | ext3,sec:room heaters` - [ ] `100090616327` · L 63 / E 63.93 / Δ +0.93 -### C127 · Σ|Δ| 0.9 · 1 certs · `hpcdb17612/gas | cavity wall | pitched 270 | ext1` +### C122 · Σ|Δ| 0.9 · 1 certs · `hpcdb17612/gas | cavity wall | pitched 270 | ext1` - [ ] `100012753833` · L 74 / E 73.07 / Δ -0.93 -### C128 · Σ|Δ| 0.9 · 1 certs · `hpcdb19006/gas | solid brick | pitched 75 | ext1,sec:room heaters` +### C123 · Σ|Δ| 0.9 · 1 certs · `hpcdb19006/gas | solid brick | pitched 75 | ext1,sec:room heaters` - [ ] `100031269525` · L 53 / E 53.91 / Δ +0.91 -### C129 · Σ|Δ| 0.9 · 1 certs · `hpcdb17964/gas | cavity wall | pitched insulated | consv3` +### C124 · Σ|Δ| 0.9 · 1 certs · `hpcdb17964/gas | cavity wall | pitched insulated | consv3` - [ ] `200003725358` · L 75 / E 74.09 / Δ -0.91 -### C130 · Σ|Δ| 0.9 · 1 certs · `hpcdb17507/gas | solid brick | pitched insulated | RR,ext3` +### C125 · Σ|Δ| 0.9 · 1 certs · `hpcdb17507/gas | solid brick | pitched insulated | RR,ext3` - [ ] `100050986162` · L 62 / E 61.10 / Δ -0.90 -### C131 · Σ|Δ| 0.9 · 1 certs · `hpcdb17985/gas | cavity wall | (another dwelling | mv2` +### C126 · Σ|Δ| 0.9 · 1 certs · `hpcdb17985/gas | cavity wall | (another dwelling | mv2` - [ ] `5300000141` · L 78 / E 77.10 / Δ -0.90 -### C132 · Σ|Δ| 0.9 · 1 certs · `hpcdb18616/gas | cavity wall | pitched 250 | sec:room heaters` +### C127 · Σ|Δ| 0.9 · 1 certs · `hpcdb18616/gas | cavity wall | pitched 250 | sec:room heaters` - [ ] `100050618404` · L 73 / E 73.89 / Δ +0.89 -### C133 · Σ|Δ| 0.9 · 1 certs · `hpcdb17507/gas | cavity wall | pitched 250 | PV,ext1` +### C128 · Σ|Δ| 0.9 · 1 certs · `hpcdb17507/gas | cavity wall | pitched 250 | PV,ext1` - [ ] `100090476016` · L 81 / E 81.89 / Δ +0.89 -### C134 · Σ|Δ| 0.9 · 1 certs · `h691/elec | timber frame | pitched 200 | RR,ext3,whw903` +### C129 · Σ|Δ| 0.9 · 1 certs · `h691/elec | timber frame | pitched 200 | RR,ext3,whw903` - [ ] `10012119141` · L 45 / E 45.89 / Δ +0.89 -### C135 · Σ|Δ| 0.9 · 1 certs · `h524/elec | cavity wall | pitched 300 | PV` +### C130 · Σ|Δ| 0.9 · 1 certs · `h524/elec | cavity wall | pitched 300 | PV` - [ ] `100040938206` · L 75 / E 75.89 / Δ +0.89 -### C136 · Σ|Δ| 0.9 · 1 certs · `hpcdb18234/gas | cavity wall | pitched insulated | RR,consv2` +### C131 · Σ|Δ| 0.9 · 1 certs · `hpcdb18234/gas | cavity wall | pitched insulated | RR,consv2` - [ ] `38188474` · L 76 / E 76.87 / Δ +0.87 -### C137 · Σ|Δ| 0.9 · 1 certs · `hpcdb16777/gas | cavity wall | pitched 100 | ext1,sec:room heaters` +### C132 · Σ|Δ| 0.9 · 1 certs · `hpcdb16777/gas | cavity wall | pitched 100 | ext1,sec:room heaters` - [ ] `100061194957` · L 69 / E 69.87 / Δ +0.87 -### C138 · Σ|Δ| 0.9 · 1 certs · `hpcdb18559/gas+2mains | solid brick | pitched 200 | whw914` +### C133 · Σ|Δ| 0.9 · 1 certs · `hpcdb18559/gas+2mains | solid brick | pitched 200 | whw914` - [ ] `100021236527` · L 72 / E 71.13 / Δ -0.87 -### C139 · Σ|Δ| 0.9 · 1 certs · `hpcdb18907/gas | cavity wall | pitched 270 | consv2` +### C134 · Σ|Δ| 0.9 · 1 certs · `hpcdb18907/gas | cavity wall | pitched 270 | consv2` - [ ] `100040193752` · L 71 / E 71.86 / Δ +0.86 -### C140 · Σ|Δ| 0.9 · 1 certs · `hpcdb10327/gas | cavity wall | (another dwelling` +### C135 · Σ|Δ| 0.9 · 1 certs · `hpcdb10327/gas | cavity wall | (another dwelling` - [ ] `100060701552` · L 74 / E 74.86 / Δ +0.86 -### C141 · Σ|Δ| 0.9 · 1 certs · `h691/elec | system built | flat insulated | whw903` +### C136 · Σ|Δ| 0.9 · 1 certs · `h691/elec | system built | flat insulated | whw903` - [ ] `10070396146` · L 61 / E 60.14 / Δ -0.86 -### C142 · Σ|Δ| 0.8 · 1 certs · `hpcdb8587/gas | cavity wall | (another dwelling` +### C137 · Σ|Δ| 0.8 · 1 certs · `hpcdb8587/gas | cavity wall | (another dwelling` - [ ] `100030528930` · L 74 / E 73.16 / Δ -0.84 -### C143 · Σ|Δ| 0.8 · 1 certs · `hpcdb18212/gas | solid brick | (another dwelling` +### C138 · Σ|Δ| 0.8 · 1 certs · `hpcdb18212/gas | solid brick | (another dwelling` - [ ] `100020982570` · L 72 / E 71.17 / Δ -0.83 -### C144 · Σ|Δ| 0.8 · 1 certs · `hpcdb16888/gas | cavity wall | pitched 350 | consv2` +### C139 · Σ|Δ| 0.8 · 1 certs · `h191/elec | solid brick | (another dwelling | ext2,whw909` +- [ ] `15129162` · L 62 / E 61.17 / Δ -0.83 + +### C140 · Σ|Δ| 0.8 · 1 certs · `hpcdb16888/gas | cavity wall | pitched 350 | consv2` - [ ] `394024487` · L 75 / E 74.17 / Δ -0.83 -### C145 · Σ|Δ| 0.8 · 1 certs · `hpcdb17503/gas | solid brick | pitched insulated | RR,ext1,mv4` +### C141 · Σ|Δ| 0.8 · 1 certs · `hpcdb17503/gas | solid brick | pitched insulated | RR,ext1,mv4` - [ ] `217070698` · L 78 / E 78.83 / Δ +0.83 -### C146 · Σ|Δ| 0.8 · 1 certs · `hpcdb15709/gas | cavity wall | (another dwelling | ext1` +### C142 · Σ|Δ| 0.8 · 1 certs · `hpcdb15709/gas | cavity wall | (another dwelling | ext1` - [ ] `77022508` · L 69 / E 68.18 / Δ -0.82 -### C147 · Σ|Δ| 0.8 · 1 certs · `hpcdb18496/gas | cavity wall | pitched 250 | RR,consv4` +### C143 · Σ|Δ| 0.8 · 1 certs · `hpcdb18496/gas | cavity wall | pitched 250 | RR,consv4` - [ ] `100040259104` · L 68 / E 68.82 / Δ +0.82 -### C148 · Σ|Δ| 0.8 · 1 certs · `hpcdb18559/gas | solid brick | pitched 270 | ext1` +### C144 · Σ|Δ| 0.8 · 1 certs · `hpcdb18559/gas | solid brick | pitched 270 | ext1` - [ ] `100031399026` · L 70 / E 70.81 / Δ +0.81 -### C149 · Σ|Δ| 0.8 · 1 certs · `hpcdb16374/gas | cavity wall | pitched unknown` +### C145 · Σ|Δ| 0.8 · 1 certs · `hpcdb16374/gas | cavity wall | pitched unknown` - [ ] `10009645698` · L 67 / E 67.81 / Δ +0.81 -### C150 · Σ|Δ| 0.8 · 1 certs · `hpcdb16395/gas | timber frame | thatched with | ext1,sec:room heaters` +### C146 · Σ|Δ| 0.8 · 1 certs · `hpcdb16395/gas | timber frame | thatched with | ext1,sec:room heaters` - [ ] `100080818887` · L 56 / E 55.19 / Δ -0.81 -### C151 · Σ|Δ| 0.8 · 1 certs · `hpcdb16835/gas | solid brick | pitched insulated | RR,ext1` +### C147 · Σ|Δ| 0.8 · 1 certs · `hpcdb16835/gas | solid brick | pitched insulated | RR,ext1` - [ ] `100012374381` · L 69 / E 69.80 / Δ +0.80 -### C152 · Σ|Δ| 0.8 · 1 certs · `hpcdb17560/gas | cavity wall | pitched 250 | ext2` +### C148 · Σ|Δ| 0.8 · 1 certs · `hpcdb17560/gas | cavity wall | pitched 250 | ext2` - [ ] `100012350270` · L 78 / E 77.20 / Δ -0.80 -### C153 · Σ|Δ| 0.8 · 1 certs · `hpcdb15166/gas+2mains | sandstone as | pitched no | PV,ext4,sec:room heaters,whw914` +### C149 · Σ|Δ| 0.8 · 1 certs · `hpcdb15166/gas+2mains | sandstone as | pitched no | PV,ext4,sec:room heaters,whw914` - [ ] `100051109723` · L 60 / E 60.78 / Δ +0.78 -### C154 · Σ|Δ| 0.8 · 1 certs · `hpcdb18911/gas | cavity wall | pitched 50 | consv4` +### C150 · Σ|Δ| 0.8 · 1 certs · `hpcdb18911/gas | cavity wall | pitched 50 | consv4` - [ ] `100061616915` · L 63 / E 62.22 / Δ -0.78 -### C155 · Σ|Δ| 0.8 · 1 certs · `h691/elec | cavity wall | pitched 200 | whw903` +### C151 · Σ|Δ| 0.8 · 1 certs · `h691/elec | cavity wall | pitched 200 | whw903` - [ ] `line71` · L 74 / E 74.77 / Δ +0.77 -### C156 · Σ|Δ| 0.8 · 1 certs · `hpcdb17986/gas | cavity wall | (another dwelling` +### C152 · Σ|Δ| 0.8 · 1 certs · `hpcdb17986/gas | cavity wall | (another dwelling` - [ ] `100011603934` · L 72 / E 71.23 / Δ -0.77 -### C157 · Σ|Δ| 0.8 · 1 certs · `hpcdb10244/gas | cavity wall | pitched 100` +### C153 · Σ|Δ| 0.8 · 1 certs · `hpcdb10244/gas | cavity wall | pitched 100` - [ ] `24030987` · L 73 / E 73.76 / Δ +0.76 -### C158 · Σ|Δ| 0.8 · 1 certs · `h193/elec | solid brick | (another dwelling | whw909` +### C154 · Σ|Δ| 0.8 · 1 certs · `h193/elec | solid brick | (another dwelling | whw909` - [ ] `100032043038` · L 74 / E 73.24 / Δ -0.76 -### C159 · Σ|Δ| 0.8 · 1 certs · `hpcdb17513/gas | cavity wall | pitched unknown` +### C155 · Σ|Δ| 0.8 · 1 certs · `hpcdb17513/gas | cavity wall | pitched unknown` - [ ] `100010866608` · L 71 / E 70.24 / Δ -0.76 -### C160 · Σ|Δ| 0.7 · 1 certs · `hpcdb17965/gas | cavity wall | pitched insulated | RR,ext1,sec:room heaters` +### C156 · Σ|Δ| 0.7 · 1 certs · `hpcdb17965/gas | cavity wall | pitched insulated | RR,ext1,sec:room heaters` - [ ] `100090399615` · L 67 / E 67.75 / Δ +0.75 -### C161 · Σ|Δ| 0.7 · 1 certs · `hpcdb16137/gas | solid brick | pitched 150` +### C157 · Σ|Δ| 0.7 · 1 certs · `hpcdb16137/gas | solid brick | pitched 150` - [ ] `200004296092` · L 71 / E 71.74 / Δ +0.74 -### C162 · Σ|Δ| 0.7 · 1 certs · `hpcdb17576/gas | cavity wall | pitched insulated | RR,consv2` +### C158 · Σ|Δ| 0.7 · 1 certs · `hpcdb17576/gas | cavity wall | pitched insulated | RR,consv2` - [ ] `10012341135` · L 79 / E 79.73 / Δ +0.73 -### C163 · Σ|Δ| 0.7 · 1 certs · `h409/elec | cavity wall | (another dwelling | sec:portable electric,whw903` +### C159 · Σ|Δ| 0.7 · 1 certs · `h409/elec | cavity wall | (another dwelling | sec:portable electric,whw903` - [ ] `100091005853` · L 70 / E 69.27 / Δ -0.73 -### C164 · Σ|Δ| 0.7 · 1 certs · `hpcdb18908/gas | cavity wall | pitched 175` +### C160 · Σ|Δ| 0.7 · 1 certs · `hpcdb18908/gas | cavity wall | pitched 175` - [ ] `100021533514` · L 71 / E 71.73 / Δ +0.73 -### C165 · Σ|Δ| 0.7 · 1 certs · `h301/coal-anth | solid brick | (another dwelling` +### C161 · Σ|Δ| 0.7 · 1 certs · `h301/coal-anth | solid brick | (another dwelling` - [ ] `5062075` · L 69 / E 69.72 / Δ +0.72 -### C166 · Σ|Δ| 0.7 · 1 certs · `hpcdb18112/gas | cavity wall | pitched 100 | RR,ext1` +### C162 · Σ|Δ| 0.7 · 1 certs · `hpcdb18112/gas | cavity wall | pitched 100 | RR,ext1` - [ ] `100040813308` · L 62 / E 62.72 / Δ +0.72 -### C167 · Σ|Δ| 0.7 · 1 certs · `h104/gas | solid brick | pitched unknown | ext2` -- [ ] `38105600` · L 68 / E 67.28 / Δ -0.72 - -### C168 · Σ|Δ| 0.7 · 1 certs · `h104/gas | solid brick | pitched 200` -- [ ] `line255` · L 72 / E 71.29 / Δ -0.71 - -### C169 · Σ|Δ| 0.7 · 1 certs · `h304/elec | cavity wall | (another dwelling | whw950` +### C163 · Σ|Δ| 0.7 · 1 certs · `h304/elec | cavity wall | (another dwelling | whw950` - [ ] `5300047580` · L 69 / E 68.30 / Δ -0.70 -### C170 · Σ|Δ| 0.7 · 1 certs · `h104/gas | cavity wall | pitched 250 | ext1` +### C164 · Σ|Δ| 0.7 · 1 certs · `h104/gas | cavity wall | pitched 250 | ext1` - [ ] `100110433403` · L 69 / E 68.32 / Δ -0.68 -### C171 · Σ|Δ| 0.7 · 1 certs · `hpcdb16835/gas | cavity wall | pitched insulated` +### C165 · Σ|Δ| 0.7 · 1 certs · `hpcdb16835/gas | cavity wall | pitched insulated` - [ ] `10093416558` · L 79 / E 78.32 / Δ -0.68 -### C172 · Σ|Δ| 0.7 · 1 certs · `hpcdb18251/gas | solid brick | pitched 100` +### C166 · Σ|Δ| 0.7 · 1 certs · `hpcdb18251/gas | solid brick | pitched 100` - [ ] `100050958217` · L 68 / E 68.68 / Δ +0.68 -### C173 · Σ|Δ| 0.7 · 1 certs · `hpcdb18224/gas | solid brick | pitched 225 | ext1` +### C167 · Σ|Δ| 0.7 · 1 certs · `hpcdb18224/gas | solid brick | pitched 225 | ext1` - [ ] `100022667797` · L 68 / E 67.32 / Δ -0.68 -### C174 · Σ|Δ| 0.7 · 1 certs · `hpcdb10243/gas | cavity wall | pitched insulated | RR,sec:room heaters` +### C168 · Σ|Δ| 0.7 · 1 certs · `hpcdb10243/gas | cavity wall | pitched insulated | RR,sec:room heaters` - [ ] `100030528925` · L 78 / E 78.65 / Δ +0.65 -### C175 · Σ|Δ| 0.6 · 1 certs · `hpcdb17657/gas | cavity wall | (another dwelling` +### C169 · Σ|Δ| 0.6 · 1 certs · `hpcdb17657/gas | cavity wall | (another dwelling` - [ ] `100110300037` · L 74 / E 73.35 / Δ -0.65 -### C176 · Σ|Δ| 0.6 · 1 certs · `hpcdb2044/gas | solid brick | (another dwelling | ext1` +### C170 · Σ|Δ| 0.6 · 1 certs · `hpcdb2044/gas | solid brick | (another dwelling | ext1` - [ ] `217092450` · L 52 / E 52.64 / Δ +0.64 -### C177 · Σ|Δ| 0.6 · 1 certs · `hpcdb15101/gas | solid brick | pitched 300` -- [ ] `100061164545` · L 72 / E 71.36 / Δ -0.64 - -### C178 · Σ|Δ| 0.6 · 1 certs · `hpcdb10328/gas | cavity wall | pitched unknown` +### C171 · Σ|Δ| 0.6 · 1 certs · `hpcdb10328/gas | cavity wall | pitched unknown` - [ ] `100051075936` · L 71 / E 70.36 / Δ -0.64 -### C179 · Σ|Δ| 0.6 · 1 certs · `hpcdb17507/gas | cavity wall | roof room(s) | RR,sec:room heaters` +### C172 · Σ|Δ| 0.6 · 1 certs · `hpcdb17507/gas | cavity wall | roof room(s) | RR,sec:room heaters` - [ ] `100011565830` · L 72 / E 72.64 / Δ +0.64 -### C180 · Σ|Δ| 0.6 · 1 certs · `hpcdb15700/gas | sandstone with | pitched 100 | RR,sec:room heaters` +### C173 · Σ|Δ| 0.6 · 1 certs · `hpcdb15700/gas | sandstone with | pitched 100 | RR,sec:room heaters` - [ ] `61010956` · L 75 / E 74.37 / Δ -0.63 -### C181 · Σ|Δ| 0.6 · 1 certs · `hpcdb17115/gas+2mains | cavity wall | pitched insulated | RR,ext4,sec:room heaters` +### C174 · Σ|Δ| 0.6 · 1 certs · `hpcdb17115/gas+2mains | cavity wall | pitched insulated | RR,ext4,sec:room heaters` - [ ] `100110752514` · L 67 / E 67.62 / Δ +0.62 -### C182 · Σ|Δ| 0.6 · 1 certs · `h404/elec | cavity wall | pitched 175 | sec:room heaters,whw909` +### C175 · Σ|Δ| 0.6 · 1 certs · `h404/elec | cavity wall | pitched 175 | sec:room heaters,whw909` - [ ] `100070295212` · L 75 / E 74.38 / Δ -0.62 -### C183 · Σ|Δ| 0.6 · 1 certs · `hpcdb18766/gas+2mains | sandstone as | pitched 100 | RR` +### C176 · Σ|Δ| 0.6 · 1 certs · `hpcdb18766/gas+2mains | sandstone as | pitched 100 | RR` - [ ] `100012534027` · L 65 / E 64.38 / Δ -0.62 -### C184 · Σ|Δ| 0.6 · 1 certs · `hpcdb9058/fuel28 | solid brick | pitched 100 | RR,ext2,sec:room heaters` +### C177 · Σ|Δ| 0.6 · 1 certs · `hpcdb9058/fuel28 | solid brick | pitched 100 | RR,ext2,sec:room heaters` - [x] `10009812472` · L 40 / E 39.40 / Δ -0.60 -### C185 · Σ|Δ| 0.6 · 1 certs · `hpcdb18218/gas | solid brick | (another dwelling` -- [ ] `34080035` · L 77 / E 76.41 / Δ -0.59 - -### C186 · Σ|Δ| 0.6 · 1 certs · `hpcdb16137/gas | cavity wall | pitched 150 | RR,ext1` +### C178 · Σ|Δ| 0.6 · 1 certs · `hpcdb16137/gas | cavity wall | pitched 150 | RR,ext1` - [ ] `21032732` · L 69 / E 69.59 / Δ +0.59 -### C187 · Σ|Δ| 0.6 · 1 certs · `h101/gas | cavity wall | pitched 100 | RR,ext1` +### C179 · Σ|Δ| 0.6 · 1 certs · `h101/gas | cavity wall | pitched 100 | RR,ext1` - [ ] `100060562056` · L 66 / E 65.41 / Δ -0.59 -### C188 · Σ|Δ| 0.6 · 1 certs · `hpcdb17514/fuel27 | cavity wall | pitched 400+ | PV,ext2` +### C180 · Σ|Δ| 0.6 · 1 certs · `hpcdb17514/fuel27 | cavity wall | pitched 400+ | PV,ext2` - [ ] `100091086558` · L 79 / E 79.58 / Δ +0.58 -### C189 · Σ|Δ| 0.6 · 1 certs · `hpcdb18908/gas | solid brick | (another dwelling` +### C181 · Σ|Δ| 0.6 · 1 certs · `hpcdb18908/gas | solid brick | (another dwelling` - [ ] `line599` · L 74 / E 73.42 / Δ -0.58 -### C190 · Σ|Δ| 0.6 · 1 certs · `hpcdb17973/gas | cavity wall | pitched insulated` +### C182 · Σ|Δ| 0.6 · 1 certs · `hpcdb17973/gas | cavity wall | pitched insulated` - [ ] `100020407741` · L 72 / E 71.42 / Δ -0.58 -### C191 · Σ|Δ| 0.6 · 1 certs · `hpcdb19007/gas | cavity wall | pitched 200 | consv2,ext1,sec:room heaters` +### C183 · Σ|Δ| 0.6 · 1 certs · `hpcdb19007/gas | cavity wall | pitched 200 | consv2,ext1,sec:room heaters` - [ ] `100050839457` · L 70 / E 70.57 / Δ +0.57 -### C192 · Σ|Δ| 0.6 · 1 certs · `hpcdb19007/gas | solid brick | pitched insulated | RR,sec:room heaters` +### C184 · Σ|Δ| 0.6 · 1 certs · `hpcdb19007/gas | solid brick | pitched insulated | RR,sec:room heaters` - [ ] `42105250` · L 59 / E 59.57 / Δ +0.57 -### C193 · Σ|Δ| 0.6 · 1 certs · `hpcdb1917/gas | timber frame | pitched insulated` +### C185 · Σ|Δ| 0.6 · 1 certs · `hpcdb1917/gas | timber frame | pitched insulated` - [ ] `77200509` · L 69 / E 69.57 / Δ +0.57 -### C194 · Σ|Δ| 0.6 · 1 certs · `hpcdb10356/gas | cavity wall | pitched insulated | ext2` +### C186 · Σ|Δ| 0.6 · 1 certs · `hpcdb10356/gas | cavity wall | pitched insulated | ext2` - [ ] `200040534` · L 65 / E 65.56 / Δ +0.56 -### C195 · Σ|Δ| 0.6 · 1 certs · `hpcdb17507/gas | solid brick | pitched 150 | ext1,mv2` +### C187 · Σ|Δ| 0.6 · 1 certs · `hpcdb17507/gas | solid brick | pitched 150 | ext1,mv2` - [ ] `200049463` · L 69 / E 69.56 / Δ +0.56 -### C196 · Σ|Δ| 0.6 · 1 certs · `h126/fuel28+2mains | cavity wall | pitched 300 | RR,consv2,ext3,sec:room heaters` +### C188 · Σ|Δ| 0.6 · 1 certs · `h126/fuel28+2mains | cavity wall | pitched 300 | RR,consv2,ext3,sec:room heaters` - [ ] `100062527568` · L 61 / E 61.56 / Δ +0.56 -### C197 · Σ|Δ| 0.6 · 1 certs · `hpcdb15700/gas | cavity wall | pitched limited | RR,ext1` +### C189 · Σ|Δ| 0.6 · 1 certs · `hpcdb15700/gas | cavity wall | pitched limited | RR,ext1` - [ ] `100100268900` · L 69 / E 69.56 / Δ +0.56 -### C198 · Σ|Δ| 0.6 · 1 certs · `hpcdb17004/fuel27+2mains | sandstone as | pitched no | ext3,sec:room heaters` +### C190 · Σ|Δ| 0.6 · 1 certs · `hpcdb17004/fuel27+2mains | sandstone as | pitched no | ext3,sec:room heaters` - [x] `200003110548` · L 29 / E 28.45 / Δ -0.55 -### C199 · Σ|Δ| 0.5 · 1 certs · `hpcdb9464/gas | cavity wall | pitched 150 | ext1` +### C191 · Σ|Δ| 0.5 · 1 certs · `hpcdb9464/gas | cavity wall | pitched 150 | ext1` - [ ] `90036914` · L 60 / E 60.55 / Δ +0.55 -### C200 · Σ|Δ| 0.5 · 1 certs · `h409/elec | cavity wall | pitched 250 | mv5,sec:room heaters,whw903` +### C192 · Σ|Δ| 0.5 · 1 certs · `h409/elec | cavity wall | pitched 250 | mv5,sec:room heaters,whw903` - [ ] `10004742671` · L 70 / E 69.46 / Δ -0.54 -### C201 · Σ|Δ| 0.5 · 1 certs · `hpcdb18251/gas | cavity wall | roof room(s) | RR` +### C193 · Σ|Δ| 0.5 · 1 certs · `hpcdb18251/gas | cavity wall | roof room(s) | RR` - [ ] `100010657450` · L 75 / E 75.54 / Δ +0.54 -### C202 · Σ|Δ| 0.5 · 1 certs · `hpcdb17507/gas | cavity wall | pitched 150 | ext1` +### C194 · Σ|Δ| 0.5 · 1 certs · `hpcdb17507/gas | cavity wall | pitched 150 | ext1` - [ ] `100061834344` · L 71 / E 70.46 / Δ -0.54 -### C203 · Σ|Δ| 0.5 · 1 certs · `h113/gas | cavity wall | pitched 200` +### C195 · Σ|Δ| 0.5 · 1 certs · `h113/gas | cavity wall | pitched 200` - [ ] `4510097194` · L 70 / E 70.53 / Δ +0.53 -### C204 · Σ|Δ| 0.5 · 1 certs · `hpcdb18907/gas | cavity wall | pitched 125 | ext1` +### C196 · Σ|Δ| 0.5 · 1 certs · `hpcdb18907/gas | cavity wall | pitched 125 | ext1` - [ ] `100010913220` · L 72 / E 72.53 / Δ +0.53 -### C205 · Σ|Δ| 0.5 · 1 certs · `hpcdb18908/gas | solid brick | pitched 300` +### C197 · Σ|Δ| 0.5 · 1 certs · `hpcdb18908/gas | solid brick | pitched 300` - [ ] `207032630` · L 74 / E 73.47 / Δ -0.53 -### C206 · Σ|Δ| 0.5 · 1 certs · `hpcdb18242/gas | sandstone as | pitched 150 | ext2,sec:room heaters` +### C198 · Σ|Δ| 0.5 · 1 certs · `hpcdb18242/gas | sandstone as | pitched 150 | ext2,sec:room heaters` - [ ] `43074051` · L 63 / E 62.48 / Δ -0.52 -### C207 · Σ|Δ| 0.5 · 1 certs · `h104/gas | cavity wall | pitched 300` +### C199 · Σ|Δ| 0.5 · 1 certs · `h104/gas | cavity wall | pitched 300` - [ ] `100010723814` · L 69 / E 69.52 / Δ +0.52 -### C208 · Σ|Δ| 0.5 · 1 certs · `hpcdb16137/gas | cavity wall | (another dwelling` +### C200 · Σ|Δ| 0.5 · 1 certs · `hpcdb16137/gas | cavity wall | (another dwelling` - [ ] `100020955792` · L 77 / E 77.52 / Δ +0.52 -### C209 · Σ|Δ| 0.5 · 1 certs · `hpcdb19079/gas | cavity wall | pitched 200 | ext1,sec:room heaters` +### C201 · Σ|Δ| 0.5 · 1 certs · `hpcdb19079/gas | cavity wall | pitched 200 | ext1,sec:room heaters` - [ ] `77172223` · L 65 / E 65.52 / Δ +0.52 -### C210 · Σ|Δ| 0.5 · 1 certs · `hpcdb10242/gas | granite or | pitched insulated | RR,sec:room heaters` +### C202 · Σ|Δ| 0.5 · 1 certs · `hpcdb10242/gas | granite or | pitched insulated | RR,sec:room heaters` - [ ] `100051245439` · L 62 / E 61.48 / Δ -0.52 -### C211 · Σ|Δ| 0.5 · 1 certs · `hpcdb10328/gas | cavity wall | (another dwelling` +### C203 · Σ|Δ| 0.5 · 1 certs · `hpcdb10328/gas | cavity wall | (another dwelling` - [ ] `100012553958` · L 67 / E 66.48 / Δ -0.52 -### C212 · Σ|Δ| 0.5 · 1 certs · `hpcdb10033/gas | cavity wall | pitched 200 | ext2,sec:room heaters` +### C204 · Σ|Δ| 0.5 · 1 certs · `hpcdb10033/gas | cavity wall | pitched 200 | ext2,sec:room heaters` - [ ] `100110108632` · L 63 / E 63.51 / Δ +0.51 -### C213 · Σ|Δ| 0.5 · 1 certs · `h109/gas | cavity wall | pitched 270 | consv2,ext1,sec:room heaters` +### C205 · Σ|Δ| 0.5 · 1 certs · `h109/gas | cavity wall | pitched 270 | consv2,ext1,sec:room heaters` - [ ] `100091102581` · L 65 / E 65.51 / Δ +0.51 -### C214 · Σ|Δ| 0.5 · 1 certs · `hpcdb19008/gas | cavity wall | pitched 150 | ext1,sec:room heaters` +### C206 · Σ|Δ| 0.5 · 1 certs · `hpcdb19008/gas | cavity wall | pitched 150 | ext1,sec:room heaters` - [ ] `10002326688` · L 69 / E 68.50 / Δ -0.50 -### C215 · Σ|Δ| 0.5 · 1 certs · `h129/fuel28 | granite or | pitched 200 | ext2,sec:room heaters` +### C207 · Σ|Δ| 0.5 · 1 certs · `h129/fuel28 | granite or | pitched 200 | ext2,sec:room heaters` - [ ] `10023132869` · L 46 / E 45.50 / Δ -0.50 -### C216 · Σ|Δ| 0.5 · 1 certs · `hpcdb9007/fuel28 | sandstone as | pitched insulated | RR,ext4,sec:room heaters` +### C208 · Σ|Δ| 0.5 · 1 certs · `hpcdb9007/fuel28 | sandstone as | pitched insulated | RR,ext4,sec:room heaters` - [ ] `10091707271` · L 41 / E 41.50 / Δ +0.50 -## Resolved ≤0.5 (778 certs — no action) +## Resolved ≤0.5 (786 certs — no action)
expand @@ -698,7 +674,6 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `100100840201` · L 60 / E 60.48 / Δ +0.48 · `hpcdb10244/gas | granite or | pitched 100 | ext1` - [x] `100071135609` · L 68 / E 68.48 / Δ +0.48 · `hpcdb18229/gas | solid brick | pitched 200 | ext2` - [x] `100121043224` · L 77 / E 77.48 / Δ +0.48 · `hpcdb1758/gas | cavity wall | pitched 300` -- [x] `30025401` · L 70 / E 69.52 / Δ -0.48 · `hpcdb18433/gas | cavity wall | pitched 270 | ext2` - [x] `100021037247` · L 71 / E 70.52 / Δ -0.48 · `hpcdb10112/gas | solid brick | (another dwelling` - [x] `10013778285` · L 80 / E 79.52 / Δ -0.48 · `hpcdb16138/gas | cavity wall | (another dwelling` - [x] `100010348502` · L 76 / E 75.52 / Δ -0.48 · `hpcdb16839/gas | cavity wall | pitched 175` @@ -732,7 +707,6 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `1775121297` · L 79 / E 78.55 / Δ -0.45 · `hpcdb16400/gas | cavity wall | pitched 300 | PV` - [x] `100070304824` · L 76 / E 76.45 / Δ +0.45 · `hpcdb18119/gas | cavity wall | pitched insulated` - [x] `41229335` · L 76 / E 75.55 / Δ -0.45 · `hpcdb16839/gas | cavity wall | pitched 200` -- [x] `200000571972` · L 64 / E 63.55 / Δ -0.45 · `hpcdb17986/gas | solid brick | pitched 250 | ext1` - [x] `600039` · L 71 / E 70.55 / Δ -0.45 · `h104/gas | cavity wall | pitched 200` - [x] `100050635265` · L 68 / E 67.55 / Δ -0.45 · `hpcdb17956/gas | cavity wall | pitched 250 | sec:room heaters` - [x] `100040685184` · L 59 / E 59.45 / Δ +0.45 · `hpcdb18432/gas | cavity wall | pitched 300 | sec:room heaters` @@ -801,6 +775,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `10070086972` · L 18 / E 17.59 / Δ -0.41 · `hpcdb710/fuel28+2mains | sandstone as | pitched insulated | RR,ext4,sec:room heaters` - [x] `100050397944` · L 65 / E 64.59 / Δ -0.41 · `hpcdb17681/gas | cavity wall | pitched 250 | ext1,sec:room heaters` - [x] `63105357` · L 74 / E 74.41 / Δ +0.41 · `hpcdb17507/gas | cavity wall | pitched 270 | sec:room heaters` +- [x] `217091901` · L 62 / E 61.59 / Δ -0.41 · `hpcdb18658/gas | solid brick | flat no` <- FIXED: RdSAP 10 Table 13 200/280mm solid-brick boundary bug (was 60.82); also fixed this cert's build script (never set wall thickness, inherited a stale value) - [x] `100090989765` · L 70 / E 70.41 / Δ +0.41 · `hpcdb16408/gas | timber frame | pitched 300 | consv3,ext1,sec:room heaters` - [x] `30523770` · L 72 / E 71.60 / Δ -0.40 · `hpcdb8191/fuel28 | granite or | pitched 200 | sec:room heaters` - [x] `200004700463` · L 65 / E 65.40 / Δ +0.40 · `hpcdb18559/gas | sandstone as | pitched 75` @@ -838,6 +813,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `77069148` · L 71 / E 71.39 / Δ +0.39 · `hpcdb17507/gas | cavity wall | pitched 200` - [x] `148002976` · L 72 / E 71.61 / Δ -0.39 · `hpcdb16840/gas | cavity wall | pitched insulated | ext1` - [x] `10096536318` · L 66 / E 65.62 / Δ -0.38 · `h301/coal-anth | cavity wall | (another dwelling` +- [x] `100061164545` · L 72 / E 72.38 / Δ +0.38 · `hpcdb15101/gas | solid brick | pitched 300` - [x] `100030585620` · L 59 / E 59.38 / Δ +0.38 · `h102/gas | cavity wall | pitched insulated | ext4,sec:room heaters` - [x] `10008325030` · L 82 / E 81.62 / Δ -0.38 · `h301/coal-anth | system built | (another dwelling` - [x] `100050869353` · L 75 / E 74.62 / Δ -0.38 · `hpcdb10547/gas | solid brick | pitched 300` @@ -870,6 +846,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `200033141` · L 76 / E 75.64 / Δ -0.36 · `hpcdb16738/gas | cavity wall | flat insulated` - [x] `100030357741` · L 72 / E 72.36 / Δ +0.36 · `hpcdb18560/gas | cavity wall | pitched 300 | consv2,ext1` - [x] `100080707176` · L 72 / E 71.64 / Δ -0.36 · `hpcdb10243/gas | cavity wall | pitched 100` +- [x] `30025401` · L 70 / E 69.64 / Δ -0.36 · `hpcdb18433/gas | cavity wall | pitched 270 | ext2` - [x] `100062132377` · L 81 / E 80.64 / Δ -0.36 · `h214/elec | cavity wall | (another dwelling` - [x] `100062132384` · L 81 / E 80.64 / Δ -0.36 · `h214/elec | cavity wall | (another dwelling` - [x] `100031679582` · L 66 / E 66.36 / Δ +0.36 · `hpcdb10243/gas | cavity wall | pitched insulated` @@ -952,6 +929,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `10008275334` · L 76 / E 76.31 / Δ +0.31 · `h691/elec | system built | (another dwelling | whw903` - [x] `100110174472` · L 74 / E 74.31 / Δ +0.31 · `hpcdb8368/gas | solid brick | pitched 175 | ext2,mv5` - [x] `100071450760` · L 55 / E 54.69 / Δ -0.31 · `h691/elec | cavity wall | (another dwelling | whw903` +- [x] `line255` · L 72 / E 72.31 / Δ +0.31 · `h104/gas | solid brick | pitched 200` - [x] `207096040` · L 69 / E 69.31 / Δ +0.31 · `hpcdb18219/gas | solid brick | pitched insulated | ext1` - [x] `200004695006` · L 69 / E 69.31 / Δ +0.31 · `hpcdb10243/gas | sandstone as | pitched insulated` - [x] `100100800577` · L 64 / E 64.31 / Δ +0.31 · `h301/coal-anth | system built | pitched 200 | whw903` @@ -1037,6 +1015,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `50011586` · L 69 / E 68.74 / Δ -0.26 · `hpcdb10328/gas | cavity wall | pitched 225 | consv2` - [x] `100010680786` · L 73 / E 73.26 / Δ +0.26 · `hpcdb18907/gas | granite or | pitched 400+ | ext1` - [x] `422000127372` · L 79 / E 78.75 / Δ -0.25 · `hpcdb17741/gas | cavity wall | pitched 400+` +- [x] `100050477371` · L 65 / E 64.75 / Δ -0.25 · `hpcdb16406/gas | solid brick | pitched insulated | RR,consv2,ext2,sec:room heaters` - [x] `200000339841` · L 72 / E 72.25 / Δ +0.25 · `hpcdb18736/gas | cavity wall | (another dwelling` - [x] `72380835` · L 82 / E 82.25 / Δ +0.25 · `hpcdb17507/gas | cavity wall | pitched 200 | PV,sec:room heaters` - [x] `38193503` · L 65 / E 65.25 / Δ +0.25 · `hpcdb18907/gas | cavity wall | pitched 100 | ext2,sec:room heaters` @@ -1053,6 +1032,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `100060721835` · L 68 / E 67.76 / Δ -0.24 · `hpcdb16841/gas | system built | pitched 250` - [x] `100011764055` · L 72 / E 71.76 / Δ -0.24 · `hpcdb15823/gas | cavity wall | pitched 200 | ext4,sec:room heaters` - [x] `5067734` · L 67 / E 67.24 / Δ +0.24 · `hpcdb17726/gas | solid brick | (another dwelling` +- [x] `38105600` · L 68 / E 67.76 / Δ -0.24 · `h104/gas | solid brick | pitched unknown | ext2` - [x] `100021942272` · L 69 / E 69.24 / Δ +0.24 · `hpcdb17501/gas | solid brick | pitched insulated | RR` - [x] `100050587694` · L 70 / E 70.24 / Δ +0.24 · `hpcdb18516/gas | cavity wall | pitched 270` - [x] `100062195466` · L 73 / E 72.76 / Δ -0.24 · `h104/gas | cavity wall | (another dwelling` @@ -1095,6 +1075,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `100050841010` · L 69 / E 69.21 / Δ +0.21 · `hpcdb10546/gas | cavity wall | pitched 100 | ext1` - [x] `10090481366` · L 77 / E 76.79 / Δ -0.21 · `h691/elec | cavity wall | (another dwelling | whw909` - [x] `100070308464` · L 65 / E 65.21 / Δ +0.21 · `h104/gas | cavity wall | pitched 250 | PV` +- [x] `100031768368` · L 61 / E 61.21 / Δ +0.21 · `hpcdb18250/gas | solid brick | pitched 100 | consv2,sec:room heaters` <- FIXED: RdSAP 10 Table 13 200/280mm solid-brick boundary bug (was 59.12) — Elmhurst-validated, see test_rdsap_uvalues.py - [x] `100012547948` · L 75 / E 74.79 / Δ -0.21 · `hpcdb17931/gas | cavity wall | pitched 100` - [x] `100061316828` · L 69 / E 69.21 / Δ +0.21 · `hpcdb18403/gas | cavity wall | pitched 250 | mv2` - [x] `83067520` · L 65 / E 64.79 / Δ -0.21 · `hpcdb18119/gas | cavity wall | pitched 200` @@ -1112,6 +1093,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `100060370575` · L 70 / E 69.80 / Δ -0.20 · `hpcdb17760/gas | cavity wall | (another dwelling` - [x] `10022972987` · L 11 / E 11.20 / Δ +0.20 · `h691/elec | sandstone as | pitched 200 | ext1,sec:room heaters,whw921` - [x] `10023219663` · L 78 / E 77.80 / Δ -0.20 · `h301/coal-anth | system built | (another dwelling` +- [x] `100021326422` · L 70 / E 69.80 / Δ -0.20 · `hpcdb17679/gas | solid brick | pitched 125 | ext1` <- non-electric fabric/demand: per-cert lodged-geometry noise (field_bias scan clean, no coherent subgroup) - [x] `100020603440` · L 74 / E 73.80 / Δ -0.20 · `h104/gas | cavity wall | pitched insulated` - [x] `45040195` · L 75 / E 75.19 / Δ +0.19 · `hpcdb18250/gas | cavity wall | pitched 100` - [x] `653958` · L 79 / E 78.81 / Δ -0.19 · `hpcdb10071/gas | cavity wall | pitched 250` @@ -1180,6 +1162,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `10033267429` · L 57 / E 56.85 / Δ -0.15 · `h691/elec | system built | (another dwelling | whw903` - [x] `100090372586` · L 71 / E 71.15 / Δ +0.15 · `h102/gas | cavity wall | pitched 300 | PV,consv3` - [x] `100060587363` · L 62 / E 62.15 / Δ +0.15 · `hpcdb10462/gas | solid brick | pitched 250 | consv2,ext2,sec:room heaters` +- [x] `34080035` · L 77 / E 76.85 / Δ -0.15 · `hpcdb18218/gas | solid brick | (another dwelling` - [x] `207037318` · L 59 / E 58.85 / Δ -0.15 · `h699/fuel0 | cavity wall | (another dwelling | sec:portable electric,whw903` - [x] `100051048523` · L 67 / E 67.15 / Δ +0.15 · `hpcdb17507/gas | cavity wall | pitched 250 | ext1,sec:room heaters` - [x] `551000` · L 72 / E 72.15 / Δ +0.15 · `h104/gas | cavity wall | pitched 150` @@ -1196,6 +1179,7 @@ Statuses: `[ ]` todo · `[x]` resolved ≤0.5 · 🔧 fix landed · ⚠ xfail en - [x] `100080902546` · L 79 / E 78.85 / Δ -0.15 · `hpcdb17696/gas | solid brick | pitched insulated | RR,ext1` - [x] `100021286876` · L 66 / E 66.15 / Δ +0.15 · `hpcdb17503/gas | solid brick | pitched insulated` - [x] `100070056707` · L 72 / E 72.14 / Δ +0.14 · `hpcdb17505/gas | system built | (another dwelling` +- [x] `200000571972` · L 64 / E 63.86 / Δ -0.14 · `hpcdb17986/gas | solid brick | pitched 250 | ext1` - [x] `72513535` · L 70 / E 70.14 / Δ +0.14 · `h691/elec | system built | (another dwelling | whw903` - [x] `100080166923` · L 68 / E 67.86 / Δ -0.14 · `hpcdb18826/gas | cavity wall | pitched 75 | consv3,ext1` - [x] `100023483089` · L 77 / E 76.86 / Δ -0.14 · `h409/elec | system built | (another dwelling | sec:portable electric,whw903` diff --git a/scripts/hyde/build_100031768368.py b/scripts/hyde/build_100031768368.py new file mode 100644 index 000000000..867974856 --- /dev/null +++ b/scripts/hyde/build_100031768368.py @@ -0,0 +1,252 @@ +"""Elmhurst build for UPRN 100031768368 (RdSAP-21.0.1, END-TERRACE HOUSE, band C, +SOLID BRICK 280 mm uninsulated, pitched 100 mm loft insulation, SOLID ground +floor uninsulated, mains-gas COMBI (PCDB 18250, Baxi 630 Combi 88.4%) + +gas ROOM-HEATER SECONDARY, single electric shower with WWHRS (no mixer +shower), extract fan lodged (1) + 1 other-heater flue, 10 DG windows across +2 orientations (~13.48 m2 total: East 5.52 m2, West 5.13+2.83 m2 across two +glazing eras), TFA 76. Lodged SAP 61; engine 59.12 (Delta -1.88). + +Per-cert Elmhurst validation, continuing the 1-2 SAP-band reconciliation +pass. Uses set_window_groups (3 orientation/glazing groups, not a single +collapsed window — see the 100061275133 finding that a single-orientation +simplification meaningfully understates solar gain) + reset_transient_state +at the end so the shared assessment is left neutral. Run: + DISPLAY=:99 python scripts/hyde/build_100031768368.py +""" +from __future__ import annotations +import sys +from pathlib import Path +import elmhurst_lib as E + +DIM = "TabContainer_TabPanelMain_WebUserControlDimensionsMain_" +WALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_" + "TabPanelExternalWallMain_WebUserControlWallMain_") +PWALL = ("TabContainer_TabPanelMain_InnerTabContainerMain_" + "TabPanelPartyWallMain_WebUserControlPartyWallMain_") +ROOF = "TabContainer_TabPanelMain_WebUserControlRoofMain_" +FLOOR = "TabContainer_TabPanelMain_WebUserControlFloorsMain_" +DP = "TabContainer_TabPanelDoorsPanel_" +VP = "TabContainer_TabPanelVentilationPanel_" +APT = "TabContainer_TabPanelAirPressureTest_" +LP = "TabContainer_TabPanelLighting_" +MV = "TabContainer_TabPanelMechVent_" +WH = "TabContainer_TabPanelWaterHeating_" +MH1B = "TabContainer_TabPanelMainHeating1_WebUserControlMainHeating1_" +_SAMPLE_DIR = (Path(__file__).parent.parent.parent + / "backend/epc_api/json_samples/real_life_examples" + / "RdSAP-Schema-21.0.1/uprn_100031768368") + + +def _pick(page, suffix, contains): + val = page.evaluate( + """(a)=>{const s=document.getElementById(a[0]);if(!s)return null; + for(const o of s.options){if(o.text.toLowerCase().includes(a[1].toLowerCase()))return o.value;}return null;}""", + [f"{E.FP}{suffix}", contains]) + if val is not None: + E.set_select(page, suffix, val) + return val + + +def property_description(page): + E.goto(page, "PropertyDescription", "WebFormPropertyDescription.aspx") + E.set_select(page, "DropDownListPropertyType1", "H House") + _pick(page, "DropDownListPropertyType2", "end") + E.set_text(page, "TextBoxStoreys", "2") + E.set_text(page, "TextBoxHabitableRooms", "4") + E.set_text(page, "TextBoxHeatedHabitableRooms", "4") + print("date ->", _pick(page, "DropDownListDateBuiltMain", "1930-1949")) + E.set_select(page, "DropDownListDateBuiltFirst", "") + E.set_select(page, "DropDownListRoomInRoofMain", "") + E.save_close(page) + + +def dimensions(page): + E.goto(page, "Dimensions", "WebFormDimensions.aspx") + E.set_text(page, f"{DIM}TextBoxFloorAreaLowestFloor", "37.75") + E.set_text(page, f"{DIM}TextBoxRoomHeightLowestFloor", "2.45") + E.set_text(page, f"{DIM}TextBoxWallPerimeterLowestFloor", "20.1") + E.set_text(page, f"{DIM}TextBoxPartyWallLengthLowestFloor", "5.0") + E.set_text(page, f"{DIM}TextBoxFloorArea1stFloor", "37.75") + E.set_text(page, f"{DIM}TextBoxRoomHeight1stFloor", "2.45") + E.set_text(page, f"{DIM}TextBoxWallPerimeter1stFloor", "20.1") + E.set_text(page, f"{DIM}TextBoxPartyWallLength1stFloor", "5.0") + E.save_close(page) + + +def walls(page): + E.goto(page, "Walls", "WebFormWalls.aspx") + _pick(page, f"{WALL}DropDownListType", "solid brick") + page.wait_for_timeout(500) + print("insulation ->", _pick(page, f"{WALL}DropDownListInsulation", "as built")) + print("party ->", _pick(page, f"{PWALL}DropDownListPartyWallType", "determine") + or _pick(page, f"{PWALL}DropDownListPartyWallType", "masonry")) + for suf in (f"{WALL}TextBoxThickness", f"{WALL}TextBoxWallThickness"): + if page.locator(f"#{E.FP}{suf}").count(): + E.set_text(page, suf, "280"); print("wall thickness set 280 via", suf); break + E.save_close(page) + + +def roofs(page): + E.goto(page, "Roofs", "WebFormRoofs.aspx") + _pick(page, f"{ROOF}DropDownListType", "access to loft") or \ + _pick(page, f"{ROOF}DropDownListType", "loft") + _pick(page, f"{ROOF}DropDownListInsulationAt", "joists") + _pick(page, f"{ROOF}DropDownListThickness", "100") + E.save_close(page) + + +def floors(page): + E.goto(page, "Floors", "WebFormFloors.aspx") + _pick(page, f"{FLOOR}DropDownListLocation", "ground floor") + page.wait_for_timeout(400) + _pick(page, f"{FLOOR}DropDownListType", "solid") + ins = page.locator(f"#{E.FP}{FLOOR}DropDownListInsulation") + if ins.count(): + E.set_select(page, f"{FLOOR}DropDownListInsulation", "A As built") + E.save_close(page) + + +def openings(page): + E.goto(page, "Openings", "WebFormOpenings.aspx") + E.click_tab(page, "TabContainer_TabPanelWindowsPanel") + page.wait_for_timeout(500) + # 3 orientation/glazing-era groups (not a single collapsed window): + # East 5.52 m2 (double pre-2002), West 5.13 m2 (double pre-2002), West + # 2.83 m2 (double 2002-2022, the one larger newer window index 8). + # The glazing dropdown retains its last-selected value across adds; if + # the next row wants the SAME text, selecting it again fires no 'change' + # event (identical value -> no transition) and the row commits with a + # blank Glazing Type. Force a transition through a different option + # first so every add gets a genuine change event. + for area, orient, glazing in ( + (5.5174, "East", "Double pre 2002"), + (5.1346, "West", "Double pre 2002"), + (2.8288, "West", "Double between 2002 and 2021"), + ): + E.set_select(page, f"{E._WP}DropDownListExtGlazing", "Secondary glazing") # noqa: SLF001 + page.wait_for_timeout(300) + E.add_combined_window(page, area, orient, glazing) + page.wait_for_timeout(500) + guard = 0 + while E.window_row_count(page) > 3 and guard < 25: + guard += 1 + i = E.window_row_count(page) - 1 + if not E._delete_window_at_index(page, i): # noqa: SLF001 + break + E.click_tab(page, "TabContainer_TabPanelDoorsPanel") + E.set_text(page, f"{DP}TextBoxDoors", "1") + E.set_text(page, f"{DP}TextBoxDoorsInsulated", "0") + E.set_text(page, f"{DP}TextBoxDraughtProofedDoors", "1") # cert: percent_draughtproofed=100 + E.save_close(page) + + +def ventilation(page): + E.goto(page, "VentilationAndCooling", "WebFormVentilationAndCooling.aspx") + E.click_tab(page, "TabContainer_TabPanelVentilationPanel") + E.set_text(page, f"{VP}TextBoxIntermittentFans", "1") # cert: extract_fans_count=1 + E.set_text(page, f"{VP}TextBoxFluesOtherHeater", "1") # cert: other_flues_count=1 + cool = page.locator(f"#{E.FP}{VP}CheckBoxFixedSpaceCooling") + if cool.count() and cool.is_checked(): + E.commit(page, cool.uncheck) + E.click_tab(page, "TabContainer_TabPanelMechVent") + mv = page.locator(f"#{E.FP}{MV}CheckBoxMechanicalVentilation") + if mv.count() and mv.is_checked(): + E.commit(page, mv.uncheck) + E.click_tab(page, "TabContainer_TabPanelAirPressureTest") + E.set_select(page, f"{APT}DropDownListTestMethod", "Not available") + E.click_tab(page, "TabContainer_TabPanelLighting") + E.set_text(page, f"{LP}TextBoxLightsTotal", "13") + E.set_text(page, f"{LP}TextBoxLedLightsTotal", "10") + E.set_text(page, f"{LP}TextBoxCflLightsTotal", "2") + E.save_close(page) + + +def space_heating(page): + # PCDB exact-boiler entry: cert's main_heating_index_number 18250 + # resolves to Baxi 630 Combi, 88.4% winter efficiency. + E.goto(page, "SpaceHeating", "WebFormSpaceHeating.aspx") + page.wait_for_timeout(1000) + E.clear_main_heating_code(page) + desc = E.set_pcdb_boiler(page, 18250) + print("PCDB boiler ->", desc) + E.set_heating_dialog(page, f"{MH1B}ButtonMainHeatingControls", + "^Boilers", "^Standard", "CBE Programmer, room thermostat and TRVs") + print("control:", page.locator(f"#{E.MH1}TextBoxMainHeatingControls").input_value()) + # cert lodges secondary_heating_type=605 ("Room heaters, mains gas", + # SAP10 Table 4a eff 0.40) — a genuine secondary system, unlike + # 100061275133's "None". The dialog is a 4-level cascade (fuel -> + # sub-fuel -> heater class -> specific product), not 3 like main + # heating controls. Probed all 12 RGx options by resolved SAP code; + # "RGH Flush fitting live effect gas fire, sealed to chimney" is the + # exact match for code 605. + E.set_select(page, "DropDownListSecondaryHeatingPresent", "Yes") + page.wait_for_timeout(900) + E.set_heating_dialog(page, "ButtonSecondaryHeatingCode", + "^Gas", "Mains gas", "Room Heaters", "RGH") + tb = page.locator(f"#{E.FP}TextBoxSecondaryHeatingCode") + print("secondary code:", tb.input_value() if tb.count() else "?") + E.save_close(page) + + +def water_heating(page): + E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx") + E.click_tab(page, "TabContainer_TabPanelWaterHeating") + page.wait_for_timeout(400) + E.clear_hot_water_cylinder(page) + E.set_heating_dialog(page, f"{WH}ButtonWaterHeatingCode", + "From Space Heating", "From the primary heating system") + print("water code:", page.locator(f"#{E.FP}{WH}TextBoxWaterHeatingCode").input_value()) + E.save_close(page) + + +def baths_showers(page): + """cert: number_baths=1, shower_outlets=[electric shower, WWHRS=1], + mixer_shower_count=0 — an instantaneous ELECTRIC shower with WWHRS + fitted, not a mixer shower off the main system.""" + E.goto(page, "WaterHeating", "WebFormWaterHeating.aspx") + E.click_tab(page, "TabContainer_TabPanelBathsShowers") + page.wait_for_timeout(400) + print("(manual check needed: set 1 bath, 1 electric shower with WWHRS)") + + +def download(page): + _SAMPLE_DIR.mkdir(parents=True, exist_ok=True) + E.goto(page, "Recommendations", "WebFormRecommendations.aspx") + page.wait_for_timeout(1500) + with page.expect_navigation(wait_until="networkidle", timeout=40000): + page.click("#ContentBody_buttonActionSummary_Link", timeout=15000) + page.wait_for_timeout(2000) + for btn, fname in (("ContentBody_ContentPlaceHolder1_LinkButtonSummary", "elmhurst_summary.pdf"), + ("ContentBody_ContentPlaceHolder1_ButtonDebugInforPdf", "elmhurst_worksheet.pdf")): + try: + with page.expect_download(timeout=60000) as dl: + page.click(f"#{btn}", timeout=15000) + dl.value.save_as(str(_SAMPLE_DIR / fname)) + print("saved", fname) + except Exception as e: + print("!! download failed", fname, type(e).__name__, str(e)[:120]) + + +def cleanup(page): + """Leave the shared assessment neutral for the next build.""" + E.reset_transient_state(page) + print("reset transient state (RR/secondary/meter)") + + +_ORDER = ["property_description", "dimensions", "walls", "roofs", "floors", + "openings", "ventilation", "space_heating", "baths_showers", + "water_heating", "download", "cleanup"] + + +def main(): + if len(sys.argv) < 2 or sys.argv[1] not in _ORDER: + print("usage: build_100031768368.py <" + "|".join(_ORDER) + ">") + return 2 + with E.session() as (ctx, page): + globals()[sys.argv[1]](page) + print("done:", sys.argv[1], "->", page.url) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/hyde/build_217091901.py b/scripts/hyde/build_217091901.py index f487d973c..b803b3ba6 100644 --- a/scripts/hyde/build_217091901.py +++ b/scripts/hyde/build_217091901.py @@ -85,6 +85,15 @@ def walls(page): print("insulation opts:", _options(page, f"{WALL}DropDownListInsulation")) _pick(page, f"{WALL}DropDownListInsulation", "as built") or \ _pick(page, f"{WALL}DropDownListInsulation", "none") + # cert lodges wall_thickness=280mm measured=Y — this field was never + # set here, so the shared assessment silently carried over whatever + # thickness an earlier build had left (260mm), masking the real + # RdSAP 10 Table 13 200/280mm boundary bug on this cert's exact + # thickness. See domain/sap10_ml/rdsap_uvalues.py + # _u_brick_thin_wall_age_a_to_e's docstring. + for suf in (f"{WALL}TextBoxThickness", f"{WALL}TextBoxWallThickness"): + if page.locator(f"#{E.FP}{suf}").count(): + E.set_text(page, suf, "280"); print("wall thickness set 280 via", suf); break E.save_close(page) diff --git a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py index 0bc3a95a7..d2c0cefa6 100644 --- a/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py +++ b/tests/domain/sap10_calculator/test_real_cert_sap_accuracy.py @@ -846,22 +846,35 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( # TOP-FLOOR FLAT, band A (before 1900), SOLID BRICK 280 mm uninsulated, # FLAT ROOF no insulation (As Built), floor over ANOTHER DWELLING below # (zero loss), mains-gas COMBI, SINGLE glazing (glazed_type 5), TFA 63. - # Lodged 62 / engine 61 (60.82). Built in accredited Elmhurst RdSAP10 - # (evidence saved: elmhurst_summary.pdf / elmhurst_worksheet.pdf): EVERY - # heat-loss element matches the engine EXACTLY — walls (solid U 1.70, - # 53.18 W/K), flat roof (uninsulated U 2.30, 145.57 W/K), floor (0), - # party (0), and — with the faithful SINGLE glazing entered — WINDOWS - # (8.62 m² × effective U 4.0268 = 34.7114 W/K, identical to the engine). - # This confirms the mapper''s glazed_type 5 → single (raw U 4.8) is - # spec-correct (epc_codes.csv: glazed_type 5 = single glazing), NOT the - # double it superficially resembles. The −1.18 vs lodged is a cost-side - # methodology residual (PE +8.7 kWh/m², CO2/PE-driven), not a fabric bug. - # PINNED to the observed engine 61. + # Lodged 62 / engine 62 (61.59, was 60.82 before the §5.7 Table 13 + # 280mm-boundary fix below) — now an EXACT match. Built in accredited + # Elmhurst RdSAP10 (evidence saved: elmhurst_summary.pdf / + # elmhurst_worksheet.pdf): flat roof (uninsulated U 2.30, 145.57 W/K), + # floor (0), party (0), and — with the faithful SINGLE glazing entered — + # WINDOWS (8.62 m² × effective U 4.0268 = 34.7114 W/K, identical to the + # engine) all match EXACTLY. This confirms the mapper''s glazed_type 5 + # → single (raw U 4.8) is spec-correct (epc_codes.csv: glazed_type 5 = + # single glazing), NOT the double it superficially resembles. + # + # Wall U originally logged as "1.70, matches engine exactly" — but that + # was a FALSE match: this cert's build script (build_217091901.py) + # never actually set a wall thickness field, so the shared assessment + # silently carried over a stale 260mm from an earlier build instead of + # this cert's real 280mm (measured=Y). Rebuilt with the corrected + # 280mm entry: Elmhurst's own worksheet (29a) NOW gives wall U=1.40, + # not 1.70 — see the sibling fix on cert 100061275133/100031768368's + # investigation, which found the SAME 280mm-exactly boundary bug in + # `_u_brick_thin_wall_age_a_to_e` (RdSAP 10 Table 13's 200/280mm rows + # share an unlabelled edge; Elmhurst puts it in the upper "280-420mm + # -> 1.4" row, our `<= 280` check put it in the lower row). Fixed; + # both this cert (band A) and 100031768368 (band C) now agree on + # U=1.40 at exactly 280mm — two independent Elmhurst confirmations. + # PINNED to the observed exact match (vs lodged, the correct target). RealCertExpectation( schema="RdSAP-Schema-21.0.1", sample="uprn_217091901", cert_num="uprn-217091901", - sap_score=61, + sap_score=62, ), # UPRN 100061275133 (corpus; no cert number lodged). RdSAP-21.0.1 native — # 2-storey SEMI-DETACHED HOUSE, band B, SOLID BRICK 400 mm with EXTERNAL @@ -1006,6 +1019,47 @@ _EXPECTATIONS: Final[tuple[RealCertExpectation, ...]] = ( cert_num="uprn-10091578598", sap_score=79, ), + # UPRN 100031768368 (corpus; no cert number lodged). RdSAP-21.0.1 native — + # END-TERRACE HOUSE, band C (1930-1949), SOLID BRICK 280 mm uninsulated + # (wall_thickness_measured=Y), pitched 100 mm loft insulation, SOLID + # ground floor uninsulated, mains-gas COMBI (PCDB 18250, Baxi 630 Combi + # 88.4%) + gas ROOM-HEATER SECONDARY (code 605), electric shower with + # WWHRS (no mixer shower — mixer_shower_count genuinely 0, corpus- + # checked: no systematic bias on the 170-cert "bath+no shower data" + # cohort), 10 windows across 2 real orientations (East 5.52 m²/West + # 8.0 m² split further by glazing era), TFA 75.5. Lodged 61 / engine + # 61 (61.21, was 59.12 before the fix below) — now within 0.5. + # + # Built in accredited Elmhurst RdSAP10 (evidence saved: + # elmhurst_summary.pdf / elmhurst_worksheet.pdf): dwelling volume + # (194.4125 m³), ventilation ACH (21)=0.7229, ground floor W/K + # (27.5575), and doors W/K (5.55) all matched EXACTLY on the first + # build. The wall did not: engine U=1.70 (149.92 W/K) vs Elmhurst + # U=1.40 (123.47 W/K) despite identical inputs (construction, "As + # Built" insulation, 280mm thickness, age band all confirmed matching + # via the input-summary crosswalk). + # + # Root cause: RdSAP 10 §5.7 Table 13 (spec PDF p.41) gives four + # thickness bands for an uninsulated solid-brick wall — "up to 200mm" + # -> 2.5, "200 to 280mm" -> 1.7, "280 to 420mm" -> 1.4, "more than + # 420mm" -> 1.1 — and this cert's wall sits EXACTLY on the shared + # 200/280 edge, which the table text doesn't disambiguate. + # `_u_brick_thin_wall_age_a_to_e`'s `<= 280` check put the edge in the + # lower (1.7) row; Elmhurst puts it in the upper (1.4) row. Verified + # independently on a SECOND cert (217091901, band A, also exactly + # 280mm) after discovering and fixing its build script's own bug + # (never actually set a wall thickness, silently inheriting a stale + # 260mm from an earlier build) — rebuilt with the corrected 280mm + # entry, Elmhurst's worksheet gives the SAME U=1.40. Fixed in + # `domain/sap10_ml/rdsap_uvalues.py`; corpus gauge 77.8%->78.6% + # within-0.5, MAE 0.636->0.627 (14 corpus certs lodge exactly 280mm + # solid brick). PINNED to the observed match vs lodged. + RealCertExpectation( + schema="RdSAP-Schema-21.0.1", + sample="uprn_100031768368", + cert_num="uprn-100031768368", + sap_score=61, + ), ) diff --git a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py index ff14c1fa7..5271c2434 100644 --- a/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py +++ b/tests/infrastructure/epc_client/test_sap_accuracy_corpus.py @@ -239,7 +239,9 @@ _CORPUS = Path( # 2.97, CO2 0.074 -> 0.072. # 77.7% -> 77.8% via the §5 (12) suspended-timber floor-infiltration # fallback (see the `_MAX_SAP_MAE` note below for the mechanism). -_MIN_WITHIN_HALF_SAP = 0.778 +# 77.8% -> 78.6% via the RdSAP 10 §5.7 Table 13 200/280mm solid-brick-wall +# boundary fix (see the `_MAX_SAP_MAE` note below). +_MIN_WITHIN_HALF_SAP = 0.786 # 0.793 -> 0.789 via the §12 Unknown-meter + dual-electric-immersion off-peak # trigger (RdSAP 10 PDF p.62): Apartment 241 (main 691 + 903 dual immersion) # -5.38 -> -1.05. Worksheet-validated on "simulated case 48" (Elmhurst SAP 57, @@ -334,7 +336,19 @@ _MIN_WITHIN_HALF_SAP = 0.778 # description for the U-value calc — the infiltration rule did not). # Cert 100061275133 (Elmhurst-validated build): SAP 77.24 -> 76.33, now # an EXACT match to lodged (76). Unit-pinned in test_cert_to_inputs. -_MAX_SAP_MAE = 0.637 +# 77.8% -> 78.6% (MAE 0.636 -> 0.627) via the RdSAP 10 §5.7 Table 13 +# 200/280mm solid-brick-wall boundary fix: the spec table's "200 to +# 280mm -> 1.7" / "280 to 420mm -> 1.4" rows share an unlabelled edge at +# exactly 280mm; `_u_brick_thin_wall_age_a_to_e`'s `<= 280` check put it +# in the lower row. Elmhurst-validated on TWO certs at exactly 280mm in +# different age bands (100031768368 band C, 217091901 band A after +# fixing that cert's build script — it never set a wall thickness field +# and had silently inherited a stale value from an earlier build) — both +# independently confirm the shared edge belongs to the upper (1.4) row. +# 100031768368 SAP 59.12 -> 61.21 (lodged 61, now within 0.5); 217091901 +# 60.82 -> 61.59 (lodged 62, now an exact match). 14 corpus certs lodge +# exactly 280mm solid brick. Unit-pinned in test_rdsap_uvalues. +_MAX_SAP_MAE = 0.628 _MAX_CO2_MAE_TONNES = 0.072 # t CO2 / yr vs co2_emissions_current _MAX_PE_PER_M2_MAE = 3.0 # kWh / m2 / yr vs energy_consumption_current From 0f091e3ab1b0ed511b84f7155d913fc670a045e6 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:04:21 +0000 Subject: [PATCH 02/23] =?UTF-8?q?Record=20ADR-0052:=20electric=20boiler/CP?= =?UTF-8?q?SU=20overrides=20drag=20category=202=20+=20conservative=20Group?= =?UTF-8?q?=201=20control=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1444 Parts A+B for codes 191/192, diagnosed on portfolio 796: 30 of the first 40 live overrides inherit a replaced system's category (6/4/7) — suppressing HHRSH or billing direct-acting heat as storage. Co-Authored-By: Claude Opus 4.8 --- ...gory-2-and-conservative-group-1-control.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/adr/0052-electric-boiler-and-cpsu-overrides-drag-category-2-and-conservative-group-1-control.md diff --git a/docs/adr/0052-electric-boiler-and-cpsu-overrides-drag-category-2-and-conservative-group-1-control.md b/docs/adr/0052-electric-boiler-and-cpsu-overrides-drag-category-2-and-conservative-group-1-control.md new file mode 100644 index 000000000..170e40be5 --- /dev/null +++ b/docs/adr/0052-electric-boiler-and-cpsu-overrides-drag-category-2-and-conservative-group-1-control.md @@ -0,0 +1,88 @@ +# Electric boiler and CPSU overrides drag category 2 and the conservative Group 1 control + +## Status + +accepted + +## Context + +[ADR-0048](0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md) +requires a system-replacing heating override to emit a complete companion set +(category and control archetype-forced, never inherited) and deferred the +electric boiler / CPSU expansion; [ADR-0050](0050-electric-underfloor-override-drags-category-8-and-conservative-group-7-control.md) +completed the underfloor half. This ADR completes the electric-boiler half +(#1444 Parts A+B for codes **191/192** — the archetypes "Direct-acting +electric", "Electric boiler", "Electric CPSU"). + +A portfolio-796 scan of the 126 live 191/192 overrides found **30 of the first +40 in a suppressed or mis-billed state** from inherited companions: + +- **Inherited category 6** (replaced community heating — the bulk): the heating + generator's `is_heat_network_main` gate reads the dwelling as a heat network, + so the **HHRSH bundle is never offered** — only tune-ups. Same class as + ADR-0050's evidence property. +- **Inherited category 4** (replaced heat pump — property 711954): *every* + heating bundle suppressed. +- **Inherited category 7** (replaced storage heaters — property 721912): the + SAP 10.2 Table 12a resolver's category-7 branch bills the direct-acting + boiler as storage — **100% at the off-peak low rate** — the precise + ADR-0048 room-heater mis-billing, resurfaced. +- Stale controls ride along (community 23xx, storage 2401, room-heater 2603), + applying the wrong Table 4e temperature adjustment. + +## Decision + +The electric-boiler codes (SAP Table 4a **191** direct-acting electric boiler, +**192** electric CPSU) join the forced-companion branches of +`main_heating_system_overlay`: + +1. **Category 2** ("boiler system with radiators or underfloor heating") in + `_category_for` — deterministic from SAP Table 4a; the same category the + gas-boiler overlay already stamps. Table 12a billing for these codes is + code-keyed (191 → `DIRECT_ACTING_ELECTRIC_BOILER`, 192 → the deliberate + Appendix-F fallback), so the category change *removes* stale-category + misrouting rather than selecting a new billing row. +2. **Control 2101** (SAP Table 4e **Group 1**, "no time or thermostatic control + of room temperature", **+0.6 °C**) in `_control_for` — the conservative + default within the group the category fixes: the largest Group 1 + mean-internal-temperature adjustment, so an unobserved control is never + over-credited (the boiler mirror of storage 2401 / room-heater 2601 / + underfloor 2701). This **supersedes the earlier "direct-acting electric + drags no control" choice** — code 191 is a wet Group-1 boiler, and leaving + the control unset inherits the replaced system's (the ADR-0048 bug class), + which is strictly worse than a conservative assumption. + + Deliberate asymmetry with gas boilers: the gas archetypes force **2106** + (full modern controls) because they also assume a modern condensing install + (ADR-0035, decided with Khalim) — the whole system is modelled modern. The + electric-boiler archetypes describe an *existing* system of unknown vintage + modelled at code 191/192's intrinsic efficiency, so the conservative + default applies. + +Fuel (electricity 29, ADR-0045) and meter (192 off-peak Dual via §12; 191 +Single) were already correct and are unchanged. A forced 2101 also makes the +system tune-up options available (2101 is improvable) — a coherent cheap lever +for these dwellings. + +## Consequences + +- The suppressed cohort regains its heating pathway: category 6/4 inheritors + become HHRSH-eligible (verified live: 711954 gains the HHRSH option), and + category-7 inheritors stop billing direct-acting heat at the all-night low + rate (721912). +- The "incomplete companion set" error log no longer fires for + Direct-acting electric / Electric boiler / Electric CPSU. Remaining gap-log + archetypes (#1444): heat pumps (Group 2 control), community heating + (Group 3 control — interacts with the Table 4c(3) charging factor, needs an + accredited worksheet), then the log flips to a raise (Part C). +- Overrides re-model with a different (usually lower) baseline where a stale + category was over-crediting — an accuracy correction, not a regression. + +### Alternatives rejected + +- **Keep "no control" for direct-acting electric.** Rejected: `None` is not + "no control assumed" — by last-wins composition it means *inherit the + replaced system's control*, the silent mis-rating ADR-0048 exists to stop. +- **Force modern controls (2106) like the gas boilers.** Rejected: nothing in + the electric-boiler archetype implies a modern install; assuming full + controls over-credits every dwelling whose controls were never observed. From 6d8139e47e072faf1b69cb0c73e6d97737061703 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:04:56 +0000 Subject: [PATCH 03/23] =?UTF-8?q?Electric=20boiler=20and=20CPSU=20override?= =?UTF-8?q?s=20stamp=20the=20Table=204a=20boiler=20category=20(2)=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes the "direct-acting electric drags no control/category" pin: None does not mean unset — it means inherit the replaced system's value (ADR-0048 bug class), verified suppressing HHRSH on portfolio 796. Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 5c1b680d2..45ecc9ecf 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -434,14 +434,27 @@ def test_storage_heaters_drag_along_conservative_manual_charge_control( assert simulation.heating.main_heating_category == 7 -def test_direct_acting_electric_does_not_drag_along_a_control() -> None: - # Charge control is a storage concept and full boiler controls are a wet- - # system concept; direct-acting electric panel heaters take neither, so the - # overlay leaves the control untouched. - simulation = main_heating_overlay_for("Direct-acting electric", 0) +@pytest.mark.parametrize( + "main_heating_value", + ["Direct-acting electric", "Electric boiler", "Electric CPSU"], +) +def test_electric_boiler_overlays_the_boiler_category( + main_heating_value: str, +) -> None: + # A system-replacing override must stamp the boiler heating category (SAP + # Table 4a Category 2 — codes 191/192 are wet electric boilers). Leaving it + # unset inherits the replaced system's: on portfolio 796, 30 of the first + # 40 live 191/192 overrides carried an inherited category — 6 (community) + # or 4 (heat pump) suppressed the HHRSH pathway entirely, and 7 (storage) + # made the Table 12a resolver bill direct-acting heat 100% at the off-peak + # low rate (ADR-0052, #1444). + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert — SAP Table 4a Category 2 (boiler system). assert simulation is not None assert simulation.heating is not None - assert simulation.heating.main_heating_control is None + assert simulation.heating.main_heating_category == 2 @pytest.mark.parametrize( From 82cb567a1d7dfe6c8d13ee50195ea1a3f7e8c750 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:06:52 +0000 Subject: [PATCH 04/23] =?UTF-8?q?Electric=20boiler=20and=20CPSU=20override?= =?UTF-8?q?s=20stamp=20the=20Table=204a=20boiler=20category=20(2)=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/property_overlays/main_heating_system_overlay.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index f99a24f5c..295a7e18f 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -130,6 +130,13 @@ _ELECTRIC_ROOM_HEATER_CODES = frozenset(range(691, 702)) # unambiguously electric, so they drag electricity like the electric room heaters # (ADR-0045, closing the prior no-fuel gap on 191/192). _ELECTRIC_BOILER_CODES = frozenset({191, 192}) +# SAP Table 4a "boiler system" category — codes 191/192 are wet electric +# boilers, same category the gas-boiler overlay stamps. Forced so an +# electric-boiler override never keeps the replaced system's category: on +# portfolio 796 an inherited 6 (community) or 4 (heat pump) suppressed the +# HHRSH pathway, and an inherited 7 (storage) made the Table 12a resolver bill +# direct-acting heat wholly at the off-peak low rate (ADR-0052, #1444). +_ELECTRIC_BOILER_CATEGORY = 2 # Electric underfloor — SAP Table 4a 421/422 (off-peak) and 424 (screed) — also # unambiguously electric (ADR-0046). _ELECTRIC_UNDERFLOOR_CODES = frozenset({421, 422, 424}) @@ -304,6 +311,8 @@ def _category_for(code: int) -> Optional[int]: return _STORAGE_HEATER_CATEGORY if code in _ELECTRIC_UNDERFLOOR_CODES: return _UNDERFLOOR_CATEGORY + if code in _ELECTRIC_BOILER_CODES: + return _ELECTRIC_BOILER_CATEGORY if code in _HEAT_PUMP_CODES: return _HEAT_PUMP_CATEGORY if code in _HEAT_NETWORK_CODES: From afa781719d8a4be2f6d94ae184e487e52b7c21c6 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:07:22 +0000 Subject: [PATCH 05/23] =?UTF-8?q?Electric=20boiler=20and=20CPSU=20override?= =?UTF-8?q?s=20drag=20the=20conservative=20Group=201=20control=20(2101)=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 45ecc9ecf..07171cfca 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -457,6 +457,31 @@ def test_electric_boiler_overlays_the_boiler_category( assert simulation.heating.main_heating_category == 2 +@pytest.mark.parametrize( + "main_heating_value", + ["Direct-acting electric", "Electric boiler", "Electric CPSU"], +) +def test_electric_boiler_drags_the_conservative_group_1_control( + main_heating_value: str, +) -> None: + # The landlord names the system, not its control, so the overlay assumes + # the conservative Table 4e Group 1 default: 2101 ("no time or thermostatic + # control", +0.6 C — the largest Group 1 adjustment, so an unobserved + # control is never over-credited; the boiler mirror of storage 2401 / + # room-heater 2601 / underfloor 2701). Supersedes the old "direct-acting + # drags no control" pin: an unset control is not "no control" — it INHERITS + # the replaced system's (a community 2306 or storage 2401 rode along on + # portfolio 796). Deliberately NOT the gas boilers' modern 2106 — nothing + # in the electric archetype implies a modern install (ADR-0052). + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert — SAP Table 4e Group 1 conservative boiler control. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_heating_control == 2101 + + @pytest.mark.parametrize( "main_heating_value", ["Gas boiler, combi", "Gas boiler, regular", "Gas CPSU"] ) From 077dcf6bacf415351b1246b2d31ef334155e44e2 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:08:06 +0000 Subject: [PATCH 06/23] =?UTF-8?q?Electric=20boiler=20and=20CPSU=20override?= =?UTF-8?q?s=20drag=20the=20conservative=20Group=201=20control=20(2101)=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 295a7e18f..2053423b9 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -137,6 +137,15 @@ _ELECTRIC_BOILER_CODES = frozenset({191, 192}) # HHRSH pathway, and an inherited 7 (storage) made the Table 12a resolver bill # direct-acting heat wholly at the off-peak low rate (ADR-0052, #1444). _ELECTRIC_BOILER_CATEGORY = 2 +# SAP Table 4e Group 1 boiler control: 2101 ("no time or thermostatic control +# of room temperature", +0.6 C) is the conservative default when the landlord +# named only the system — the largest Group 1 mean-internal-temperature +# adjustment, so an unobserved control is never over-credited (the boiler +# mirror of storage 2401 / room-heater 2601 / underfloor 2701). Deliberately +# NOT the gas boilers' modern 2106: the gas archetypes assume a whole modern +# condensing install; the electric-boiler archetypes describe an existing +# system of unknown vintage (ADR-0052). +_CONSERVATIVE_BOILER_CONTROL = 2101 # Electric underfloor — SAP Table 4a 421/422 (off-peak) and 424 (screed) — also # unambiguously electric (ADR-0046). _ELECTRIC_UNDERFLOOR_CODES = frozenset({421, 422, 424}) @@ -281,8 +290,9 @@ def _meter_for(code: int) -> Optional[str]: def _control_for(code: int) -> Optional[int]: """The control to assume when the landlord named only the system: a conservative manual charge control for storage heaters, full modern controls - for a gas boiler, None for systems that take neither (direct-acting electric). - Overwrites a stale control inherited from the system being replaced.""" + for a gas boiler, the conservative in-group default for the other mapped + families (room heaters, underfloor, electric boilers). Overwrites a stale + control inherited from the system being replaced.""" if code == _HHR_STORAGE_CODE: return _HHR_CHARGE_CONTROL if code in _STORAGE_HEATER_CODES: @@ -293,6 +303,8 @@ def _control_for(code: int) -> Optional[int]: return _ROOM_HEATER_CONTROL if code in _ELECTRIC_UNDERFLOOR_CODES: return _UNDERFLOOR_CONTROL + if code in _ELECTRIC_BOILER_CODES: + return _CONSERVATIVE_BOILER_CONTROL return None From 7a99c7eccb756a719d6da87fe62b33a46075b277 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:08:30 +0000 Subject: [PATCH 07/23] =?UTF-8?q?Electric=20boiler=20overrides=20no=20long?= =?UTF-8?q?er=20log=20an=20incomplete=20companion=20set=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 07171cfca..b42effd9b 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -457,6 +457,24 @@ def test_electric_boiler_overlays_the_boiler_category( assert simulation.heating.main_heating_category == 2 +@pytest.mark.parametrize( + "main_heating_value", + ["Direct-acting electric", "Electric boiler", "Electric CPSU"], +) +def test_complete_electric_boiler_overlay_does_not_log_a_companion_gap( + main_heating_value: str, caplog: pytest.LogCaptureFixture +) -> None: + # Electric boilers now resolve a full companion set (category 2 + control + # 2101, ADR-0052), so the overlay must NOT log an incomplete-companion gap + # — guarding that the set stays complete. + # Act + with caplog.at_level(logging.ERROR): + main_heating_overlay_for(main_heating_value, 0) + + # Assert + assert "incomplete companion set" not in caplog.text + + @pytest.mark.parametrize( "main_heating_value", ["Direct-acting electric", "Electric boiler", "Electric CPSU"], From 6d414c7b51a18919a627c225e285e017937cf49e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:18:16 +0000 Subject: [PATCH 08/23] =?UTF-8?q?Air-source=20heat-pump=20overrides=20drag?= =?UTF-8?q?=20the=20conservative=20Group=202=20control=20(2201)=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index b42effd9b..795a9defe 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -95,17 +95,34 @@ def test_electric_room_heaters_overlay_the_room_heater_charge_control() -> None: def test_overlay_logs_when_it_cannot_complete_a_companion_set( caplog: pytest.LogCaptureFixture, ) -> None: - # An archetype the overlay cannot yet fully companion — here an air-source - # heat pump, whose Table 4e Group 2 conservative control is not yet mapped — + # An archetype the overlay cannot yet fully companion — here community + # heating, whose Table 4e Group 3 conservative control is not yet mapped — # is still overlaid (log-and-continue), but the incomplete companion set is # logged as an error so the known gap is visible (CloudWatch) rather than # silently inheriting the replaced system's stale value. # Act with caplog.at_level(logging.ERROR): - main_heating_overlay_for("Air source heat pump", 0) + main_heating_overlay_for("Community heating, boilers", 0) # Assert — the gap is reported and names the archetype. - assert "Air source heat pump" in caplog.text + assert "Community heating, boilers" in caplog.text + + +def test_air_source_heat_pump_drags_the_conservative_group_2_control() -> None: + # The landlord names the system, not its control, so the overlay assumes + # the conservative Table 4e Group 2 default: 2201 ("no time or thermostatic + # control", +0.3 C — the largest Group 2 adjustment, so an unobserved + # control is never over-credited; the heat-pump mirror of storage 2401 / + # boiler 2101). Deliberately NOT the ASHP Measure's 2210 zone control — + # a measure designs a modern end-state (ADR-0024); an override describes an + # existing pump (ADR-0053). + # Act + simulation = main_heating_overlay_for("Air source heat pump", 0) + + # Assert — SAP Table 4e Group 2 conservative heat-pump control. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_heating_control == 2201 def test_complete_room_heater_overlay_does_not_log_a_companion_gap( From 6bad3a216a6a92ee2641b2685e4a3304e19a9e17 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:19:20 +0000 Subject: [PATCH 09/23] =?UTF-8?q?Air-source=20heat-pump=20overrides=20drag?= =?UTF-8?q?=20the=20conservative=20Group=202=20control=20(2201)=20?= =?UTF-8?q?=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 2053423b9..af334f41c 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -168,6 +168,17 @@ _UNDERFLOOR_CONTROL = 2701 # without a PCDB index (ADR-0041). _HEAT_PUMP_CATEGORY = 4 _HEAT_PUMP_CODES = frozenset(range(211, 225)) | frozenset(range(521, 528)) +# SAP Table 4e Group 2 heat-pump control: 2201 ("no time or thermostatic +# control of room temperature", +0.3 C) is the conservative default when the +# landlord named only the system — the largest Group 2 adjustment, so an +# unobserved control is never over-credited. Deliberately NOT the ASHP +# Measure's 2210 zone control: a measure designs a modern end-state +# (ADR-0024); an override describes an existing pump (ADR-0053). Scoped to the +# WET heat pumps (211-224) — warm-air pumps (521-527) take Table 4e Group 5 +# and stay unmapped (no archetype maps to them; the incomplete-companion raise +# guards that a future one cannot land without choosing its Group 5 control). +_WET_HEAT_PUMP_CODES = frozenset(range(211, 225)) +_CONSERVATIVE_HEAT_PUMP_CONTROL = 2201 # Community / heat-network heating (SAP Table 4a 301-304) is category 6; the # calculator's `_is_heat_network` keys off code OR category 6. The boiler-driven @@ -305,6 +316,8 @@ def _control_for(code: int) -> Optional[int]: return _UNDERFLOOR_CONTROL if code in _ELECTRIC_BOILER_CODES: return _CONSERVATIVE_BOILER_CONTROL + if code in _WET_HEAT_PUMP_CODES: + return _CONSERVATIVE_HEAT_PUMP_CONTROL return None From 950ec31d84a52717b67602bc1aab83b2d142c0f8 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:19:52 +0000 Subject: [PATCH 10/23] =?UTF-8?q?Community-heating=20overrides=20drag=20th?= =?UTF-8?q?e=20conservative=20Group=203=20control=20(2301)=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 795a9defe..c092c5114 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -108,6 +108,30 @@ def test_overlay_logs_when_it_cannot_complete_a_companion_set( assert "Community heating, boilers" in caplog.text +@pytest.mark.parametrize( + "main_heating_value", + ["Community heating, boilers", "Community heating, CHP and boilers"], +) +def test_community_heating_drags_the_conservative_group_3_control( + main_heating_value: str, +) -> None: + # The landlord names the scheme, not its control or charging arrangement. + # The conservative Table 4e Group 3 default is 2301 ("flat rate charging, + # no thermostatic control of room temperature"): the largest Group 3 + # temperature adjustment (+0.3 C) AND the worst Table 4c(3) charging + # factors (space 1.10, DHW 1.05) — an unobserved control is never + # over-credited and an unobserved charging arrangement is never assumed + # usage-linked (ADR-0053). Leaving it unset inherits the replaced system's + # control instead (the ADR-0048 bug class). + # Act + simulation = main_heating_overlay_for(main_heating_value, 0) + + # Assert — SAP Table 4e Group 3 conservative heat-network control. + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.main_heating_control == 2301 + + def test_air_source_heat_pump_drags_the_conservative_group_2_control() -> None: # The landlord names the system, not its control, so the overlay assumes # the conservative Table 4e Group 2 default: 2201 ("no time or thermostatic From 61f9a63b66fa2f456bb0a1a2b41d55c9451ff1ce Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:21:03 +0000 Subject: [PATCH 11/23] =?UTF-8?q?Community-heating=20overrides=20drag=20th?= =?UTF-8?q?e=20conservative=20Group=203=20control=20(2301)=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last real archetype on the companion-gap log; the log test now simulates a future incomplete archetype (warm-air heat pump, Group 5 control deliberately unmapped). Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 10 ++++++++++ .../epc/test_main_heating_system_overlay.py | 20 ++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index af334f41c..b8c945393 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -188,6 +188,14 @@ _CONSERVATIVE_HEAT_PUMP_CONTROL = 2201 _HEAT_NETWORK_CATEGORY = 6 _HEAT_NETWORK_CODES = frozenset({301, 302, 303, 304}) _COMMUNITY_BOILER_CODES = frozenset({301, 302, 303}) +# SAP Table 4e Group 3 heat-network control: 2301 ("flat rate charging, no +# thermostatic control of room temperature") is the conservative default when +# the landlord named only the scheme. It is never-over-crediting on BOTH axes +# the control drives: the largest Group 3 temperature adjustment (+0.3 C, tied +# with 2302) and the worst Table 4c(3) charging factors (space 1.10, DHW 1.05 +# — an unobserved charging arrangement is never assumed usage-linked). The +# community mirror of storage 2401 / boiler 2101 / heat-pump 2201 (ADR-0053). +_CONSERVATIVE_HEAT_NETWORK_CONTROL = 2301 _COMMUNITY_GAS_FUEL = 20 # SAP Table 4c full boiler-control code: programmer + room thermostat + TRVs. The @@ -318,6 +326,8 @@ def _control_for(code: int) -> Optional[int]: return _CONSERVATIVE_BOILER_CONTROL if code in _WET_HEAT_PUMP_CODES: return _CONSERVATIVE_HEAT_PUMP_CONTROL + if code in _HEAT_NETWORK_CODES: + return _CONSERVATIVE_HEAT_NETWORK_CONTROL return None diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index c092c5114..fb856b75c 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -94,18 +94,24 @@ def test_electric_room_heaters_overlay_the_room_heater_charge_control() -> None: def test_overlay_logs_when_it_cannot_complete_a_companion_set( caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, ) -> None: - # An archetype the overlay cannot yet fully companion — here community - # heating, whose Table 4e Group 3 conservative control is not yet mapped — - # is still overlaid (log-and-continue), but the incomplete companion set is - # logged as an error so the known gap is visible (CloudWatch) rather than - # silently inheriting the replaced system's stale value. + # Every real archetype now resolves a complete companion set (ADR-0053), + # so the incomplete case is simulated: a hypothetical warm-air heat-pump + # archetype (SAP 521 — category 4 but its Table 4e Group 5 control is + # deliberately unmapped). The overlay still applies (log-and-continue), + # but the incomplete companion set is reported rather than silently + # inheriting the replaced system's stale value. + from domain.epc.property_overlays import main_heating_system_overlay as mod + + monkeypatch.setitem(mod._MAIN_HEATING_CODES, "Warm-air heat pump", 521) + # Act with caplog.at_level(logging.ERROR): - main_heating_overlay_for("Community heating, boilers", 0) + main_heating_overlay_for("Warm-air heat pump", 0) # Assert — the gap is reported and names the archetype. - assert "Community heating, boilers" in caplog.text + assert "Warm-air heat pump" in caplog.text @pytest.mark.parametrize( From 1925cdd65d68cc03f7b505da69d81e240ae75fc1 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:21:34 +0000 Subject: [PATCH 12/23] =?UTF-8?q?An=20incomplete=20heating=20companion=20s?= =?UTF-8?q?et=20raises=20instead=20of=20logging=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index fb856b75c..ec9e978aa 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -92,27 +92,24 @@ def test_electric_room_heaters_overlay_the_room_heater_charge_control() -> None: assert simulation.heating.main_heating_control == 2601 -def test_overlay_logs_when_it_cannot_complete_a_companion_set( - caplog: pytest.LogCaptureFixture, +def test_overlay_raises_on_an_incomplete_companion_set( monkeypatch: pytest.MonkeyPatch, ) -> None: - # Every real archetype now resolves a complete companion set (ADR-0053), - # so the incomplete case is simulated: a hypothetical warm-air heat-pump - # archetype (SAP 521 — category 4 but its Table 4e Group 5 control is - # deliberately unmapped). The overlay still applies (log-and-continue), - # but the incomplete companion set is reported rather than silently - # inheriting the replaced system's stale value. + # Every real archetype resolves a complete companion set, so the + # log-and-continue mitigation has no remaining population and the + # ADR-0048 end-state applies: an incomplete set FAILS LOUDLY instead of + # shipping a silently-incoherent cert (ADR-0053, #1444 Part C). Simulated + # with a hypothetical warm-air heat-pump archetype (SAP 521 — category 4 + # but its Table 4e Group 5 control is deliberately unmapped): a future + # archetype cannot land without choosing its companions. from domain.epc.property_overlays import main_heating_system_overlay as mod monkeypatch.setitem(mod._MAIN_HEATING_CODES, "Warm-air heat pump", 521) - # Act - with caplog.at_level(logging.ERROR): + # Act / Assert — the raise names the archetype. + with pytest.raises(ValueError, match="Warm-air heat pump"): main_heating_overlay_for("Warm-air heat pump", 0) - # Assert — the gap is reported and names the archetype. - assert "Warm-air heat pump" in caplog.text - @pytest.mark.parametrize( "main_heating_value", From 6707ab084fd1c38371a052e38b5bae1da39abb7c Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:22:40 +0000 Subject: [PATCH 13/23] =?UTF-8?q?An=20incomplete=20heating=20companion=20s?= =?UTF-8?q?et=20raises=20instead=20of=20logging=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-0048's intended end-state (#1444 Part C): every mapped archetype now resolves confident companions, so an incomplete set can only mean a new archetype landed without choosing them — fail loudly at overlay time. Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index b8c945393..6d25d979c 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -28,7 +28,6 @@ are left UNKNOWN until modelled. Unresolvable values produce no overlay. from __future__ import annotations -import logging from typing import Optional from domain.modelling.simulation import EpcSimulation, HeatingOverlay @@ -36,8 +35,6 @@ from domain.sap10_calculator.tables.table_12a import ( OFF_PEAK_IMPLYING_HEATING_CODES, ) -logger = logging.getLogger(__name__) - # Off-peak (Economy 7) meter. Electric storage / CPSU systems charge overnight at # the low rate and cannot run economically on a single-rate meter; "Dual" lets # the §12 dispatch resolve the specific tariff (storage 7-hour, CPSU 10-hour). @@ -418,21 +415,19 @@ def main_heating_overlay_for( category = _category_for(code) control = _control_for(code) if category is None or control is None: - # A system-replacing override should stamp a complete, coherent companion + # A system-replacing override must stamp a complete, coherent companion # set; an unmapped category or control leaves the field unset, so the - # effective cert inherits the REPLACED system's value (a silent mis-rating - # — e.g. a room heater keeping a storage heater's category/control). We do - # not yet have confident defaults for every archetype, so log-and-continue - # (matching `flag_fuel_mismatch`) to make the gap visible for review - # rather than raise mid-run. - logger.error( - "Landlord main_heating_system %r (SAP code %d) overlaid with an " - "incomplete companion set (category=%s, control=%s); the effective " - "cert may inherit the replaced system's value — known gap, review", - main_heating_value, - code, - category, - control, + # effective cert inherits the REPLACED system's value — the silent + # mis-rating class ADR-0048 exists to stop. Every mapped archetype now + # carries confident defaults (ADR-0050/0052/0053), so an incomplete set + # can only mean a new archetype landed without choosing its companions: + # fail loudly at overlay time rather than ship an incoherent cert + # (ADR-0048's intended end-state, #1444 Part C). + raise ValueError( + f"Landlord main_heating_system {main_heating_value!r} (SAP code " + f"{code}) resolves an incomplete companion set (category=" + f"{category}, control={control}); add the archetype's SAP Table 4a " + f"category and conservative Table 4e control before mapping it" ) return EpcSimulation( heating=HeatingOverlay( From 77110f22604f3009075ad61e052b0e50bfe770c4 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 14:23:44 +0000 Subject: [PATCH 14/23] =?UTF-8?q?Record=20ADR-0053:=20companion=20taxonomy?= =?UTF-8?q?=20complete,=20incomplete=20set=20raises=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...taxonomy-complete-incomplete-set-raises.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md diff --git a/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md b/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md new file mode 100644 index 000000000..152e19224 --- /dev/null +++ b/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md @@ -0,0 +1,89 @@ +# The heating-override companion taxonomy is complete; an incomplete set now raises + +## Status + +accepted + +## Context + +[ADR-0048](0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md) +set the invariant — a system-replacing heating override emits a complete +category + control companion set, never inheriting the replaced system's — and +intended, once the taxonomy was covered, to "flip the log to a raise, so the +coherence invariant is enforced" (#1444 Part C). +[ADR-0050](0050-electric-underfloor-override-drags-category-8-and-conservative-group-7-control.md) +(underfloor) and +[ADR-0052](0052-electric-boiler-and-cpsu-overrides-drag-category-2-and-conservative-group-1-control.md) +(electric boilers) filled two families. Two archetype families remained on the +gap-log, and with them Part C stayed blocked: + +- **"Air source heat pump" (SAP 211)** — category 4 already forced, control + `None` (Table 4e Group 2 unmapped). No live overrides today (census across + all portfolios: 0), so this is taxonomy completion, not a live defect. +- **"Community heating, boilers" / "…, CHP and boilers" (301/302)** — category + 6 already forced, control `None` (Group 3 unmapped). **2,220 live + overrides** — the largest population on the log. The inherited control is + doubly load-bearing for heat networks: it feeds the Table 4e temperature + adjustment *and* the SAP 10.2 Table 4c(3) charging factors (worksheet + (305)/(305a)). + +## Decision + +1. **Wet heat pumps (Table 4a 211-224) drag control 2201** — Table 4e Group 2 + "no time or thermostatic control of room temperature", **+0.3 °C**, the + largest Group 2 adjustment: the conservative in-group default (the heat-pump + mirror of storage 2401 / room-heater 2601 / underfloor 2701 / boiler 2101). + Warm-air heat pumps (521-527) are deliberately left unmapped — no archetype + reaches them, and the raise below is the guard that a future warm-air + archetype cannot land without choosing its Group 5 control. + + This is the *existing-system* fill policy: the ASHP **Measure** overlay + installs 2210 (time-and-temperature zone control) because a measure designs + a modern end-state (ADR-0024); a landlord override describes an existing + pump whose control was never observed, so the conservative default applies. + +2. **Community heating (301/302) drags control 2301** — Table 4e Group 3 + "flat rate charging, no thermostatic control of room temperature". It is + the never-over-crediting default on **both** axes the control drives: the + largest Group 3 temperature adjustment (**+0.3 °C**, tied with 2302) and + the worst Table 4c(3) charging factors (space **1.10**, DHW **1.05** — an + unobserved charging arrangement is never assumed to be usage-linked). + As with every forced companion (ADR-0035/0048 pattern), this overwrites a + better control lodged for the replaced system: the override asserts the + system afresh and synthesis owns coherence. + +3. **An incomplete companion set now raises** (`ValueError`) instead of + logging. Every archetype in `_MAIN_HEATING_CODES` resolves a complete + category + control (gas boilers via their dedicated overlay; all others via + `_category_for`/`_control_for`), so the log-and-continue mitigation has no + remaining population — and a silent log was only ever a stop-gap + (ADR-0048: "the intended end-state is to fill each archetype's + category/control … and then flip the log to a raise"). A future archetype + added without companions now fails loudly at overlay time rather than + shipping a silently-incoherent cert. + +## Consequences + +- The companion-gap error log is gone; #1444 closes. +- Community overrides re-model against the conservative 2301: dwellings whose + replaced-system control was inherited converge on one defensible assumption. + Live sample (portfolio 796, 12 properties): baseline shifts of **0 to −3 + SAP** (typically −1 to −2). The inherited controls were unobserved + usage-linked credits (2306/2307/2309) or outright group-incoherent — two + dwellings carried a Group-1 *boiler* zone control (2110) on a heat network — + so the shift removes credit that was never evidenced. +- Adding a heating archetype now *requires* choosing its category + control + (or extending the family sets) — the raise turns the ADR-0048 checklist into + an enforced contract. + +### Alternatives rejected + +- **A zero-adjustment / usage-linked community default (2307+).** Rejected: + credits a charging arrangement and thermostat the landlord never reported; + the conservative default is the established pattern. +- **Keep log-and-continue indefinitely.** Rejected: the log was the interim + mitigation while the taxonomy was incomplete; leaving it means the next + archetype ships the ADR-0048 inheritance bug silently again. +- **Raise on warm-air heat-pump codes' missing control now.** Not applicable — + no archetype maps to 521-527; the raise fires exactly when one is added + without its Group 5 control, which is the point. From 2bfda711d8cc84024cbecbef97e09aa21907107b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 16:32:15 +0000 Subject: [PATCH 15/23] =?UTF-8?q?Community-heating=20overrides=20default?= =?UTF-8?q?=20to=20Group=203=20control=202313=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index ec9e978aa..576b8716d 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -115,24 +115,26 @@ def test_overlay_raises_on_an_incomplete_companion_set( "main_heating_value", ["Community heating, boilers", "Community heating, CHP and boilers"], ) -def test_community_heating_drags_the_conservative_group_3_control( +def test_community_heating_drags_the_default_group_3_control( main_heating_value: str, ) -> None: # The landlord names the scheme, not its control or charging arrangement. - # The conservative Table 4e Group 3 default is 2301 ("flat rate charging, - # no thermostatic control of room temperature"): the largest Group 3 - # temperature adjustment (+0.3 C) AND the worst Table 4c(3) charging - # factors (space 1.10, DHW 1.05) — an unobserved control is never - # over-credited and an unobserved charging arrangement is never assumed - # usage-linked (ADR-0053). Leaving it unset inherits the replaced system's - # control instead (the ADR-0048 bug class). + # The Table 4e Group 3 default is 2313 ("flat rate charging, room + # thermostat and TRVs"): community schemes skew modern and the portfolio's + # own lodged community certs are overwhelmingly thermostatted (modal 2306; + # none lodge 2301), so the KIT axis concedes to the evidence — while the + # CHARGING axis stays flat-rate (space 1.05 / DHW 1.05): the billing + # arrangement is scheme paperwork an override genuinely cannot see, so + # usage-linked (1.0/1.0) is never assumed (ADR-0053, decided with Khalim). + # Leaving the control unset inherits the replaced system's instead (the + # ADR-0048 bug class). # Act simulation = main_heating_overlay_for(main_heating_value, 0) - # Assert — SAP Table 4e Group 3 conservative heat-network control. + # Assert — SAP Table 4e Group 3 default heat-network control. assert simulation is not None assert simulation.heating is not None - assert simulation.heating.main_heating_control == 2301 + assert simulation.heating.main_heating_control == 2313 def test_air_source_heat_pump_drags_the_conservative_group_2_control() -> None: From 72ccc920c5d8a5a56355b20407bfc90ca6743ba7 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 16:32:58 +0000 Subject: [PATCH 16/23] =?UTF-8?q?Community-heating=20overrides=20default?= =?UTF-8?q?=20to=20Group=203=20control=202313=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-axis choice (decided with Khalim): the kit axis concedes to the evidence (community schemes skew modern; lodged community certs are overwhelmingly thermostatted, none lodge 2301), the charging axis stays flat-rate — usage-linked billing is scheme paperwork an override cannot see. Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 6d25d979c..243326cfd 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -185,14 +185,18 @@ _CONSERVATIVE_HEAT_PUMP_CONTROL = 2201 _HEAT_NETWORK_CATEGORY = 6 _HEAT_NETWORK_CODES = frozenset({301, 302, 303, 304}) _COMMUNITY_BOILER_CODES = frozenset({301, 302, 303}) -# SAP Table 4e Group 3 heat-network control: 2301 ("flat rate charging, no -# thermostatic control of room temperature") is the conservative default when -# the landlord named only the scheme. It is never-over-crediting on BOTH axes -# the control drives: the largest Group 3 temperature adjustment (+0.3 C, tied -# with 2302) and the worst Table 4c(3) charging factors (space 1.10, DHW 1.05 -# — an unobserved charging arrangement is never assumed usage-linked). The -# community mirror of storage 2401 / boiler 2101 / heat-pump 2201 (ADR-0053). -_CONSERVATIVE_HEAT_NETWORK_CONTROL = 2301 +# SAP Table 4e Group 3 heat-network control: 2313 ("flat rate charging, room +# thermostat and TRVs") is the default when the landlord named only the scheme +# — a per-axis choice (ADR-0053, decided with Khalim). KIT axis: community +# schemes skew modern and the portfolio's lodged community certs are +# overwhelmingly thermostatted (modal 2306; none lodge 2301), so a room +# thermostat + TRVs is the evidence-backed assumption (temperature adjustment +# 0.0). CHARGING axis: flat rate (Table 4c(3) space 1.05 / DHW 1.05) — the +# billing arrangement is scheme paperwork an override cannot see, so the +# usage-linked credit (1.0/1.0) is never assumed. An assessor-OBSERVED Group 3 +# control on the cert is kept in preference to this default (the overlay sets +# `keep_existing_heat_network_control`; the applicator honours it). +_DEFAULT_HEAT_NETWORK_CONTROL = 2313 _COMMUNITY_GAS_FUEL = 20 # SAP Table 4c full boiler-control code: programmer + room thermostat + TRVs. The @@ -324,7 +328,7 @@ def _control_for(code: int) -> Optional[int]: if code in _WET_HEAT_PUMP_CODES: return _CONSERVATIVE_HEAT_PUMP_CONTROL if code in _HEAT_NETWORK_CODES: - return _CONSERVATIVE_HEAT_NETWORK_CONTROL + return _DEFAULT_HEAT_NETWORK_CONTROL return None From 04810896e06d39c407fe80b9fd43a1edd7c679ee Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 16:33:40 +0000 Subject: [PATCH 17/23] =?UTF-8?q?A=20community=20override=20keeps=20an=20a?= =?UTF-8?q?ssessor-observed=20Group=203=20control=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../epc/test_main_heating_system_overlay.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/domain/epc/test_main_heating_system_overlay.py b/tests/domain/epc/test_main_heating_system_overlay.py index 576b8716d..dc50dd2d5 100644 --- a/tests/domain/epc/test_main_heating_system_overlay.py +++ b/tests/domain/epc/test_main_heating_system_overlay.py @@ -647,6 +647,51 @@ def test_unresolvable_or_unmodelled_heating_produces_no_overlay( assert simulation is None +def test_community_override_keeps_an_observed_group_3_control() -> None: + # A community override on a cert that ALREADY lodges community heating is a + # confirmation, not a system switch — the assessor observed the control + # (e.g. 2306, "charging linked to use, programmer and TRVs", worth ~1-2 SAP + # over the flat-rate default). Stamping the 2313 default over it would + # destroy real information, so the overlay opts into keeping an existing + # Table 4e GROUP 3 control (the heat-network mirror of + # `keep_existing_off_peak_meter`, ADR-0053). + # Arrange — a cert lodging community heating with an observed 2306. + baseline = build_epc() + main = baseline.sap_heating.main_heating_details[0] + main.sap_main_heating_code = 301 + main.main_heating_category = 6 + main.main_heating_control = 2306 + overlay = main_heating_overlay_for("Community heating, boilers", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the observed usage-linked control survives. + assert result.sap_heating.main_heating_details[0].main_heating_control == 2306 + + +def test_community_override_replaces_a_cross_family_control() -> None: + # When the replaced system was a DIFFERENT family, its control is stale by + # definition — a Group-1 boiler zone control (2110) has no meaning on a + # heat network (two live portfolio-796 dwellings carried exactly that) — + # so the Group 3 default is stamped (ADR-0053). + # Arrange — a cert lodging a gas boiler with zone control. + baseline = build_epc() + main = baseline.sap_heating.main_heating_details[0] + main.sap_main_heating_code = 102 + main.main_heating_category = 2 + main.main_heating_control = 2110 + overlay = main_heating_overlay_for("Community heating, boilers", 0) + assert overlay is not None + + # Act + result = apply_simulations(baseline, [overlay]) + + # Assert — the stale boiler control is replaced by the Group 3 default. + assert result.sap_heating.main_heating_details[0].main_heating_control == 2313 + + def test_main_heating_override_remaps_the_primary_system_code() -> None: # Arrange baseline = build_epc() From 9153fa2523a5887b36ab5868d5c454d024b12f1f Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 16:35:08 +0000 Subject: [PATCH 18/23] =?UTF-8?q?A=20community=20override=20keeps=20an=20a?= =?UTF-8?q?ssessor-observed=20Group=203=20control=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The heat-network mirror of keep_existing_off_peak_meter: the override's 2313 default applies only when the replaced system's control is cross-family (stale); an observed community control survives. Co-Authored-By: Claude Opus 4.8 --- .../main_heating_system_overlay.py | 5 ++++ .../modelling/scoring/overlay_applicator.py | 24 +++++++++++++++++-- domain/modelling/simulation.py | 9 +++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/domain/epc/property_overlays/main_heating_system_overlay.py b/domain/epc/property_overlays/main_heating_system_overlay.py index 243326cfd..946c648ca 100644 --- a/domain/epc/property_overlays/main_heating_system_overlay.py +++ b/domain/epc/property_overlays/main_heating_system_overlay.py @@ -445,5 +445,10 @@ def main_heating_overlay_for( # (e.g. a 24-hour all-low tariff). Measures, which re-meter for real, # build their HeatingOverlay directly and leave this False. keep_existing_off_peak_meter=True, + # Likewise a heat-network override's control default (2313) must + # not overwrite an assessor-OBSERVED Group 3 control on a community + # cert being confirmed — only a stale cross-family control is + # replaced (ADR-0053). + keep_existing_heat_network_control=code in _HEAT_NETWORK_CODES, ) ) diff --git a/domain/modelling/scoring/overlay_applicator.py b/domain/modelling/scoring/overlay_applicator.py index 216463dab..1a9357033 100644 --- a/domain/modelling/scoring/overlay_applicator.py +++ b/domain/modelling/scoring/overlay_applicator.py @@ -152,6 +152,13 @@ _SAP_HEATING_FIELDS: tuple[str, ...] = ( _ENERGY_SOURCE_FIELDS: tuple[str, ...] = ("meter_type", "gas_connection_available") +def _is_heat_network_control(control: object) -> bool: + """True iff the lodged control is a SAP Table 4e Group 3 (heat network) + code — 2301-2314 — i.e. an assessor-observed community control a + heat-network override's default must not overwrite (ADR-0053).""" + return isinstance(control, int) and 2301 <= control <= 2314 + + def _is_off_peak_meter(meter_type: object) -> bool: """True iff the meter resolves to an off-peak Table 12a tariff (not the STANDARD single-rate column). Unparseable / absent meters count as not @@ -177,8 +184,21 @@ def _fold_heating(epc: EpcPropertyData, overlay: HeatingOverlay) -> None: main = epc.sap_heating.main_heating_details[0] for field_name in _MAIN_HEATING_FIELDS: value = getattr(overlay, field_name) - if value is not None: - setattr(main, field_name, value) + if value is None: + continue + # A community override's control is a coherent default, not an observed + # one: when it opts in (`keep_existing_heat_network_control`) and the + # cert already lodges a Table 4e GROUP 3 control (assessor-observed on + # a community cert being confirmed), keep the cert's. A cross-family + # control (boiler 2110, storage 2401) is stale and is still replaced + # (ADR-0053). + if ( + field_name == "main_heating_control" + and overlay.keep_existing_heat_network_control + and _is_heat_network_control(main.main_heating_control) + ): + continue + setattr(main, field_name, value) # `main_heating_index_number` (PCDB-resolved, e.g. a heat pump) and # `sap_main_heating_code` (Table 4a-resolved, e.g. storage heaters) are # mutually-exclusive efficiency anchors: a whole-system replacement to one diff --git a/domain/modelling/simulation.py b/domain/modelling/simulation.py index 79855940e..55c52855e 100644 --- a/domain/modelling/simulation.py +++ b/domain/modelling/simulation.py @@ -200,6 +200,15 @@ class HeatingOverlay: # MEASURE re-meters for real (Elmhurst re-lodges 18-hour → Dual on a storage # install), so it leaves this False. `_fold_heating` reads it. keep_existing_off_peak_meter: bool = False + # The community-heating mirror of the flag above (ADR-0053): a heat-network + # override's `main_heating_control` is a coherent DEFAULT, not an observed + # control — when the cert already lodges a Table 4e Group 3 control the + # assessor observed it (a community cert being confirmed, not replaced), so + # the default must not overwrite it (e.g. a usage-linked 2306 downgraded to + # flat-rate 2313 destroys ~1-2 real SAP). A cross-family control (a boiler + # 2110, storage 2401) is stale by definition and is still replaced. + # `_fold_heating` reads it. + keep_existing_heat_network_control: bool = False @dataclass(frozen=True) From 1bcb55784a811d1dc44fb0d8e93e4f89dff4cc6b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 16:37:20 +0000 Subject: [PATCH 19/23] =?UTF-8?q?Amend=20ADR-0053:=20community=20default?= =?UTF-8?q?=202313=20with=20keep-observed=20Group=203=20controls=20?= =?UTF-8?q?=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...taxonomy-complete-incomplete-set-raises.md | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md b/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md index 152e19224..0f1ca0cd7 100644 --- a/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md +++ b/docs/adr/0053-heating-override-companion-taxonomy-complete-incomplete-set-raises.md @@ -42,15 +42,27 @@ gap-log, and with them Part C stayed blocked: a modern end-state (ADR-0024); a landlord override describes an existing pump whose control was never observed, so the conservative default applies. -2. **Community heating (301/302) drags control 2301** — Table 4e Group 3 - "flat rate charging, no thermostatic control of room temperature". It is - the never-over-crediting default on **both** axes the control drives: the - largest Group 3 temperature adjustment (**+0.3 °C**, tied with 2302) and - the worst Table 4c(3) charging factors (space **1.10**, DHW **1.05** — an - unobserved charging arrangement is never assumed to be usage-linked). - As with every forced companion (ADR-0035/0048 pattern), this overwrites a - better control lodged for the replaced system: the override asserts the - system afresh and synthesis owns coherence. +2. **Community heating (301/302) defaults to control 2313, keeping an + assessor-observed Group 3 control** (decided with Khalim, revising the + first-cut 2301). The control encodes two independent unknowns and the + default is chosen per axis: + - **Kit axis** (thermostat/TRVs): community schemes skew modern, and the + portfolio's own lodged community certs are overwhelmingly thermostatted + (modal control 2306; none lodge 2301) — so 2313 ("flat rate charging, + room thermostat and TRVs", temperature adjustment 0.0) concedes this + axis to the evidence. + - **Charging axis** (flat rate vs usage-linked): the billing arrangement + is scheme paperwork an override genuinely cannot see, so flat rate + (Table 4c(3) space **1.05**, DHW **1.05**) is retained — the + usage-linked credit (1.0/1.0) is never assumed. + - **Keep-observed**: when the cert already lodges a Group 3 control the + override is confirming a community system, not replacing one — the + assessor observed that control, and stamping any default over it + destroys real information (a usage-linked 2306 is worth ~1-2 SAP over + flat-rate). The overlay opts in via + `keep_existing_heat_network_control` (the control mirror of ADR-0035's + `keep_existing_off_peak_meter`); only a stale cross-family control (the + boiler-2110 / storage-2401 inheritances) is replaced by 2313. 3. **An incomplete companion set now raises** (`ValueError`) instead of logging. Every archetype in `_MAIN_HEATING_CODES` resolves a complete @@ -65,22 +77,30 @@ gap-log, and with them Part C stayed blocked: ## Consequences - The companion-gap error log is gone; #1444 closes. -- Community overrides re-model against the conservative 2301: dwellings whose - replaced-system control was inherited converge on one defensible assumption. - Live sample (portfolio 796, 12 properties): baseline shifts of **0 to −3 - SAP** (typically −1 to −2). The inherited controls were unobserved - usage-linked credits (2306/2307/2309) or outright group-incoherent — two - dwellings carried a Group-1 *boiler* zone control (2110) on a heat network — - so the shift removes credit that was never evidenced. +- Community overrides converge on defensible controls with minimal score + churn. Live sample (portfolio 796, 12 properties): the ten dwellings with + assessor-observed Group 3 controls (2306/2307/2309) shift **±0.0** — their + observed controls survive the override — and only the two group-incoherent + dwellings (a Group-1 *boiler* zone control 2110 riding on a heat network) + move, **−1.0** each onto the 2313 default. Contrast the first-cut 2301 + design, which shifted the whole sample 0 to −3. - Adding a heating archetype now *requires* choosing its category + control (or extending the family sets) — the raise turns the ADR-0048 checklist into an enforced contract. ### Alternatives rejected -- **A zero-adjustment / usage-linked community default (2307+).** Rejected: - credits a charging arrangement and thermostat the landlord never reported; - the conservative default is the established pattern. +- **The maximally conservative community default (2301).** The first cut. + Rejected on evidence: no lodged community cert in the portfolio carries + 2301, so it under-rates the typical (modern, thermostatted) scheme and + shifted the sampled cohort 0 to −3 SAP for no informational gain. +- **A usage-linked community default (2306/2312/2314).** Rejected: credits a + billing arrangement the landlord never reported — the charging axis is the + one the override genuinely cannot observe, so it stays flat-rate. +- **Stamping the default over an observed Group 3 control.** Rejected: the + ADR-0035 "always stamp" pattern exists to kill *stale* companions; an + in-group control on a confirmed community cert is observed data, and + overwriting it is information destruction, not coherence. - **Keep log-and-continue indefinitely.** Rejected: the log was the interim mitigation while the taxonomy was incomplete; leaving it means the next archetype ships the ADR-0048 inheritance bug silently again. From 409934a730a0e61ab41fc6a5a1400ab8089e02b4 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 6 Jul 2026 12:54:03 +0000 Subject: [PATCH 20/23] Fix fastapi lambda exceeding AWS's 250MB unzipped size limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev deploys have failed since PR #1231 (2026-06-15) with InvalidParameterValueException: Unzipped size must be smaller than 262144000 bytes. The lambda zip is built from the whole repo root (deployment/terraform/modules/lambda_with_api_gateway) and only excluded tests/deployment/pycache — so .git (206MB packed), the per-schema corpus.jsonl fixtures under backend/epc_api/json_samples/ (~50MB), and the sap10_calculator PDF specs (~13MB) were all being zipped up even though none of them are read at runtime. Also add scripts/check_lambda_zip_size.py, which mirrors the terraform module's pip install + zip-with-excludes behaviour to report the projected unzipped size without mutating the repo, and wire it into deploy_terraform.yml as a fast_api_lambda_zip_size_check job that gates the real deploy so this fails fast with a clear message instead of surfacing as an opaque Terraform/AWS error. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/deploy_terraform.yml | 28 +++- .../lambda_with_api_gateway/variables.tf | 5 +- scripts/check_lambda_zip_size.py | 149 ++++++++++++++++++ 3 files changed, 180 insertions(+), 2 deletions(-) create mode 100755 scripts/check_lambda_zip_size.py diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 5e469dc66..8f2d405f4 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -534,11 +534,37 @@ jobs: TF_VAR_social_housing_wave_3_sharepoint_id: ${{ secrets.SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID }} + # ============================================================ + # Check FastAPI Lambda package will fit AWS's unzipped size limit + # ============================================================ + fast_api_lambda_zip_size_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Check projected unzipped Lambda size + run: | + python3 scripts/check_lambda_zip_size.py \ + --source-dir . \ + --requirements backend/app/requirements/requirements.txt \ + --exclude "**/__pycache__/**" \ + --exclude "**/*.pyc" \ + --exclude "**/.pytest_cache/**" \ + --exclude "**/tests/**" \ + --exclude "**/deployment/**" \ + --exclude "**/.git/**" \ + --exclude "**/json_samples/**" \ + --exclude "**/docs/specs/**" + # ============================================================ # Deploy FastAPI Lambda # ============================================================ fast_api_lambda: - needs: [determine_stage, ara_engine_lambda, categorisation_lambda, postcodeSplitter_lambda, bulk_address2uprn_combiner_lambda, bulkUploadFinaliser_lambda] + needs: [determine_stage, ara_engine_lambda, categorisation_lambda, postcodeSplitter_lambda, bulk_address2uprn_combiner_lambda, bulkUploadFinaliser_lambda, fast_api_lambda_zip_size_check] uses: ./.github/workflows/_deploy_lambda.yml with: lambda_name: ara_fast_api diff --git a/deployment/terraform/modules/lambda_with_api_gateway/variables.tf b/deployment/terraform/modules/lambda_with_api_gateway/variables.tf index b5d0515a0..7576a4bf0 100644 --- a/deployment/terraform/modules/lambda_with_api_gateway/variables.tf +++ b/deployment/terraform/modules/lambda_with_api_gateway/variables.tf @@ -11,7 +11,10 @@ variable "zip_excludes" { "**/*.pyc", "**/.pytest_cache/**", "**/tests/**", - "**/deployment/**" + "**/deployment/**", + "**/.git/**", + "**/json_samples/**", + "**/docs/specs/**" ] } diff --git a/scripts/check_lambda_zip_size.py b/scripts/check_lambda_zip_size.py new file mode 100755 index 000000000..160ac4b63 --- /dev/null +++ b/scripts/check_lambda_zip_size.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Estimate the unzipped size of a Terraform-packaged Lambda before deploying. + +Mirrors what `deployment/terraform/modules/lambda_with_api_gateway/main.tf` does: +`pip install -r -t ...` followed by zipping +`source_dir` minus `zip_excludes`. AWS Lambda's hard limit is 262144000 bytes +(250 MiB) unzipped. We install the dependencies into a scratch directory +instead of the real source tree so this can run locally or in CI without +mutating the repo. + +Usage: + python3 scripts/check_lambda_zip_size.py \ + --source-dir . \ + --requirements backend/app/requirements/requirements.txt \ + --exclude "**/tests/**" --exclude "**/.git/**" ... + +Exits non-zero if the projected unzipped size exceeds --limit (or exceeds +--warn-pct of the limit, when --strict is not passed the warning is just +printed). +""" +from __future__ import annotations + +import argparse +import fnmatch +import subprocess +import sys +import tempfile +from pathlib import Path + +LAMBDA_UNZIPPED_LIMIT_BYTES = 262_144_000 + + +def doublestar_match(pattern_parts: list[str], path_parts: list[str]) -> bool: + if not pattern_parts: + return not path_parts + head, rest = pattern_parts[0], pattern_parts[1:] + if head == "**": + if doublestar_match(rest, path_parts): + return True + if path_parts and doublestar_match(pattern_parts, path_parts[1:]): + return True + return False + if not path_parts: + return False + if fnmatch.fnmatchcase(path_parts[0], head): + return doublestar_match(rest, path_parts[1:]) + return False + + +def is_excluded(rel_path: Path, excludes: list[str]) -> bool: + parts = rel_path.as_posix().split("/") + return any(doublestar_match(pattern.split("/"), parts) for pattern in excludes) + + +def sum_dir_size(root: Path, excludes: list[str] | None = None) -> int: + total = 0 + for path in root.rglob("*"): + if path.is_dir(): + continue + rel = path.relative_to(root) + if excludes and is_excluded(rel, excludes): + continue + total += path.stat().st_size + return total + + +def install_dependencies(requirements: Path, target: Path) -> int: + subprocess.run( + [ + "pip", + "install", + "-r", + str(requirements), + "-t", + str(target), + "--platform", + "manylinux2014_x86_64", + "--implementation", + "cp", + "--python-version", + "3.11", + "--only-binary=:all:", + ], + check=True, + ) + return sum_dir_size(target) + + +def human(n: int) -> str: + return f"{n:,} bytes ({n / 1024 / 1024:.1f} MiB)" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-dir", type=Path, default=Path(".")) + parser.add_argument("--requirements", type=Path, default=None) + parser.add_argument("--exclude", action="append", default=[], dest="excludes") + parser.add_argument("--limit", type=int, default=LAMBDA_UNZIPPED_LIMIT_BYTES) + parser.add_argument( + "--warn-pct", + type=float, + default=85.0, + help="Print a warning once projected size crosses this %% of the limit.", + ) + parser.add_argument( + "--strict", + action="store_true", + help="Exit non-zero on the warning threshold too, not just the hard limit.", + ) + args = parser.parse_args() + + source_size = sum_dir_size(args.source_dir, args.excludes) + + deps_size = 0 + if args.requirements is not None: + with tempfile.TemporaryDirectory(prefix="lambda-deps-") as tmp: + deps_size = install_dependencies(args.requirements, Path(tmp)) + + total = source_size + deps_size + pct = total / args.limit * 100 + + print(f"source files (post-exclude): {human(source_size)}") + if args.requirements is not None: + print(f"pip dependencies: {human(deps_size)}") + print(f"projected unzipped total: {human(total)} [{pct:.1f}% of limit]") + print(f"AWS limit: {human(args.limit)}") + + if total > args.limit: + print( + f"\nFAIL: projected unzipped size exceeds the Lambda limit by " + f"{human(total - args.limit)}.", + file=sys.stderr, + ) + return 1 + + if pct >= args.warn_pct: + msg = ( + f"\nWARNING: projected unzipped size is at {pct:.1f}% of the " + f"{human(args.limit)} limit." + ) + print(msg, file=sys.stderr) + if args.strict: + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From d5f1c61689ef513df7ab2c97aa3d5db039b11ad0 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 6 Jul 2026 13:00:59 +0000 Subject: [PATCH 21/23] Move zip-size checker next to the fastapi requirements it checks Relocate scripts/check_lambda_zip_size.py to backend/app/requirements/, right beside the requirements.txt it's built to measure, since it's specific to the fastapi lambda rather than a generic repo script. While moving it, also make the excludes list self-updating: the script now reads zip_excludes straight out of deployment/terraform/modules/lambda_with_api_gateway/variables.tf by default instead of duplicating it as CLI flags, so the CI check and the actual Terraform packaging can't drift apart. requirements.txt defaults to the sibling file too, so both the CI step and local runs now collapse to a bare `check_lambda_zip_size.py` invocation. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/deploy_terraform.yml | 13 +--- .../requirements}/check_lambda_zip_size.py | 68 +++++++++++++++---- 2 files changed, 55 insertions(+), 26 deletions(-) rename {scripts => backend/app/requirements}/check_lambda_zip_size.py (63%) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 8f2d405f4..9907ebee3 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -547,18 +547,7 @@ jobs: python-version: "3.11" - name: Check projected unzipped Lambda size - run: | - python3 scripts/check_lambda_zip_size.py \ - --source-dir . \ - --requirements backend/app/requirements/requirements.txt \ - --exclude "**/__pycache__/**" \ - --exclude "**/*.pyc" \ - --exclude "**/.pytest_cache/**" \ - --exclude "**/tests/**" \ - --exclude "**/deployment/**" \ - --exclude "**/.git/**" \ - --exclude "**/json_samples/**" \ - --exclude "**/docs/specs/**" + run: python3 backend/app/requirements/check_lambda_zip_size.py # ============================================================ # Deploy FastAPI Lambda diff --git a/scripts/check_lambda_zip_size.py b/backend/app/requirements/check_lambda_zip_size.py similarity index 63% rename from scripts/check_lambda_zip_size.py rename to backend/app/requirements/check_lambda_zip_size.py index 160ac4b63..2ccd9a07a 100755 --- a/scripts/check_lambda_zip_size.py +++ b/backend/app/requirements/check_lambda_zip_size.py @@ -1,18 +1,22 @@ #!/usr/bin/env python3 -"""Estimate the unzipped size of a Terraform-packaged Lambda before deploying. +"""Estimate the unzipped size of the fastapi Lambda before deploying. -Mirrors what `deployment/terraform/modules/lambda_with_api_gateway/main.tf` does: -`pip install -r -t ...` followed by zipping +Mirrors what `deployment/terraform/modules/lambda_with_api_gateway/main.tf` does +for the `ara_fast_api` lambda (`deployment/terraform/lambda/fast-api`): +`pip install -r requirements.txt -t ...` followed by zipping `source_dir` minus `zip_excludes`. AWS Lambda's hard limit is 262144000 bytes (250 MiB) unzipped. We install the dependencies into a scratch directory instead of the real source tree so this can run locally or in CI without mutating the repo. -Usage: - python3 scripts/check_lambda_zip_size.py \ - --source-dir . \ - --requirements backend/app/requirements/requirements.txt \ - --exclude "**/tests/**" --exclude "**/.git/**" ... +Usage (from the repo root): + python3 backend/app/requirements/check_lambda_zip_size.py --source-dir . + +--requirements defaults to the requirements.txt next to this script, and +--exclude defaults to whatever `zip_excludes` is set to in +`deployment/terraform/modules/lambda_with_api_gateway/variables.tf` — read +straight from that file, so this can never silently drift from what +Terraform actually excludes. Pass --exclude explicitly to override. Exits non-zero if the projected unzipped size exceeds --limit (or exceeds --warn-pct of the limit, when --strict is not passed the warning is just @@ -22,6 +26,7 @@ from __future__ import annotations import argparse import fnmatch +import re import subprocess import sys import tempfile @@ -29,6 +34,24 @@ from pathlib import Path LAMBDA_UNZIPPED_LIMIT_BYTES = 262_144_000 +REPO_ROOT = Path(__file__).resolve().parents[3] +DEFAULT_REQUIREMENTS = Path(__file__).resolve().parent / "requirements.txt" +DEFAULT_TF_VARIABLES_FILE = ( + REPO_ROOT / "deployment/terraform/modules/lambda_with_api_gateway/variables.tf" +) + + +def parse_zip_excludes_from_tf(tf_variables_file: Path) -> list[str]: + text = tf_variables_file.read_text() + match = re.search( + r'variable\s+"zip_excludes"\s*{.*?default\s*=\s*\[(.*?)\]', text, re.S + ) + if not match: + raise ValueError( + f'could not find variable "zip_excludes" default in {tf_variables_file}' + ) + return re.findall(r'"([^"]+)"', match.group(1)) + def doublestar_match(pattern_parts: list[str], path_parts: list[str]) -> bool: if not pattern_parts: @@ -92,9 +115,21 @@ def human(n: int) -> str: def main() -> int: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--source-dir", type=Path, default=Path(".")) - parser.add_argument("--requirements", type=Path, default=None) - parser.add_argument("--exclude", action="append", default=[], dest="excludes") + parser.add_argument("--source-dir", type=Path, default=REPO_ROOT) + parser.add_argument("--requirements", type=Path, default=DEFAULT_REQUIREMENTS) + parser.add_argument( + "--skip-deps", + action="store_true", + help="Skip the pip install step and only measure source files.", + ) + parser.add_argument( + "--exclude", + action="append", + default=None, + dest="excludes", + help="Repeatable. Defaults to zip_excludes read from --tf-variables-file.", + ) + parser.add_argument("--tf-variables-file", type=Path, default=DEFAULT_TF_VARIABLES_FILE) parser.add_argument("--limit", type=int, default=LAMBDA_UNZIPPED_LIMIT_BYTES) parser.add_argument( "--warn-pct", @@ -109,10 +144,15 @@ def main() -> int: ) args = parser.parse_args() - source_size = sum_dir_size(args.source_dir, args.excludes) + excludes = args.excludes + if excludes is None: + excludes = parse_zip_excludes_from_tf(args.tf_variables_file) + print(f"excludes (from {args.tf_variables_file.relative_to(REPO_ROOT)}): {excludes}") + + source_size = sum_dir_size(args.source_dir, excludes) deps_size = 0 - if args.requirements is not None: + if not args.skip_deps: with tempfile.TemporaryDirectory(prefix="lambda-deps-") as tmp: deps_size = install_dependencies(args.requirements, Path(tmp)) @@ -120,7 +160,7 @@ def main() -> int: pct = total / args.limit * 100 print(f"source files (post-exclude): {human(source_size)}") - if args.requirements is not None: + if not args.skip_deps: print(f"pip dependencies: {human(deps_size)}") print(f"projected unzipped total: {human(total)} [{pct:.1f}% of limit]") print(f"AWS limit: {human(args.limit)}") From e816972c4597e4e314c9df26aad126b872bbf7a4 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 6 Jul 2026 13:28:24 +0000 Subject: [PATCH 22/23] Move the fastapi Lambda zip-size gate into the PR test suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy_terraform.yml only triggers on push to dev/prod — by the time it runs, the change has already merged to main and rolled into dev. That's exactly how the size regression sat undetected for weeks: it only ever surfaced deep inside terraform apply on dev. Drop the fast_api_lambda_zip_size_check job from deploy_terraform.yml and instead add tests/test_lambda_zip_size.py, which runs backend/app/requirements/check_lambda_zip_size.py as part of the existing pytest tests/ suite. ddd_tests.yml already runs that suite on `pull_request: branches: ["**"]`, so this now fails the PR before anything merges to main, rather than failing the dev deploy after. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/deploy_terraform.yml | 17 +---------- tests/test_lambda_zip_size.py | 42 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 tests/test_lambda_zip_size.py diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 9907ebee3..5e469dc66 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -534,26 +534,11 @@ jobs: TF_VAR_social_housing_wave_3_sharepoint_id: ${{ secrets.SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID }} - # ============================================================ - # Check FastAPI Lambda package will fit AWS's unzipped size limit - # ============================================================ - fast_api_lambda_zip_size_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Check projected unzipped Lambda size - run: python3 backend/app/requirements/check_lambda_zip_size.py - # ============================================================ # Deploy FastAPI Lambda # ============================================================ fast_api_lambda: - needs: [determine_stage, ara_engine_lambda, categorisation_lambda, postcodeSplitter_lambda, bulk_address2uprn_combiner_lambda, bulkUploadFinaliser_lambda, fast_api_lambda_zip_size_check] + needs: [determine_stage, ara_engine_lambda, categorisation_lambda, postcodeSplitter_lambda, bulk_address2uprn_combiner_lambda, bulkUploadFinaliser_lambda] uses: ./.github/workflows/_deploy_lambda.yml with: lambda_name: ara_fast_api diff --git a/tests/test_lambda_zip_size.py b/tests/test_lambda_zip_size.py new file mode 100644 index 000000000..a9cc89bca --- /dev/null +++ b/tests/test_lambda_zip_size.py @@ -0,0 +1,42 @@ +"""Guard against the fastapi Lambda's unzipped package exceeding AWS's +262144000 byte (250 MiB) limit. + +dev deployed with `InvalidParameterValueException: Unzipped size must be +smaller than 262144000 bytes` for several weeks (2026-06-15 to 2026-07-03) +before anyone noticed, because the failure only ever surfaced deep inside +`terraform apply` on a push to dev/prod — well after a PR to main had merged. +This runs the same check as part of the PR-gated test suite so a regression +(e.g. a large fixture or dependency landing outside the Lambda's zip_excludes) +fails the PR instead of the dev deploy. + +Runs the real `check_lambda_zip_size.py` script as a subprocess (not an +in-process import) so this exercises exactly what a human or CI would run +locally: `python3 backend/app/requirements/check_lambda_zip_size.py`. +""" +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +CHECK_SCRIPT = REPO_ROOT / "backend/app/requirements/check_lambda_zip_size.py" + + +def test_fastapi_lambda_unzipped_size_within_aws_limit() -> None: + result = subprocess.run( + [sys.executable, str(CHECK_SCRIPT)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + print(result.stdout) + assert result.returncode == 0, ( + "fastapi Lambda's projected unzipped size exceeds (or is dangerously " + "close to) AWS's 250 MiB limit — see output above. If this is a real " + "new dependency/fixture, either trim it or extend `zip_excludes` in " + "deployment/terraform/modules/lambda_with_api_gateway/variables.tf " + "(only if it's not needed by the Lambda at runtime).\n" + + result.stdout + + result.stderr + ) From adb1bf4b56c73f81043cc0bc62466cd26ad292ec Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Mon, 6 Jul 2026 13:43:36 +0000 Subject: [PATCH 23/23] Log GITHUB_SHA on /health; move zip-size gate to its own PR workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /health already returned GITHUB_SHA in its response but never logged it, and the fastapi lambda's Terraform environment never actually set GITHUB_SHA — so every health check response contained "unknown" in production. Wire var.github_sha through the fastapi lambda module (default "unknown" for local/other invocations) via a new TF_VAR_github_sha env var set from `github.sha` in _deploy_lambda.yml's Terraform Plan step, and log it on every /health call so a request in CloudWatch can be tied back to the deploy that served it. Also fix the zip-size gate added for PR #1469: putting it in tests/test_lambda_zip_size.py (run via the Docker-based ddd_tests.yml suite) broke CI, because Dockerfile.test's build context excludes deployment/* (.dockerignore), so check_lambda_zip_size.py couldn't find variables.tf to read zip_excludes from inside that container. Move the check to its own lightweight workflow, check_lambda_zip_size.yml, triggered on pull_request into main — a plain checkout (no Docker build) has the full repo, so the check works, runs fast, and still gates merges to main before a regression can roll into the dev deploy. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/_deploy_lambda.yml | 1 + .github/workflows/check_lambda_zip_size.yml | 23 ++++++++++ backend/app/main.py | 4 +- deployment/terraform/lambda/fast-api/main.tf | 11 ++--- .../terraform/lambda/fast-api/variables.tf | 8 +++- tests/test_lambda_zip_size.py | 42 ------------------- 6 files changed, 40 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/check_lambda_zip_size.yml delete mode 100644 tests/test_lambda_zip_size.py diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index bce793dcb..9f6b07beb 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -148,6 +148,7 @@ jobs: - name: Terraform Plan working-directory: ${{ inputs.lambda_path }} env: + TF_VAR_github_sha: ${{ github.sha }} TF_VAR_db_host: ${{ secrets.TF_VAR_db_host }} TF_VAR_db_name: ${{ secrets.TF_VAR_db_name }} TF_VAR_db_port: ${{ secrets.TF_VAR_db_port }} diff --git a/.github/workflows/check_lambda_zip_size.yml b/.github/workflows/check_lambda_zip_size.yml new file mode 100644 index 000000000..c65e9cfd2 --- /dev/null +++ b/.github/workflows/check_lambda_zip_size.yml @@ -0,0 +1,23 @@ +name: Check FastAPI Lambda package size + +# Runs on PRs targeting main so a Lambda-size regression fails the PR before +# it can merge to main and roll into the dev deploy — deploy_terraform.yml +# only triggers on push to dev/prod, which is too late (see PR #1469: this +# broke dev for weeks before anyone noticed). +on: + pull_request: + branches: + - main + +jobs: + fast_api_lambda_zip_size_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Check projected unzipped Lambda size + run: python3 backend/app/requirements/check_lambda_zip_size.py diff --git a/backend/app/main.py b/backend/app/main.py index 2904fb978..0dd9e489b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -53,7 +53,9 @@ async def log_requests(request: Request, call_next): @app.get("/health") async def health(): - return {"status": "ok", "sha": os.getenv("GITHUB_SHA", "unknown")} + sha = os.getenv("GITHUB_SHA", "unknown") + logger.info(f"Health check OK - deployed sha={sha}") + return {"status": "ok", "sha": sha} app.include_router(tasks_router.router, prefix="/v1") diff --git a/deployment/terraform/lambda/fast-api/main.tf b/deployment/terraform/lambda/fast-api/main.tf index dea9b7d92..04d05af72 100644 --- a/deployment/terraform/lambda/fast-api/main.tf +++ b/deployment/terraform/lambda/fast-api/main.tf @@ -86,6 +86,7 @@ module "fastapi" { environment = { ENVIRONMENT = var.stage + GITHUB_SHA = var.github_sha API_KEY = var.api_key SECRET_KEY = var.secret_key # DOMAIN_NAME = var.domain_name @@ -110,11 +111,11 @@ module "fastapi" { CARBON_BASELINE_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_carbon_baseline_predictions_bucket_name HEAT_BASELINE_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_heat_baseline_predictions_bucket_name - ENGINE_SQS_URL = data.terraform_remote_state.engine.outputs.ara_engine_queue_url - CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.outputs.categorisation_queue_url - POSTCODE_SPLITTER_SQS_URL = data.terraform_remote_state.postcode_splitter.outputs.postcode_splitter_queue_url - COMBINER_SQS_URL = data.terraform_remote_state.bulk_address2uprn_combiner.outputs.bulk_address2uprn_combiner_queue_url - FINALISER_SQS_URL = data.terraform_remote_state.bulk_upload_finaliser.outputs.bulk_upload_finaliser_queue_url + ENGINE_SQS_URL = data.terraform_remote_state.engine.outputs.ara_engine_queue_url + CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.outputs.categorisation_queue_url + POSTCODE_SPLITTER_SQS_URL = data.terraform_remote_state.postcode_splitter.outputs.postcode_splitter_queue_url + COMBINER_SQS_URL = data.terraform_remote_state.bulk_address2uprn_combiner.outputs.bulk_address2uprn_combiner_queue_url + FINALISER_SQS_URL = data.terraform_remote_state.bulk_upload_finaliser.outputs.bulk_upload_finaliser_queue_url } } diff --git a/deployment/terraform/lambda/fast-api/variables.tf b/deployment/terraform/lambda/fast-api/variables.tf index a31575902..c421d2f57 100644 --- a/deployment/terraform/lambda/fast-api/variables.tf +++ b/deployment/terraform/lambda/fast-api/variables.tf @@ -41,4 +41,10 @@ variable "epc_auth_token" { variable "google_solar_api_key" { type = string sensitive = true -} \ No newline at end of file +} + +variable "github_sha" { + type = string + description = "Commit SHA being deployed, surfaced via /health so cloud logs can be tied back to a deploy." + default = "unknown" +} diff --git a/tests/test_lambda_zip_size.py b/tests/test_lambda_zip_size.py deleted file mode 100644 index a9cc89bca..000000000 --- a/tests/test_lambda_zip_size.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Guard against the fastapi Lambda's unzipped package exceeding AWS's -262144000 byte (250 MiB) limit. - -dev deployed with `InvalidParameterValueException: Unzipped size must be -smaller than 262144000 bytes` for several weeks (2026-06-15 to 2026-07-03) -before anyone noticed, because the failure only ever surfaced deep inside -`terraform apply` on a push to dev/prod — well after a PR to main had merged. -This runs the same check as part of the PR-gated test suite so a regression -(e.g. a large fixture or dependency landing outside the Lambda's zip_excludes) -fails the PR instead of the dev deploy. - -Runs the real `check_lambda_zip_size.py` script as a subprocess (not an -in-process import) so this exercises exactly what a human or CI would run -locally: `python3 backend/app/requirements/check_lambda_zip_size.py`. -""" -from __future__ import annotations - -import subprocess -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).resolve().parents[1] -CHECK_SCRIPT = REPO_ROOT / "backend/app/requirements/check_lambda_zip_size.py" - - -def test_fastapi_lambda_unzipped_size_within_aws_limit() -> None: - result = subprocess.run( - [sys.executable, str(CHECK_SCRIPT)], - cwd=REPO_ROOT, - capture_output=True, - text=True, - ) - print(result.stdout) - assert result.returncode == 0, ( - "fastapi Lambda's projected unzipped size exceeds (or is dangerously " - "close to) AWS's 250 MiB limit — see output above. If this is a real " - "new dependency/fixture, either trim it or extend `zip_excludes` in " - "deployment/terraform/modules/lambda_with_api_gateway/variables.tf " - "(only if it's not needed by the Lambda at runtime).\n" - + result.stdout - + result.stderr - )