code wiki / (root) / nx_hvac_efficiency_test.nx

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}