@sentientui/policy 0.2.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 +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +34 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
|
|
2
|
+
type Persona = (typeof PERSONAS)[number];
|
|
3
|
+
declare const UNKNOWN_PERSONA: "unknown";
|
|
4
|
+
type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
|
|
5
|
+
/** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
|
|
6
|
+
declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Every label ever written for a persona, mapped to its canonical form. The
|
|
9
|
+
* plural/hyphen labels are the pre-069 cluster seed labels
|
|
10
|
+
* ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
|
|
11
|
+
*/
|
|
12
|
+
declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
|
|
13
|
+
/**
|
|
14
|
+
* Canonicalizes any persona/cluster label to the pinned vocabulary.
|
|
15
|
+
* Null, undefined, empty, and unrecognized labels all become 'unknown' —
|
|
16
|
+
* "we don't know" is always a safe answer; a guessed persona is not.
|
|
17
|
+
*/
|
|
18
|
+
declare function canonicalPersona(label: string | null | undefined): PersonaKey;
|
|
19
|
+
|
|
20
|
+
declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Reorders section IDs based on the persona's semantic priority.
|
|
23
|
+
* Sections with no graph entry are treated as 'generic'.
|
|
24
|
+
* Returns the input unchanged for the unknown persona.
|
|
25
|
+
*/
|
|
26
|
+
declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The candidate layout orderings for a page — the distinct section orders
|
|
29
|
+
* produced by every persona's semantic priority (plus the requesting
|
|
30
|
+
* persona's own, which for 'unknown' is the identity order). These are the
|
|
31
|
+
* "arms" the layout bandit explores. Returned as hash → order so it joins
|
|
32
|
+
* directly against layout_weights rows keyed by the same hashLayout.
|
|
33
|
+
*/
|
|
34
|
+
declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Stable 16-char SHA-256 prefix for a section order array.
|
|
38
|
+
* MUST stay byte-identical to the legacy implementation in
|
|
39
|
+
* apps/api (node:crypto createHash('sha256')) — every
|
|
40
|
+
* layout_weights.layout_hash row in production was written by it
|
|
41
|
+
* (parity-pinned in hash.test.ts).
|
|
42
|
+
*
|
|
43
|
+
* Implemented as pure-JS FIPS 180-4 SHA-256 (no node:crypto) so this package
|
|
44
|
+
* is fully isomorphic: the keyless local engine and browser bundles import it
|
|
45
|
+
* without a Node builtin. Only the first 16 hex chars (h0+h1) are returned.
|
|
46
|
+
*/
|
|
47
|
+
declare function hashLayout(order: string[]): string;
|
|
48
|
+
|
|
49
|
+
/** Learned Beta(alpha,beta) posterior for one candidate layout, keyed by hash. */
|
|
50
|
+
type LearnedLayout = {
|
|
51
|
+
layoutHash: string;
|
|
52
|
+
alpha: number;
|
|
53
|
+
beta: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Thompson-samples the layout order to serve a persona over the candidate
|
|
57
|
+
* orderings, using learned posteriors from layout_weights. Candidates with no
|
|
58
|
+
* learned row use the uniform 1/1 prior — identical to variant cold start.
|
|
59
|
+
* Falls back to the persona's heuristic only if sampling yields no candidate.
|
|
60
|
+
*/
|
|
61
|
+
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
|
|
62
|
+
|
|
63
|
+
/** Learned Beta(alpha, beta) posterior for one arm. */
|
|
64
|
+
type ArmPosterior = {
|
|
65
|
+
arm: string;
|
|
66
|
+
alpha: number;
|
|
67
|
+
beta: number;
|
|
68
|
+
};
|
|
69
|
+
/** One draw from Beta(alpha, beta). Moved verbatim from apps/api/src/domain/bandit.ts. */
|
|
70
|
+
declare function sampleBeta(alpha: number, beta: number, rand?: () => number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Thompson Sampling selection: samples Beta(alpha, beta) per arm and returns
|
|
73
|
+
* the argmax arm id, or null when no arms are given. Uncertain arms get
|
|
74
|
+
* explored; confident winners get exploited — same semantics as the legacy
|
|
75
|
+
* chooseVariant, generalized to arbitrary arm strings.
|
|
76
|
+
*/
|
|
77
|
+
declare function sampleArm(arms: ArmPosterior[], rand?: () => number): string | null;
|
|
78
|
+
|
|
79
|
+
/** A slot declaration as sent on /v1/decide — exactly one of arms|dims. */
|
|
80
|
+
type SlotDecl = {
|
|
81
|
+
id: string;
|
|
82
|
+
arms?: string[];
|
|
83
|
+
dims?: Record<string, string[]>;
|
|
84
|
+
baseline?: string | Record<string, string>;
|
|
85
|
+
};
|
|
86
|
+
/** What the client receives for a slot: arm id, or per-dim values for dims slots. */
|
|
87
|
+
type SlotResult = string | Record<string, string>;
|
|
88
|
+
/** Canonical arm string for a dims combination: sorted `dim=value` joined with '|'. */
|
|
89
|
+
declare function canonicalArm(values: Record<string, string>): string;
|
|
90
|
+
/**
|
|
91
|
+
* Inverse of canonicalArm. Returns null when the string is not a
|
|
92
|
+
* dims-encoded arm (enumerated arm ids, empty, malformed, duplicate dims) —
|
|
93
|
+
* callers use `parseArm(arm) !== null` to distinguish dims arms from
|
|
94
|
+
* enumerated arms.
|
|
95
|
+
*/
|
|
96
|
+
declare function parseArm(arm: string): Record<string, string> | null;
|
|
97
|
+
/** Storage key for one marginal posterior row of a dims slot. */
|
|
98
|
+
declare function marginalArmKey(dim: string, value: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Canonical arm string of the declared baseline, or the default baseline
|
|
101
|
+
* (first arm / first value per dim) when none is declared. Assumes the decl
|
|
102
|
+
* passed validateSlotDecl.
|
|
103
|
+
*/
|
|
104
|
+
declare function slotBaselineArm(decl: SlotDecl): string;
|
|
105
|
+
/**
|
|
106
|
+
* Validates a slot declaration against the pinned rules: exactly one of
|
|
107
|
+
* arms|dims; arms 2..12 (unique); dims 1..4 dims of 2..6 unique values each
|
|
108
|
+
* with product ≤ 64; a declared baseline must live in the declared space.
|
|
109
|
+
*/
|
|
110
|
+
declare function validateSlotDecl(decl: SlotDecl): {
|
|
111
|
+
ok: true;
|
|
112
|
+
} | {
|
|
113
|
+
ok: false;
|
|
114
|
+
reason: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Decodes a stored/served arm for the client: dims slot → parsed per-dim
|
|
118
|
+
* record (falling back to the baseline record if the arm fails to parse);
|
|
119
|
+
* arms slot → the arm id verbatim.
|
|
120
|
+
*/
|
|
121
|
+
declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
|
|
122
|
+
|
|
123
|
+
/** Empirical-Bayes pooling strength: w = m / (m + exposures). */
|
|
124
|
+
declare const SHRINKAGE_M = 20;
|
|
125
|
+
/**
|
|
126
|
+
* Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
|
|
127
|
+
* (pooled posterior dominates at 0 exposures) and detach as their own data
|
|
128
|
+
* accumulates. Formula pinned in CONTRACTS.md:
|
|
129
|
+
* w = m / (m + persona.exposures)
|
|
130
|
+
* alpha' = persona.alpha + w * pooled.alpha
|
|
131
|
+
* beta' = persona.beta + w * pooled.beta
|
|
132
|
+
*/
|
|
133
|
+
declare function shrunkPosterior(persona: {
|
|
134
|
+
alpha: number;
|
|
135
|
+
beta: number;
|
|
136
|
+
exposures: number;
|
|
137
|
+
}, pooled: {
|
|
138
|
+
alpha: number;
|
|
139
|
+
beta: number;
|
|
140
|
+
}, m?: number): {
|
|
141
|
+
alpha: number;
|
|
142
|
+
beta: number;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* FNV-1a 32-bit hash. Same algorithm (offset basis, prime-by-shifts, >>> 0)
|
|
147
|
+
* as the private hashUnit in apps/api/src/domain/holdout.ts — parity is
|
|
148
|
+
* pinned by fixture in deterministic.test.ts and cross-checked against
|
|
149
|
+
* assignHoldout in apps/api.
|
|
150
|
+
*/
|
|
151
|
+
declare function fnv1a(input: string): number;
|
|
152
|
+
/**
|
|
153
|
+
* Deterministic arm pick for the keyless local engine:
|
|
154
|
+
* fnv1a(`${sessionId}:${slotId}`) % arms.length over the SORTED arms, so the
|
|
155
|
+
* same session sees the same decision across reloads and tabs regardless of
|
|
156
|
+
* declaration order.
|
|
157
|
+
*/
|
|
158
|
+
declare function pickDeterministicArm(sessionId: string, slotId: string, arms: string[]): string;
|
|
159
|
+
/**
|
|
160
|
+
* Buckets a raw confidence float for CSS-facing use.
|
|
161
|
+
* Pinned: c < 0.3 → low; c < 0.7 → medium; else high.
|
|
162
|
+
* (Written NaN-safe: a non-comparable confidence must read as low.)
|
|
163
|
+
*/
|
|
164
|
+
declare function confidenceBand(c: number): 'low' | 'medium' | 'high';
|
|
165
|
+
|
|
166
|
+
export { type ArmPosterior, CLUSTER_PRIORITY, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, type Persona, type PersonaKey, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, sampleArm, sampleBeta, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
|
|
2
|
+
type Persona = (typeof PERSONAS)[number];
|
|
3
|
+
declare const UNKNOWN_PERSONA: "unknown";
|
|
4
|
+
type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
|
|
5
|
+
/** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
|
|
6
|
+
declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Every label ever written for a persona, mapped to its canonical form. The
|
|
9
|
+
* plural/hyphen labels are the pre-069 cluster seed labels
|
|
10
|
+
* ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
|
|
11
|
+
*/
|
|
12
|
+
declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
|
|
13
|
+
/**
|
|
14
|
+
* Canonicalizes any persona/cluster label to the pinned vocabulary.
|
|
15
|
+
* Null, undefined, empty, and unrecognized labels all become 'unknown' —
|
|
16
|
+
* "we don't know" is always a safe answer; a guessed persona is not.
|
|
17
|
+
*/
|
|
18
|
+
declare function canonicalPersona(label: string | null | undefined): PersonaKey;
|
|
19
|
+
|
|
20
|
+
declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Reorders section IDs based on the persona's semantic priority.
|
|
23
|
+
* Sections with no graph entry are treated as 'generic'.
|
|
24
|
+
* Returns the input unchanged for the unknown persona.
|
|
25
|
+
*/
|
|
26
|
+
declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
|
|
27
|
+
/**
|
|
28
|
+
* The candidate layout orderings for a page — the distinct section orders
|
|
29
|
+
* produced by every persona's semantic priority (plus the requesting
|
|
30
|
+
* persona's own, which for 'unknown' is the identity order). These are the
|
|
31
|
+
* "arms" the layout bandit explores. Returned as hash → order so it joins
|
|
32
|
+
* directly against layout_weights rows keyed by the same hashLayout.
|
|
33
|
+
*/
|
|
34
|
+
declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Stable 16-char SHA-256 prefix for a section order array.
|
|
38
|
+
* MUST stay byte-identical to the legacy implementation in
|
|
39
|
+
* apps/api (node:crypto createHash('sha256')) — every
|
|
40
|
+
* layout_weights.layout_hash row in production was written by it
|
|
41
|
+
* (parity-pinned in hash.test.ts).
|
|
42
|
+
*
|
|
43
|
+
* Implemented as pure-JS FIPS 180-4 SHA-256 (no node:crypto) so this package
|
|
44
|
+
* is fully isomorphic: the keyless local engine and browser bundles import it
|
|
45
|
+
* without a Node builtin. Only the first 16 hex chars (h0+h1) are returned.
|
|
46
|
+
*/
|
|
47
|
+
declare function hashLayout(order: string[]): string;
|
|
48
|
+
|
|
49
|
+
/** Learned Beta(alpha,beta) posterior for one candidate layout, keyed by hash. */
|
|
50
|
+
type LearnedLayout = {
|
|
51
|
+
layoutHash: string;
|
|
52
|
+
alpha: number;
|
|
53
|
+
beta: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Thompson-samples the layout order to serve a persona over the candidate
|
|
57
|
+
* orderings, using learned posteriors from layout_weights. Candidates with no
|
|
58
|
+
* learned row use the uniform 1/1 prior — identical to variant cold start.
|
|
59
|
+
* Falls back to the persona's heuristic only if sampling yields no candidate.
|
|
60
|
+
*/
|
|
61
|
+
declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
|
|
62
|
+
|
|
63
|
+
/** Learned Beta(alpha, beta) posterior for one arm. */
|
|
64
|
+
type ArmPosterior = {
|
|
65
|
+
arm: string;
|
|
66
|
+
alpha: number;
|
|
67
|
+
beta: number;
|
|
68
|
+
};
|
|
69
|
+
/** One draw from Beta(alpha, beta). Moved verbatim from apps/api/src/domain/bandit.ts. */
|
|
70
|
+
declare function sampleBeta(alpha: number, beta: number, rand?: () => number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Thompson Sampling selection: samples Beta(alpha, beta) per arm and returns
|
|
73
|
+
* the argmax arm id, or null when no arms are given. Uncertain arms get
|
|
74
|
+
* explored; confident winners get exploited — same semantics as the legacy
|
|
75
|
+
* chooseVariant, generalized to arbitrary arm strings.
|
|
76
|
+
*/
|
|
77
|
+
declare function sampleArm(arms: ArmPosterior[], rand?: () => number): string | null;
|
|
78
|
+
|
|
79
|
+
/** A slot declaration as sent on /v1/decide — exactly one of arms|dims. */
|
|
80
|
+
type SlotDecl = {
|
|
81
|
+
id: string;
|
|
82
|
+
arms?: string[];
|
|
83
|
+
dims?: Record<string, string[]>;
|
|
84
|
+
baseline?: string | Record<string, string>;
|
|
85
|
+
};
|
|
86
|
+
/** What the client receives for a slot: arm id, or per-dim values for dims slots. */
|
|
87
|
+
type SlotResult = string | Record<string, string>;
|
|
88
|
+
/** Canonical arm string for a dims combination: sorted `dim=value` joined with '|'. */
|
|
89
|
+
declare function canonicalArm(values: Record<string, string>): string;
|
|
90
|
+
/**
|
|
91
|
+
* Inverse of canonicalArm. Returns null when the string is not a
|
|
92
|
+
* dims-encoded arm (enumerated arm ids, empty, malformed, duplicate dims) —
|
|
93
|
+
* callers use `parseArm(arm) !== null` to distinguish dims arms from
|
|
94
|
+
* enumerated arms.
|
|
95
|
+
*/
|
|
96
|
+
declare function parseArm(arm: string): Record<string, string> | null;
|
|
97
|
+
/** Storage key for one marginal posterior row of a dims slot. */
|
|
98
|
+
declare function marginalArmKey(dim: string, value: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Canonical arm string of the declared baseline, or the default baseline
|
|
101
|
+
* (first arm / first value per dim) when none is declared. Assumes the decl
|
|
102
|
+
* passed validateSlotDecl.
|
|
103
|
+
*/
|
|
104
|
+
declare function slotBaselineArm(decl: SlotDecl): string;
|
|
105
|
+
/**
|
|
106
|
+
* Validates a slot declaration against the pinned rules: exactly one of
|
|
107
|
+
* arms|dims; arms 2..12 (unique); dims 1..4 dims of 2..6 unique values each
|
|
108
|
+
* with product ≤ 64; a declared baseline must live in the declared space.
|
|
109
|
+
*/
|
|
110
|
+
declare function validateSlotDecl(decl: SlotDecl): {
|
|
111
|
+
ok: true;
|
|
112
|
+
} | {
|
|
113
|
+
ok: false;
|
|
114
|
+
reason: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Decodes a stored/served arm for the client: dims slot → parsed per-dim
|
|
118
|
+
* record (falling back to the baseline record if the arm fails to parse);
|
|
119
|
+
* arms slot → the arm id verbatim.
|
|
120
|
+
*/
|
|
121
|
+
declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
|
|
122
|
+
|
|
123
|
+
/** Empirical-Bayes pooling strength: w = m / (m + exposures). */
|
|
124
|
+
declare const SHRINKAGE_M = 20;
|
|
125
|
+
/**
|
|
126
|
+
* Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
|
|
127
|
+
* (pooled posterior dominates at 0 exposures) and detach as their own data
|
|
128
|
+
* accumulates. Formula pinned in CONTRACTS.md:
|
|
129
|
+
* w = m / (m + persona.exposures)
|
|
130
|
+
* alpha' = persona.alpha + w * pooled.alpha
|
|
131
|
+
* beta' = persona.beta + w * pooled.beta
|
|
132
|
+
*/
|
|
133
|
+
declare function shrunkPosterior(persona: {
|
|
134
|
+
alpha: number;
|
|
135
|
+
beta: number;
|
|
136
|
+
exposures: number;
|
|
137
|
+
}, pooled: {
|
|
138
|
+
alpha: number;
|
|
139
|
+
beta: number;
|
|
140
|
+
}, m?: number): {
|
|
141
|
+
alpha: number;
|
|
142
|
+
beta: number;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* FNV-1a 32-bit hash. Same algorithm (offset basis, prime-by-shifts, >>> 0)
|
|
147
|
+
* as the private hashUnit in apps/api/src/domain/holdout.ts — parity is
|
|
148
|
+
* pinned by fixture in deterministic.test.ts and cross-checked against
|
|
149
|
+
* assignHoldout in apps/api.
|
|
150
|
+
*/
|
|
151
|
+
declare function fnv1a(input: string): number;
|
|
152
|
+
/**
|
|
153
|
+
* Deterministic arm pick for the keyless local engine:
|
|
154
|
+
* fnv1a(`${sessionId}:${slotId}`) % arms.length over the SORTED arms, so the
|
|
155
|
+
* same session sees the same decision across reloads and tabs regardless of
|
|
156
|
+
* declaration order.
|
|
157
|
+
*/
|
|
158
|
+
declare function pickDeterministicArm(sessionId: string, slotId: string, arms: string[]): string;
|
|
159
|
+
/**
|
|
160
|
+
* Buckets a raw confidence float for CSS-facing use.
|
|
161
|
+
* Pinned: c < 0.3 → low; c < 0.7 → medium; else high.
|
|
162
|
+
* (Written NaN-safe: a non-comparable confidence must read as low.)
|
|
163
|
+
*/
|
|
164
|
+
declare function confidenceBand(c: number): 'low' | 'medium' | 'high';
|
|
165
|
+
|
|
166
|
+
export { type ArmPosterior, CLUSTER_PRIORITY, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, type Persona, type PersonaKey, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, sampleArm, sampleBeta, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var v=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var X=(e,r)=>{for(var t in r)v(e,t,{get:r[t],enumerable:!0})},Z=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of J(r))!Q.call(e,o)&&o!==t&&v(e,o,{get:()=>r[o],enumerable:!(n=V(r,o))||n.enumerable});return e};var ee=e=>Z(v({},"__esModule",{value:!0}),e);var me={};X(me,{CLUSTER_PRIORITY:()=>G,LEGACY_PERSONA_MAP:()=>B,PERSONAS:()=>L,PERSONA_DISPLAY:()=>re,SHRINKAGE_M:()=>we,UNKNOWN_PERSONA:()=>k,applyClusterHeuristic:()=>S,candidateLayouts:()=>q,canonicalArm:()=>U,canonicalPersona:()=>te,chooseLayout:()=>oe,confidenceBand:()=>le,fnv1a:()=>W,hashLayout:()=>E,marginalArmKey:()=>ae,parseArm:()=>H,pickDeterministicArm:()=>fe,sampleArm:()=>j,sampleBeta:()=>$,shrunkPosterior:()=>ue,slotBaselineArm:()=>z,slotResultFor:()=>ce,validateSlotDecl:()=>ie});module.exports=ee(me);var L=["buyer","researcher","deal_seeker","browser"],k="unknown",re={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},B={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function te(e){var t;if(e==null)return k;let r=e.trim().toLowerCase();return(t=B[r])!=null?t:k}var ne=[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 b(e,r){return e>>>r|e<<32-r}function C(e){return(e>>>0).toString(16).padStart(8,"0")}function se(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 u=1779033703,c=3144134277,f=1013904242,p=2773480762,d=1359893119,m=2600822924,w=528734635,R=1541459225,l=new Uint32Array(64);for(let M=0;M<o;M+=64){for(let i=0;i<16;i++)l[i]=a.getUint32(M+i*4,!1);for(let i=16;i<64;i++){let _=b(l[i-15],7)^b(l[i-15],18)^l[i-15]>>>3,K=b(l[i-2],17)^b(l[i-2],19)^l[i-2]>>>10;l[i]=l[i-16]+_+l[i-7]+K>>>0}let x=u,h=c,y=f,O=p,g=d,A=m,P=w,N=R;for(let i=0;i<64;i++){let _=b(g,6)^b(g,11)^b(g,25),K=g&A^~g&P,I=N+_+K+ne[i]+l[i]>>>0,Y=b(x,2)^b(x,13)^b(x,22),F=x&h^x&y^h&y,T=Y+F>>>0;N=P,P=A,A=g,g=O+I>>>0,O=y,y=h,h=x,x=I+T>>>0}u=u+x>>>0,c=c+h>>>0,f=f+y>>>0,p=p+O>>>0,d=d+g>>>0,m=m+A>>>0,w=w+P>>>0,R=R+N>>>0}return C(u)+C(c)}function E(e){return se(e.join("|"))}var G={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 S(e,r,t){let n=t===k?void 0:G[t];return n?[...e].sort((o,s)=>{var c,f;let a=(c=r.get(o))!=null?c:"generic",u=(f=r.get(s))!=null?f:"generic";return n.indexOf(a)-n.indexOf(u)}):e}function q(e,r,t){let n=new Map;for(let o of[...L,t]){let s=S(e,r,o);n.set(E(s),s)}return n}function D(e,r){if(e<1)return D(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 u=Math.max(1e-15,r()),c=r();o=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),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 $(e,r,t=Math.random){let n=D(e,t),o=D(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=$(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=$(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function oe(e,r,t,n,o=Math.random){var f,p;let s=q(e,r,t),a=[];for(let d of s.keys()){let m=n.get(d);a.push({arm:d,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(p=m==null?void 0:m.beta)!=null?p:1})}let u=j(a,o),c=u?s.get(u):void 0;return c!=null?c:S(e,r,t)}function U(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function H(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 ae(e,r){return`${e}=${r}`}function z(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 U(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 U(r)}function ie(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(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(([c])=>c).sort(),u=Object.keys(s).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,f]of n)if(!f.includes(s[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function ce(e,r){var t,n;return e.dims!=null?(n=(t=H(r))!=null?t:H(z(e)))!=null?n:{}:r}var we=20;function ue(e,r,t=20){let n=t/(t+e.exposures);return{alpha:e.alpha+n*r.alpha,beta:e.beta+n*r.beta}}function W(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 fe(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[W(`${e}:${r}`)%n.length]}function le(e){return e>=.3?e<.7?"medium":"high":"low"}0&&(module.exports={CLUSTER_PRIORITY,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,SHRINKAGE_M,UNKNOWN_PERSONA,applyClusterHeuristic,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,confidenceBand,fnv1a,hashLayout,marginalArmKey,parseArm,pickDeterministicArm,sampleArm,sampleBeta,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var E=["buyer","researcher","deal_seeker","browser"],P="unknown",J={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 Q(e){var t;if(e==null)return P;let r=e.trim().toLowerCase();return(t=z[r])!=null?t:P}var W=[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 b(e,r){return e>>>r|e<<32-r}function q(e){return(e>>>0).toString(16).padStart(8,"0")}function Y(e){let r=new TextEncoder().encode(e),t=r.length,s=t*8,o=(t+8>>6)+1<<6,n=new Uint8Array(o);n.set(r),n[t]=128;let a=new DataView(n.buffer);a.setUint32(o-8,Math.floor(s/4294967296),!1),a.setUint32(o-4,s>>>0,!1);let u=1779033703,c=3144134277,f=1013904242,p=2773480762,d=1359893119,m=2600822924,S=528734635,w=1541459225,l=new Uint32Array(64);for(let R=0;R<o;R+=64){for(let i=0;i<16;i++)l[i]=a.getUint32(R+i*4,!1);for(let i=16;i<64;i++){let N=b(l[i-15],7)^b(l[i-15],18)^l[i-15]>>>3,_=b(l[i-2],17)^b(l[i-2],19)^l[i-2]>>>10;l[i]=l[i-16]+N+l[i-7]+_>>>0}let x=u,h=c,y=f,M=p,g=d,k=m,A=S,O=w;for(let i=0;i<64;i++){let N=b(g,6)^b(g,11)^b(g,25),_=g&k^~g&A,L=O+N+_+W[i]+l[i]>>>0,B=b(x,2)^b(x,13)^b(x,22),C=x&h^x&y^h&y,G=B+C>>>0;O=A,A=k,k=g,g=M+L>>>0,M=y,y=h,h=x,x=L+G>>>0}u=u+x>>>0,c=c+h>>>0,f=f+y>>>0,p=p+M>>>0,d=d+g>>>0,m=m+k>>>0,S=S+A>>>0,w=w+O>>>0}return q(u)+q(c)}function D(e){return Y(e.join("|"))}var F={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 K(e,r,t){let s=t===P?void 0:F[t];return s?[...e].sort((o,n)=>{var c,f;let a=(c=r.get(o))!=null?c:"generic",u=(f=r.get(n))!=null?f:"generic";return s.indexOf(a)-s.indexOf(u)}):e}function $(e,r,t){let s=new Map;for(let o of[...E,t]){let n=K(e,r,o);s.set(D(n),n)}return s}function v(e,r){if(e<1)return v(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,s=1/Math.sqrt(9*t);for(;;){let o,n;do{let u=Math.max(1e-15,r()),c=r();o=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),n=1+s*o}while(n<=0);n=n*n*n;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-n+Math.log(n)))return t*n}}function j(e,r,t=Math.random){let s=v(e,t),o=v(r,t),n=s+o;return n<=0?e/(e+r):s/n}function U(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],s=j(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let n=e[o],a=j(n.alpha,n.beta,r);a>s&&(t=n,s=a)}return t.arm}function ae(e,r,t,s,o=Math.random){var f,p;let n=$(e,r,t),a=[];for(let d of n.keys()){let m=s.get(d);a.push({arm:d,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(p=m==null?void 0:m.beta)!=null?p:1})}let u=U(a,o),c=u?n.get(u):void 0;return c!=null?c:K(e,r,t)}function H(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function I(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let s=t.indexOf("=");if(s<=0||s!==t.lastIndexOf("=")||s===t.length-1)return null;let o=t.slice(0,s);if(o in r)return null;r[o]=t.slice(s+1)}return r}function ce(e,r){return`${e}=${r}`}function T(e){var t,s,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 H(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[n,a]of Object.entries((s=e.dims)!=null?s:{}))r[n]=(o=a[0])!=null?o:"";return H(r)}function ue(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 n=e.arms;if(n.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(n.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(n).size!==n.length)return{ok:!1,reason:"arms must be unique"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!n.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let s=Object.entries(e.dims);if(s.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(s.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[n,a]of s){if(a.length<2)return{ok:!1,reason:`dim "${n}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${n}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${n}" 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 n=e.baseline,a=s.map(([c])=>c).sort(),u=Object.keys(n).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,f]of s)if(!f.includes(n[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function fe(e,r){var t,s;return e.dims!=null?(s=(t=I(r))!=null?t:I(T(e)))!=null?s:{}:r}var me=20;function be(e,r,t=20){let s=t/(t+e.exposures);return{alpha:e.alpha+s*r.alpha,beta:e.beta+s*r.beta}}function V(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 ge(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let s=[...t].sort();return s[V(`${e}:${r}`)%s.length]}function pe(e){return e>=.3?e<.7?"medium":"high":"low"}export{F as CLUSTER_PRIORITY,z as LEGACY_PERSONA_MAP,E as PERSONAS,J as PERSONA_DISPLAY,me as SHRINKAGE_M,P as UNKNOWN_PERSONA,K as applyClusterHeuristic,$ as candidateLayouts,H as canonicalArm,Q as canonicalPersona,ae as chooseLayout,pe as confidenceBand,V as fnv1a,D as hashLayout,ce as marginalArmKey,I as parseArm,ge as pickDeterministicArm,U as sampleArm,j as sampleBeta,be as shrunkPosterior,T as slotBaselineArm,fe as slotResultFor,ue as validateSlotDecl};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sentientui/policy",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Pure decision-policy functions shared by the SentientUI API and the keyless local engine",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.mjs",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.10.2",
|
|
24
|
+
"tsup": "^8.3.5",
|
|
25
|
+
"typescript": "^5.7.2",
|
|
26
|
+
"vitest": "^2.1.8"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "eslint src"
|
|
33
|
+
}
|
|
34
|
+
}
|