@rudra-js/attested 0.8.1 → 0.8.3
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 +243 -241
- package/dist/phrases.d.ts.map +1 -1
- package/dist/phrases.js +9 -1
- package/dist/phrases.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +7 -5
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/phrases.ts +12 -1
- package/src/verify.ts +7 -6
package/README.md
CHANGED
|
@@ -2,34 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
This package is part of [rudra-js](https://rudrajs.com).
|
|
4
4
|
|
|
5
|
-
rudra-js is an open-source TypeScript library (MIT) you install from npm. A
|
|
5
|
+
rudra-js is an open-source TypeScript library (MIT) you install from npm. A
|
|
6
|
+
language model designs one product-recommendation block for a page in your shop,
|
|
7
|
+
rudra-js checks it against your catalog, and React renders it.
|
|
6
8
|
|
|
7
|
-
It is not a hosted platform, a visual or low-code app builder, or a workspace
|
|
9
|
+
It is not a hosted platform, a visual or low-code app builder, or a workspace
|
|
10
|
+
you sign up for.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
This package checks model-written product copy against the facts your shop
|
|
13
|
+
stands behind.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
The reference for both functions is on rudrajs.com:
|
|
16
|
+
[`verify`](https://rudrajs.com/reference/attested/verify) and
|
|
17
|
+
[`verifyFields`](https://rudrajs.com/reference/attested/verifyFields).
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`fetch` to a model, your own template.
|
|
19
|
+
It's a pure function over text and facts. It has no dependencies and does no
|
|
20
|
+
I/O. It uses no Node built-ins either, so it runs in a browser or an edge
|
|
21
|
+
runtime as well as in Node. Drop it in after anything that writes copy:
|
|
22
|
+
json-render, the Vercel AI SDK, a raw `fetch` to a model, or your own template.
|
|
17
23
|
|
|
18
24
|
## The guarantee
|
|
19
25
|
|
|
20
26
|
**When `quantity.supported` is true, every numeral in the text is one you
|
|
21
|
-
supplied
|
|
22
|
-
pass.**
|
|
27
|
+
supplied. A numeral the package can't read counts as a failure.**
|
|
23
28
|
|
|
24
|
-
That
|
|
25
|
-
|
|
26
|
-
supplied can still be written as a sales figure. It says nothing at all about
|
|
27
|
-
`wording.supported`, which is a denylist over an open set and carries
|
|
28
|
-
`strength: 'best-effort'` on every result for that reason.
|
|
29
|
+
That's the whole promise, and it covers numerals only. It doesn't say the
|
|
30
|
+
sentence around a number is true. It says nothing about `wording.supported`.
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
[What it cannot catch](#what-it-cannot-catch).
|
|
32
|
-
|
|
32
|
+
The gaps it leaves are listed under
|
|
33
|
+
[What it cannot catch](#what-it-cannot-catch). None of them is a bug waiting for
|
|
34
|
+
a fix.
|
|
33
35
|
|
|
34
36
|
## Install
|
|
35
37
|
|
|
@@ -49,131 +51,144 @@ result.quantity.findings;
|
|
|
49
51
|
|
|
50
52
|
## The two layers
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
a proof.
|
|
54
|
+
`verify` checks two layers and reports each one separately. Layer one,
|
|
55
|
+
quantities, is a proof. Layer two, wording, is best-effort.
|
|
54
56
|
|
|
55
|
-
### Layer one
|
|
57
|
+
### Layer one: quantities
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
of digits in a string is closed and checkable, and it does not care what
|
|
60
|
-
language the sentence is in.
|
|
59
|
+
Layer one finds every run of digits in the text and checks it against the values
|
|
60
|
+
you supplied. It works the same whatever language the sentence is in.
|
|
61
61
|
|
|
62
62
|
```ts
|
|
63
63
|
verify('Nur noch 2 übrig', { values: [39] }).quantity.supported; // false
|
|
64
64
|
verify('4,8 von 5', { values: [4.8, 5] }).quantity.supported; // true
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
It
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
67
|
+
It leans toward rejecting. `"Only 2 left"` fails unless you supplied a fact
|
|
68
|
+
carrying 2, even when 2 is also the number of colourways. A wrong rejection
|
|
69
|
+
costs you a duller sentence. A wrong pass costs you a made-up claim.
|
|
70
|
+
|
|
71
|
+
**What it reads.**
|
|
72
|
+
|
|
73
|
+
- Whole numbers and decimals.
|
|
74
|
+
- Both decimal separators: `4.8` and `4,8`.
|
|
75
|
+
- Currency symbols, attached or detached.
|
|
76
|
+
- Percentages.
|
|
77
|
+
- Any Unicode decimal digit. Eastern Arabic `٤٫٨`, Devanagari `४.८` and
|
|
78
|
+
mathematical `𝟰.𝟴` all read as 4.8.
|
|
79
|
+
- Digit grouping in the Western (`12,345,678`), Indian (`1,29,999`), space
|
|
80
|
+
(`1 299`) and Swiss (`1'299`) shapes. The space shape also accepts the
|
|
81
|
+
no-break and narrow no-break spaces.
|
|
82
|
+
|
|
83
|
+
**How it reads a dot or a comma.**
|
|
84
|
+
|
|
85
|
+
- One dot or comma with exactly three digits after it is grouping, never a
|
|
86
|
+
decimal point. `1,299` and `1.299` both read as 1299 and nothing else. So a
|
|
87
|
+
rating of 4.8 can't back a written `4.800`, and a stock count of 12 can't back
|
|
88
|
+
`12.000`.
|
|
89
|
+
- Where grouping is impossible, the decimal reading stands. `1234,567` is
|
|
90
|
+
1234.567.
|
|
91
|
+
- Where the two marks differ, the last one is the decimal point. `1.299,50` is
|
|
92
|
+
1299.5.
|
|
93
|
+
|
|
94
|
+
**What it refuses to read.** Layer one reports these as findings:
|
|
95
|
+
|
|
96
|
+
- A numeric character that isn't a decimal digit, like `½`, `²`, `②` or `Ⅲ`.
|
|
97
|
+
These show up on the page as numbers, and the package has no way to check the
|
|
98
|
+
value they carry.
|
|
99
|
+
- A magnitude mark glued to a run of digits, like `4.8万`, `12M` or `4.8k`. The
|
|
100
|
+
digits may be yours. The multiplier isn't.
|
|
101
|
+
- A run no locale reads as a number, like `24.12.2026` or `3.14.15`. Only a fact
|
|
102
|
+
written exactly the same way backs it.
|
|
103
|
+
|
|
104
|
+
Before any of this, layer one drops characters that take no room on the page. So
|
|
105
|
+
a zero-width space, a soft hyphen, a variation selector, a backspace or a
|
|
106
|
+
combining overline can't split `213` into a `2` and a `13` that you happen to
|
|
107
|
+
have supplied separately. The rule works from Unicode's own categories, so it
|
|
108
|
+
covers all of Unicode with no hand-kept list. It drops:
|
|
109
|
+
|
|
110
|
+
- every format character
|
|
111
|
+
- every default-ignorable code point
|
|
112
|
+
- every control character except tabs and line breaks
|
|
113
|
+
- every mark that attaches to the character before it
|
|
103
114
|
|
|
104
115
|
A character that does take room still separates two numbers. A tab, a line break
|
|
105
|
-
and a Devanagari matra all leave a gap the shopper can see
|
|
106
|
-
is two numbers and a fact of 23
|
|
116
|
+
and a Devanagari matra all leave a gap the shopper can see. So
|
|
117
|
+
`"Only 2\n3 left"` is two numbers, and a fact of 23 doesn't back it.
|
|
107
118
|
|
|
108
|
-
### Layer two
|
|
119
|
+
### Layer two: wording
|
|
109
120
|
|
|
110
|
-
|
|
111
|
-
"selling fast", "in stock", "free delivery", "trending".
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
Layer two looks for claims with no number in them: "cheap", "bargain", "on
|
|
122
|
+
sale", "best-selling", "selling fast", "in stock", "free delivery", "trending".
|
|
123
|
+
There's no value to check, so this layer is a denylist, a list of banned
|
|
124
|
+
phrases. Every result it gives carries `strength: 'best-effort'`:
|
|
114
125
|
|
|
115
126
|
```ts
|
|
116
127
|
verify('Selling fast', { values: [] }).wording;
|
|
117
|
-
// { supported: false, strength: 'best-effort', checked:
|
|
128
|
+
// { supported: false, strength: 'best-effort', checked: 125, findings: [...] }
|
|
118
129
|
```
|
|
119
130
|
|
|
120
|
-
The list also
|
|
121
|
-
neutral"
|
|
122
|
-
|
|
131
|
+
The list also covers generic environmental claims, like "eco-friendly", "carbon
|
|
132
|
+
neutral" and "sustainable". EU Directive 2024/825 applies through each country's
|
|
133
|
+
own law from 27 September 2026. Under it, a generic environmental claim is
|
|
123
134
|
unfair unless the trader can show recognised excellent environmental
|
|
124
|
-
performance
|
|
125
|
-
|
|
135
|
+
performance. A "neutral" claim based on offsetting is unfair outright. The list
|
|
136
|
+
leaves out the bare word "green", because it's too often a colour.
|
|
126
137
|
|
|
127
|
-
Add
|
|
138
|
+
Add phrases for your own language, and name the wording you stand behind:
|
|
128
139
|
|
|
129
140
|
```ts
|
|
130
141
|
verify('Nur noch wenige', { values: [], bannedPhrases: ['nur noch'] });
|
|
131
142
|
verify('In stock, ships today', { values: [], allowedPhrases: ['in stock'] });
|
|
132
143
|
```
|
|
133
144
|
|
|
134
|
-
`bannedPhrases`
|
|
135
|
-
|
|
136
|
-
sitting wholly inside it
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
Without an allowance
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
- **
|
|
146
|
-
`back in stock`, which claims a restock
|
|
147
|
-
- **Allowances
|
|
148
|
-
`
|
|
149
|
-
|
|
150
|
-
- **An allowance forgives whatever
|
|
151
|
-
later version.** `['in stock and ready to ship']` forgives
|
|
152
|
-
today
|
|
153
|
-
wherever your sentence appears. Keep
|
|
154
|
-
- **An allowance
|
|
155
|
-
through one, so `"Free\n\nshipping"` is still caught
|
|
156
|
-
|
|
157
|
-
`"We do not offer"` above a horizontal rule
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
145
|
+
`bannedPhrases` adds to the built-in list. `allowedPhrases` works on the text,
|
|
146
|
+
not on the list. **Where one of your allowed phrases appears, a banned claim
|
|
147
|
+
sitting wholly inside it isn't reported. The same words elsewhere in the copy
|
|
148
|
+
still are.** So `['back in stock']` clears `"Back in stock"` but still catches
|
|
149
|
+
the `in stock` in `"Only 2 in stock, selling fast"`. `checked` counts the whole
|
|
150
|
+
list either way, because an allowance removes nothing from it.
|
|
151
|
+
|
|
152
|
+
Without an allowance, the denylist bans a true statement as hard as a made-up
|
|
153
|
+
one. A shop that really offers free shipping couldn't say so. Before you write
|
|
154
|
+
an allowance, know this:
|
|
155
|
+
|
|
156
|
+
- **A shorter allowance doesn't clear a longer claim.** Allowing `in stock`
|
|
157
|
+
doesn't clear `back in stock`, which also claims a restock.
|
|
158
|
+
- **Allowances don't combine, and they don't join up.** `['sold', 'out']` leaves
|
|
159
|
+
`sold out` caught. `['back in', 'stock']` leaves both `in stock` and
|
|
160
|
+
`back in stock` caught.
|
|
161
|
+
- **An allowance forgives whatever sits inside it, including phrases added to
|
|
162
|
+
the list in a later version.** `['in stock and ready to ship']` forgives
|
|
163
|
+
nothing extra today. The day `ready to ship` joins the list, that allowance
|
|
164
|
+
silences it wherever your sentence appears. Keep allowances tight.
|
|
165
|
+
- **An allowance doesn't cross a line break.** The denylist reads straight
|
|
166
|
+
through one, so `"Free\n\nshipping"` is still caught. An allowance won't
|
|
167
|
+
bridge it. The model decides where paragraph breaks fall. A shopper reads
|
|
168
|
+
`"We do not offer"` above a horizontal rule and a free shipping claim below it
|
|
169
|
+
as two separate things. Both rules lean toward reporting a claim.
|
|
170
|
+
|
|
171
|
+
Before matching, layer two folds the copy and the phrases into one plain form:
|
|
172
|
+
|
|
173
|
+
- Case is ignored.
|
|
174
|
+
- Hyphens become spaces.
|
|
175
|
+
- Accents are composed, so `café` keeps its `é`. This happens before anything is
|
|
176
|
+
dropped. An overline composes with nothing, so it gets dropped and can't hide
|
|
177
|
+
`free` from its own entry.
|
|
178
|
+
- Fullwidth forms are folded to their normal-width versions.
|
|
179
|
+
- Characters that take no room are dropped.
|
|
180
|
+
- A handful of Cyrillic and Greek letters that look like Latin ones are folded
|
|
181
|
+
to Latin.
|
|
182
|
+
- Greek letters lose their accents, so `ΕΥΚΑΙΡΙΑ` matches `ευκαιρία`. Greek
|
|
183
|
+
words told apart only by their accent read as one, so `προσφορά` also catches
|
|
184
|
+
`πρόσφορα`.
|
|
185
|
+
- Spaces are dropped, so `送料 無料` still reads as `送料無料`.
|
|
186
|
+
|
|
187
|
+
A phrase that starts or ends with an ASCII letter or digit won't match glued
|
|
188
|
+
inside a longer word. So `cheap` misses `cheapskate`. A trailing `s` is allowed,
|
|
189
|
+
so `discount` catches `discounts`. This word-gap rule reads the folded letters.
|
|
190
|
+
So a Greek phrase that starts or ends on a letter folded to Latin is held to it
|
|
191
|
+
too: `φθηνό` misses `φθηνότερο`. List each form you want caught.
|
|
177
192
|
|
|
178
193
|
## The result
|
|
179
194
|
|
|
@@ -198,11 +213,10 @@ interface Finding {
|
|
|
198
213
|
}
|
|
199
214
|
```
|
|
200
215
|
|
|
201
|
-
`checked`
|
|
202
|
-
|
|
203
|
-
the
|
|
204
|
-
|
|
205
|
-
read, so `"Only ② left"` is a finding rather than a silent zero.
|
|
216
|
+
`checked` tells you how much a layer actually looked at. When `quantity.checked`
|
|
217
|
+
is 0, the text held no numeral and layer one proved nothing about it. Layer one
|
|
218
|
+
counts the numerals it refuses to read, so `"Only ② left"` gives a finding
|
|
219
|
+
instead of a silent zero.
|
|
206
220
|
|
|
207
221
|
## Facts
|
|
208
222
|
|
|
@@ -214,54 +228,46 @@ interface Facts {
|
|
|
214
228
|
}
|
|
215
229
|
```
|
|
216
230
|
|
|
217
|
-
`values` is
|
|
218
|
-
|
|
219
|
-
numeral in each
|
|
220
|
-
|
|
221
|
-
A number is
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
product fact under any reading, so it
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
JavaScript number fits well inside the limit
|
|
231
|
+
`values` is one flat list with no types. Write each value the way your own
|
|
232
|
+
system writes it, like `39`, `'$1,299.00'`, `'4,8'` or `'ships 13 March'`. Every
|
|
233
|
+
numeral in each value becomes a supported value.
|
|
234
|
+
|
|
235
|
+
A number is written out in plain digits first, whatever `String` would have made
|
|
236
|
+
of it. So a fact of `1e21` backs `'1,000,000,000,000,000,000,000'` and backs
|
|
237
|
+
neither `1` nor `21`. A string that holds nothing but a number in exponent
|
|
238
|
+
notation is written out the same way. `String`, `JSON.stringify`, spreadsheet
|
|
239
|
+
exports and APIs that write 64-bit values as strings all produce such strings.
|
|
240
|
+
You didn't pick those digits. Any other string is read exactly as
|
|
241
|
+
you typed it, so `'SKU AX-220e5'` still supplies 220 and 5.
|
|
242
|
+
|
|
243
|
+
A number in exponent notation that would run past a thousand digits when written
|
|
244
|
+
out backs no numeral at all.
|
|
245
|
+
The package checks the length before it writes anything out. `'1e1000'` isn't a
|
|
246
|
+
product fact under any reading, so it hits the limit and is refused. The limit
|
|
247
|
+
stops the extreme cases. `'1e2000000000'` is twelve characters, but writing it
|
|
248
|
+
out would take more memory than the process has. Every finite
|
|
249
|
+
JavaScript number fits well inside the limit. The longest is `5e-324`, at 326
|
|
236
250
|
characters.
|
|
237
251
|
|
|
238
|
-
Past 2^53 a number has lost its exact digits before it ever reaches
|
|
239
|
-
`JSON.parse` turns `9007199254740993` into `...92
|
|
240
|
-
bigint or
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
- **Supply only numbers you are happy to see anywhere.** A SKU of `AT-2199` in
|
|
259
|
-
`values` makes `"Was $2,199"` a supported sentence. A listing date makes its
|
|
260
|
-
day number a supported sales figure. If a number in your record is not a fact
|
|
261
|
-
about the product a shopper would read, leave it out.
|
|
262
|
-
- **`values` is a trust boundary.** On a marketplace the product title is
|
|
263
|
-
written by the seller, not by you. Putting it in `values` hands the model
|
|
264
|
-
every number the seller chose to type. Supply fields you author.
|
|
252
|
+
Past 2^53, a number has lost its exact digits before it ever reaches this
|
|
253
|
+
package. `JSON.parse` turns `9007199254740993` into `...92`. Pass a long id as a
|
|
254
|
+
bigint or a string.
|
|
255
|
+
|
|
256
|
+
A null or undefined field backs no numeral and doesn't throw, so one empty field
|
|
257
|
+
doesn't break the whole call. Anything else is read through `String()` and
|
|
258
|
+
supplies whatever digits that gives. For a `Date`, that's the day, the year and
|
|
259
|
+
every digit of the clock. So a `listedAt` passed straight through isn't ignored.
|
|
260
|
+
It's one of the hazards under [What it cannot catch](#what-it-cannot-catch).
|
|
261
|
+
|
|
262
|
+
That section also lists what a flat list costs you. Two habits keep the cost
|
|
263
|
+
down:
|
|
264
|
+
|
|
265
|
+
- **Only supply numbers you're happy to see anywhere.** A SKU of `AT-2199` in
|
|
266
|
+
`values` makes `"Was $2,199"` a supported sentence. If a number in your record
|
|
267
|
+
isn't a fact about the product that a shopper would read, leave it out.
|
|
268
|
+
- **Treat `values` as a trust boundary.** On a marketplace, the seller writes
|
|
269
|
+
the product title, not you. Putting it in `values` hands the model every
|
|
270
|
+
number the seller chose to type. Supply only fields you write.
|
|
265
271
|
|
|
266
272
|
## Many fields, one product
|
|
267
273
|
|
|
@@ -278,103 +284,99 @@ result.fields; // [{ field, result }, ...] in the order the fields were given
|
|
|
278
284
|
result.acrossFields; // the wording layer over every field read as one
|
|
279
285
|
```
|
|
280
286
|
|
|
281
|
-
The facts are read once and reused. A card
|
|
282
|
-
other, and the model picks
|
|
283
|
-
`{ badge: 'Free', delivery: 'delivery on every order' }`
|
|
284
|
-
shopper
|
|
285
|
-
text and counts toward `supported`.
|
|
287
|
+
The facts are read once and reused for every field. A card shows its fields next
|
|
288
|
+
to each other, and the model picks where one field ends and the next begins. So
|
|
289
|
+
`{ badge: 'Free', delivery: 'delivery on every order' }` reads as one sentence
|
|
290
|
+
to a shopper, though neither field carries the claim alone. `acrossFields`
|
|
291
|
+
checks the joined text, and it counts toward `supported`.
|
|
286
292
|
|
|
287
293
|
## What it cannot catch
|
|
288
294
|
|
|
289
|
-
This is the honest list.
|
|
290
|
-
|
|
291
295
|
### Claims with a number in them
|
|
292
296
|
|
|
293
|
-
- **A number that is real but means something else.** This is the
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
`"nur 4,80 €"` passes as a price, because 4.80 and 4.8 are one number.
|
|
297
|
+
- **A number that is real but means something else.** This is the biggest gap in
|
|
298
|
+
layer one. Supply a price of 39, and `"39 sold in the last hour"`,
|
|
299
|
+
`"Save $39 today"` and `"Under $39"` all pass. Supply a rating scale of 5, and
|
|
300
|
+
`"Only 5 left"` passes. Supply a rating of 4.8, and a German `"nur 4,80 €"`
|
|
301
|
+
passes as a price, because 4.80 and 4.8 are the same number.
|
|
299
302
|
- **Numbers written as words, in any script.** `"Only two left"` has no digit in
|
|
300
|
-
it, so layer one never sees it.
|
|
301
|
-
character rather than a digit
|
|
302
|
-
dual ending and there is no numeral at all. `quantity.checked` is 0
|
|
303
|
-
three, and that
|
|
304
|
-
- **Magnitudes written as words.**
|
|
305
|
-
`4.8 millions`, `4,8 mil`, `4.8 लाख`
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
digits are yours and the currency symbol is not a digit. So does
|
|
303
|
+
it, so layer one never sees it. Layer one also misses `残り二点`, where 二 is a
|
|
304
|
+
character rather than a digit. It misses Arabic `قطعتين` too, where "two" is
|
|
305
|
+
the noun's dual ending and there is no numeral at all. `quantity.checked` is 0
|
|
306
|
+
for all three, and that's the only signal you get.
|
|
307
|
+
- **Magnitudes written as words.** The multiplier in `4,8 Millionen`,
|
|
308
|
+
`4.8 millions`, `4,8 mil`, `4.8 लाख` or `٤٫٨ مليون` isn't caught.
|
|
309
|
+
- **The unit on the number.** `¥39.99` passes against a price of $39.99, because
|
|
310
|
+
the digits are yours and the currency symbol isn't a digit. So does
|
|
309
311
|
`"39,99 Cent"`. Layer one checks the number, never what it is a number of.
|
|
310
312
|
- **A number your facts happen to contain.** A fact of `'2026-09-13'` makes
|
|
311
|
-
2026, 9 and 13 supported
|
|
312
|
-
- **Where in the text a token was.** A finding names the token
|
|
313
|
+
2026, 9 and 13 supported anywhere in the text, including as a stock count.
|
|
314
|
+
- **Where in the text a token was.** A finding names the token but not its
|
|
315
|
+
position.
|
|
313
316
|
|
|
314
317
|
### Claims with no number in them
|
|
315
318
|
|
|
316
|
-
- **Wording, in general.**
|
|
317
|
-
|
|
318
|
-
`"
|
|
319
|
-
and the list will never be finished.
|
|
319
|
+
- **Wording, in general.** There are endless ways to word a claim, and a list
|
|
320
|
+
can't hold them all. A careful rewording gets past layer two, like
|
|
321
|
+
`"Half of what it was"` for half price, or `"送料は無料"` with a particle dropped
|
|
322
|
+
in. Entries are added as they're found, and the list will never be finished.
|
|
320
323
|
- **Your language, until you give it one.** Every built-in phrase is English.
|
|
321
324
|
`"Gratis Versand"`, `"Livraison offerte"` and `"شحن مجاني"` all pass by
|
|
322
|
-
default.
|
|
323
|
-
- **Negation.** The list matches substrings, so
|
|
324
|
-
product"` is caught as a sale claim
|
|
325
|
-
`no
|
|
326
|
-
|
|
327
|
-
- **A character that folds into your allowance.**
|
|
328
|
-
|
|
329
|
-
words. Allow `no sale` and `"№ SALE"` passes, because U+2116
|
|
330
|
-
|
|
331
|
-
decoration
|
|
332
|
-
|
|
333
|
-
- **A letter glued to the end of a phrase.** `"FREE SHIPPINGᵃ see terms"`
|
|
334
|
-
reported. The superscript folds to an `a`, which glues
|
|
325
|
+
default. Add them with `bannedPhrases`. That list is yours to keep.
|
|
326
|
+
- **Negation.** The list matches substrings, so
|
|
327
|
+
`"there is no sale on this product"` is caught as a sale claim. No rule reads
|
|
328
|
+
the `no`. `allowedPhrases: ['no sale']` lets that sentence pass. That
|
|
329
|
+
allowance is narrow, so a `sale` elsewhere in the same text still fails.
|
|
330
|
+
- **A character that folds into your allowance.** Allowances match the copy
|
|
331
|
+
after the folding described under layer two. Anything that folds to your words
|
|
332
|
+
counts as your words. Allow `no sale` and `"№ SALE"` passes, because U+2116
|
|
333
|
+
folds to `no`. A superscript `ⁿᵒ` does the same. The shopper reads the numero
|
|
334
|
+
sign as decoration, but the package reads it as a word. Write allowances the
|
|
335
|
+
way a shop really writes them.
|
|
336
|
+
- **A letter glued to the end of a phrase.** `"FREE SHIPPINGᵃ see terms"` isn't
|
|
337
|
+
reported. The superscript folds to an `a`, which glues onto `shipping`. The
|
|
335
338
|
word-gap rule that stops `cheap` matching `cheapskate` then treats the whole
|
|
336
|
-
thing as a longer word.
|
|
339
|
+
thing as a longer word. This holds for every entry on the list. A footnote
|
|
337
340
|
marker is exactly how a real shop writes one.
|
|
338
341
|
|
|
339
342
|
### Preconditions the package cannot check for you
|
|
340
343
|
|
|
341
344
|
- **The text you check has to be the text you render.** `verify` sees a string.
|
|
342
|
-
|
|
343
|
-
`2` into a 2
|
|
344
|
-
are different documents
|
|
345
|
+
Something downstream may decode it, like an HTML sink turning `½` into ½
|
|
346
|
+
or `2` into a 2. Then the string you checked and the string on the page
|
|
347
|
+
are different documents. The guarantee covers only the one you checked.
|
|
345
348
|
- **The font has to agree with Unicode about what renders as nothing.** The
|
|
346
|
-
|
|
347
|
-
those a visible glyph the two disagree
|
|
348
|
-
people really paste as a blank
|
|
349
|
-
font that widens it shows the shopper `1 3`.
|
|
350
|
-
- **Everything that
|
|
351
|
-
compatibility, a certification, a warranty condition or a person is
|
|
352
|
-
|
|
349
|
+
package drops what Unicode marks as taking no room. Where a font gives one of
|
|
350
|
+
those characters a visible glyph, the two disagree. U+3164 HANGUL FILLER is
|
|
351
|
+
the one people really paste as a blank. Layer one reads `"1<U+3164>3"` as 13,
|
|
352
|
+
while a font that widens it shows the shopper `1 3`.
|
|
353
|
+
- **Everything that isn't a number or a phrase.** A claim about materials,
|
|
354
|
+
compatibility, a certification, a warranty condition or a person is out of
|
|
355
|
+
scope.
|
|
353
356
|
|
|
354
357
|
## What it over-rejects
|
|
355
358
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
- **Every incidental number has to be in `values`.**
|
|
359
|
-
|
|
360
|
-
denominator of a rating scale. None of them
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
decimal
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
359
|
+
Leaning toward rejection has a cost:
|
|
360
|
+
|
|
361
|
+
- **Every incidental number has to be in `values`.** That means a VAT rate, a
|
|
362
|
+
size, a care temperature, a number from the product specification, a support
|
|
363
|
+
phone number, a founding year, the denominator of a rating scale. None of them
|
|
364
|
+
are claims, but all of them are digits. Copy that mentions one you didn't
|
|
365
|
+
supply is rejected.
|
|
366
|
+
- **Three digits after a dot or comma are always grouping.** A real
|
|
367
|
+
three-decimal number, like a weight or a price in a currency with three
|
|
368
|
+
decimal places, can't be written with a dot or a comma. `1.299 kg` reads
|
|
369
|
+
as 1299. Supply it and write it the same way.
|
|
370
|
+
- **Numeric characters that aren't digits are refused outright.** `2 m²` and
|
|
371
|
+
`½ inch` fail.
|
|
368
372
|
- **A magnitude letter glued to a digit is refused.** `4K display` fails.
|
|
369
|
-
`4 K display` and `1kg`
|
|
373
|
+
`4 K display` and `1kg` don't, because a letter followed by another letter is
|
|
370
374
|
a unit, not a multiplier.
|
|
371
|
-
- **
|
|
372
|
-
is
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
bill for laying facts out positionally, and a lab or bulk-goods shop that
|
|
377
|
-
prints the value the way its own data prints it pays it.
|
|
375
|
+
- **Scientific notation in the copy can never be supported.** A fact of `1e-7`
|
|
376
|
+
is written out as `0.0000001`, and the text `"1e-7 g"` reads as the two
|
|
377
|
+
numerals 1 and 7, so the two never meet. `"0.0000001 g"` passes. That's the
|
|
378
|
+
cost of writing facts out in plain digits. It falls on a lab or bulk-goods
|
|
379
|
+
shop that prints a value the way its own data prints it.
|
|
378
380
|
|
|
379
381
|
## Licence
|
|
380
382
|
|
package/dist/phrases.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EA8H3C,CAAC;AAsEF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,MAAM,CAOnE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,MAAM,CAMtE;AAQD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYnD;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CA2BhE"}
|
package/dist/phrases.js
CHANGED
|
@@ -74,6 +74,8 @@ export const BANNED_PHRASES = [
|
|
|
74
74
|
'most popular',
|
|
75
75
|
'popular pick',
|
|
76
76
|
'top pick',
|
|
77
|
+
'strongest all round pick',
|
|
78
|
+
'strongest all around pick',
|
|
77
79
|
'top rated',
|
|
78
80
|
'highly rated',
|
|
79
81
|
'highest rated',
|
|
@@ -90,6 +92,7 @@ export const BANNED_PHRASES = [
|
|
|
90
92
|
'people are viewing',
|
|
91
93
|
'others are buying',
|
|
92
94
|
'people are buying',
|
|
95
|
+
'other shoppers',
|
|
93
96
|
'in other carts',
|
|
94
97
|
'in other baskets',
|
|
95
98
|
'money back',
|
|
@@ -169,12 +172,17 @@ const CAPITALS = {
|
|
|
169
172
|
Ϻ: 'm',
|
|
170
173
|
Ϝ: 'f',
|
|
171
174
|
Ԍ: 'g',
|
|
175
|
+
Ѡ: 'w',
|
|
176
|
+
Ϙ: 'q',
|
|
177
|
+
Ϗ: 'k',
|
|
178
|
+
Ͳ: 't',
|
|
172
179
|
};
|
|
173
180
|
const BEFORE_NFKC = {
|
|
174
181
|
ϲ: 'c',
|
|
175
182
|
Ϲ: 'c',
|
|
176
183
|
};
|
|
177
184
|
const LINE_BREAK = /[\n\v\f\r\u0085\u2028\u2029]/;
|
|
185
|
+
const GREEK_MARKS = /(\p{Script=Greek})[\p{Mn}\p{Me}]+/gu;
|
|
178
186
|
function foldChars(text, table) {
|
|
179
187
|
let folded = '';
|
|
180
188
|
for (const char of text)
|
|
@@ -184,7 +192,7 @@ function foldChars(text, table) {
|
|
|
184
192
|
export function foldLookalikes(text, byLook = false) {
|
|
185
193
|
const compatible = foldChars(stripInvisible(text), BEFORE_NFKC).normalize('NFKC');
|
|
186
194
|
const cased = byLook ? foldChars(compatible, CAPITALS) : compatible;
|
|
187
|
-
const lowered = stripMarks(cased.toLowerCase().normalize('NFC'));
|
|
195
|
+
const lowered = stripMarks(cased.toLowerCase().normalize('NFD').replace(GREEK_MARKS, '$1').normalize('NFC'));
|
|
188
196
|
return foldChars(lowered, CONFUSABLES);
|
|
189
197
|
}
|
|
190
198
|
export function normalisePhrasing(text, byLook = false) {
|
package/dist/phrases.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phrases.js","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"phrases.js","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,GAAG,aAAa,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC9C,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,YAAY,EAAE,WAAW,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,GAAG,aAAa,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,WAAW,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC9C,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,YAAY,EAAE,WAAW,CAAC;CAC3B;AAmJD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,CAE/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,WAAW,CAgBtF"}
|
package/dist/verify.js
CHANGED
|
@@ -78,7 +78,7 @@ function isInside(spans, hit) {
|
|
|
78
78
|
}
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
|
-
function
|
|
81
|
+
function forgivenInAnyReading(scans, allowed) {
|
|
82
82
|
const forgiven = [];
|
|
83
83
|
for (const scan of scans) {
|
|
84
84
|
for (const phrase of allowed) {
|
|
@@ -101,10 +101,12 @@ function isCaught(scans, phrase, forgiven) {
|
|
|
101
101
|
}
|
|
102
102
|
function checkWording(text, prepared) {
|
|
103
103
|
const { banned, allowed } = prepared;
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
const readings = new Set([normalisePhrasing(text), normalisePhrasing(text, true)]);
|
|
105
|
+
if (text.includes('\u04C0')) {
|
|
106
|
+
readings.add(normalisePhrasing(text.replaceAll('\u04C0', '\u04CF'), true));
|
|
107
|
+
}
|
|
108
|
+
const scans = [...readings].map((reading) => indexPhrasing(reading));
|
|
109
|
+
const forgiven = forgivenInAnyReading(scans, allowed);
|
|
108
110
|
const findings = [];
|
|
109
111
|
for (const phrase of banned) {
|
|
110
112
|
if (!isCaught(scans, phrase, forgiven))
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/phrases.ts
CHANGED
|
@@ -75,6 +75,8 @@ export const BANNED_PHRASES: readonly string[] = [
|
|
|
75
75
|
'most popular',
|
|
76
76
|
'popular pick',
|
|
77
77
|
'top pick',
|
|
78
|
+
'strongest all round pick',
|
|
79
|
+
'strongest all around pick',
|
|
78
80
|
'top rated',
|
|
79
81
|
'highly rated',
|
|
80
82
|
'highest rated',
|
|
@@ -91,6 +93,7 @@ export const BANNED_PHRASES: readonly string[] = [
|
|
|
91
93
|
'people are viewing',
|
|
92
94
|
'others are buying',
|
|
93
95
|
'people are buying',
|
|
96
|
+
'other shoppers',
|
|
94
97
|
'in other carts',
|
|
95
98
|
'in other baskets',
|
|
96
99
|
'money back',
|
|
@@ -172,6 +175,10 @@ const CAPITALS: Record<string, string> = {
|
|
|
172
175
|
Ϻ: 'm',
|
|
173
176
|
Ϝ: 'f',
|
|
174
177
|
Ԍ: 'g',
|
|
178
|
+
Ѡ: 'w',
|
|
179
|
+
Ϙ: 'q',
|
|
180
|
+
Ϗ: 'k',
|
|
181
|
+
Ͳ: 't',
|
|
175
182
|
};
|
|
176
183
|
|
|
177
184
|
const BEFORE_NFKC: Record<string, string> = {
|
|
@@ -181,6 +188,8 @@ const BEFORE_NFKC: Record<string, string> = {
|
|
|
181
188
|
|
|
182
189
|
const LINE_BREAK = /[\n\v\f\r\u0085\u2028\u2029]/;
|
|
183
190
|
|
|
191
|
+
const GREEK_MARKS = /(\p{Script=Greek})[\p{Mn}\p{Me}]+/gu;
|
|
192
|
+
|
|
184
193
|
function foldChars(text: string, table: Record<string, string>): string {
|
|
185
194
|
let folded = '';
|
|
186
195
|
for (const char of text) folded += table[char] ?? char;
|
|
@@ -190,7 +199,9 @@ function foldChars(text: string, table: Record<string, string>): string {
|
|
|
190
199
|
export function foldLookalikes(text: string, byLook = false): string {
|
|
191
200
|
const compatible = foldChars(stripInvisible(text), BEFORE_NFKC).normalize('NFKC');
|
|
192
201
|
const cased = byLook ? foldChars(compatible, CAPITALS) : compatible;
|
|
193
|
-
const lowered = stripMarks(
|
|
202
|
+
const lowered = stripMarks(
|
|
203
|
+
cased.toLowerCase().normalize('NFD').replace(GREEK_MARKS, '$1').normalize('NFC'),
|
|
204
|
+
);
|
|
194
205
|
return foldChars(lowered, CONFUSABLES);
|
|
195
206
|
}
|
|
196
207
|
|
package/src/verify.ts
CHANGED
|
@@ -135,7 +135,7 @@ function isInside(spans: Span[], hit: Span): boolean {
|
|
|
135
135
|
return false;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function
|
|
138
|
+
function forgivenInAnyReading(scans: Indexed[], allowed: readonly string[]): Span[] {
|
|
139
139
|
const forgiven: Span[] = [];
|
|
140
140
|
for (const scan of scans) {
|
|
141
141
|
for (const phrase of allowed) {
|
|
@@ -158,12 +158,13 @@ function isCaught(scans: Indexed[], phrase: string, forgiven: Span[]): boolean {
|
|
|
158
158
|
|
|
159
159
|
function checkWording(text: string, prepared: PreparedFacts): LayerReport {
|
|
160
160
|
const { banned, allowed } = prepared;
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
const readings = new Set([normalisePhrasing(text), normalisePhrasing(text, true)]);
|
|
162
|
+
if (text.includes('\u04C0')) {
|
|
163
|
+
readings.add(normalisePhrasing(text.replaceAll('\u04C0', '\u04CF'), true));
|
|
164
|
+
}
|
|
165
|
+
const scans = [...readings].map((reading) => indexPhrasing(reading));
|
|
165
166
|
|
|
166
|
-
const forgiven =
|
|
167
|
+
const forgiven = forgivenInAnyReading(scans, allowed);
|
|
167
168
|
|
|
168
169
|
const findings: Finding[] = [];
|
|
169
170
|
for (const phrase of banned) {
|