code wiki / _hdl_build / nx_skill_request.nx
nx_skill_request.nx source
↩ module page · 215 lines · 8854 B
1// nx_skill_request.nx -- LIB: "my chef hasn't covered this -- ask them." Requests as a DEMAND INSTRUMENT.
2//
3// The estate already banked the rule this is built on: MEASURE demand, never predict desirability, and
4// DUPLICATE COUNT IS POPULARITY -- it is free and already computed. So a request is not a wish list entry, it
5// is a counted signal, and the count is what a chef is actually shown when deciding what to film next.
6//
7// THREE THINGS IT REFUSES TO DO, because each one would corrupt the signal it exists to produce:
8//
9// 1. IT WILL NOT COUNT THE SAME PERSON TWICE. Demand is DISTINCT requesters, not clicks. One enthusiast
10// hitting the button ten times is one person who wants it; counting clicks would let a single loud user
11// outrank a genuinely popular gap, which is precisely the failure mode that makes vote counts worthless.
12//
13// 2. IT WILL NOT REQUEST WHAT ALREADY EXISTS. If the chef has already covered that skill, the request is
14// REFUSED and the existing video is handed back instead. Sending someone a request for work they have
15// already done wastes their goodwill and teaches them to ignore the channel.
16//
17// 3. IT WILL NOT TREAT "WE NEVER INDEXED THEM" AS "THEY HAVEN'T DONE IT." A request against a chef we do not
18// hold is accepted but TAGGED as un-indexed, so it routes to our own crawling backlog rather than to a
19// chef who may already have the video sitting on their channel.
20//
21// Schema (prefix passed in, e.g. knowledge/store/skillreq-):
22// req:ids -> TAB list of request keys
23// req:<chef>:<skill> -> distinct-requester count <t> kind <t> chef-indexed(1|0)
24// reqby:<chef>:<skill> -> TAB list of unit ids that asked (the dedup set AND the audit trail)
25// license_tier: ORIGINAL No hw writes (Rule 26).
26import "nx_skill_video.nx"
27import "nx_meal_plan.nx"
28import "nx_food_science.nx"
29import "nx_seg_store.nx"
30import "nx_syscalls.nx"
31
32const SR_TAB: i64 = 9
33const SR_KEYCAP: i64 = 240
34const SR_VALCAP: i64 = 1024
35const SR_MAXU: i64 = 512
36
37// req record fields
38const SR_F_COUNT: i64 = 0
39const SR_F_KIND: i64 = 1
40const SR_F_INDEXED: i64 = 2
41
42// sr_request outcomes
43const SR_ALREADY_COVERED: i64 = 0 - 2 // refused: the chef has already made this
44const SR_DUPLICATE: i64 = 0 // this unit already asked; the count did NOT move
45const SR_COUNTED: i64 = 1 // a new distinct requester
46
47static SR_KB: i64
48static SR_PQ: i64
49static SR_LQ: i64
50func sr_keybuf() -> *u8 { if SR_KB == 0 { SR_KB = sys_mmap(SR_KEYCAP) as i64 } return SR_KB as *u8 }
51func sr_pq() -> *i64 { if SR_PQ == 0 { SR_PQ = sys_mmap(16) as i64 } return SR_PQ as *i64 }
52func sr_lq() -> *i64 { if SR_LQ == 0 { SR_LQ = sys_mmap(16) as i64 } return SR_LQ as *i64 }
53func sr_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
54
55// "<chef>:<skill>" -- one key per (who we are asking, what we are asking for)
56func sr_pair(chef: *u8, sid: *u8, out: *u8) -> i64 {
57 var o: i64 = as_append(out, 0, chef)
58 out[o] = 58 as u8; o = o + 1
59 o = as_append(out, o, sid)
60 out[o] = 0 as u8
61 return o
62}
63func sr_req_key(chef: *u8, sid: *u8, out: *u8) -> i64 {
64 var o: i64 = as_append(out, 0, "req:" as *u8)
65 o = as_append(out, o, chef)
66 out[o] = 58 as u8; o = o + 1
67 o = as_append(out, o, sid)
68 out[o] = 0 as u8
69 return o
70}
71func sr_by_key(chef: *u8, sid: *u8, out: *u8) -> i64 {
72 var o: i64 = as_append(out, 0, "reqby:" as *u8)
73 o = as_append(out, o, chef)
74 out[o] = 58 as u8; o = o + 1
75 o = as_append(out, o, sid)
76 out[o] = 0 as u8
77 return o
78}
79
80func sr_field(prefix: *u8, chef: *u8, sid: *u8, f: i64, out: *u8) -> i64 {
81 let key: *u8 = sr_keybuf()
82 sr_req_key(chef, sid, key)
83 let pq: *i64 = sr_pq()
84 let lq: *i64 = sr_lq()
85 if ss_get(prefix, key, pq, lq) != 1 { out[0] = 0 as u8; return 0 }
86 return fd_field(pq[0] as *u8, lq[0], f, out)
87}
88
89// DISTINCT requesters for a (chef, skill). 0 when nobody has asked.
90func sr_count(prefix: *u8, chef: *u8, sid: *u8) -> i64 {
91 let b: *u8 = sys_mmap(64)
92 if sr_field(prefix, chef, sid, SR_F_COUNT, b) == 0 { return 0 }
93 return fd_atoi(b, sr_len(b))
94}
95
96// has this unit already asked for this? (the dedup predicate, and the audit trail)
97func sr_has_asked(prefix: *u8, chef: *u8, sid: *u8, unit: *u8) -> i64 {
98 let key: *u8 = sr_keybuf()
99 sr_by_key(chef, sid, key)
100 let pq: *i64 = sr_pq()
101 let lq: *i64 = sr_lq()
102 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
103 let toks: *i64 = sys_mmap(8 * SR_MAXU) as *i64
104 let n: i64 = mp_split(pq[0] as *u8, lq[0], SR_TAB, toks)
105 var i: i64 = 0
106 while i < n { if fd_streq(toks[i] as *u8, unit) == 1 { return 1 } i = i + 1 }
107 return 0
108}
109
110// the requesters, for the audit trail a chef is entitled to see.
111func sr_requesters(prefix: *u8, chef: *u8, sid: *u8, outtoks: *i64) -> i64 {
112 let key: *u8 = sr_keybuf()
113 sr_by_key(chef, sid, key)
114 let pq: *i64 = sr_pq()
115 let lq: *i64 = sr_lq()
116 if ss_get(prefix, key, pq, lq) != 1 { return 0 }
117 return mp_split(pq[0] as *u8, lq[0], SR_TAB, outtoks)
118}
119
120// Record one request.
121// vidprefix = the nx_skill_video registry, consulted so we never ask for work already done
122// out_vid[0] = the existing video id when the outcome is SR_ALREADY_COVERED
123// Returns SR_ALREADY_COVERED / SR_DUPLICATE / SR_COUNTED.
124func sr_request(prefix: *u8, vidprefix: *u8, unit: *u8, chef: *u8, sid: *u8, kind: *u8, out_vid: *i64) -> i64 {
125 out_vid[0] = 0
126 // 2) never ask for what exists. UNKNOWN chef is NOT covered -- it is unknown, and still requestable.
127 let cov: i64 = sv_chef_covers(vidprefix, chef, sid, out_vid)
128 if cov == SV_CHEF_YES { return SR_ALREADY_COVERED }
129 var indexed: i64 = 1
130 if cov == SV_CHEF_UNKNOWN { indexed = 0 }
131
132 // 1) demand is DISTINCT requesters
133 if sr_has_asked(prefix, chef, sid, unit) == 1 { return SR_DUPLICATE }
134
135 let bykey: *u8 = sys_mmap(SR_KEYCAP)
136 sr_by_key(chef, sid, bykey)
137 mp_list_add(prefix, bykey, unit)
138
139 let n: i64 = sr_count(prefix, chef, sid) + 1
140 let key: *u8 = sys_mmap(SR_KEYCAP)
141 sr_req_key(chef, sid, key)
142 let val: *u8 = sys_mmap(SR_VALCAP)
143 var o: i64 = fd_apnum(val, 0, n); val[o] = SR_TAB as u8; o = o + 1
144 o = as_append(val, o, kind); val[o] = SR_TAB as u8; o = o + 1
145 o = fd_apnum(val, o, indexed)
146 val[o] = 0 as u8
147 mp_put(prefix, key, val)
148
149 let pk: *u8 = sys_mmap(SR_KEYCAP)
150 sr_pair(chef, sid, pk)
151 mp_list_add(prefix, "req:ids" as *u8, pk)
152 return SR_COUNTED
153}
154
155// 1 when the request routes to OUR crawling backlog rather than to a chef (we never indexed them).
156func sr_is_unindexed(prefix: *u8, chef: *u8, sid: *u8) -> i64 {
157 let b: *u8 = sys_mmap(64)
158 if sr_field(prefix, chef, sid, SR_F_INDEXED, b) == 0 { return 0 }
159 if fd_atoi(b, sr_len(b)) == 0 { return 1 }
160 return 0
161}
162
163// every (chef, skill) that has been asked for, ranked by DISTINCT requesters, descending.
164// This is the build order a chef is shown -- measured, never guessed.
165func sr_rank(prefix: *u8, out_pairs: *i64, out_counts: *i64, max: i64) -> i64 {
166 let pq: *i64 = sr_pq()
167 let lq: *i64 = sr_lq()
168 if ss_get(prefix, "req:ids" as *u8, pq, lq) != 1 { return 0 }
169 let all: *i64 = sys_mmap(8 * SR_MAXU) as *i64
170 let n: i64 = mp_split(pq[0] as *u8, lq[0], SR_TAB, all)
171 var c: i64 = 0
172 var i: i64 = 0
173 while i < n {
174 let pair: *u8 = all[i] as *u8
175 // split "<chef>:<skill>" back apart
176 let chef: *u8 = sys_mmap(SR_KEYCAP)
177 let sid: *u8 = sys_mmap(SR_KEYCAP)
178 var k: i64 = 0
179 var cut: i64 = 0 - 1
180 while pair[k] != (0 as u8) { if pair[k] == (58 as u8) { cut = k } k = k + 1 }
181 if cut > 0 {
182 var a: i64 = 0
183 while a < cut { chef[a] = pair[a]; a = a + 1 }
184 chef[cut] = 0 as u8
185 var b: i64 = 0
186 var p: i64 = cut + 1
187 while pair[p] != (0 as u8) { sid[b] = pair[p]; b = b + 1; p = p + 1 }
188 sid[b] = 0 as u8
189 let cnt: i64 = sr_count(prefix, chef, sid)
190 // insertion sort, descending, bounded
191 var pos: i64 = c
192 var scan: i64 = 1
193 var j: i64 = 0
194 while scan == 1 {
195 if j >= c { pos = c; scan = 0 } else {
196 if out_counts[j] < cnt { pos = j; scan = 0 } else { j = j + 1 }
197 }
198 }
199 if pos < max {
200 var m: i64 = c
201 if m >= max { m = max - 1 }
202 while m > pos {
203 out_counts[m] = out_counts[m - 1]
204 out_pairs[m] = out_pairs[m - 1]
205 m = m - 1
206 }
207 out_counts[pos] = cnt
208 out_pairs[pos] = pair as i64
209 if c < max { c = c + 1 }
210 }
211 }
212 i = i + 1
213 }
214 return c
215}