@mailwoman/kind-classifier 8.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/classify.ts CHANGED
@@ -49,6 +49,7 @@ export function classifyKindSync(input: NormalizedInputLite, shape: QueryShapeLi
49
49
  const scored = SCORERS.map((s) => ({ kind: s.kind, confidence: s.score(input, shape) })).filter(
50
50
  (s) => s.confidence > 0
51
51
  )
52
+
52
53
  scored.sort((a, b) => b.confidence - a.confidence)
53
54
 
54
55
  const top = scored[0] ?? { kind: "vague" as QueryKind, confidence: 0.3 }
@@ -74,7 +75,9 @@ export async function classifyKind(
74
75
  return classifyKindSync(input, shape)
75
76
  }
76
77
 
77
- /** Options for {@link createKindClassifier}. */
78
+ /**
79
+ * Options for {@link createKindClassifier}.
80
+ */
78
81
  export interface KindClassifierOpts {
79
82
  /**
80
83
  * POI phrase lexicon (spec §3.1). When present, a `poi_query` scorer joins the rule set — injected, never imported,
@@ -114,7 +117,7 @@ export function createKindClassifier(
114
117
 
115
118
  return {
116
119
  ...base,
117
- alternatives: [...base.alternatives, poiAlternative].sort((a, b) => b.confidence - a.confidence),
120
+ alternatives: [...base.alternatives, poiAlternative].toSorted((a, b) => b.confidence - a.confidence),
118
121
  }
119
122
  }
120
123
  }
package/index.ts CHANGED
@@ -17,6 +17,7 @@ export { classifyKind, classifyKindSync, createKindClassifier } from "./classify
17
17
  export type { KindClassifierOpts } from "./classify.ts"
18
18
  export { matchPOISubject } from "./poi.ts"
19
19
  export type { POIPhraseMatch, POIPhraseLookup, POISubjectMatch } from "./poi.ts"
20
+
20
21
  export {
21
22
  scoreIntersection,
22
23
  scoreLandmark,
@@ -26,4 +27,5 @@ export {
26
27
  scoreStructuredAddress,
27
28
  scoreVague,
28
29
  } from "./rules.ts"
30
+
29
31
  export type { LocaleHint, NormalizedInputLite, QueryKind, QueryKindResult, QueryShapeLike } from "./types.ts"
package/out/classify.d.ts CHANGED
@@ -25,7 +25,9 @@ export declare function classifyKindSync(input: NormalizedInputLite, shape: Quer
25
25
  * The locale parameter is accepted for future locale-aware rules (Japanese honorifics, etc.) but not currently used.
26
26
  */
27
27
  export declare function classifyKind(input: NormalizedInputLite, shape: QueryShapeLike, _locale?: LocaleHint): Promise<QueryKindResult>;
28
- /** Options for {@link createKindClassifier}. */
28
+ /**
29
+ * Options for {@link createKindClassifier}.
30
+ */
29
31
  export interface KindClassifierOpts {
30
32
  /**
31
33
  * POI phrase lexicon (spec §3.1). When present, a `poi_query` scorer joins the rule set — injected, never imported,
@@ -1 +1 @@
1
- {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAWpE,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAa,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAiB7G;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,eAAe,CAcnG;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CACjC,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,eAAe,CAAC,CAE1B;AAED,gDAAgD;AAChD,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED;;;GAGG;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,CA2BtG"}
1
+ {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAWpE,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAa,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAiB7G;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,eAAe,CAenG;AAED;;;;GAIG;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;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED;;;GAGG;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,CA2BtG"}
package/out/classify.js CHANGED
@@ -70,7 +70,7 @@ export function createKindClassifier(opts = {}) {
70
70
  const poiAlternative = { kind: "poi_query", confidence: poiConfidence };
71
71
  return {
72
72
  ...base,
73
- alternatives: [...base.alternatives, poiAlternative].sort((a, b) => b.confidence - a.confidence),
73
+ alternatives: [...base.alternatives, poiAlternative].toSorted((a, b) => b.confidence - a.confidence),
74
74
  };
75
75
  };
76
76
  }
@@ -1 +1 @@
1
- {"version":3,"file":"classify.js","sourceRoot":"","sources":["../classify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../classify.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAChF,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,GACV,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAEhF,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,GACV,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
package/out/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEpF,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE1C,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,GACV,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEpF,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG1C,OAAO,EACN,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,UAAU,GACV,MAAM,YAAY,CAAA"}
package/out/poi.d.ts CHANGED
@@ -9,7 +9,9 @@
9
9
  * `poiQueryKind` flag (default-ON since 2026-07-20). Spec §3.1.
10
10
  */
11
11
  import type { NormalizedInputLite, QueryShapeLike } from "./types.ts";
12
- /** One lexicon hit for a candidate subject phrase. */
12
+ /**
13
+ * One lexicon hit for a candidate subject phrase.
14
+ */
13
15
  export interface POIPhraseMatch {
14
16
  /**
15
17
  * The matched subject's identifier string. For `kind: "category"`, a `@mailwoman/poi-taxonomy` category id. For
@@ -19,22 +21,32 @@ export interface POIPhraseMatch {
19
21
  categoryID: string;
20
22
  matchedPhrase: string;
21
23
  confidence: number;
22
- /** Which lexicon this hit came from. Existing category lookups set `"category"` (backward-compatible default). */
23
24
  /**
24
25
  * Absent = "category" (the pre-brand shape) — optional so pre-7.3 POIPhraseLookup implementors stay
25
26
  * source-compatible.
26
27
  */
27
28
  kind?: "category" | "brand";
28
- /** Wikidata QID, when known. `kind: "brand"` only — absent when a brand resolved by name alone (no QID match). */
29
+ /**
30
+ * Wikidata QID, when known. `kind: "brand"` only — absent when a brand resolved by name alone (no QID match).
31
+ */
29
32
  wikidata?: string;
30
33
  }
31
- /** Injected phrase→category lookup. Exact-phrase, locale-aware; returns [] on miss. */
34
+ /**
35
+ * Injected phrase→category lookup. Exact-phrase, locale-aware; returns [] on miss.
36
+ */
32
37
  export type POIPhraseLookup = (phrase: string, locale?: string) => ReadonlyArray<POIPhraseMatch>;
38
+ /**
39
+ * Which lexicon this hit came from. Existing category lookups set `"category"` (backward-compatible default).
40
+ */
33
41
  export interface POISubjectMatch {
34
42
  match: POIPhraseMatch;
35
- /** The matched subject text as it appeared in the query. */
43
+ /**
44
+ * The matched subject text as it appeared in the query.
45
+ */
36
46
  subject: string;
37
- /** The anchor remainder after the separator; `""` when the whole input matched. */
47
+ /**
48
+ * The anchor remainder after the separator; `""` when the whole input matched.
49
+ */
38
50
  remainder: string;
39
51
  }
40
52
  /**
package/out/poi.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"poi.d.ts","sourceRoot":"","sources":["../poi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAErE,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,kHAAkH;IAClH;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC3B,kHAAkH;IAClH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,uFAAuF;AACvF,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC,cAAc,CAAC,CAAA;AAEhG,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,cAAc,CAAA;IACrB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAA;IACf,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAA;CACjB;AAyBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,EAAE,eAAe,GACrB,eAAe,GAAG,IAAI,CA6BxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,MAAM,GACb,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,KAAK,MAAM,CAiB/D"}
1
+ {"version":3,"file":"poi.d.ts","sourceRoot":"","sources":["../poi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAQrE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC3B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC,cAAc,CAAC,CAAA;AAEhG;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,cAAc,CAAA;IACrB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;CACjB;AA2BD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,EAAE,eAAe,GACrB,eAAe,GAAG,IAAI,CA6BxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,MAAM,GACb,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,KAAK,MAAM,CAiB/D"}
package/out/poi.js CHANGED
@@ -8,6 +8,11 @@
8
8
  * lives in `@mailwoman/poi-taxonomy` and is wired in by `createRuntimePipeline` behind the
9
9
  * `poiQueryKind` flag (default-ON since 2026-07-20). Spec §3.1.
10
10
  */
11
+ /**
12
+ * Comma-segment ceiling for a POI-led query. Past it the input is a venue plus a full address (`X, 350 5th Ave, New
13
+ * York, NY`), which the structured-address scorer should claim instead.
14
+ */
15
+ const MAX_POI_SEGMENTS = 3;
11
16
  /**
12
17
  * Anchor separator between subject and place: comma, or near/in/at/around — scanned left-to-right until a prefix hits
13
18
  * the lexicon.
@@ -27,7 +32,9 @@
27
32
  * (systematic + fuzzed adversarial whitespace + shared-whitespace anchor/comma chains).
28
33
  */
29
34
  const ANCHOR_SEPARATOR = /,\s*|\s(?:near|in|at|around)\s+/gi;
30
- /** Longest subject we accept, in tokens. Lexicon phrases are short; 4 covers the table. */
35
+ /**
36
+ * Longest subject we accept, in tokens. Lexicon phrases are short; 4 covers the table.
37
+ */
31
38
  const MAX_SUBJECT_TOKENS = 4;
32
39
  /**
33
40
  * Match a POI subject: the whole input, or the text before the FIRST anchor separator WHOSE PREFIX HITS THE LEXICON (≤
@@ -40,7 +47,7 @@ export function matchPOISubject(text, locale, lookup) {
40
47
  if (!trimmed)
41
48
  return null;
42
49
  const whole = lookup(trimmed, locale);
43
- if (whole.length > 0) {
50
+ if (whole.length) {
44
51
  return { match: whole[0], subject: trimmed, remainder: "" };
45
52
  }
46
53
  for (const separator of trimmed.matchAll(ANCHOR_SEPARATOR)) {
@@ -51,7 +58,7 @@ export function matchPOISubject(text, locale, lookup) {
51
58
  if (subject.split(/\s+/).length > MAX_SUBJECT_TOKENS)
52
59
  break;
53
60
  const hits = lookup(subject, locale);
54
- if (hits.length === 0)
61
+ if (!hits.length)
55
62
  continue;
56
63
  const remainder = trimmed.slice(separator.index + separator[0].length).trim();
57
64
  return { match: hits[0], subject, remainder };
@@ -75,7 +82,7 @@ export function createScorePOIQuery(lookup, locale) {
75
82
  if (/^\d+\s/.test(matched.remainder))
76
83
  return 0;
77
84
  const segCount = shape.segments?.length ?? 1;
78
- if (segCount > 3)
85
+ if (segCount > MAX_POI_SEGMENTS)
79
86
  return 0;
80
87
  return 0.9 * matched.match.confidence;
81
88
  };
package/out/poi.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"poi.js","sourceRoot":"","sources":["../poi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmCH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAE5D,2FAA2F;AAC3F,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAE5B;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC9B,IAAY,EACZ,MAA0B,EAC1B,MAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAE3B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAErC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAC7D,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC5D,IAAI,SAAS,CAAC,KAAK,KAAK,CAAC;YAAE,SAAQ;QAEnC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAExD,uFAAuF;QACvF,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,kBAAkB;YAAE,MAAK;QAE3D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAE/B,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QAE7E,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IAC/C,CAAC;IAED,OAAO,IAAI,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAClC,MAAuB,EACvB,MAAe;IAEf,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QAExF,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAA;QAEtB,IAAI,OAAO,CAAC,SAAS,KAAK,EAAE;YAAE,OAAO,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;QAEpE,gFAAgF;QAChF,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,OAAO,CAAC,CAAA;QAE9C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAA;QAE5C,IAAI,QAAQ,GAAG,CAAC;YAAE,OAAO,CAAC,CAAA;QAE1B,OAAO,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;IACtC,CAAC,CAAA;AACF,CAAC"}
1
+ {"version":3,"file":"poi.js","sourceRoot":"","sources":["../poi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,CAAA;AA6C1B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAE5D;;GAEG;AACH,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAE5B;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC9B,IAAY,EACZ,MAA0B,EAC1B,MAAuB;IAEvB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAE3B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAEzB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAErC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAC7D,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC5D,IAAI,SAAS,CAAC,KAAK,KAAK,CAAC;YAAE,SAAQ;QAEnC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QAExD,uFAAuF;QACvF,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,kBAAkB;YAAE,MAAK;QAE3D,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEpC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,SAAQ;QAE1B,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QAE7E,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IAC/C,CAAC;IAED,OAAO,IAAI,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAClC,MAAuB,EACvB,MAAe;IAEf,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QAExF,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAA;QAEtB,IAAI,OAAO,CAAC,SAAS,KAAK,EAAE;YAAE,OAAO,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;QAEpE,gFAAgF;QAChF,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;YAAE,OAAO,CAAC,CAAA;QAE9C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAA;QAE5C,IAAI,QAAQ,GAAG,gBAAgB;YAAE,OAAO,CAAC,CAAA;QAEzC,OAAO,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;IACtC,CAAC,CAAA;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAwBrE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAQrF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAQ5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAQxF;AA6BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAkD5F;AAcD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAc3F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAe3F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAmBhG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAEtF"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAwErE;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAQrF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAQ5F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAQxF;AA+BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAkD5F;AAgBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAc3F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAe3F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAmBhG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAEtF"}
package/out/rules.js CHANGED
@@ -9,7 +9,45 @@
9
9
  * Bitter-lesson-safe: only universal structural patterns — no place-name dictionaries. ~1 small
10
10
  * regex set per new locale, not 50K dictionary entries.
11
11
  */
12
- /** Landmark vocabulary — phrases that suggest a vague-location description rather than an address. */
12
+ /**
13
+ * Longest input still plausible as a bare venue or landmark name. Beyond it the query is carrying an address as well,
14
+ * and belongs to the structured-address scorer.
15
+ */
16
+ const MAX_LANDMARK_LENGTH = 50;
17
+ /**
18
+ * Longest input still plausible as a bare postcode, allowing for a country prefix and separators.
19
+ */
20
+ const MAX_POSTCODE_ONLY_LENGTH = 16;
21
+ /**
22
+ * Share of the input the postcode must occupy before `postcode_only` fires. Below it the query is carrying something
23
+ * else too — a locality, a street — and another kind should win.
24
+ */
25
+ const MIN_POSTCODE_COVERAGE = 0.7;
26
+ /**
27
+ * Longest input still plausible as a bare locality name, including a trailing region code.
28
+ */
29
+ const MAX_LOCALITY_ONLY_LENGTH = 30;
30
+ /**
31
+ * Word count of a short capitalized phrase — the shape of a venue name like `Empire State Building`. Wider than this
32
+ * and the phrase is more likely a full address line.
33
+ */
34
+ const VENUE_PHRASE_MIN_WORDS = 2;
35
+ /**
36
+ * Upper bound of the short-phrase venue window; see {@link VENUE_PHRASE_MIN_WORDS}.
37
+ */
38
+ const VENUE_PHRASE_MAX_WORDS = 4;
39
+ /**
40
+ * Word count above which a single-segment proper-case phrase stops reading as a venue name.
41
+ */
42
+ const LONG_VENUE_PHRASE_MAX_WORDS = 6;
43
+ /**
44
+ * Length at which a single-segment alphanumeric input reads as a full postcode rather than a fragment. Shorter
45
+ * alphanumeric inputs score lower because they are as likely to be a unit number.
46
+ */
47
+ const ALPHANUMERIC_POSTCODE_MIN_LENGTH = 15;
48
+ /**
49
+ * Landmark vocabulary — phrases that suggest a vague-location description rather than an address.
50
+ */
13
51
  const LANDMARK_LEADERS = [
14
52
  "behind",
15
53
  "near",
@@ -21,7 +59,9 @@ const LANDMARK_LEADERS = [
21
59
  "close to",
22
60
  "beside",
23
61
  ];
24
- /** Intersection vocabulary — words that signal "where two streets cross" rather than an address. */
62
+ /**
63
+ * Intersection vocabulary — words that signal "where two streets cross" rather than an address.
64
+ */
25
65
  const INTERSECTION_PATTERNS = [
26
66
  /\bcorner of\b/i,
27
67
  /\bintersection of\b/i,
@@ -64,7 +104,9 @@ export function scoreLandmark(input, _shape) {
64
104
  }
65
105
  return 0;
66
106
  }
67
- /** Street-suffix tokens that indicate an address, not a venue name. */
107
+ /**
108
+ * Street-suffix tokens that indicate an address, not a venue name.
109
+ */
68
110
  const STREET_SUFFIXES = new Set([
69
111
  "st",
70
112
  "street",
@@ -100,13 +142,13 @@ const STREET_SUFFIXES = new Set([
100
142
  export function scoreVenueLandmark(input, shape) {
101
143
  const text = input.normalized.trim();
102
144
  const len = text.length;
103
- if (len === 0 || len > 50)
145
+ if (len === 0 || len > MAX_LANDMARK_LENGTH)
104
146
  return 0;
105
147
  // Must have at least one capitalized word.
106
148
  if (!/[A-Z]/.test(text))
107
149
  return 0;
108
150
  // Reject if any known postcode format hit exists.
109
- if (shape.knownFormats.length > 0)
151
+ if (shape.knownFormats.length)
110
152
  return 0;
111
153
  // Reject if it looks like a multi-segment structured address (City, ST ZIP).
112
154
  const segCount = shape.segments?.length ?? 1;
@@ -127,7 +169,7 @@ export function scoreVenueLandmark(input, shape) {
127
169
  const allProperCase = words.length > 1 && words.every((w) => /^[A-Z]/.test(w));
128
170
  // Boost for short single-segment capitalized phrases (2-4 words).
129
171
  const wordCount = words.length;
130
- if (wordCount >= 2 && wordCount <= 4 && segCount === 1) {
172
+ if (wordCount >= VENUE_PHRASE_MIN_WORDS && wordCount <= VENUE_PHRASE_MAX_WORDS && segCount === 1) {
131
173
  if (hasInternalNumber)
132
174
  return 0.88;
133
175
  if (allProperCase)
@@ -135,12 +177,14 @@ export function scoreVenueLandmark(input, shape) {
135
177
  return 0.65;
136
178
  }
137
179
  // Longer single-segment capitalized phrases get moderate confidence.
138
- if (wordCount <= 6 && segCount === 1 && allProperCase) {
180
+ if (wordCount <= LONG_VENUE_PHRASE_MAX_WORDS && segCount === 1 && allProperCase) {
139
181
  return 0.75;
140
182
  }
141
183
  return 0;
142
184
  }
143
- /** Known QueryShape format strings that indicate "this token is a postcode". */
185
+ /**
186
+ * Known QueryShape format strings that indicate "this token is a postcode".
187
+ */
144
188
  const POSTCODE_FORMATS = new Set([
145
189
  "us_zip",
146
190
  "us_zip4",
@@ -166,14 +210,14 @@ export function isPostcodeFormat(format) {
166
210
  */
167
211
  export function scorePostcodeOnly(input, shape) {
168
212
  const len = input.normalized.length;
169
- if (len === 0 || len > 16)
213
+ if (len === 0 || len > MAX_POSTCODE_ONLY_LENGTH)
170
214
  return 0;
171
215
  const postcodeHit = shape.knownFormats.find((f) => isPostcodeFormat(f.format));
172
216
  if (!postcodeHit)
173
217
  return 0;
174
218
  const hitLen = postcodeHit.span.end - postcodeHit.span.start;
175
219
  // At least 70% of the input must be the postcode for the rule to fire confidently.
176
- if (hitLen / len < 0.7)
220
+ if (hitLen / len < MIN_POSTCODE_COVERAGE)
177
221
  return 0;
178
222
  // Confidence scales with how much of the input is the postcode and how confident the format hit was.
179
223
  return Math.min(1, postcodeHit.confidence * (hitLen / len) + 0.1);
@@ -186,11 +230,11 @@ export function scorePostcodeOnly(input, shape) {
186
230
  */
187
231
  export function scoreLocalityOnly(input, shape) {
188
232
  const len = input.normalized.length;
189
- if (len === 0 || len > 30)
233
+ if (len === 0 || len > MAX_LOCALITY_ONLY_LENGTH)
190
234
  return 0;
191
235
  if (shape.characterClass !== "alpha")
192
236
  return 0;
193
- if (shape.knownFormats.length > 0)
237
+ if (shape.knownFormats.length)
194
238
  return 0;
195
239
  // Locality-only inputs typically have 1-3 segments (e.g. "New York" is 1 segment, "Paris, FR" is 2).
196
240
  // We allow up to 2 segments before deciding it's structured.
@@ -212,13 +256,13 @@ export function scoreStructuredAddress(input, shape) {
212
256
  if (segCount >= 2 && shape.characterClass === "alphanumeric")
213
257
  return 0.9;
214
258
  // Single-segment but reasonably long and alphanumeric = moderate confidence.
215
- if (len >= 15 && shape.characterClass === "alphanumeric")
259
+ if (len >= ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric")
216
260
  return 0.75;
217
261
  // Multi-segment but pure-alpha = moderate (could be a multi-word locality).
218
262
  if (segCount >= 2)
219
263
  return 0.6;
220
264
  // Single-segment, short, alphanumeric (e.g. "10118-1234" with no other content) — weak.
221
- if (len < 15 && shape.characterClass === "alphanumeric")
265
+ if (len < ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric")
222
266
  return 0.4;
223
267
  return 0;
224
268
  }
package/out/rules.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../rules.ts"],"names":[],"mappings":"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"}
package/out/types.d.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
- /** Re-exports of the canonical types from `@mailwoman/core/pipeline`. */
6
+ /**
7
+ * Re-exports of the canonical types from `@mailwoman/core/pipeline`.
8
+ */
7
9
  export type { LocaleHint, QueryKind, QueryKindResult } from "@mailwoman/core/pipeline";
8
10
  /**
9
11
  * Minimal `NormalizedInput` shape consumed by `classifyKind`. Compatible with `@mailwoman/normalize`'s output.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,yEAAyE;AACzE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAEtF;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,YAAY,EAAE,aAAa,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;QACpC,UAAU,EAAE,MAAM,CAAA;KAClB,CAAC,CAAA;IACF,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAEtF;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,YAAY,EAAE,aAAa,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;QACpC,UAAU,EAAE,MAAM,CAAA;KAClB,CAAC,CAAA;IACF,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailwoman/kind-classifier",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "Stage 2.5 of the runtime pipeline — categorize inputs by query shape (postcode_only / locality_only / structured_address / intersection / po_box / landmark / vague). Rule-based for v1.",
5
5
  "license": "AGPL-3.0-only OR LicenseRef-Commercial",
6
6
  "repository": {
@@ -41,6 +41,6 @@
41
41
  }
42
42
  },
43
43
  "dependencies": {
44
- "@mailwoman/core": "8.1.0"
44
+ "@mailwoman/core": "8.2.0"
45
45
  }
46
46
  }
package/poi.ts CHANGED
@@ -11,7 +11,15 @@
11
11
 
12
12
  import type { NormalizedInputLite, QueryShapeLike } from "./types.ts"
13
13
 
14
- /** One lexicon hit for a candidate subject phrase. */
14
+ /**
15
+ * Comma-segment ceiling for a POI-led query. Past it the input is a venue plus a full address (`X, 350 5th Ave, New
16
+ * York, NY`), which the structured-address scorer should claim instead.
17
+ */
18
+ const MAX_POI_SEGMENTS = 3
19
+
20
+ /**
21
+ * One lexicon hit for a candidate subject phrase.
22
+ */
15
23
  export interface POIPhraseMatch {
16
24
  /**
17
25
  * The matched subject's identifier string. For `kind: "category"`, a `@mailwoman/poi-taxonomy` category id. For
@@ -21,24 +29,34 @@ export interface POIPhraseMatch {
21
29
  categoryID: string
22
30
  matchedPhrase: string
23
31
  confidence: number
24
- /** Which lexicon this hit came from. Existing category lookups set `"category"` (backward-compatible default). */
25
32
  /**
26
33
  * Absent = "category" (the pre-brand shape) — optional so pre-7.3 POIPhraseLookup implementors stay
27
34
  * source-compatible.
28
35
  */
29
36
  kind?: "category" | "brand"
30
- /** Wikidata QID, when known. `kind: "brand"` only — absent when a brand resolved by name alone (no QID match). */
37
+ /**
38
+ * Wikidata QID, when known. `kind: "brand"` only — absent when a brand resolved by name alone (no QID match).
39
+ */
31
40
  wikidata?: string
32
41
  }
33
42
 
34
- /** Injected phrase→category lookup. Exact-phrase, locale-aware; returns [] on miss. */
43
+ /**
44
+ * Injected phrase→category lookup. Exact-phrase, locale-aware; returns [] on miss.
45
+ */
35
46
  export type POIPhraseLookup = (phrase: string, locale?: string) => ReadonlyArray<POIPhraseMatch>
36
47
 
48
+ /**
49
+ * Which lexicon this hit came from. Existing category lookups set `"category"` (backward-compatible default).
50
+ */
37
51
  export interface POISubjectMatch {
38
52
  match: POIPhraseMatch
39
- /** The matched subject text as it appeared in the query. */
53
+ /**
54
+ * The matched subject text as it appeared in the query.
55
+ */
40
56
  subject: string
41
- /** The anchor remainder after the separator; `""` when the whole input matched. */
57
+ /**
58
+ * The anchor remainder after the separator; `""` when the whole input matched.
59
+ */
42
60
  remainder: string
43
61
  }
44
62
 
@@ -62,7 +80,9 @@ export interface POISubjectMatch {
62
80
  */
63
81
  const ANCHOR_SEPARATOR = /,\s*|\s(?:near|in|at|around)\s+/gi
64
82
 
65
- /** Longest subject we accept, in tokens. Lexicon phrases are short; 4 covers the table. */
83
+ /**
84
+ * Longest subject we accept, in tokens. Lexicon phrases are short; 4 covers the table.
85
+ */
66
86
  const MAX_SUBJECT_TOKENS = 4
67
87
 
68
88
  /**
@@ -82,7 +102,7 @@ export function matchPOISubject(
82
102
 
83
103
  const whole = lookup(trimmed, locale)
84
104
 
85
- if (whole.length > 0) {
105
+ if (whole.length) {
86
106
  return { match: whole[0]!, subject: trimmed, remainder: "" }
87
107
  }
88
108
 
@@ -96,7 +116,7 @@ export function matchPOISubject(
96
116
 
97
117
  const hits = lookup(subject, locale)
98
118
 
99
- if (hits.length === 0) continue
119
+ if (!hits.length) continue
100
120
 
101
121
  const remainder = trimmed.slice(separator.index + separator[0].length).trim()
102
122
 
@@ -128,7 +148,7 @@ export function createScorePOIQuery(
128
148
 
129
149
  const segCount = shape.segments?.length ?? 1
130
150
 
131
- if (segCount > 3) return 0
151
+ if (segCount > MAX_POI_SEGMENTS) return 0
132
152
 
133
153
  return 0.9 * matched.match.confidence
134
154
  }
package/rules.ts CHANGED
@@ -12,7 +12,53 @@
12
12
 
13
13
  import type { NormalizedInputLite, QueryShapeLike } from "./types.ts"
14
14
 
15
- /** Landmark vocabulary — phrases that suggest a vague-location description rather than an address. */
15
+ /**
16
+ * Longest input still plausible as a bare venue or landmark name. Beyond it the query is carrying an address as well,
17
+ * and belongs to the structured-address scorer.
18
+ */
19
+ const MAX_LANDMARK_LENGTH = 50
20
+
21
+ /**
22
+ * Longest input still plausible as a bare postcode, allowing for a country prefix and separators.
23
+ */
24
+ const MAX_POSTCODE_ONLY_LENGTH = 16
25
+
26
+ /**
27
+ * Share of the input the postcode must occupy before `postcode_only` fires. Below it the query is carrying something
28
+ * else too — a locality, a street — and another kind should win.
29
+ */
30
+ const MIN_POSTCODE_COVERAGE = 0.7
31
+
32
+ /**
33
+ * Longest input still plausible as a bare locality name, including a trailing region code.
34
+ */
35
+ const MAX_LOCALITY_ONLY_LENGTH = 30
36
+
37
+ /**
38
+ * Word count of a short capitalized phrase — the shape of a venue name like `Empire State Building`. Wider than this
39
+ * and the phrase is more likely a full address line.
40
+ */
41
+ const VENUE_PHRASE_MIN_WORDS = 2
42
+
43
+ /**
44
+ * Upper bound of the short-phrase venue window; see {@link VENUE_PHRASE_MIN_WORDS}.
45
+ */
46
+ const VENUE_PHRASE_MAX_WORDS = 4
47
+
48
+ /**
49
+ * Word count above which a single-segment proper-case phrase stops reading as a venue name.
50
+ */
51
+ const LONG_VENUE_PHRASE_MAX_WORDS = 6
52
+
53
+ /**
54
+ * Length at which a single-segment alphanumeric input reads as a full postcode rather than a fragment. Shorter
55
+ * alphanumeric inputs score lower because they are as likely to be a unit number.
56
+ */
57
+ const ALPHANUMERIC_POSTCODE_MIN_LENGTH = 15
58
+
59
+ /**
60
+ * Landmark vocabulary — phrases that suggest a vague-location description rather than an address.
61
+ */
16
62
  const LANDMARK_LEADERS = [
17
63
  "behind",
18
64
  "near",
@@ -25,7 +71,9 @@ const LANDMARK_LEADERS = [
25
71
  "beside",
26
72
  ]
27
73
 
28
- /** Intersection vocabulary — words that signal "where two streets cross" rather than an address. */
74
+ /**
75
+ * Intersection vocabulary — words that signal "where two streets cross" rather than an address.
76
+ */
29
77
  const INTERSECTION_PATTERNS = [
30
78
  /\bcorner of\b/i,
31
79
  /\bintersection of\b/i,
@@ -75,7 +123,9 @@ export function scoreLandmark(input: NormalizedInputLite, _shape: QueryShapeLike
75
123
  return 0
76
124
  }
77
125
 
78
- /** Street-suffix tokens that indicate an address, not a venue name. */
126
+ /**
127
+ * Street-suffix tokens that indicate an address, not a venue name.
128
+ */
79
129
  const STREET_SUFFIXES = new Set([
80
130
  "st",
81
131
  "street",
@@ -113,13 +163,13 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
113
163
  const text = input.normalized.trim()
114
164
  const len = text.length
115
165
 
116
- if (len === 0 || len > 50) return 0
166
+ if (len === 0 || len > MAX_LANDMARK_LENGTH) return 0
117
167
 
118
168
  // Must have at least one capitalized word.
119
169
  if (!/[A-Z]/.test(text)) return 0
120
170
 
121
171
  // Reject if any known postcode format hit exists.
122
- if (shape.knownFormats.length > 0) return 0
172
+ if (shape.knownFormats.length) return 0
123
173
 
124
174
  // Reject if it looks like a multi-segment structured address (City, ST ZIP).
125
175
  const segCount = shape.segments?.length ?? 1
@@ -145,7 +195,7 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
145
195
  // Boost for short single-segment capitalized phrases (2-4 words).
146
196
  const wordCount = words.length
147
197
 
148
- if (wordCount >= 2 && wordCount <= 4 && segCount === 1) {
198
+ if (wordCount >= VENUE_PHRASE_MIN_WORDS && wordCount <= VENUE_PHRASE_MAX_WORDS && segCount === 1) {
149
199
  if (hasInternalNumber) return 0.88
150
200
 
151
201
  if (allProperCase) return 0.88
@@ -154,14 +204,16 @@ export function scoreVenueLandmark(input: NormalizedInputLite, shape: QueryShape
154
204
  }
155
205
 
156
206
  // Longer single-segment capitalized phrases get moderate confidence.
157
- if (wordCount <= 6 && segCount === 1 && allProperCase) {
207
+ if (wordCount <= LONG_VENUE_PHRASE_MAX_WORDS && segCount === 1 && allProperCase) {
158
208
  return 0.75
159
209
  }
160
210
 
161
211
  return 0
162
212
  }
163
213
 
164
- /** Known QueryShape format strings that indicate "this token is a postcode". */
214
+ /**
215
+ * Known QueryShape format strings that indicate "this token is a postcode".
216
+ */
165
217
  const POSTCODE_FORMATS: ReadonlySet<string> = new Set([
166
218
  "us_zip",
167
219
  "us_zip4",
@@ -190,14 +242,14 @@ export function isPostcodeFormat(format: string): boolean {
190
242
  export function scorePostcodeOnly(input: NormalizedInputLite, shape: QueryShapeLike): number {
191
243
  const len = input.normalized.length
192
244
 
193
- if (len === 0 || len > 16) return 0
245
+ if (len === 0 || len > MAX_POSTCODE_ONLY_LENGTH) return 0
194
246
  const postcodeHit = shape.knownFormats.find((f) => isPostcodeFormat(f.format))
195
247
 
196
248
  if (!postcodeHit) return 0
197
249
  const hitLen = postcodeHit.span.end - postcodeHit.span.start
198
250
 
199
251
  // At least 70% of the input must be the postcode for the rule to fire confidently.
200
- if (hitLen / len < 0.7) return 0
252
+ if (hitLen / len < MIN_POSTCODE_COVERAGE) return 0
201
253
 
202
254
  // Confidence scales with how much of the input is the postcode and how confident the format hit was.
203
255
  return Math.min(1, postcodeHit.confidence * (hitLen / len) + 0.1)
@@ -212,11 +264,11 @@ export function scorePostcodeOnly(input: NormalizedInputLite, shape: QueryShapeL
212
264
  export function scoreLocalityOnly(input: NormalizedInputLite, shape: QueryShapeLike): number {
213
265
  const len = input.normalized.length
214
266
 
215
- if (len === 0 || len > 30) return 0
267
+ if (len === 0 || len > MAX_LOCALITY_ONLY_LENGTH) return 0
216
268
 
217
269
  if (shape.characterClass !== "alpha") return 0
218
270
 
219
- if (shape.knownFormats.length > 0) return 0
271
+ if (shape.knownFormats.length) return 0
220
272
  // Locality-only inputs typically have 1-3 segments (e.g. "New York" is 1 segment, "Paris, FR" is 2).
221
273
  // We allow up to 2 segments before deciding it's structured.
222
274
  const segCount = shape.segments?.length ?? 1
@@ -240,13 +292,13 @@ export function scoreStructuredAddress(input: NormalizedInputLite, shape: QueryS
240
292
  if (segCount >= 2 && shape.characterClass === "alphanumeric") return 0.9
241
293
 
242
294
  // Single-segment but reasonably long and alphanumeric = moderate confidence.
243
- if (len >= 15 && shape.characterClass === "alphanumeric") return 0.75
295
+ if (len >= ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric") return 0.75
244
296
 
245
297
  // Multi-segment but pure-alpha = moderate (could be a multi-word locality).
246
298
  if (segCount >= 2) return 0.6
247
299
 
248
300
  // Single-segment, short, alphanumeric (e.g. "10118-1234" with no other content) — weak.
249
- if (len < 15 && shape.characterClass === "alphanumeric") return 0.4
301
+ if (len < ALPHANUMERIC_POSTCODE_MIN_LENGTH && shape.characterClass === "alphanumeric") return 0.4
250
302
 
251
303
  return 0
252
304
  }
package/types.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- /** Re-exports of the canonical types from `@mailwoman/core/pipeline`. */
7
+ /**
8
+ * Re-exports of the canonical types from `@mailwoman/core/pipeline`.
9
+ */
8
10
  export type { LocaleHint, QueryKind, QueryKindResult } from "@mailwoman/core/pipeline"
9
11
 
10
12
  /**