diff --git a/scripts/profile_case34.py b/scripts/profile_case34.py new file mode 100644 index 00000000..21484cc2 --- /dev/null +++ b/scripts/profile_case34.py @@ -0,0 +1,95 @@ +"""Decompose simulated case 34 (electric-storage corridor flat) vs its +dr87/P960 worksheet, channel by channel. + +Routes the tracked fixture +`backend/documents_parser/tests/fixtures/Summary_case34_storage_flat.pdf` +through extractor -> mapper -> calculator (Summary path) and prints the §3 +heat-transmission breakdown + the space-heating demand / ventilation / +gains / MIT intermediates against the worksheet line refs. + +The fabric (33), bridging (36), (31) and the door channel are EXACT after +HEAD c10881ae. The +46.3 kWh/yr (+0.41%) space-heating over-count was the +§2 (13) draught lobby: a corridor flat assumes a lobby (0.0), not the 0.05 +no-lobby penalty (RdSAP 10 spec p.30) — closed at HEAD 450e33e1, which lands +effective air change (25)m on the worksheet (avg 0.6024) and SAP at 35.3130 +vs ws 35.3094 (Δ +0.0036, sub-2dp-rounding). The residual now sits at the +worksheet's own 2dp display floor (walls -0.017 W/K). Use this to audit. + + PYTHONPATH=/workspaces/model python scripts/profile_case34.py +""" +import re +import subprocess +from pathlib import Path + +from backend.documents_parser.elmhurst_extractor import ElmhurstSiteNotesExtractor +from datatypes.epc.domain.mapper import EpcPropertyDataMapper +from domain.sap10_calculator.calculator import calculate_sap_from_inputs +from domain.sap10_calculator.rdsap.cert_to_inputs import ( + SAP_10_2_SPEC_PRICES, + cert_to_inputs, + heat_transmission_section_from_cert, +) + +_FIXTURE = ( + Path(__file__).parent.parent + / "backend/documents_parser/tests/fixtures/Summary_case34_storage_flat.pdf" +) + + +def _pages(pdf: Path) -> list[str]: + info = subprocess.run( + ["pdfinfo", str(pdf)], capture_output=True, text=True, check=True + ).stdout + pc = int(re.search(r"Pages:\s+(\d+)", info).group(1)) # type: ignore[union-attr] + pages: list[str] = [] + for i in range(1, pc + 1): + layout = subprocess.run( + ["pdftotext", "-layout", "-f", str(i), "-l", str(i), str(pdf), "-"], + capture_output=True, text=True, check=True, + ).stdout + toks: list[str] = [] + for line in layout.splitlines(): + if not line.strip(): + toks.append("") + continue + toks.extend([p for p in re.split(r"\s{2,}", line.strip()) if p]) + pages.append("\n".join(toks)) + return pages + + +def main() -> None: + sn = ElmhurstSiteNotesExtractor(_pages(_FIXTURE)).extract() + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(sn) + ht = heat_transmission_section_from_cert(epc) + print("== §3 HEAT TRANSMISSION (all EXACT at c10881ae) ==") + print(f"(31) ext elem area = {ht.total_external_element_area_m2:.4f} | ws 120.369") + print(f"(29a) walls = {ht.walls_w_per_k:.4f} | ws 30.902") + print(f"(30) roof = {ht.roof_w_per_k:.4f} | ws 91.954") + print(f"(28a) floor = {ht.floor_w_per_k:.4f} | ws 27.986") + print(f"(26) doors = {ht.doors_w_per_k:.4f} | ws 8.14 (corridor 1.4 + ext 3.0)") + print(f"(33) fabric = {ht.fabric_heat_loss_w_per_k:.4f} | ws 207.484") + print(f"(36) bridging = {ht.thermal_bridging_w_per_k:.4f} | ws 18.054") + print(f"(37) total fabric = {ht.total_w_per_k:.4f} | ws 225.538") + + res = calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES)) + im = res.intermediate + print("\n== RESIDUAL CLOSED at 450e33e1: was +46.3 kWh, now -0.96 kWh ==") + keys = [ + "useful_space_heating_kwh_per_yr", # ws (98) = 11357.24 + "infiltration_ach", "infiltration_w_per_k", + "internal_gains_annual_avg_w", # ws (73)/(84) + "mean_internal_temp_annual_avg_c", # ws (85)-(93) + ] + for k in keys: + if k in im: + print(f" {k} = {round(im[k], 4)}") + print( + "\nworksheet targets: (98) space heat=11357.24 | (35) TMP=250 |" + " (25)m eff ach 0.46-0.64 | (20) shelter=0.85 | (19) sheltered sides=2" + ) + print(f"\nSAP cont = {res.sap_score_continuous:.4f} | ws 35.3094 | " + f"Δ = {res.sap_score_continuous - 35.3094:+.4f}") + + +if __name__ == "__main__": + main()