code wiki / (root) / nx_maint_action.nx

nx_maint_action.nx source

↩ module page · 191 lines · 7685 B

1// nx_maint_action.nx -- R5: diagnosis -> action. Turns a kernel verdict + the 2// R4 trend into an actionable plan: probable CAUSE, recommended ACTION 3// (monitor / fix-DIY / fix-pro / replace consumable|component|unit / shut down), 4// DIY-vs-pro split (feeds the two R6 fronts), and an urgency. 5// 6// Operator (2026-06-23): "from the hardware rung up each rung." This is R5 on 7// top of R3's unified severity + R4's trend -- it completes monitor->diagnose->ACT. 8// 9// Rules that matter: 10// - SAFETY OVERRIDE: any verdict that unifies to SAFETY (overload, ground- 11// fault, overheat, burst) -> SHUTDOWN_SAFETY + call a pro, regardless of 12// kernel. The R3 safety guarantee drives this. 13// - PREDICTIVE ESCALATION: a WORSENING trend turns a "monitor" into a "fix 14// now" and bumps urgency -- act before failure (the R4 trend earns its keep). 15// 16// (Starter maintenance playbook -- the cause/action mappings are real field 17// practice; in production they live in svc-config #11, refined per asset.) 18// 19// NEVER-BRICK (#26): emits a recommendation; takes no action, writes nothing. 20// 21// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (R5 action) 22// license_tier: ORIGINAL 23 24import "nx_maint_unify.nx" // nx_maint_severity + NX_HEALTH_* + kernel verdict consts + NX_MK_* 25import "nx_maint_ledger.nx" // NX_TREND_* 26 27// ---- Recommended action (ascending intervention) ------------------- 28const NX_ACT_NONE: i64 = 0 29const NX_ACT_MONITOR: i64 = 1 30const NX_ACT_FIX_DIY: i64 = 2 31const NX_ACT_FIX_PRO: i64 = 3 32const NX_ACT_REPLACE_CONSUMABLE: i64 = 4 33const NX_ACT_REPLACE_COMPONENT: i64 = 5 34const NX_ACT_REPLACE_UNIT: i64 = 6 35const NX_ACT_SHUTDOWN_SAFETY: i64 = 7 36const NX_ACT_N: i64 = 8 37 38// ---- Probable cause (the diagnosis) -------------------------------- 39const NX_CAUSE_NONE: i64 = 0 40const NX_CAUSE_OVERSIZED: i64 = 1 41const NX_CAUSE_DIRTY_FILTER: i64 = 2 42const NX_CAUSE_CONTROL_CAL: i64 = 3 43const NX_CAUSE_IMBALANCE: i64 = 4 44const NX_CAUSE_BEARING: i64 = 5 45const NX_CAUSE_CONSUMABLE_SPENT: i64 = 6 46const NX_CAUSE_SEAL_LEAK: i64 = 7 47const NX_CAUSE_PUMP_OR_BIGLEAK: i64 = 8 48const NX_CAUSE_CIRCUIT_LOAD: i64 = 9 49const NX_CAUSE_VOLTAGE_SUPPLY: i64 = 10 50const NX_CAUSE_SAFETY_HAZARD: i64 = 11 51const NX_CAUSE_N: i64 = 12 52 53func nx_action_is_valid(a: i64) -> i64 { 54 if a < 0 { return 0 } 55 if a >= NX_ACT_N { return 0 } 56 return 1 57} 58 59// A DIYer can do: nothing-needed, monitor, a DIY fix, a consumable swap. 60// A pro is needed for: pro fix, component/unit replace, a safety shutdown. 61func nx_action_is_diy(a: i64) -> i64 { 62 if a == NX_ACT_NONE { return 1 } 63 if a == NX_ACT_MONITOR { return 1 } 64 if a == NX_ACT_FIX_DIY { return 1 } 65 if a == NX_ACT_REPLACE_CONSUMABLE { return 1 } 66 return 0 67} 68 69// ---- Per-kernel base action (non-safety verdicts) ------------------ 70func nx_act_thermal(v: i64) -> i64 { 71 if v == NX_HVAC_SHORT_CYCLING { return NX_ACT_FIX_PRO } 72 if v == NX_HVAC_DEGRADED_CAPACITY { return NX_ACT_FIX_DIY } 73 if v == NX_HVAC_POOR_TRACKING { return NX_ACT_MONITOR } 74 return NX_ACT_NONE 75} 76func nx_act_rot(v: i64) -> i64 { 77 if v == NX_ROT_ROUGH_WARNING { return NX_ACT_MONITOR } 78 if v == NX_ROT_ROUGH_ALARM { return NX_ACT_FIX_PRO } 79 if v == NX_ROT_BEARING_WEAR { return NX_ACT_REPLACE_COMPONENT } 80 return NX_ACT_NONE 81} 82func nx_act_cons(v: i64) -> i64 { 83 if v == NX_CONS_DUE_SOON { return NX_ACT_MONITOR } 84 if v == NX_CONS_DUE { return NX_ACT_REPLACE_CONSUMABLE } 85 if v == NX_CONS_OVERDUE { return NX_ACT_REPLACE_CONSUMABLE } 86 return NX_ACT_NONE 87} 88func nx_act_fluid(v: i64) -> i64 { 89 if v == NX_FLUID_SEEPAGE { return NX_ACT_MONITOR } 90 if v == NX_FLUID_LEAK { return NX_ACT_FIX_DIY } 91 if v == NX_FLUID_NO_PRESSURE { return NX_ACT_FIX_PRO } 92 return NX_ACT_NONE 93} 94func nx_act_elec(v: i64) -> i64 { 95 if v == NX_ELEC_HIGH_LOAD { return NX_ACT_FIX_DIY } 96 if v == NX_ELEC_VOLTAGE_SAG { return NX_ACT_FIX_PRO } 97 if v == NX_ELEC_VOLTAGE_SWELL { return NX_ACT_FIX_PRO } 98 return NX_ACT_NONE 99} 100 101// ---- Per-kernel probable cause ------------------------------------- 102func nx_cause_thermal(v: i64) -> i64 { 103 if v == NX_HVAC_SHORT_CYCLING { return NX_CAUSE_OVERSIZED } 104 if v == NX_HVAC_DEGRADED_CAPACITY { return NX_CAUSE_DIRTY_FILTER } 105 if v == NX_HVAC_POOR_TRACKING { return NX_CAUSE_CONTROL_CAL } 106 return NX_CAUSE_NONE 107} 108func nx_cause_rot(v: i64) -> i64 { 109 if v == NX_ROT_ROUGH_WARNING { return NX_CAUSE_IMBALANCE } 110 if v == NX_ROT_ROUGH_ALARM { return NX_CAUSE_IMBALANCE } 111 if v == NX_ROT_BEARING_WEAR { return NX_CAUSE_BEARING } 112 return NX_CAUSE_NONE 113} 114func nx_cause_cons(v: i64) -> i64 { 115 if v == NX_CONS_DUE_SOON { return NX_CAUSE_CONSUMABLE_SPENT } 116 if v == NX_CONS_DUE { return NX_CAUSE_CONSUMABLE_SPENT } 117 if v == NX_CONS_OVERDUE { return NX_CAUSE_CONSUMABLE_SPENT } 118 return NX_CAUSE_NONE 119} 120func nx_cause_fluid(v: i64) -> i64 { 121 if v == NX_FLUID_SEEPAGE { return NX_CAUSE_SEAL_LEAK } 122 if v == NX_FLUID_LEAK { return NX_CAUSE_SEAL_LEAK } 123 if v == NX_FLUID_NO_PRESSURE { return NX_CAUSE_PUMP_OR_BIGLEAK } 124 return NX_CAUSE_NONE 125} 126func nx_cause_elec(v: i64) -> i64 { 127 if v == NX_ELEC_HIGH_LOAD { return NX_CAUSE_CIRCUIT_LOAD } 128 if v == NX_ELEC_VOLTAGE_SAG { return NX_CAUSE_VOLTAGE_SUPPLY } 129 if v == NX_ELEC_VOLTAGE_SWELL { return NX_CAUSE_VOLTAGE_SUPPLY } 130 return NX_CAUSE_NONE 131} 132 133func nx_action_for(kernel: i64, verdict: i64) -> i64 { 134 if kernel == NX_MK_THERMAL { return nx_act_thermal(verdict) } 135 if kernel == NX_MK_ROTATING { return nx_act_rot(verdict) } 136 if kernel == NX_MK_CONSUMABLE { return nx_act_cons(verdict) } 137 if kernel == NX_MK_FLUID { return nx_act_fluid(verdict) } 138 if kernel == NX_MK_ELECTRICAL { return nx_act_elec(verdict) } 139 return NX_ACT_NONE 140} 141func nx_cause_for(kernel: i64, verdict: i64) -> i64 { 142 if kernel == NX_MK_THERMAL { return nx_cause_thermal(verdict) } 143 if kernel == NX_MK_ROTATING { return nx_cause_rot(verdict) } 144 if kernel == NX_MK_CONSUMABLE { return nx_cause_cons(verdict) } 145 if kernel == NX_MK_FLUID { return nx_cause_fluid(verdict) } 146 if kernel == NX_MK_ELECTRICAL { return nx_cause_elec(verdict) } 147 return NX_CAUSE_NONE 148} 149 150// ---- The actionable plan ------------------------------------------- 151struct ActionPlan { 152 action: i64, // NX_ACT_* 153 cause: i64, // NX_CAUSE_* 154 diy: i64, // 1 = DIYer can do it, 0 = needs a pro 155 urgency: i64, // NX_HEALTH_* severity, escalated one step if WORSENING 156} 157 158func nx_plan_for(kernel: i64, verdict: i64, trend: i64, out: *ActionPlan) -> i64 { 159 let sev: i64 = nx_maint_severity(kernel, verdict) 160 161 // urgency = severity, escalated one step on a worsening trend (real concerns only) 162 var urg: i64 = sev 163 if trend == NX_TREND_WORSENING { 164 if sev >= NX_HEALTH_WATCH { 165 if sev < NX_HEALTH_SAFETY { 166 urg = sev + 1 167 } 168 } 169 } 170 out.urgency = urg 171 172 // SAFETY override: any SAFETY verdict -> shut it down, get a pro. 173 if sev == NX_HEALTH_SAFETY { 174 out.action = NX_ACT_SHUTDOWN_SAFETY 175 out.cause = NX_CAUSE_SAFETY_HAZARD 176 out.diy = 0 177 return out.action 178 } 179 180 var act: i64 = nx_action_for(kernel, verdict) 181 // PREDICTIVE ESCALATION: don't just watch a worsening asset -- fix it. 182 if act == NX_ACT_MONITOR { 183 if trend == NX_TREND_WORSENING { 184 act = NX_ACT_FIX_DIY 185 } 186 } 187 out.action = act 188 out.cause = nx_cause_for(kernel, verdict) 189 out.diy = nx_action_is_diy(act) 190 return out.action 191}