mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
76 lines
3.7 KiB
Python
76 lines
3.7 KiB
Python
"""Categorize every corpus cert outside +-0.5 SAP by the REASON it diverges,
|
|
combining the computed cost-vs-demand signature, the electric heating code /
|
|
meter, and the ledger's RR/multipart tokens."""
|
|
from __future__ import annotations
|
|
import json, re
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
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_demand_inputs, cert_to_inputs, _is_electric_main,
|
|
_first_main_heating)
|
|
|
|
CORPUS = Path("backend/epc_api/json_samples/RdSAP-Schema-21.0.1/corpus.jsonl")
|
|
# ledger signatures (for RR / multipart tokens)
|
|
sig_of={}; cur=""
|
|
for l in Path("scripts/corpus_1000/worklist.md").read_text().splitlines():
|
|
if l.startswith('### C'):
|
|
m=re.search(r'`([^`]*)`',l); cur=m.group(1) if m else ""
|
|
m=re.match(r'^- .*?`([^`]+)`',l)
|
|
if m: sig_of[m.group(1)]=cur
|
|
|
|
def bucket(doc, dsap, dpe, dco2, code, mt, sig):
|
|
rr = 'RR' in (sig or '')
|
|
elec = code in range(191,600) if code else False
|
|
demand = dpe is not None and abs(dpe) >= 4 and (dpe>0)==(dsap<0)
|
|
cost = dpe is not None and abs(dpe) < 4
|
|
# 1. electric storage-boiler / CPSU on off-peak (spec §12 Rule 2/1)
|
|
if code in (192,193,194,195,196) and dsap>0:
|
|
return "elec storage/CPSU boiler → §12 off-peak (spec-faithful; lodged over-billed)"
|
|
# 2. electric direct + storage heaters, tariff/cost
|
|
if elec and cost:
|
|
return "electric tariff/cost (meter-column & high-rate fraction; spec-faithful vs lodged)"
|
|
if elec and demand:
|
|
return "electric + fabric/demand mismatch"
|
|
if elec:
|
|
return "electric other"
|
|
# 3. RR
|
|
if rr:
|
|
return "room-in-roof surface geometry (spec-mandated, worksheet-validated; high variance)"
|
|
# 4. non-elec demand/fabric
|
|
if demand:
|
|
return "non-electric fabric/demand (per-cert: glazing area, lodged geometry, mixed walls)"
|
|
if cost:
|
|
return "non-electric cost (secondary fuel / cylinder / tariff)"
|
|
return "unclassified"
|
|
|
|
cnt=defaultdict(list)
|
|
band=defaultdict(int)
|
|
for line in CORPUS.read_text().splitlines():
|
|
doc=json.loads(line); L=doc.get('energy_rating_current')
|
|
if L is None: continue
|
|
try:
|
|
epc=EpcPropertyDataMapper.from_api_response(doc)
|
|
r=calculate_sap_from_inputs(cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
|
d=calculate_sap_from_inputs(cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES))
|
|
except Exception: continue
|
|
dsap=r.sap_score_continuous-L
|
|
if abs(dsap)<0.5: continue
|
|
peL=doc.get('energy_consumption_current'); co2L=doc.get('co2_emissions_current')
|
|
dpe=(d.primary_energy_kwh_per_m2-peL) if peL is not None else None
|
|
dco2=(d.co2_kg_per_yr/1000-co2L) if co2L is not None else None
|
|
sh=doc.get('sap_heating') or {}; det=(sh.get('main_heating_details') or [{}])[0]
|
|
code=det.get('sap_main_heating_code'); mt=(doc.get('sap_energy_source') or {}).get('meter_type')
|
|
b=bucket(doc,dsap,dpe,dco2,code,mt,sig_of.get(str(doc.get('uprn')),''))
|
|
cnt[b].append((doc.get('uprn'),L,round(r.sap_score_continuous,1),round(dsap,1)))
|
|
ad=abs(dsap); band['0.5-1' if ad<1 else '1-2' if ad<2 else '2-4' if ad<4 else '4+']+=1
|
|
|
|
total=sum(len(v) for v in cnt.values())
|
|
print(f"TOTAL diverging (|Δ|≥0.5): {total}\n")
|
|
print("by |Δ| band:", dict(band), "\n")
|
|
print("=== by REASON (count, mean|Δ|, examples) ===")
|
|
for b,rows in sorted(cnt.items(), key=lambda x:-len(x[1])):
|
|
mabs=sum(abs(x[3]) for x in rows)/len(rows)
|
|
ex=", ".join(f"{x[0]}({x[3]:+.1f})" for x in sorted(rows,key=lambda x:-abs(x[3]))[:3])
|
|
print(f"\n[{len(rows):3d}] mean|Δ|={mabs:.1f} {b}\n e.g. {ex}")
|