nx_hvac_efficiency_test.nx source
↩ module page · 144 lines · 7092 B
1// nx_hvac_efficiency_test.nx -- gate for the independent HVAC efficiency
2// verdict engine.
3//
4// Proves, from canned out-of-band traces (NO sockets, deterministic), that
5// the verdict catches the three defects a "happy" thermostat hides, and that
6// the verdict provably TRACKS the real temperature trajectory (liar-kill) so
7// it cannot be a hard-coded constant.
8//
9// Coverage:
10// - sealed-enum validity gate
11// - S1 EFFICIENT : clean heat recovery, 1 cycle, good rate, tiny overshoot
12// - S2 SHORT_CYCLING : oversized unit cycles 6x in 30 min (vendor hides this)
13// - S3 DEGRADED_CAPACITY: runs 100% duty but barely moves temp (fouled/low charge)
14// - S4 POOR_TRACKING : good rate + low cycling but overshoots setpoint hard
15// - S5 IDLE : never ran -> not a fault, but loud (not "efficient")
16// - S6 INSUFFICIENT_DATA: 1 sample -> loud, never a silent zero verdict
17// - S7 BAD_ARG : invalid mode -> loud
18// - LIAR-KILL : same timing, different indoor trajectory -> response
19// rate AND verdict both move (slow->DEGRADED, fast->EFFICIENT)
20//
21// expect_exit: 0
22//
23// license_tier: ORIGINAL
24
25import "nx_syscalls_x86_64.nx"
26import "nx_hvac_efficiency.nx"
27
28func main() -> i64 {
29 // ---- sealed-enum validity gate ---------------------------------
30 if nx_hvac_verdict_is_valid(NX_HVAC_EFFICIENT) != 1 { return 1 }
31 if nx_hvac_verdict_is_valid(NX_HVAC_IDLE) != 1 { return 2 }
32 if nx_hvac_verdict_is_valid(NX_HVAC_N) != 0 { return 3 }
33 if nx_hvac_verdict_is_valid(-1) != 0 { return 4 }
34
35 // ---- reusable buffers ------------------------------------------
36 let t: *i64 = sys_mmap(128) as *i64
37 let indoor: *i64 = sys_mmap(128) as *i64
38 let outdoor: *i64 = sys_mmap(128) as *i64
39 let on: *i64 = sys_mmap(128) as *i64
40 let out: *HvacEff = sys_mmap(128) as *HvacEff
41 let th: *HvacThresh = sys_mmap(64) as *HvacThresh
42
43 // Config (would be svc-config in production; documented field defaults).
44 th.max_cph_x10 = 35 // 3.5 cycles/hr ceiling
45 th.min_resp_mC_per_min = 150 // 0.15 degC/min floor while running
46 th.max_overshoot_mC = 600 // 0.6 degC overshoot tolerance
47
48 // ===== S1: EFFICIENT heat recovery ==============================
49 t[0]=0; indoor[0]=19000; outdoor[0]=5000; on[0]=1
50 t[1]=600; indoor[1]=21000; outdoor[1]=5000; on[1]=1
51 t[2]=660; indoor[2]=21100; outdoor[2]=5000; on[2]=0
52 t[3]=1800; indoor[3]=20300; outdoor[3]=5000; on[3]=0
53 nx_hvac_analyze(t, indoor, outdoor, on, 4, 21000, NX_HVAC_MODE_HEAT, th, out)
54 if out.verdict != NX_HVAC_EFFICIENT { return 10 }
55 if out.total_sec != 1800 { return 11 }
56 if out.on_sec != 660 { return 12 }
57 if out.duty_permille != 366 { return 13 }
58 if out.cycles != 1 { return 14 }
59 if out.cph_x10 != 20 { return 15 }
60 if out.resp_mC_per_min != 190 { return 16 }
61 if out.peak_overshoot_mC != 100 { return 17 }
62 if out.mean_abs_err_mC != 700 { return 18 }
63 if out.mean_outdoor_mC != 5000 { return 19 }
64
65 // ===== S2: SHORT_CYCLING (oversized unit, 6 bursts/30min) =======
66 t[0]=0; indoor[0]=20800; outdoor[0]=5000; on[0]=1
67 t[1]=120; indoor[1]=21100; outdoor[1]=5000; on[1]=0
68 t[2]=300; indoor[2]=20700; outdoor[2]=5000; on[2]=1
69 t[3]=420; indoor[3]=21050; outdoor[3]=5000; on[3]=0
70 t[4]=600; indoor[4]=20700; outdoor[4]=5000; on[4]=1
71 t[5]=720; indoor[5]=21050; outdoor[5]=5000; on[5]=0
72 t[6]=900; indoor[6]=20700; outdoor[6]=5000; on[6]=1
73 t[7]=1020; indoor[7]=21050; outdoor[7]=5000; on[7]=0
74 t[8]=1200; indoor[8]=20700; outdoor[8]=5000; on[8]=1
75 t[9]=1320; indoor[9]=21050; outdoor[9]=5000; on[9]=0
76 t[10]=1500; indoor[10]=20700; outdoor[10]=5000; on[10]=1
77 t[11]=1620; indoor[11]=21050; outdoor[11]=5000; on[11]=0
78 t[12]=1800; indoor[12]=20750; outdoor[12]=5000; on[12]=0
79 nx_hvac_analyze(t, indoor, outdoor, on, 13, 21000, NX_HVAC_MODE_HEAT, th, out)
80 if out.verdict != NX_HVAC_SHORT_CYCLING { return 20 }
81 if out.cycles != 6 { return 21 }
82 if out.cph_x10 != 120 { return 22 }
83
84 // ===== S3: DEGRADED_CAPACITY (runs 100% but barely heats) =======
85 t[0]=0; indoor[0]=18000; outdoor[0]=2000; on[0]=1
86 t[1]=600; indoor[1]=18200; outdoor[1]=2000; on[1]=1
87 t[2]=1200; indoor[2]=18400; outdoor[2]=2000; on[2]=1
88 t[3]=1800; indoor[3]=18600; outdoor[3]=2000; on[3]=1
89 nx_hvac_analyze(t, indoor, outdoor, on, 4, 21000, NX_HVAC_MODE_HEAT, th, out)
90 if out.verdict != NX_HVAC_DEGRADED_CAPACITY { return 30 }
91 if out.resp_mC_per_min != 20 { return 31 }
92 if out.on_sec != 1800 { return 32 }
93 if out.duty_permille != 1000 { return 33 }
94 if out.cph_x10 != 20 { return 34 }
95
96 // ===== S4: POOR_TRACKING (good rate, low cycling, big overshoot) =
97 t[0]=0; indoor[0]=19000; outdoor[0]=5000; on[0]=1
98 t[1]=300; indoor[1]=21000; outdoor[1]=5000; on[1]=1
99 t[2]=360; indoor[2]=22500; outdoor[2]=5000; on[2]=0
100 t[3]=1800; indoor[3]=20500; outdoor[3]=5000; on[3]=0
101 nx_hvac_analyze(t, indoor, outdoor, on, 4, 21000, NX_HVAC_MODE_HEAT, th, out)
102 if out.verdict != NX_HVAC_POOR_TRACKING { return 40 }
103 if out.peak_overshoot_mC != 1500 { return 41 }
104 if out.resp_mC_per_min != 583 { return 42 }
105
106 // ===== S5: IDLE (never ran) =====================================
107 t[0]=0; indoor[0]=21000; outdoor[0]=5000; on[0]=0
108 t[1]=1800; indoor[1]=21000; outdoor[1]=5000; on[1]=0
109 nx_hvac_analyze(t, indoor, outdoor, on, 2, 21000, NX_HVAC_MODE_HEAT, th, out)
110 if out.verdict != NX_HVAC_IDLE { return 50 }
111 if out.on_sec != 0 { return 51 }
112
113 // ===== S6: INSUFFICIENT_DATA (1 sample) =========================
114 t[0]=0; indoor[0]=21000; outdoor[0]=5000; on[0]=1
115 nx_hvac_analyze(t, indoor, outdoor, on, 1, 21000, NX_HVAC_MODE_HEAT, th, out)
116 if out.verdict != NX_HVAC_INSUFFICIENT_DATA { return 60 }
117
118 // ===== S7: BAD_ARG (invalid mode) ===============================
119 t[0]=0; indoor[0]=20000; outdoor[0]=5000; on[0]=1
120 t[1]=1800; indoor[1]=21000; outdoor[1]=5000; on[1]=1
121 nx_hvac_analyze(t, indoor, outdoor, on, 2, 21000, 7, th, out)
122 if out.verdict != NX_HVAC_BAD_ARG { return 70 }
123
124 // ===== LIAR-KILL: the verdict must track the real trajectory =====
125 // Identical timing + runtime; only the indoor curve differs. A constant
126 // would give the same answer both ways -- it must not.
127 t[0]=0; indoor[0]=18000; outdoor[0]=2000; on[0]=1
128 t[1]=1800; indoor[1]=18300; outdoor[1]=2000; on[1]=1
129 nx_hvac_analyze(t, indoor, outdoor, on, 2, 25000, NX_HVAC_MODE_HEAT, th, out)
130 let resp_slow: i64 = out.resp_mC_per_min
131 let v_slow: i64 = out.verdict
132 if v_slow != NX_HVAC_DEGRADED_CAPACITY { return 80 }
133 if resp_slow != 10 { return 81 }
134
135 indoor[1]=24000
136 nx_hvac_analyze(t, indoor, outdoor, on, 2, 25000, NX_HVAC_MODE_HEAT, th, out)
137 let resp_fast: i64 = out.resp_mC_per_min
138 let v_fast: i64 = out.verdict
139 if v_fast != NX_HVAC_EFFICIENT { return 82 }
140 if resp_fast != 200 { return 83 }
141 if resp_fast == resp_slow { return 84 }
142
143 return 0
144}