nx_ac_monitor_test.nx source
↩ module page · 123 lines · 4621 B
1// nx_ac_monitor_test.nx -- gate for the unified product brain.
2//
3// Proves the full pipeline (reading -> trust -> FDD + drift -> alert) and,
4// critically, that the LIAR-KILLER GATES the diagnosis: an impossible or
5// conflicted reading yields alert=UNTRUSTED with NO fault emitted (the monitor
6// cannot raise a false alarm from a lying sensor).
7//
8// expect_exit: 0
9// license_tier: ORIGINAL
10
11import "nx_syscalls_x86_64.nx"
12import "nx_ac_monitor.nx"
13
14// healthy reading with outdoor + refrigerant channels -> PLAUSIBLE / NOMINAL
15func mon_reset_rd(r: *AcReading) -> i64 {
16 r.mode = NX_HVAC_MODE_COOL
17 r.t_supply_mC = 12000
18 r.rh_supply_pm = 900
19 r.t_return_mC = 24000
20 r.rh_return_pm = 500
21 r.airflow_cfm = 1200
22 r.rho_gm3 = 1200
23 r.t_suction_mC = 13000
24 r.t_sat_evap_mC = 5000
25 r.t_liquid_mC = 39000
26 r.t_sat_cond_mC = 44000
27 r.t_outdoor_mC = 35000
28 r.p_elec_w = 3000
29 r.t_return_b_mC = NX_ACV_ABSENT
30 r.return_a_src = 1
31 r.return_b_src = 0
32 r.return_tol_mC = 500
33 return 0
34}
35
36func mon_reset_th(t: *FddThresh) -> i64 {
37 t.target_superheat_mC = 8333
38 t.superheat_band_mC = 2778
39 t.target_subcool_mC = 5556
40 t.subcool_band_mC = 2778
41 t.split_ceiling_mC = 13889
42 t.cond_over_ambient_ceiling_mC = 25000
43 t.cfm_per_ton_floor = 350
44 t.max_cph_x10 = 35
45 t.freeze_suction_sat_mC = 0
46 return 0
47}
48
49func main() -> i64 {
50 if nx_alert_is_valid(NX_ALERT_CRITICAL) != 1 { return 1 }
51 if nx_alert_is_valid(NX_ALERT_N) != 0 { return 2 }
52
53 let r: *AcReading = sys_mmap(256) as *AcReading
54 let av: *AcVerify = sys_mmap(128) as *AcVerify
55 let fin: *FddInput = sys_mmap(128) as *FddInput
56 let th: *FddThresh = sys_mmap(128) as *FddThresh
57 let fout: *FddOut = sys_mmap(64) as *FddOut
58 let o: *MonitorResult = sys_mmap(64) as *MonitorResult
59 mon_reset_th(th)
60
61 // ===== healthy -> PLAUSIBLE / NOMINAL / OK ======================
62 mon_reset_rd(r)
63 nx_ac_monitor(r, av, fin, th, fout, 360, 150, 80, o)
64 if o.trust_verdict != NX_ACV_PLAUSIBLE { return 10 }
65 if o.fdd_verdict != NX_FDD_NOMINAL { return 11 }
66 if o.alert_level != NX_ALERT_OK { return 12 }
67 if o.cop_x100 < 300 { return 13 }
68 if o.cop_x100 > 420 { return 14 }
69
70 // ===== undercharge -> PLAUSIBLE / UNDERCHARGE / WARN ============
71 mon_reset_rd(r)
72 r.t_suction_mC = 20000
73 r.t_sat_evap_mC = 3000 // superheat 17000 (high)
74 r.t_liquid_mC = 42000
75 r.t_sat_cond_mC = 44000 // subcool 2000 (low)
76 nx_ac_monitor(r, av, fin, th, fout, 360, 150, 80, o)
77 if o.trust_verdict != NX_ACV_PLAUSIBLE { return 20 }
78 if o.fdd_verdict != NX_FDD_UNDERCHARGE { return 21 }
79 if o.alert_level != NX_ALERT_WARN { return 22 }
80
81 // ===== frozen coil -> CRITICAL (severity beats everything) ======
82 mon_reset_rd(r)
83 r.t_sat_evap_mC = -2000 // evaporator saturation below freezing
84 nx_ac_monitor(r, av, fin, th, fout, 360, 150, 80, o)
85 if o.trust_verdict != NX_ACV_PLAUSIBLE { return 30 }
86 if o.fdd_verdict != NX_FDD_FROZEN_COIL { return 31 }
87 if o.alert_level != NX_ALERT_CRITICAL { return 32 }
88
89 // ===== efficiency drift -> WARN (no fault, just degraded) =======
90 // healthy unit, but baseline COP 4.60 vs current ~3.58 = ~22% worse
91 mon_reset_rd(r)
92 nx_ac_monitor(r, av, fin, th, fout, 460, 150, 80, o)
93 if o.fdd_verdict != NX_FDD_NOMINAL { return 40 }
94 if o.alert_level != NX_ALERT_WARN { return 41 }
95 if o.efficiency_drift_pm < 150 { return 42 }
96
97 // ===== efficiency drift -> INFO (mild) ==========================
98 mon_reset_rd(r)
99 nx_ac_monitor(r, av, fin, th, fout, 400, 150, 80, o)
100 if o.alert_level != NX_ALERT_INFO { return 45 }
101
102 // ===== UNTRUSTED: impossible reading -> NO fault emitted ========
103 // The liar-killer GATES the diagnosis: a 150% RH reading cannot raise
104 // a fault alert; it is flagged UNTRUSTED and the FDD is not run.
105 mon_reset_rd(r)
106 r.rh_return_pm = 1500
107 nx_ac_monitor(r, av, fin, th, fout, 360, 150, 80, o)
108 if o.trust_verdict != NX_ACV_IMPLAUSIBLE_PHYSICS { return 50 }
109 if o.alert_level != NX_ALERT_UNTRUSTED { return 51 }
110 if o.fdd_verdict != NX_FDD_INSUFFICIENT_DATA { return 52 }
111
112 // ===== UNTRUSTED: conflicting independent sensors ===============
113 mon_reset_rd(r)
114 r.t_return_b_mC = 27000
115 r.return_a_src = 1
116 r.return_b_src = 2
117 nx_ac_monitor(r, av, fin, th, fout, 360, 150, 80, o)
118 if o.trust_verdict != NX_ACV_CONFLICTED { return 60 }
119 if o.alert_level != NX_ALERT_UNTRUSTED { return 61 }
120 if o.fdd_verdict != NX_FDD_INSUFFICIENT_DATA { return 62 }
121
122 return 0
123}