"""12 — Transition grid-charging via an EG4 Chargeverter GC (DC-coupled, islanded).
Phase-1→off-grid TRANSITION option: a UL-listed AC battery charger (EG4 Chargeverter GC)
fed from shore/grid power, outputting DC onto the SELECTED Path-A battery busbar. The grid
only ever feeds the charger (a branch-circuit load) — it never parallels the inverter AC
output, so the system stays a stand-alone (NEC Art. 710) island: no 705 interconnection,
no backfeed, no synchronization. This script sizes the charger's AC input + DC output
circuits and — the safety-critical bit — checks the COMBINED PV + charger current against
the 2-pack BMS charge limit.
EG4 Chargeverter GC (datasheet, eg4electronics.com): 48 V nominal, DC out 0-100 A @240V
(0-45 A @120V / 0-50 A @208V), 5120 W, AC in max 26 A @240V, charge V 43-57 (def 54), 94% eff,
built-in breaker, UL listed. Conductor sizing reuses the validated size_run() from 03.
Run: ~/.venvs/prsm-electrical/bin/python scripts/12_chargeverter.py
"""
import importlib
from common import load_site, require, banner, result, dump_result
from nec_tables import std_ocpd_at_least, T310_16
_c = importlib.import_module("03_conductors")
size_run, report = _c.size_run, _c.report
# --- EG4 Chargeverter GC (CONFIRMED from datasheet) ---
CV_DC_A_MAX = 100 # DC output @ 240 VAC (adjustable 0-100)
CV_AC_A_MAX = 26 # AC input current @ 240 VAC (max)
CV_W = 5120 # max output @ 240 VAC
CV_AC_V = 240.0
CV_V_CHG_DEFAULT = 54.0 # default charge voltage (adjustable 43-57)
def main():
cfg = load_site()
banner("12 TRANSITION — EG4 Chargeverter GC (DC-coupled grid charging, islanded)")
vnom = require(cfg, "battery.nominal_v")
cap_ah = require(cfg, "battery.capacity_ah")
bms_chg = require(cfg, "battery.bms_continuous_charge_a") # 150 A per pack
inv_pv_chg = require(cfg, "inverter.batt_max_charge_a") # 125 A per 6000XP from PV
npack, n_inv = 2, 2 # selected Path-A topology
runs = (cfg.get("phase2") or {}).get("runs_ft", cfg["runs_ft"])
tgt = cfg["targets"]
result("Chargeverter GC", f"DC 0-{CV_DC_A_MAX}A / AC {CV_AC_A_MAX}A @240V / {CV_W}W", "",
"UL-listed battery charger; grid feeds ONLY this (branch-circuit load)")
result("on the Path-A bank", f"{npack} packs, {n_inv}x 6000XP", "",
"charger lands on the same 250A DC busbar as the packs + inverters")
# ---------- THE safety-critical check: combined charge current vs BMS limit ----------
bank_chg_limit = npack * bms_chg # 300 A
pv_chg_max = n_inv * inv_pv_chg # 250 A (2x 6000XP from PV)
cv = CV_DC_A_MAX # 100 A charger
print("\n COMBINED charge current (PV chargers + Chargeverter) vs the 2-pack BMS limit:")
result("bank BMS charge limit", f"{bank_chg_limit}", "A", f"{npack} x {bms_chg}A/pack")
result("PV charge max (2x 6000XP)", f"{pv_chg_max}", "A", "both inverters charging from PV")
for label, total in [("Chargeverter ALONE (PV~0, night/winter)", cv),
("Chargeverter @100A + PV @250A (worst)", cv + pv_chg_max),
("Chargeverter @50A + PV @250A", 50 + pv_chg_max)]:
per_pack = total / npack
crate = per_pack / cap_ah
ok = per_pack <= bms_chg
result(f" {label}", f"{total}A bank -> {per_pack:.0f}A/pack ({crate:.2f}C)", "",
f"{'OK' if ok else 'EXCEEDS '+str(bms_chg)+'A/pack BMS'}")
cv_max_with_full_pv = bank_chg_limit - pv_chg_max
result("max Chargeverter if run WITH full PV", f"{cv_max_with_full_pv}", "A",
f"so PV{pv_chg_max}+CV <= {bank_chg_limit}A bank (150A/pack)")
print(" -> Rule: grid-charge when PV is low (the natural transition case) at up to 100A; if the")
print(" charger may run alongside MAX PV, cap it at ~50A. The JK-BMS (150A/pack) is the backstop.")
# ---------- AC input branch circuit (ordinary NEC 210/240, NOT interconnection) ----------
print("\n AC INPUT branch circuit (utility -> charger; a LOAD, not a source):")
r_ac = size_run("Chargeverter AC input (240V branch)", mode="continuous",
current=CV_AC_A_MAX, vref=CV_AC_V, length_ft=runs.get("inverter_to_loads", 10),
target_pct=tgt["vdrop_ac_pct_max"], ambient_c=30, ccc=2)
report(r_ac)
# ---------- DC output to the battery busbar (a charge source on the ESS bus) ----------
print("\n DC OUTPUT to the battery busbar (690.9/706.31 charge-source OCPD + disconnect):")
r_dc = size_run("Chargeverter DC output (-> busbar)", mode="continuous",
current=CV_DC_A_MAX, vref=vnom, length_ft=runs.get("inverter_to_battery", 10),
target_pct=tgt["vdrop_battery_pct_max"], ambient_c=30, ccc=2)
report(r_dc)
result(" note", "2/0 matches the bank cabling (175A) for one cable type", "",
f"computed minimum is {r_dc['final_gauge_AWG']}; fuse {r_dc['ocpd_A']}A at the busbar tap")
verdict = (cv / npack <= bms_chg) and ((cv_max_with_full_pv + pv_chg_max) <= bank_chg_limit)
print()
result("OVERALL", "PASS (with charge-current coordination)" if verdict else "REVIEW", "",
"islanded DC-coupled charging; grid never touches the inverter or AC panel")
dump_result("12_chargeverter", {
"charger": "EG4 Chargeverter GC", "dc_out_a_max": CV_DC_A_MAX, "ac_in_a_max": CV_AC_A_MAX,
"bank_charge_limit_a": bank_chg_limit, "pv_charge_max_a": pv_chg_max,
"max_cv_with_full_pv_a": cv_max_with_full_pv,
"cv_alone_per_pack_a": cv / npack, "cv_alone_crate": round((cv / npack) / cap_ah, 3),
"ac_input": {"gauge": r_ac["final_gauge_AWG"], "ocpd_a": r_ac["ocpd_A"], "egc": r_ac["egc_AWG"]},
"dc_output": {"gauge": r_dc["final_gauge_AWG"], "ocpd_a": r_dc["ocpd_A"], "egc": r_dc["egc_AWG"]},
"compliance": "Art.710 stand-alone; grid feeds only the charger (branch-circuit load); "
"no 705 interconnection, no AC paralleling, no backfeed.",
"verdict": "PASS (coordinate charge current)" if verdict else "REVIEW",
})
if __name__ == "__main__":
main()