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}