nx_market.nx source
↩ module page · 137 lines · 7312 B
1// nx_market.nx -- the NISHI MARKET RESEARCHER: sees what the team can CAPITALIZE on (operator). It
2// maps the team's capabilities to OPPORTUNITIES and scores each by VALUE x FEASIBILITY -- but only
3// recommends ones the team can actually DELIVER (its capability is at the grade the opportunity
4// REQUIRES, per the Examiner). High-value opportunities we can't deliver yet are HONESTLY flagged
5// BUILD-FIRST (they feed the Futurist + the backlog), not sold as ready. So the team chases value it
6// can actually ship, and knows exactly what to build to unlock the rest. composes Examiner (grades)
7// + Futurist (timing). RACI: Market Researcher scores; Builder builds the unlock; Scientist publishes
8// the proof a buyer/critic needs. license_tier: ORIGINAL Refs: grade-gated opportunity scoring.
9
10import "nx_sclass_grader.nx" // SG_* tiers as the required/held capability grade
11import "nx_bom.nx" // mk_water_production_cost composes the REAL bill of materials
12
13// ===== UNIT ECONOMICS =================================================================================
14// Added 2026-07-31. nx_market_test.nx specifies NINE mk_* functions that existed nowhere; this file held
15// only the mkt_* opportunity scorer, so the test could never once have passed. Kept in a named section
16// because scoring an opportunity and pricing a unit are different jobs sharing one import graph.
17// Every constant below is fixed by the test's assertions.
18
19const MK_PCT: i64 = 100
20const MK_MIN_MARGIN_PCT: i64 = 40 // below this the margin cannot absorb support + returns
21const MK_MAX_PAYBACK_DAYS: i64 = 180 // 6 months; past that a buyer stops believing the savings story
22
23// Production cost at volume = a bulk fraction of the hand-built prototype.
24func mk_production_unit_cost(prototype_cents: i64, bulk_pct: i64) -> i64 {
25 return prototype_cents * bulk_pct / MK_PCT
26}
27
28// Wright's law: every DOUBLING of cumulative volume retains retention_pct of the unit cost. Applied one
29// doubling at a time, truncating each step, rather than as a single power -- a real cost-down ladder is
30// renegotiated per volume tier, so the rounding happens at each rung and not once at the end.
31func mk_learning_cost(base_cents: i64, retention_pct: i64, doublings: i64) -> i64 {
32 var c: i64 = base_cents
33 var d: i64 = 0
34 while d < doublings {
35 c = c * retention_pct / MK_PCT
36 d = d + 1
37 }
38 return c
39}
40
41// Price for a target GROSS margin: price = cost / (1 - margin).
42func mk_price_for_margin(unit_cost_cents: i64, target_margin_pct: i64) -> i64 {
43 if target_margin_pct >= MK_PCT { return 0 - 1 } // 100% margin needs an infinite price -- REFUSE
44 if target_margin_pct < 0 { return 0 - 1 }
45 return unit_cost_cents * MK_PCT / (MK_PCT - target_margin_pct)
46}
47
48func mk_gross_margin_pct(price_cents: i64, unit_cost_cents: i64) -> i64 {
49 if price_cents <= 0 { return 0 - 1 }
50 return (price_cents - unit_cost_cents) * MK_PCT / price_cents
51}
52
53// Units that must sell to cover fixed cost. CEILING: a fraction of a unit pays for nothing, so 370.37
54// means you are still short until the 371st sells.
55func mk_break_even_units(fixed_cents: i64, price_cents: i64, unit_cost_cents: i64) -> i64 {
56 let contribution: i64 = price_cents - unit_cost_cents
57 if contribution <= 0 { return 0 - 1 } // sells at or below cost: NEVER breaks even, and -1 says so
58 return (fixed_cents + contribution - 1) / contribution
59}
60
61func mk_daily_savings_cents(liters_per_day: i64, alt_cost_per_l: i64, our_cost_per_l: i64) -> i64 {
62 return liters_per_day * (alt_cost_per_l - our_cost_per_l)
63}
64
65// Payback period, FLOOR -- and the asymmetry with mk_break_even_units above is deliberate, not an
66// oversight. Break-even is a REQUIRED COUNT of a discrete thing, so it rounds UP. Payback is a REPORTED
67// DURATION, quoted the way every payback figure is quoted, so it truncates. WORTH KNOWING: at floor the
68// buyer is not actually whole on day 66 (66 x 340 = 22440 < 22500) -- they are whole on day 67. The test
69// pins 66, so 66 is the contract; a stricter reading would use the same ceiling as break-even.
70func mk_payback_days(price_cents: i64, daily_savings_cents: i64) -> i64 {
71 if daily_savings_cents <= 0 { return 0 - 1 } // no savings: payback never arrives
72 return price_cents / daily_savings_cents
73}
74
75// Go-to-market verdict: healthy margin AND a payback a buyer will believe. Both bars are named constants
76// so "why 40?" has an answer that is not a number buried in a branch (rule 11).
77func mk_viable(margin_pct: i64, payback_days: i64) -> i64 {
78 if payback_days < 0 { return 0 } // never pays back
79 if margin_pct < MK_MIN_MARGIN_PCT { return 0 }
80 if payback_days > MK_MAX_PAYBACK_DAYS { return 0 }
81 return 1
82}
83
84// COMPOSES the real BOM: the prototype cost is not a literal here, it is whatever the catalog sums to,
85// so a part price change flows straight through to the production cost.
86func mk_water_production_cost(bulk_pct: i64) -> i64 {
87 let b: *NxBom = nx_bom_water_assemble()
88 let proto: i64 = b.total_cost_cents
89 sys_munmap(b as *u8, BOM_BOM_BYTES)
90 return mk_production_unit_cost(proto, bulk_pct)
91}
92
93const MKT_CAPITALIZE: i64 = 1 // valuable AND deliverable now -> go
94const MKT_BUILD_FIRST: i64 = 2 // valuable but capability not at the required grade -> build the unlock
95const MKT_SKIP: i64 = 3 // not valuable enough
96
97// can the team DELIVER this opportunity? its held capability grade >= the grade the opportunity needs.
98func mkt_can_deliver(capability_grade: i64, required_grade: i64) -> i64 {
99 if capability_grade >= required_grade { return 1 }
100 return 0
101}
102
103// disposition for one opportunity.
104func mkt_disposition(value: i64, value_floor: i64, capability_grade: i64, required_grade: i64) -> i64 {
105 if value < value_floor { return MKT_SKIP }
106 if mkt_can_deliver(capability_grade, required_grade) == 1 { return MKT_CAPITALIZE }
107 return MKT_BUILD_FIRST
108}
109
110// prioritization score (value x feasibility) -- only meaningful for deliverable opportunities.
111func mkt_score(value: i64, feasibility: i64) -> i64 { return value * feasibility }
112
113// the top DELIVERABLE opportunity (max value x feasibility among those we can ship now); -1 if none.
114func mkt_top_deliverable(n: i64, value: *i64, feasibility: *i64, grade: *i64, required: *i64, value_floor: i64) -> i64 {
115 var best: i64 = 0 - 1; var bestscore: i64 = 0 - 1; var i: i64 = 0
116 while i < n {
117 if mkt_disposition(value[i], value_floor, grade[i], required[i]) == MKT_CAPITALIZE {
118 let s: i64 = mkt_score(value[i], feasibility[i])
119 if s > bestscore { bestscore = s; best = i }
120 }
121 i = i + 1
122 }
123 return best
124}
125
126// the highest-VALUE opportunity that is currently BLOCKED (build-first) -- names the unlock worth
127// building toward (the bridge from Market Researcher to Builder/Futurist).
128func mkt_top_unlock(n: i64, value: *i64, grade: *i64, required: *i64, value_floor: i64) -> i64 {
129 var best: i64 = 0 - 1; var bestv: i64 = 0 - 1; var i: i64 = 0
130 while i < n {
131 if mkt_disposition(value[i], value_floor, grade[i], required[i]) == MKT_BUILD_FIRST {
132 if value[i] > bestv { bestv = value[i]; best = i }
133 }
134 i = i + 1
135 }
136 return best
137}