nx_bom_rt.nx source
↩ module page · 158 lines · 6274 B
1// nx_bom.nx -- GO-TO-MARKET / BILL OF MATERIALS engine. The first hardware
2// rung made concrete: turn a gated DESIGN into a buildable bench prototype
3// -- the real components, their cost, mass, lead time, and make-vs-buy --
4// with rollups (total cost, total mass, the longest-lead CRITICAL-PATH
5// item that gates the build schedule) and per-category subtotals. Generic
6// across any product; instantiated here for the water AWG bench prototype
7// (advances water-system from MRL 2 toward MRL 3 = manufacturing proof of
8// concept).
9//
10// INTEGER-EXACT: cost in cents, mass in grams, lead time in days. The
11// enclosure is MAKE (we 3D-print it with our own CAD/G-code capability =
12// vertical integration), everything else is BUY.
13//
14// grounded: bill_of_materials + design_for_manufacturability + contract_manufacture
15// genealogy_id: bill_of_materials_bench_prototype + nishi_go_to_market
16
17import "nx_syscalls.nx"
18
19// ===== Component categories =======================================
20
21const BC_HARVEST: i64 = 0 // cooler + fans + prefilter (pull water from air)
22const BC_COLLECT: i64 = 1 // tray + tubing + pump + tank
23const BC_TREAT: i64 = 2 // UV-C + carbon (make it safe)
24const BC_MINERAL: i64 = 3 // remineralization media (make it taste + healthful)
25const BC_CONTROL: i64 = 4 // MCU + sensors + power + wiring
26const BC_ENCLOSURE: i64 = 5 // the case (we print it)
27
28const BM_BUY: i64 = 0
29const BM_MAKE: i64 = 1
30
31// ===== Part ids (0..15 ARE the water bench-prototype BOM, in order) =
32
33const BP_PELTIER: i64 = 0
34const BP_HEATSINK_FAN: i64 = 1
35const BP_INTAKE_FAN: i64 = 2
36const BP_PREFILTER: i64 = 3
37const BP_TRAY: i64 = 4
38const BP_TUBING: i64 = 5
39const BP_PUMP: i64 = 6
40const BP_UVC_LED: i64 = 7
41const BP_CARBON: i64 = 8
42const BP_MINERAL_CART: i64 = 9
43const BP_TANK: i64 = 10
44const BP_MCU: i64 = 11
45const BP_SENSORS: i64 = 12
46const BP_PSU: i64 = 13
47const BP_ENCLOSURE: i64 = 14
48const BP_WIRING: i64 = 15
49const BP_N: i64 = 16
50
51// ===== Struct: NxPart =============================================
52
53struct NxPart {
54 part_id: i64,
55 category: i64,
56 unit_cost_cents: i64,
57 mass_g: i64,
58 make_buy: i64,
59 lead_days: i64,
60}
61
62func nx_bom_part(id: i64) -> *NxPart {
63 let p: *NxPart = (sys_mmap(64)) as *NxPart
64 p.part_id = id
65 p.category = BC_CONTROL
66 p.unit_cost_cents = 0
67 p.mass_g = 0
68 p.make_buy = BM_BUY
69 p.lead_days = 0
70 if id == BP_PELTIER { p.category = BC_HARVEST; p.unit_cost_cents = 500; p.mass_g = 25; p.lead_days = 7 }
71 if id == BP_HEATSINK_FAN { p.category = BC_HARVEST; p.unit_cost_cents = 1200; p.mass_g = 200; p.lead_days = 7 }
72 if id == BP_INTAKE_FAN { p.category = BC_HARVEST; p.unit_cost_cents = 800; p.mass_g = 150; p.lead_days = 5 }
73 if id == BP_PREFILTER { p.category = BC_HARVEST; p.unit_cost_cents = 600; p.mass_g = 50; p.lead_days = 5 }
74 if id == BP_TRAY { p.category = BC_COLLECT; p.unit_cost_cents = 1000; p.mass_g = 120; p.lead_days = 10 }
75 if id == BP_TUBING { p.category = BC_COLLECT; p.unit_cost_cents = 700; p.mass_g = 80; p.lead_days = 5 }
76 if id == BP_PUMP { p.category = BC_COLLECT; p.unit_cost_cents = 1500; p.mass_g = 180; p.lead_days = 10 }
77 if id == BP_UVC_LED { p.category = BC_TREAT; p.unit_cost_cents = 1800; p.mass_g = 30; p.lead_days = 14 }
78 if id == BP_CARBON { p.category = BC_TREAT; p.unit_cost_cents = 1200; p.mass_g = 150; p.lead_days = 7 }
79 if id == BP_MINERAL_CART { p.category = BC_MINERAL; p.unit_cost_cents = 2000; p.mass_g = 300; p.lead_days = 14 }
80 if id == BP_TANK { p.category = BC_COLLECT; p.unit_cost_cents = 1400; p.mass_g = 250; p.lead_days = 10 }
81 if id == BP_MCU { p.category = BC_CONTROL; p.unit_cost_cents = 1000; p.mass_g = 20; p.lead_days = 7 }
82 if id == BP_SENSORS { p.category = BC_CONTROL; p.unit_cost_cents = 2500; p.mass_g = 100; p.lead_days = 10 }
83 if id == BP_PSU { p.category = BC_CONTROL; p.unit_cost_cents = 1600; p.mass_g = 400; p.lead_days = 7 }
84 // The enclosure is MAKE: printed with our own CAD + G-code capability.
85 if id == BP_ENCLOSURE { p.category = BC_ENCLOSURE; p.unit_cost_cents = 800; p.mass_g = 600; p.make_buy = BM_MAKE; p.lead_days = 2 }
86 if id == BP_WIRING { p.category = BC_CONTROL; p.unit_cost_cents = 900; p.mass_g = 60; p.lead_days = 5 }
87 return p
88}
89
90// ===== Struct: NxBom (rollup) =====================================
91
92struct NxBom {
93 total_cost_cents: i64,
94 total_mass_g: i64,
95 max_lead_days: i64,
96 part_count: i64,
97 make_count: i64,
98}
99
100func nx_bom_new() -> *NxBom {
101 let b: *NxBom = (sys_mmap(48)) as *NxBom
102 b.total_cost_cents = 0
103 b.total_mass_g = 0
104 b.max_lead_days = 0
105 b.part_count = 0
106 b.make_count = 0
107 return b
108}
109
110func nx_bom_add(b: *NxBom, part_id: i64, qty: i64) -> i64 {
111 let p: *NxPart = nx_bom_part(part_id)
112 b.total_cost_cents = b.total_cost_cents + p.unit_cost_cents * qty
113 b.total_mass_g = b.total_mass_g + p.mass_g * qty
114 if p.lead_days > b.max_lead_days { b.max_lead_days = p.lead_days }
115 b.part_count = b.part_count + qty
116 if p.make_buy == BM_MAKE { b.make_count = b.make_count + qty }
117 return 0
118}
119
120// ===== The water AWG bench prototype BOM ==========================
121//
122// Parts 0..15 in id order; two intake fans, one of everything else.
123
124func nx_bom_water_count() -> i64 {
125 return BP_N
126}
127
128func nx_bom_water_qty_at(i: i64) -> i64 {
129 if i == BP_INTAKE_FAN { return 2 }
130 return 1
131}
132
133func nx_bom_water_assemble() -> *NxBom {
134 let b: *NxBom = nx_bom_new()
135 var i: i64 = 0
136 while i < nx_bom_water_count() {
137 nx_bom_add(b, i, nx_bom_water_qty_at(i))
138 i = i + 1
139 }
140 return b
141}
142
143// Per-category cost subtotal across the water BOM.
144func nx_bom_water_category_cost(cat: i64) -> i64 {
145 var c: i64 = 0
146 var i: i64 = 0
147 while i < nx_bom_water_count() {
148 let p: *NxPart = nx_bom_part(i)
149 if p.category == cat { c = c + p.unit_cost_cents * nx_bom_water_qty_at(i) }
150 i = i + 1
151 }
152 return c
153}
154
155// The critical-path lead time = the longest single-part lead (gates the build).
156func nx_bom_critical_path_days(b: *NxBom) -> i64 {
157 return b.max_lead_days
158}