@mailwoman/kind-classifier 10.0.0 → 10.1.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 +13 -12
- package/lib/classify.ts +33 -34
- package/lib/index.ts +1 -8
- package/lib/intent/markers.ts +28 -25
- package/lib/intent/rules.ts +82 -83
- package/lib/poi.ts +96 -84
- package/lib/rules.ts +141 -79
- package/out/classify.d.ts +18 -20
- package/out/classify.d.ts.map +1 -1
- package/out/classify.js +28 -31
- package/out/classify.js.map +1 -1
- package/out/index.d.ts +1 -8
- package/out/index.d.ts.map +1 -1
- package/out/index.js +1 -8
- package/out/index.js.map +1 -1
- package/out/intent/markers.d.ts +17 -18
- package/out/intent/markers.d.ts.map +1 -1
- package/out/intent/markers.js +24 -23
- package/out/intent/markers.js.map +1 -1
- package/out/intent/rules.d.ts +31 -35
- package/out/intent/rules.d.ts.map +1 -1
- package/out/intent/rules.js +82 -83
- package/out/intent/rules.js.map +1 -1
- package/out/poi.d.ts +64 -56
- package/out/poi.d.ts.map +1 -1
- package/out/poi.js +60 -50
- package/out/poi.js.map +1 -1
- package/out/rules.d.ts +23 -40
- package/out/rules.d.ts.map +1 -1
- package/out/rules.js +117 -80
- package/out/rules.js.map +1 -1
- package/package.json +15 -50
package/lib/rules.ts
CHANGED
|
@@ -3,66 +3,57 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Rule-based
|
|
7
|
-
* and returns a confidence
|
|
8
|
-
*
|
|
9
|
-
* Bitter-lesson-safe: only universal structural patterns — no place-name dictionaries. ~1 small
|
|
10
|
-
* regex set per new locale, not 50K dictionary entries.
|
|
6
|
+
* Rule-based `QueryKind` scorers. Each scorer reads structural patterns in the normalized input and
|
|
7
|
+
* its QueryShape and returns a confidence from 0 to 1.
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
|
-
import { NAME_PRONE_US_SUFFIXES, US_STREET_SUFFIX_LOOKUP } from "@mailwoman/codex/us/street
|
|
10
|
+
import { NAME_PRONE_US_SUFFIXES, US_STREET_SUFFIX_LOOKUP } from "@mailwoman/codex/us/street/suffix"
|
|
14
11
|
import type { NormalizedInputLite, QueryShapeSegmentsView as QueryShapeLike } from "@mailwoman/query-shape"
|
|
12
|
+
import { classifyTokens, foldInputClass } from "@mailwoman/query-shape/character-class"
|
|
15
13
|
import { isPostcodeFormat } from "@mailwoman/query-shape/known-formats"
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
* and belongs to the structured-address scorer.
|
|
15
|
+
* Maximum length for a bare venue or landmark query.
|
|
19
16
|
*/
|
|
20
17
|
const MAX_LANDMARK_LENGTH = 50
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
|
-
*
|
|
20
|
+
* Maximum length for a bare postcode, including an optional country prefix and separators.
|
|
24
21
|
*/
|
|
25
22
|
const MAX_POSTCODE_ONLY_LENGTH = 16
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
|
-
*
|
|
29
|
-
* else too — a locality, a street — and another kind should win.
|
|
25
|
+
* Minimum fraction of the input that a postcode must cover for `postcode_only`.
|
|
30
26
|
*/
|
|
31
27
|
const MIN_POSTCODE_COVERAGE = 0.7
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* Exported so `intent-rules.ts`'s `bare_toponym` can share the exact same ceiling. Sharing it is what makes
|
|
37
|
-
* "bare_toponym is a strict refinement of locality_only" a structural property rather than two numbers that happen to
|
|
38
|
-
* agree today.
|
|
30
|
+
* Maximum length for a bare locality.
|
|
31
|
+
* The `bare_toponym` intent rule uses the same limit.
|
|
39
32
|
*/
|
|
40
33
|
export const MAX_LOCALITY_ONLY_LENGTH = 30
|
|
41
34
|
|
|
42
35
|
/**
|
|
43
|
-
*
|
|
44
|
-
* and the phrase is more likely a full address line.
|
|
36
|
+
* Minimum word count for a short venue phrase.
|
|
45
37
|
*/
|
|
46
38
|
const VENUE_PHRASE_MIN_WORDS = 2
|
|
47
39
|
|
|
48
40
|
/**
|
|
49
|
-
*
|
|
41
|
+
* Maximum word count for a short venue phrase.
|
|
50
42
|
*/
|
|
51
43
|
const VENUE_PHRASE_MAX_WORDS = 4
|
|
52
44
|
|
|
53
45
|
/**
|
|
54
|
-
*
|
|
46
|
+
* Maximum word count for a longer single-segment venue phrase.
|
|
55
47
|
*/
|
|
56
48
|
const LONG_VENUE_PHRASE_MAX_WORDS = 6
|
|
57
49
|
|
|
58
50
|
/**
|
|
59
|
-
* Length at which
|
|
60
|
-
* alphanumeric inputs score lower because they are as likely to be a unit number.
|
|
51
|
+
* Length at which single-segment alphanumeric input scores as a likely structured address.
|
|
61
52
|
*/
|
|
62
53
|
const ALPHANUMERIC_POSTCODE_MIN_LENGTH = 15
|
|
63
54
|
|
|
64
55
|
/**
|
|
65
|
-
*
|
|
56
|
+
* Phrases that signal a relative landmark description.
|
|
66
57
|
*/
|
|
67
58
|
const LANDMARK_LEADERS = [
|
|
68
59
|
"behind",
|
|
@@ -77,32 +68,30 @@ const LANDMARK_LEADERS = [
|
|
|
77
68
|
]
|
|
78
69
|
|
|
79
70
|
/**
|
|
80
|
-
*
|
|
71
|
+
* Patterns that signal a street intersection.
|
|
81
72
|
*/
|
|
82
73
|
const INTERSECTION_PATTERNS = [
|
|
83
74
|
/\bcorner of\b/i,
|
|
84
75
|
/\bintersection of\b/i,
|
|
85
76
|
/\bat the corner of\b/i,
|
|
86
|
-
// "5th and Main", "Broadway & 42nd"
|
|
77
|
+
// Examples: "5th and Main", "Broadway & 42nd".
|
|
87
78
|
/\b\w+(?:st|nd|rd|th|street|ave|avenue|blvd|boulevard|road|rd|lane|ln)?\s+(?:and|&|@)\s+\w+/i,
|
|
88
79
|
]
|
|
89
80
|
|
|
90
81
|
/**
|
|
91
|
-
*
|
|
92
|
-
* covers all locale variants (US "PO Box 123", FR "BP 42", etc.).
|
|
82
|
+
* Scores input in which QueryShape detected a PO Box format.
|
|
93
83
|
*/
|
|
94
84
|
export function scorePoBox(_input: NormalizedInputLite, shape: QueryShapeLike): number {
|
|
95
85
|
const hit = shape.knownFormats.find((f) => f.format === "po_box")
|
|
96
86
|
|
|
97
87
|
if (!hit) return 0
|
|
98
88
|
|
|
99
|
-
//
|
|
100
|
-
// both rules fire on the same input.
|
|
89
|
+
// The bonus makes a PO Box win a tie with a structured address.
|
|
101
90
|
return Math.min(1, hit.confidence + 0.1)
|
|
102
91
|
}
|
|
103
92
|
|
|
104
93
|
/**
|
|
105
|
-
*
|
|
94
|
+
* Scores street-intersection phrasing.
|
|
106
95
|
*/
|
|
107
96
|
export function scoreIntersection(input: NormalizedInputLite, _shape: QueryShapeLike): number {
|
|
108
97
|
const text = input.normalized
|
|
@@ -115,8 +104,7 @@ export function scoreIntersection(input: NormalizedInputLite, _shape: QueryShape
|
|
|
115
104
|
}
|
|
116
105
|
|
|
117
106
|
/**
|
|
118
|
-
*
|
|
119
|
-
* location relative to another place.
|
|
107
|
+
* Scores input that begins with a relative-landmark phrase.
|
|
120
108
|
*/
|
|
121
109
|
export function scoreLandmark(input: NormalizedInputLite, _shape: QueryShapeLike): number {
|
|
122
110
|
const lc = input.normalized.toLowerCase().trim()
|
|
@@ -129,19 +117,14 @@ export function scoreLandmark(input: NormalizedInputLite, _shape: QueryShapeLike
|
|
|
129
117
|
}
|
|
130
118
|
|
|
131
119
|
/**
|
|
132
|
-
*
|
|
133
|
-
* matters: a trailing comma or doubled separator otherwise yields an empty string that inflates the word count and
|
|
134
|
-
* falsifies every-word predicates like the proper-case check below.
|
|
120
|
+
* Splits text on whitespace and commas and drops empty tokens.
|
|
135
121
|
*/
|
|
136
122
|
export function wordsOf(text: string): string[] {
|
|
137
|
-
return text.split(/[\s,]+/).filter((word) => word.length
|
|
123
|
+
return text.split(/[\s,]+/).filter((word) => word.length)
|
|
138
124
|
}
|
|
139
125
|
|
|
140
126
|
/**
|
|
141
|
-
*
|
|
142
|
-
* `@mailwoman/codex`, minus its curated name-prone canonicals (PARK, FIELD, HILL, LAKE, …). Those double as ordinary
|
|
143
|
-
* proper-name heads ("Wrigley Field", "Menlo Park"), and disqualifying on them would reject the very venue and place
|
|
144
|
-
* names the rules below exist to capture. Shared with `intent-rules.ts` so both rule sets read one definition.
|
|
127
|
+
* Reports whether a word is a USPS street suffix that rarely appears in place names.
|
|
145
128
|
*/
|
|
146
129
|
export function isDisqualifyingStreetSuffix(word: string): boolean {
|
|
147
130
|
const canonical = US_STREET_SUFFIX_LOOKUP.get(word.trim().toLowerCase())
|
|
@@ -150,11 +133,80 @@ export function isDisqualifyingStreetSuffix(word: string): boolean {
|
|
|
150
133
|
}
|
|
151
134
|
|
|
152
135
|
/**
|
|
153
|
-
*
|
|
154
|
-
|
|
136
|
+
* Removes postcode spans in the final segment and collapses the remaining separators.
|
|
137
|
+
*/
|
|
138
|
+
export function withoutPostcodeSpans(text: string, shape: QueryShapeLike): string {
|
|
139
|
+
const merged = mergedPostcodeSpans(shape)
|
|
140
|
+
|
|
141
|
+
if (!merged.length) return text
|
|
142
|
+
|
|
143
|
+
let remainder = text
|
|
144
|
+
|
|
145
|
+
// The loop removes spans from last to first.
|
|
146
|
+
// This order keeps the remaining offsets valid.
|
|
147
|
+
for (let index = merged.length - 1; index >= 0; index--) {
|
|
148
|
+
const span = merged[index]!
|
|
149
|
+
|
|
150
|
+
remainder = remainder.slice(0, span.start) + remainder.slice(span.end)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return remainder
|
|
154
|
+
.replaceAll(/[\s,]+/gu, " ")
|
|
155
|
+
.replace(/[\s,]+$/u, "")
|
|
156
|
+
.trim()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Reports whether text contains a Unicode letter.
|
|
155
161
|
*
|
|
156
|
-
*
|
|
157
|
-
|
|
162
|
+
* By itself, the `alpha` character class can match punctuation-only input.
|
|
163
|
+
*/
|
|
164
|
+
export function carriesLetter(text: string): boolean {
|
|
165
|
+
return /\p{L}/u.test(text)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Merges overlapping postcode hits in the final segment into sorted, disjoint spans.
|
|
170
|
+
*/
|
|
171
|
+
function mergedPostcodeSpans(shape: QueryShapeLike): Array<{ start: number; end: number }> {
|
|
172
|
+
// Postcode hits in earlier segments are often house numbers.
|
|
173
|
+
const tail = shape.segments?.at(-1)?.span
|
|
174
|
+
|
|
175
|
+
if (!tail) return []
|
|
176
|
+
|
|
177
|
+
const merged: Array<{ start: number; end: number }> = []
|
|
178
|
+
|
|
179
|
+
for (const hit of shape.knownFormats) {
|
|
180
|
+
if (!isPostcodeFormat(hit.format)) continue
|
|
181
|
+
|
|
182
|
+
if (hit.span.start < tail.start || hit.span.end > tail.end) continue
|
|
183
|
+
|
|
184
|
+
merged.push({ start: hit.span.start, end: hit.span.end })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (merged.length < 2) return merged
|
|
188
|
+
|
|
189
|
+
merged.sort((left, right) => left.start - right.start)
|
|
190
|
+
|
|
191
|
+
const disjoint: Array<{ start: number; end: number }> = [merged[0]!]
|
|
192
|
+
|
|
193
|
+
for (const span of merged.slice(1)) {
|
|
194
|
+
const last = disjoint.at(-1)!
|
|
195
|
+
|
|
196
|
+
if (span.start <= last.end) {
|
|
197
|
+
last.end = Math.max(last.end, span.end)
|
|
198
|
+
|
|
199
|
+
continue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
disjoint.push(span)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return disjoint
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Scores short capitalized venue names that have no street suffix or recognized format.
|
|
158
210
|
*/
|
|
159
211
|
export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShapeLike): number {
|
|
160
212
|
const text = input.normalized.trim()
|
|
@@ -162,34 +214,28 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
|
|
|
162
214
|
|
|
163
215
|
if (len === 0 || len > MAX_LANDMARK_LENGTH) return 0
|
|
164
216
|
|
|
165
|
-
// Must have at least one capitalized word.
|
|
166
217
|
if (!/[A-Z]/.test(text)) return 0
|
|
167
218
|
|
|
168
|
-
// Reject if any known postcode format hit exists.
|
|
169
219
|
if (shape.knownFormats.length) return 0
|
|
170
220
|
|
|
171
|
-
// Reject if it looks like a multi-segment structured address (City, ST ZIP).
|
|
172
221
|
const segCount = shape.segments?.length ?? 1
|
|
173
222
|
|
|
174
223
|
if (segCount > 2) return 0
|
|
175
224
|
|
|
176
|
-
// Reject if any word is an unambiguous street suffix.
|
|
177
225
|
const words = wordsOf(text)
|
|
178
226
|
|
|
179
227
|
for (const w of words) {
|
|
180
228
|
if (isDisqualifyingStreetSuffix(w)) return 0
|
|
181
229
|
}
|
|
182
230
|
|
|
183
|
-
//
|
|
231
|
+
// A leading number usually is a house number.
|
|
184
232
|
if (/^\d+\s/.test(text)) return 0
|
|
185
233
|
|
|
186
|
-
//
|
|
234
|
+
// Venue names often contain a number after the first word.
|
|
187
235
|
const hasInternalNumber = /\s\d+/.test(text) && !/^\d/.test(text)
|
|
188
236
|
|
|
189
|
-
// Check if every word starts with uppercase (proper-noun pattern).
|
|
190
237
|
const allProperCase = words.length > 1 && words.every((w) => /^[A-Z]/.test(w))
|
|
191
238
|
|
|
192
|
-
// Boost for short single-segment capitalized phrases (2-4 words).
|
|
193
239
|
const wordCount = words.length
|
|
194
240
|
|
|
195
241
|
if (wordCount >= VENUE_PHRASE_MIN_WORDS && wordCount <= VENUE_PHRASE_MAX_WORDS && segCount === 1) {
|
|
@@ -200,7 +246,6 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
|
|
|
200
246
|
return 0.65
|
|
201
247
|
}
|
|
202
248
|
|
|
203
|
-
// Longer single-segment capitalized phrases get moderate confidence.
|
|
204
249
|
if (wordCount <= LONG_VENUE_PHRASE_MAX_WORDS && segCount === 1 && allProperCase) {
|
|
205
250
|
return 0.75
|
|
206
251
|
}
|
|
@@ -209,10 +254,7 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
|
|
|
209
254
|
}
|
|
210
255
|
|
|
211
256
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* The "covering most of it" check is what distinguishes `"10118"` (postcode-only) from `"350 5th Ave 10118"`
|
|
215
|
-
* (structured-address with a postcode in it).
|
|
257
|
+
* Scores short input in which a postcode covers most of the text.
|
|
216
258
|
*/
|
|
217
259
|
export function scorePostcodeOnly(input: NormalizedInputLite, shape: QueryShapeLike): number {
|
|
218
260
|
const len = input.normalized.length
|
|
@@ -223,29 +265,49 @@ export function scorePostcodeOnly(input: NormalizedInputLite, shape: QueryShapeL
|
|
|
223
265
|
if (!postcodeHit) return 0
|
|
224
266
|
const hitLen = postcodeHit.span.end - postcodeHit.span.start
|
|
225
267
|
|
|
226
|
-
// At least 70% of the input must be the postcode for the rule to fire confidently.
|
|
227
268
|
if (hitLen / len < MIN_POSTCODE_COVERAGE) return 0
|
|
228
269
|
|
|
229
|
-
// Confidence scales with how much of the input is the postcode and how confident the format hit was.
|
|
230
270
|
return Math.min(1, postcodeHit.confidence * (hitLen / len) + 0.1)
|
|
231
271
|
}
|
|
232
272
|
|
|
233
273
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* Examples: `"Paris"`, `"NYC NY"`, `"Tokyo"`. Distinguishes from `structured_address` (multiple segments) and `vague`
|
|
237
|
-
* (long or mixed-class).
|
|
274
|
+
* Scores a place name with at most an administrative tail and an optional postcode.
|
|
238
275
|
*/
|
|
239
276
|
export function scoreLocalityOnly(input: NormalizedInputLite, shape: QueryShapeLike): number {
|
|
240
|
-
|
|
277
|
+
// Any recognized format other than a postcode rules out a bare locality.
|
|
278
|
+
let carriesPostcode = false
|
|
279
|
+
|
|
280
|
+
for (const hit of shape.knownFormats) {
|
|
281
|
+
if (isPostcodeFormat(hit.format)) {
|
|
282
|
+
carriesPostcode = true
|
|
283
|
+
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return 0
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Only postcode hits in the final segment can be removed.
|
|
291
|
+
const removable = carriesPostcode ? mergedPostcodeSpans(shape) : []
|
|
292
|
+
|
|
293
|
+
if (carriesPostcode && !removable.length) return 0
|
|
294
|
+
|
|
295
|
+
const withoutPostcode = carriesPostcode ? withoutPostcodeSpans(input.normalized, shape) : input.normalized
|
|
296
|
+
const len = withoutPostcode.length
|
|
241
297
|
|
|
242
298
|
if (len === 0 || len > MAX_LOCALITY_ONLY_LENGTH) return 0
|
|
243
299
|
|
|
244
|
-
if (
|
|
300
|
+
if (!carriesLetter(withoutPostcode)) return 0
|
|
245
301
|
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
302
|
+
// The remaining text must be alphabetic.
|
|
303
|
+
// Its class is recomputed only after a postcode was removed.
|
|
304
|
+
if (shape.characterClass !== "alpha") {
|
|
305
|
+
if (!carriesPostcode) return 0
|
|
306
|
+
|
|
307
|
+
if (foldInputClass(classifyTokens(withoutPostcode)) !== "alpha") return 0
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// A locality and an administrative tail use at most two segments.
|
|
249
311
|
const segCount = shape.segments?.length ?? 1
|
|
250
312
|
|
|
251
313
|
if (segCount > 2) return 0
|
|
@@ -254,8 +316,7 @@ export function scoreLocalityOnly(input: NormalizedInputLite, shape: QueryShapeL
|
|
|
254
316
|
}
|
|
255
317
|
|
|
256
318
|
/**
|
|
257
|
-
*
|
|
258
|
-
* mixed-class.
|
|
319
|
+
* Scores multi-component address shapes.
|
|
259
320
|
*/
|
|
260
321
|
export function scoreStructuredAddress(input: NormalizedInputLite, shape: QueryShapeLike): number {
|
|
261
322
|
const len = input.normalized.length
|
|
@@ -263,26 +324,27 @@ export function scoreStructuredAddress(input: NormalizedInputLite, shape: QueryS
|
|
|
263
324
|
if (len === 0) return 0
|
|
264
325
|
const segCount = shape.segments?.length ?? 1
|
|
265
326
|
|
|
266
|
-
//
|
|
267
|
-
if (
|
|
327
|
+
// Digits outside the postcode are required, so a locality with a postcode does not score here.
|
|
328
|
+
if (
|
|
329
|
+
segCount >= 2 &&
|
|
330
|
+
shape.characterClass === "alphanumeric" &&
|
|
331
|
+
/\d/u.test(withoutPostcodeSpans(input.normalized, shape))
|
|
332
|
+
) {
|
|
333
|
+
return 0.9
|
|
334
|
+
}
|
|
268
335
|
|
|
269
|
-
// Single-segment but reasonably long and alphanumeric = moderate confidence.
|
|
270
336
|
if (len >= ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric") return 0.75
|
|
271
337
|
|
|
272
|
-
// Multi-segment
|
|
338
|
+
// Multi-segment input without digits may still be a multi-word locality, so it scores lower.
|
|
273
339
|
if (segCount >= 2) return 0.6
|
|
274
340
|
|
|
275
|
-
// Single-segment, short, alphanumeric (e.g. "10118-1234" with no other content) — weak.
|
|
276
341
|
if (len < ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric") return 0.4
|
|
277
342
|
|
|
278
343
|
return 0
|
|
279
344
|
}
|
|
280
345
|
|
|
281
346
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* Returns a moderate baseline so `vague` always shows up as an alternative, even when other rules dominate. The
|
|
285
|
-
* coordinator decides whether to trust vague as the primary kind.
|
|
347
|
+
* Returns a low constant fallback score for ambiguous input.
|
|
286
348
|
*/
|
|
287
349
|
export function scoreVague(_input: NormalizedInputLite, _shape: QueryShapeLike): number {
|
|
288
350
|
return 0.3
|
package/out/classify.d.ts
CHANGED
|
@@ -3,30 +3,25 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* `classifyKind` is the entry point for Stage 2.5 (kind classification), composing the per-kind rules
|
|
7
|
+
* from `rules.ts` and `intent-rules.ts` and returning alternatives sorted by confidence.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Per the project's "possibilities not constraints" principle, every kind that fires above 0
|
|
13
|
-
* surfaces in `alternatives` — the caller decides whether to act on the top kind only or consider
|
|
14
|
-
* runner-ups. The ROAD_TO_V9 §4 intent vocabulary leans on that: `bare_toponym` and `route_pair`
|
|
15
|
-
* are scored below their structural incumbent precisely so they land in `alternatives`, where they
|
|
16
|
-
* inform the markers without moving the routing decision.
|
|
9
|
+
* Per the project's "possibilities not constraints" principle, every kind that fires above 0 surfaces
|
|
10
|
+
* in `alternatives` and the caller decides whether to act on the top kind only: `bare_toponym` and
|
|
11
|
+
* `route_pair` are scored below their structural incumbent precisely so they land in `alternatives`,
|
|
12
|
+
* where they inform the markers without moving the routing decision.
|
|
17
13
|
*/
|
|
18
14
|
import type { LocaleHint, QueryKindResult } from "@mailwoman/core/pipeline";
|
|
19
15
|
import type { NormalizedInputLite, QueryShapeSegmentsView as QueryShapeLike } from "@mailwoman/query-shape";
|
|
20
16
|
import { type POIPhraseLookup } from "#poi";
|
|
21
17
|
/**
|
|
22
|
-
* Classify the query shape into a `QueryKind
|
|
23
|
-
* shape)` pair.
|
|
18
|
+
* Classify the query shape into a `QueryKind`, synchronously and purely,
|
|
19
|
+
* producing the same result for the same `(input, shape)` pair.
|
|
24
20
|
*/
|
|
25
21
|
export declare function classifyKindSync(input: NormalizedInputLite, shape: QueryShapeLike): QueryKindResult;
|
|
26
22
|
/**
|
|
27
|
-
* Async variant matching the runtime
|
|
28
|
-
*
|
|
29
|
-
* The locale parameter is accepted for future locale-aware rules (Japanese honorifics, etc.) but not currently used.
|
|
23
|
+
* Async variant matching the runtime pipeline's `classifyKind` interface;
|
|
24
|
+
* `_locale` is accepted for future locale-aware rules but currently unused.
|
|
30
25
|
*/
|
|
31
26
|
export declare function classifyKind(input: NormalizedInputLite, shape: QueryShapeLike, _locale?: LocaleHint): Promise<QueryKindResult>;
|
|
32
27
|
/**
|
|
@@ -34,15 +29,18 @@ export declare function classifyKind(input: NormalizedInputLite, shape: QuerySha
|
|
|
34
29
|
*/
|
|
35
30
|
export interface KindClassifierOpts {
|
|
36
31
|
/**
|
|
37
|
-
* POI phrase lexicon (spec §3.1)
|
|
38
|
-
*
|
|
39
|
-
*
|
|
32
|
+
* POI phrase lexicon (spec §3.1); when present the `poi_query`
|
|
33
|
+
* and `poi_category` scorers join the rule set.
|
|
34
|
+
*
|
|
35
|
+
* They are injected rather than imported so this package stays dictionary-free.
|
|
36
|
+
* When absent, the returned classifier behaves identically to {@link classifyKind}.
|
|
40
37
|
*/
|
|
41
38
|
poiLexicon?: POIPhraseLookup;
|
|
42
39
|
}
|
|
43
40
|
/**
|
|
44
|
-
* Build a kind classifier
|
|
45
|
-
* additionally scores `poi_query` + `poi_category` (ROAD_TO_V9 §4.4)
|
|
41
|
+
* Build a kind classifier that is exactly {@link classifyKind} without options,
|
|
42
|
+
* or that additionally scores `poi_query` + `poi_category` (ROAD_TO_V9 §4.4)
|
|
43
|
+
* and merges them into the ranked result when given a `poiLexicon`.
|
|
46
44
|
*/
|
|
47
45
|
export declare function createKindClassifier(opts?: KindClassifierOpts): (input: NormalizedInputLite, shape: QueryShapeLike, locale?: LocaleHint) => Promise<QueryKindResult>;
|
|
48
46
|
//# sourceMappingURL=classify.d.ts.map
|
package/out/classify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../lib/classify.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../lib/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgC,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACzG,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAI3G,OAAO,EAA+C,KAAK,eAAe,EAAE,MAAM,MAAM,CAAA;AA4ExF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,eAAe,CAMnG;AAED;;;GAGG;AACH,wBAAsB,YAAY,CACjC,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,eAAe,CAAC,CAE1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,IAAI,GAAE,kBAAuB,GAC3B,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,eAAe,CAAC,CAgCtG"}
|
package/out/classify.js
CHANGED
|
@@ -3,17 +3,13 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* `classifyKind` is the entry point for Stage 2.5 (kind classification), composing the per-kind rules
|
|
7
|
+
* from `rules.ts` and `intent-rules.ts` and returning alternatives sorted by confidence.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Per the project's "possibilities not constraints" principle, every kind that fires above 0
|
|
13
|
-
* surfaces in `alternatives` — the caller decides whether to act on the top kind only or consider
|
|
14
|
-
* runner-ups. The ROAD_TO_V9 §4 intent vocabulary leans on that: `bare_toponym` and `route_pair`
|
|
15
|
-
* are scored below their structural incumbent precisely so they land in `alternatives`, where they
|
|
16
|
-
* inform the markers without moving the routing decision.
|
|
9
|
+
* Per the project's "possibilities not constraints" principle, every kind that fires above 0 surfaces
|
|
10
|
+
* in `alternatives` and the caller decides whether to act on the top kind only: `bare_toponym` and
|
|
11
|
+
* `route_pair` are scored below their structural incumbent precisely so they land in `alternatives`,
|
|
12
|
+
* where they inform the markers without moving the routing decision.
|
|
17
13
|
*/
|
|
18
14
|
import { deriveIntentMarkers } from "#intent/markers";
|
|
19
15
|
import { scoreBareToponym, scoreNearMe, scoreRoutePair } from "#intent/rules";
|
|
@@ -26,17 +22,17 @@ const SCORERS = [
|
|
|
26
22
|
{ kind: "postcode_only", score: scorePostcodeOnly },
|
|
27
23
|
{ kind: "locality_only", score: scoreLocalityOnly },
|
|
28
24
|
{ kind: "structured_address", score: scoreStructuredAddress },
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
25
|
+
// Intent is vocabulary rather than a stage.
|
|
26
|
+
// The sort below decides, so `bare_toponym` and `route_pair` sit here cosmetically
|
|
27
|
+
// and are scored under `locality_only` on purpose (see `intent-rules.ts`).
|
|
32
28
|
{ kind: "bare_toponym", score: scoreBareToponym },
|
|
33
29
|
{ kind: "route_pair", score: scoreRoutePair },
|
|
34
30
|
{ kind: "near_me", score: scoreNearMe },
|
|
35
31
|
{ kind: "vague", score: scoreVague },
|
|
36
32
|
];
|
|
37
33
|
/**
|
|
38
|
-
* Rank a scored list
|
|
39
|
-
* drift in how they break ties or build `alternatives`.
|
|
34
|
+
* Rank a scored list into a verdict, shared by the lexicon-free and lexicon-wired paths
|
|
35
|
+
* so the two cannot drift in how they break ties or build `alternatives`.
|
|
40
36
|
*/
|
|
41
37
|
function rank(scored) {
|
|
42
38
|
scored.sort((a, b) => b.confidence - a.confidence);
|
|
@@ -48,17 +44,17 @@ function rank(scored) {
|
|
|
48
44
|
};
|
|
49
45
|
}
|
|
50
46
|
/**
|
|
51
|
-
* Every kind whose verdict
|
|
52
|
-
*
|
|
47
|
+
* Every kind whose verdict includes `intentMarkers`, checked before the marker builder
|
|
48
|
+
* so the hot path pays one set membership test per kind.
|
|
53
49
|
*/
|
|
54
|
-
const
|
|
50
|
+
const MARKER_KINDS = new Set(["route_pair", "near_me", "poi_category"]);
|
|
55
51
|
/**
|
|
56
|
-
* Attach markers to a verdict
|
|
57
|
-
*
|
|
52
|
+
* Attach markers to a verdict or return it untouched, separate from {@link rank}
|
|
53
|
+
* because the lexicon-wired path merges `poi_query`/`poi_category` in first.
|
|
58
54
|
*/
|
|
59
55
|
function withIntentMarkers(verdict, input, poiLexicon, locale) {
|
|
60
56
|
const kinds = [{ kind: verdict.kind, confidence: verdict.confidence }, ...verdict.alternatives];
|
|
61
|
-
if (!kinds.some((k) =>
|
|
57
|
+
if (!kinds.some((k) => MARKER_KINDS.has(k.kind)))
|
|
62
58
|
return verdict;
|
|
63
59
|
const intentMarkers = deriveIntentMarkers(kinds, { input, poiLexicon, locale });
|
|
64
60
|
if (!intentMarkers.length)
|
|
@@ -66,24 +62,24 @@ function withIntentMarkers(verdict, input, poiLexicon, locale) {
|
|
|
66
62
|
return { ...verdict, intentMarkers };
|
|
67
63
|
}
|
|
68
64
|
/**
|
|
69
|
-
* Classify the query shape into a `QueryKind
|
|
70
|
-
* shape)` pair.
|
|
65
|
+
* Classify the query shape into a `QueryKind`, synchronously and purely,
|
|
66
|
+
* producing the same result for the same `(input, shape)` pair.
|
|
71
67
|
*/
|
|
72
68
|
export function classifyKindSync(input, shape) {
|
|
73
69
|
const scored = SCORERS.map((s) => ({ kind: s.kind, confidence: s.score(input, shape) })).filter((s) => s.confidence > 0);
|
|
74
70
|
return withIntentMarkers(rank(scored), input);
|
|
75
71
|
}
|
|
76
72
|
/**
|
|
77
|
-
* Async variant matching the runtime
|
|
78
|
-
*
|
|
79
|
-
* The locale parameter is accepted for future locale-aware rules (Japanese honorifics, etc.) but not currently used.
|
|
73
|
+
* Async variant matching the runtime pipeline's `classifyKind` interface;
|
|
74
|
+
* `_locale` is accepted for future locale-aware rules but currently unused.
|
|
80
75
|
*/
|
|
81
76
|
export async function classifyKind(input, shape, _locale) {
|
|
82
77
|
return classifyKindSync(input, shape);
|
|
83
78
|
}
|
|
84
79
|
/**
|
|
85
|
-
* Build a kind classifier
|
|
86
|
-
* additionally scores `poi_query` + `poi_category` (ROAD_TO_V9 §4.4)
|
|
80
|
+
* Build a kind classifier that is exactly {@link classifyKind} without options,
|
|
81
|
+
* or that additionally scores `poi_query` + `poi_category` (ROAD_TO_V9 §4.4)
|
|
82
|
+
* and merges them into the ranked result when given a `poiLexicon`.
|
|
87
83
|
*/
|
|
88
84
|
export function createKindClassifier(opts = {}) {
|
|
89
85
|
const { poiLexicon } = opts;
|
|
@@ -96,9 +92,10 @@ export function createKindClassifier(opts = {}) {
|
|
|
96
92
|
const categoryConfidence = createScorePOICategory(poiLexicon, localeTag)(input, shape);
|
|
97
93
|
if (poiConfidence <= 0 && categoryConfidence <= 0)
|
|
98
94
|
return base;
|
|
99
|
-
// Re-
|
|
100
|
-
//
|
|
101
|
-
//
|
|
95
|
+
// Re-ranking over the union rather than special-casing whether POI beat the
|
|
96
|
+
// base preserves the base's own alternatives.
|
|
97
|
+
// Those keep `bare_toponym`/`route_pair` visible to the marker builder even
|
|
98
|
+
// when a POI kind takes the top slot.
|
|
102
99
|
const merged = [
|
|
103
100
|
{ kind: base.kind, confidence: base.confidence },
|
|
104
101
|
...base.alternatives,
|
package/out/classify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../lib/classify.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../lib/classify.ts"],"names":[],"mappings":"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"}
|
package/out/index.d.ts
CHANGED
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Categorize inputs into one of eight `QueryKind`s by composing rule-based scorers over the
|
|
9
|
-
* QueryShape sub-system's output. Pure functions, no ML, no place-name dictionaries. Returns
|
|
10
|
-
* possibilities (alternatives) alongside the top pick so the coordinator can fall back when the
|
|
11
|
-
* winning kind isn't actionable.
|
|
12
|
-
*
|
|
13
|
-
* See `docs/engineering/reference/STAGES.md` § Stage 2.5 for the contract.
|
|
6
|
+
* Stage 2.5 classifier: compose rule-based scorers over QueryShape and return a top kind with alternatives.
|
|
14
7
|
*/
|
|
15
8
|
export { classifyKind, classifyKindSync, createKindClassifier } from "#classify";
|
|
16
9
|
export type { KindClassifierOpts } from "#classify";
|
package/out/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAChF,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE9G,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,EACV,kBAAkB,GAClB,MAAM,QAAQ,CAAA"}
|