code wiki / _hdl_build / nx_skill_video.nx

nx_skill_video.nx source

↩ module page · 248 lines · 9428 B

1// nx_skill_video.nx -- LIB: WHO you learn a technique from is the learner's choice, not ours. 2// 3// For any skill the coach teaches, there may be several ways to watch it done: a PROFESSIONAL source, an 4// AMATEUR one (often better -- a home cook's kitchen looks like yours), or a specific CHEF the learner already 5// follows. This registry holds those sources per skill, keeps the chefs a person follows, and answers the 6// question that actually gets asked: "has the cook I like already covered this?" 7// 8// THAT QUESTION IS A TRI-STATE, ON PURPOSE. sv_chef_covers returns -1 for a chef we have never heard of, 0 for 9// a chef we know who has not covered this skill, and 1 for covered. Collapsing the first two into "no" would 10// tell someone their favourite chef has not made the video when the truth is we never indexed them -- and that 11// wrong answer is exactly what would send a pointless request to a chef who already has one. 12// 13// LINK ROT IS THE SAME PROBLEM AS THE RECIPE. A technique video is a citation like any other, so this reuses 14// ms_is_archived from nx_meal_source rather than growing a second archive predicate: a source renders as 15// preserved ONLY when its bytes actually come back out of the WARC. 16// 17// Schema (prefix passed in, e.g. knowledge/store/skillvid-): 18// svid:ids -> TAB list of video ids 19// svid:<vid> -> skill <t> title <t> creator <t> kind <t> url <t> licence <t> seconds <t> chef-id 20// svid:skill:<sid> -> TAB list of video ids teaching that skill 21// chef:ids -> TAB list of chef ids 22// chef:<cid> -> display name <t> handle <t> home url 23// follow:<unit> -> TAB list of chef ids this unit follows 24// license_tier: ORIGINAL No hw writes (Rule 26). 25import "nx_meal_plan.nx" 26import "nx_meal_source.nx" 27import "nx_food_science.nx" 28import "nx_seg_store.nx" 29import "nx_syscalls.nx" 30 31const SV_TAB: i64 = 9 32const SV_KEYCAP: i64 = 200 33const SV_VALCAP: i64 = 2048 34const SV_MAXV: i64 = 256 35 36// svid:<vid> fields 37const SV_F_SKILL: i64 = 0 38const SV_F_TITLE: i64 = 1 39const SV_F_CREATOR: i64 = 2 40const SV_F_KIND: i64 = 3 41const SV_F_URL: i64 = 4 42const SV_F_LICENSE: i64 = 5 43const SV_F_SECONDS: i64 = 6 44const SV_F_CHEF: i64 = 7 45 46// chef:<cid> fields 47const SV_C_NAME: i64 = 0 48const SV_C_HANDLE: i64 = 1 49const SV_C_HOME: i64 = 2 50 51// sv_chef_covers outcomes -- three distinct facts, never two 52const SV_CHEF_UNKNOWN: i64 = 0 - 1 53const SV_CHEF_NO: i64 = 0 54const SV_CHEF_YES: i64 = 1 55 56static SV_KB: i64 57static SV_PQ: i64 58static SV_LQ: i64 59func sv_keybuf() -> *u8 { if SV_KB == 0 { SV_KB = sys_mmap(SV_KEYCAP) as i64 } return SV_KB as *u8 } 60func sv_pq() -> *i64 { if SV_PQ == 0 { SV_PQ = sys_mmap(16) as i64 } return SV_PQ as *i64 } 61func sv_lq() -> *i64 { if SV_LQ == 0 { SV_LQ = sys_mmap(16) as i64 } return SV_LQ as *i64 } 62func sv_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 63 64static SV_EMPTY: i64 65func sv_empty() -> *u8 { 66 if SV_EMPTY == 0 { SV_EMPTY = sys_mmap(8) as i64 } 67 let b: *u8 = SV_EMPTY as *u8 68 b[0] = 0 as u8 69 return b 70} 71 72func sv_field_at(prefix: *u8, key: *u8, f: i64, out: *u8) -> i64 { 73 let pq: *i64 = sv_pq() 74 let lq: *i64 = sv_lq() 75 if ss_get(prefix, key, pq, lq) != 1 { out[0] = 0 as u8; return 0 } 76 return fd_field(pq[0] as *u8, lq[0], f, out) 77} 78 79// ---- videos ---- 80 81func sv_vid_key(vid: *u8, out: *u8) -> i64 { 82 var o: i64 = as_append(out, 0, "svid:" as *u8) 83 o = as_append(out, o, vid) 84 out[o] = 0 as u8 85 return o 86} 87func sv_skill_key(sid: *u8, out: *u8) -> i64 { 88 var o: i64 = as_append(out, 0, "svid:skill:" as *u8) 89 o = as_append(out, o, sid) 90 out[o] = 0 as u8 91 return o 92} 93 94// kind is "pro" | "amateur" | "chef". chef is the chef id for kind=chef, else an empty buffer. 95func sv_add_video(prefix: *u8, vid: *u8, sid: *u8, title: *u8, creator: *u8, kind: *u8, url: *u8, license: *u8, seconds: i64, chef: *u8) -> i64 { 96 let key: *u8 = sys_mmap(SV_KEYCAP) 97 sv_vid_key(vid, key) 98 let val: *u8 = sys_mmap(SV_VALCAP) 99 var o: i64 = 0 100 o = as_append(val, o, sid); val[o] = SV_TAB as u8; o = o + 1 101 o = as_append(val, o, title); val[o] = SV_TAB as u8; o = o + 1 102 o = as_append(val, o, creator); val[o] = SV_TAB as u8; o = o + 1 103 o = as_append(val, o, kind); val[o] = SV_TAB as u8; o = o + 1 104 o = as_append(val, o, url); val[o] = SV_TAB as u8; o = o + 1 105 o = as_append(val, o, license); val[o] = SV_TAB as u8; o = o + 1 106 o = fd_apnum(val, o, seconds); val[o] = SV_TAB as u8; o = o + 1 107 o = as_append(val, o, chef) 108 val[o] = 0 as u8 109 let w: i64 = mp_put(prefix, key, val) 110 mp_list_add(prefix, "svid:ids" as *u8, vid) 111 let sk: *u8 = sys_mmap(SV_KEYCAP) 112 sv_skill_key(sid, sk) 113 mp_list_add(prefix, sk, vid) 114 return w 115} 116 117func sv_field(prefix: *u8, vid: *u8, f: i64, out: *u8) -> i64 { 118 let key: *u8 = sv_keybuf() 119 sv_vid_key(vid, key) 120 return sv_field_at(prefix, key, f, out) 121} 122 123func sv_seconds(prefix: *u8, vid: *u8) -> i64 { 124 let b: *u8 = sys_mmap(64) 125 if sv_field(prefix, vid, SV_F_SECONDS, b) == 0 { return 0 - 1 } 126 return fd_atoi(b, sv_len(b)) 127} 128 129// every video teaching a skill; returns the count. 130func sv_by_skill(prefix: *u8, sid: *u8, outtoks: *i64) -> i64 { 131 let key: *u8 = sv_keybuf() 132 sv_skill_key(sid, key) 133 let pq: *i64 = sv_pq() 134 let lq: *i64 = sv_lq() 135 if ss_get(prefix, key, pq, lq) != 1 { return 0 } 136 return mp_split(pq[0] as *u8, lq[0], SV_TAB, outtoks) 137} 138 139// the learner's filter: only professional sources, only amateur ones, or only a followed chef's. 140func sv_by_skill_kind(prefix: *u8, sid: *u8, kind: *u8, outtoks: *i64) -> i64 { 141 let all: *i64 = sys_mmap(8 * SV_MAXV) as *i64 142 let n: i64 = sv_by_skill(prefix, sid, all) 143 let k: *u8 = sys_mmap(64) 144 var c: i64 = 0 145 var i: i64 = 0 146 while i < n { 147 let vid: *u8 = all[i] as *u8 148 if sv_field(prefix, vid, SV_F_KIND, k) > 0 { 149 if fd_streq(k, kind) == 1 { outtoks[c] = vid as i64; c = c + 1 } 150 } 151 i = i + 1 152 } 153 return c 154} 155 156// ---- chefs, and who a learner follows ---- 157 158func sv_chef_key(cid: *u8, out: *u8) -> i64 { 159 var o: i64 = as_append(out, 0, "chef:" as *u8) 160 o = as_append(out, o, cid) 161 out[o] = 0 as u8 162 return o 163} 164func sv_follow_key(unit: *u8, out: *u8) -> i64 { 165 var o: i64 = as_append(out, 0, "follow:" as *u8) 166 o = as_append(out, o, unit) 167 out[o] = 0 as u8 168 return o 169} 170 171func sv_add_chef(prefix: *u8, cid: *u8, name: *u8, handle: *u8, home: *u8) -> i64 { 172 let key: *u8 = sys_mmap(SV_KEYCAP) 173 sv_chef_key(cid, key) 174 let val: *u8 = sys_mmap(SV_VALCAP) 175 var o: i64 = 0 176 o = as_append(val, o, name); val[o] = SV_TAB as u8; o = o + 1 177 o = as_append(val, o, handle); val[o] = SV_TAB as u8; o = o + 1 178 o = as_append(val, o, home) 179 val[o] = 0 as u8 180 let w: i64 = mp_put(prefix, key, val) 181 mp_list_add(prefix, "chef:ids" as *u8, cid) 182 return w 183} 184 185func sv_chef_field(prefix: *u8, cid: *u8, f: i64, out: *u8) -> i64 { 186 let key: *u8 = sv_keybuf() 187 sv_chef_key(cid, key) 188 return sv_field_at(prefix, key, f, out) 189} 190 191func sv_chef_known(prefix: *u8, cid: *u8) -> i64 { 192 let b: *u8 = sys_mmap(SV_VALCAP) 193 if sv_chef_field(prefix, cid, SV_C_NAME, b) == 0 { return 0 } 194 return 1 195} 196 197func sv_follow(prefix: *u8, unit: *u8, cid: *u8) -> i64 { 198 let key: *u8 = sys_mmap(SV_KEYCAP) 199 sv_follow_key(unit, key) 200 return mp_list_add(prefix, key, cid) 201} 202 203func sv_follows(prefix: *u8, unit: *u8, outtoks: *i64) -> i64 { 204 let key: *u8 = sv_keybuf() 205 sv_follow_key(unit, key) 206 let pq: *i64 = sv_pq() 207 let lq: *i64 = sv_lq() 208 if ss_get(prefix, key, pq, lq) != 1 { return 0 } 209 return mp_split(pq[0] as *u8, lq[0], SV_TAB, outtoks) 210} 211 212// THE QUESTION PEOPLE ACTUALLY ASK: has the cook I follow covered this? 213// SV_CHEF_UNKNOWN -- we have never indexed this chef; we do not know, and must not say no 214// SV_CHEF_NO -- we know them, and they have not covered this skill (a legitimate request) 215// SV_CHEF_YES -- covered; the video id lands in out_vid 216func sv_chef_covers(prefix: *u8, cid: *u8, sid: *u8, out_vid: *i64) -> i64 { 217 if sv_chef_known(prefix, cid) == 0 { return SV_CHEF_UNKNOWN } 218 let all: *i64 = sys_mmap(8 * SV_MAXV) as *i64 219 let n: i64 = sv_by_skill(prefix, sid, all) 220 let c: *u8 = sys_mmap(SV_KEYCAP) 221 var i: i64 = 0 222 while i < n { 223 let vid: *u8 = all[i] as *u8 224 if sv_field(prefix, vid, SV_F_CHEF, c) > 0 { 225 if fd_streq(c, cid) == 1 { out_vid[0] = vid as i64; return SV_CHEF_YES } 226 } 227 i = i + 1 228 } 229 out_vid[0] = 0 230 return SV_CHEF_NO 231} 232 233// ---- link rot, reusing the recipe's own predicate ---- 234 235// 1 only when this video's URL actually resolves to preserved bytes. A surface may render "archived copy" 236// ONLY on a 1 -- the same rule the recipe credit strip follows, and the same function enforcing it. 237func sv_is_preserved(prefix: *u8, vid: *u8, warc: *u8, warclen: i64) -> i64 { 238 let u: *u8 = sys_mmap(SV_VALCAP) 239 if sv_field(prefix, vid, SV_F_URL, u) == 0 { return 0 } 240 return ms_is_archived(warc, warclen, u) 241} 242 243// preserved byte count for a video source, or -1 when nothing is held. 244func sv_preserved_bytes(prefix: *u8, vid: *u8, warc: *u8, warclen: i64) -> i64 { 245 let u: *u8 = sys_mmap(SV_VALCAP) 246 if sv_field(prefix, vid, SV_F_URL, u) == 0 { return 0 - 1 } 247 return ms_archive_bytes(warc, warclen, u) 248}