@rudra-js/core 0.7.3 → 0.7.4
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 +113 -22
- package/dist/claim-screening.d.ts.map +1 -1
- package/dist/claim-screening.js +9 -11
- package/dist/claim-screening.js.map +1 -1
- package/package.json +3 -3
- package/src/claim-screening.ts +9 -13
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ package _is_ zod schemas, and an app on any zod 4 shares one copy with it. An
|
|
|
16
16
|
app on zod 3 gets its own copy alongside ours, and the two don't mix: build on
|
|
17
17
|
the exported schemas with their own methods (`productSchema.array()`) rather
|
|
18
18
|
than your zod's (`z.array(productSchema)`), and read `error.issues` rather than
|
|
19
|
-
checking `instanceof ZodError`.
|
|
19
|
+
checking `instanceof ZodError`. `@rudra-js/anthropic`'s own zod writes the tool
|
|
20
20
|
schema we send the model, so its wording shifts a little between releases:
|
|
21
21
|
before 4.5, a nullable field is written as `anyOf` rather than a type array. It
|
|
22
22
|
means the same thing either way.
|
|
@@ -176,15 +176,6 @@ Which strings, exactly:
|
|
|
176
176
|
- A `reason` or a `badge` sits under a named product, so it's read against that
|
|
177
177
|
product's own tags and category. Every other field reads all the candidates'
|
|
178
178
|
pooled.
|
|
179
|
-
- A tag or category that is nothing but a number in exponent notation, and whose
|
|
180
|
-
exponent runs past a thousand, is dropped here. `1e2000000000` is twelve
|
|
181
|
-
characters that lay out into a run long enough to end the process, and
|
|
182
|
-
`@rudra-js/attested` is a peer dependency, so the copy you have installed may
|
|
183
|
-
be one that still tries. `@rudra-js/attested` draws its own line in the same
|
|
184
|
-
place but measures the laid-out run rather than the exponent, so a handful of
|
|
185
|
-
tags near the margin — `1e1000`, `1.5e1000`, `9999e998` — get past this rule
|
|
186
|
-
and then stand behind nothing on the other side of it. Nothing that wide is a
|
|
187
|
-
product fact either way.
|
|
188
179
|
|
|
189
180
|
Two caveats worth saying out loud, because they're the shape of the check rather
|
|
190
181
|
than bugs in it. Pooled means pooled: one product's `40 litre` tag stands behind
|
|
@@ -246,13 +237,14 @@ list, and not an `Accept-Language` header.
|
|
|
246
237
|
|
|
247
238
|
### Cohorts
|
|
248
239
|
|
|
249
|
-
By default one generated component is shared between shoppers who look alike,
|
|
250
|
-
each shopper's own products are filled in per request. A cohort is the
|
|
251
|
-
segment, the surface and slot, the locale, the item count, whether
|
|
252
|
-
|
|
253
|
-
towards.
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
By default one generated component is shared between shoppers who look alike,
|
|
241
|
+
and each shopper's own products are filled in per request. A cohort is the
|
|
242
|
+
shopper's segment, the surface and slot, the locale, the item count, whether
|
|
243
|
+
they have any history, the category being browsed, and the category they lean
|
|
244
|
+
towards. Who they are, what they searched for and the products they liked or
|
|
245
|
+
viewed stay out of it. For the shared cohort we work out only two things from
|
|
246
|
+
their own data: the category they lean towards and whether they have any
|
|
247
|
+
history. That's what lets many page views reuse one call.
|
|
256
248
|
|
|
257
249
|
The candidate list is part of the cohort too, since the model is shown those
|
|
258
250
|
products and writes about them. In most shops candidates come from the page, so
|
|
@@ -405,7 +397,8 @@ The two generation modes send different things. Cohort is the default.
|
|
|
405
397
|
- the category being browsed (`context.currentCategory`)
|
|
406
398
|
- the name of the category the shopper leans towards most. Just the name, the
|
|
407
399
|
score stays behind
|
|
408
|
-
- whether this shopper has no history at all
|
|
400
|
+
- whether this shopper has no history at all, meaning no likes, dislikes,
|
|
401
|
+
purchases, basket items or views. Searches and other interactions don't count
|
|
409
402
|
- how many products the component may place (`context.maxItems`)
|
|
410
403
|
- the candidate list: one line per product, with its SKU, title, category, rating
|
|
411
404
|
and tags
|
|
@@ -421,7 +414,7 @@ Everything above, and:
|
|
|
421
414
|
- purchased SKUs, and what is in the basket
|
|
422
415
|
- the most-viewed SKUs, each with its view count
|
|
423
416
|
- recent searches
|
|
424
|
-
-
|
|
417
|
+
- up to six categories they lean towards, strongest first
|
|
425
418
|
- the other kinds of interaction, each with a count
|
|
426
419
|
|
|
427
420
|
### Left out of both
|
|
@@ -499,8 +492,14 @@ for `ttlMs`, 60,000 milliseconds by default, so one minute. It holds up to
|
|
|
499
492
|
`maxEntries`, 10,000 by default. Once it's full, the entry read longest ago is
|
|
500
493
|
the first to go.
|
|
501
494
|
|
|
502
|
-
An entry holds the generated spec
|
|
503
|
-
the
|
|
495
|
+
An entry holds the generated spec, as the model returned it before screening,
|
|
496
|
+
and `generatedAt`, the epoch milliseconds when the model produced it. No payload
|
|
497
|
+
and no prompt. In cohort mode it holds no shopper id and everyone in the cohort
|
|
498
|
+
shares it, unless its segment or candidates were picked for one shopper. In
|
|
499
|
+
per-shopper mode the model wrote it from that shopper's signals, so its text can
|
|
500
|
+
repeat them, a search term for instance, and the entry is personal data until it
|
|
501
|
+
goes. Expiry is checked when an entry is read, so an expired one stays in memory
|
|
502
|
+
until it's read again or pushed out by a newer one.
|
|
504
503
|
|
|
505
504
|
The port is two methods, and an optional third:
|
|
506
505
|
|
|
@@ -542,7 +541,99 @@ These are the numbers worth keeping:
|
|
|
542
541
|
`cache: 'timeout'`, rather than as a rising bill.
|
|
543
542
|
- **Spend** — sum `usage` over the events where `calledModel` is true. Requests
|
|
544
543
|
that joined an in-flight generation carry the same `usage`, so summing over
|
|
545
|
-
every event counts one call many times.
|
|
544
|
+
every event counts one call many times. A call the model answered but the
|
|
545
|
+
adapter turned down (a `max_tokens` stop, a refusal, no tool call, or a reply
|
|
546
|
+
that doesn't fit the schema) was billed too, and arrives as a
|
|
547
|
+
`'provider-error'` with no `usage`, so this sum is lower than your bill.
|
|
548
|
+
|
|
549
|
+
## Running it in a regulated shop
|
|
550
|
+
|
|
551
|
+
This isn't legal advice, and which rules apply depends on where you sell. It's
|
|
552
|
+
what the package does, so you can answer what a privacy or consumer-law review
|
|
553
|
+
will ask.
|
|
554
|
+
|
|
555
|
+
**What leaves your server.** Nothing, unless you pass a `provider`, or a `cache`
|
|
556
|
+
whose store lives somewhere else. With a provider, cohort mode sends the page
|
|
557
|
+
and locale, the category, the segment you set, the shopper's strongest category,
|
|
558
|
+
whether they have any history, and the candidate products. There's no shopper id
|
|
559
|
+
in it, but it can still be personal data: the strongest category and the history
|
|
560
|
+
flag come from that shopper's data, the segment goes exactly as you wrote it,
|
|
561
|
+
and the candidate list is about them too if you pick or order it per shopper.
|
|
562
|
+
Per-shopper mode adds the shopper's own signals. Both lists are under [What the
|
|
563
|
+
model sees](#what-the-model-sees). `user.id` is never sent in either mode. The
|
|
564
|
+
provider should be your processor, under a contract that says so; for Anthropic,
|
|
565
|
+
see the `@rudra-js/anthropic` README.
|
|
566
|
+
|
|
567
|
+
**A shopper who objects to profiling.** Leave `signals` and `user.segment` out
|
|
568
|
+
of their payload, and in per-shopper mode `context.searchQuery` and
|
|
569
|
+
`user.isReturning` too. They get the cold-start component, built from the page
|
|
570
|
+
and the catalog, not from them. With those left out, `generateDeterministic(input)`
|
|
571
|
+
gives the same kind of component without calling the model or the cache. Called
|
|
572
|
+
with signals, it still personalises.
|
|
573
|
+
|
|
574
|
+
**Deleting a shopper's data.** The package keeps nothing about a shopper except
|
|
575
|
+
the cache. In cohort mode an entry holds no shopper id and everyone in the
|
|
576
|
+
cohort shares it, so usually there's no one shopper's entry to delete. The
|
|
577
|
+
exception is a cohort entry whose segment or candidate list was picked for one
|
|
578
|
+
shopper: like every per-shopper entry, it's about that shopper. Nothing maps a
|
|
579
|
+
shopper to their entries, so keep `ttlMs` short or pass `createNullSpecCache()`.
|
|
580
|
+
`onEvent` sends no shopper id, but in per-shopper mode its `key` is derived from
|
|
581
|
+
one, and `violations` can name SKUs from a shopper's basket or purchases, so
|
|
582
|
+
treat logs of it as personal data.
|
|
583
|
+
|
|
584
|
+
**Caching pages.** A personalised page says things like "Goes with your cart" and
|
|
585
|
+
"You looked at this recently" in its HTML. Serve those pages with
|
|
586
|
+
`Cache-Control: private` or `no-store`, so a shared cache never hands one
|
|
587
|
+
shopper's page to another.
|
|
588
|
+
|
|
589
|
+
**Telling shoppers a model wrote the copy.** The wrapper's `data-rudra-source`
|
|
590
|
+
says `llm` or `cache` when a model wrote the copy and `fallback` when it didn't,
|
|
591
|
+
so it's a machine-readable marker you can build on, for example to show a note.
|
|
592
|
+
Whether you must label AI-written copy, and how, depends on where you sell; in
|
|
593
|
+
the EU, Article 50 of the AI Act is the rule to ask about.
|
|
594
|
+
|
|
595
|
+
**Claims.** The copy on your page is your own advertising, whoever wrote it. The
|
|
596
|
+
screen catches prices, discounts, stock, ratings, delivery, urgency and generic
|
|
597
|
+
environmental claims, but it's a list, and rewordings get through (SECURITY.md
|
|
598
|
+
explains why, and the tests list the known ones). Anything it misses is still
|
|
599
|
+
yours to stand behind.
|
|
600
|
+
|
|
601
|
+
**Children.** In the UK, the ICO's Children's Code covers any shop that
|
|
602
|
+
under-18s are more likely than not to use, not only one aimed at them. Its
|
|
603
|
+
standard 12 asks for profiling to be off by default unless you can show a
|
|
604
|
+
compelling reason, judged by the child's best interests, and for safeguards when
|
|
605
|
+
it's on. For a shop like that, don't use per-shopper mode. For a shopper under
|
|
606
|
+
18, leave `signals` and `user.segment` out of their payload unless they've
|
|
607
|
+
switched personalisation on, as for a shopper who objects to profiling. If you
|
|
608
|
+
can't tell a shopper's age with confidence that fits the risk, treat every
|
|
609
|
+
shopper as under 18. Cohort mode alone doesn't turn profiling off: the default
|
|
610
|
+
`rank: 'signals'` still orders products by the shopper's signals in cohort mode
|
|
611
|
+
and in the fallback component, even with no provider, and cohort mode still
|
|
612
|
+
sends their strongest category, whether they have any history, and any segment
|
|
613
|
+
to the model.
|
|
614
|
+
|
|
615
|
+
**Marketplaces.** If other sellers list products on your site, EU platform rules
|
|
616
|
+
(Article 27 of the Digital Services Act) ask you, unless you're a micro or small
|
|
617
|
+
business, to set out in your terms your recommender's main parameters, why they
|
|
618
|
+
weigh as they do, and any options shoppers have to change them. If shoppers can
|
|
619
|
+
pick between options, such as switching personalisation off, the control has to
|
|
620
|
+
sit where the products are shown. Which parameters apply depends on your mode
|
|
621
|
+
and your `rank` setting. `rank` sets the order in cohort mode and in the
|
|
622
|
+
fallback component. With `rank: 'given'` that's the order you sent, so describe
|
|
623
|
+
your own ranking. With `rank: 'signals'` it comes from the shopper's category
|
|
624
|
+
interest (the category they're browsing counts too), how often they've viewed a
|
|
625
|
+
product, its rating, and tags it shares with products they've engaged with.
|
|
626
|
+
Category weighs most. In per-shopper mode the model picks which candidates to
|
|
627
|
+
show and in what order, from what's under
|
|
628
|
+
[What the model sees](#what-the-model-sees). In cohort mode the model picks the
|
|
629
|
+
hero's product, from the cohort list there. In every mode, anything out of stock, disliked or
|
|
630
|
+
being looked at right now is left out, and so is anything bought or in the
|
|
631
|
+
basket, except as part of a bundle. A bundle is picked by what's in the basket,
|
|
632
|
+
then what they viewed, then the category being browsed.
|
|
633
|
+
|
|
634
|
+
**Accessibility.** Every card is one link with a readable name, and headings
|
|
635
|
+
only render when they have text. Contrast, focus styles and layout come from
|
|
636
|
+
your stylesheet, so those are yours to check.
|
|
546
637
|
|
|
547
638
|
## Licence
|
|
548
639
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claim-screening.d.ts","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"claim-screening.d.ts","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAoKlE,eAAO,MAAM,eAAe,UAK3B,CAAC;AAIF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAOvD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAWzD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnD"}
|
package/dist/claim-screening.js
CHANGED
|
@@ -63,6 +63,7 @@ const CLAIM_PATTERNS = [
|
|
|
63
63
|
/\blimited stock\b/,
|
|
64
64
|
/\brestocked?\b|\bsold out\b/,
|
|
65
65
|
/\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
66
|
+
/\bonly\s+(?:two|three|four|five|six|seven|eight|nine|ten)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
66
67
|
/\bselling fast\b|\b(?:almost|nearly) gone\b|\bwhile stocks last\b/,
|
|
67
68
|
],
|
|
68
69
|
},
|
|
@@ -136,22 +137,19 @@ function normaliseForClaims(text, byLook) {
|
|
|
136
137
|
folded += CONFUSABLES[char] ?? char;
|
|
137
138
|
return folded;
|
|
138
139
|
}
|
|
139
|
-
export const ALLOWED_PHRASES = [
|
|
140
|
+
export const ALLOWED_PHRASES = [
|
|
141
|
+
'does not feel cheap',
|
|
142
|
+
"doesn't feel cheap",
|
|
143
|
+
'last few miles',
|
|
144
|
+
'sustainable pace',
|
|
145
|
+
];
|
|
140
146
|
const DIGIT = /\p{Nd}/u;
|
|
141
|
-
const BARE_EXPONENT = /^[+-]?\d+(?:\.\d+)?[eE]([+-]?\d+)$/;
|
|
142
|
-
const MAX_EXPONENT = 1000;
|
|
143
|
-
function canLayOut(fact) {
|
|
144
|
-
const match = BARE_EXPONENT.exec(fact.trim());
|
|
145
|
-
if (match === null)
|
|
146
|
-
return true;
|
|
147
|
-
return Math.abs(Number(match[1] ?? '')) <= MAX_EXPONENT;
|
|
148
|
-
}
|
|
149
147
|
export function productFacts(product) {
|
|
150
148
|
const facts = [];
|
|
151
|
-
if (DIGIT.test(product.category)
|
|
149
|
+
if (DIGIT.test(product.category))
|
|
152
150
|
facts.push(product.category);
|
|
153
151
|
for (const tag of product.tags) {
|
|
154
|
-
if (DIGIT.test(tag)
|
|
152
|
+
if (DIGIT.test(tag))
|
|
155
153
|
facts.push(tag);
|
|
156
154
|
}
|
|
157
155
|
return facts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claim-screening.js","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"claim-screening.js","sourceRoot":"","sources":["../src/claim-screening.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rudra-js/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "The contracts and logic that turn one tracking payload into one renderable component specification",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"zod": "^4.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@rudra-js/attested": "^0.7.
|
|
57
|
+
"@rudra-js/attested": "^0.7.4"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@rudra-js/attested": "0.7.
|
|
60
|
+
"@rudra-js/attested": "0.7.4",
|
|
61
61
|
"@types/node": "^26.4.1"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/src/claim-screening.ts
CHANGED
|
@@ -82,6 +82,7 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
82
82
|
/\brestocked?\b|\bsold out\b/,
|
|
83
83
|
|
|
84
84
|
/\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
85
|
+
/\bonly\s+(?:two|three|four|five|six|seven|eight|nine|ten)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
85
86
|
/\bselling fast\b|\b(?:almost|nearly) gone\b|\bwhile stocks last\b/,
|
|
86
87
|
],
|
|
87
88
|
},
|
|
@@ -162,25 +163,20 @@ function normaliseForClaims(text: string, byLook: boolean): string {
|
|
|
162
163
|
return folded;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
export const ALLOWED_PHRASES = [
|
|
166
|
+
export const ALLOWED_PHRASES = [
|
|
167
|
+
'does not feel cheap',
|
|
168
|
+
"doesn't feel cheap",
|
|
169
|
+
'last few miles',
|
|
170
|
+
'sustainable pace',
|
|
171
|
+
];
|
|
166
172
|
|
|
167
173
|
const DIGIT = /\p{Nd}/u;
|
|
168
174
|
|
|
169
|
-
const BARE_EXPONENT = /^[+-]?\d+(?:\.\d+)?[eE]([+-]?\d+)$/;
|
|
170
|
-
|
|
171
|
-
const MAX_EXPONENT = 1000;
|
|
172
|
-
|
|
173
|
-
function canLayOut(fact: string): boolean {
|
|
174
|
-
const match = BARE_EXPONENT.exec(fact.trim());
|
|
175
|
-
if (match === null) return true;
|
|
176
|
-
return Math.abs(Number(match[1] ?? '')) <= MAX_EXPONENT;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
175
|
export function productFacts(product: Product): string[] {
|
|
180
176
|
const facts: string[] = [];
|
|
181
|
-
if (DIGIT.test(product.category)
|
|
177
|
+
if (DIGIT.test(product.category)) facts.push(product.category);
|
|
182
178
|
for (const tag of product.tags) {
|
|
183
|
-
if (DIGIT.test(tag)
|
|
179
|
+
if (DIGIT.test(tag)) facts.push(tag);
|
|
184
180
|
}
|
|
185
181
|
return facts;
|
|
186
182
|
}
|