code wiki / (root) / nx_electrical.nx

nx_electrical.nx source

↩ module page · 145 lines · 6672 B

1// nx_electrical.nx -- Monitors electrical circuits for overload, voltage anomalies, and ground faults to ensure safety and accurate energy measurement. 2const NX_MAGIC_1500: i64 = 1500 3const NX_MAGIC_2000: i64 = 2000 4const NX_MAGIC_12000: i64 = 12000 5const NX_MAGIC_10000: i64 = 10000 6const NX_MAGIC_3600: i64 = 3600 7// nx_electrical.nx -- electrical-health kernel (#5; the "electric" domain). 8// Independent electrical safety + energy from whole-panel CT-monitor signals 9// (current + voltage per circuit -- exactly what Emporia Vue / Sense read). 10// 11// Operator (2026-06-23): "we want electric and all that." Electrical is the #1 12// home-fire domain (overloaded circuits) AND the CIQ top-build 13// whole-panel-energy-measure (beats Sense) -- same CT signals feed both. 14// 15// Verdicts (severity-ordered, integer/no-float): 16// - GROUND_FAULT leakage (line-neutral imbalance) over the GFCI threshold -> shock/fire 17// - OVERLOAD sustained current at/over breaker rating -> fire / nuisance trip 18// - VOLTAGE_SWELL over the nominal band -> equipment damage 19// - VOLTAGE_SAG under the nominal band -> brownout / motor damage 20// - HIGH_LOAD approaching the breaker rating (warning) 21// - IDLE / NOMINAL 22// Plus real power + energy (Wh) measured directly per circuit = sovereign 23// energy metering (we do what Sense's ML disaggregation guesses, measured). 24// 25// HONEST: true arc-fault (AFCI) detection needs high-frequency sampling a 26// low-rate CT feed does not give -> NOT claimed here; it is a future HF rung. 27// 28// NEVER-BRICK (#26): reads current/voltage, emits a verdict; trips no breaker, 29// writes nothing. Read-only by construction. 30// 31// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (kernel #5 electric; CIQ energy-measure) 32// license_tier: ORIGINAL 33// 34// nx_capability_claims: 35// needs: [pointer_arithmetic] 36// provides: [circuit_overload_detect, voltage_sag_swell_detect, 37// ground_fault_leakage_detect, sovereign_energy_metering] 38// safety: [no_floating_point, no_syscall, bounded_iteration, 39// read_only_no_device_write, no_firmware_write_by_construction, 40// sealed_enum_verdict] 41// verdict: [sealed_enum_elec, no_silent_failure] 42// license: ORIGINAL 43// kind: maintenance_runtime_primitive 44 45const NX_ELEC_INSUFFICIENT_DATA: i64 = 0 46const NX_ELEC_NOMINAL: i64 = 1 47const NX_ELEC_HIGH_LOAD: i64 = 2 // approaching breaker rating 48const NX_ELEC_OVERLOAD: i64 = 3 // at/over rating -> fire/trip 49const NX_ELEC_VOLTAGE_SAG: i64 = 4 // under nominal band 50const NX_ELEC_VOLTAGE_SWELL: i64 = 5 // over nominal band 51const NX_ELEC_GROUND_FAULT: i64 = 6 // leakage imbalance -> shock/fire 52const NX_ELEC_IDLE: i64 = 7 // no load (valid) 53const NX_ELEC_BAD_ARG: i64 = 8 54const NX_ELEC_N: i64 = 9 55 56func nx_elec_verdict_is_valid(v: i64) -> i64 { 57 if v < 0 { return 0 } 58 if v >= NX_ELEC_N { return 0 } 59 return 1 60} 61 62// Data-driven spec = the circuit's envelope (#11; svc-config / panel map in prod). 63struct ElecSpec { 64 breaker_rating_ca: i64, // breaker rating, centi-amps (NX_MAGIC_1500=15A, NX_MAGIC_2000=20A) 65 high_load_permille: i64, // warn at this fraction of rating (800 = 80%) 66 nominal_voltage_cv: i64, // nominal voltage, centi-volts (NX_MAGIC_12000 = 120V) 67 voltage_band_permille: i64, // allowed +/- band (50 = +/-5%) 68 ground_fault_ma: i64, // leakage trip threshold, milli-amps (5 = GFCI) 69 idle_current_ca: i64, // below this = IDLE (50 = 0.5A) 70} 71 72struct ElecEff { 73 mean_current_ca: i64, 74 peak_current_ca: i64, 75 load_permille: i64, 76 min_voltage_cv: i64, 77 max_voltage_cv: i64, 78 leakage_ma: i64, 79 mean_power_w: i64, 80 energy_wh: i64, 81 verdict: i64, 82} 83 84// current_ca[i] / voltage_cv[i] = per-sample RMS current/voltage from the CT 85// monitor. total_sec = window duration (for energy). leakage_ma = measured 86// line-neutral imbalance over the window (0 = balanced = good). 87func nx_elec_analyze(current_ca: *i64, voltage_cv: *i64, n: i64, total_sec: i64, 88 leakage_ma: i64, spec: *ElecSpec, out: *ElecEff) -> i64 { 89 out.mean_current_ca = 0 90 out.peak_current_ca = 0 91 out.load_permille = 0 92 out.min_voltage_cv = 0 93 out.max_voltage_cv = 0 94 out.leakage_ma = 0 95 out.mean_power_w = 0 96 out.energy_wh = 0 97 out.verdict = NX_ELEC_INSUFFICIENT_DATA 98 99 if leakage_ma < 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 100 if total_sec < 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 101 if spec.breaker_rating_ca <= 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 102 if spec.nominal_voltage_cv <= 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 103 if n < 1 { out.verdict = NX_ELEC_INSUFFICIENT_DATA; return out.verdict } 104 105 var sum_i: i64 = 0 106 var peak_i: i64 = 0 107 var sum_v: i64 = 0 108 var min_v: i64 = voltage_cv[0] 109 var max_v: i64 = voltage_cv[0] 110 var k: i64 = 0 111 while k < n { 112 let ci: i64 = current_ca[k] 113 let vi: i64 = voltage_cv[k] 114 if ci < 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 115 if vi < 0 { out.verdict = NX_ELEC_BAD_ARG; return out.verdict } 116 if ci > peak_i { peak_i = ci } 117 sum_i = sum_i + ci 118 if vi < min_v { min_v = vi } 119 if vi > max_v { max_v = vi } 120 sum_v = sum_v + vi 121 k = k + 1 122 } 123 let mean_i: i64 = sum_i / n 124 let mean_v: i64 = sum_v / n 125 out.mean_current_ca = mean_i 126 out.peak_current_ca = peak_i 127 out.min_voltage_cv = min_v 128 out.max_voltage_cv = max_v 129 out.leakage_ma = leakage_ma 130 out.load_permille = (mean_i * 1000) / spec.breaker_rating_ca 131 out.mean_power_w = (mean_i * mean_v) / NX_MAGIC_10000 132 out.energy_wh = (out.mean_power_w * total_sec) / NX_MAGIC_3600 133 134 let hi: i64 = (spec.nominal_voltage_cv * (1000 + spec.voltage_band_permille)) / 1000 135 let lo: i64 = (spec.nominal_voltage_cv * (1000 - spec.voltage_band_permille)) / 1000 136 137 if leakage_ma >= spec.ground_fault_ma { out.verdict = NX_ELEC_GROUND_FAULT; return out.verdict } 138 if out.load_permille >= 1000 { out.verdict = NX_ELEC_OVERLOAD; return out.verdict } 139 if max_v > hi { out.verdict = NX_ELEC_VOLTAGE_SWELL; return out.verdict } 140 if min_v < lo { out.verdict = NX_ELEC_VOLTAGE_SAG; return out.verdict } 141 if out.load_permille >= spec.high_load_permille { out.verdict = NX_ELEC_HIGH_LOAD; return out.verdict } 142 if mean_i < spec.idle_current_ca { out.verdict = NX_ELEC_IDLE; return out.verdict } 143 out.verdict = NX_ELEC_NOMINAL 144 return out.verdict 145}