@rudra-js/core 0.1.0 → 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/README.md +185 -3
- package/dist/component-generator.d.ts +11 -0
- package/dist/component-generator.d.ts.map +1 -1
- package/dist/component-generator.js +14 -6
- package/dist/component-generator.js.map +1 -1
- package/dist/component-spec.d.ts +7 -7
- package/dist/component-spec.js +7 -7
- package/dist/fit-to-shopper.js +1 -1
- package/dist/fit-to-shopper.js.map +1 -1
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/reconciliation.d.ts.map +1 -1
- package/dist/reconciliation.js +20 -8
- package/dist/reconciliation.js.map +1 -1
- package/dist/signal-digest.d.ts.map +1 -1
- package/dist/signal-digest.js +10 -7
- package/dist/signal-digest.js.map +1 -1
- package/dist/spec-cache.d.ts +6 -5
- package/dist/spec-cache.d.ts.map +1 -1
- package/dist/spec-cache.js +7 -0
- package/dist/spec-cache.js.map +1 -1
- package/dist/tracking-input.d.ts +2 -0
- package/dist/tracking-input.d.ts.map +1 -1
- package/dist/tracking-input.js +10 -2
- package/dist/tracking-input.js.map +1 -1
- package/package.json +3 -3
- package/src/component-generator.ts +29 -12
- package/src/component-spec.ts +7 -7
- package/src/fit-to-shopper.ts +1 -1
- package/src/index.ts +2 -6
- package/src/reconciliation.ts +21 -8
- package/src/signal-digest.ts +10 -7
- package/src/spec-cache.ts +15 -5
- package/src/tracking-input.ts +10 -2
package/README.md
CHANGED
|
@@ -40,6 +40,19 @@ To render a spec you wrote yourself, without a model, pass
|
|
|
40
40
|
that spec, which is how the tests exercise blocks the deterministic component
|
|
41
41
|
never emits.
|
|
42
42
|
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
Everything `createComponentGenerator` takes, and what it does without it.
|
|
46
|
+
|
|
47
|
+
| Option | What it does | Default |
|
|
48
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
49
|
+
| `provider` | The model adapter. `null` runs without a model and bills nothing. | `null` |
|
|
50
|
+
| `cache` | Where generated specs are kept between requests. Pass `createNullSpecCache()` to keep none. | `createMemorySpecCache()` |
|
|
51
|
+
| `generation` | `'cohort'` shares one component between shoppers who look alike; `'per-shopper'` generates for each. | `'cohort'` |
|
|
52
|
+
| `modelTimeoutMs` | How long the model gets. Past this the request is aborted and the deterministic component renders. | `1500` |
|
|
53
|
+
| `cacheTimeoutMs` | How long a cache read gets. Past this the request generates as if the store had nothing. | `50` |
|
|
54
|
+
| `onEvent` | Called once per `generate` with a `GenerationEvent`. A hook that throws is swallowed. | none |
|
|
55
|
+
|
|
43
56
|
## `tracking-input`
|
|
44
57
|
|
|
45
58
|
The boundary between a host application and rudra-js. rudra-js collects,
|
|
@@ -80,9 +93,10 @@ request. Every category defaults to `[]`, so the host needs no special case.
|
|
|
80
93
|
### What the host must supply
|
|
81
94
|
|
|
82
95
|
`user.id`, `context.surface`, and at least one entry in `candidates`.
|
|
83
|
-
`candidates` is the merchandising boundary
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
`candidates` is the merchandising boundary. Every SKU the model writes is looked
|
|
97
|
+
up in that list, and one that is not on it is dropped by reconciliation before
|
|
98
|
+
anything renders — so a product you left out does not reach the page. SKUs must
|
|
99
|
+
be unique.
|
|
86
100
|
|
|
87
101
|
`bundles` is optional: the sets the shop sells together, each with the shop's
|
|
88
102
|
own price for the set, the currency that price is in, and, if you want one,
|
|
@@ -131,6 +145,9 @@ wrote, not text the model wrote, and it renders ahead of the model's words.
|
|
|
131
145
|
| `mostViewed[].views` | `1` |
|
|
132
146
|
| `lastPurchased[].quantity` | `1` |
|
|
133
147
|
|
|
148
|
+
`context.locale` has to be a single language tag, such as `en-US`. One tag, not
|
|
149
|
+
a list and not an `Accept-Language` header.
|
|
150
|
+
|
|
134
151
|
### Cohorts
|
|
135
152
|
|
|
136
153
|
By default one generated component is shared between shoppers who look alike,
|
|
@@ -153,6 +170,20 @@ the model chooses the products too, and every shopper pays for their own call.
|
|
|
153
170
|
createComponentGenerator({ provider, generation: 'per-shopper' });
|
|
154
171
|
```
|
|
155
172
|
|
|
173
|
+
#### What you put in `segment`
|
|
174
|
+
|
|
175
|
+
`segment` is sent to the model exactly as you wrote it, in both modes, and the
|
|
176
|
+
contract takes any string up to 128 characters. Use plain merchandising labels —
|
|
177
|
+
`lapsed`, `high-value`, `trial`, `wholesale`. Keep out anything that says
|
|
178
|
+
something protected about a person: health, race, ethnic origin, religion or
|
|
179
|
+
belief, sex life or sexual orientation, politics, union membership, biometric or
|
|
180
|
+
genetic data.
|
|
181
|
+
|
|
182
|
+
The same applies to `recentSearches`, `context.searchQuery` and
|
|
183
|
+
`interaction.type` in per-shopper mode. Those three are shopper text, and they
|
|
184
|
+
are sent as written. What a shopper types is theirs; what you label them with is
|
|
185
|
+
your choice.
|
|
186
|
+
|
|
156
187
|
### Limits
|
|
157
188
|
|
|
158
189
|
Every free-text field and every array is capped, because host strings end up
|
|
@@ -172,6 +203,8 @@ numbers rather than discovering them from a rejection.
|
|
|
172
203
|
| `candidates` | 200 | `candidates` |
|
|
173
204
|
| `productsPerBundle` | 5 | `bundles[].skus`, which also needs at least 2 |
|
|
174
205
|
| `bundles` | 20 | `bundles` |
|
|
206
|
+
| `localeTag` | 35 | `context.locale`, which also has to be one language tag |
|
|
207
|
+
| `maxItems` | 12 | `context.maxItems`, which also needs at least 1 |
|
|
175
208
|
|
|
176
209
|
These bound each field individually; they are not an aggregate prompt budget.
|
|
177
210
|
Fitting a payload into a context window is `digest`'s job, and it trims rather
|
|
@@ -184,6 +217,155 @@ Every fixed-shape object is a `strictObject`. A host that misspells
|
|
|
184
217
|
first-time visitor. `interaction.meta` is the one dynamic shape — an open
|
|
185
218
|
record, minus the keys that would mutate a prototype instead of the object.
|
|
186
219
|
|
|
220
|
+
## What the model sees
|
|
221
|
+
|
|
222
|
+
The two generation modes send different things. Cohort is the default.
|
|
223
|
+
|
|
224
|
+
### Cohort mode
|
|
225
|
+
|
|
226
|
+
- the surface and the slot
|
|
227
|
+
- the locale
|
|
228
|
+
- the segment, when you set one
|
|
229
|
+
- the category being browsed (`context.currentCategory`)
|
|
230
|
+
- the name of the category the shopper leans towards most — the name only, the
|
|
231
|
+
score stays behind
|
|
232
|
+
- whether this shopper has no history at all
|
|
233
|
+
- how many products the component may place (`context.maxItems`)
|
|
234
|
+
- the candidate list: one line per product, with its SKU, title, category,
|
|
235
|
+
rating and tags
|
|
236
|
+
|
|
237
|
+
### Per-shopper mode
|
|
238
|
+
|
|
239
|
+
Everything above, and:
|
|
240
|
+
|
|
241
|
+
- the SKU being looked at right now
|
|
242
|
+
- the current search
|
|
243
|
+
- whether this is a returning shopper
|
|
244
|
+
- liked SKUs, and disliked SKUs
|
|
245
|
+
- purchased SKUs, and what is in the basket
|
|
246
|
+
- the most-viewed SKUs, each with its view count
|
|
247
|
+
- recent searches
|
|
248
|
+
- every category they lean towards, strongest first
|
|
249
|
+
- the other kinds of interaction, each with a count
|
|
250
|
+
|
|
251
|
+
### Left out of both
|
|
252
|
+
|
|
253
|
+
- `user.id`
|
|
254
|
+
- every timestamp (`at`) — used to sort signals, then dropped
|
|
255
|
+
- dwell time (`dwellMs`) — added up in the digest, left out of the prompt
|
|
256
|
+
- every price, and every currency
|
|
257
|
+
- `imageUrl`
|
|
258
|
+
- `interaction.value` and `interaction.meta` — the model is told which kinds of
|
|
259
|
+
interaction happened and how often, and no more
|
|
260
|
+
|
|
261
|
+
The candidate list is trimmed on the way out: an out-of-stock product is dropped,
|
|
262
|
+
and at most 60 products go, in the order you supplied them.
|
|
263
|
+
|
|
264
|
+
`spec-cache.test.ts` walks every field of the digest and checks each one is
|
|
265
|
+
either in the cohort key or scrubbed from the cohort prompt. A field the key
|
|
266
|
+
leaves out that still changes the prompt fails that test. Adding a field to the
|
|
267
|
+
digest fails it too, until someone says which side the field is on.
|
|
268
|
+
|
|
269
|
+
## What the model decides, by mode
|
|
270
|
+
|
|
271
|
+
What the model wrote and what is replaced before the page is served. Cohort is
|
|
272
|
+
the default.
|
|
273
|
+
|
|
274
|
+
| Decision | Cohort, the default | Per-shopper |
|
|
275
|
+
| ---------------------------------- | --------------------------------------- | -------------------------------------- |
|
|
276
|
+
| Layout and block order | The model | The model |
|
|
277
|
+
| Headline, subheadline, copy | The model | The model |
|
|
278
|
+
| Emphasis per item | The model | The model |
|
|
279
|
+
| Badge text | Dropped — written for another product | The model |
|
|
280
|
+
| Which products, and in what order | Filled in per request, not by the model | The model, from your candidates |
|
|
281
|
+
| The reason and basis per product | Filled in per request, not by the model | The model, checked against the signals |
|
|
282
|
+
| Which bundle, of the ones you pass | Chosen per request, not by the model | Chosen per request, not by the model |
|
|
283
|
+
|
|
284
|
+
In cohort mode the grid and carousel items are filled in per request, best pick
|
|
285
|
+
first, so a component written for one shopper still fits the next. The hero is
|
|
286
|
+
the exception: it keeps the product the model named, because its headline and
|
|
287
|
+
body were written about that product and swapping it would leave copy about
|
|
288
|
+
something else. Reconciliation drops the link and keeps the words if the product cannot be
|
|
289
|
+
placed: this shopper cannot see it — out of stock, not a candidate, disliked,
|
|
290
|
+
already bought, in the basket, or the one being looked at — or an earlier block
|
|
291
|
+
already placed it, or the item budget ran out before the hero was reached.
|
|
292
|
+
|
|
293
|
+
## Any provider
|
|
294
|
+
|
|
295
|
+
A provider is three things:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
export interface ComponentProvider {
|
|
299
|
+
/** Short identifier recorded on every generated spec, e.g. 'anthropic'. */
|
|
300
|
+
readonly name: string;
|
|
301
|
+
/** Concrete model identifier, e.g. 'claude-opus-5'. */
|
|
302
|
+
readonly model: string;
|
|
303
|
+
generate(request: ProviderRequest): Promise<ProviderResult>;
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`createComponentGenerator({ provider })` takes any object of that shape — a
|
|
308
|
+
hosted API, a model you run yourself, a deployment inside your own tenancy, or a
|
|
309
|
+
recorded fixture. `@rudra-js/core` depends on no vendor SDK.
|
|
310
|
+
`@rudra-js/anthropic` is one adapter, not a requirement, and `provider: null` is
|
|
311
|
+
the default that costs nothing.
|
|
312
|
+
|
|
313
|
+
An adapter takes its API key as an option, so you choose where the key comes
|
|
314
|
+
from. `ANTHROPIC_API_KEY` is the name the example shop uses for its own
|
|
315
|
+
convenience. No package here reads the environment.
|
|
316
|
+
|
|
317
|
+
## The cache
|
|
318
|
+
|
|
319
|
+
`cache` defaults to an in-process store. `createMemorySpecCache()` keeps an entry
|
|
320
|
+
for `ttlMs` — 60,000 milliseconds by default, so one minute — and holds up to
|
|
321
|
+
`maxEntries`, 10,000 by default. Once it is full, the entry read longest ago goes
|
|
322
|
+
first.
|
|
323
|
+
|
|
324
|
+
An entry holds the generated spec and `generatedAt`, the epoch milliseconds when
|
|
325
|
+
the model produced it. That is the whole of it — no payload, no shopper, no
|
|
326
|
+
prompt.
|
|
327
|
+
|
|
328
|
+
The port is two methods, and an optional third:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
export interface SpecCache {
|
|
332
|
+
get(key: string): Promise<CachedSpec | undefined>;
|
|
333
|
+
set(key: string, cached: CachedSpec): Promise<void>;
|
|
334
|
+
delete?(key: string): Promise<void>;
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Pass your own store — Redis, Memcached, whatever you already run — and it keeps
|
|
339
|
+
entries on its own terms. What that store holds, and for how long, is yours to
|
|
340
|
+
declare to your users, because this package does not set it. Pass
|
|
341
|
+
`createNullSpecCache()` to store nothing at all.
|
|
342
|
+
|
|
343
|
+
### When a generation is wrong
|
|
344
|
+
|
|
345
|
+
Pass `provider: null` and nothing new is generated; every page renders the
|
|
346
|
+
deterministic component. Shorten `ttlMs` and a bad entry ends sooner. A store
|
|
347
|
+
with `delete` can drop one entry by the `key` on its `GenerationEvent`, and the
|
|
348
|
+
next request generates again. Per-shopper entries end only by TTL, because
|
|
349
|
+
nothing maps a shopper to their keys.
|
|
350
|
+
|
|
351
|
+
## Watching it in production
|
|
352
|
+
|
|
353
|
+
The generator never fails a render, so a provider that has been down for a
|
|
354
|
+
week only shows as plainer pages. The way to know is `onEvent`: every call to
|
|
355
|
+
`generate` that gets past input validation reports exactly one
|
|
356
|
+
`GenerationEvent`, and these are the numbers to keep from it. A payload that
|
|
357
|
+
fails `parseTrackingInput` throws instead, and reports nothing.
|
|
358
|
+
|
|
359
|
+
- **Fallback share** — the share of events with `source: 'fallback'`. Alert
|
|
360
|
+
when it climbs. `degradedReason` says which way the call failed, and `error`
|
|
361
|
+
carries what was thrown when the reason is `'provider-error'` or `'timeout'`.
|
|
362
|
+
- **Cache hit rate** — `cache: 'hit'` over the events that have a `cache`
|
|
363
|
+
field. A store that is down now shows as `cache: 'error'`, and a slow one as
|
|
364
|
+
`cache: 'timeout'`, rather than as a rising bill.
|
|
365
|
+
- **Spend** — sum `usage` over the events where `calledModel` is true.
|
|
366
|
+
Requests that joined an in-flight generation carry the same `usage`, so
|
|
367
|
+
summing over every event counts one call many times.
|
|
368
|
+
|
|
187
369
|
## Licence
|
|
188
370
|
|
|
189
371
|
[MIT](./LICENSE)
|
|
@@ -50,6 +50,8 @@ export interface GenerationEvent {
|
|
|
50
50
|
violations?: string[];
|
|
51
51
|
usage?: TokenUsage;
|
|
52
52
|
degradedReason?: DegradedReason;
|
|
53
|
+
error?: unknown;
|
|
54
|
+
cache?: 'hit' | 'miss' | 'error' | 'timeout';
|
|
53
55
|
}
|
|
54
56
|
export interface ComponentGeneratorOptions {
|
|
55
57
|
/**
|
|
@@ -71,6 +73,12 @@ export interface ComponentGeneratorOptions {
|
|
|
71
73
|
* hold the page open, which is exactly what this module exists to prevent.
|
|
72
74
|
*/
|
|
73
75
|
cacheTimeoutMs?: number;
|
|
76
|
+
/**
|
|
77
|
+
* 'cohort' shares one generated component between shoppers who look alike and
|
|
78
|
+
* fills in each shopper's own products. 'per-shopper' generates for the
|
|
79
|
+
* individual, which is what the benchmark compares against. Defaults to
|
|
80
|
+
* 'cohort'.
|
|
81
|
+
*/
|
|
74
82
|
generation?: 'cohort' | 'per-shopper';
|
|
75
83
|
/** Observability. Never allowed to break a render. */
|
|
76
84
|
onEvent?: (event: GenerationEvent) => void;
|
|
@@ -80,5 +88,8 @@ export interface ComponentGenerator {
|
|
|
80
88
|
/** The deterministic component, without consulting a model or a cache. */
|
|
81
89
|
generateDeterministic(input: TrackingInputDraft): ComponentSpec;
|
|
82
90
|
}
|
|
91
|
+
export declare class TimeoutError extends Error {
|
|
92
|
+
constructor(label: string, milliseconds: number);
|
|
93
|
+
}
|
|
83
94
|
export declare function createComponentGenerator(options?: ComponentGeneratorOptions): ComponentGenerator;
|
|
84
95
|
//# sourceMappingURL=component-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-generator.d.ts","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAUnE,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"component-generator.d.ts","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,cAAc,EAEnB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAUnE,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACtC,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5D,0EAA0E;IAC1E,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAAC;CACjE;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,YAAY,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAG9C;CACF;AA8JD,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAuPpB"}
|
|
@@ -8,7 +8,7 @@ import { fitToShopper } from './fit-to-shopper.js';
|
|
|
8
8
|
import { buildDigest, toCohortDigest } from './signal-digest.js';
|
|
9
9
|
import { createMemorySpecCache, cohortCacheKey, specCacheKey, } from './spec-cache.js';
|
|
10
10
|
import { parseTrackingInput, } from './tracking-input.js';
|
|
11
|
-
class TimeoutError extends Error {
|
|
11
|
+
export class TimeoutError extends Error {
|
|
12
12
|
constructor(label, milliseconds) {
|
|
13
13
|
super(`${label} exceeded ${milliseconds}ms`);
|
|
14
14
|
this.name = 'TimeoutError';
|
|
@@ -187,12 +187,12 @@ export function createComponentGenerator(options = {}) {
|
|
|
187
187
|
try {
|
|
188
188
|
const stored = await withinBudget('cache read', cacheTimeoutMs, () => cache.get(key));
|
|
189
189
|
const parsed = cachedSpecSchema.safeParse(stored);
|
|
190
|
-
return parsed.success ? parsed.data :
|
|
190
|
+
return parsed.success ? { outcome: 'hit', entry: parsed.data } : { outcome: 'miss' };
|
|
191
191
|
}
|
|
192
|
-
catch {
|
|
192
|
+
catch (error) {
|
|
193
193
|
// A store that is down or slow degrades to generating, not to an error
|
|
194
194
|
// page. Nothing here is worth failing a render over.
|
|
195
|
-
return
|
|
195
|
+
return { outcome: error instanceof TimeoutError ? 'timeout' : 'error' };
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
198
|
/**
|
|
@@ -251,7 +251,8 @@ export function createComponentGenerator(options = {}) {
|
|
|
251
251
|
const key = generation === 'cohort'
|
|
252
252
|
? cohortCacheKey(digest, input.candidates.map((product) => product.sku), providerId)
|
|
253
253
|
: specCacheKey(digest, input.candidates.map((product) => product.sku), providerId);
|
|
254
|
-
const
|
|
254
|
+
const read = await readCache(key);
|
|
255
|
+
const cached = read.entry;
|
|
255
256
|
let calledModel = false;
|
|
256
257
|
let answer;
|
|
257
258
|
// When the model produced this, not when it was served. A cached
|
|
@@ -276,11 +277,16 @@ export function createComponentGenerator(options = {}) {
|
|
|
276
277
|
// The request went out. Leaving `calledModel` to default here reported
|
|
277
278
|
// every failed call as no call at all, so the calls that cost money
|
|
278
279
|
// and produced nothing were the only ones missing from the count.
|
|
279
|
-
return buildDeterministic(input, digest, startedAt, key, reason, {
|
|
280
|
+
return buildDeterministic(input, digest, startedAt, key, reason, {
|
|
281
|
+
calledModel,
|
|
282
|
+
cache: read.outcome,
|
|
283
|
+
error,
|
|
284
|
+
});
|
|
280
285
|
}
|
|
281
286
|
if (!call.spec) {
|
|
282
287
|
return buildDeterministic(input, digest, startedAt, key, 'invalid-generation', {
|
|
283
288
|
calledModel,
|
|
289
|
+
cache: read.outcome,
|
|
284
290
|
...(call.usage ? { usage: call.usage } : {}),
|
|
285
291
|
});
|
|
286
292
|
}
|
|
@@ -303,6 +309,7 @@ export function createComponentGenerator(options = {}) {
|
|
|
303
309
|
if (!reconciled.isUsable) {
|
|
304
310
|
return buildDeterministic(input, digest, startedAt, key, 'unusable-on-serve', {
|
|
305
311
|
calledModel,
|
|
312
|
+
cache: read.outcome,
|
|
306
313
|
violations: reconciled.violations,
|
|
307
314
|
...(answer.usage ? { usage: answer.usage } : {}),
|
|
308
315
|
});
|
|
@@ -314,6 +321,7 @@ export function createComponentGenerator(options = {}) {
|
|
|
314
321
|
source,
|
|
315
322
|
elapsedMs: finishedAt - startedAt,
|
|
316
323
|
calledModel,
|
|
324
|
+
cache: read.outcome,
|
|
317
325
|
violations: reconciled.violations,
|
|
318
326
|
...(answer.usage ? { usage: answer.usage } : {}),
|
|
319
327
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-generator.js","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,mBAAmB,GAMpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAoB,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAqB,MAAM,oBAAoB,CAAC;AACpF,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,YAAY,GAGb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,GAGnB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"component-generator.js","sourceRoot":"","sources":["../src/component-generator.ts"],"names":[],"mappings":"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"}
|
package/dist/component-spec.d.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { z } from 'zod';
|
|
|
3
3
|
* The component specification — the only thing a language model is ever allowed
|
|
4
4
|
* to return.
|
|
5
5
|
*
|
|
6
|
-
* The model chooses layout, ordering, emphasis
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* The model chooses layout, ordering, emphasis and copy. Which products to show
|
|
7
|
+
* and why is its call only in per-shopper mode, apart from the product a hero
|
|
8
|
+
* names. It never returns markup, code, URLs, prices, product titles, or
|
|
9
|
+
* images. Every field is an enum, a bounded number, a SKU reference resolved
|
|
10
|
+
* against the candidate set, or free text that is clamped and escaped before it
|
|
11
|
+
* renders. That is what makes generated output safe to put in a server-rendered
|
|
12
|
+
* response.
|
|
13
13
|
*
|
|
14
14
|
* Two constraints shape this file, and both are easy to undo by accident:
|
|
15
15
|
*
|
package/dist/component-spec.js
CHANGED
|
@@ -3,13 +3,13 @@ import { z } from 'zod';
|
|
|
3
3
|
* The component specification — the only thing a language model is ever allowed
|
|
4
4
|
* to return.
|
|
5
5
|
*
|
|
6
|
-
* The model chooses layout, ordering, emphasis
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* The model chooses layout, ordering, emphasis and copy. Which products to show
|
|
7
|
+
* and why is its call only in per-shopper mode, apart from the product a hero
|
|
8
|
+
* names. It never returns markup, code, URLs, prices, product titles, or
|
|
9
|
+
* images. Every field is an enum, a bounded number, a SKU reference resolved
|
|
10
|
+
* against the candidate set, or free text that is clamped and escaped before it
|
|
11
|
+
* renders. That is what makes generated output safe to put in a server-rendered
|
|
12
|
+
* response.
|
|
13
13
|
*
|
|
14
14
|
* Two constraints shape this file, and both are easy to undo by accident:
|
|
15
15
|
*
|
package/dist/fit-to-shopper.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The model picks the shape
|
|
1
|
+
// The model picks the shape. Selection picks the grid and carousel products and
|
|
2
2
|
// what may be said about them, so a shared component still fits one shopper.
|
|
3
3
|
export function fitToShopper(spec, picks, maxItems) {
|
|
4
4
|
// Picks are ordered best first. Every slot takes the next one.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fit-to-shopper.js","sourceRoot":"","sources":["../src/fit-to-shopper.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"fit-to-shopper.js","sourceRoot":"","sources":["../src/fit-to-shopper.ts"],"names":[],"mappings":"AAGA,gFAAgF;AAChF,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAC1B,IAAmB,EACnB,KAA6B,EAC7B,QAAgB;IAEhB,+DAA+D;IAC/D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,qEAAqE;QACrE,4EAA4E;QAC5E,2EAA2E;QAC3E,oCAAoC;QAEpC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACvD,MAAM,KAAK,GAAuB,EAAE,CAAC;YACrC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAC/B,IAAI,IAAI,IAAI,KAAK;oBAAE,MAAM,CAAC,oBAAoB;gBAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAE,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG;oBACvB,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,mDAAmD;oBACnD,KAAK,EAAE,IAAI;oBACX,QAAQ,EAAE,IAAI,CAAC,QAAQ;iBACxB,CAAC,CAAC;gBACH,IAAI,IAAI,CAAC,CAAC;YACZ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACjC,SAAS;QACX,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
export { FIELD_LIMITS, productSchema, skuSignalSchema, viewSignalSchema, purchaseSignalSchema, interactionSchema, renderContextSchema, trackingSignalsSchema, bundleSchema, trackingInputSchema, parseTrackingInput, safeParseTrackingInput, type Product, type SkuSignal, type ViewSignal, type PurchaseSignal, type Interaction, type RenderContext, type TrackingSignals, type Bundle, type TrackingInput, type TrackingInputDraft, type TrackingInputResult, } from './tracking-input.js';
|
|
9
9
|
export { DIGEST_LIMITS, buildDigest, toCohortDigest, type CategoryAffinity, type InteractionCount, type SignalDigest, type ViewedProduct, } from './signal-digest.js';
|
|
10
10
|
export { BANNER_TONES, EMPHASIS, RECOMMENDATION_BASES, SPEC_VERSION, TONES, blockSchema, generatedSpecSchema, parseGeneratedSpec, productReferenceSchema, safeParseGeneratedSpec, type BannerBlock, type Block, type BlockKind, type BundleBlock, type CarouselBlock, type ComponentSpec, type CopyBlock, type GeneratedSpec, type GridBlock, type HeroBlock, type ProductReference, type RecommendationBasis, type DegradedReason, type SpecSource, } from './component-spec.js';
|
|
11
|
-
export {
|
|
11
|
+
export { reconcileSpec, type ReconcileResult } from './reconciliation.js';
|
|
12
12
|
export { selectProducts, type ProductPick } from './product-selection.js';
|
|
13
|
-
export { fitToShopper } from './fit-to-shopper.js';
|
|
14
|
-
export { buildFallbackSpec } from './fallback-component.js';
|
|
15
13
|
export { createFixedSpecProvider, type ComponentProvider, type ProviderRequest, type ProviderResult, type TokenUsage, } from './provider.js';
|
|
16
|
-
export { createMemorySpecCache, createNullSpecCache,
|
|
17
|
-
export {
|
|
14
|
+
export { createMemorySpecCache, createNullSpecCache, type CachedSpec, type MemorySpecCacheOptions, type SpecCache, } from './spec-cache.js';
|
|
15
|
+
export { buildPrompt, type PromptPair } from './model-prompt.js';
|
|
18
16
|
export { createComponentGenerator, type ComponentGenerator, type ComponentGeneratorOptions, type GenerationEvent, } from './component-generator.js';
|
|
19
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EACL,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
export { FIELD_LIMITS, productSchema, skuSignalSchema, viewSignalSchema, purchaseSignalSchema, interactionSchema, renderContextSchema, trackingSignalsSchema, bundleSchema, trackingInputSchema, parseTrackingInput, safeParseTrackingInput, } from './tracking-input.js';
|
|
9
9
|
export { DIGEST_LIMITS, buildDigest, toCohortDigest, } from './signal-digest.js';
|
|
10
10
|
export { BANNER_TONES, EMPHASIS, RECOMMENDATION_BASES, SPEC_VERSION, TONES, blockSchema, generatedSpecSchema, parseGeneratedSpec, productReferenceSchema, safeParseGeneratedSpec, } from './component-spec.js';
|
|
11
|
-
export {
|
|
11
|
+
export { reconcileSpec } from './reconciliation.js';
|
|
12
12
|
export { selectProducts } from './product-selection.js';
|
|
13
|
-
export { fitToShopper } from './fit-to-shopper.js';
|
|
14
|
-
export { buildFallbackSpec } from './fallback-component.js';
|
|
15
13
|
export { createFixedSpecProvider, } from './provider.js';
|
|
16
|
-
export { createMemorySpecCache, createNullSpecCache,
|
|
17
|
-
export {
|
|
14
|
+
export { createMemorySpecCache, createNullSpecCache, } from './spec-cache.js';
|
|
15
|
+
export { buildPrompt } from './model-prompt.js';
|
|
18
16
|
export { createComponentGenerator, } from './component-generator.js';
|
|
19
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GAYvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,cAAc,GAKf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,GAevB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GAYvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,cAAc,GAKf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,GAevB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAwB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAoB,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EACL,uBAAuB,GAKxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,GAIpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EACL,wBAAwB,GAIzB,MAAM,0BAA0B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconciliation.d.ts","sourceRoot":"","sources":["../src/reconciliation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EAGd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAW,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"reconciliation.d.ts","sourceRoot":"","sources":["../src/reconciliation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,aAAa,EAGd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAW,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAuC1E;;;;;GAKG;AACH,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AA2BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,CAQhE;AAmWD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,MAAM,GAAG,SAAS,CAQpB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,YAAY,GACnB,MAAM,EAAE,CAcV;AAoID,wBAAgB,aAAa,CAC3B,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,YAAY,GACnB,eAAe,CA0CjB"}
|
package/dist/reconciliation.js
CHANGED
|
@@ -30,6 +30,8 @@ const CLAMP = {
|
|
|
30
30
|
bannerText: 160,
|
|
31
31
|
copyBody: 420,
|
|
32
32
|
rationale: 300,
|
|
33
|
+
// Not rendered anywhere — this is the SKU as it reads back in a violation.
|
|
34
|
+
violationSku: 32,
|
|
33
35
|
};
|
|
34
36
|
/**
|
|
35
37
|
* More than this and the component stops being a component.
|
|
@@ -145,6 +147,8 @@ const CLAIM_PATTERNS = [
|
|
|
145
147
|
/\b(?:customer|shopper|buyer|user|average|overall)[\s-]ratings?\b/,
|
|
146
148
|
// A score out of five is written as a decimal. A spec is "-5C" or "20,000mm".
|
|
147
149
|
/\bratings?\s+of\s+[0-5]\.\d\b/,
|
|
150
|
+
// The same score with the word "rating" nowhere near it: "4.8 out of 5".
|
|
151
|
+
/\b[0-5](?:\.\d)?\s+out of\s+(?:5|five)\b/,
|
|
148
152
|
/\b(?:highly|top|best|well|poorly|five|four)[\s-]rated\b/,
|
|
149
153
|
// "rated 4.8" is a score. "rated 3 season" is what the tent is built for.
|
|
150
154
|
/\brated\s+(?:[0-5]\.\d|(?:[0-5]|three|four|five)\s+(?:stars?|out of))\b/,
|
|
@@ -156,8 +160,12 @@ const CLAIM_PATTERNS = [
|
|
|
156
160
|
{
|
|
157
161
|
kind: 'price',
|
|
158
162
|
patterns: [
|
|
159
|
-
/[
|
|
163
|
+
/[$£€¥₹]\s?\d/,
|
|
160
164
|
/\b\d+(?:\.\d+)?\s?(?:usd|eur|gbp|dollars?|pounds?|euros?)\b/,
|
|
165
|
+
// The same codes on the other side of the number: "USD 20", "EUR 5.99".
|
|
166
|
+
/\b(?:usd|eur|gbp)\s?\d/,
|
|
167
|
+
// The krona is spelled out rather than drawn, so it needs a number beside it.
|
|
168
|
+
/\bkr\s?\d|\d\s?kr\b/,
|
|
161
169
|
/\bpric(?:e|es|ed|ing)\b/,
|
|
162
170
|
// A before-and-after is a price claim even with no currency on it.
|
|
163
171
|
/\bwas\s+[$£€¥]?\s?\d[\d,.]*\s*[,;–—-]?\s*now\s+[$£€¥]?\s?\d/,
|
|
@@ -182,6 +190,8 @@ const CLAIM_PATTERNS = [
|
|
|
182
190
|
/\bdiscount(?:s|ed)?\b/,
|
|
183
191
|
/\bsale\b|\bmarked down\b|\bdeal of\b/,
|
|
184
192
|
/\bhalf[\s-]?(?:price|off)\b/,
|
|
193
|
+
// Money off with no percent sign anywhere on it: "save 20 off".
|
|
194
|
+
/\bsaves?\s+\d[\d.,]*\s+off\b/,
|
|
185
195
|
// "extra clearance for thick socks" is room inside the shoe.
|
|
186
196
|
/\bclearance\s+(?:sale|price|event|deal)\b|\bon clearance\b/,
|
|
187
197
|
// "reduced weight" and "reduced to 900g" are specifications. Only a
|
|
@@ -206,10 +216,10 @@ const CLAIM_PATTERNS = [
|
|
|
206
216
|
kind: 'stock',
|
|
207
217
|
patterns: [
|
|
208
218
|
/\b(?:in|out of|low on) stock\b/,
|
|
219
|
+
/\blimited stock\b/,
|
|
209
220
|
/\brestocked?\b|\bsold out\b/,
|
|
210
221
|
// "the last few miles" is a distance, so a count needs "left" after it.
|
|
211
|
-
/\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|
|
|
212
|
-
/\blast (?:one|few)\s+(?:left|remaining|in stock)\b/,
|
|
222
|
+
/\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
213
223
|
/\bselling fast\b|\b(?:almost|nearly) gone\b|\bwhile stocks last\b/,
|
|
214
224
|
],
|
|
215
225
|
},
|
|
@@ -288,15 +298,17 @@ function screenRequired(value, field, tracker) {
|
|
|
288
298
|
* so each one has to name the fault that actually fired.
|
|
289
299
|
*/
|
|
290
300
|
function rejectionFor(sku, allowlist, tracker) {
|
|
301
|
+
// A rejected SKU is whatever the model wrote, and the schema cannot bound it.
|
|
302
|
+
const named = clamp(sku, CLAMP.violationSku);
|
|
291
303
|
// Either hallucinated or out of stock. Either way it cannot render.
|
|
292
304
|
if (!allowlist.allowed.has(sku))
|
|
293
|
-
return `unknown-sku:${
|
|
305
|
+
return `unknown-sku:${named}`;
|
|
294
306
|
if (allowlist.blocked.has(sku))
|
|
295
|
-
return `blocked-sku:${
|
|
307
|
+
return `blocked-sku:${named}`;
|
|
296
308
|
if (tracker.hasPlaced(sku))
|
|
297
|
-
return `duplicate-sku:${
|
|
309
|
+
return `duplicate-sku:${named}`;
|
|
298
310
|
if (tracker.remaining <= 0)
|
|
299
|
-
return `budget:dropped:${
|
|
311
|
+
return `budget:dropped:${named}`;
|
|
300
312
|
return null;
|
|
301
313
|
}
|
|
302
314
|
function reconcileItems(items, allowlist, candidatesBySku, digest, tracker) {
|
|
@@ -549,7 +561,7 @@ export function reconcileSpec(generated, input, digest) {
|
|
|
549
561
|
headline: screenRequired(clamp(generated.headline, CLAMP.headline), 'headline', tracker),
|
|
550
562
|
subheadline: screenClaim(clampNullable(generated.subheadline, CLAMP.subheadline), 'subheadline', tracker),
|
|
551
563
|
blocks,
|
|
552
|
-
rationale: clamp(generated.rationale, CLAMP.rationale),
|
|
564
|
+
rationale: screenRequired(clamp(generated.rationale, CLAMP.rationale), 'rationale', tracker),
|
|
553
565
|
};
|
|
554
566
|
// A component that recommends nothing is worse than no component at all.
|
|
555
567
|
// Recorded separately: "no products survived" and "the model returned no
|