@sentientui/policy 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/index.d.cts +284 -49
- package/dist/index.d.ts +284 -49
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,14 +30,16 @@ replayable decisions.
|
|
|
30
30
|
- `validateSlotDecl(decl)` — structural validation of a `SlotDecl`, returning `{ ok: true }` or `{ ok: false, reason }`.
|
|
31
31
|
|
|
32
32
|
**Layout selection** (`layout-heuristics.ts`, `choose-layout.ts`, `hash.ts`)
|
|
33
|
-
- `candidateLayouts(sections, sectionTypes,
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
33
|
+
- `candidateLayouts(sections, sectionTypes, sectionRoles?)` — the candidate orderings for a PAGE: the authored order plus each archetype's. Takes no persona — the persona selects posteriors, not which layouts are reachable.
|
|
34
|
+
- `LAYOUT_ARCHETYPES` / `orderByArchetype(sections, sectionTypes, archetype, sectionRoles?)` — a catalogue of orderings (`conversion_led`, `evidence_led`, `price_led`, `discovery_led`). Not a persona taxonomy.
|
|
35
|
+
- `previewOrderForPersona(sections, sectionTypes, persona, sectionRoles?)` — **keyless local mode only.** Maps any persona key onto an archetype so `?sentient_persona=<anything>` visibly rearranges a page with no server. Never use it for serving.
|
|
36
|
+
- `chooseLayout(sections, sectionTypes, persona, learned, rand?)` — Thompson-samples the learned layout posteriors over the candidates. The authored order is always among them, so "leave the page alone" can win.
|
|
37
|
+
- *Deprecated:* `applyClusterHeuristic` (use `orderByArchetype` on the server, `previewOrderForPersona` locally).
|
|
36
38
|
- `hashLayout(order)` — stable hash of a section order (the `layoutHash` key).
|
|
37
39
|
|
|
38
40
|
**Personas** (`personas.ts`)
|
|
39
|
-
- `
|
|
40
|
-
- `canonicalPersona(label)` — normalize
|
|
41
|
+
- `UNKNOWN_PERSONA`, `UNKNOWN_PERSONA_DISPLAY` — the only persona key the package defines. There is no built-in persona list: every persona comes from the project's own vocabulary (declared by the app, or promoted by discovery).
|
|
42
|
+
- `canonicalPersona(label)` — normalize any label to a persona key (trimmed, lowercased, key-shaped), or `'unknown'`. Says nothing about vocabulary membership.
|
|
41
43
|
|
|
42
44
|
**Deterministic helpers** (`deterministic.ts`)
|
|
43
45
|
- `fnv1a(input)` — FNV-1a hash.
|
|
@@ -65,7 +67,7 @@ const chosenSeeded = sampleArm(arms, mySeededRng);
|
|
|
65
67
|
import { chooseLayout, hashLayout, type LearnedLayout } from '@sentientui/policy';
|
|
66
68
|
|
|
67
69
|
const learned = new Map<string, LearnedLayout>(); // from your layout_weights store
|
|
68
|
-
const order = chooseLayout(sections, sectionTypes, '
|
|
70
|
+
const order = chooseLayout(sections, sectionTypes, 'evaluator', learned); // a key from the project's own vocabulary
|
|
69
71
|
const key = hashLayout(order);
|
|
70
72
|
```
|
|
71
73
|
|
package/dist/index.d.cts
CHANGED
|
@@ -1,50 +1,111 @@
|
|
|
1
1
|
import { SectionRole } from './taxonomy.cjs';
|
|
2
2
|
|
|
3
|
-
declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
|
|
4
|
-
type Persona = (typeof PERSONAS)[number];
|
|
5
|
-
declare const UNKNOWN_PERSONA: "unknown";
|
|
6
|
-
type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
|
|
7
|
-
/** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
|
|
8
|
-
declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* The one persona key the product itself defines: "we don't know".
|
|
5
|
+
*
|
|
6
|
+
* There is deliberately no persona LIST here. Until 2026-09-13 this module
|
|
7
|
+
* exported a hardcoded four-persona vocabulary (with display names and a
|
|
8
|
+
* plural-label alias map) that every project inherited, and the product
|
|
9
|
+
* presented those names as if it knew the customer's audience. Every persona
|
|
10
|
+
* now comes from the project's own vocabulary — declared by the app or
|
|
11
|
+
* promoted by discovery (see `vocabulary.ts`) — and anything else is
|
|
12
|
+
* `unknown`, which is where the pooled bandit does the actual work.
|
|
13
13
|
*/
|
|
14
|
-
declare const
|
|
14
|
+
declare const UNKNOWN_PERSONA: "unknown";
|
|
15
|
+
/** Human-facing name of `UNKNOWN_PERSONA`. Every other persona's display name
|
|
16
|
+
* comes from its vocabulary member, never from a table in this package. */
|
|
17
|
+
declare const UNKNOWN_PERSONA_DISPLAY = "Unknown";
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
20
|
+
* A catalogue of plausible page orderings — NOT a persona taxonomy.
|
|
21
|
+
*
|
|
22
|
+
* These four orderings were keyed by the four seeded persona names until
|
|
23
|
+
* 2026-09-13 (the table was exported as `CLUSTER_PRIORITY`, now removed).
|
|
24
|
+
* Those personas were removed, but the deeper problem was that keying orderings by persona NAME was
|
|
25
|
+
* wrong even while they existed, in two ways that were invisible until a
|
|
26
|
+
* customer declared a persona of their own:
|
|
27
|
+
*
|
|
28
|
+
* 1. THE ARM SPACE DEPENDED ON WHAT THE CUSTOMER NAMED THEIR PERSONA. A
|
|
29
|
+
* project that declared `admin` got five candidate orders including the
|
|
30
|
+
* page exactly as authored; a project that declared one of the seeded names got four, and
|
|
31
|
+
* the authored order was NOT among them — so its own page order could never
|
|
32
|
+
* be served to that persona, and there was no control arm to lose to.
|
|
33
|
+
* Renaming a persona silently changed which layouts were reachable.
|
|
34
|
+
*
|
|
35
|
+
* 2. THE FEASIBILITY GATE SERVED THE COLLIDING ARCHETYPE DETERMINISTICALLY.
|
|
36
|
+
* `routes/decide.ts` serves a heuristic order (never explored, never
|
|
37
|
+
* learned) when layout feasibility is `infeasible` — the state every new
|
|
38
|
+
* project is in. A customer who declared the seeded conversion persona had pricing hoisted above
|
|
39
|
+
* everything on every visit, permanently, because of a table written in
|
|
40
|
+
* May; a customer who declared `admin` correctly kept their own page.
|
|
41
|
+
*
|
|
42
|
+
* So the orderings are now named for what they DO. The names are internal and
|
|
43
|
+
* carry no claim about any visitor: they are four opinions about what a page
|
|
44
|
+
* should lead with, and the bandit decides between them and the authored order
|
|
45
|
+
* using evidence. The arrays are unchanged, so every `layout_weights` row in
|
|
46
|
+
* production still joins — `hashLayout` hashes the resulting ORDER, never the
|
|
47
|
+
* key that produced it.
|
|
19
48
|
*/
|
|
20
|
-
declare
|
|
21
|
-
|
|
22
|
-
|
|
49
|
+
declare const LAYOUT_ARCHETYPES: Record<string, readonly string[]>;
|
|
50
|
+
/** The archetype names, in a pinned order — iteration order decides candidate
|
|
51
|
+
* insertion order, so it must not depend on object-key enumeration luck. */
|
|
52
|
+
declare const LAYOUT_ARCHETYPE_NAMES: readonly ["conversion_led", "evidence_led", "price_led", "discovery_led"];
|
|
53
|
+
type LayoutArchetype = (typeof LAYOUT_ARCHETYPE_NAMES)[number];
|
|
23
54
|
/**
|
|
24
|
-
* Reorders section IDs
|
|
55
|
+
* Reorders section IDs by one archetype's semantic priority.
|
|
25
56
|
* Sections with no graph entry are treated as 'generic'.
|
|
26
|
-
* Returns the input unchanged for 'unknown' — and for any custom vocabulary
|
|
27
|
-
* persona (declared/discovered): those have no semantic prior, so they serve
|
|
28
|
-
* the natural order until the layout bandit has learned rows, the same
|
|
29
|
-
* cold-start posture 'unknown' gets.
|
|
30
57
|
*
|
|
31
58
|
* With `sectionRoles` (spec 2026-09-04 §1, phase 2d) the ordering projection is
|
|
32
59
|
* `(role, parent)`: structural sections are PINNED at their original index and
|
|
33
60
|
* only converters/persuaders re-rank around them. The pin is not cosmetic —
|
|
34
|
-
* 'navigation' ranks near last in every
|
|
35
|
-
*
|
|
61
|
+
* 'navigation' ranks near last in every archetype, so an unpinned navbar or
|
|
62
|
+
* footer would sort to the bottom of the page, exactly the visible damage a
|
|
36
63
|
* reorder must never do. Callers without role data (the client-local fallback)
|
|
37
64
|
* omit the map and get the pre-2d behaviour unchanged.
|
|
38
65
|
*/
|
|
39
|
-
declare function
|
|
66
|
+
declare function orderByArchetype(sections: string[], sectionTypes: Map<string, string>, archetype: LayoutArchetype, sectionRoles?: Map<string, SectionRole>): string[];
|
|
40
67
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
68
|
+
* PREVIEW ONLY — the keyless local engine (`@sentientui/core` index-local).
|
|
69
|
+
*
|
|
70
|
+
* Maps an arbitrary persona string to one archetype so that
|
|
71
|
+
* `?sentient_persona=<anything>` visibly rearranges a page with no API key and
|
|
72
|
+
* no server. It used to look the key up in the four-persona table, so only
|
|
73
|
+
* the four seeded names did anything and every other key silently no-oped;
|
|
74
|
+
* now any key previews an arrangement, which is what the docs promise.
|
|
75
|
+
*
|
|
76
|
+
* DO NOT USE THIS ON THE SERVER. The mapping is a hash, not a belief: it
|
|
77
|
+
* carries no claim that this persona wants this ordering. Server serving picks
|
|
78
|
+
* between the archetypes and the authored order with `chooseLayout` /
|
|
79
|
+
* `chooseLayoutFactored`, on evidence.
|
|
80
|
+
*
|
|
81
|
+
* 'unknown' and the empty string return the sections untouched — the natural
|
|
82
|
+
* order is what an unidentified visitor gets, here as everywhere.
|
|
83
|
+
*/
|
|
84
|
+
declare function previewOrderForPersona(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated Renamed. Use `orderByArchetype` on the server (by archetype) or
|
|
87
|
+
* `previewOrderForPersona` in the keyless local engine (by arbitrary key).
|
|
88
|
+
* Kept as an alias of the preview mapping so the published signature survives.
|
|
46
89
|
*/
|
|
47
|
-
declare
|
|
90
|
+
declare const applyClusterHeuristic: typeof previewOrderForPersona;
|
|
91
|
+
/**
|
|
92
|
+
* The candidate layout orderings for a page — the arms the layout bandit
|
|
93
|
+
* explores. Returned as hash → order so it joins directly against
|
|
94
|
+
* `layout_weights` rows keyed by the same `hashLayout`.
|
|
95
|
+
*
|
|
96
|
+
* THE AUTHORED ORDER IS ALWAYS AN ARM. It is the control: the customer built
|
|
97
|
+
* this page in this order, and a bandit whose arm space excludes the baseline
|
|
98
|
+
* can never conclude "leave it alone" — it is structurally obliged to reorder
|
|
99
|
+
* something, and there is nothing for a holdout comparison to mean. Before
|
|
100
|
+
* 2026-09-13 the authored order was included only by accident, when the
|
|
101
|
+
* requesting persona's name happened to miss the archetype table; a persona
|
|
102
|
+
* whose name matched the table had it excluded entirely.
|
|
103
|
+
*
|
|
104
|
+
* It no longer takes a persona. The set of orderings a page COULD be shown in
|
|
105
|
+
* is a property of the page, not of who is looking at it — what the persona
|
|
106
|
+
* changes is which arm wins, and that is the posterior's job.
|
|
107
|
+
*/
|
|
108
|
+
declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
|
|
48
109
|
|
|
49
110
|
/**
|
|
50
111
|
* Stable 16-char SHA-256 prefix for a section order array.
|
|
@@ -69,15 +130,25 @@ type LearnedLayout = {
|
|
|
69
130
|
* Thompson-samples the layout order to serve a persona over the candidate
|
|
70
131
|
* orderings, using learned posteriors from layout_weights. Candidates with no
|
|
71
132
|
* learned row use the uniform 1/1 prior — identical to variant cold start.
|
|
72
|
-
*
|
|
133
|
+
*
|
|
134
|
+
* The candidate set always contains the AUTHORED order, so "leave this page
|
|
135
|
+
* alone" is a real arm that can win, and at cold start it is exactly as likely
|
|
136
|
+
* as any reorder. This used to fall back to the persona's heuristic when
|
|
137
|
+
* sampling yielded nothing — which could not happen (candidateLayouts is never
|
|
138
|
+
* empty) and would have been the wrong answer anyway: with nothing to choose
|
|
139
|
+
* on, the page the customer built is the only defensible thing to serve.
|
|
140
|
+
*
|
|
141
|
+
* `persona` no longer selects candidates — the orderings a page COULD be shown
|
|
142
|
+
* in are a property of the page. It stays in the signature because it is what
|
|
143
|
+
* the CALLER keyed `learned` by, which is where the persona belongs.
|
|
73
144
|
*
|
|
74
145
|
* @param rand Uniform [0,1) source. Defaults to `Math.random`, which is
|
|
75
146
|
* NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
|
|
76
147
|
* (tests, replayable decisions) — otherwise the sampled order varies per call.
|
|
77
148
|
* @param sectionRoles Optional role map (phase 2d): structural sections are
|
|
78
|
-
* pinned in place across every candidate; see `
|
|
149
|
+
* pinned in place across every candidate; see `orderByArchetype`.
|
|
79
150
|
*/
|
|
80
|
-
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>,
|
|
151
|
+
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, _persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
|
|
81
152
|
|
|
82
153
|
/**
|
|
83
154
|
* Factored layout value model (spec 2026-09-04 §3a).
|
|
@@ -133,10 +204,130 @@ declare function factorCellsForOrder(order: string[], sectionTypes: Map<string,
|
|
|
133
204
|
* start degrades gracefully: empty cells draw from Beta(1,1), which still
|
|
134
205
|
* randomises across candidates, so exploration survives the switch.
|
|
135
206
|
*
|
|
136
|
-
*
|
|
207
|
+
* The candidate set always contains the AUTHORED order, so leaving the page as
|
|
208
|
+
* built is a real arm rather than something only reachable by accident.
|
|
137
209
|
*/
|
|
138
210
|
declare function chooseLayoutFactored(sections: string[], sectionTypes: Map<string, string>, persona: string, cells: LayoutFactorCell[], rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
|
|
139
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Factored slot value model — the slot analogue of `layout-factored.ts`.
|
|
214
|
+
*
|
|
215
|
+
* `slot_weights` keys one independent Beta posterior per
|
|
216
|
+
* (arm, segment, persona), where `segment` is the joined `device:source`. That
|
|
217
|
+
* is why the decision context stopped at two fields: every field ADDED
|
|
218
|
+
* multiplies the cell count. Three devices x six sources x five personas is
|
|
219
|
+
* already 90 cells per arm; adding new-vs-returning makes it 180, adding
|
|
220
|
+
* country multiplies again — and each cell estimates from its own slice of the
|
|
221
|
+
* same thin traffic, so a richer context converges WORSE, not better.
|
|
222
|
+
*
|
|
223
|
+
* This model replaces one-parameter-per-context-combination with one per
|
|
224
|
+
* context FACTOR:
|
|
225
|
+
*
|
|
226
|
+
* V(arm | context) = Σ_f draw[arm, factor f, level of f in this context]
|
|
227
|
+
*
|
|
228
|
+
* Four factors at 3 + 6 + 5 + 2 levels is 16 cells per arm instead of 180, and
|
|
229
|
+
* every trial teaches every context that shares ANY factor level: a conversion
|
|
230
|
+
* on mobile/paid/returning updates "this arm on mobile", which transfers to
|
|
231
|
+
* mobile/organic/new. Additive over factors is the same shape the layout model
|
|
232
|
+
* uses additively over positions, and it is fair across arms for the same
|
|
233
|
+
* reason — every arm in one request is scored under the identical factor set,
|
|
234
|
+
* so the sum has the same number of terms for each.
|
|
235
|
+
*
|
|
236
|
+
* It improves sample efficiency; it does not manufacture signal. With a handful
|
|
237
|
+
* of conversions no model learns a ranking, which is what the feasibility gate
|
|
238
|
+
* exists to say out loud.
|
|
239
|
+
*/
|
|
240
|
+
/** Persona key of the pooled global cells. Reserved — never a real persona. */
|
|
241
|
+
declare const GLOBAL_FACTOR_LEVEL = "__global__";
|
|
242
|
+
/**
|
|
243
|
+
* Context factors the model conditions on, in a pinned order.
|
|
244
|
+
*
|
|
245
|
+
* Widening this list is the whole point of the design — it costs O(levels), not
|
|
246
|
+
* O(product) — but it is NOT free: each factor adds a term to the sum, and a
|
|
247
|
+
* factor with no signal adds variance to every score. Add one when there is a
|
|
248
|
+
* reason to believe it changes which arm wins, not because the column exists.
|
|
249
|
+
*/
|
|
250
|
+
declare const SLOT_FACTORS: readonly ["device", "source", "persona", "visit"];
|
|
251
|
+
type SlotFactor = (typeof SLOT_FACTORS)[number];
|
|
252
|
+
type SlotFactorContext = {
|
|
253
|
+
device: string | null;
|
|
254
|
+
source: string | null;
|
|
255
|
+
persona: string;
|
|
256
|
+
/** 'new' | 'returning' | null when the visit count is unknown. */
|
|
257
|
+
visit: string | null;
|
|
258
|
+
};
|
|
259
|
+
type SlotFactorCell = {
|
|
260
|
+
arm: string;
|
|
261
|
+
factor: string;
|
|
262
|
+
/** Factor level, or GLOBAL_FACTOR_LEVEL for the arm's context-free row. */
|
|
263
|
+
level: string;
|
|
264
|
+
exposures: number;
|
|
265
|
+
conversions: number;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* The (factor, level) pairs one served decision contributes to — the write-side
|
|
269
|
+
* projection close-out uses, and the read-side lookup serving uses. Both call
|
|
270
|
+
* this so the two can never disagree about what a context decomposes into.
|
|
271
|
+
*
|
|
272
|
+
* An UNKNOWN persona contributes no persona term. That is the Pareto safety
|
|
273
|
+
* invariant of CONTRACTS §4 restated for this model: unknown-persona traffic
|
|
274
|
+
* must run on exactly the persona-agnostic policy, so the persona factor is not
|
|
275
|
+
* merely empty for them, it is absent — an `unknown` LEVEL would otherwise
|
|
276
|
+
* become a real segment that accumulates its own rate and steers serving.
|
|
277
|
+
*
|
|
278
|
+
* A null device/source/visit is likewise absent rather than levelled as
|
|
279
|
+
* 'unknown', for the same reason: "not measured" is not a level.
|
|
280
|
+
*/
|
|
281
|
+
declare function factorLevelsFor(ctx: SlotFactorContext): Array<{
|
|
282
|
+
factor: SlotFactor;
|
|
283
|
+
level: string;
|
|
284
|
+
}>;
|
|
285
|
+
type FactoredSlotChoice = {
|
|
286
|
+
arm: string;
|
|
287
|
+
factorsUsed: number;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Thompson-style selection over the factored model.
|
|
291
|
+
*
|
|
292
|
+
* ONE draw per (arm, factor, level) cell, cached for the call — an arm's score
|
|
293
|
+
* is the sum of its factor draws, and every arm is scored under the same factor
|
|
294
|
+
* set, so the comparison is made in a single sampled world. A fresh draw per
|
|
295
|
+
* comparison would add pure noise to the ranking rather than exploration.
|
|
296
|
+
*
|
|
297
|
+
* Shrinkage follows CONTRACTS §4 exactly: a factor-level cell shrinks toward
|
|
298
|
+
* the arm's GLOBAL cell, and the global cell shrinks toward the slot's pooled
|
|
299
|
+
* rate across all arms. The MEAN crosses each boundary, never the sample size —
|
|
300
|
+
* so a thin factor level keeps a posterior as wide as its own evidence warrants
|
|
301
|
+
* and Thompson sampling still explores it.
|
|
302
|
+
*
|
|
303
|
+
* Cold start degrades to the unfactored behaviour: with no cells at all every
|
|
304
|
+
* arm draws from the same shrunken pool, which still randomises, so exploration
|
|
305
|
+
* survives enabling this model on a project with history.
|
|
306
|
+
*/
|
|
307
|
+
declare function chooseSlotArmFactored(arms: readonly string[], ctx: SlotFactorContext, cells: readonly SlotFactorCell[], rand?: () => number): FactoredSlotChoice | null;
|
|
308
|
+
/**
|
|
309
|
+
* The cells one closed trial writes, given its context — the write-side
|
|
310
|
+
* projection. Always includes the arm's GLOBAL row, which is what every factor
|
|
311
|
+
* level shrinks toward and what the slot-wide pool is summed from.
|
|
312
|
+
*
|
|
313
|
+
* `personaWeight` is the SOFT assignment the layout model established: the
|
|
314
|
+
* persona term trains at the portrait's `reliability_score` (declared personas
|
|
315
|
+
* at 1.0) while every other factor trains at 1. A mismeasured persona otherwise
|
|
316
|
+
* induces attenuation bias — it drags a cell toward the population mean in
|
|
317
|
+
* proportion to how often it is wrong — and soft weighting lets the large
|
|
318
|
+
* unknown mass inform the global term instead of forming a dead bucket.
|
|
319
|
+
*/
|
|
320
|
+
declare function factorCellsForTrial(arm: string, ctx: SlotFactorContext, personaWeight: number): Array<{
|
|
321
|
+
arm: string;
|
|
322
|
+
factor: string;
|
|
323
|
+
level: string;
|
|
324
|
+
weight: number;
|
|
325
|
+
}>;
|
|
326
|
+
/** Visit-count bucket. Two levels on purpose: new-vs-returning is one of the
|
|
327
|
+
* largest conversion differences on any site, and finer buckets would spend
|
|
328
|
+
* parameters on a tail that prod does not currently have. */
|
|
329
|
+
declare function visitLevel(visitCount: number | null | undefined): string | null;
|
|
330
|
+
|
|
140
331
|
/** Learned Beta(alpha, beta) posterior for one arm. */
|
|
141
332
|
type ArmPosterior = {
|
|
142
333
|
arm: string;
|
|
@@ -412,8 +603,7 @@ declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => numb
|
|
|
412
603
|
/**
|
|
413
604
|
* Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
|
|
414
605
|
*
|
|
415
|
-
* The persona axis
|
|
416
|
-
* becomes a per-project member list (persona_sets / persona_set_members,
|
|
606
|
+
* The persona axis is a per-project member list (persona_sets / persona_set_members,
|
|
417
607
|
* migration 113). This module owns resolution: which persona a decision is
|
|
418
608
|
* keyed on, given what the customer's app declared and what clustering
|
|
419
609
|
* inferred. The weight tables and pooling are already string-generic —
|
|
@@ -444,12 +634,30 @@ declare const PERSONA_KEY_RE: RegExp;
|
|
|
444
634
|
*/
|
|
445
635
|
declare function normalizeDeclaredPersona(raw: string | null | undefined): string | null;
|
|
446
636
|
/**
|
|
447
|
-
* Keys no vocabulary member may claim
|
|
448
|
-
* in weightCellsFor / CONTRACTS §4
|
|
449
|
-
*
|
|
450
|
-
*
|
|
637
|
+
* Keys no vocabulary member may claim: 'unknown' and '__all__' are structural
|
|
638
|
+
* in weightCellsFor / CONTRACTS §4. MUST stay in sync with the CHECK constraint
|
|
639
|
+
* on persona_set_members.key (migration 113, relaxed by 153).
|
|
640
|
+
*
|
|
641
|
+
* This also reserved four plural labels until 2026-09-13, because the
|
|
642
|
+
* name-specific alias map that remapped them at resolve time would have
|
|
643
|
+
* silently rewritten a member claiming one. That map went with the seeded
|
|
644
|
+
* personas, so those strings are ordinary keys now.
|
|
451
645
|
*/
|
|
452
646
|
declare const RESERVED_PERSONA_KEYS: readonly string[];
|
|
647
|
+
/**
|
|
648
|
+
* Canonicalizes any persona/cluster label to a persona KEY: trimmed,
|
|
649
|
+
* lowercased, and shaped like one (`PERSONA_KEY_RE`). Null, empty, and
|
|
650
|
+
* anything that could never be a key become 'unknown' — "we don't know" is
|
|
651
|
+
* always a safe answer.
|
|
652
|
+
*
|
|
653
|
+
* Generic on purpose. This used to be a lookup in a table of four seeded
|
|
654
|
+
* persona names, so every OTHER label — including every key a customer
|
|
655
|
+
* declared — folded to 'unknown': a registry pin scoped to `admin` was stored
|
|
656
|
+
* under 'unknown' and then applied to every unidentified visitor. It says
|
|
657
|
+
* nothing about membership; callers that serve must still check the project's
|
|
658
|
+
* vocabulary (resolvePersona does).
|
|
659
|
+
*/
|
|
660
|
+
declare function canonicalPersona(label: string | null | undefined): string;
|
|
453
661
|
type PersonaVocabularyMember = {
|
|
454
662
|
key: string;
|
|
455
663
|
displayName: string;
|
|
@@ -459,9 +667,34 @@ type PersonaVocabularyMember = {
|
|
|
459
667
|
status?: 'active' | 'retired';
|
|
460
668
|
};
|
|
461
669
|
/**
|
|
462
|
-
* The
|
|
463
|
-
*
|
|
464
|
-
*
|
|
670
|
+
* The vocabulary a project has when it has declared nothing: EMPTY.
|
|
671
|
+
*
|
|
672
|
+
* This shipped as a hardcoded four seeded personas, which the product then
|
|
673
|
+
* presented as if it knew the customer's audience.
|
|
674
|
+
* Earned rows (2026-09-12) demoted them to a `starter` state; this removes them.
|
|
675
|
+
*
|
|
676
|
+
* The measurement that settled it, across all of production history:
|
|
677
|
+
* `unknown` served 10,882 decisions, the four seeded personas served **18
|
|
678
|
+
* between them**. They were not a taxonomy, they were decoration on an axis
|
|
679
|
+
* that was 99.8% empty — and every one of them was an assertion about visitors
|
|
680
|
+
* nobody had met.
|
|
681
|
+
*
|
|
682
|
+
* A persona now has exactly two honest origins:
|
|
683
|
+
*
|
|
684
|
+
* - **declared** — the customer's own code tells us (a role, a plan tier).
|
|
685
|
+
* Ground truth; no evidence gate, because eligibility is their decision.
|
|
686
|
+
* - **discovered** — `persona-discovery.ts` finds it in real behaviour and it
|
|
687
|
+
* clears the interaction gate (the RANKING of arms must differ inside vs
|
|
688
|
+
* outside the segment, not merely the conversion rate).
|
|
689
|
+
*
|
|
690
|
+
* Everything else resolves to `unknown`, which is where day-0 value accrues and
|
|
691
|
+
* where the pooled bandit has always done the actual work.
|
|
692
|
+
*
|
|
693
|
+
* THIS IS ONLY SAFE BECAUSE SERVING NO LONGER NEEDS A PERSONA. The factored
|
|
694
|
+
* model (migration 149) conditions on device, source and visit count as
|
|
695
|
+
* first-class factors — measured, not guessed — so a project with no personas
|
|
696
|
+
* still adapts per visitor. Before that landed, emptying this would have meant
|
|
697
|
+
* no personalization at all.
|
|
465
698
|
*/
|
|
466
699
|
declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
|
|
467
700
|
type PersonaResolution = {
|
|
@@ -481,8 +714,7 @@ type PersonaResolution = {
|
|
|
481
714
|
* Resolves the persona a decision is keyed on.
|
|
482
715
|
*
|
|
483
716
|
* Precedence, in order:
|
|
484
|
-
* 1. Declared value matching an active member (directly
|
|
485
|
-
* legacy plural label whose canonical form is a member) → that key,
|
|
717
|
+
* 1. Declared value matching an active member (directly or via alias) → that key,
|
|
486
718
|
* confidence 1. Declared skips reliability gating: it is ground truth from
|
|
487
719
|
* the customer's app, the same trust level as everything else the pk_ key
|
|
488
720
|
* sends.
|
|
@@ -502,11 +734,14 @@ declare function resolvePersona(input: {
|
|
|
502
734
|
* Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
|
|
503
735
|
* for training. Unlike `canonicalPersona`, this trusts the stored value
|
|
504
736
|
* verbatim: it was validated against the project vocabulary when the decision
|
|
505
|
-
* was written, and re-squashing it through
|
|
737
|
+
* was written, and re-squashing it through a closed global union at close-out
|
|
506
738
|
* silently rerouted every declared-persona trial onto the 'unknown' marginals
|
|
507
|
-
* (the double-squash bug, spec §4.3).
|
|
508
|
-
*
|
|
739
|
+
* (the double-squash bug, spec §4.3).
|
|
740
|
+
*
|
|
741
|
+
* It also remapped four pre-069 plural labels until 2026-09-13; migration 069
|
|
742
|
+
* had already rewritten those rows, and the remap was keyed on the retired
|
|
743
|
+
* seeded names, so it went with them.
|
|
509
744
|
*/
|
|
510
745
|
declare function decisionPersona(label: string | null | undefined): string;
|
|
511
746
|
|
|
512
|
-
export { type ArmPosterior,
|
|
747
|
+
export { type ArmPosterior, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, type FactoredSlotChoice, GLOBAL_FACTOR_LEVEL, GLOBAL_FACTOR_PERSONA, LAYOUT_ARCHETYPES, LAYOUT_ARCHETYPE_NAMES, LAYOUT_FACTOR_BUCKETS, type LayoutArchetype, type LayoutFactorCell, type LearnedLayout, PERSONA_KEY_RE, POOL_ALL, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, SLOT_FACTORS, type SlotDecl, type SlotFactor, type SlotFactorCell, type SlotFactorContext, type SlotResult, UNKNOWN_PERSONA, UNKNOWN_PERSONA_DISPLAY, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, chooseSlotArmFactored, confidenceBand, decisionPersona, factorCellsForOrder, factorCellsForTrial, factorLevelsFor, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, orderByArchetype, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, previewOrderForPersona, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, visitLevel, weightCellsFor };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,50 +1,111 @@
|
|
|
1
1
|
import { SectionRole } from './taxonomy.js';
|
|
2
2
|
|
|
3
|
-
declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
|
|
4
|
-
type Persona = (typeof PERSONAS)[number];
|
|
5
|
-
declare const UNKNOWN_PERSONA: "unknown";
|
|
6
|
-
type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
|
|
7
|
-
/** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
|
|
8
|
-
declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* The one persona key the product itself defines: "we don't know".
|
|
5
|
+
*
|
|
6
|
+
* There is deliberately no persona LIST here. Until 2026-09-13 this module
|
|
7
|
+
* exported a hardcoded four-persona vocabulary (with display names and a
|
|
8
|
+
* plural-label alias map) that every project inherited, and the product
|
|
9
|
+
* presented those names as if it knew the customer's audience. Every persona
|
|
10
|
+
* now comes from the project's own vocabulary — declared by the app or
|
|
11
|
+
* promoted by discovery (see `vocabulary.ts`) — and anything else is
|
|
12
|
+
* `unknown`, which is where the pooled bandit does the actual work.
|
|
13
13
|
*/
|
|
14
|
-
declare const
|
|
14
|
+
declare const UNKNOWN_PERSONA: "unknown";
|
|
15
|
+
/** Human-facing name of `UNKNOWN_PERSONA`. Every other persona's display name
|
|
16
|
+
* comes from its vocabulary member, never from a table in this package. */
|
|
17
|
+
declare const UNKNOWN_PERSONA_DISPLAY = "Unknown";
|
|
18
|
+
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
20
|
+
* A catalogue of plausible page orderings — NOT a persona taxonomy.
|
|
21
|
+
*
|
|
22
|
+
* These four orderings were keyed by the four seeded persona names until
|
|
23
|
+
* 2026-09-13 (the table was exported as `CLUSTER_PRIORITY`, now removed).
|
|
24
|
+
* Those personas were removed, but the deeper problem was that keying orderings by persona NAME was
|
|
25
|
+
* wrong even while they existed, in two ways that were invisible until a
|
|
26
|
+
* customer declared a persona of their own:
|
|
27
|
+
*
|
|
28
|
+
* 1. THE ARM SPACE DEPENDED ON WHAT THE CUSTOMER NAMED THEIR PERSONA. A
|
|
29
|
+
* project that declared `admin` got five candidate orders including the
|
|
30
|
+
* page exactly as authored; a project that declared one of the seeded names got four, and
|
|
31
|
+
* the authored order was NOT among them — so its own page order could never
|
|
32
|
+
* be served to that persona, and there was no control arm to lose to.
|
|
33
|
+
* Renaming a persona silently changed which layouts were reachable.
|
|
34
|
+
*
|
|
35
|
+
* 2. THE FEASIBILITY GATE SERVED THE COLLIDING ARCHETYPE DETERMINISTICALLY.
|
|
36
|
+
* `routes/decide.ts` serves a heuristic order (never explored, never
|
|
37
|
+
* learned) when layout feasibility is `infeasible` — the state every new
|
|
38
|
+
* project is in. A customer who declared the seeded conversion persona had pricing hoisted above
|
|
39
|
+
* everything on every visit, permanently, because of a table written in
|
|
40
|
+
* May; a customer who declared `admin` correctly kept their own page.
|
|
41
|
+
*
|
|
42
|
+
* So the orderings are now named for what they DO. The names are internal and
|
|
43
|
+
* carry no claim about any visitor: they are four opinions about what a page
|
|
44
|
+
* should lead with, and the bandit decides between them and the authored order
|
|
45
|
+
* using evidence. The arrays are unchanged, so every `layout_weights` row in
|
|
46
|
+
* production still joins — `hashLayout` hashes the resulting ORDER, never the
|
|
47
|
+
* key that produced it.
|
|
19
48
|
*/
|
|
20
|
-
declare
|
|
21
|
-
|
|
22
|
-
|
|
49
|
+
declare const LAYOUT_ARCHETYPES: Record<string, readonly string[]>;
|
|
50
|
+
/** The archetype names, in a pinned order — iteration order decides candidate
|
|
51
|
+
* insertion order, so it must not depend on object-key enumeration luck. */
|
|
52
|
+
declare const LAYOUT_ARCHETYPE_NAMES: readonly ["conversion_led", "evidence_led", "price_led", "discovery_led"];
|
|
53
|
+
type LayoutArchetype = (typeof LAYOUT_ARCHETYPE_NAMES)[number];
|
|
23
54
|
/**
|
|
24
|
-
* Reorders section IDs
|
|
55
|
+
* Reorders section IDs by one archetype's semantic priority.
|
|
25
56
|
* Sections with no graph entry are treated as 'generic'.
|
|
26
|
-
* Returns the input unchanged for 'unknown' — and for any custom vocabulary
|
|
27
|
-
* persona (declared/discovered): those have no semantic prior, so they serve
|
|
28
|
-
* the natural order until the layout bandit has learned rows, the same
|
|
29
|
-
* cold-start posture 'unknown' gets.
|
|
30
57
|
*
|
|
31
58
|
* With `sectionRoles` (spec 2026-09-04 §1, phase 2d) the ordering projection is
|
|
32
59
|
* `(role, parent)`: structural sections are PINNED at their original index and
|
|
33
60
|
* only converters/persuaders re-rank around them. The pin is not cosmetic —
|
|
34
|
-
* 'navigation' ranks near last in every
|
|
35
|
-
*
|
|
61
|
+
* 'navigation' ranks near last in every archetype, so an unpinned navbar or
|
|
62
|
+
* footer would sort to the bottom of the page, exactly the visible damage a
|
|
36
63
|
* reorder must never do. Callers without role data (the client-local fallback)
|
|
37
64
|
* omit the map and get the pre-2d behaviour unchanged.
|
|
38
65
|
*/
|
|
39
|
-
declare function
|
|
66
|
+
declare function orderByArchetype(sections: string[], sectionTypes: Map<string, string>, archetype: LayoutArchetype, sectionRoles?: Map<string, SectionRole>): string[];
|
|
40
67
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
68
|
+
* PREVIEW ONLY — the keyless local engine (`@sentientui/core` index-local).
|
|
69
|
+
*
|
|
70
|
+
* Maps an arbitrary persona string to one archetype so that
|
|
71
|
+
* `?sentient_persona=<anything>` visibly rearranges a page with no API key and
|
|
72
|
+
* no server. It used to look the key up in the four-persona table, so only
|
|
73
|
+
* the four seeded names did anything and every other key silently no-oped;
|
|
74
|
+
* now any key previews an arrangement, which is what the docs promise.
|
|
75
|
+
*
|
|
76
|
+
* DO NOT USE THIS ON THE SERVER. The mapping is a hash, not a belief: it
|
|
77
|
+
* carries no claim that this persona wants this ordering. Server serving picks
|
|
78
|
+
* between the archetypes and the authored order with `chooseLayout` /
|
|
79
|
+
* `chooseLayoutFactored`, on evidence.
|
|
80
|
+
*
|
|
81
|
+
* 'unknown' and the empty string return the sections untouched — the natural
|
|
82
|
+
* order is what an unidentified visitor gets, here as everywhere.
|
|
83
|
+
*/
|
|
84
|
+
declare function previewOrderForPersona(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated Renamed. Use `orderByArchetype` on the server (by archetype) or
|
|
87
|
+
* `previewOrderForPersona` in the keyless local engine (by arbitrary key).
|
|
88
|
+
* Kept as an alias of the preview mapping so the published signature survives.
|
|
46
89
|
*/
|
|
47
|
-
declare
|
|
90
|
+
declare const applyClusterHeuristic: typeof previewOrderForPersona;
|
|
91
|
+
/**
|
|
92
|
+
* The candidate layout orderings for a page — the arms the layout bandit
|
|
93
|
+
* explores. Returned as hash → order so it joins directly against
|
|
94
|
+
* `layout_weights` rows keyed by the same `hashLayout`.
|
|
95
|
+
*
|
|
96
|
+
* THE AUTHORED ORDER IS ALWAYS AN ARM. It is the control: the customer built
|
|
97
|
+
* this page in this order, and a bandit whose arm space excludes the baseline
|
|
98
|
+
* can never conclude "leave it alone" — it is structurally obliged to reorder
|
|
99
|
+
* something, and there is nothing for a holdout comparison to mean. Before
|
|
100
|
+
* 2026-09-13 the authored order was included only by accident, when the
|
|
101
|
+
* requesting persona's name happened to miss the archetype table; a persona
|
|
102
|
+
* whose name matched the table had it excluded entirely.
|
|
103
|
+
*
|
|
104
|
+
* It no longer takes a persona. The set of orderings a page COULD be shown in
|
|
105
|
+
* is a property of the page, not of who is looking at it — what the persona
|
|
106
|
+
* changes is which arm wins, and that is the posterior's job.
|
|
107
|
+
*/
|
|
108
|
+
declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
|
|
48
109
|
|
|
49
110
|
/**
|
|
50
111
|
* Stable 16-char SHA-256 prefix for a section order array.
|
|
@@ -69,15 +130,25 @@ type LearnedLayout = {
|
|
|
69
130
|
* Thompson-samples the layout order to serve a persona over the candidate
|
|
70
131
|
* orderings, using learned posteriors from layout_weights. Candidates with no
|
|
71
132
|
* learned row use the uniform 1/1 prior — identical to variant cold start.
|
|
72
|
-
*
|
|
133
|
+
*
|
|
134
|
+
* The candidate set always contains the AUTHORED order, so "leave this page
|
|
135
|
+
* alone" is a real arm that can win, and at cold start it is exactly as likely
|
|
136
|
+
* as any reorder. This used to fall back to the persona's heuristic when
|
|
137
|
+
* sampling yielded nothing — which could not happen (candidateLayouts is never
|
|
138
|
+
* empty) and would have been the wrong answer anyway: with nothing to choose
|
|
139
|
+
* on, the page the customer built is the only defensible thing to serve.
|
|
140
|
+
*
|
|
141
|
+
* `persona` no longer selects candidates — the orderings a page COULD be shown
|
|
142
|
+
* in are a property of the page. It stays in the signature because it is what
|
|
143
|
+
* the CALLER keyed `learned` by, which is where the persona belongs.
|
|
73
144
|
*
|
|
74
145
|
* @param rand Uniform [0,1) source. Defaults to `Math.random`, which is
|
|
75
146
|
* NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
|
|
76
147
|
* (tests, replayable decisions) — otherwise the sampled order varies per call.
|
|
77
148
|
* @param sectionRoles Optional role map (phase 2d): structural sections are
|
|
78
|
-
* pinned in place across every candidate; see `
|
|
149
|
+
* pinned in place across every candidate; see `orderByArchetype`.
|
|
79
150
|
*/
|
|
80
|
-
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>,
|
|
151
|
+
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, _persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
|
|
81
152
|
|
|
82
153
|
/**
|
|
83
154
|
* Factored layout value model (spec 2026-09-04 §3a).
|
|
@@ -133,10 +204,130 @@ declare function factorCellsForOrder(order: string[], sectionTypes: Map<string,
|
|
|
133
204
|
* start degrades gracefully: empty cells draw from Beta(1,1), which still
|
|
134
205
|
* randomises across candidates, so exploration survives the switch.
|
|
135
206
|
*
|
|
136
|
-
*
|
|
207
|
+
* The candidate set always contains the AUTHORED order, so leaving the page as
|
|
208
|
+
* built is a real arm rather than something only reachable by accident.
|
|
137
209
|
*/
|
|
138
210
|
declare function chooseLayoutFactored(sections: string[], sectionTypes: Map<string, string>, persona: string, cells: LayoutFactorCell[], rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
|
|
139
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Factored slot value model — the slot analogue of `layout-factored.ts`.
|
|
214
|
+
*
|
|
215
|
+
* `slot_weights` keys one independent Beta posterior per
|
|
216
|
+
* (arm, segment, persona), where `segment` is the joined `device:source`. That
|
|
217
|
+
* is why the decision context stopped at two fields: every field ADDED
|
|
218
|
+
* multiplies the cell count. Three devices x six sources x five personas is
|
|
219
|
+
* already 90 cells per arm; adding new-vs-returning makes it 180, adding
|
|
220
|
+
* country multiplies again — and each cell estimates from its own slice of the
|
|
221
|
+
* same thin traffic, so a richer context converges WORSE, not better.
|
|
222
|
+
*
|
|
223
|
+
* This model replaces one-parameter-per-context-combination with one per
|
|
224
|
+
* context FACTOR:
|
|
225
|
+
*
|
|
226
|
+
* V(arm | context) = Σ_f draw[arm, factor f, level of f in this context]
|
|
227
|
+
*
|
|
228
|
+
* Four factors at 3 + 6 + 5 + 2 levels is 16 cells per arm instead of 180, and
|
|
229
|
+
* every trial teaches every context that shares ANY factor level: a conversion
|
|
230
|
+
* on mobile/paid/returning updates "this arm on mobile", which transfers to
|
|
231
|
+
* mobile/organic/new. Additive over factors is the same shape the layout model
|
|
232
|
+
* uses additively over positions, and it is fair across arms for the same
|
|
233
|
+
* reason — every arm in one request is scored under the identical factor set,
|
|
234
|
+
* so the sum has the same number of terms for each.
|
|
235
|
+
*
|
|
236
|
+
* It improves sample efficiency; it does not manufacture signal. With a handful
|
|
237
|
+
* of conversions no model learns a ranking, which is what the feasibility gate
|
|
238
|
+
* exists to say out loud.
|
|
239
|
+
*/
|
|
240
|
+
/** Persona key of the pooled global cells. Reserved — never a real persona. */
|
|
241
|
+
declare const GLOBAL_FACTOR_LEVEL = "__global__";
|
|
242
|
+
/**
|
|
243
|
+
* Context factors the model conditions on, in a pinned order.
|
|
244
|
+
*
|
|
245
|
+
* Widening this list is the whole point of the design — it costs O(levels), not
|
|
246
|
+
* O(product) — but it is NOT free: each factor adds a term to the sum, and a
|
|
247
|
+
* factor with no signal adds variance to every score. Add one when there is a
|
|
248
|
+
* reason to believe it changes which arm wins, not because the column exists.
|
|
249
|
+
*/
|
|
250
|
+
declare const SLOT_FACTORS: readonly ["device", "source", "persona", "visit"];
|
|
251
|
+
type SlotFactor = (typeof SLOT_FACTORS)[number];
|
|
252
|
+
type SlotFactorContext = {
|
|
253
|
+
device: string | null;
|
|
254
|
+
source: string | null;
|
|
255
|
+
persona: string;
|
|
256
|
+
/** 'new' | 'returning' | null when the visit count is unknown. */
|
|
257
|
+
visit: string | null;
|
|
258
|
+
};
|
|
259
|
+
type SlotFactorCell = {
|
|
260
|
+
arm: string;
|
|
261
|
+
factor: string;
|
|
262
|
+
/** Factor level, or GLOBAL_FACTOR_LEVEL for the arm's context-free row. */
|
|
263
|
+
level: string;
|
|
264
|
+
exposures: number;
|
|
265
|
+
conversions: number;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* The (factor, level) pairs one served decision contributes to — the write-side
|
|
269
|
+
* projection close-out uses, and the read-side lookup serving uses. Both call
|
|
270
|
+
* this so the two can never disagree about what a context decomposes into.
|
|
271
|
+
*
|
|
272
|
+
* An UNKNOWN persona contributes no persona term. That is the Pareto safety
|
|
273
|
+
* invariant of CONTRACTS §4 restated for this model: unknown-persona traffic
|
|
274
|
+
* must run on exactly the persona-agnostic policy, so the persona factor is not
|
|
275
|
+
* merely empty for them, it is absent — an `unknown` LEVEL would otherwise
|
|
276
|
+
* become a real segment that accumulates its own rate and steers serving.
|
|
277
|
+
*
|
|
278
|
+
* A null device/source/visit is likewise absent rather than levelled as
|
|
279
|
+
* 'unknown', for the same reason: "not measured" is not a level.
|
|
280
|
+
*/
|
|
281
|
+
declare function factorLevelsFor(ctx: SlotFactorContext): Array<{
|
|
282
|
+
factor: SlotFactor;
|
|
283
|
+
level: string;
|
|
284
|
+
}>;
|
|
285
|
+
type FactoredSlotChoice = {
|
|
286
|
+
arm: string;
|
|
287
|
+
factorsUsed: number;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Thompson-style selection over the factored model.
|
|
291
|
+
*
|
|
292
|
+
* ONE draw per (arm, factor, level) cell, cached for the call — an arm's score
|
|
293
|
+
* is the sum of its factor draws, and every arm is scored under the same factor
|
|
294
|
+
* set, so the comparison is made in a single sampled world. A fresh draw per
|
|
295
|
+
* comparison would add pure noise to the ranking rather than exploration.
|
|
296
|
+
*
|
|
297
|
+
* Shrinkage follows CONTRACTS §4 exactly: a factor-level cell shrinks toward
|
|
298
|
+
* the arm's GLOBAL cell, and the global cell shrinks toward the slot's pooled
|
|
299
|
+
* rate across all arms. The MEAN crosses each boundary, never the sample size —
|
|
300
|
+
* so a thin factor level keeps a posterior as wide as its own evidence warrants
|
|
301
|
+
* and Thompson sampling still explores it.
|
|
302
|
+
*
|
|
303
|
+
* Cold start degrades to the unfactored behaviour: with no cells at all every
|
|
304
|
+
* arm draws from the same shrunken pool, which still randomises, so exploration
|
|
305
|
+
* survives enabling this model on a project with history.
|
|
306
|
+
*/
|
|
307
|
+
declare function chooseSlotArmFactored(arms: readonly string[], ctx: SlotFactorContext, cells: readonly SlotFactorCell[], rand?: () => number): FactoredSlotChoice | null;
|
|
308
|
+
/**
|
|
309
|
+
* The cells one closed trial writes, given its context — the write-side
|
|
310
|
+
* projection. Always includes the arm's GLOBAL row, which is what every factor
|
|
311
|
+
* level shrinks toward and what the slot-wide pool is summed from.
|
|
312
|
+
*
|
|
313
|
+
* `personaWeight` is the SOFT assignment the layout model established: the
|
|
314
|
+
* persona term trains at the portrait's `reliability_score` (declared personas
|
|
315
|
+
* at 1.0) while every other factor trains at 1. A mismeasured persona otherwise
|
|
316
|
+
* induces attenuation bias — it drags a cell toward the population mean in
|
|
317
|
+
* proportion to how often it is wrong — and soft weighting lets the large
|
|
318
|
+
* unknown mass inform the global term instead of forming a dead bucket.
|
|
319
|
+
*/
|
|
320
|
+
declare function factorCellsForTrial(arm: string, ctx: SlotFactorContext, personaWeight: number): Array<{
|
|
321
|
+
arm: string;
|
|
322
|
+
factor: string;
|
|
323
|
+
level: string;
|
|
324
|
+
weight: number;
|
|
325
|
+
}>;
|
|
326
|
+
/** Visit-count bucket. Two levels on purpose: new-vs-returning is one of the
|
|
327
|
+
* largest conversion differences on any site, and finer buckets would spend
|
|
328
|
+
* parameters on a tail that prod does not currently have. */
|
|
329
|
+
declare function visitLevel(visitCount: number | null | undefined): string | null;
|
|
330
|
+
|
|
140
331
|
/** Learned Beta(alpha, beta) posterior for one arm. */
|
|
141
332
|
type ArmPosterior = {
|
|
142
333
|
arm: string;
|
|
@@ -412,8 +603,7 @@ declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => numb
|
|
|
412
603
|
/**
|
|
413
604
|
* Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
|
|
414
605
|
*
|
|
415
|
-
* The persona axis
|
|
416
|
-
* becomes a per-project member list (persona_sets / persona_set_members,
|
|
606
|
+
* The persona axis is a per-project member list (persona_sets / persona_set_members,
|
|
417
607
|
* migration 113). This module owns resolution: which persona a decision is
|
|
418
608
|
* keyed on, given what the customer's app declared and what clustering
|
|
419
609
|
* inferred. The weight tables and pooling are already string-generic —
|
|
@@ -444,12 +634,30 @@ declare const PERSONA_KEY_RE: RegExp;
|
|
|
444
634
|
*/
|
|
445
635
|
declare function normalizeDeclaredPersona(raw: string | null | undefined): string | null;
|
|
446
636
|
/**
|
|
447
|
-
* Keys no vocabulary member may claim
|
|
448
|
-
* in weightCellsFor / CONTRACTS §4
|
|
449
|
-
*
|
|
450
|
-
*
|
|
637
|
+
* Keys no vocabulary member may claim: 'unknown' and '__all__' are structural
|
|
638
|
+
* in weightCellsFor / CONTRACTS §4. MUST stay in sync with the CHECK constraint
|
|
639
|
+
* on persona_set_members.key (migration 113, relaxed by 153).
|
|
640
|
+
*
|
|
641
|
+
* This also reserved four plural labels until 2026-09-13, because the
|
|
642
|
+
* name-specific alias map that remapped them at resolve time would have
|
|
643
|
+
* silently rewritten a member claiming one. That map went with the seeded
|
|
644
|
+
* personas, so those strings are ordinary keys now.
|
|
451
645
|
*/
|
|
452
646
|
declare const RESERVED_PERSONA_KEYS: readonly string[];
|
|
647
|
+
/**
|
|
648
|
+
* Canonicalizes any persona/cluster label to a persona KEY: trimmed,
|
|
649
|
+
* lowercased, and shaped like one (`PERSONA_KEY_RE`). Null, empty, and
|
|
650
|
+
* anything that could never be a key become 'unknown' — "we don't know" is
|
|
651
|
+
* always a safe answer.
|
|
652
|
+
*
|
|
653
|
+
* Generic on purpose. This used to be a lookup in a table of four seeded
|
|
654
|
+
* persona names, so every OTHER label — including every key a customer
|
|
655
|
+
* declared — folded to 'unknown': a registry pin scoped to `admin` was stored
|
|
656
|
+
* under 'unknown' and then applied to every unidentified visitor. It says
|
|
657
|
+
* nothing about membership; callers that serve must still check the project's
|
|
658
|
+
* vocabulary (resolvePersona does).
|
|
659
|
+
*/
|
|
660
|
+
declare function canonicalPersona(label: string | null | undefined): string;
|
|
453
661
|
type PersonaVocabularyMember = {
|
|
454
662
|
key: string;
|
|
455
663
|
displayName: string;
|
|
@@ -459,9 +667,34 @@ type PersonaVocabularyMember = {
|
|
|
459
667
|
status?: 'active' | 'retired';
|
|
460
668
|
};
|
|
461
669
|
/**
|
|
462
|
-
* The
|
|
463
|
-
*
|
|
464
|
-
*
|
|
670
|
+
* The vocabulary a project has when it has declared nothing: EMPTY.
|
|
671
|
+
*
|
|
672
|
+
* This shipped as a hardcoded four seeded personas, which the product then
|
|
673
|
+
* presented as if it knew the customer's audience.
|
|
674
|
+
* Earned rows (2026-09-12) demoted them to a `starter` state; this removes them.
|
|
675
|
+
*
|
|
676
|
+
* The measurement that settled it, across all of production history:
|
|
677
|
+
* `unknown` served 10,882 decisions, the four seeded personas served **18
|
|
678
|
+
* between them**. They were not a taxonomy, they were decoration on an axis
|
|
679
|
+
* that was 99.8% empty — and every one of them was an assertion about visitors
|
|
680
|
+
* nobody had met.
|
|
681
|
+
*
|
|
682
|
+
* A persona now has exactly two honest origins:
|
|
683
|
+
*
|
|
684
|
+
* - **declared** — the customer's own code tells us (a role, a plan tier).
|
|
685
|
+
* Ground truth; no evidence gate, because eligibility is their decision.
|
|
686
|
+
* - **discovered** — `persona-discovery.ts` finds it in real behaviour and it
|
|
687
|
+
* clears the interaction gate (the RANKING of arms must differ inside vs
|
|
688
|
+
* outside the segment, not merely the conversion rate).
|
|
689
|
+
*
|
|
690
|
+
* Everything else resolves to `unknown`, which is where day-0 value accrues and
|
|
691
|
+
* where the pooled bandit has always done the actual work.
|
|
692
|
+
*
|
|
693
|
+
* THIS IS ONLY SAFE BECAUSE SERVING NO LONGER NEEDS A PERSONA. The factored
|
|
694
|
+
* model (migration 149) conditions on device, source and visit count as
|
|
695
|
+
* first-class factors — measured, not guessed — so a project with no personas
|
|
696
|
+
* still adapts per visitor. Before that landed, emptying this would have meant
|
|
697
|
+
* no personalization at all.
|
|
465
698
|
*/
|
|
466
699
|
declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
|
|
467
700
|
type PersonaResolution = {
|
|
@@ -481,8 +714,7 @@ type PersonaResolution = {
|
|
|
481
714
|
* Resolves the persona a decision is keyed on.
|
|
482
715
|
*
|
|
483
716
|
* Precedence, in order:
|
|
484
|
-
* 1. Declared value matching an active member (directly
|
|
485
|
-
* legacy plural label whose canonical form is a member) → that key,
|
|
717
|
+
* 1. Declared value matching an active member (directly or via alias) → that key,
|
|
486
718
|
* confidence 1. Declared skips reliability gating: it is ground truth from
|
|
487
719
|
* the customer's app, the same trust level as everything else the pk_ key
|
|
488
720
|
* sends.
|
|
@@ -502,11 +734,14 @@ declare function resolvePersona(input: {
|
|
|
502
734
|
* Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
|
|
503
735
|
* for training. Unlike `canonicalPersona`, this trusts the stored value
|
|
504
736
|
* verbatim: it was validated against the project vocabulary when the decision
|
|
505
|
-
* was written, and re-squashing it through
|
|
737
|
+
* was written, and re-squashing it through a closed global union at close-out
|
|
506
738
|
* silently rerouted every declared-persona trial onto the 'unknown' marginals
|
|
507
|
-
* (the double-squash bug, spec §4.3).
|
|
508
|
-
*
|
|
739
|
+
* (the double-squash bug, spec §4.3).
|
|
740
|
+
*
|
|
741
|
+
* It also remapped four pre-069 plural labels until 2026-09-13; migration 069
|
|
742
|
+
* had already rewritten those rows, and the remap was keyed on the retired
|
|
743
|
+
* seeded names, so it went with them.
|
|
509
744
|
*/
|
|
510
745
|
declare function decisionPersona(label: string | null | undefined): string;
|
|
511
746
|
|
|
512
|
-
export { type ArmPosterior,
|
|
747
|
+
export { type ArmPosterior, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, type FactoredSlotChoice, GLOBAL_FACTOR_LEVEL, GLOBAL_FACTOR_PERSONA, LAYOUT_ARCHETYPES, LAYOUT_ARCHETYPE_NAMES, LAYOUT_FACTOR_BUCKETS, type LayoutArchetype, type LayoutFactorCell, type LearnedLayout, PERSONA_KEY_RE, POOL_ALL, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, SLOT_FACTORS, type SlotDecl, type SlotFactor, type SlotFactorCell, type SlotFactorContext, type SlotResult, UNKNOWN_PERSONA, UNKNOWN_PERSONA_DISPLAY, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, chooseSlotArmFactored, confidenceBand, decisionPersona, factorCellsForOrder, factorCellsForTrial, factorLevelsFor, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, orderByArchetype, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, previewOrderForPersona, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, visitLevel, weightCellsFor };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var F=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,he=Object.prototype.propertyIsEnumerable;var Q=(e,r,t)=>r in e?F(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,B=(e,r)=>{for(var t in r||(r={}))ee.call(r,t)&&Q(e,t,r[t]);if(J)for(var t of J(r))he.call(r,t)&&Q(e,t,r[t]);return e};var ye=(e,r)=>{for(var t in r)F(e,t,{get:r[t],enumerable:!0})},ve=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of xe(r))!ee.call(e,o)&&o!==t&&F(e,o,{get:()=>r[o],enumerable:!(n=de(r,o))||n.enumerable});return e};var Pe=e=>ve(F({},"__esModule",{value:!0}),e);var qe={};ye(qe,{CLUSTER_PRIORITY:()=>te,DEFAULT_PERSONA_VOCABULARY:()=>me,EV_SHRINK_K:()=>ue,GLOBAL_FACTOR_PERSONA:()=>se,LAYOUT_FACTOR_BUCKETS:()=>Y,LEGACY_PERSONA_MAP:()=>U,PERSONAS:()=>K,PERSONA_DISPLAY:()=>q,PERSONA_KEY_RE:()=>ce,POOL_ALL:()=>P,RESERVED_PERSONA_KEYS:()=>fe,SHRINKAGE_M:()=>ne,UNKNOWN_PERSONA:()=>R,WEIGHTS_FALLBACK_PRIOR_PULLS:()=>oe,applyClusterHeuristic:()=>L,broadestValueCell:()=>_e,candidateLayouts:()=>D,canonicalArm:()=>T,canonicalPersona:()=>V,chooseLayout:()=>Se,chooseLayoutFactored:()=>Me,confidenceBand:()=>Ke,decisionPersona:()=>Be,factorCellsForOrder:()=>we,fnv1a:()=>ie,hashLayout:()=>H,layoutBucketOf:()=>W,marginalArmKey:()=>Ee,normalizeDeclaredPersona:()=>Fe,parseArm:()=>Z,pickDeterministicArm:()=>Ie,pickFromWeights:()=>Re,pooledPosterior:()=>Ce,posteriorOfCounts:()=>A,resolvePersona:()=>$e,sampleArm:()=>z,sampleArmEv:()=>De,sampleBeta:()=>w,shrunkAvgValue:()=>le,shrunkPosterior:()=>C,slotBaselineArm:()=>ae,slotResultFor:()=>Ne,validateSlotDecl:()=>Oe,weightCellsFor:()=>Le});module.exports=Pe(qe);var K=["buyer","researcher","deal_seeker","browser"],R="unknown",q={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},U={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function V(e){var t;if(e==null)return R;let r=e.trim().toLowerCase();return(t=U[r])!=null?t:R}var ke=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function k(e,r){return e>>>r|e<<32-r}function re(e){return(e>>>0).toString(16).padStart(8,"0")}function Ae(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let i=1779033703,u=3144134277,l=1013904242,f=2773480762,h=1359893119,g=2600822924,p=528734635,y=1541459225,b=new Uint32Array(64);for(let M=0;M<o;M+=64){for(let m=0;m<16;m++)b[m]=a.getUint32(M+m*4,!1);for(let m=16;m<64;m++){let N=k(b[m-15],7)^k(b[m-15],18)^b[m-15]>>>3,I=k(b[m-2],17)^k(b[m-2],19)^b[m-2]>>>10;b[m]=b[m-16]+N+b[m-7]+I>>>0}let c=i,v=u,d=l,E=f,x=h,_=g,S=p,O=y;for(let m=0;m<64;m++){let N=k(x,6)^k(x,11)^k(x,25),I=x&_^~x&S,X=O+N+I+ke[m]+b[m]>>>0,pe=k(c,2)^k(c,13)^k(c,22),ge=c&v^c&d^v&d,be=pe+ge>>>0;O=S,S=_,_=x,x=E+X>>>0,E=d,d=v,v=c,c=X+be>>>0}i=i+c>>>0,u=u+v>>>0,l=l+d>>>0,f=f+E>>>0,h=h+x>>>0,g=g+_>>>0,p=p+S>>>0,y=y+O>>>0}return re(i)+re(u)}function H(e){return Ae(e.join("|"))}var te={buyer:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],researcher:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],deal_seeker:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],browser:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]};function L(e,r,t,n){let o=te[t];if(!o)return e;let s=o.indexOf("generic"),a=l=>{let f=o.indexOf(l);return f===-1?s:f},i=n?e.filter(l=>n.get(l)!=="structural"):[...e];if(i.sort((l,f)=>{var p,y;let h=(p=r.get(l))!=null?p:"generic",g=(y=r.get(f))!=null?y:"generic";return a(h)-a(g)}),!n)return i;let u=0;return e.map(l=>n.get(l)==="structural"?l:i[u++])}function D(e,r,t,n){let o=new Map;for(let s of[...K,t]){let a=L(e,r,s,n);o.set(H(a),a)}return o}function j(e,r){if(e<1)return j(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let i=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(i))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function w(e,r,t=Math.random){let n=j(e,t),o=j(r,t),s=n+o;return s<=0?e/(e+r):n/s}function z(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=w(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=w(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function Se(e,r,t,n,o=Math.random,s){var f,h;let a=D(e,r,t,s),i=[];for(let g of a.keys()){let p=n.get(g);i.push({arm:g,alpha:(f=p==null?void 0:p.alpha)!=null?f:1,beta:(h=p==null?void 0:p.beta)!=null?h:1})}let u=z(i,o),l=u?a.get(u):void 0;return l!=null?l:L(e,r,t,s)}var ne=20;function C(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var oe=5;function Re(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,i=a>0?a*s.avgReward/(a+oe):0;(!t||i>t.score)&&(t={variantId:s.variantId,score:i})}return(o=t==null?void 0:t.variantId)!=null?o:null}var P="__all__",$={exposures:0,conversions:0};function A(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ce(e,r,t=20){var g,p,y,b;let n=(g=e.segment)!=null?g:$,o=(p=e.global)!=null?p:$,s=A(o),a=C(A(n),s,t);if(!r)return a;let i=(y=e.persona)!=null?y:$,u=(b=e.child)!=null?b:$,l=C(A(i),s,t),f=(n.exposures+1)/(n.exposures+i.exposures+2),h={alpha:f*a.alpha+(1-f)*l.alpha,beta:f*a.beta+(1-f)*l.beta};return C(A(u),h,t)}function _e(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===P,i=s.persona===P,u=a&&i?3:a||i?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Le(e,r){return r==="unknown"||r===P||r===""?[{segment:e,persona:P},{segment:P,persona:P}]:[{segment:e,persona:r},{segment:e,persona:P},{segment:P,persona:r},{segment:P,persona:P}]}var Y=4,se="__global__";function W(e,r){return r<=0?0:Math.min(Y-1,Math.floor(e*Y/r))}function we(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:W(n,e.length)}})}var G=(e,r)=>`${e}#${r}`;function Me(e,r,t,n,o=Math.random,s){var M;let a=D(e,r,t,s);if(a.size===0)return L(e,r,t,s);let i=new Map,u=new Map,l=0,f=0;for(let c of n)c.persona===se?(i.set(G(c.parent,c.bucket),c),l+=c.exposures,f+=c.conversions):c.persona===t&&u.set(G(c.parent,c.bucket),c);let h=A({exposures:l,conversions:f}),g=new Map,p=(c,v)=>{var N,I;let d=G(c,v),E=g.get(d);if(E!==void 0)return E;let x=i.get(d),_=C(A({exposures:(N=x==null?void 0:x.exposures)!=null?N:0,conversions:(I=x==null?void 0:x.conversions)!=null?I:0}),h),S=u.get(d),O=S?C(A({exposures:S.exposures,conversions:S.conversions}),_):_,m=w(O.alpha,O.beta,o);return g.set(d,m),m},y=null,b=-1/0;for(let c of a.values()){let v=0;for(let d=0;d<c.length;d++)v+=p((M=r.get(c[d]))!=null?M:"generic",W(d,c.length));v>b&&(b=v,y=c)}return y!=null?y:L(e,r,t,s)}function T(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function Z(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function Ee(e,r){return`${e}=${r}`}function ae(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return T(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return T(r)}function Oe(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),i=Object.keys(s).sort();if(a.join(" ")!==i.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,l]of n)if(!l.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function Ne(e,r){var t,n;return e.dims!=null?(n=(t=Z(r))!=null?t:Z(ae(e)))!=null?n:{}:r}function ie(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function Ie(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ie(`${e}:${r}`)%n.length]}function Ke(e){return e>=.3?e<.7?"medium":"high":"low"}var ue=20;function le(e,r,t=ue){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function De(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=w(s.alpha,s.beta,t)*le(s,r);a>o&&(n=s,o=a)}return n.arm}var ce=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Fe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&ce.test(r)?r:null}var fe=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],me=K.map(e=>({key:e,displayName:q[e]})),Ue=64;function Ve(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])fe.includes(o)||r.set(o,n.key)}return r}function $e(e,r=me){var i,u,l;let t=Ve(r),n=(i=e.inferredConfidence)!=null?i:0,o,s=(l=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?l:"";if(s!==""){let f=t.get(s),h=f===void 0?t.get(V(s)):void 0,g=f!=null?f:h;if(g!==void 0)return{persona:g,source:"declared",confidence:1};o=s.slice(0,Ue)}let a=V(e.clusterLabel);return a!==R&&t.has(a)?B({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):B({persona:R,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function Be(e){var t;if(e==null)return R;let r=e.trim().toLowerCase();return r===""?R:(t=U[r])!=null?t:r}0&&(module.exports={CLUSTER_PRIORITY,DEFAULT_PERSONA_VOCABULARY,EV_SHRINK_K,GLOBAL_FACTOR_PERSONA,LAYOUT_FACTOR_BUCKETS,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,PERSONA_KEY_RE,POOL_ALL,RESERVED_PERSONA_KEYS,SHRINKAGE_M,UNKNOWN_PERSONA,WEIGHTS_FALLBACK_PRIOR_PULLS,applyClusterHeuristic,broadestValueCell,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,chooseLayoutFactored,confidenceBand,decisionPersona,factorCellsForOrder,fnv1a,hashLayout,layoutBucketOf,marginalArmKey,normalizeDeclaredPersona,parseArm,pickDeterministicArm,pickFromWeights,pooledPosterior,posteriorOfCounts,resolvePersona,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,weightCellsFor});
|
|
1
|
+
"use strict";var K=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,Ae=Object.prototype.propertyIsEnumerable;var Q=(e,r,t)=>r in e?K(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,$=(e,r)=>{for(var t in r||(r={}))ee.call(r,t)&&Q(e,t,r[t]);if(J)for(var t of J(r))Ae.call(r,t)&&Q(e,t,r[t]);return e};var Se=(e,r)=>{for(var t in r)K(e,t,{get:r[t],enumerable:!0})},Ce=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of ve(r))!ee.call(e,o)&&o!==t&&K(e,o,{get:()=>r[o],enumerable:!(n=ye(r,o))||n.enumerable});return e};var _e=e=>Ce(K({},"__esModule",{value:!0}),e);var Xe={};Se(Xe,{DEFAULT_PERSONA_VOCABULARY:()=>be,EV_SHRINK_K:()=>ue,GLOBAL_FACTOR_LEVEL:()=>U,GLOBAL_FACTOR_PERSONA:()=>ae,LAYOUT_ARCHETYPES:()=>te,LAYOUT_ARCHETYPE_NAMES:()=>T,LAYOUT_FACTOR_BUCKETS:()=>Y,PERSONA_KEY_RE:()=>fe,POOL_ALL:()=>_,RESERVED_PERSONA_KEYS:()=>me,SHRINKAGE_M:()=>oe,SLOT_FACTORS:()=>Ie,UNKNOWN_PERSONA:()=>M,UNKNOWN_PERSONA_DISPLAY:()=>ke,WEIGHTS_FALLBACK_PRIOR_PULLS:()=>se,applyClusterHeuristic:()=>Re,broadestValueCell:()=>Fe,candidateLayouts:()=>N,canonicalArm:()=>W,canonicalPersona:()=>ge,chooseLayout:()=>we,chooseLayoutFactored:()=>Ke,chooseSlotArmFactored:()=>Te,confidenceBand:()=>Ye,decisionPersona:()=>Ze,factorCellsForOrder:()=>Ue,factorCellsForTrial:()=>Ve,factorLevelsFor:()=>G,fnv1a:()=>le,hashLayout:()=>I,layoutBucketOf:()=>j,marginalArmKey:()=>De,normalizeDeclaredPersona:()=>pe,orderByArchetype:()=>D,parseArm:()=>Z,pickDeterministicArm:()=>qe,pickFromWeights:()=>Oe,pooledPosterior:()=>Ee,posteriorOfCounts:()=>S,previewOrderForPersona:()=>ne,resolvePersona:()=>We,sampleArm:()=>H,sampleArmEv:()=>je,sampleBeta:()=>R,shrunkAvgValue:()=>ce,shrunkPosterior:()=>C,slotBaselineArm:()=>ie,slotResultFor:()=>He,validateSlotDecl:()=>Be,visitLevel:()=>$e,weightCellsFor:()=>Ne});module.exports=_e(Xe);var M="unknown",ke="Unknown";var Le=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function P(e,r){return e>>>r|e<<32-r}function re(e){return(e>>>0).toString(16).padStart(8,"0")}function Pe(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let l=1779033703,u=3144134277,f=1013904242,m=2773480762,y=1359893119,d=2600822924,b=528734635,c=1541459225,p=new Uint32Array(64);for(let A=0;A<o;A+=64){for(let g=0;g<16;g++)p[g]=a.getUint32(A+g*4,!1);for(let g=16;g<64;g++){let E=P(p[g-15],7)^P(p[g-15],18)^p[g-15]>>>3,F=P(p[g-2],17)^P(p[g-2],19)^p[g-2]>>>10;p[g]=p[g-16]+E+p[g-7]+F>>>0}let i=l,v=u,x=f,k=m,h=y,L=d,w=b,O=c;for(let g=0;g<64;g++){let E=P(h,6)^P(h,11)^P(h,25),F=h&L^~h&w,X=O+E+F+Le[g]+p[g]>>>0,de=P(i,2)^P(i,13)^P(i,22),xe=i&v^i&x^v&x,he=de+xe>>>0;O=w,w=L,L=h,h=k+X>>>0,k=x,x=v,v=i,i=X+he>>>0}l=l+i>>>0,u=u+v>>>0,f=f+x>>>0,m=m+k>>>0,y=y+h>>>0,d=d+L>>>0,b=b+w>>>0,c=c+O>>>0}return re(l)+re(u)}function I(e){return Pe(e.join("|"))}var te={conversion_led:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],evidence_led:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],price_led:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],discovery_led:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]},T=["conversion_led","evidence_led","price_led","discovery_led"];function D(e,r,t,n){let o=te[t];if(!o)return e;let s=o.indexOf("generic"),a=f=>{let m=o.indexOf(f);return m===-1?s:m},l=n?e.filter(f=>n.get(f)!=="structural"):[...e];if(l.sort((f,m)=>{var b,c;let y=(b=r.get(f))!=null?b:"generic",d=(c=r.get(m))!=null?c:"generic";return a(y)-a(d)}),!n)return l;let u=0;return e.map(f=>n.get(f)==="structural"?f:l[u++])}function Me(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r>>>0}function ne(e,r,t,n){if(!t||t==="unknown")return e;let o=T[Me(t)%T.length];return D(e,r,o,n)}var Re=ne;function N(e,r,t){let n=new Map;n.set(I(e),[...e]);for(let o of T){let s=D(e,r,o,t),a=I(s);n.has(a)||n.set(a,s)}return n}function B(e,r){if(e<1)return B(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let l=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(l))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function R(e,r,t=Math.random){let n=B(e,t),o=B(r,t),s=n+o;return s<=0?e/(e+r):n/s}function H(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=R(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=R(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function we(e,r,t,n,o=Math.random,s){var m,y;let a=N(e,r,s),l=[];for(let d of a.keys()){let b=n.get(d);l.push({arm:d,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(y=b==null?void 0:b.beta)!=null?y:1})}let u=H(l,o),f=u?a.get(u):void 0;return f!=null?f:e}var oe=20;function C(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var se=5;function Oe(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,l=a>0?a*s.avgReward/(a+se):0;(!t||l>t.score)&&(t={variantId:s.variantId,score:l})}return(o=t==null?void 0:t.variantId)!=null?o:null}var _="__all__",V={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ee(e,r,t=20){var d,b,c,p;let n=(d=e.segment)!=null?d:V,o=(b=e.global)!=null?b:V,s=S(o),a=C(S(n),s,t);if(!r)return a;let l=(c=e.persona)!=null?c:V,u=(p=e.child)!=null?p:V,f=C(S(l),s,t),m=(n.exposures+1)/(n.exposures+l.exposures+2),y={alpha:m*a.alpha+(1-m)*f.alpha,beta:m*a.beta+(1-m)*f.beta};return C(S(u),y,t)}function Fe(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===_,l=s.persona===_,u=a&&l?3:a||l?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Ne(e,r){return r==="unknown"||r===_||r===""?[{segment:e,persona:_},{segment:_,persona:_}]:[{segment:e,persona:r},{segment:e,persona:_},{segment:_,persona:r},{segment:_,persona:_}]}var Y=4,ae="__global__";function j(e,r){return r<=0?0:Math.min(Y-1,Math.floor(e*Y/r))}function Ue(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:j(n,e.length)}})}var q=(e,r)=>`${e}#${r}`;function Ke(e,r,t,n,o=Math.random,s){var A;let a=N(e,r,s),l=new Map,u=new Map,f=0,m=0;for(let i of n)i.persona===ae?(l.set(q(i.parent,i.bucket),i),f+=i.exposures,m+=i.conversions):i.persona===t&&u.set(q(i.parent,i.bucket),i);let y=S({exposures:f,conversions:m}),d=new Map,b=(i,v)=>{var E,F;let x=q(i,v),k=d.get(x);if(k!==void 0)return k;let h=l.get(x),L=C(S({exposures:(E=h==null?void 0:h.exposures)!=null?E:0,conversions:(F=h==null?void 0:h.conversions)!=null?F:0}),y),w=u.get(x),O=w?C(S({exposures:w.exposures,conversions:w.conversions}),L):L,g=R(O.alpha,O.beta,o);return d.set(x,g),g},c=null,p=-1/0;for(let i of a.values()){let v=0;for(let x=0;x<i.length;x++)v+=b((A=r.get(i[x]))!=null?A:"generic",j(x,i.length));v>p&&(p=v,c=i)}return c!=null?c:e}var U="__global__",Ie=["device","source","persona","visit"];function G(e){let r=[];return e.device&&r.push({factor:"device",level:e.device}),e.source&&r.push({factor:"source",level:e.source}),e.persona&&e.persona!==M&&r.push({factor:"persona",level:e.persona}),e.visit&&r.push({factor:"visit",level:e.visit}),r}var z=(e,r,t)=>`${e}\0${r}\0${t}`;function Te(e,r,t,n=Math.random){if(e.length===0)return null;if(e.length===1)return{arm:e[0],factorsUsed:0};let o=new Map,s=0,a=0;for(let c of t)o.set(z(c.arm,c.factor,c.level),c),c.level===U&&(s+=c.exposures,a+=c.conversions);let l=S({exposures:s,conversions:a}),u=G(r),f=new Map,m=c=>{var A,i;let p=o.get(z(c,"global",U));return C(S({exposures:(A=p==null?void 0:p.exposures)!=null?A:0,conversions:(i=p==null?void 0:p.conversions)!=null?i:0}),l)},y=(c,p,A)=>{let i=z(c,p,A),v=f.get(i);if(v!==void 0)return v;let x=m(c),k=o.get(i),h=k?C(S({exposures:k.exposures,conversions:k.conversions}),x):x,L=R(h.alpha,h.beta,n);return f.set(i,L),L},d=null,b=-1/0;for(let c of e){let p=u.length===0?y(c,"global",U):0;for(let{factor:A,level:i}of u)p+=y(c,A,i);p>b&&(b=p,d=c)}return d===null?null:{arm:d,factorsUsed:u.length}}function Ve(e,r,t){let n=Number.isFinite(t)?Math.max(0,Math.min(1,t)):0,o=[{arm:e,factor:"global",level:U,weight:1}];for(let{factor:s,level:a}of G(r))o.push({arm:e,factor:s,level:a,weight:s==="persona"?n:1});return o}function $e(e){return e==null||!Number.isFinite(e)||e<1?null:e>1?"returning":"new"}function W(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function Z(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function De(e,r){return`${e}=${r}`}function ie(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return W(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return W(r)}function Be(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),l=Object.keys(s).sort();if(a.join(" ")!==l.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,f]of n)if(!f.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function He(e,r){var t,n;return e.dims!=null?(n=(t=Z(r))!=null?t:Z(ie(e)))!=null?n:{}:r}function le(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function qe(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[le(`${e}:${r}`)%n.length]}function Ye(e){return e>=.3?e<.7?"medium":"high":"low"}var ue=20;function ce(e,r,t=ue){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function je(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=R(s.alpha,s.beta,t)*ce(s,r);a>o&&(n=s,o=a)}return n.arm}var fe=/^[a-z0-9][a-z0-9_-]{0,31}$/;function pe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&fe.test(r)?r:null}var me=["unknown","__all__"];function ge(e){var r;return(r=pe(e))!=null?r:M}var be=[],ze=64;function Ge(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])me.includes(o)||r.set(o,n.key)}return r}function We(e,r=be){var l,u,f;let t=Ge(r),n=(l=e.inferredConfidence)!=null?l:0,o,s=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(s!==""){let m=t.get(s);if(m!==void 0)return{persona:m,source:"declared",confidence:1};o=s.slice(0,ze)}let a=ge(e.clusterLabel);return a!==M&&t.has(a)?$({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):$({persona:M,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function Ze(e){if(e==null)return M;let r=e.trim().toLowerCase();return r===""?M:r}0&&(module.exports={DEFAULT_PERSONA_VOCABULARY,EV_SHRINK_K,GLOBAL_FACTOR_LEVEL,GLOBAL_FACTOR_PERSONA,LAYOUT_ARCHETYPES,LAYOUT_ARCHETYPE_NAMES,LAYOUT_FACTOR_BUCKETS,PERSONA_KEY_RE,POOL_ALL,RESERVED_PERSONA_KEYS,SHRINKAGE_M,SLOT_FACTORS,UNKNOWN_PERSONA,UNKNOWN_PERSONA_DISPLAY,WEIGHTS_FALLBACK_PRIOR_PULLS,applyClusterHeuristic,broadestValueCell,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,chooseLayoutFactored,chooseSlotArmFactored,confidenceBand,decisionPersona,factorCellsForOrder,factorCellsForTrial,factorLevelsFor,fnv1a,hashLayout,layoutBucketOf,marginalArmKey,normalizeDeclaredPersona,orderByArchetype,parseArm,pickDeterministicArm,pickFromWeights,pooledPosterior,posteriorOfCounts,previewOrderForPersona,resolvePersona,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,visitLevel,weightCellsFor});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as U}from"./chunk-HBG7RQ56.mjs";var K=["buyer","researcher","deal_seeker","browser"],C="unknown",j={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},V={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function $(e){var t;if(e==null)return C;let r=e.trim().toLowerCase();return(t=V[r])!=null?t:C}var te=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function P(e,r){return e>>>r|e<<32-r}function z(e){return(e>>>0).toString(16).padStart(8,"0")}function ne(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,s=(t+8>>6)+1<<6,o=new Uint8Array(s);o.set(r),o[t]=128;let a=new DataView(o.buffer);a.setUint32(s-8,Math.floor(n/4294967296),!1),a.setUint32(s-4,n>>>0,!1);let i=1779033703,u=3144134277,l=1013904242,f=2773480762,h=1359893119,g=2600822924,p=528734635,y=1541459225,b=new Uint32Array(64);for(let L=0;L<s;L+=64){for(let m=0;m<16;m++)b[m]=a.getUint32(L+m*4,!1);for(let m=16;m<64;m++){let E=P(b[m-15],7)^P(b[m-15],18)^b[m-15]>>>3,O=P(b[m-2],17)^P(b[m-2],19)^b[m-2]>>>10;b[m]=b[m-16]+E+b[m-7]+O>>>0}let c=i,v=u,d=l,w=f,x=h,R=g,A=p,M=y;for(let m=0;m<64;m++){let E=P(x,6)^P(x,11)^P(x,25),O=x&R^~x&A,H=M+E+O+te[m]+b[m]>>>0,Q=P(c,2)^P(c,13)^P(c,22),ee=c&v^c&d^v&d,re=Q+ee>>>0;M=A,A=R,R=x,x=w+H>>>0,w=d,d=v,v=c,c=H+re>>>0}i=i+c>>>0,u=u+v>>>0,l=l+d>>>0,f=f+w>>>0,h=h+x>>>0,g=g+R>>>0,p=p+A>>>0,y=y+M>>>0}return z(i)+z(u)}function G(e){return ne(e.join("|"))}var oe={buyer:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],researcher:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],deal_seeker:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],browser:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]};function N(e,r,t,n){let s=oe[t];if(!s)return e;let o=s.indexOf("generic"),a=l=>{let f=s.indexOf(l);return f===-1?o:f},i=n?e.filter(l=>n.get(l)!=="structural"):[...e];if(i.sort((l,f)=>{var p,y;let h=(p=r.get(l))!=null?p:"generic",g=(y=r.get(f))!=null?y:"generic";return a(h)-a(g)}),!n)return i;let u=0;return e.map(l=>n.get(l)==="structural"?l:i[u++])}function D(e,r,t,n){let s=new Map;for(let o of[...K,t]){let a=N(e,r,o,n);s.set(G(a),a)}return s}function B(e,r){if(e<1)return B(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let s,o;do{let i=Math.max(1e-15,r()),u=r();s=Math.sqrt(-2*Math.log(i))*Math.cos(2*Math.PI*u),o=1+n*s}while(o<=0);o=o*o*o;let a=r();if(a<1-.0331*s*s*s*s||Math.log(a)<.5*s*s+t*(1-o+Math.log(o)))return t*o}}function I(e,r,t=Math.random){let n=B(e,t),s=B(r,t),o=n+s;return o<=0?e/(e+r):n/o}function Y(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=I(t.alpha,t.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=I(o.alpha,o.beta,r);a>n&&(t=o,n=a)}return t.arm}function Se(e,r,t,n,s=Math.random,o){var f,h;let a=D(e,r,t,o),i=[];for(let g of a.keys()){let p=n.get(g);i.push({arm:g,alpha:(f=p==null?void 0:p.alpha)!=null?f:1,beta:(h=p==null?void 0:p.beta)!=null?h:1})}let u=Y(i,s),l=u?a.get(u):void 0;return l!=null?l:N(e,r,t,o)}var W=20;function _(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let s=r.alpha/n,o=t*n/(n+t);return{alpha:e.alpha+o*s,beta:e.beta+o*(1-s)}}var se=5;function Ce(e,r){var n,s;let t=null;for(let o of e){if(!r.includes(o.variantId))continue;let a=(n=o.pulls)!=null?n:0,i=a>0?a*o.avgReward/(a+se):0;(!t||i>t.score)&&(t={variantId:o.variantId,score:i})}return(s=t==null?void 0:t.variantId)!=null?s:null}var k="__all__",F={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function we(e,r,t=20){var g,p,y,b;let n=(g=e.segment)!=null?g:F,s=(p=e.global)!=null?p:F,o=S(s),a=_(S(n),o,t);if(!r)return a;let i=(y=e.persona)!=null?y:F,u=(b=e.child)!=null?b:F,l=_(S(i),o,t),f=(n.exposures+1)/(n.exposures+i.exposures+2),h={alpha:f*a.alpha+(1-f)*l.alpha,beta:f*a.beta+(1-f)*l.beta};return _(S(u),h,t)}function Me(e){var n,s;let r=null,t=-1;for(let o of e){let a=o.segment===k,i=o.persona===k,u=a&&i?3:a||i?2:1;u>t&&(t=u,r=o)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(s=r==null?void 0:r.valueCount)!=null?s:0}}function Ee(e,r){return r==="unknown"||r===k||r===""?[{segment:e,persona:k},{segment:k,persona:k}]:[{segment:e,persona:r},{segment:e,persona:k},{segment:k,persona:r},{segment:k,persona:k}]}var T=4,ae="__global__";function Z(e,r){return r<=0?0:Math.min(T-1,Math.floor(e*T/r))}function Fe(e,r){return e.map((t,n)=>{var s;return{parent:(s=r.get(t))!=null?s:"generic",bucket:Z(n,e.length)}})}var q=(e,r)=>`${e}#${r}`;function Ue(e,r,t,n,s=Math.random,o){var L;let a=D(e,r,t,o);if(a.size===0)return N(e,r,t,o);let i=new Map,u=new Map,l=0,f=0;for(let c of n)c.persona===ae?(i.set(q(c.parent,c.bucket),c),l+=c.exposures,f+=c.conversions):c.persona===t&&u.set(q(c.parent,c.bucket),c);let h=S({exposures:l,conversions:f}),g=new Map,p=(c,v)=>{var E,O;let d=q(c,v),w=g.get(d);if(w!==void 0)return w;let x=i.get(d),R=_(S({exposures:(E=x==null?void 0:x.exposures)!=null?E:0,conversions:(O=x==null?void 0:x.conversions)!=null?O:0}),h),A=u.get(d),M=A?_(S({exposures:A.exposures,conversions:A.conversions}),R):R,m=I(M.alpha,M.beta,s);return g.set(d,m),m},y=null,b=-1/0;for(let c of a.values()){let v=0;for(let d=0;d<c.length;d++)v+=p((L=r.get(c[d]))!=null?L:"generic",Z(d,c.length));v>b&&(b=v,y=c)}return y!=null?y:N(e,r,t,o)}function X(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function J(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let s=t.slice(0,n);if(s in r)return null;r[s]=t.slice(n+1)}return r}function $e(e,r){return`${e}=${r}`}function ie(e){var t,n,s;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return X(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[o,a]of Object.entries((n=e.dims)!=null?n:{}))r[o]=(s=a[0])!=null?s:"";return X(r)}function Be(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let o=e.arms;if(o.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(o.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(o).size!==o.length)return{ok:!1,reason:"arms must be unique"};if(o.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!o.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let s=1;for(let[o,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${o}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${o}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${o}" has duplicate values`};s*=a.length}if(s>64)return{ok:!1,reason:`declared space of ${s} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let o=e.baseline,a=n.map(([u])=>u).sort(),i=Object.keys(o).sort();if(a.join(" ")!==i.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,l]of n)if(!l.includes(o[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function qe(e,r){var t,n;return e.dims!=null?(n=(t=J(r))!=null?t:J(ie(e)))!=null?n:{}:r}function ue(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function je(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ue(`${e}:${r}`)%n.length]}function ze(e){return e>=.3?e<.7?"medium":"high":"low"}var le=20;function ce(e,r,t=le){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let s=e.valueCount/(e.valueCount+t);return s*n+(1-s)*r}function We(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,s=-1/0;for(let o of e){let a=I(o.alpha,o.beta,t)*ce(o,r);a>s&&(n=o,s=a)}return n.arm}var fe=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Xe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&fe.test(r)?r:null}var me=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],pe=K.map(e=>({key:e,displayName:j[e]})),ge=64;function be(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let s of(t=n.aliases)!=null?t:[])me.includes(s)||r.set(s,n.key)}return r}function Je(e,r=pe){var i,u,l;let t=be(r),n=(i=e.inferredConfidence)!=null?i:0,s,o=(l=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?l:"";if(o!==""){let f=t.get(o),h=f===void 0?t.get($(o)):void 0,g=f!=null?f:h;if(g!==void 0)return{persona:g,source:"declared",confidence:1};s=o.slice(0,ge)}let a=$(e.clusterLabel);return a!==C&&t.has(a)?U({persona:t.get(a),source:"inferred",confidence:n},s!==void 0&&{unrecognizedDeclared:s}):U({persona:C,source:"none",confidence:n},s!==void 0&&{unrecognizedDeclared:s})}function Qe(e){var t;if(e==null)return C;let r=e.trim().toLowerCase();return r===""?C:(t=V[r])!=null?t:r}export{oe as CLUSTER_PRIORITY,pe as DEFAULT_PERSONA_VOCABULARY,le as EV_SHRINK_K,ae as GLOBAL_FACTOR_PERSONA,T as LAYOUT_FACTOR_BUCKETS,V as LEGACY_PERSONA_MAP,K as PERSONAS,j as PERSONA_DISPLAY,fe as PERSONA_KEY_RE,k as POOL_ALL,me as RESERVED_PERSONA_KEYS,W as SHRINKAGE_M,C as UNKNOWN_PERSONA,se as WEIGHTS_FALLBACK_PRIOR_PULLS,N as applyClusterHeuristic,Me as broadestValueCell,D as candidateLayouts,X as canonicalArm,$ as canonicalPersona,Se as chooseLayout,Ue as chooseLayoutFactored,ze as confidenceBand,Qe as decisionPersona,Fe as factorCellsForOrder,ue as fnv1a,G as hashLayout,Z as layoutBucketOf,$e as marginalArmKey,Xe as normalizeDeclaredPersona,J as parseArm,je as pickDeterministicArm,Ce as pickFromWeights,we as pooledPosterior,S as posteriorOfCounts,Je as resolvePersona,Y as sampleArm,We as sampleArmEv,I as sampleBeta,ce as shrunkAvgValue,_ as shrunkPosterior,ie as slotBaselineArm,qe as slotResultFor,Be as validateSlotDecl,Ee as weightCellsFor};
|
|
1
|
+
import{a as I}from"./chunk-HBG7RQ56.mjs";var R="unknown",ve="Unknown";var te=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function k(e,r){return e>>>r|e<<32-r}function q(e){return(e>>>0).toString(16).padStart(8,"0")}function ne(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let l=1779033703,u=3144134277,f=1013904242,m=2773480762,y=1359893119,d=2600822924,b=528734635,c=1541459225,p=new Uint32Array(64);for(let A=0;A<o;A+=64){for(let g=0;g<16;g++)p[g]=a.getUint32(A+g*4,!1);for(let g=16;g<64;g++){let E=k(p[g-15],7)^k(p[g-15],18)^p[g-15]>>>3,F=k(p[g-2],17)^k(p[g-2],19)^p[g-2]>>>10;p[g]=p[g-16]+E+p[g-7]+F>>>0}let i=l,v=u,x=f,C=m,h=y,_=d,M=b,O=c;for(let g=0;g<64;g++){let E=k(h,6)^k(h,11)^k(h,25),F=h&_^~h&M,H=O+E+F+te[g]+p[g]>>>0,Q=k(i,2)^k(i,13)^k(i,22),ee=i&v^i&x^v&x,re=Q+ee>>>0;O=M,M=_,_=h,h=C+H>>>0,C=x,x=v,v=i,i=H+re>>>0}l=l+i>>>0,u=u+v>>>0,f=f+x>>>0,m=m+C>>>0,y=y+h>>>0,d=d+_>>>0,b=b+M>>>0,c=c+O>>>0}return q(l)+q(u)}function T(e){return ne(e.join("|"))}var oe={conversion_led:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],evidence_led:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],price_led:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],discovery_led:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]},V=["conversion_led","evidence_led","price_led","discovery_led"];function Y(e,r,t,n){let o=oe[t];if(!o)return e;let s=o.indexOf("generic"),a=f=>{let m=o.indexOf(f);return m===-1?s:m},l=n?e.filter(f=>n.get(f)!=="structural"):[...e];if(l.sort((f,m)=>{var b,c;let y=(b=r.get(f))!=null?b:"generic",d=(c=r.get(m))!=null?c:"generic";return a(y)-a(d)}),!n)return l;let u=0;return e.map(f=>n.get(f)==="structural"?f:l[u++])}function se(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r>>>0}function ae(e,r,t,n){if(!t||t==="unknown")return e;let o=V[se(t)%V.length];return Y(e,r,o,n)}var _e=ae;function N(e,r,t){let n=new Map;n.set(T(e),[...e]);for(let o of V){let s=Y(e,r,o,t),a=T(s);n.has(a)||n.set(a,s)}return n}function $(e,r){if(e<1)return $(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let l=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(l))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function w(e,r,t=Math.random){let n=$(e,t),o=$(r,t),s=n+o;return s<=0?e/(e+r):n/s}function j(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=w(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=w(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function Re(e,r,t,n,o=Math.random,s){var m,y;let a=N(e,r,s),l=[];for(let d of a.keys()){let b=n.get(d);l.push({arm:d,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(y=b==null?void 0:b.beta)!=null?y:1})}let u=j(l,o),f=u?a.get(u):void 0;return f!=null?f:e}var z=20;function L(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var ie=5;function Oe(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,l=a>0?a*s.avgReward/(a+ie):0;(!t||l>t.score)&&(t={variantId:s.variantId,score:l})}return(o=t==null?void 0:t.variantId)!=null?o:null}var P="__all__",U={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ne(e,r,t=20){var d,b,c,p;let n=(d=e.segment)!=null?d:U,o=(b=e.global)!=null?b:U,s=S(o),a=L(S(n),s,t);if(!r)return a;let l=(c=e.persona)!=null?c:U,u=(p=e.child)!=null?p:U,f=L(S(l),s,t),m=(n.exposures+1)/(n.exposures+l.exposures+2),y={alpha:m*a.alpha+(1-m)*f.alpha,beta:m*a.beta+(1-m)*f.beta};return L(S(u),y,t)}function Ue(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===P,l=s.persona===P,u=a&&l?3:a||l?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Ke(e,r){return r==="unknown"||r===P||r===""?[{segment:e,persona:P},{segment:P,persona:P}]:[{segment:e,persona:r},{segment:e,persona:P},{segment:P,persona:r},{segment:P,persona:P}]}var G=4,le="__global__";function W(e,r){return r<=0?0:Math.min(G-1,Math.floor(e*G/r))}function Be(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:W(n,e.length)}})}var D=(e,r)=>`${e}#${r}`;function He(e,r,t,n,o=Math.random,s){var A;let a=N(e,r,s),l=new Map,u=new Map,f=0,m=0;for(let i of n)i.persona===le?(l.set(D(i.parent,i.bucket),i),f+=i.exposures,m+=i.conversions):i.persona===t&&u.set(D(i.parent,i.bucket),i);let y=S({exposures:f,conversions:m}),d=new Map,b=(i,v)=>{var E,F;let x=D(i,v),C=d.get(x);if(C!==void 0)return C;let h=l.get(x),_=L(S({exposures:(E=h==null?void 0:h.exposures)!=null?E:0,conversions:(F=h==null?void 0:h.conversions)!=null?F:0}),y),M=u.get(x),O=M?L(S({exposures:M.exposures,conversions:M.conversions}),_):_,g=w(O.alpha,O.beta,o);return d.set(x,g),g},c=null,p=-1/0;for(let i of a.values()){let v=0;for(let x=0;x<i.length;x++)v+=b((A=r.get(i[x]))!=null?A:"generic",W(x,i.length));v>p&&(p=v,c=i)}return c!=null?c:e}var K="__global__",We=["device","source","persona","visit"];function Z(e){let r=[];return e.device&&r.push({factor:"device",level:e.device}),e.source&&r.push({factor:"source",level:e.source}),e.persona&&e.persona!==R&&r.push({factor:"persona",level:e.persona}),e.visit&&r.push({factor:"visit",level:e.visit}),r}var B=(e,r,t)=>`${e}\0${r}\0${t}`;function Ze(e,r,t,n=Math.random){if(e.length===0)return null;if(e.length===1)return{arm:e[0],factorsUsed:0};let o=new Map,s=0,a=0;for(let c of t)o.set(B(c.arm,c.factor,c.level),c),c.level===K&&(s+=c.exposures,a+=c.conversions);let l=S({exposures:s,conversions:a}),u=Z(r),f=new Map,m=c=>{var A,i;let p=o.get(B(c,"global",K));return L(S({exposures:(A=p==null?void 0:p.exposures)!=null?A:0,conversions:(i=p==null?void 0:p.conversions)!=null?i:0}),l)},y=(c,p,A)=>{let i=B(c,p,A),v=f.get(i);if(v!==void 0)return v;let x=m(c),C=o.get(i),h=C?L(S({exposures:C.exposures,conversions:C.conversions}),x):x,_=w(h.alpha,h.beta,n);return f.set(i,_),_},d=null,b=-1/0;for(let c of e){let p=u.length===0?y(c,"global",K):0;for(let{factor:A,level:i}of u)p+=y(c,A,i);p>b&&(b=p,d=c)}return d===null?null:{arm:d,factorsUsed:u.length}}function Xe(e,r,t){let n=Number.isFinite(t)?Math.max(0,Math.min(1,t)):0,o=[{arm:e,factor:"global",level:K,weight:1}];for(let{factor:s,level:a}of Z(r))o.push({arm:e,factor:s,level:a,weight:s==="persona"?n:1});return o}function Je(e){return e==null||!Number.isFinite(e)||e<1?null:e>1?"returning":"new"}function X(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function J(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function er(e,r){return`${e}=${r}`}function ue(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return X(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return X(r)}function rr(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),l=Object.keys(s).sort();if(a.join(" ")!==l.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,f]of n)if(!f.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function tr(e,r){var t,n;return e.dims!=null?(n=(t=J(r))!=null?t:J(ue(e)))!=null?n:{}:r}function ce(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function or(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ce(`${e}:${r}`)%n.length]}function sr(e){return e>=.3?e<.7?"medium":"high":"low"}var fe=20;function pe(e,r,t=fe){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function lr(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=w(s.alpha,s.beta,t)*pe(s,r);a>o&&(n=s,o=a)}return n.arm}var me=/^[a-z0-9][a-z0-9_-]{0,31}$/;function ge(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&me.test(r)?r:null}var be=["unknown","__all__"];function de(e){var r;return(r=ge(e))!=null?r:R}var xe=[],he=64;function ye(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])be.includes(o)||r.set(o,n.key)}return r}function fr(e,r=xe){var l,u,f;let t=ye(r),n=(l=e.inferredConfidence)!=null?l:0,o,s=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(s!==""){let m=t.get(s);if(m!==void 0)return{persona:m,source:"declared",confidence:1};o=s.slice(0,he)}let a=de(e.clusterLabel);return a!==R&&t.has(a)?I({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):I({persona:R,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function pr(e){if(e==null)return R;let r=e.trim().toLowerCase();return r===""?R:r}export{xe as DEFAULT_PERSONA_VOCABULARY,fe as EV_SHRINK_K,K as GLOBAL_FACTOR_LEVEL,le as GLOBAL_FACTOR_PERSONA,oe as LAYOUT_ARCHETYPES,V as LAYOUT_ARCHETYPE_NAMES,G as LAYOUT_FACTOR_BUCKETS,me as PERSONA_KEY_RE,P as POOL_ALL,be as RESERVED_PERSONA_KEYS,z as SHRINKAGE_M,We as SLOT_FACTORS,R as UNKNOWN_PERSONA,ve as UNKNOWN_PERSONA_DISPLAY,ie as WEIGHTS_FALLBACK_PRIOR_PULLS,_e as applyClusterHeuristic,Ue as broadestValueCell,N as candidateLayouts,X as canonicalArm,de as canonicalPersona,Re as chooseLayout,He as chooseLayoutFactored,Ze as chooseSlotArmFactored,sr as confidenceBand,pr as decisionPersona,Be as factorCellsForOrder,Xe as factorCellsForTrial,Z as factorLevelsFor,ce as fnv1a,T as hashLayout,W as layoutBucketOf,er as marginalArmKey,ge as normalizeDeclaredPersona,Y as orderByArchetype,J as parseArm,or as pickDeterministicArm,Oe as pickFromWeights,Ne as pooledPosterior,S as posteriorOfCounts,ae as previewOrderForPersona,fr as resolvePersona,j as sampleArm,lr as sampleArmEv,w as sampleBeta,pe as shrunkAvgValue,L as shrunkPosterior,ue as slotBaselineArm,tr as slotResultFor,rr as validateSlotDecl,Je as visitLevel,Ke as weightCellsFor};
|