code wiki / _hdl_build / nx_batt_intake.nx
nx_batt_intake.nx source
↩ module page · 99 lines · 4545 B
1// nx_batt_intake.nx -- R1 PACK INTAKE & TRIAGE: the diagnose->verdict brain of the Li-ion reclamation business.
2// Given a pack's measured state + a DATA-DRIVEN IntakeSpec (#11 -- thresholds are svc-config, never magic numbers),
3// returns ONE sealed verdict:
4// UNSAFE-REJECT : swollen/damaged, out of temp range, or a cell below the unsafe floor (over-discharged ->
5// copper-shunt/dendrite risk). The NEVER-IGNITE priority: SAFETY IS SCREENED FIRST, before any
6// economic decision -- a pack that could ignite is refused, never charged. (Spine = nx_battery_safety.)
7// RETIRE-RECYCLE : too degraded (SoH below the retire line) or too many bad cells to be worth saving -> safe recycle.
8// RE-CELL : a few bad cells in an otherwise good pack -> replace cells + rebalance (the EGO-grade rebuild).
9// BMS-RESET : cells healthy + balanced but the pack reads dead at the terminals = BMS lockout -> just wake it
10// (the "dead battery that's actually fine" -- the cheapest, highest-margin recovery).
11// RECONDITION : cells healthy but imbalanced -> balance charge.
12// PASS-HEALTHY : healthy, balanced, capacity OK.
13// HONEST: "EGO-grade top capacity" = rebuild-to-spec (RE-CELL / rebalance / BMS-reset), NOT un-aging a worn cell
14// (capacity fade is largely irreversible). SoH = capacity retention per-mille, computed with the SAME formula as the
15// nx_consumable kernel (the gate proves they match = real reuse). Pure compute: no syscalls, no float, read-only
16// (never-brick #26). license_tier: ORIGINAL
17
18const NX_BI_UNSAFE_REJECT: i64 = 0
19const NX_BI_RETIRE_RECYCLE: i64 = 1
20const NX_BI_RECELL: i64 = 2
21const NX_BI_BMS_RESET: i64 = 3
22const NX_BI_RECONDITION: i64 = 4
23const NX_BI_PASS_HEALTHY: i64 = 5
24const NX_BI_BAD_ARG: i64 = 6
25const NX_BI_N: i64 = 7
26
27func nx_bi_verdict_is_valid(v: i64) -> i64 {
28 if v < 0 { return 0 }
29 if v >= NX_BI_N { return 0 }
30 return 1
31}
32
33struct PackState {
34 n_cells: i64,
35 min_cell_mv: i64,
36 max_cell_mv: i64,
37 n_bad_cells: i64,
38 rated_capacity_mah: i64,
39 measured_capacity_mah: i64,
40 max_ir_mohm: i64,
41 pack_temp_mc: i64,
42 terminal_mv: i64,
43 swollen: i64,
44}
45
46struct IntakeSpec {
47 unsafe_cell_floor_mv: i64,
48 healthy_cell_min_mv: i64,
49 healthy_cell_max_mv: i64,
50 soh_retire_permille: i64,
51 max_imbalance_mv: i64,
52 max_ir_mohm: i64,
53 temp_min_mc: i64,
54 temp_max_mc: i64,
55 recell_max_bad: i64,
56}
57
58// SoH = capacity retention per-mille. IDENTICAL formula to nx_cons_analyze's remaining_permille
59// (prior_consumed = capacity lost, clamped >=0): remaining = rated - lost; permille = remaining*1000/rated.
60func nx_bi_soh_permille(ps: *PackState) -> i64 {
61 if ps.rated_capacity_mah <= 0 { return 0 }
62 var lost: i64 = ps.rated_capacity_mah - ps.measured_capacity_mah
63 if lost < 0 { lost = 0 }
64 let remaining: i64 = ps.rated_capacity_mah - lost
65 return (remaining * 1000) / ps.rated_capacity_mah
66}
67
68func nx_batt_triage(ps: *PackState, spec: *IntakeSpec) -> i64 {
69 if ps.rated_capacity_mah <= 0 { return NX_BI_BAD_ARG }
70 if ps.n_cells <= 0 { return NX_BI_BAD_ARG }
71
72 // ===== SAFETY SCREEN FIRST (never-ignite priority): refuse before any value decision =====
73 if ps.swollen != 0 { return NX_BI_UNSAFE_REJECT }
74 if ps.pack_temp_mc < spec.temp_min_mc { return NX_BI_UNSAFE_REJECT }
75 if ps.pack_temp_mc > spec.temp_max_mc { return NX_BI_UNSAFE_REJECT }
76 if ps.min_cell_mv < spec.unsafe_cell_floor_mv { return NX_BI_UNSAFE_REJECT }
77
78 let soh: i64 = nx_bi_soh_permille(ps)
79 let imbalance: i64 = ps.max_cell_mv - ps.min_cell_mv
80
81 // ===== too degraded to be worth saving? =====
82 if soh < spec.soh_retire_permille {
83 if ps.n_bad_cells > 0 {
84 if ps.n_bad_cells <= spec.recell_max_bad { return NX_BI_RECELL }
85 }
86 return NX_BI_RETIRE_RECYCLE
87 }
88
89 // ===== SoH OK: a few bad cells -> re-cell; too many -> retire =====
90 if ps.n_bad_cells > 0 {
91 if ps.n_bad_cells <= spec.recell_max_bad { return NX_BI_RECELL }
92 return NX_BI_RETIRE_RECYCLE
93 }
94
95 // ===== healthy cells: BMS lockout (dead terminal) -> reset; imbalanced -> recondition; else healthy =====
96 if ps.terminal_mv == 0 { return NX_BI_BMS_RESET }
97 if imbalance > spec.max_imbalance_mv { return NX_BI_RECONDITION }
98 return NX_BI_PASS_HEALTHY
99}