@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.
- package/dist/{ask-72UZSXFM.js → ask-OOIREJML.js} +1 -1
- package/dist/certificates.d.ts +16 -0
- package/dist/{chunk-IEM2XBLP.js → chunk-BCNSMJ7Q.js} +32 -6
- package/dist/worker_public/src/index.js +2 -2
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +1 -0
- package/workers/worker_public/src/certificates.ts +48 -12
package/dist/certificates.d.ts
CHANGED
|
@@ -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
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
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(
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
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
|