@konneal/engine 0.2.43 → 0.2.45

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-IEM2XBLP.js";
3
+ } from "./chunk-BCNSMJ7Q.js";
4
4
  import "./chunk-N2UOENDY.js";
5
5
  import "./chunk-R2V3X6SQ.js";
6
6
  import "./chunk-A3QHHUN5.js";
@@ -15,7 +15,23 @@ export declare function isRegisterShaped(query: string): boolean;
15
15
  * 2+ characters that are not register question words, plus bare model
16
16
  * numbers ("190", "HM14H1"). Upper-cased tokens match case-insensitively
17
17
  * in SQL LIKE. */
18
+ /** The family the question names ("R 60", "R60", "D 31") — the register
19
+ * search filters to it, so a question about R 60 never presents R 76
20
+ * rows. */
21
+ export declare function queryFamily(query: string): string | null;
18
22
  export declare function registerTokens(query: string): string[];
23
+ /** The register query for a token set. Numeric tokens (a model number
24
+ * like "190") must match as a STANDALONE value — `LIKE '%190%'` also
25
+ * catches "XK3190", which the live Utilcell probe demonstrated — so a
26
+ * number matches the exact model, or as a delimiter-separated word
27
+ * (hyphens and punctuation normalize to spaces). Word tokens keep the
28
+ * substring LIKE (names and model words do not overmatch the way
29
+ * numbers do). Each token takes its own numbered placeholder,
30
+ * referenced three times (holder, model, number). */
31
+ export declare function buildRegisterQuery(tokens: string[]): {
32
+ sql: string;
33
+ params: string[];
34
+ };
19
35
  export declare function searchRegister(db: any, query: string): Promise<{
20
36
  rows: RegisterRow[];
21
37
  tokens: string[];
@@ -1751,6 +1751,10 @@ function semanticCacheKey(indexVersion, gen, signature) {
1751
1751
  function isRegisterShaped(query) {
1752
1752
  return /\bcertif(ied|icates?|ication)s?\b/i.test(query) && /\b(is|are|still|currently|valid|status|suspended|revoked|was|were)\b/i.test(query);
1753
1753
  }
1754
+ function queryFamily(query) {
1755
+ const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
1756
+ return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
1757
+ }
1754
1758
  function registerTokens(query) {
1755
1759
  const stop = /* @__PURE__ */ new Set([
1756
1760
  "the",
@@ -1810,17 +1814,38 @@ function registerTokens(query) {
1810
1814
  }
1811
1815
  return tokens2.slice(0, 6);
1812
1816
  }
1817
+ function buildRegisterQuery(tokens2) {
1818
+ const params = [];
1819
+ const clauses = tokens2.map((t) => {
1820
+ if (/^\d+$/.test(t)) {
1821
+ const padded = `% ${t.toLowerCase()} %`;
1822
+ params.push(t, t, padded);
1823
+ const n1 = params.length - 2;
1824
+ const n2 = params.length;
1825
+ return `(model = ?${n1} OR num = ?${n1} OR (' ' || REPLACE(REPLACE(REPLACE(LOWER(model), '-', ' '), ',', ' '), '.', ' ') || ' ') LIKE ?${n2})`;
1826
+ }
1827
+ params.push(`%${t}%`);
1828
+ const n = params.length;
1829
+ return `(holder LIKE ?${n} OR model LIKE ?${n} OR num LIKE ?${n})`;
1830
+ });
1831
+ return {
1832
+ sql: `SELECT num, family, holder, model, year, status FROM certificates WHERE ${clauses.join(" OR ")} LIMIT 6`,
1833
+ params
1834
+ };
1835
+ }
1813
1836
  async function searchRegister(db, query) {
1814
1837
  if (!isRegisterShaped(query)) return null;
1815
1838
  const tokens2 = registerTokens(query);
1816
1839
  if (!tokens2.length) return null;
1817
- const clauses = tokens2.map((_, i) => {
1818
- const n = i + 1;
1819
- return `(holder LIKE ?${n} OR model LIKE ?${n} OR num LIKE ?${n})`;
1820
- }).join(" OR ");
1821
- const params = tokens2.map((t) => `%${t}%`);
1840
+ const built = buildRegisterQuery(tokens2);
1841
+ let { sql, params } = built;
1842
+ const family = queryFamily(query);
1843
+ if (family) {
1844
+ sql = sql.replace(" WHERE ", " WHERE family = ?0 AND ");
1845
+ params.unshift(family);
1846
+ }
1822
1847
  try {
1823
- const res = await db.prepare(`SELECT num, family, holder, model, year, status FROM certificates WHERE ${clauses} LIMIT 6`).bind(...params).all();
1848
+ const res = await db.prepare(sql).bind(...params).all();
1824
1849
  return { rows: res.results ?? [], tokens: tokens2 };
1825
1850
  } catch {
1826
1851
  return { rows: [], tokens: tokens2 };
@@ -2168,6 +2193,7 @@ async function handleAsk(env, ctx, req, tier, key) {
2168
2193
  const graphDocNumbers = await graphExpand(env, understanding);
2169
2194
  const eNote = await editionNote(env, understanding);
2170
2195
  const register = await searchRegister(env.DB, q.query);
2196
+ console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
2171
2197
  const regNote = register ? registerNote(register.rows) : void 0;
2172
2198
  if (understanding?.intent !== "conversational" && !nodeScoped && !contextual && !declaredCtx && !draftAct && !apiCallIntent && !userImage && !fresh) {
2173
2199
  const warmVec = await warmEmbed ?? null;
@@ -9,7 +9,7 @@ import {
9
9
  modelGroundingBlock,
10
10
  scoreJudge,
11
11
  standardForDocNumber
12
- } from "../../chunk-IEM2XBLP.js";
12
+ } from "../../chunk-BCNSMJ7Q.js";
13
13
  import {
14
14
  buildMessages,
15
15
  citations,
@@ -999,7 +999,7 @@ async function handleMcp(env, ctx, req, tier, key) {
999
999
  // stream:false forces the JSON lane (anon defaults to SSE)
1000
1000
  body: JSON.stringify({ ...args, stream: false })
1001
1001
  });
1002
- const res = name === "ask" ? await (await import("../../ask-72UZSXFM.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-QZDQVB57.js")).handleSearch(env, ctx, inner, tier, key);
1002
+ const res = name === "ask" ? await (await import("../../ask-OOIREJML.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-QZDQVB57.js")).handleSearch(env, ctx, inner, tier, key);
1003
1003
  return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
1004
1004
  });
1005
1005
  if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
package/package.json CHANGED
@@ -27,7 +27,7 @@
27
27
  "playwright": "^1.62.1",
28
28
  "yaml": "^2.9.1"
29
29
  },
30
- "version": "0.2.43",
30
+ "version": "0.2.45",
31
31
  "description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
32
32
  "license": "BSD-3-Clause",
33
33
  "type": "module",
@@ -583,6 +583,7 @@ async function handleAsk(
583
583
  // join the prompt as an authoritative note — exact matches, never a
584
584
  // similarity guess about certification standing
585
585
  const register = await searchRegister(env.DB, q.query);
586
+ console.log("register-search:", JSON.stringify({ shaped: !!register, tokens: register?.tokens ?? null, rows: register?.rows?.length ?? null, first: register?.rows?.[0]?.num ?? null }));
586
587
  const regNote = register ? registerNote(register.rows) : undefined;
587
588
 
588
589
  // semantic cache: near-duplicate of a recently answered question —
@@ -29,6 +29,14 @@ export function isRegisterShaped(query: string): boolean {
29
29
  * 2+ characters that are not register question words, plus bare model
30
30
  * numbers ("190", "HM14H1"). Upper-cased tokens match case-insensitively
31
31
  * in SQL LIKE. */
32
+ /** The family the question names ("R 60", "R60", "D 31") — the register
33
+ * search filters to it, so a question about R 60 never presents R 76
34
+ * rows. */
35
+ export function queryFamily(query: string): string | null {
36
+ const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
37
+ return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
38
+ }
39
+
32
40
  export function registerTokens(query: string): string[] {
33
41
  const stop = new Set([
34
42
  "the", "a", "an", "is", "are", "was", "were", "still", "currently", "in", "on", "for", "of", "and", "or",
@@ -48,23 +56,51 @@ export function registerTokens(query: string): string[] {
48
56
  return tokens.slice(0, 6);
49
57
  }
50
58
 
59
+ /** The register query for a token set. Numeric tokens (a model number
60
+ * like "190") must match as a STANDALONE value — `LIKE '%190%'` also
61
+ * catches "XK3190", which the live Utilcell probe demonstrated — so a
62
+ * number matches the exact model, or as a delimiter-separated word
63
+ * (hyphens and punctuation normalize to spaces). Word tokens keep the
64
+ * substring LIKE (names and model words do not overmatch the way
65
+ * numbers do). Each token takes its own numbered placeholder,
66
+ * referenced three times (holder, model, number). */
67
+ export function buildRegisterQuery(tokens: string[]): { sql: string; params: string[] } {
68
+ const params: string[] = [];
69
+ const clauses = tokens.map((t) => {
70
+ if (/^\d+$/.test(t)) {
71
+ // a number matches as a STANDALONE value: the exact model or
72
+ // certificate number, or a delimiter-separated word (hyphens and
73
+ // punctuation normalize to spaces) — never a substring ("190"
74
+ // must not catch "XK3190")
75
+ const padded = `% ${t.toLowerCase()} %`;
76
+ params.push(t, t, padded);
77
+ const n1 = params.length - 2;
78
+ const n2 = params.length;
79
+ return `(model = ?${n1} OR num = ?${n1} OR (' ' || REPLACE(REPLACE(REPLACE(LOWER(model), '-', ' '), ',', ' '), '.', ' ') || ' ') LIKE ?${n2})`;
80
+ }
81
+ params.push(`%${t}%`);
82
+ const n = params.length;
83
+ return `(holder LIKE ?${n} OR model LIKE ?${n} OR num LIKE ?${n})`;
84
+ });
85
+ return {
86
+ sql: `SELECT num, family, holder, model, year, status FROM certificates WHERE ${clauses.join(" OR ")} LIMIT 6`,
87
+ params,
88
+ };
89
+ }
90
+
51
91
  export async function searchRegister(db: any, query: string): Promise<{ rows: RegisterRow[]; tokens: string[] } | null> {
52
92
  if (!isRegisterShaped(query)) return null;
53
93
  const tokens = registerTokens(query);
54
94
  if (!tokens.length) return null;
55
- // each token takes its own numbered placeholder, referenced three
56
- // times (holder, model, number) — a shared ?1 would silently search
57
- // the first token only
58
- const clauses = tokens.map((_, i) => {
59
- const n = i + 1;
60
- return `(holder LIKE ?${n} OR model LIKE ?${n} OR num LIKE ?${n})`;
61
- }).join(" OR ");
62
- const params = tokens.map((t) => `%${t}%`);
95
+ const built = buildRegisterQuery(tokens);
96
+ let { sql, params } = built;
97
+ const family = queryFamily(query);
98
+ if (family) {
99
+ sql = sql.replace(" WHERE ", " WHERE family = ?0 AND ");
100
+ params.unshift(family);
101
+ }
63
102
  try {
64
- const res = await db
65
- .prepare(`SELECT num, family, holder, model, year, status FROM certificates WHERE ${clauses} LIMIT 6`)
66
- .bind(...params)
67
- .all();
103
+ const res = await db.prepare(sql).bind(...params).all();
68
104
  return { rows: (res.results ?? []) as RegisterRow[], tokens };
69
105
  } catch {
70
106
  // a missing table or a D1 hiccup degrades to no-note honestly