@sentientui/policy 0.4.0 → 0.6.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/dist/index.d.cts CHANGED
@@ -21,9 +21,12 @@ declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
21
21
  /**
22
22
  * Reorders section IDs based on the persona's semantic priority.
23
23
  * Sections with no graph entry are treated as 'generic'.
24
- * Returns the input unchanged for the unknown persona.
24
+ * Returns the input unchanged for 'unknown' — and for any custom vocabulary
25
+ * persona (declared/discovered): those have no semantic prior, so they serve
26
+ * the natural order until the layout bandit has learned rows, the same
27
+ * cold-start posture 'unknown' gets.
25
28
  */
26
- declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
29
+ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: string): string[];
27
30
  /**
28
31
  * The candidate layout orderings for a page — the distinct section orders
29
32
  * produced by every persona's semantic priority (plus the requesting
@@ -31,7 +34,7 @@ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<str
31
34
  * "arms" the layout bandit explores. Returned as hash → order so it joins
32
35
  * directly against layout_weights rows keyed by the same hashLayout.
33
36
  */
34
- declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
37
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: string): Map<string, string[]>;
35
38
 
36
39
  /**
37
40
  * Stable 16-char SHA-256 prefix for a section order array.
@@ -62,7 +65,7 @@ type LearnedLayout = {
62
65
  * NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
63
66
  * (tests, replayable decisions) — otherwise the sampled order varies per call.
64
67
  */
65
- declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
68
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: string, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
66
69
 
67
70
  /** Learned Beta(alpha, beta) posterior for one arm. */
68
71
  type ArmPosterior = {
@@ -134,20 +137,49 @@ declare function validateSlotDecl(decl: SlotDecl): {
134
137
  */
135
138
  declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
136
139
 
137
- /** Empirical-Bayes pooling strength: w = m / (m + exposures). */
140
+ /**
141
+ * Empirical-Bayes pooling strength, in pseudo-observations. A cell is born
142
+ * holding `m` imaginary trials drawn at its parent's rate, and its own data
143
+ * outvotes them once it has collected more than `m` real ones.
144
+ */
138
145
  declare const SHRINKAGE_M = 20;
139
146
  /**
140
- * Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
141
- * (pooled posterior dominates at 0 exposures) and detach as their own data
142
- * accumulates. Formula pinned in CONTRACTS.md:
143
- * w = m / (m + persona.exposures)
144
- * alpha' = persona.alpha + w * pooled.alpha
145
- * beta' = persona.beta + w * pooled.beta
147
+ * Empirical-Bayes shrinkage toward a parent posterior, at read time.
148
+ * Pinned in CONTRACTS.md §4.
149
+ *
150
+ * A cell is born warm — with no data of its own it sits at the parent's rate —
151
+ * and detaches as its own evidence accumulates.
152
+ *
153
+ * mu = pooled.alpha / (pooled.alpha + pooled.beta) // parent's rate
154
+ * strength = m * mass / (mass + m), mass = pooled.alpha + pooled.beta
155
+ * alpha' = cell.alpha + strength * mu
156
+ * beta' = cell.beta + strength * (1 - mu)
157
+ *
158
+ * The prior contributes a FIXED number of pseudo-observations, never a copy of
159
+ * the parent's counts. That distinction is the whole point of this function.
160
+ * The previous form was `cell.alpha + w * pooled.alpha` with
161
+ * `w = m / (m + cell.exposures)`, which folded the parent's SAMPLE SIZE into
162
+ * the child, and broke in two compounding ways once a project had real traffic:
163
+ *
164
+ * - Because the write path expands every trial into the child, both marginals
165
+ * and the global row (`weightCellsFor`), the parent's counts grow with total
166
+ * project volume. A cell then needed roughly sqrt(m * N_parent) exposures
167
+ * before its own rate mattered — ~1,400 against a 100k-exposure parent, not
168
+ * the ~20 the constant advertises. Personalization effectively never arrived.
169
+ * - Worse, the child inherited the parent's CONFIDENCE along with its rate. A
170
+ * 20-exposure cell emerged with a posterior of pseudo-count ~8,400 and a
171
+ * standard deviation of 0.002 against the ~0.09 its evidence justifies.
172
+ * Thompson Sampling draws from that posterior are effectively deterministic,
173
+ * so exploration collapsed exactly in the thin cells that needed it.
174
+ *
175
+ * `strength` is itself damped by the parent's mass so a parent that has barely
176
+ * any data of its own cannot inject `m` confident pseudo-observations of a rate
177
+ * nobody knows yet: an empty parent (mass 2, the flat Beta(1,1)) contributes
178
+ * ~1.8 pseudo-trials, a well-sampled one contributes the full `m`.
146
179
  */
147
- declare function shrunkPosterior(persona: {
180
+ declare function shrunkPosterior(cell: {
148
181
  alpha: number;
149
182
  beta: number;
150
- exposures: number;
151
183
  }, pooled: {
152
184
  alpha: number;
153
185
  beta: number;
@@ -191,11 +223,45 @@ declare function posteriorOfCounts(c: PoolCounts): {
191
223
  * Every cell is optional; an absent cell contributes Beta(1,1)-with-0-evidence,
192
224
  * which is what lets the same function reproduce the legacy variant (segment-only)
193
225
  * and legacy slot (persona-only) behaviors on day one after migration.
226
+ *
227
+ * Only the parent's MEAN crosses each shrink boundary (see shrunkPosterior) —
228
+ * never its sample size. That is what keeps a thin child's posterior as WIDE as
229
+ * its own evidence warrants, so Thompson Sampling still explores it. The blend
230
+ * weight below therefore decides which axis sets the parent's rate; the levels'
231
+ * absolute magnitudes no longer leak into the child's confidence.
194
232
  */
195
233
  declare function pooledPosterior(cells: PoolCells, personaKnown: boolean, m?: number): {
196
234
  alpha: number;
197
235
  beta: number;
198
236
  };
237
+ /** A weight row's value-posterior columns, plus the cell it belongs to. */
238
+ type ValueCellRow = {
239
+ segment: string;
240
+ persona: string;
241
+ valueSum: number;
242
+ valueCount: number;
243
+ };
244
+ /**
245
+ * The value cell for EV ranking: the BROADEST cell present for an arm.
246
+ *
247
+ * Never a sum across cells. `weightCellsFor` writes each trial to the child,
248
+ * both marginals AND the global row, so adding them up counts every real order
249
+ * 2-4x depending on whether the persona was known. The average survives that
250
+ * (numerator and denominator inflate together) but the EB shrinkage weight does
251
+ * not: `valueCount / (valueCount + EV_SHRINK_K)` with K = 20 is meant to give a
252
+ * cell its own voice at ~20 valued orders, and at 4x inflation it happened at
253
+ * 5 — by a factor that varied with the arm's persona mix, so identical arms
254
+ * shrank differently.
255
+ *
256
+ * The broadest cell already holds every trial in its slice exactly once, which
257
+ * makes this correct for both serving views: the pooled hierarchy (up to four
258
+ * rows per arm, global wins) and the legacy marginal view (exactly one row per
259
+ * arm, which is therefore the broadest).
260
+ */
261
+ declare function broadestValueCell(rows: readonly ValueCellRow[]): {
262
+ valueSum: number;
263
+ valueCount: number;
264
+ };
199
265
  /**
200
266
  * Write-side cell expansion: which weight rows one trial/credit must bump.
201
267
  * Unknown persona bumps ONLY the segment marginal + global — no 'unknown'
@@ -238,4 +304,87 @@ declare function shrunkAvgValue(cell: ValueCell, reference: number, k?: number):
238
304
  type EvArm = ArmPosterior & ValueCell;
239
305
  declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => number): string | null;
240
306
 
241
- export { type ArmPosterior, CLUSTER_PRIORITY, EV_SHRINK_K, type EvArm, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, POOL_ALL, type Persona, type PersonaKey, type PoolCells, type PoolCounts, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, pooledPosterior, posteriorOfCounts, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
307
+ /**
308
+ * Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
309
+ *
310
+ * The persona axis stops being the closed global union in `personas.ts` and
311
+ * becomes a per-project member list (persona_sets / persona_set_members,
312
+ * migration 113). This module owns resolution: which persona a decision is
313
+ * keyed on, given what the customer's app declared and what clustering
314
+ * inferred. The weight tables and pooling are already string-generic —
315
+ * resolution is the only place vocabulary rules live.
316
+ */
317
+ /**
318
+ * Key shape for vocabulary members. Personas are partition labels that land in
319
+ * slot_weights and in customers' CSS selectors — no '@', no spaces, no
320
+ * uppercase, so emails/usernames structurally cannot become partition keys.
321
+ * MUST stay in sync with the CHECK constraint in migration 113.
322
+ */
323
+ declare const PERSONA_KEY_RE: RegExp;
324
+ /**
325
+ * Keys no vocabulary member may claim. 'unknown' and '__all__' are structural
326
+ * in weightCellsFor / CONTRACTS §4; the plural forms are pre-069 legacy labels
327
+ * that canonicalPersona still remaps, so a member claiming one would be
328
+ * silently rewritten at resolve time. MUST stay in sync with migration 113.
329
+ */
330
+ declare const RESERVED_PERSONA_KEYS: readonly string[];
331
+ type PersonaVocabularyMember = {
332
+ key: string;
333
+ displayName: string;
334
+ /** Prior keys (renames) that resolve to this member. */
335
+ aliases?: readonly string[];
336
+ /** Retired members stop resolving but their weight rows keep pooling. */
337
+ status?: 'active' | 'retired';
338
+ };
339
+ /**
340
+ * The pinned four as a vocabulary — what every project's version-1 default set
341
+ * contains, and the fallback when a project has no active set (a missed
342
+ * app-code insert degrades to today's behaviour, never an error).
343
+ */
344
+ declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
345
+ type PersonaResolution = {
346
+ /** Vocabulary key, or 'unknown'. This is what decisions/weights key on. */
347
+ persona: string;
348
+ /** Which path produced the persona; 'none' means nothing resolved. */
349
+ source: 'declared' | 'inferred' | 'none';
350
+ /** Declared is ground truth from the customer's own app → 1. Otherwise the
351
+ * portrait reliability the caller passed through (0 when absent). */
352
+ confidence: number;
353
+ /** Set when a declared value did not resolve — feeds the dashboard's
354
+ * "your app sent 'staff' 1.2k times, add it?" nudge. Normalized and
355
+ * truncated; never served. */
356
+ unrecognizedDeclared?: string;
357
+ };
358
+ /**
359
+ * Resolves the persona a decision is keyed on.
360
+ *
361
+ * Precedence, in order:
362
+ * 1. Declared value matching an active member (directly, via alias, or via a
363
+ * legacy plural label whose canonical form is a member) → that key,
364
+ * confidence 1. Declared skips reliability gating: it is ground truth from
365
+ * the customer's app, the same trust level as everything else the pk_ key
366
+ * sends.
367
+ * 2. Declared present but unrecognized → NOT served (a config gap is not a
368
+ * signal); reported via `unrecognizedDeclared` and resolution falls through
369
+ * to the inferred path, byte-identical to an undeclared session.
370
+ * 3. Inferred cluster label, canonicalized, but only if the active vocabulary
371
+ * still contains it — a retired member must not keep being served just
372
+ * because the nightly refit still emits its label.
373
+ */
374
+ declare function resolvePersona(input: {
375
+ declared?: string | null;
376
+ clusterLabel?: string | null;
377
+ inferredConfidence?: number | null;
378
+ }, members?: readonly PersonaVocabularyMember[]): PersonaResolution;
379
+ /**
380
+ * Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
381
+ * for training. Unlike `canonicalPersona`, this trusts the stored value
382
+ * verbatim: it was validated against the project vocabulary when the decision
383
+ * was written, and re-squashing it through the closed global union at close-out
384
+ * silently rerouted every declared-persona trial onto the 'unknown' marginals
385
+ * (the double-squash bug, spec §4.3). Only the legacy plural labels are still
386
+ * remapped — pre-069 rows carry them.
387
+ */
388
+ declare function decisionPersona(label: string | null | undefined): string;
389
+
390
+ export { type ArmPosterior, CLUSTER_PRIORITY, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, PERSONA_KEY_RE, POOL_ALL, type Persona, type PersonaKey, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, type ValueCellRow, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, decisionPersona, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, pooledPosterior, posteriorOfCounts, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
package/dist/index.d.ts CHANGED
@@ -21,9 +21,12 @@ declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
21
21
  /**
22
22
  * Reorders section IDs based on the persona's semantic priority.
23
23
  * Sections with no graph entry are treated as 'generic'.
24
- * Returns the input unchanged for the unknown persona.
24
+ * Returns the input unchanged for 'unknown' — and for any custom vocabulary
25
+ * persona (declared/discovered): those have no semantic prior, so they serve
26
+ * the natural order until the layout bandit has learned rows, the same
27
+ * cold-start posture 'unknown' gets.
25
28
  */
26
- declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
29
+ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: string): string[];
27
30
  /**
28
31
  * The candidate layout orderings for a page — the distinct section orders
29
32
  * produced by every persona's semantic priority (plus the requesting
@@ -31,7 +34,7 @@ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<str
31
34
  * "arms" the layout bandit explores. Returned as hash → order so it joins
32
35
  * directly against layout_weights rows keyed by the same hashLayout.
33
36
  */
34
- declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
37
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: string): Map<string, string[]>;
35
38
 
36
39
  /**
37
40
  * Stable 16-char SHA-256 prefix for a section order array.
@@ -62,7 +65,7 @@ type LearnedLayout = {
62
65
  * NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
63
66
  * (tests, replayable decisions) — otherwise the sampled order varies per call.
64
67
  */
65
- declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
68
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: string, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
66
69
 
67
70
  /** Learned Beta(alpha, beta) posterior for one arm. */
68
71
  type ArmPosterior = {
@@ -134,20 +137,49 @@ declare function validateSlotDecl(decl: SlotDecl): {
134
137
  */
135
138
  declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
136
139
 
137
- /** Empirical-Bayes pooling strength: w = m / (m + exposures). */
140
+ /**
141
+ * Empirical-Bayes pooling strength, in pseudo-observations. A cell is born
142
+ * holding `m` imaginary trials drawn at its parent's rate, and its own data
143
+ * outvotes them once it has collected more than `m` real ones.
144
+ */
138
145
  declare const SHRINKAGE_M = 20;
139
146
  /**
140
- * Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
141
- * (pooled posterior dominates at 0 exposures) and detach as their own data
142
- * accumulates. Formula pinned in CONTRACTS.md:
143
- * w = m / (m + persona.exposures)
144
- * alpha' = persona.alpha + w * pooled.alpha
145
- * beta' = persona.beta + w * pooled.beta
147
+ * Empirical-Bayes shrinkage toward a parent posterior, at read time.
148
+ * Pinned in CONTRACTS.md §4.
149
+ *
150
+ * A cell is born warm — with no data of its own it sits at the parent's rate —
151
+ * and detaches as its own evidence accumulates.
152
+ *
153
+ * mu = pooled.alpha / (pooled.alpha + pooled.beta) // parent's rate
154
+ * strength = m * mass / (mass + m), mass = pooled.alpha + pooled.beta
155
+ * alpha' = cell.alpha + strength * mu
156
+ * beta' = cell.beta + strength * (1 - mu)
157
+ *
158
+ * The prior contributes a FIXED number of pseudo-observations, never a copy of
159
+ * the parent's counts. That distinction is the whole point of this function.
160
+ * The previous form was `cell.alpha + w * pooled.alpha` with
161
+ * `w = m / (m + cell.exposures)`, which folded the parent's SAMPLE SIZE into
162
+ * the child, and broke in two compounding ways once a project had real traffic:
163
+ *
164
+ * - Because the write path expands every trial into the child, both marginals
165
+ * and the global row (`weightCellsFor`), the parent's counts grow with total
166
+ * project volume. A cell then needed roughly sqrt(m * N_parent) exposures
167
+ * before its own rate mattered — ~1,400 against a 100k-exposure parent, not
168
+ * the ~20 the constant advertises. Personalization effectively never arrived.
169
+ * - Worse, the child inherited the parent's CONFIDENCE along with its rate. A
170
+ * 20-exposure cell emerged with a posterior of pseudo-count ~8,400 and a
171
+ * standard deviation of 0.002 against the ~0.09 its evidence justifies.
172
+ * Thompson Sampling draws from that posterior are effectively deterministic,
173
+ * so exploration collapsed exactly in the thin cells that needed it.
174
+ *
175
+ * `strength` is itself damped by the parent's mass so a parent that has barely
176
+ * any data of its own cannot inject `m` confident pseudo-observations of a rate
177
+ * nobody knows yet: an empty parent (mass 2, the flat Beta(1,1)) contributes
178
+ * ~1.8 pseudo-trials, a well-sampled one contributes the full `m`.
146
179
  */
147
- declare function shrunkPosterior(persona: {
180
+ declare function shrunkPosterior(cell: {
148
181
  alpha: number;
149
182
  beta: number;
150
- exposures: number;
151
183
  }, pooled: {
152
184
  alpha: number;
153
185
  beta: number;
@@ -191,11 +223,45 @@ declare function posteriorOfCounts(c: PoolCounts): {
191
223
  * Every cell is optional; an absent cell contributes Beta(1,1)-with-0-evidence,
192
224
  * which is what lets the same function reproduce the legacy variant (segment-only)
193
225
  * and legacy slot (persona-only) behaviors on day one after migration.
226
+ *
227
+ * Only the parent's MEAN crosses each shrink boundary (see shrunkPosterior) —
228
+ * never its sample size. That is what keeps a thin child's posterior as WIDE as
229
+ * its own evidence warrants, so Thompson Sampling still explores it. The blend
230
+ * weight below therefore decides which axis sets the parent's rate; the levels'
231
+ * absolute magnitudes no longer leak into the child's confidence.
194
232
  */
195
233
  declare function pooledPosterior(cells: PoolCells, personaKnown: boolean, m?: number): {
196
234
  alpha: number;
197
235
  beta: number;
198
236
  };
237
+ /** A weight row's value-posterior columns, plus the cell it belongs to. */
238
+ type ValueCellRow = {
239
+ segment: string;
240
+ persona: string;
241
+ valueSum: number;
242
+ valueCount: number;
243
+ };
244
+ /**
245
+ * The value cell for EV ranking: the BROADEST cell present for an arm.
246
+ *
247
+ * Never a sum across cells. `weightCellsFor` writes each trial to the child,
248
+ * both marginals AND the global row, so adding them up counts every real order
249
+ * 2-4x depending on whether the persona was known. The average survives that
250
+ * (numerator and denominator inflate together) but the EB shrinkage weight does
251
+ * not: `valueCount / (valueCount + EV_SHRINK_K)` with K = 20 is meant to give a
252
+ * cell its own voice at ~20 valued orders, and at 4x inflation it happened at
253
+ * 5 — by a factor that varied with the arm's persona mix, so identical arms
254
+ * shrank differently.
255
+ *
256
+ * The broadest cell already holds every trial in its slice exactly once, which
257
+ * makes this correct for both serving views: the pooled hierarchy (up to four
258
+ * rows per arm, global wins) and the legacy marginal view (exactly one row per
259
+ * arm, which is therefore the broadest).
260
+ */
261
+ declare function broadestValueCell(rows: readonly ValueCellRow[]): {
262
+ valueSum: number;
263
+ valueCount: number;
264
+ };
199
265
  /**
200
266
  * Write-side cell expansion: which weight rows one trial/credit must bump.
201
267
  * Unknown persona bumps ONLY the segment marginal + global — no 'unknown'
@@ -238,4 +304,87 @@ declare function shrunkAvgValue(cell: ValueCell, reference: number, k?: number):
238
304
  type EvArm = ArmPosterior & ValueCell;
239
305
  declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => number): string | null;
240
306
 
241
- export { type ArmPosterior, CLUSTER_PRIORITY, EV_SHRINK_K, type EvArm, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, POOL_ALL, type Persona, type PersonaKey, type PoolCells, type PoolCounts, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, pooledPosterior, posteriorOfCounts, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
307
+ /**
308
+ * Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
309
+ *
310
+ * The persona axis stops being the closed global union in `personas.ts` and
311
+ * becomes a per-project member list (persona_sets / persona_set_members,
312
+ * migration 113). This module owns resolution: which persona a decision is
313
+ * keyed on, given what the customer's app declared and what clustering
314
+ * inferred. The weight tables and pooling are already string-generic —
315
+ * resolution is the only place vocabulary rules live.
316
+ */
317
+ /**
318
+ * Key shape for vocabulary members. Personas are partition labels that land in
319
+ * slot_weights and in customers' CSS selectors — no '@', no spaces, no
320
+ * uppercase, so emails/usernames structurally cannot become partition keys.
321
+ * MUST stay in sync with the CHECK constraint in migration 113.
322
+ */
323
+ declare const PERSONA_KEY_RE: RegExp;
324
+ /**
325
+ * Keys no vocabulary member may claim. 'unknown' and '__all__' are structural
326
+ * in weightCellsFor / CONTRACTS §4; the plural forms are pre-069 legacy labels
327
+ * that canonicalPersona still remaps, so a member claiming one would be
328
+ * silently rewritten at resolve time. MUST stay in sync with migration 113.
329
+ */
330
+ declare const RESERVED_PERSONA_KEYS: readonly string[];
331
+ type PersonaVocabularyMember = {
332
+ key: string;
333
+ displayName: string;
334
+ /** Prior keys (renames) that resolve to this member. */
335
+ aliases?: readonly string[];
336
+ /** Retired members stop resolving but their weight rows keep pooling. */
337
+ status?: 'active' | 'retired';
338
+ };
339
+ /**
340
+ * The pinned four as a vocabulary — what every project's version-1 default set
341
+ * contains, and the fallback when a project has no active set (a missed
342
+ * app-code insert degrades to today's behaviour, never an error).
343
+ */
344
+ declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
345
+ type PersonaResolution = {
346
+ /** Vocabulary key, or 'unknown'. This is what decisions/weights key on. */
347
+ persona: string;
348
+ /** Which path produced the persona; 'none' means nothing resolved. */
349
+ source: 'declared' | 'inferred' | 'none';
350
+ /** Declared is ground truth from the customer's own app → 1. Otherwise the
351
+ * portrait reliability the caller passed through (0 when absent). */
352
+ confidence: number;
353
+ /** Set when a declared value did not resolve — feeds the dashboard's
354
+ * "your app sent 'staff' 1.2k times, add it?" nudge. Normalized and
355
+ * truncated; never served. */
356
+ unrecognizedDeclared?: string;
357
+ };
358
+ /**
359
+ * Resolves the persona a decision is keyed on.
360
+ *
361
+ * Precedence, in order:
362
+ * 1. Declared value matching an active member (directly, via alias, or via a
363
+ * legacy plural label whose canonical form is a member) → that key,
364
+ * confidence 1. Declared skips reliability gating: it is ground truth from
365
+ * the customer's app, the same trust level as everything else the pk_ key
366
+ * sends.
367
+ * 2. Declared present but unrecognized → NOT served (a config gap is not a
368
+ * signal); reported via `unrecognizedDeclared` and resolution falls through
369
+ * to the inferred path, byte-identical to an undeclared session.
370
+ * 3. Inferred cluster label, canonicalized, but only if the active vocabulary
371
+ * still contains it — a retired member must not keep being served just
372
+ * because the nightly refit still emits its label.
373
+ */
374
+ declare function resolvePersona(input: {
375
+ declared?: string | null;
376
+ clusterLabel?: string | null;
377
+ inferredConfidence?: number | null;
378
+ }, members?: readonly PersonaVocabularyMember[]): PersonaResolution;
379
+ /**
380
+ * Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
381
+ * for training. Unlike `canonicalPersona`, this trusts the stored value
382
+ * verbatim: it was validated against the project vocabulary when the decision
383
+ * was written, and re-squashing it through the closed global union at close-out
384
+ * silently rerouted every declared-persona trial onto the 'unknown' marginals
385
+ * (the double-squash bug, spec §4.3). Only the legacy plural labels are still
386
+ * remapped — pre-069 rows carry them.
387
+ */
388
+ declare function decisionPersona(label: string | null | undefined): string;
389
+
390
+ export { type ArmPosterior, CLUSTER_PRIORITY, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, PERSONA_KEY_RE, POOL_ALL, type Persona, type PersonaKey, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, type ValueCellRow, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, decisionPersona, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, pooledPosterior, posteriorOfCounts, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var M=Object.defineProperty,ie=Object.defineProperties,ue=Object.getOwnPropertyDescriptor,ce=Object.getOwnPropertyDescriptors,le=Object.getOwnPropertyNames,Y=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,fe=Object.prototype.propertyIsEnumerable;var F=(e,r,t)=>r in e?M(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,O=(e,r)=>{for(var t in r||(r={}))T.call(r,t)&&F(e,t,r[t]);if(Y)for(var t of Y(r))fe.call(r,t)&&F(e,t,r[t]);return e},E=(e,r)=>ie(e,ce(r));var me=(e,r)=>{for(var t in r)M(e,t,{get:r[t],enumerable:!0})},be=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of le(r))!T.call(e,s)&&s!==t&&M(e,s,{get:()=>r[s],enumerable:!(n=ue(r,s))||n.enumerable});return e};var pe=e=>be(M({},"__esModule",{value:!0}),e);var Ce={};me(Ce,{CLUSTER_PRIORITY:()=>Q,EV_SHRINK_K:()=>te,LEGACY_PERSONA_MAP:()=>Z,PERSONAS:()=>H,PERSONA_DISPLAY:()=>xe,POOL_ALL:()=>g,SHRINKAGE_M:()=>ee,UNKNOWN_PERSONA:()=>v,applyClusterHeuristic:()=>N,candidateLayouts:()=>U,canonicalArm:()=>G,canonicalPersona:()=>ge,chooseLayout:()=>ye,confidenceBand:()=>Re,fnv1a:()=>re,hashLayout:()=>I,marginalArmKey:()=>Pe,parseArm:()=>z,pickDeterministicArm:()=>we,pooledPosterior:()=>ve,posteriorOfCounts:()=>R,sampleArm:()=>V,sampleArmEv:()=>_e,sampleBeta:()=>S,shrunkAvgValue:()=>ne,shrunkPosterior:()=>w,slotBaselineArm:()=>X,slotResultFor:()=>Ae,validateSlotDecl:()=>ke,weightCellsFor:()=>Se});module.exports=pe(Ce);var H=["buyer","researcher","deal_seeker","browser"],v="unknown",xe={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},Z={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function ge(e){var t;if(e==null)return v;let r=e.trim().toLowerCase();return(t=Z[r])!=null?t:v}var de=[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 x(e,r){return e>>>r|e<<32-r}function J(e){return(e>>>0).toString(16).padStart(8,"0")}function he(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 u=1779033703,c=3144134277,m=1013904242,l=2773480762,p=1359893119,b=2600822924,y=528734635,P=1541459225,f=new Uint32Array(64);for(let L=0;L<s;L+=64){for(let i=0;i<16;i++)f[i]=a.getUint32(L+i*4,!1);for(let i=16;i<64;i++){let $=x(f[i-15],7)^x(f[i-15],18)^f[i-15]>>>3,j=x(f[i-2],17)^x(f[i-2],19)^f[i-2]>>>10;f[i]=f[i-16]+$+f[i-7]+j>>>0}let d=u,k=c,A=m,q=l,h=p,_=b,C=y,D=P;for(let i=0;i<64;i++){let $=x(h,6)^x(h,11)^x(h,25),j=h&_^~h&C,W=D+$+j+de[i]+f[i]>>>0,oe=x(d,2)^x(d,13)^x(d,22),se=d&k^d&A^k&A,ae=oe+se>>>0;D=C,C=_,_=h,h=q+W>>>0,q=A,A=k,k=d,d=W+ae>>>0}u=u+d>>>0,c=c+k>>>0,m=m+A>>>0,l=l+q>>>0,p=p+h>>>0,b=b+_>>>0,y=y+C>>>0,P=P+D>>>0}return J(u)+J(c)}function I(e){return he(e.join("|"))}var Q={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){let n=t===v?void 0:Q[t];if(!n)return e;let s=n.indexOf("generic"),o=a=>{let u=n.indexOf(a);return u===-1?s:u};return[...e].sort((a,u)=>{var l,p;let c=(l=r.get(a))!=null?l:"generic",m=(p=r.get(u))!=null?p:"generic";return o(c)-o(m)})}function U(e,r,t){let n=new Map;for(let s of[...H,t]){let o=N(e,r,s);n.set(I(o),o)}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 s,o;do{let u=Math.max(1e-15,r()),c=r();s=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),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 S(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 V(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=S(t.alpha,t.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=S(o.alpha,o.beta,r);a>n&&(t=o,n=a)}return t.arm}function ye(e,r,t,n,s=Math.random){var m,l;let o=U(e,r,t),a=[];for(let p of o.keys()){let b=n.get(p);a.push({arm:p,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(l=b==null?void 0:b.beta)!=null?l:1})}let u=V(a,s),c=u?o.get(u):void 0;return c!=null?c:N(e,r,t)}function G(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 s=t.slice(0,n);if(s in r)return null;r[s]=t.slice(n+1)}return r}function Pe(e,r){return`${e}=${r}`}function X(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 G(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 G(r)}function ke(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(([c])=>c).sort(),u=Object.keys(o).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,m]of n)if(!m.includes(o[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function Ae(e,r){var t,n;return e.dims!=null?(n=(t=z(r))!=null?t:z(X(e)))!=null?n:{}:r}var ee=20;function w(e,r,t=20){let n=t/(t+e.exposures);return{alpha:e.alpha+n*r.alpha,beta:e.beta+n*r.beta}}var g="__all__",K={exposures:0,conversions:0};function R(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function ve(e,r,t=20){var b,y,P,f;let n=(b=e.segment)!=null?b:K,s=(y=e.global)!=null?y:K,o=R(s),a=w(E(O({},R(n)),{exposures:n.exposures}),o,t);if(!r)return a;let u=(P=e.persona)!=null?P:K,c=(f=e.child)!=null?f:K,m=w(E(O({},R(u)),{exposures:u.exposures}),o,t),l=(n.exposures+1)/(n.exposures+u.exposures+2),p={alpha:l*a.alpha+(1-l)*m.alpha,beta:l*a.beta+(1-l)*m.beta};return w(E(O({},R(c)),{exposures:c.exposures}),p,t)}function Se(e,r){return r==="unknown"||r===g||r===""?[{segment:e,persona:g},{segment:g,persona:g}]:[{segment:e,persona:r},{segment:e,persona:g},{segment:g,persona:r},{segment:g,persona:g}]}function re(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 we(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[re(`${e}:${r}`)%n.length]}function Re(e){return e>=.3?e<.7?"medium":"high":"low"}var te=20;function ne(e,r,t=te){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 _e(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=S(o.alpha,o.beta,t)*ne(o,r);a>s&&(n=o,s=a)}return n.arm}0&&(module.exports={CLUSTER_PRIORITY,EV_SHRINK_K,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,POOL_ALL,SHRINKAGE_M,UNKNOWN_PERSONA,applyClusterHeuristic,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,confidenceBand,fnv1a,hashLayout,marginalArmKey,parseArm,pickDeterministicArm,pooledPosterior,posteriorOfCounts,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,weightCellsFor});
1
+ "use strict";var L=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames,T=Object.getOwnPropertySymbols;var X=Object.prototype.hasOwnProperty,me=Object.prototype.propertyIsEnumerable;var Z=(e,r,n)=>r in e?L(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,$=(e,r)=>{for(var n in r||(r={}))X.call(r,n)&&Z(e,n,r[n]);if(T)for(var n of T(r))me.call(r,n)&&Z(e,n,r[n]);return e};var be=(e,r)=>{for(var n in r)L(e,n,{get:r[n],enumerable:!0})},pe=(e,r,n,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of fe(r))!X.call(e,s)&&s!==n&&L(e,s,{get:()=>r[s],enumerable:!(t=ce(r,s))||t.enumerable});return e};var ge=e=>pe(L({},"__esModule",{value:!0}),e);var Oe={};be(Oe,{CLUSTER_PRIORITY:()=>Q,DEFAULT_PERSONA_VOCABULARY:()=>ae,EV_SHRINK_K:()=>te,LEGACY_PERSONA_MAP:()=>M,PERSONAS:()=>S,PERSONA_DISPLAY:()=>j,PERSONA_KEY_RE:()=>we,POOL_ALL:()=>g,RESERVED_PERSONA_KEYS:()=>se,SHRINKAGE_M:()=>re,UNKNOWN_PERSONA:()=>y,applyClusterHeuristic:()=>O,broadestValueCell:()=>Re,candidateLayouts:()=>z,canonicalArm:()=>G,canonicalPersona:()=>N,chooseLayout:()=>he,confidenceBand:()=>_e,decisionPersona:()=>Ne,fnv1a:()=>ne,hashLayout:()=>H,marginalArmKey:()=>ye,parseArm:()=>F,pickDeterministicArm:()=>ve,pooledPosterior:()=>ke,posteriorOfCounts:()=>C,resolvePersona:()=>Me,sampleArm:()=>Y,sampleArmEv:()=>Ce,sampleBeta:()=>v,shrunkAvgValue:()=>oe,shrunkPosterior:()=>_,slotBaselineArm:()=>ee,slotResultFor:()=>Ae,validateSlotDecl:()=>Pe,weightCellsFor:()=>Se});module.exports=ge(Oe);var S=["buyer","researcher","deal_seeker","browser"],y="unknown",j={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},M={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function N(e){var n;if(e==null)return y;let r=e.trim().toLowerCase();return(n=M[r])!=null?n:y}var de=[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 d(e,r){return e>>>r|e<<32-r}function J(e){return(e>>>0).toString(16).padStart(8,"0")}function xe(e){let r=new TextEncoder().encode(e),n=r.length,t=n*8,s=(n+8>>6)+1<<6,o=new Uint8Array(s);o.set(r),o[n]=128;let a=new DataView(o.buffer);a.setUint32(s-8,Math.floor(t/4294967296),!1),a.setUint32(s-4,t>>>0,!1);let i=1779033703,u=3144134277,f=1013904242,c=2773480762,p=1359893119,m=2600822924,P=528734635,A=1541459225,b=new Uint32Array(64);for(let K=0;K<s;K+=64){for(let l=0;l<16;l++)b[l]=a.getUint32(K+l*4,!1);for(let l=16;l<64;l++){let q=d(b[l-15],7)^d(b[l-15],18)^b[l-15]>>>3,I=d(b[l-2],17)^d(b[l-2],19)^b[l-2]>>>10;b[l]=b[l-16]+q+b[l-7]+I>>>0}let x=i,k=u,R=f,V=c,h=p,w=m,E=P,U=A;for(let l=0;l<64;l++){let q=d(h,6)^d(h,11)^d(h,25),I=h&w^~h&E,W=U+q+I+de[l]+b[l]>>>0,ie=d(x,2)^d(x,13)^d(x,22),ue=x&k^x&R^k&R,le=ie+ue>>>0;U=E,E=w,w=h,h=V+W>>>0,V=R,R=k,k=x,x=W+le>>>0}i=i+x>>>0,u=u+k>>>0,f=f+R>>>0,c=c+V>>>0,p=p+h>>>0,m=m+w>>>0,P=P+E>>>0,A=A+U>>>0}return J(i)+J(u)}function H(e){return xe(e.join("|"))}var Q={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 O(e,r,n){let t=Q[n];if(!t)return e;let s=t.indexOf("generic"),o=a=>{let i=t.indexOf(a);return i===-1?s:i};return[...e].sort((a,i)=>{var c,p;let u=(c=r.get(a))!=null?c:"generic",f=(p=r.get(i))!=null?p:"generic";return o(u)-o(f)})}function z(e,r,n){let t=new Map;for(let s of[...S,n]){let o=O(e,r,s);t.set(H(o),o)}return t}function B(e,r){if(e<1)return B(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let n=e-1/3,t=1/Math.sqrt(9*n);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+t*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+n*(1-o+Math.log(o)))return n*o}}function v(e,r,n=Math.random){let t=B(e,n),s=B(r,n),o=t+s;return o<=0?e/(e+r):t/o}function Y(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=e[0],t=v(n.alpha,n.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=v(o.alpha,o.beta,r);a>t&&(n=o,t=a)}return n.arm}function he(e,r,n,t,s=Math.random){var f,c;let o=z(e,r,n),a=[];for(let p of o.keys()){let m=t.get(p);a.push({arm:p,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(c=m==null?void 0:m.beta)!=null?c:1})}let i=Y(a,s),u=i?o.get(i):void 0;return u!=null?u:O(e,r,n)}function G(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function F(e){if(e.length===0)return null;let r={};for(let n of e.split("|")){let t=n.indexOf("=");if(t<=0||t!==n.lastIndexOf("=")||t===n.length-1)return null;let s=n.slice(0,t);if(s in r)return null;r[s]=n.slice(t+1)}return r}function ye(e,r){return`${e}=${r}`}function ee(e){var n,t,s;if(e.arms)return typeof e.baseline=="string"?e.baseline:(n=e.arms[0])!=null?n:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return G(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[o,a]of Object.entries((t=e.dims)!=null?t:{}))r[o]=(s=a[0])!=null?s:"";return G(r)}function Pe(e){let r=Array.isArray(e.arms),n=e.dims!=null;if(r&&n)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!n)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 t=Object.entries(e.dims);if(t.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(t.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let s=1;for(let[o,a]of t){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=t.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,f]of t)if(!f.includes(o[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function Ae(e,r){var n,t;return e.dims!=null?(t=(n=F(r))!=null?n:F(ee(e)))!=null?t:{}:r}var re=20;function _(e,r,n=20){let t=r.alpha+r.beta;if(t<=0||n<=0)return{alpha:e.alpha,beta:e.beta};let s=r.alpha/t,o=n*t/(t+n);return{alpha:e.alpha+o*s,beta:e.beta+o*(1-s)}}var g="__all__",D={exposures:0,conversions:0};function C(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function ke(e,r,n=20){var m,P,A,b;let t=(m=e.segment)!=null?m:D,s=(P=e.global)!=null?P:D,o=C(s),a=_(C(t),o,n);if(!r)return a;let i=(A=e.persona)!=null?A:D,u=(b=e.child)!=null?b:D,f=_(C(i),o,n),c=(t.exposures+1)/(t.exposures+i.exposures+2),p={alpha:c*a.alpha+(1-c)*f.alpha,beta:c*a.beta+(1-c)*f.beta};return _(C(u),p,n)}function Re(e){var t,s;let r=null,n=-1;for(let o of e){let a=o.segment===g,i=o.persona===g,u=a&&i?3:a||i?2:1;u>n&&(n=u,r=o)}return{valueSum:(t=r==null?void 0:r.valueSum)!=null?t:0,valueCount:(s=r==null?void 0:r.valueCount)!=null?s:0}}function Se(e,r){return r==="unknown"||r===g||r===""?[{segment:e,persona:g},{segment:g,persona:g}]:[{segment:e,persona:r},{segment:e,persona:g},{segment:g,persona:r},{segment:g,persona:g}]}function ne(e){let r=2166136261;for(let n=0;n<e.length;n++)r^=e.charCodeAt(n),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function ve(e,r,n){if(n.length===0)throw new Error("pickDeterministicArm requires at least one arm");let t=[...n].sort();return t[ne(`${e}:${r}`)%t.length]}function _e(e){return e>=.3?e<.7?"medium":"high":"low"}var te=20;function oe(e,r,n=te){let t=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return t;if(e.valueCount<=0)return r;let s=e.valueCount/(e.valueCount+n);return s*t+(1-s)*r}function Ce(e,r,n=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=null,s=-1/0;for(let o of e){let a=v(o.alpha,o.beta,n)*oe(o,r);a>s&&(t=o,s=a)}return t.arm}var we=/^[a-z0-9][a-z0-9_-]{0,31}$/,se=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],ae=S.map(e=>({key:e,displayName:j[e]})),Ee=64;function Le(e){var n;let r=new Map;for(let t of e)if(t.status!=="retired"){r.set(t.key,t.key);for(let s of(n=t.aliases)!=null?n:[])se.includes(s)||r.set(s,t.key)}return r}function Me(e,r=ae){var i,u,f;let n=Le(r),t=(i=e.inferredConfidence)!=null?i:0,s,o=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(o!==""){let c=n.get(o),p=c===void 0?n.get(N(o)):void 0,m=c!=null?c:p;if(m!==void 0)return{persona:m,source:"declared",confidence:1};s=o.slice(0,Ee)}let a=N(e.clusterLabel);return a!==y&&n.has(a)?$({persona:n.get(a),source:"inferred",confidence:t},s!==void 0&&{unrecognizedDeclared:s}):$({persona:y,source:"none",confidence:t},s!==void 0&&{unrecognizedDeclared:s})}function Ne(e){var n;if(e==null)return y;let r=e.trim().toLowerCase();return r===""?y:(n=M[r])!=null?n:r}0&&(module.exports={CLUSTER_PRIORITY,DEFAULT_PERSONA_VOCABULARY,EV_SHRINK_K,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,PERSONA_KEY_RE,POOL_ALL,RESERVED_PERSONA_KEYS,SHRINKAGE_M,UNKNOWN_PERSONA,applyClusterHeuristic,broadestValueCell,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,confidenceBand,decisionPersona,fnv1a,hashLayout,marginalArmKey,parseArm,pickDeterministicArm,pooledPosterior,posteriorOfCounts,resolvePersona,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,weightCellsFor});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var X=Object.defineProperty,ee=Object.defineProperties;var re=Object.getOwnPropertyDescriptors;var I=Object.getOwnPropertySymbols;var te=Object.prototype.hasOwnProperty,ne=Object.prototype.propertyIsEnumerable;var U=(e,r,t)=>r in e?X(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,w=(e,r)=>{for(var t in r||(r={}))te.call(r,t)&&U(e,t,r[t]);if(I)for(var t of I(r))ne.call(r,t)&&U(e,t,r[t]);return e},R=(e,r)=>ee(e,re(r));var B=["buyer","researcher","deal_seeker","browser"],_="unknown",be={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},oe={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function pe(e){var t;if(e==null)return _;let r=e.trim().toLowerCase();return(t=oe[r])!=null?t:_}var se=[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 x(e,r){return e>>>r|e<<32-r}function V(e){return(e>>>0).toString(16).padStart(8,"0")}function ae(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 u=1779033703,c=3144134277,m=1013904242,l=2773480762,p=1359893119,b=2600822924,y=528734635,P=1541459225,f=new Uint32Array(64);for(let N=0;N<s;N+=64){for(let i=0;i<16;i++)f[i]=a.getUint32(N+i*4,!1);for(let i=16;i<64;i++){let q=x(f[i-15],7)^x(f[i-15],18)^f[i-15]>>>3,D=x(f[i-2],17)^x(f[i-2],19)^f[i-2]>>>10;f[i]=f[i-16]+q+f[i-7]+D>>>0}let g=u,k=c,A=m,K=l,d=p,v=b,S=y,L=P;for(let i=0;i<64;i++){let q=x(d,6)^x(d,11)^x(d,25),D=d&v^~d&S,H=L+q+D+se[i]+f[i]>>>0,Z=x(g,2)^x(g,13)^x(g,22),J=g&k^g&A^k&A,Q=Z+J>>>0;L=S,S=v,v=d,d=K+H>>>0,K=A,A=k,k=g,g=H+Q>>>0}u=u+g>>>0,c=c+k>>>0,m=m+A>>>0,l=l+K>>>0,p=p+d>>>0,b=b+v>>>0,y=y+S>>>0,P=P+L>>>0}return V(u)+V(c)}function G(e){return ae(e.join("|"))}var ie={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 $(e,r,t){let n=t===_?void 0:ie[t];if(!n)return e;let s=n.indexOf("generic"),o=a=>{let u=n.indexOf(a);return u===-1?s:u};return[...e].sort((a,u)=>{var l,p;let c=(l=r.get(a))!=null?l:"generic",m=(p=r.get(u))!=null?p:"generic";return o(c)-o(m)})}function z(e,r,t){let n=new Map;for(let s of[...B,t]){let o=$(e,r,s);n.set(G(o),o)}return n}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 s,o;do{let u=Math.max(1e-15,r()),c=r();s=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),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 C(e,r,t=Math.random){let n=j(e,t),s=j(r,t),o=n+s;return o<=0?e/(e+r):n/o}function W(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=C(t.alpha,t.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=C(o.alpha,o.beta,r);a>n&&(t=o,n=a)}return t.arm}function ve(e,r,t,n,s=Math.random){var m,l;let o=z(e,r,t),a=[];for(let p of o.keys()){let b=n.get(p);a.push({arm:p,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(l=b==null?void 0:b.beta)!=null?l:1})}let u=W(a,s),c=u?o.get(u):void 0;return c!=null?c:$(e,r,t)}function Y(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function F(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 we(e,r){return`${e}=${r}`}function ue(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 Y(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 Y(r)}function Re(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(([c])=>c).sort(),u=Object.keys(o).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,m]of n)if(!m.includes(o[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function _e(e,r){var t,n;return e.dims!=null?(n=(t=F(r))!=null?t:F(ue(e)))!=null?n:{}:r}var T=20;function M(e,r,t=20){let n=t/(t+e.exposures);return{alpha:e.alpha+n*r.alpha,beta:e.beta+n*r.beta}}var h="__all__",O={exposures:0,conversions:0};function E(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ee(e,r,t=20){var b,y,P,f;let n=(b=e.segment)!=null?b:O,s=(y=e.global)!=null?y:O,o=E(s),a=M(R(w({},E(n)),{exposures:n.exposures}),o,t);if(!r)return a;let u=(P=e.persona)!=null?P:O,c=(f=e.child)!=null?f:O,m=M(R(w({},E(u)),{exposures:u.exposures}),o,t),l=(n.exposures+1)/(n.exposures+u.exposures+2),p={alpha:l*a.alpha+(1-l)*m.alpha,beta:l*a.beta+(1-l)*m.beta};return M(R(w({},E(c)),{exposures:c.exposures}),p,t)}function Ne(e,r){return r==="unknown"||r===h||r===""?[{segment:e,persona:h},{segment:h,persona:h}]:[{segment:e,persona:r},{segment:e,persona:h},{segment:h,persona:r},{segment:h,persona:h}]}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 qe(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 De(e){return e>=.3?e<.7?"medium":"high":"low"}var le=20;function fe(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 He(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=C(o.alpha,o.beta,t)*fe(o,r);a>s&&(n=o,s=a)}return n.arm}export{ie as CLUSTER_PRIORITY,le as EV_SHRINK_K,oe as LEGACY_PERSONA_MAP,B as PERSONAS,be as PERSONA_DISPLAY,h as POOL_ALL,T as SHRINKAGE_M,_ as UNKNOWN_PERSONA,$ as applyClusterHeuristic,z as candidateLayouts,Y as canonicalArm,pe as canonicalPersona,ve as chooseLayout,De as confidenceBand,ce as fnv1a,G as hashLayout,we as marginalArmKey,F as parseArm,qe as pickDeterministicArm,Ee as pooledPosterior,E as posteriorOfCounts,W as sampleArm,He as sampleArmEv,C as sampleBeta,fe as shrunkAvgValue,M as shrunkPosterior,ue as slotBaselineArm,_e as slotResultFor,Re as validateSlotDecl,Ne as weightCellsFor};
1
+ var re=Object.defineProperty;var H=Object.getOwnPropertySymbols;var ne=Object.prototype.hasOwnProperty,te=Object.prototype.propertyIsEnumerable;var z=(e,r,n)=>r in e?re(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,V=(e,r)=>{for(var n in r||(r={}))ne.call(r,n)&&z(e,n,r[n]);if(H)for(var n of H(r))te.call(r,n)&&z(e,n,r[n]);return e};var _=["buyer","researcher","deal_seeker","browser"],y="unknown",B={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 q(e){var n;if(e==null)return y;let r=e.trim().toLowerCase();return(n=U[r])!=null?n:y}var oe=[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 g(e,r){return e>>>r|e<<32-r}function Y(e){return(e>>>0).toString(16).padStart(8,"0")}function se(e){let r=new TextEncoder().encode(e),n=r.length,t=n*8,s=(n+8>>6)+1<<6,o=new Uint8Array(s);o.set(r),o[n]=128;let a=new DataView(o.buffer);a.setUint32(s-8,Math.floor(t/4294967296),!1),a.setUint32(s-4,t>>>0,!1);let i=1779033703,u=3144134277,f=1013904242,c=2773480762,p=1359893119,m=2600822924,P=528734635,A=1541459225,b=new Uint32Array(64);for(let M=0;M<s;M+=64){for(let l=0;l<16;l++)b[l]=a.getUint32(M+l*4,!1);for(let l=16;l<64;l++){let D=g(b[l-15],7)^g(b[l-15],18)^b[l-15]>>>3,K=g(b[l-2],17)^g(b[l-2],19)^b[l-2]>>>10;b[l]=b[l-16]+D+b[l-7]+K>>>0}let x=i,k=u,R=f,N=c,h=p,S=m,v=P,O=A;for(let l=0;l<64;l++){let D=g(h,6)^g(h,11)^g(h,25),K=h&S^~h&v,j=O+D+K+oe[l]+b[l]>>>0,J=g(x,2)^g(x,13)^g(x,22),Q=x&k^x&R^k&R,ee=J+Q>>>0;O=v,v=S,S=h,h=N+j>>>0,N=R,R=k,k=x,x=j+ee>>>0}i=i+x>>>0,u=u+k>>>0,f=f+R>>>0,c=c+N>>>0,p=p+h>>>0,m=m+S>>>0,P=P+v>>>0,A=A+O>>>0}return Y(i)+Y(u)}function G(e){return se(e.join("|"))}var ae={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 I(e,r,n){let t=ae[n];if(!t)return e;let s=t.indexOf("generic"),o=a=>{let i=t.indexOf(a);return i===-1?s:i};return[...e].sort((a,i)=>{var c,p;let u=(c=r.get(a))!=null?c:"generic",f=(p=r.get(i))!=null?p:"generic";return o(u)-o(f)})}function F(e,r,n){let t=new Map;for(let s of[..._,n]){let o=I(e,r,s);t.set(G(o),o)}return t}function $(e,r){if(e<1)return $(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let n=e-1/3,t=1/Math.sqrt(9*n);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+t*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+n*(1-o+Math.log(o)))return n*o}}function C(e,r,n=Math.random){let t=$(e,n),s=$(r,n),o=t+s;return o<=0?e/(e+r):t/o}function W(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=e[0],t=C(n.alpha,n.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=C(o.alpha,o.beta,r);a>t&&(n=o,t=a)}return n.arm}function Se(e,r,n,t,s=Math.random){var f,c;let o=F(e,r,n),a=[];for(let p of o.keys()){let m=t.get(p);a.push({arm:p,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(c=m==null?void 0:m.beta)!=null?c:1})}let i=W(a,s),u=i?o.get(i):void 0;return u!=null?u:I(e,r,n)}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 n of e.split("|")){let t=n.indexOf("=");if(t<=0||t!==n.lastIndexOf("=")||t===n.length-1)return null;let s=n.slice(0,t);if(s in r)return null;r[s]=n.slice(t+1)}return r}function _e(e,r){return`${e}=${r}`}function ie(e){var n,t,s;if(e.arms)return typeof e.baseline=="string"?e.baseline:(n=e.arms[0])!=null?n:"";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[o,a]of Object.entries((t=e.dims)!=null?t:{}))r[o]=(s=a[0])!=null?s:"";return T(r)}function Ce(e){let r=Array.isArray(e.arms),n=e.dims!=null;if(r&&n)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!n)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 t=Object.entries(e.dims);if(t.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(t.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let s=1;for(let[o,a]of t){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=t.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,f]of t)if(!f.includes(o[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function we(e,r){var n,t;return e.dims!=null?(t=(n=Z(r))!=null?n:Z(ie(e)))!=null?t:{}:r}var X=20;function w(e,r,n=20){let t=r.alpha+r.beta;if(t<=0||n<=0)return{alpha:e.alpha,beta:e.beta};let s=r.alpha/t,o=n*t/(t+n);return{alpha:e.alpha+o*s,beta:e.beta+o*(1-s)}}var d="__all__",E={exposures:0,conversions:0};function L(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ne(e,r,n=20){var m,P,A,b;let t=(m=e.segment)!=null?m:E,s=(P=e.global)!=null?P:E,o=L(s),a=w(L(t),o,n);if(!r)return a;let i=(A=e.persona)!=null?A:E,u=(b=e.child)!=null?b:E,f=w(L(i),o,n),c=(t.exposures+1)/(t.exposures+i.exposures+2),p={alpha:c*a.alpha+(1-c)*f.alpha,beta:c*a.beta+(1-c)*f.beta};return w(L(u),p,n)}function Oe(e){var t,s;let r=null,n=-1;for(let o of e){let a=o.segment===d,i=o.persona===d,u=a&&i?3:a||i?2:1;u>n&&(n=u,r=o)}return{valueSum:(t=r==null?void 0:r.valueSum)!=null?t:0,valueCount:(s=r==null?void 0:r.valueCount)!=null?s:0}}function De(e,r){return r==="unknown"||r===d||r===""?[{segment:e,persona:d},{segment:d,persona:d}]:[{segment:e,persona:r},{segment:e,persona:d},{segment:d,persona:r},{segment:d,persona:d}]}function ue(e){let r=2166136261;for(let n=0;n<e.length;n++)r^=e.charCodeAt(n),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function Ve(e,r,n){if(n.length===0)throw new Error("pickDeterministicArm requires at least one arm");let t=[...n].sort();return t[ue(`${e}:${r}`)%t.length]}function Ue(e){return e>=.3?e<.7?"medium":"high":"low"}var le=20;function ce(e,r,n=le){let t=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return t;if(e.valueCount<=0)return r;let s=e.valueCount/(e.valueCount+n);return s*t+(1-s)*r}function $e(e,r,n=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=null,s=-1/0;for(let o of e){let a=C(o.alpha,o.beta,n)*ce(o,r);a>s&&(t=o,s=a)}return t.arm}var ze=/^[a-z0-9][a-z0-9_-]{0,31}$/,fe=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],me=_.map(e=>({key:e,displayName:B[e]})),be=64;function pe(e){var n;let r=new Map;for(let t of e)if(t.status!=="retired"){r.set(t.key,t.key);for(let s of(n=t.aliases)!=null?n:[])fe.includes(s)||r.set(s,t.key)}return r}function Be(e,r=me){var i,u,f;let n=pe(r),t=(i=e.inferredConfidence)!=null?i:0,s,o=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(o!==""){let c=n.get(o),p=c===void 0?n.get(q(o)):void 0,m=c!=null?c:p;if(m!==void 0)return{persona:m,source:"declared",confidence:1};s=o.slice(0,be)}let a=q(e.clusterLabel);return a!==y&&n.has(a)?V({persona:n.get(a),source:"inferred",confidence:t},s!==void 0&&{unrecognizedDeclared:s}):V({persona:y,source:"none",confidence:t},s!==void 0&&{unrecognizedDeclared:s})}function Ye(e){var n;if(e==null)return y;let r=e.trim().toLowerCase();return r===""?y:(n=U[r])!=null?n:r}export{ae as CLUSTER_PRIORITY,me as DEFAULT_PERSONA_VOCABULARY,le as EV_SHRINK_K,U as LEGACY_PERSONA_MAP,_ as PERSONAS,B as PERSONA_DISPLAY,ze as PERSONA_KEY_RE,d as POOL_ALL,fe as RESERVED_PERSONA_KEYS,X as SHRINKAGE_M,y as UNKNOWN_PERSONA,I as applyClusterHeuristic,Oe as broadestValueCell,F as candidateLayouts,T as canonicalArm,q as canonicalPersona,Se as chooseLayout,Ue as confidenceBand,Ye as decisionPersona,ue as fnv1a,G as hashLayout,_e as marginalArmKey,Z as parseArm,Ve as pickDeterministicArm,Ne as pooledPosterior,L as posteriorOfCounts,Be as resolvePersona,W as sampleArm,$e as sampleArmEv,C as sampleBeta,ce as shrunkAvgValue,w as shrunkPosterior,ie as slotBaselineArm,we as slotResultFor,Ce as validateSlotDecl,De as weightCellsFor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/policy",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Pure decision-policy functions shared by the SentientUI API and the keyless local engine",
6
6
  "license": "MIT",