@konneal/engine 0.2.76 → 0.2.78

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,12 +1,12 @@
1
1
  import {
2
2
  handleAsk
3
- } from "./chunk-64G44J7U.js";
3
+ } from "./chunk-JIN6Y4PD.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-VGVTIPFW.js";
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-VGVTIPFW.js";
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,
@@ -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-VGVTIPFW.js";
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-DNRUOYRF.js");
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,25 @@ ${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 the
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. Work in two steps.
2026
2024
 
2027
- {"manufacturer": "\u2026", "model": "\u2026"}
2025
+ Step 1 \u2014 transcribe: list every inscription you can actually read on the
2026
+ photograph, verbatim: names, model designations, serials, accuracy-class
2027
+ markings, capacities with units, and any certificate or approval number
2028
+ (it looks like R76/2006-A-GB1-18.08 \u2014 a family, an edition year, an
2029
+ authority code, a year and a sequence).
2028
2030
 
2029
- If the photograph shows no manufacturer or no model, use null for the
2030
- missing value. Never guess beyond what is printed.
2031
+ Step 2 \u2014 answer with ONLY a JSON object on one line:
2032
+
2033
+ {"manufacturer": "\u2026", "model": "\u2026", "certificate_number": "\u2026"}
2034
+
2035
+ - manufacturer: the manufacturer's name exactly as printed, else null.
2036
+ - model: the model designation exactly as printed, else null.
2037
+ - certificate_number: the OIML certificate number if one is printed,
2038
+ exactly as printed, else null.
2039
+
2040
+ Never guess beyond what is printed; a low-quality photograph yields nulls,
2041
+ not inventions.
2031
2042
  `;
2032
2043
 
2033
2044
  // workers/worker_public/src/nameplate-parse.ts
@@ -2037,15 +2048,19 @@ function parseNameplate(text) {
2037
2048
  try {
2038
2049
  const j = JSON.parse(m[0]);
2039
2050
  const clean = (v) => typeof v === "string" && v.trim() ? v.trim().slice(0, 80) : null;
2040
- const out = { manufacturer: clean(j.manufacturer), model: clean(j.model) };
2041
- return out.manufacturer || out.model ? out : null;
2051
+ const out = {
2052
+ manufacturer: clean(j.manufacturer),
2053
+ model: clean(j.model),
2054
+ certificate_number: clean(j.certificate_number)
2055
+ };
2056
+ return out.manufacturer || out.model || out.certificate_number ? out : null;
2042
2057
  } catch {
2043
2058
  return null;
2044
2059
  }
2045
2060
  }
2046
2061
  function nameplateRegisterQuery(np, question) {
2047
2062
  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(" ");
2063
+ return [np.certificate_number, np.manufacturer, np.model, fam ? `${fam[1]} ${fam[2]}` : null].filter(Boolean).join(" ");
2049
2064
  }
2050
2065
 
2051
2066
  // workers/worker_public/src/nameplate.ts
@@ -2055,13 +2070,22 @@ async function extractNameplate(ai, model, image) {
2055
2070
  const res = await ai.run(model, {
2056
2071
  messages: [
2057
2072
  { role: "system", content: nameplate_default.trimEnd() },
2073
+ // the question rides along: the transcription-first framing that
2074
+ // succeeds in the answer path works better WITH its context than
2075
+ // as an isolated extract-a-JSON task (the isolated form returned
2076
+ // nulls on plates the answer model read every time)
2077
+ { role: "user", content: "This photograph accompanies the user's certificate question. Transcribe what you can read, then answer with the JSON object." },
2058
2078
  { role: "user", content: [{ type: "image_url", image_url: { url: image } }] }
2059
2079
  ],
2060
2080
  // the budget rises with the retry's effort — elevated effort under
2061
2081
  // a flat budget starves the answer on this model (measured,
2062
- // 2026-09-18: 5/38 at medium with the flat budget)
2082
+ // 2026-09-18: 5/38 at medium with the flat budget); the
2083
+ // temperature is a READING temperature — a transcription task at
2084
+ // default sampling hallucinated brands on a low-res plate
2085
+ // (observed live 2026-10-03: "Tektronix" on a CAS label)
2063
2086
  max_tokens: attempt === 0 ? 512 : 1536,
2064
- reasoning_effort: attempt === 0 ? "low" : "medium"
2087
+ reasoning_effort: attempt === 0 ? "low" : "medium",
2088
+ temperature: 0.15
2065
2089
  });
2066
2090
  const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
2067
2091
  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: the nameplate's tokens lead,
7
- * the question's family (if named) scopes. */
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,19 @@
1
- You read instrument nameplates. From the photograph, extract the
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. Work in two steps.
4
2
 
5
- {"manufacturer": "…", "model": "…"}
3
+ Step 1 — transcribe: list every inscription you can actually read on the
4
+ photograph, verbatim: names, model designations, serials, accuracy-class
5
+ markings, capacities with units, and any certificate or approval number
6
+ (it looks like R76/2006-A-GB1-18.08 — a family, an edition year, an
7
+ authority code, a year and a sequence).
6
8
 
7
- If the photograph shows no manufacturer or no model, use null for the
8
- missing value. Never guess beyond what is printed.
9
+ Step 2 — answer with ONLY a JSON object on one line:
10
+
11
+ {"manufacturer": "…", "model": "…", "certificate_number": "…"}
12
+
13
+ - manufacturer: the manufacturer's name exactly as printed, else null.
14
+ - model: the model designation exactly as printed, else null.
15
+ - certificate_number: the OIML certificate number if one is printed,
16
+ exactly as printed, else null.
17
+
18
+ Never guess beyond what is printed; a low-quality photograph yields nulls,
19
+ not inventions.
@@ -6,7 +6,7 @@ import {
6
6
  modelGroundingBlock,
7
7
  scoreFaithfulness,
8
8
  scoreJudge
9
- } from "../../chunk-64G44J7U.js";
9
+ } from "../../chunk-JIN6Y4PD.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-VGVTIPFW.js";
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-PK75QJ66.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-LVUQLA3C.js")).handleSearch(env, ctx, inner, tier, key);
962
+ const res = name === "ask" ? await (await import("../../ask-IANJ4PXT.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.76",
30
+ "version": "0.2.78",
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,19 @@
1
- You read instrument nameplates. From the photograph, extract the
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. Work in two steps.
4
2
 
5
- {"manufacturer": "…", "model": "…"}
3
+ Step 1 — transcribe: list every inscription you can actually read on the
4
+ photograph, verbatim: names, model designations, serials, accuracy-class
5
+ markings, capacities with units, and any certificate or approval number
6
+ (it looks like R76/2006-A-GB1-18.08 — a family, an edition year, an
7
+ authority code, a year and a sequence).
6
8
 
7
- If the photograph shows no manufacturer or no model, use null for the
8
- missing value. Never guess beyond what is printed.
9
+ Step 2 — answer with ONLY a JSON object on one line:
10
+
11
+ {"manufacturer": "…", "model": "…", "certificate_number": "…"}
12
+
13
+ - manufacturer: the manufacturer's name exactly as printed, else null.
14
+ - model: the model designation exactly as printed, else null.
15
+ - certificate_number: the OIML certificate number if one is printed,
16
+ exactly as printed, else null.
17
+
18
+ Never guess beyond what is printed; a low-quality photograph yields nulls,
19
+ not inventions.
@@ -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 = { manufacturer: clean(j.manufacturer), model: clean(j.model) };
18
- return out.manufacturer || out.model ? out : null;
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: the nameplate's tokens lead,
25
- * the question's family (if named) scopes. */
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
  }
@@ -24,13 +24,22 @@ export async function extractNameplate(ai: any, model: string, image: string): P
24
24
  const res: any = await ai.run(model, {
25
25
  messages: [
26
26
  { role: "system", content: nameplatePrompt.trimEnd() },
27
+ // the question rides along: the transcription-first framing that
28
+ // succeeds in the answer path works better WITH its context than
29
+ // as an isolated extract-a-JSON task (the isolated form returned
30
+ // nulls on plates the answer model read every time)
31
+ { role: "user", content: "This photograph accompanies the user's certificate question. Transcribe what you can read, then answer with the JSON object." },
27
32
  { role: "user", content: [{ type: "image_url", image_url: { url: image } }] },
28
33
  ],
29
34
  // the budget rises with the retry's effort — elevated effort under
30
35
  // a flat budget starves the answer on this model (measured,
31
- // 2026-09-18: 5/38 at medium with the flat budget)
36
+ // 2026-09-18: 5/38 at medium with the flat budget); the
37
+ // temperature is a READING temperature — a transcription task at
38
+ // default sampling hallucinated brands on a low-res plate
39
+ // (observed live 2026-10-03: "Tektronix" on a CAS label)
32
40
  max_tokens: attempt === 0 ? 512 : 1536,
33
41
  reasoning_effort: attempt === 0 ? "low" : "medium",
42
+ temperature: 0.15,
34
43
  });
35
44
  const text = typeof res?.response === "string" ? res.response : res?.choices?.[0]?.message?.content;
36
45
  const np = parseNameplate(text ?? "");