@rudra-js/core 0.7.4 → 0.7.6
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 +14 -4
- package/dist/claim-screening.d.ts +3 -3
- package/dist/claim-screening.d.ts.map +1 -1
- package/dist/claim-screening.js +30 -93
- package/dist/claim-screening.js.map +1 -1
- package/dist/component-generator.d.ts.map +1 -1
- package/dist/component-generator.js +161 -176
- package/dist/component-generator.js.map +1 -1
- package/dist/component-spec.d.ts +1 -0
- package/dist/component-spec.d.ts.map +1 -1
- package/dist/component-spec.js +3 -0
- package/dist/component-spec.js.map +1 -1
- package/dist/fallback-component.d.ts +1 -1
- package/dist/fallback-component.d.ts.map +1 -1
- package/dist/fallback-component.js +9 -17
- package/dist/fallback-component.js.map +1 -1
- package/dist/fit-to-shopper.d.ts.map +1 -1
- package/dist/fit-to-shopper.js +19 -19
- package/dist/fit-to-shopper.js.map +1 -1
- package/dist/model-prompt.d.ts.map +1 -1
- package/dist/model-prompt.js +56 -39
- package/dist/model-prompt.js.map +1 -1
- package/dist/product-selection.d.ts.map +1 -1
- package/dist/product-selection.js +15 -31
- package/dist/product-selection.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js.map +1 -1
- package/dist/reconciliation.d.ts +3 -3
- package/dist/reconciliation.d.ts.map +1 -1
- package/dist/reconciliation.js +133 -171
- package/dist/reconciliation.js.map +1 -1
- package/dist/signal-digest.d.ts +1 -1
- package/dist/signal-digest.d.ts.map +1 -1
- package/dist/signal-digest.js +51 -52
- package/dist/signal-digest.js.map +1 -1
- package/dist/spec-cache.d.ts +0 -4
- package/dist/spec-cache.d.ts.map +1 -1
- package/dist/spec-cache.js +15 -35
- package/dist/spec-cache.js.map +1 -1
- package/dist/tracking-input.d.ts.map +1 -1
- package/dist/tracking-input.js +16 -19
- package/dist/tracking-input.js.map +1 -1
- package/package.json +8 -6
- package/src/claim-screening.ts +34 -114
- package/src/component-generator.ts +223 -220
- package/src/component-spec.ts +4 -15
- package/src/fallback-component.ts +11 -20
- package/src/fit-to-shopper.ts +19 -21
- package/src/model-prompt.ts +55 -54
- package/src/product-selection.ts +21 -50
- package/src/provider.ts +0 -4
- package/src/reconciliation.ts +159 -268
- package/src/signal-digest.ts +50 -79
- package/src/spec-cache.ts +15 -48
- package/src/tracking-input.ts +21 -36
package/src/claim-screening.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { foldLookalikes, verify } from '@rudra-js/attested';
|
|
1
2
|
import { offeredCandidates } from './model-prompt.js';
|
|
2
|
-
import type {
|
|
3
|
+
import type { TrackingInput } from './tracking-input.js';
|
|
3
4
|
|
|
4
5
|
const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
5
6
|
{
|
|
@@ -8,18 +9,13 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
8
9
|
/\breviews?\b|\breviewed\b/,
|
|
9
10
|
/\b(?:\d+(?:\.\d+)?|three|four|five)[\s-]?stars?\b/,
|
|
10
11
|
/\bstars?[\s-]?ratings?\b/,
|
|
11
|
-
|
|
12
12
|
/\b(?:customer|shopper|buyer|user|average|overall)[\s-]ratings?\b/,
|
|
13
|
-
|
|
14
13
|
/\bratings?\s+of\s+[0-5]\.\d\b/,
|
|
15
|
-
|
|
16
14
|
/\b[0-5](?:\.\d)?\s+out of\s+(?:5|five)\b/,
|
|
17
15
|
/\b(?:highly|top|best|well|poorly|five|four)[\s-]rated\b/,
|
|
18
|
-
|
|
19
|
-
/\brated\s+(?:[0-5]
|
|
20
|
-
|
|
16
|
+
/\brated\s+[0-5]\.\d\b/,
|
|
17
|
+
/\brated\s+(?:[0-5]|three|four|five)\s+(?:stars?|out of)\b/,
|
|
21
18
|
/\bbest[\s-]?sell(?:er|ers|ing)\b/,
|
|
22
|
-
|
|
23
19
|
/(?:\bnumber one|\bno\.? ?1|#\s?1)[\s-]?sell(?:er|ers|ing)\b/,
|
|
24
20
|
/\bloved by (?:thousands|hundreds|millions|\d)/,
|
|
25
21
|
],
|
|
@@ -29,22 +25,15 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
29
25
|
patterns: [
|
|
30
26
|
/[$£€¥₹]/,
|
|
31
27
|
/\b\d+(?:\.\d+)?\s?(?:usd|eur|gbp|dollars?|pounds?|euros?)\b/,
|
|
32
|
-
|
|
33
28
|
/\bdollars?\b|\beuros?\b/,
|
|
34
|
-
|
|
35
29
|
/\b(?:usd|eur|gbp)\s?\d/,
|
|
36
|
-
|
|
37
30
|
/\bkr\s?\d|\d\s?kr\b/,
|
|
38
31
|
/\bpric(?:e|es|ed|ing)\b/,
|
|
39
|
-
|
|
40
32
|
/\bwas\s+[$£€¥]?\s?\d[\d,.]*\s*[,;–—-]?\s*now\s+[$£€¥]?\s?\d/,
|
|
41
|
-
|
|
42
33
|
/\bcheap(?:er|est)\b/,
|
|
43
34
|
/\baffordable\b|\bbargain\b/,
|
|
44
|
-
|
|
45
35
|
/\bcosts?\s+(?:less|more|only|just|about|around|[$£€¥]?\d)/,
|
|
46
36
|
/\blow(?:er)?[\s-]cost\b/,
|
|
47
|
-
|
|
48
37
|
/\bsaves?\s+(?:you\s+)?(?:money|cash|[$£€¥]\s?\d)/,
|
|
49
38
|
],
|
|
50
39
|
},
|
|
@@ -56,12 +45,12 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
56
45
|
/\bdiscount(?:s|ed)?\b/,
|
|
57
46
|
/\bsale\b|\bmarked down\b|\bdeal of\b/,
|
|
58
47
|
/\bhalf[\s-]?(?:price|off)\b/,
|
|
59
|
-
|
|
60
48
|
/\bsaves?\s+\d[\d.,]*\s+off\b/,
|
|
61
|
-
|
|
62
49
|
/\bclearance\s+(?:sale|price|event|deal)\b|\bon clearance\b/,
|
|
63
|
-
|
|
64
|
-
/\
|
|
50
|
+
/\bprice reduced\b/,
|
|
51
|
+
/\breduced price\b/,
|
|
52
|
+
/\breduced by \d+\s?(?:%|percent)/,
|
|
53
|
+
/\breduced\s+(?:this|next|last)\s+week\b/,
|
|
65
54
|
],
|
|
66
55
|
},
|
|
67
56
|
{
|
|
@@ -80,7 +69,6 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
80
69
|
/\b(?:in|out of|low on) stock\b/,
|
|
81
70
|
/\blimited stock\b/,
|
|
82
71
|
/\brestocked?\b|\bsold out\b/,
|
|
83
|
-
|
|
84
72
|
/\b(?:only\s+)?(?:\d+|a few|a handful|a couple|one|few)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
85
73
|
/\bonly\s+(?:two|three|four|five|six|seven|eight|nine|ten)\s+(?:left|remain(?:s|ing)?)\b/,
|
|
86
74
|
/\bselling fast\b|\b(?:almost|nearly) gone\b|\bwhile stocks last\b/,
|
|
@@ -88,81 +76,6 @@ const CLAIM_PATTERNS: { kind: string; patterns: RegExp[] }[] = [
|
|
|
88
76
|
},
|
|
89
77
|
];
|
|
90
78
|
|
|
91
|
-
const CONFUSABLES: Record<string, string> = {
|
|
92
|
-
а: 'a',
|
|
93
|
-
в: 'b',
|
|
94
|
-
е: 'e',
|
|
95
|
-
к: 'k',
|
|
96
|
-
м: 'm',
|
|
97
|
-
н: 'h',
|
|
98
|
-
о: 'o',
|
|
99
|
-
р: 'p',
|
|
100
|
-
с: 'c',
|
|
101
|
-
т: 't',
|
|
102
|
-
х: 'x',
|
|
103
|
-
у: 'y',
|
|
104
|
-
ѕ: 's',
|
|
105
|
-
і: 'i',
|
|
106
|
-
ј: 'j',
|
|
107
|
-
ԁ: 'd',
|
|
108
|
-
һ: 'h',
|
|
109
|
-
ӏ: 'l',
|
|
110
|
-
α: 'a',
|
|
111
|
-
ε: 'e',
|
|
112
|
-
ι: 'i',
|
|
113
|
-
κ: 'k',
|
|
114
|
-
ν: 'v',
|
|
115
|
-
ο: 'o',
|
|
116
|
-
ρ: 'p',
|
|
117
|
-
τ: 't',
|
|
118
|
-
υ: 'u',
|
|
119
|
-
χ: 'x',
|
|
120
|
-
ϳ: 'j',
|
|
121
|
-
ѵ: 'v',
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Capitals that lowercasing reads wrong: Ν becomes ν, which reads as v.
|
|
125
|
-
const CAPITALS: Record<string, string> = {
|
|
126
|
-
Β: 'b',
|
|
127
|
-
Ζ: 'z',
|
|
128
|
-
Η: 'h',
|
|
129
|
-
Μ: 'm',
|
|
130
|
-
Ν: 'n',
|
|
131
|
-
Υ: 'y',
|
|
132
|
-
Ү: 'y',
|
|
133
|
-
Ӏ: 'i',
|
|
134
|
-
Ԛ: 'q',
|
|
135
|
-
Ԝ: 'w',
|
|
136
|
-
Ϻ: 'm',
|
|
137
|
-
Ϝ: 'f',
|
|
138
|
-
Ԍ: 'g',
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const INVISIBLE = /[\p{Cf}\p{Default_Ignorable_Code_Point}\p{Cc}]/gu;
|
|
142
|
-
|
|
143
|
-
const SPACING = /[\t\n\v\f\r\u0085]/;
|
|
144
|
-
|
|
145
|
-
const MARKS = /[\p{Mn}\p{Me}]/gu;
|
|
146
|
-
|
|
147
|
-
function normaliseForClaims(text: string, byLook: boolean): string {
|
|
148
|
-
// NFKC would turn the lunate sigmas into ς and Σ, which the fold can't read as c.
|
|
149
|
-
const compatible = text
|
|
150
|
-
.replace(INVISIBLE, (char) => (SPACING.test(char) ? char : ''))
|
|
151
|
-
.replace(/[ϲϹ]/g, 'c')
|
|
152
|
-
.normalize('NFKC');
|
|
153
|
-
|
|
154
|
-
let cased = compatible;
|
|
155
|
-
if (byLook) {
|
|
156
|
-
cased = '';
|
|
157
|
-
for (const char of compatible) cased += CAPITALS[char] ?? char;
|
|
158
|
-
}
|
|
159
|
-
const lowered = cased.toLowerCase().normalize('NFC').replace(MARKS, '');
|
|
160
|
-
|
|
161
|
-
let folded = '';
|
|
162
|
-
for (const char of lowered) folded += CONFUSABLES[char] ?? char;
|
|
163
|
-
return folded;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
79
|
export const ALLOWED_PHRASES = [
|
|
167
80
|
'does not feel cheap',
|
|
168
81
|
"doesn't feel cheap",
|
|
@@ -170,16 +83,10 @@ export const ALLOWED_PHRASES = [
|
|
|
170
83
|
'sustainable pace',
|
|
171
84
|
];
|
|
172
85
|
|
|
173
|
-
const
|
|
86
|
+
const FACT_DIGIT = /\p{Nd}/u;
|
|
87
|
+
const NUMERAL = /[\p{Nd}\p{No}\p{Nl}]/u;
|
|
174
88
|
|
|
175
|
-
export
|
|
176
|
-
const facts: string[] = [];
|
|
177
|
-
if (DIGIT.test(product.category)) facts.push(product.category);
|
|
178
|
-
for (const tag of product.tags) {
|
|
179
|
-
if (DIGIT.test(tag)) facts.push(tag);
|
|
180
|
-
}
|
|
181
|
-
return facts;
|
|
182
|
-
}
|
|
89
|
+
export const NO_FACTS: readonly string[] = [];
|
|
183
90
|
|
|
184
91
|
export interface HostFacts {
|
|
185
92
|
pooled: string[];
|
|
@@ -191,7 +98,7 @@ export function hostFacts(input: TrackingInput): HostFacts {
|
|
|
191
98
|
const bySku = new Map<string, string[]>();
|
|
192
99
|
|
|
193
100
|
for (const product of offeredCandidates(input)) {
|
|
194
|
-
const own =
|
|
101
|
+
const own = [product.category, ...product.tags].filter((text) => FACT_DIGIT.test(text));
|
|
195
102
|
bySku.set(product.sku, own);
|
|
196
103
|
for (const fact of own) pooled.add(fact);
|
|
197
104
|
}
|
|
@@ -199,15 +106,28 @@ export function hostFacts(input: TrackingInput): HostFacts {
|
|
|
199
106
|
return { pooled: [...pooled], bySku };
|
|
200
107
|
}
|
|
201
108
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const normalised = normaliseForClaims(text, byLook);
|
|
206
|
-
for (const claim of CLAIM_PATTERNS) {
|
|
207
|
-
for (const pattern of claim.patterns) {
|
|
208
|
-
if (pattern.test(normalised)) return claim.kind;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
109
|
+
function kindIn(reading: string): string | null {
|
|
110
|
+
for (const claim of CLAIM_PATTERNS) {
|
|
111
|
+
if (claim.patterns.some((pattern) => pattern.test(reading))) return claim.kind;
|
|
211
112
|
}
|
|
212
113
|
return null;
|
|
213
114
|
}
|
|
115
|
+
|
|
116
|
+
function claimIn(text: string): string | null {
|
|
117
|
+
return kindIn(foldLookalikes(text)) ?? kindIn(foldLookalikes(text, true));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function unbackedClaim(
|
|
121
|
+
raw: string,
|
|
122
|
+
clamped: string,
|
|
123
|
+
facts: readonly string[],
|
|
124
|
+
): string | null {
|
|
125
|
+
const kind = claimIn(raw) ?? claimIn(clamped);
|
|
126
|
+
if (kind !== null) return kind;
|
|
127
|
+
|
|
128
|
+
const values = NUMERAL.test(clamped) ? facts : NO_FACTS;
|
|
129
|
+
const result = verify(clamped, { values, allowedPhrases: ALLOWED_PHRASES });
|
|
130
|
+
if (!result.quantity.supported) return 'quantity';
|
|
131
|
+
if (!result.wording.supported) return 'wording';
|
|
132
|
+
return null;
|
|
133
|
+
}
|