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}