@rudra-js/core 0.5.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/README.md +51 -4
- package/dist/claim-screening.d.ts +33 -0
- package/dist/claim-screening.d.ts.map +1 -0
- package/dist/claim-screening.js +226 -0
- package/dist/claim-screening.js.map +1 -0
- package/dist/component-generator.d.ts.map +1 -1
- package/dist/component-generator.js +32 -98
- package/dist/component-generator.js.map +1 -1
- package/dist/component-spec.d.ts +2 -49
- package/dist/component-spec.d.ts.map +1 -1
- package/dist/component-spec.js +3 -17
- package/dist/component-spec.js.map +1 -1
- package/dist/fallback-component.d.ts +3 -0
- package/dist/fallback-component.d.ts.map +1 -1
- package/dist/fallback-component.js +3 -13
- package/dist/fallback-component.js.map +1 -1
- package/dist/fit-to-shopper.d.ts +5 -1
- package/dist/fit-to-shopper.d.ts.map +1 -1
- package/dist/fit-to-shopper.js +4 -3
- package/dist/fit-to-shopper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model-prompt.d.ts +1 -1
- package/dist/model-prompt.d.ts.map +1 -1
- package/dist/model-prompt.js +38 -18
- package/dist/model-prompt.js.map +1 -1
- package/dist/product-selection.d.ts +0 -6
- package/dist/product-selection.d.ts.map +1 -1
- package/dist/product-selection.js +1 -3
- package/dist/product-selection.js.map +1 -1
- package/dist/provider.d.ts +1 -8
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +1 -8
- package/dist/provider.js.map +1 -1
- package/dist/reconciliation.d.ts +10 -22
- package/dist/reconciliation.d.ts.map +1 -1
- package/dist/reconciliation.js +116 -399
- package/dist/reconciliation.js.map +1 -1
- package/dist/signal-digest.d.ts +10 -0
- package/dist/signal-digest.d.ts.map +1 -1
- package/dist/signal-digest.js +38 -28
- package/dist/signal-digest.js.map +1 -1
- package/dist/spec-cache.d.ts +14 -2
- package/dist/spec-cache.d.ts.map +1 -1
- package/dist/spec-cache.js +14 -24
- package/dist/spec-cache.js.map +1 -1
- package/dist/tracking-input.d.ts.map +1 -1
- package/dist/tracking-input.js +15 -13
- package/dist/tracking-input.js.map +1 -1
- package/package.json +4 -4
- package/src/claim-screening.ts +242 -0
- package/src/component-generator.ts +37 -123
- package/src/component-spec.ts +5 -18
- package/src/fallback-component.ts +3 -14
- package/src/fit-to-shopper.ts +9 -4
- package/src/index.ts +1 -0
- package/src/model-prompt.ts +42 -20
- package/src/product-selection.ts +1 -9
- package/src/provider.ts +1 -8
- package/src/reconciliation.ts +135 -430
- package/src/signal-digest.ts +41 -31
- package/src/spec-cache.ts +27 -24
- package/src/tracking-input.ts +16 -13
package/src/index.ts
CHANGED
package/src/model-prompt.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BANNER_TONES, EMPHASIS, RECOMMENDATION_BASES, TONES } from './component-spec.js';
|
|
2
2
|
import type { SignalDigest } from './signal-digest.js';
|
|
3
|
-
import type
|
|
3
|
+
import { FIELD_LIMITS, type Product, type TrackingInput } from './tracking-input.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* What actually reaches the model.
|
|
@@ -140,34 +140,57 @@ on.`;
|
|
|
140
140
|
* text exactly the same way, and it missed U+0085, U+061C and U+00AD as well.
|
|
141
141
|
* Properties cover the ones nobody has thought of yet.
|
|
142
142
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* caught by Cn, and is invisible.
|
|
143
|
+
* The general categories alone do not, though. Half of what renders as nothing
|
|
144
|
+
* is a mark or a letter rather than a format character \u2014 the Mongolian free
|
|
145
|
+
* variation selectors, U+034F, the Hangul fillers, and the sixteen variation
|
|
146
|
+
* selectors at U+FE00 \u2014 so `Default_Ignorable_Code_Point` is in the class too.
|
|
147
|
+
* It is the property that means "takes up no room", which is the thing that
|
|
148
|
+
* makes a character able to carry text nobody sees.
|
|
150
149
|
*
|
|
151
|
-
* The zero-width joiner is
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
150
|
+
* Three are left out. The zero-width joiner is a format character, but it is
|
|
151
|
+
* also how a family emoji is spelled, and U+FE0E and U+FE0F are what make a
|
|
152
|
+
* character render as an emoji rather than as text. Escaping those mangles
|
|
153
|
+
* ordinary product titles. The other thirteen selectors at U+FE00 spell
|
|
154
|
+
* nothing and are escaped like the rest.
|
|
155
155
|
*/
|
|
156
|
-
const UNPRINTABLE =
|
|
156
|
+
const UNPRINTABLE =
|
|
157
|
+
/(?!\u{200D}|\u{FE0E}|\u{FE0F})[\p{Cc}\p{Cf}\p{Cn}\p{Co}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/gu;
|
|
157
158
|
|
|
158
159
|
/**
|
|
159
|
-
*
|
|
160
|
+
* How long one quoted value may be once escaped.
|
|
160
161
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* one
|
|
162
|
+
* The contract caps the string a host may send, and this caps what that string
|
|
163
|
+
* turns into. They are not the same number: an escape writes up to eight
|
|
164
|
+
* characters for one, so a field on its cap can still buy eight times the
|
|
165
|
+
* prompt \u2014 and the bill \u2014 that the cap implies. Ordinary text escapes nothing
|
|
166
|
+
* and never comes near this.
|
|
164
167
|
*/
|
|
165
|
-
const
|
|
166
|
-
|
|
168
|
+
const MAX_QUOTED = FIELD_LIMITS.shortText * 2;
|
|
169
|
+
|
|
170
|
+
const escapeUnprintable = (text: string) =>
|
|
171
|
+
text.replace(UNPRINTABLE, (character) => {
|
|
167
172
|
const codePoint = character.codePointAt(0)!;
|
|
168
173
|
return `\\u{${codePoint.toString(16).toUpperCase()}}`;
|
|
169
174
|
});
|
|
170
175
|
|
|
176
|
+
/** Host-supplied text, written so it cannot introduce structure of its own. */
|
|
177
|
+
const quote = (value: string) => {
|
|
178
|
+
const escaped = escapeUnprintable(JSON.stringify(value));
|
|
179
|
+
if (escaped.length <= MAX_QUOTED) return escaped;
|
|
180
|
+
|
|
181
|
+
// Cut whole characters, so the result cannot end inside an escape sequence.
|
|
182
|
+
let length = 2;
|
|
183
|
+
let kept = '';
|
|
184
|
+
for (const character of value) {
|
|
185
|
+
const cost = escapeUnprintable(JSON.stringify(character)).length - 2;
|
|
186
|
+
if (length + cost > MAX_QUOTED) break;
|
|
187
|
+
length += cost;
|
|
188
|
+
kept += character;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return escapeUnprintable(JSON.stringify(kept));
|
|
192
|
+
};
|
|
193
|
+
|
|
171
194
|
function section(heading: string, body: string | undefined): string | null {
|
|
172
195
|
if (!body || body.length === 0) return null;
|
|
173
196
|
return `${heading}: ${body}`;
|
|
@@ -207,7 +230,6 @@ function describeShopper(digest: SignalDigest): string {
|
|
|
207
230
|
return lines.filter((line): line is string => line !== null).join('\n');
|
|
208
231
|
}
|
|
209
232
|
|
|
210
|
-
/** One candidate per line. Facts the model must not restate are left out. */
|
|
211
233
|
function describeCandidate(product: Product): string {
|
|
212
234
|
const parts = [quote(product.sku), quote(product.title), quote(product.category)];
|
|
213
235
|
if (product.rating !== undefined) parts.push(`rated ${product.rating}`);
|
package/src/product-selection.ts
CHANGED
|
@@ -42,10 +42,8 @@ const SCORE_WEIGHTS = {
|
|
|
42
42
|
/** Beyond this many shared tags, more overlap says nothing new. */
|
|
43
43
|
const MAX_TAG_OVERLAP = 3;
|
|
44
44
|
|
|
45
|
-
/** Assumed rating for a product the catalog does not rate. */
|
|
46
45
|
const UNRATED = 3.5;
|
|
47
46
|
|
|
48
|
-
/** Tags on the products this shopper has actually engaged with. */
|
|
49
47
|
function engagedTags(input: TrackingInput, digest: SignalDigest): Set<string> {
|
|
50
48
|
const engagedSkus = new Set([
|
|
51
49
|
...digest.likedSkus,
|
|
@@ -98,12 +96,6 @@ function basisFor(
|
|
|
98
96
|
return { basis: 'popular', reason: `Popular in ${product.category}` };
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
/**
|
|
102
|
-
* Scores every eligible candidate and returns them best first.
|
|
103
|
-
*
|
|
104
|
-
* Ties break on SKU so the order is total: two runs over the same payload
|
|
105
|
-
* produce the same list, which is what makes the control arm reproducible.
|
|
106
|
-
*/
|
|
107
99
|
/** How the picks are ordered once the unplaceable ones are gone. */
|
|
108
100
|
export type RankOrder = 'signals' | 'given';
|
|
109
101
|
|
|
@@ -129,7 +121,7 @@ export function selectProducts(
|
|
|
129
121
|
// same thing whether a shopper has two signals or two hundred.
|
|
130
122
|
const strongestAffinity = Math.max(1, ...affinityByCategory.values());
|
|
131
123
|
const tags = engagedTags(input, digest);
|
|
132
|
-
const excluded = neverRecommend(
|
|
124
|
+
const excluded = neverRecommend(input);
|
|
133
125
|
const viewsBySku = new Map(digest.topViewed.map((viewed) => [viewed.sku, viewed.views]));
|
|
134
126
|
|
|
135
127
|
const picks: ProductPick[] = [];
|
package/src/provider.ts
CHANGED
|
@@ -74,14 +74,7 @@ export interface ComponentProvider {
|
|
|
74
74
|
generate(request: ProviderRequest): Promise<ProviderResult>;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* A provider that returns a fixed spec and never touches the network.
|
|
79
|
-
*
|
|
80
|
-
* This is the benchmark's control: it isolates the cost of the framework from
|
|
81
|
-
* the latency of a model, which is the only way to report what server-side
|
|
82
|
-
* rendering itself costs. It is also what the generator's tests run against,
|
|
83
|
-
* since a test that needs an API key is a test nobody runs.
|
|
84
|
-
*/
|
|
77
|
+
/** A provider that returns a fixed spec and never touches the network. */
|
|
85
78
|
export function createFixedSpecProvider(spec: GeneratedSpec): ComponentProvider {
|
|
86
79
|
return {
|
|
87
80
|
name: 'fixed',
|