code wiki / _hdl_build / nx_ad_conversion.nx

nx_ad_conversion.nx source

↩ module page · 54 lines · 3064 B

1// nx_ad_conversion.nx -- LIB (no main): privacy-preserving CONVERSION measurement for the 2// "Brought to you by" performance ad engine (ADS-018, CALLOUT-004). The server-side count an 3// advertiser is allowed to SEE for a campaign-period, built to the charter (W-AN-CHARTER-001) 4// instead of against it. NO-FLOATING: this imports its two foundations and cannot run without 5// them -- bot-filter (ADS-019) + k-anon floor (nx_kanon). 6// 7// THE PIPELINE (per campaign-period), all AGGREGATE, never per-visitor: 8// 1. CONSENT-GATE -- only buckets the visitor consented to count (consent==1) 9// 2. BOT-FILTER -- drop invalid traffic via the shared ad_botrules (ADS-019) 10// 3. AGGREGATE SUM -- add the surviving counts into ONE number per campaign-period 11// 4. K-ANON FLOOR -- release that number only if it clears K, else "<k" (-1) suppressed 12// 13// A conversion = a consented, first-party, AGGREGATE-counted outcome (a click-through the visitor 14// chose, a self-reported "interested/bought", or a purchase on a Nishi-hosted property). It is a 15// COUNT per campaign-period, NEVER attributed to an identified individual: the data model has no 16// visitor id / IP / fingerprint -- only class-bucket counts -- so attribution is impossible by 17// construction. (Attribution state, if a campaign uses device-local attribution, lives on the 18// VISITOR-DEVICE vault, ADS-015 -- a separate row; the server only ever receives an incremented 19// count.) license_tier: ORIGINAL 20import "nx_ad_botfilter.nx" 21import "nx_kanon.nx" 22import "nx_syscalls.nx" 23 24// bucket layout = [consent, conv_type, ua_class, src_class, js_class, n] (6 ints). 25// consent : 0=not consented (excluded) 1=consented (counted) 26// conv_type: 0=click-through 1=self-report 2=purchase (informational; all consented types count) 27// ua/src/js: aggregate class codes for the bot-filter (NO identity) 28// n : count of events in this class+consent bucket 29 30// cv_raw: sum of CONSENTED, NON-BOT conversion counts. Steps 1-3. A pure aggregate SUM -- there is 31// no visitor field to attribute against, so this can only ever produce a number, never a profile. 32func cv_raw(ev: *i64, m: i64, rules: *i64, nrules: i64) -> i64 { 33 var total: i64 = 0 34 var b: i64 = 0 35 while b < m { 36 let consent: i64 = ev[b * 6] 37 let ua: i64 = ev[b * 6 + 2] 38 let src: i64 = ev[b * 6 + 3] 39 let js: i64 = ev[b * 6 + 4] 40 let n: i64 = ev[b * 6 + 5] 41 if consent == 1 { if bf_invalid(ua, src, js, rules, nrules) == 0 { total = total + n } } 42 b = b + 1 43 } 44 return total 45} 46 47// cv_release: the complete server-side conversion measurement (steps 1-4). Returns the count an 48// advertiser may see -- the consented, bot-filtered aggregate -- but ONLY if it clears the k-anon 49// floor k; otherwise -1 ("<k", suppressed) so a small campaign can never single out its visitors. 50func cv_release(ev: *i64, m: i64, rules: *i64, nrules: i64, k: i64) -> i64 { 51 let raw: i64 = cv_raw(ev, m, rules, nrules) 52 if kn_release(raw, k) == 1 { return raw } 53 return 0 - 1 54}