nx_maint_rul.nx source
↩ module page · 108 lines · 4048 B
1// nx_maint_rul.nx -- remaining-useful-life (RUL) PROGNOSTICS: the learning loop's
2// prediction. From an asset's health-metric HISTORY (the R4 ledger), estimate the
3// wear rate (first-order system identification) and PREDICT time-to-failure, so
4// the platform acts BEFORE failure -- proactive, not reactive.
5//
6// Operator (2026-06-23): "full learning loop ... close the feedback loops on
7// physical things for wear and tear." Researcher-grounded (knowledge/fetched/
8// maint_c_prognostics, maint_c_condmon, maint_e_sysid, maint_e_bathtub):
9// prognostics/RUL estimate condition + remaining life from the trend.
10//
11// THE EXCEED: OBD / Sense report the PRESENT (reactive -- "it's broken now"). We
12// predict the FUTURE from the trend (proactive -- "it will hit failure in ~N").
13// Augury does this at enterprise price/cloud; we do it sovereign + cheap + broad.
14//
15// Method (integer/no-float): wear_rate = decline / span (first-order); RUL =
16// (current - failure_threshold) / wear_rate. A flat/improving trend -> STABLE
17// (no predicted failure). Honest: first-order linear extrapolation; nonlinear
18// degradation (bathtub end-of-life knee) + Kalman state estimation = next.
19//
20// NEVER-BRICK (#26): reads a metric series, predicts; no syscalls, no writes.
21//
22// genealogy_id: project-maintenance-platform-sclass-2026-06-23 (RUL prognostics; researcher-grounded)
23// license_tier: ORIGINAL
24
25const NX_RUL_INSUFFICIENT: i64 = 0
26const NX_RUL_STABLE: i64 = 1 // not wearing -> no predicted failure
27const NX_RUL_PLENTY: i64 = 2 // long life remaining
28const NX_RUL_PLAN: i64 = 3 // plan the maintenance
29const NX_RUL_SOON: i64 = 4 // schedule soon
30const NX_RUL_IMMINENT: i64 = 5 // failure imminent
31const NX_RUL_FAILED: i64 = 6 // already past the failure threshold
32const NX_RUL_BAD_ARG: i64 = 7
33const NX_RUL_N: i64 = 8
34
35func nx_rul_verdict_is_valid(v: i64) -> i64 {
36 if v < 0 { return 0 }
37 if v >= NX_RUL_N { return 0 }
38 return 1
39}
40
41struct RulSpec {
42 fail_threshold: i64, // metric value that = failure (e.g. min response rate)
43 imminent_window: i64, // RUL below this = IMMINENT (time units)
44 soon_window: i64, // RUL below this = SOON
45 plan_window: i64, // RUL below this = PLAN
46}
47
48struct RulEst {
49 wear_rate_milli: i64, // decline per time unit x1000 (telemetry)
50 current: i64, // latest metric value
51 rul: i64, // estimated time-to-failure; -1 = stable, 0 = failed
52 verdict: i64,
53}
54
55// metric[i] = a declining-with-wear health metric (e.g. response rate); t[i] =
56// time/cycle, ascending. (Caller orients the metric so wear = decline.)
57func nx_rul_estimate(metric: *i64, t: *i64, n: i64, spec: *RulSpec, out: *RulEst) -> i64 {
58 out.wear_rate_milli = 0
59 out.current = 0
60 out.rul = 0
61 out.verdict = NX_RUL_INSUFFICIENT
62
63 if n < 2 {
64 out.verdict = NX_RUL_INSUFFICIENT
65 return out.verdict
66 }
67 let last: i64 = n - 1
68 let span: i64 = t[last] - t[0]
69 if span <= 0 {
70 out.verdict = NX_RUL_BAD_ARG
71 return out.verdict
72 }
73
74 let decline: i64 = metric[0] - metric[last]
75 out.current = metric[last]
76
77 if decline <= 0 {
78 // flat or improving -> not wearing -> no predicted failure
79 out.wear_rate_milli = 0
80 out.rul = 0 - 1
81 out.verdict = NX_RUL_STABLE
82 return out.verdict
83 }
84 out.wear_rate_milli = (decline * 1000) / span
85
86 if out.current <= spec.fail_threshold {
87 out.rul = 0
88 out.verdict = NX_RUL_FAILED
89 return out.verdict
90 }
91
92 out.rul = ((out.current - spec.fail_threshold) * span) / decline
93
94 if out.rul < spec.imminent_window {
95 out.verdict = NX_RUL_IMMINENT
96 return out.verdict
97 }
98 if out.rul < spec.soon_window {
99 out.verdict = NX_RUL_SOON
100 return out.verdict
101 }
102 if out.rul < spec.plan_window {
103 out.verdict = NX_RUL_PLAN
104 return out.verdict
105 }
106 out.verdict = NX_RUL_PLENTY
107 return out.verdict
108}