code wiki / _hdl_build / nx_price.nx
nx_price.nx source
↩ module page · 284 lines · 12790 B
1// nx_price.nx -- LIB: PRICE TRACKING across stores (R-PRICE, pillar 3 of seed-to-feed). Operator: "the tracking
2// of prices but stores in the area for stores across america and how they are doing as far as prices." A
3// sovereign price registry (knowledge/store/price-*, NO TSV, integer cents no-float): stores with an AREA, and
4// date-stamped price OBSERVATIONS per item. "How a store is doing" = a measured competitiveness INDEX vs the
5// area median (100 = exactly the area median; <100 = cheaper than median = better for the shopper). Finds the
6// cheapest store / basket per area = where to buy for least (the affordability arc).
7//
8// HONEST: prices are RECORDED observations with a date + source (rule 23), NOT live-scraped or claimed real-time;
9// the index is computed from those recorded numbers. Ties to [[project-nishi-food-branch-2026-06-22]] affordability.
10// license_tier: ORIGINAL
11import "nx_food_science.nx"
12import "nx_seg_store.nx"
13import "nx_syscalls.nx"
14
15func pr_put(prefix: *u8, key: *u8, val: *u8) -> i64 {
16 let vl: i64 = as_len(val)
17 if fd_streq_store(prefix, key, val, vl) == 1 { return 0 }
18 let w: *i64 = ss_begin()
19 ss_add(w, 1, key, val, vl)
20 let seg: i64 = fd_seg_next(prefix)
21 ss_commit(prefix, w, seg)
22 return 1
23}
24
25// split a TAB list into NUL-terminated tokens; returns count.
26func pr_split(buf: *u8, len: i64, outtoks: *i64) -> i64 {
27 var c: i64 = 0
28 var i: i64 = 0
29 var ls: i64 = 0
30 while i <= len {
31 var sep: i64 = 0
32 if i == len { sep = 1 } else { if buf[i] == (9 as u8) { sep = 1 } }
33 if sep == 1 {
34 let tl: i64 = i - ls
35 if tl > 0 {
36 let tok: *u8 = sys_mmap(48)
37 var t: i64 = 0
38 while t < tl { tok[t] = buf[ls + t]; t = t + 1 } tok[tl] = 0 as u8
39 outtoks[c] = tok as i64; c = c + 1
40 }
41 ls = i + 1
42 }
43 i = i + 1
44 }
45 return c
46}
47
48// idempotent append of `token` to the TAB list stored at `key`.
49func pr_list_append(prefix: *u8, key: *u8, token: *u8) -> i64 {
50 let pq: *i64 = sys_mmap(16) as *i64
51 let lq: *i64 = sys_mmap(16) as *i64
52 let cur: *u8 = sys_mmap(2048)
53 var o: i64 = 0
54 if ss_get(prefix, key, pq, lq) == 1 {
55 let b: *u8 = pq[0] as *u8
56 let n: i64 = lq[0]
57 // already present? scan tokens
58 let toks: *i64 = sys_mmap(8 * 256) as *i64
59 let nt: i64 = pr_split(b, n, toks)
60 var i: i64 = 0
61 while i < nt { if fd_streq(toks[i] as *u8, token) == 1 { return 0 } i = i + 1 }
62 var u: i64 = 0
63 while u < n { cur[u] = b[u]; u = u + 1 } o = n
64 cur[o] = 9 as u8; o = o + 1
65 }
66 o = as_append(cur, o, token)
67 cur[o] = 0 as u8
68 return pr_put(prefix, key, cur)
69}
70
71// ---- stores + areas ----
72func pr_store_key(sid: *u8, out: *u8) -> i64 { var o: i64 = 0; o = as_append(out, o, "price:store:" as *u8); o = as_append(out, o, sid); out[o] = 0 as u8; return o }
73func pr_area_key(area: *u8, out: *u8) -> i64 { var o: i64 = 0; o = as_append(out, o, "price:area:" as *u8); o = as_append(out, o, area); o = as_append(out, o, ":stores" as *u8); out[o] = 0 as u8; return o }
74func pr_obs_key(sid: *u8, item: *u8, out: *u8) -> i64 {
75 var o: i64 = 0
76 o = as_append(out, o, "price:obs:" as *u8); o = as_append(out, o, sid)
77 out[o] = 58 as u8; o = o + 1
78 o = as_append(out, o, item); out[o] = 0 as u8; return o
79}
80
81func pr_add_store(prefix: *u8, sid: *u8, name: *u8, chain: *u8, area: *u8, state: *u8, cite: *u8) -> i64 {
82 let key: *u8 = sys_mmap(96); pr_store_key(sid, key)
83 let val: *u8 = sys_mmap(256)
84 var o: i64 = 0
85 o = as_append(val, o, name); val[o] = 9 as u8; o = o + 1
86 o = as_append(val, o, chain); val[o] = 9 as u8; o = o + 1
87 o = as_append(val, o, area); val[o] = 9 as u8; o = o + 1
88 o = as_append(val, o, state); val[o] = 9 as u8; o = o + 1
89 o = as_append(val, o, cite)
90 val[o] = 0 as u8
91 let w: i64 = pr_put(prefix, key, val)
92 let ak: *u8 = sys_mmap(96); pr_area_key(area, ak)
93 pr_list_append(prefix, ak, sid)
94 return w
95}
96
97func pr_store_field(prefix: *u8, sid: *u8, f: i64, out: *u8) -> i64 {
98 let key: *u8 = sys_mmap(96); pr_store_key(sid, key)
99 let pq: *i64 = sys_mmap(16) as *i64
100 let lq: *i64 = sys_mmap(16) as *i64
101 if ss_get(prefix, key, pq, lq) != 1 { out[0] = 0 as u8; return 0 }
102 return fd_field(pq[0] as *u8, lq[0], f, out)
103}
104
105// record a date-stamped price observation (cents). Latest write wins (R0 keeps the current observation).
106func pr_set_price(prefix: *u8, sid: *u8, item: *u8, cents: i64, date: *u8, unit: *u8, source: *u8) -> i64 {
107 let key: *u8 = sys_mmap(96); pr_obs_key(sid, item, key)
108 let val: *u8 = sys_mmap(256)
109 var o: i64 = 0
110 o = fd_apnum(val, o, cents); val[o] = 9 as u8; o = o + 1
111 o = as_append(val, o, date); val[o] = 9 as u8; o = o + 1
112 o = as_append(val, o, unit); val[o] = 9 as u8; o = o + 1
113 o = as_append(val, o, source)
114 val[o] = 0 as u8
115 return pr_put(prefix, key, val)
116}
117// price in cents, or -1 if no observation recorded for (sid,item).
118func pr_get_price(prefix: *u8, sid: *u8, item: *u8) -> i64 {
119 let key: *u8 = sys_mmap(96); pr_obs_key(sid, item, key)
120 let pq: *i64 = sys_mmap(16) as *i64
121 let lq: *i64 = sys_mmap(16) as *i64
122 if ss_get(prefix, key, pq, lq) != 1 { return 0 - 1 }
123 let fb: *u8 = sys_mmap(24)
124 let fl: i64 = fd_field(pq[0] as *u8, lq[0], 0, fb)
125 return fd_atoi(fb, fl)
126}
127
128// a string field of a price observation (1=date, 2=unit, 3=source) -- proves recorded provenance.
129func pr_obs_field(prefix: *u8, sid: *u8, item: *u8, f: i64, out: *u8) -> i64 {
130 let key: *u8 = sys_mmap(96); pr_obs_key(sid, item, key)
131 let pq: *i64 = sys_mmap(16) as *i64
132 let lq: *i64 = sys_mmap(16) as *i64
133 if ss_get(prefix, key, pq, lq) != 1 { out[0] = 0 as u8; return 0 }
134 return fd_field(pq[0] as *u8, lq[0], f, out)
135}
136
137// the sids in an area; returns count.
138func pr_area_stores(prefix: *u8, area: *u8, outsids: *i64) -> i64 {
139 let ak: *u8 = sys_mmap(96); pr_area_key(area, ak)
140 let pq: *i64 = sys_mmap(16) as *i64
141 let lq: *i64 = sys_mmap(16) as *i64
142 if ss_get(prefix, ak, pq, lq) != 1 { return 0 }
143 return pr_split(pq[0] as *u8, lq[0], outsids)
144}
145
146// collect the prices for `item` across an area's stores into outvals; returns count of stores that have it.
147func pr_area_prices(prefix: *u8, area: *u8, item: *u8, outvals: *i64) -> i64 {
148 let sids: *i64 = sys_mmap(8 * 256) as *i64
149 let ns: i64 = pr_area_stores(prefix, area, sids)
150 var c: i64 = 0
151 var i: i64 = 0
152 while i < ns {
153 let p: i64 = pr_get_price(prefix, sids[i] as *u8, item)
154 if p >= 0 { outvals[c] = p; c = c + 1 }
155 i = i + 1
156 }
157 return c
158}
159
160// integer median of an area's prices for an item; -1 if none. (Insertion sort; lower-middle for even counts.)
161func pr_median(prefix: *u8, area: *u8, item: *u8) -> i64 {
162 let v: *i64 = sys_mmap(8 * 256) as *i64
163 let n: i64 = pr_area_prices(prefix, area, item, v)
164 if n == 0 { return 0 - 1 }
165 var i: i64 = 1
166 while i < n {
167 let key: i64 = v[i]
168 var j: i64 = i - 1
169 var go: i64 = 1
170 while go == 1 {
171 if j < 0 { go = 0 } else {
172 if v[j] > key { v[j + 1] = v[j]; j = j - 1 } else { go = 0 }
173 }
174 }
175 v[j + 1] = key
176 i = i + 1
177 }
178 return v[n / 2]
179}
180// cheapest store's price for an item in an area, with its sid via out_sid; -1 if none.
181func pr_cheapest(prefix: *u8, area: *u8, item: *u8, out_sid: *u8) -> i64 {
182 let sids: *i64 = sys_mmap(8 * 256) as *i64
183 let ns: i64 = pr_area_stores(prefix, area, sids)
184 var best: i64 = 0 - 1
185 var bsid: *u8 = "-" as *u8
186 var i: i64 = 0
187 while i < ns {
188 let p: i64 = pr_get_price(prefix, sids[i] as *u8, item)
189 if p >= 0 {
190 var take: i64 = 0
191 if best < 0 { take = 1 } else { if p < best { take = 1 } }
192 if take == 1 { best = p; bsid = sids[i] as *u8 }
193 }
194 i = i + 1
195 }
196 var u: i64 = 0
197 while bsid[u] != (0 as u8) { out_sid[u] = bsid[u]; u = u + 1 } out_sid[u] = 0 as u8
198 return best
199}
200
201// a store's basket total (cents) over items[0..nitems); skips items it doesn't stock.
202func pr_basket(prefix: *u8, sid: *u8, items: *i64, nitems: i64) -> i64 {
203 var s: i64 = 0
204 var i: i64 = 0
205 while i < nitems {
206 let p: i64 = pr_get_price(prefix, sid, items[i] as *u8)
207 if p >= 0 { s = s + p }
208 i = i + 1
209 }
210 return s
211}
212
213// competitiveness INDEX of a store vs its area's medians (avg over the basket of price*100/median). 100 = at the
214// area median; <100 = cheaper than median (doing well for shoppers); >100 = premium. -1 if no comparable items.
215func pr_store_index(prefix: *u8, area: *u8, sid: *u8, items: *i64, nitems: i64) -> i64 {
216 var sum: i64 = 0
217 var cnt: i64 = 0
218 var i: i64 = 0
219 while i < nitems {
220 let p: i64 = pr_get_price(prefix, sid, items[i] as *u8)
221 let med: i64 = pr_median(prefix, area, items[i] as *u8)
222 if p >= 0 { if med > 0 { sum = sum + (p * 100) / med; cnt = cnt + 1 } }
223 i = i + 1
224 }
225 if cnt == 0 { return 0 - 1 }
226 return sum / cnt
227}
228
229// render the area price comparison: stores sorted cheapest-basket first, with basket total + competitiveness index.
230func pr_render_area(prefix: *u8, area: *u8, items: *i64, nitems: i64, out: *u8) -> i64 {
231 let sids: *i64 = sys_mmap(8 * 256) as *i64
232 let ns: i64 = pr_area_stores(prefix, area, sids)
233 // basket per store, then insertion-sort sids by basket ascending
234 let bask: *i64 = sys_mmap(8 * 256) as *i64
235 var i: i64 = 0
236 while i < ns { bask[i] = pr_basket(prefix, sids[i] as *u8, items, nitems); i = i + 1 }
237 i = 1
238 while i < ns {
239 let kb: i64 = bask[i]
240 let ks: i64 = sids[i]
241 var j: i64 = i - 1
242 var go: i64 = 1
243 while go == 1 {
244 if j < 0 { go = 0 } else {
245 if bask[j] > kb { bask[j + 1] = bask[j]; sids[j + 1] = sids[j]; j = j - 1 } else { go = 0 }
246 }
247 }
248 bask[j + 1] = kb; sids[j + 1] = ks
249 i = i + 1
250 }
251 var o: i64 = 0
252 o = as_append(out, o, "<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Nishi price tracker</title><style>body{margin:0;font-family:system-ui,sans-serif;color:#1b2330;background:#eef2f7;line-height:1.5}header{background:#1f3a5f;color:#fff;padding:22px}header h1{margin:0;font-size:1.4rem}header p{margin:4px 0 0;color:#cfe0f5}main{max-width:680px;margin:0 auto;padding:16px}table{border-collapse:collapse;width:100%;background:#fff;border-radius:12px;overflow:hidden}th,td{padding:9px 12px;text-align:left;border-top:1px solid #eef3f8}th{background:#e4ecf6;color:#27405f}td.n{text-align:right;font-variant-numeric:tabular-nums}.best{background:#e6f3e2}.good{color:#1f7a44;font-weight:700}.hi{color:#a23a2a;font-weight:700}.muted{color:#5a6b80;font-size:.88rem}</style></head><body><header><h1>Price tracker — " as *u8)
253 o = as_append_escaped(out, o, area, as_len(area))
254 o = as_append(out, o, "</h1><p>Who's cheapest on the basket, and how each store is doing vs the local median.</p></header><main><table><tr><th>Store</th><th>Basket</th><th>Index (100=median)</th></tr>" as *u8)
255 i = 0
256 while i < ns {
257 let sid: *u8 = sids[i] as *u8
258 let nm: *u8 = sys_mmap(96); pr_store_field(prefix, sid, 0, nm)
259 let idx: i64 = pr_store_index(prefix, area, sid, items, nitems)
260 if i == 0 { o = as_append(out, o, "<tr class='best'><td>" as *u8) } else { o = as_append(out, o, "<tr><td>" as *u8) }
261 o = as_append_escaped(out, o, nm, as_len(nm))
262 o = as_append(out, o, "</td><td class='n'>" as *u8); o = im_money_local(out, o, bask[i])
263 o = as_append(out, o, "</td><td class='n " as *u8)
264 if idx <= 100 { o = as_append(out, o, "good'>" as *u8) } else { o = as_append(out, o, "hi'>" as *u8) }
265 o = fd_apnum(out, o, idx)
266 o = as_append(out, o, "</td></tr>" as *u8)
267 i = i + 1
268 }
269 o = as_append(out, o, "</table><p class='muted'>Prices are recorded, date-stamped observations with a source — not live-scraped or guaranteed current. Index = average of price÷area-median across the basket.</p></main></body></html>" as *u8)
270 out[o] = 0 as u8
271 return o
272}
273
274// local money formatter ($d.cc) so this module doesn't depend on nx_impact.
275func im_money_local(dst: *u8, off: i64, cents: i64) -> i64 {
276 var o: i64 = off
277 dst[o] = 36 as u8; o = o + 1
278 o = fd_apnum(dst, o, cents / 100)
279 dst[o] = 46 as u8; o = o + 1
280 let r: i64 = cents % 100
281 dst[o] = (48 + (r / 10)) as u8; o = o + 1
282 dst[o] = (48 + (r % 10)) as u8; o = o + 1
283 return o
284}