@konneal/engine 0.2.76 → 0.2.77
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-PK75QJ66.js → ask-DKYIVPNV.js} +2 -2
- package/dist/{certificates-DNRUOYRF.js → certificates-YFL4K7O3.js} +3 -1
- package/dist/certificates.d.ts +4 -0
- package/dist/{chunk-VGVTIPFW.js → chunk-7AQM3GSO.js} +18 -0
- package/dist/{chunk-64G44J7U.js → chunk-GOYFWC3N.js} +25 -13
- package/dist/nameplate-parse.d.ts +4 -2
- package/dist/prompts/nameplate.md +10 -6
- package/dist/worker_public/src/index.js +3 -3
- package/package.json +1 -1
- package/workers/worker_public/prompts/nameplate.md +10 -6
- package/workers/worker_public/src/certificates.ts +28 -0
- package/workers/worker_public/src/nameplate-parse.ts +12 -6
- package/workers/worker_public/src/nameplate.ts +5 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleAsk
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GOYFWC3N.js";
|
|
4
4
|
import "./chunk-7HWJOFTQ.js";
|
|
5
5
|
import "./chunk-SPKPH54J.js";
|
|
6
6
|
import "./chunk-A3QHHUN5.js";
|
|
7
7
|
import "./chunk-LVZTWUVJ.js";
|
|
8
8
|
import "./chunk-2PCUAJJO.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-7AQM3GSO.js";
|
|
10
10
|
import "./chunk-3FYJM7LH.js";
|
|
11
11
|
export {
|
|
12
12
|
handleAsk
|
|
@@ -2,16 +2,18 @@ import {
|
|
|
2
2
|
buildRegisterQuery,
|
|
3
3
|
certificateLinks,
|
|
4
4
|
isRegisterShaped,
|
|
5
|
+
printedCertificateNumber,
|
|
5
6
|
queryFamily,
|
|
6
7
|
registerNote,
|
|
7
8
|
registerTokens,
|
|
8
9
|
searchRegister
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7AQM3GSO.js";
|
|
10
11
|
import "./chunk-3FYJM7LH.js";
|
|
11
12
|
export {
|
|
12
13
|
buildRegisterQuery,
|
|
13
14
|
certificateLinks,
|
|
14
15
|
isRegisterShaped,
|
|
16
|
+
printedCertificateNumber,
|
|
15
17
|
queryFamily,
|
|
16
18
|
registerNote,
|
|
17
19
|
registerTokens,
|
package/dist/certificates.d.ts
CHANGED
|
@@ -20,6 +20,10 @@ export declare function isRegisterShaped(query: string): boolean;
|
|
|
20
20
|
* search filters to it, so a question about R 60 never presents R 76
|
|
21
21
|
* rows. */
|
|
22
22
|
export declare function queryFamily(query: string): string | null;
|
|
23
|
+
/** A PRINTED certificate number on a nameplate ("R76/2006-A-GB1-18.08")
|
|
24
|
+
* names the exact register row — it must not be shredded into word
|
|
25
|
+
* tokens that compete with it. */
|
|
26
|
+
export declare function printedCertificateNumber(query: string): string | null;
|
|
23
27
|
export declare function registerTokens(query: string): string[];
|
|
24
28
|
/** The register query for a token set. Numeric tokens (a model number
|
|
25
29
|
* like "190") must match as a STANDALONE value — `LIKE '%190%'` also
|
|
@@ -10,6 +10,10 @@ function queryFamily(query) {
|
|
|
10
10
|
const m = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(query);
|
|
11
11
|
return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
|
|
12
12
|
}
|
|
13
|
+
function printedCertificateNumber(query) {
|
|
14
|
+
const m = /\b([RDMB]\s?-?\d{2,3}\s?\/\s?\d{4}\s?-\s?[A-Z0-9]+\s?-\s?[^\s,;]+(?:\s?\.\s?\d+)?)/i.exec(query);
|
|
15
|
+
return m ? m[1].replace(/\s+/g, "").replace(/-\./, ".") : null;
|
|
16
|
+
}
|
|
13
17
|
function registerTokens(query) {
|
|
14
18
|
const stop = /* @__PURE__ */ new Set([
|
|
15
19
|
"the",
|
|
@@ -90,6 +94,19 @@ function buildRegisterQuery(tokens) {
|
|
|
90
94
|
}
|
|
91
95
|
async function searchRegister(db, query, force = false) {
|
|
92
96
|
if (!force && !isRegisterShaped(query)) return null;
|
|
97
|
+
const printed = printedCertificateNumber(query);
|
|
98
|
+
if (printed) {
|
|
99
|
+
try {
|
|
100
|
+
const seq = printed.replace(/\.\d+$/, "");
|
|
101
|
+
const res = await db.prepare(`SELECT num, family, holder, model, year, status, pdf_key FROM certificates WHERE REPLACE(num, ' ', '') = ?1 OR REPLACE(num, ' ', '') LIKE ?2 || '%' ORDER BY num LIMIT 6`).bind(printed, seq).all();
|
|
102
|
+
if ((res.results ?? []).length) {
|
|
103
|
+
console.log(`register-search: printed-number hit ${printed} \u2192 ${(res.results ?? []).length} rows`);
|
|
104
|
+
return { rows: res.results ?? [], tokens: [printed] };
|
|
105
|
+
}
|
|
106
|
+
console.log(`register-search: printed number ${printed} missed the register \u2014 falling back to tokens`);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
93
110
|
const tokens = registerTokens(query);
|
|
94
111
|
if (!tokens.length) return null;
|
|
95
112
|
const built = buildRegisterQuery(tokens);
|
|
@@ -129,6 +146,7 @@ ${links.join("\n")}` : "";
|
|
|
129
146
|
export {
|
|
130
147
|
isRegisterShaped,
|
|
131
148
|
queryFamily,
|
|
149
|
+
printedCertificateNumber,
|
|
132
150
|
registerTokens,
|
|
133
151
|
buildRegisterQuery,
|
|
134
152
|
searchRegister,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
certificateLinks,
|
|
73
73
|
registerNote,
|
|
74
74
|
searchRegister
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-7AQM3GSO.js";
|
|
76
76
|
import {
|
|
77
77
|
P
|
|
78
78
|
} from "./chunk-3FYJM7LH.js";
|
|
@@ -1896,7 +1896,7 @@ async function runTool(db, call) {
|
|
|
1896
1896
|
if (call.name !== "register_search") return null;
|
|
1897
1897
|
const query = String(call.args?.query ?? "").trim().slice(0, 160);
|
|
1898
1898
|
if (!query) return null;
|
|
1899
|
-
const { searchRegister: searchRegister2, registerNote: registerNote2 } = await import("./certificates-
|
|
1899
|
+
const { searchRegister: searchRegister2, registerNote: registerNote2 } = await import("./certificates-YFL4K7O3.js");
|
|
1900
1900
|
const reg = await searchRegister2(db, query, true);
|
|
1901
1901
|
const output = reg?.rows?.length ? registerNote2(reg.rows) : `No certificate was found for "${query}" in the certificates database (the register snapshot). State this as the search's result, with the searched string visible.`;
|
|
1902
1902
|
return { name: call.name, query, output };
|
|
@@ -2020,14 +2020,18 @@ ${matched.map((m) => `- ${m.text}`).join("\n")}`);
|
|
|
2020
2020
|
}
|
|
2021
2021
|
|
|
2022
2022
|
// workers/worker_public/prompts/nameplate.md
|
|
2023
|
-
var nameplate_default = `You read instrument nameplates. From the photograph, extract
|
|
2024
|
-
manufacturer's name exactly as printed and the model designation exactly
|
|
2025
|
-
as printed. Answer with ONLY a JSON object on one line:
|
|
2023
|
+
var nameplate_default = `You read instrument nameplates. From the photograph, extract:
|
|
2026
2024
|
|
|
2027
|
-
{"manufacturer": "\u2026", "model": "\u2026"}
|
|
2025
|
+
{"manufacturer": "\u2026", "model": "\u2026", "certificate_number": "\u2026"}
|
|
2028
2026
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2027
|
+
- manufacturer: the manufacturer's name exactly as printed, else null.
|
|
2028
|
+
- model: the model designation exactly as printed, else null.
|
|
2029
|
+
- certificate_number: the OIML certificate number if one is printed
|
|
2030
|
+
(it looks like R76/2006-A-GB1-18.08 \u2014 a family, an edition year, an
|
|
2031
|
+
authority code, a year and a sequence), exactly as printed, else null.
|
|
2032
|
+
|
|
2033
|
+
Answer with ONLY the JSON object on one line. Never guess beyond what is
|
|
2034
|
+
printed.
|
|
2031
2035
|
`;
|
|
2032
2036
|
|
|
2033
2037
|
// workers/worker_public/src/nameplate-parse.ts
|
|
@@ -2037,15 +2041,19 @@ function parseNameplate(text) {
|
|
|
2037
2041
|
try {
|
|
2038
2042
|
const j = JSON.parse(m[0]);
|
|
2039
2043
|
const clean = (v) => typeof v === "string" && v.trim() ? v.trim().slice(0, 80) : null;
|
|
2040
|
-
const out = {
|
|
2041
|
-
|
|
2044
|
+
const out = {
|
|
2045
|
+
manufacturer: clean(j.manufacturer),
|
|
2046
|
+
model: clean(j.model),
|
|
2047
|
+
certificate_number: clean(j.certificate_number)
|
|
2048
|
+
};
|
|
2049
|
+
return out.manufacturer || out.model || out.certificate_number ? out : null;
|
|
2042
2050
|
} catch {
|
|
2043
2051
|
return null;
|
|
2044
2052
|
}
|
|
2045
2053
|
}
|
|
2046
2054
|
function nameplateRegisterQuery(np, question) {
|
|
2047
2055
|
const fam = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(question);
|
|
2048
|
-
return [np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
2056
|
+
return [np.certificate_number, np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
2049
2057
|
}
|
|
2050
2058
|
|
|
2051
2059
|
// workers/worker_public/src/nameplate.ts
|
|
@@ -2059,9 +2067,13 @@ async function extractNameplate(ai, model, image) {
|
|
|
2059
2067
|
],
|
|
2060
2068
|
// the budget rises with the retry's effort — elevated effort under
|
|
2061
2069
|
// a flat budget starves the answer on this model (measured,
|
|
2062
|
-
// 2026-09-18: 5/38 at medium with the flat budget)
|
|
2070
|
+
// 2026-09-18: 5/38 at medium with the flat budget); the
|
|
2071
|
+
// temperature is a READING temperature — a transcription task at
|
|
2072
|
+
// default sampling hallucinated brands on a low-res plate
|
|
2073
|
+
// (observed live 2026-10-03: "Tektronix" on a CAS label)
|
|
2063
2074
|
max_tokens: attempt === 0 ? 512 : 1536,
|
|
2064
|
-
reasoning_effort: attempt === 0 ? "low" : "medium"
|
|
2075
|
+
reasoning_effort: attempt === 0 ? "low" : "medium",
|
|
2076
|
+
temperature: 0.15
|
|
2065
2077
|
});
|
|
2066
2078
|
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
2067
2079
|
const np = parseNameplate(text ?? "");
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export interface Nameplate {
|
|
2
2
|
manufacturer: string | null;
|
|
3
3
|
model: string | null;
|
|
4
|
+
certificate_number?: string | null;
|
|
4
5
|
}
|
|
5
6
|
export declare function parseNameplate(text: string): Nameplate | null;
|
|
6
|
-
/** The register query the extraction feeds:
|
|
7
|
-
* the
|
|
7
|
+
/** The register query the extraction feeds: a PRINTED CERTIFICATE NUMBER
|
|
8
|
+
* leads (it names the exact row), then the nameplate's tokens, then the
|
|
9
|
+
* question's family (if named) scopes. */
|
|
8
10
|
export declare function nameplateRegisterQuery(np: Nameplate, question: string): string;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
You read instrument nameplates. From the photograph, extract
|
|
2
|
-
manufacturer's name exactly as printed and the model designation exactly
|
|
3
|
-
as printed. Answer with ONLY a JSON object on one line:
|
|
1
|
+
You read instrument nameplates. From the photograph, extract:
|
|
4
2
|
|
|
5
|
-
{"manufacturer": "…", "model": "…"}
|
|
3
|
+
{"manufacturer": "…", "model": "…", "certificate_number": "…"}
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- manufacturer: the manufacturer's name exactly as printed, else null.
|
|
6
|
+
- model: the model designation exactly as printed, else null.
|
|
7
|
+
- certificate_number: the OIML certificate number if one is printed
|
|
8
|
+
(it looks like R76/2006-A-GB1-18.08 — a family, an edition year, an
|
|
9
|
+
authority code, a year and a sequence), exactly as printed, else null.
|
|
10
|
+
|
|
11
|
+
Answer with ONLY the JSON object on one line. Never guess beyond what is
|
|
12
|
+
printed.
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
modelGroundingBlock,
|
|
7
7
|
scoreFaithfulness,
|
|
8
8
|
scoreJudge
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-GOYFWC3N.js";
|
|
10
10
|
import {
|
|
11
11
|
handleSearch
|
|
12
12
|
} from "../../chunk-KO6DRI7C.js";
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
sha256Hex,
|
|
64
64
|
today
|
|
65
65
|
} from "../../chunk-2PCUAJJO.js";
|
|
66
|
-
import "../../chunk-
|
|
66
|
+
import "../../chunk-7AQM3GSO.js";
|
|
67
67
|
import {
|
|
68
68
|
P,
|
|
69
69
|
setProfile
|
|
@@ -959,7 +959,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
959
959
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
960
960
|
body: JSON.stringify({ ...args, stream: false })
|
|
961
961
|
});
|
|
962
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
962
|
+
const res = name === "ask" ? await (await import("../../ask-DKYIVPNV.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
|
|
963
963
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
964
964
|
});
|
|
965
965
|
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.77",
|
|
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",
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
You read instrument nameplates. From the photograph, extract
|
|
2
|
-
manufacturer's name exactly as printed and the model designation exactly
|
|
3
|
-
as printed. Answer with ONLY a JSON object on one line:
|
|
1
|
+
You read instrument nameplates. From the photograph, extract:
|
|
4
2
|
|
|
5
|
-
{"manufacturer": "…", "model": "…"}
|
|
3
|
+
{"manufacturer": "…", "model": "…", "certificate_number": "…"}
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- manufacturer: the manufacturer's name exactly as printed, else null.
|
|
6
|
+
- model: the model designation exactly as printed, else null.
|
|
7
|
+
- certificate_number: the OIML certificate number if one is printed
|
|
8
|
+
(it looks like R76/2006-A-GB1-18.08 — a family, an edition year, an
|
|
9
|
+
authority code, a year and a sequence), exactly as printed, else null.
|
|
10
|
+
|
|
11
|
+
Answer with ONLY the JSON object on one line. Never guess beyond what is
|
|
12
|
+
printed.
|
|
@@ -38,6 +38,14 @@ export function queryFamily(query: string): string | null {
|
|
|
38
38
|
return m ? `${m[1].toUpperCase()}${m[2].padStart(2, "0")}` : null;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** A PRINTED certificate number on a nameplate ("R76/2006-A-GB1-18.08")
|
|
42
|
+
* names the exact register row — it must not be shredded into word
|
|
43
|
+
* tokens that compete with it. */
|
|
44
|
+
export function printedCertificateNumber(query: string): string | null {
|
|
45
|
+
const m = /\b([RDMB]\s?-?\d{2,3}\s?\/\s?\d{4}\s?-\s?[A-Z0-9]+\s?-\s?[^\s,;]+(?:\s?\.\s?\d+)?)/i.exec(query);
|
|
46
|
+
return m ? m[1].replace(/\s+/g, "").replace(/-\./, ".") : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
export function registerTokens(query: string): string[] {
|
|
42
50
|
const stop = new Set([
|
|
43
51
|
"the", "a", "an", "is", "are", "was", "were", "still", "currently", "in", "on", "for", "of", "and", "or",
|
|
@@ -93,6 +101,26 @@ export async function searchRegister(db: any, query: string, force = false): Pro
|
|
|
93
101
|
// the shaping gate exists for ASK questions (is this a register
|
|
94
102
|
// question?); a TOOL invocation already decided — skip the gate
|
|
95
103
|
if (!force && !isRegisterShaped(query)) return null;
|
|
104
|
+
// a PRINTED certificate number names the row: query it first, and only
|
|
105
|
+
// fall back to the token arms when it misses (an extraction may drop a
|
|
106
|
+
// character; the prefix up to the sequence still scopes the family)
|
|
107
|
+
const printed = printedCertificateNumber(query);
|
|
108
|
+
if (printed) {
|
|
109
|
+
try {
|
|
110
|
+
const seq = printed.replace(/\.\d+$/, "");
|
|
111
|
+
const res = await db
|
|
112
|
+
.prepare(`SELECT num, family, holder, model, year, status, pdf_key FROM certificates WHERE REPLACE(num, ' ', '') = ?1 OR REPLACE(num, ' ', '') LIKE ?2 || '%' ORDER BY num LIMIT 6`)
|
|
113
|
+
.bind(printed, seq)
|
|
114
|
+
.all();
|
|
115
|
+
if ((res.results ?? []).length) {
|
|
116
|
+
console.log(`register-search: printed-number hit ${printed} → ${(res.results ?? []).length} rows`);
|
|
117
|
+
return { rows: (res.results ?? []) as RegisterRow[], tokens: [printed] };
|
|
118
|
+
}
|
|
119
|
+
console.log(`register-search: printed number ${printed} missed the register — falling back to tokens`);
|
|
120
|
+
} catch {
|
|
121
|
+
// fall through to the token arms
|
|
122
|
+
}
|
|
123
|
+
}
|
|
96
124
|
const tokens = registerTokens(query);
|
|
97
125
|
if (!tokens.length) return null;
|
|
98
126
|
const built = buildRegisterQuery(tokens);
|
|
@@ -6,24 +6,30 @@
|
|
|
6
6
|
export interface Nameplate {
|
|
7
7
|
manufacturer: string | null;
|
|
8
8
|
model: string | null;
|
|
9
|
+
certificate_number?: string | null;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function parseNameplate(text: string): Nameplate | null {
|
|
12
13
|
const m = /\{[^{}]*\}/.exec(String(text ?? ""));
|
|
13
14
|
if (!m) return null;
|
|
14
15
|
try {
|
|
15
|
-
const j = JSON.parse(m[0]) as { manufacturer?: unknown; model?: unknown };
|
|
16
|
+
const j = JSON.parse(m[0]) as { manufacturer?: unknown; model?: unknown; certificate_number?: unknown };
|
|
16
17
|
const clean = (v: unknown) => (typeof v === "string" && v.trim() ? v.trim().slice(0, 80) : null);
|
|
17
|
-
const out = {
|
|
18
|
-
|
|
18
|
+
const out = {
|
|
19
|
+
manufacturer: clean(j.manufacturer),
|
|
20
|
+
model: clean(j.model),
|
|
21
|
+
certificate_number: clean(j.certificate_number),
|
|
22
|
+
};
|
|
23
|
+
return out.manufacturer || out.model || out.certificate_number ? out : null;
|
|
19
24
|
} catch {
|
|
20
25
|
return null;
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
/** The register query the extraction feeds:
|
|
25
|
-
* the
|
|
29
|
+
/** The register query the extraction feeds: a PRINTED CERTIFICATE NUMBER
|
|
30
|
+
* leads (it names the exact row), then the nameplate's tokens, then the
|
|
31
|
+
* question's family (if named) scopes. */
|
|
26
32
|
export function nameplateRegisterQuery(np: Nameplate, question: string): string {
|
|
27
33
|
const fam = /\b([RDMB])\s?-?(\d{2,3})\b/i.exec(question);
|
|
28
|
-
return [np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
34
|
+
return [np.certificate_number, np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
|
|
29
35
|
}
|
|
@@ -28,9 +28,13 @@ export async function extractNameplate(ai: any, model: string, image: string): P
|
|
|
28
28
|
],
|
|
29
29
|
// the budget rises with the retry's effort — elevated effort under
|
|
30
30
|
// a flat budget starves the answer on this model (measured,
|
|
31
|
-
// 2026-09-18: 5/38 at medium with the flat budget)
|
|
31
|
+
// 2026-09-18: 5/38 at medium with the flat budget); the
|
|
32
|
+
// temperature is a READING temperature — a transcription task at
|
|
33
|
+
// default sampling hallucinated brands on a low-res plate
|
|
34
|
+
// (observed live 2026-10-03: "Tektronix" on a CAS label)
|
|
32
35
|
max_tokens: attempt === 0 ? 512 : 1536,
|
|
33
36
|
reasoning_effort: attempt === 0 ? "low" : "medium",
|
|
37
|
+
temperature: 0.15,
|
|
34
38
|
});
|
|
35
39
|
const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
|
|
36
40
|
const np = parseNameplate(text ?? "");
|