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}