code wiki / (root) / nx_ac_monitor.nx

nx_ac_monitor.nx source

↩ module page · 148 lines · 6003 B

1// nx_ac_monitor.nx -- the PRODUCT BRAIN: one call takes a raw sensor reading 2// and produces the full trustworthy monitoring assessment, with the LIAR-KILLER 3// GATING the diagnosis. 4// 5// THE THESIS IN CODE (operator: "monitoring as a liar killer"): the reading is 6// first stamped by nx_ac_sensor_verify. ONLY a PLAUSIBLE or CORROBORATED 7// reading is allowed to drive a fault verdict or an alert. An impossible or 8// self-contradicting reading returns alert = UNTRUSTED and emits NO fault -- 9// the monitor structurally CANNOT raise a false alarm from a lying sensor, nor 10// silently hide a real one behind a bad reading. 11// 12// Pipeline: reading -> [TRUST gate] -> derive superheat/subcool/split/... -> 13// FDD verdict (nx_ac_fdd) + efficiency drift vs baseline COP -> 14// single sealed alert level (OK / INFO / WARN / CRITICAL / UNTRUSTED) 15// 16// PURE (no syscalls): the caller owns all scratch structs (allocate once, reuse 17// across readings -> daemon-safe, no per-reading allocation). Deterministic -> 18// hard-gateable. NEVER-BRICK #26: read-only advisory, no device/firmware write. 19// 20// genealogy_id: project-hvac-efficiency-sclass-2026-06-23 (R5 unified monitor) 21// + project-nishi-verification-stack-sota-2026-07-14 (trust-gates-output) 22// license_tier: ORIGINAL 23// 24// nx_capability_claims: 25// needs: [nx_ac_sensor_verify, nx_ac_fdd] 26// provides: [unified_ac_monitor, liar_killer_gates_diagnosis, 27// trustworthy_alerting, efficiency_drift_alert, sealed_alert_level] 28// safety: [no_floating_point, no_syscall, bounded_iteration, 29// read_only_no_device_write, sealed_enum_verdict, 30// untrusted_reading_emits_no_fault] 31// verdict: [sealed_enum_alert, no_silent_failure, trust_gates_output] 32// license: ORIGINAL 33// kind: iot_runtime_primitive 34// sss: [S0 (bit-equal), S6 (no cloud), S7 (sealed verdict)] 35 36import "nx_ac_sensor_verify.nx" 37import "nx_ac_fdd.nx" 38 39// ---- sealed-enum alert level --------------------------------------- 40const NX_ALERT_OK: i64 = 0 41const NX_ALERT_INFO: i64 = 1 42const NX_ALERT_WARN: i64 = 2 43const NX_ALERT_CRITICAL: i64 = 3 44const NX_ALERT_UNTRUSTED: i64 = 4 45const NX_ALERT_N: i64 = 5 46 47func nx_alert_is_valid(v: i64) -> i64 { 48 if v < 0 { return 0 } 49 if v >= NX_ALERT_N { return 0 } 50 return 1 51} 52 53// ---- unified monitoring result ------------------------------------- 54struct MonitorResult { 55 trust_verdict: i64, // NX_ACV_* 56 trust_reason: i64, 57 corroborated: i64, 58 q_cool_w: i64, 59 cop_x100: i64, 60 efficiency_drift_pm: i64, // vs baseline (0 if not computable) 61 fdd_verdict: i64, // NX_FDD_* (INSUFFICIENT if reading untrusted) 62 alert_level: i64, // NX_ALERT_* 63} 64 65// caller-owned scratch: av (AcVerify), fin (FddInput), fth (FddThresh config), 66// fout (FddOut). Allocate once; reuse per reading. 67func nx_ac_monitor(rd: *AcReading, av: *AcVerify, fin: *FddInput, fth: *FddThresh, 68 fout: *FddOut, baseline_cop_x100: i64, warn_drift_pm: i64, 69 info_drift_pm: i64, out: *MonitorResult) -> i64 { 70 // ---- 1. trust the reading (the liar-killer) ------------------ 71 nx_acv_verify(rd, av) 72 out.trust_verdict = av.verdict 73 out.trust_reason = av.reason 74 out.corroborated = av.corroborated 75 out.q_cool_w = av.q_cool_w 76 out.cop_x100 = av.cop_x100 77 out.efficiency_drift_pm = 0 78 out.fdd_verdict = NX_FDD_INSUFFICIENT_DATA 79 out.alert_level = NX_ALERT_UNTRUSTED 80 81 var trusted: i64 = 0 82 if av.verdict == NX_ACV_PLAUSIBLE { trusted = 1 } 83 if av.verdict == NX_ACV_CORROBORATED { trusted = 1 } 84 if trusted == 0 { 85 // untrusted reading -> refuse to diagnose (no fault, no alert-from-lie) 86 return out.alert_level 87 } 88 89 // ---- 2. derive FDD inputs from the trusted reading ----------- 90 fin.superheat_mC = NX_FDD_ABSENT 91 if rd.t_suction_mC != NX_ACV_ABSENT { 92 if rd.t_sat_evap_mC != NX_ACV_ABSENT { 93 fin.superheat_mC = rd.t_suction_mC - rd.t_sat_evap_mC 94 } 95 } 96 fin.subcool_mC = NX_FDD_ABSENT 97 if rd.t_sat_cond_mC != NX_ACV_ABSENT { 98 if rd.t_liquid_mC != NX_ACV_ABSENT { 99 fin.subcool_mC = rd.t_sat_cond_mC - rd.t_liquid_mC 100 } 101 } 102 fin.suction_sat_mC = NX_FDD_ABSENT 103 if rd.t_sat_evap_mC != NX_ACV_ABSENT { 104 fin.suction_sat_mC = rd.t_sat_evap_mC 105 } 106 fin.evap_split_mC = rd.t_return_mC - rd.t_supply_mC 107 fin.cond_over_ambient_mC = NX_FDD_ABSENT 108 if rd.t_sat_cond_mC != NX_ACV_ABSENT { 109 if rd.t_outdoor_mC != NX_ACV_ABSENT { 110 fin.cond_over_ambient_mC = rd.t_sat_cond_mC - rd.t_outdoor_mC 111 } 112 } 113 fin.cfm_per_ton = NX_FDD_ABSENT // needs capacity spec (per-unit config) 114 fin.cph_x10 = NX_FDD_ABSENT // needs time series (nx_hvac_efficiency) 115 116 // ---- 3. FDD verdict ------------------------------------------ 117 nx_ac_fdd_analyze(fin, fth, fout) 118 out.fdd_verdict = fout.verdict 119 120 // ---- 4. efficiency drift vs baseline COP --------------------- 121 var drift: i64 = 0 122 if av.cop_x100 != NX_ACM_INFEASIBLE { 123 if baseline_cop_x100 > 0 { 124 drift = nx_ac_efficiency_drift_permille(av.cop_x100, baseline_cop_x100) 125 } 126 } 127 out.efficiency_drift_pm = drift 128 129 // ---- 5. single alert level ----------------------------------- 130 // fault severity first, then efficiency drift. 131 var lvl: i64 = NX_ALERT_OK 132 if fout.verdict == NX_FDD_FROZEN_COIL { lvl = NX_ALERT_CRITICAL } 133 if lvl == NX_ALERT_OK { 134 if fout.verdict != NX_FDD_NOMINAL { 135 if fout.verdict != NX_FDD_INSUFFICIENT_DATA { 136 lvl = NX_ALERT_WARN 137 } 138 } 139 } 140 if lvl == NX_ALERT_OK { 141 if drift >= warn_drift_pm { lvl = NX_ALERT_WARN } 142 } 143 if lvl == NX_ALERT_OK { 144 if drift >= info_drift_pm { lvl = NX_ALERT_INFO } 145 } 146 out.alert_level = lvl 147 return lvl 148}