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}