@konneal/engine 0.2.16 → 0.2.18

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-XHJI73N2.js";
3
+ } from "./chunk-PBXE2LFD.js";
4
4
  import "./chunk-7UT76RQO.js";
5
5
  import "./chunk-EFQALN2Z.js";
6
6
  import "./chunk-DBBGOOMZ.js";
@@ -931,19 +931,40 @@ function cellValue(row, cols, col) {
931
931
  const raw = row[idx] ?? "";
932
932
  return num2(raw) ?? String(raw).trim();
933
933
  }
934
- function pickTable(nodes, queryLower) {
934
+ function classValueExists(rows, cols, token) {
935
+ const cc = classColumn(cols);
936
+ if (!cc) return false;
937
+ const idx = cols.indexOf(cc);
938
+ return rows.some((r) => String(r[idx] ?? "").trim().toLowerCase() === token);
939
+ }
940
+ function statedUnits(query) {
941
+ const out = /* @__PURE__ */ new Set();
942
+ const re = new RegExp(`(${NUM2})\\s*([%\xB0a-zA-Z][a-zA-Z/.%\xB0]*\\b)`, "g");
943
+ for (const m of query.matchAll(re)) {
944
+ if (m[2]) out.add(m[2].replace("\u2062", "").trim());
945
+ }
946
+ return out;
947
+ }
948
+ function pickTable(nodes, query) {
949
+ const queryLower = query.toLowerCase();
950
+ const stated = statedUnits(query);
951
+ const cToken = classToken(query);
935
952
  let best = null;
936
953
  for (const n of nodes) {
937
954
  const c = n.content ?? {};
938
955
  const payload = c.payload ?? {};
939
956
  if (!Array.isArray(payload.rows) || !payload.rows.length) continue;
957
+ const cols = Array.isArray(payload.columns) ? payload.columns : [];
958
+ const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r));
940
959
  let score = 0;
941
960
  for (const t of tokens(n.node_id.replace("/table/", ""))) {
942
961
  if (t.length >= 3 && queryLower.includes(t)) score += 2;
943
962
  }
944
- for (const w of String(c.name ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
963
+ for (const w of String(c.name ?? c.definition ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
945
964
  if (w.length >= 4 && queryLower.includes(w)) score += 1;
946
965
  }
966
+ if (intervalPairs(cols).some((p) => p.low.unit && stated.has(p.low.unit))) score += 3;
967
+ if (cToken && (classAsColumn(cols, cToken) || classValueExists(rows, cols, cToken))) score += 2;
947
968
  if (!best || score > best.score) best = { node: n, score };
948
969
  }
949
970
  if (best && best.score > 0) return best.node;
@@ -953,14 +974,16 @@ function evaluateAggregation(nodes, query) {
953
974
  const qLower = query.toLowerCase();
954
975
  const operation = /\bhow many\b|\bnumber of\b/.test(qLower) ? "count" : /\b(minimum|smallest|shortest|lowest|least)\b/.test(qLower) ? "min" : /\b(maximum|largest|longest|highest|greatest)\b/.test(qLower) ? "max" : "lookup";
955
976
  if (!nodes.length) return null;
956
- const node = pickTable(nodes, qLower);
977
+ const node = pickTable(nodes, query);
957
978
  if (!node) return null;
958
979
  const content = node.content && typeof node.content === "object" ? node.content : {};
959
980
  const payload = content.payload ?? {};
960
981
  const cols = Array.isArray(payload.columns) ? payload.columns : [];
961
982
  const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r));
962
983
  if (!cols.length || !rows.length) return null;
963
- const tableTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
984
+ const rawTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
985
+ const cut = rawTitle.indexOf(" (");
986
+ const tableTitle = cut > 0 ? rawTitle.slice(0, cut) : rawTitle.slice(0, 120);
964
987
  const cite = (what) => `COMPUTED (${operation}) \u2014 ${what}, read from the typed table "${tableTitle}" (${node.node_id}). Present this result and cite the table's clause; the value is machine-computed from the table payload, do not recompute or round it differently.`;
965
988
  if (operation === "count") {
966
989
  const cc2 = classColumn(cols);
@@ -1843,6 +1866,17 @@ ${summary}` }] : [],
1843
1866
  const modelNote = boundModel && !boundModel.gated ? modelGroundingBlock(boundModel) : void 0;
1844
1867
  const machineVerdict = boundModel && !boundModel.gated ? evaluate(boundModel.content, q.query) : null;
1845
1868
  const machineNote = machineVerdict && boundModel ? verdictNote(machineVerdict, boundModel) : void 0;
1869
+ const modelNodeRows = async (kind, docNum) => {
1870
+ const attempt = (num3) => {
1871
+ const sql = num3 ? `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}' AND standard LIKE '%' || ?1` : `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}'`;
1872
+ return num3 ? env.DB.prepare(sql).bind(num3) : env.DB.prepare(sql);
1873
+ };
1874
+ let rows = await attempt(docNum).all().catch(() => ({ results: [] }));
1875
+ if (!(rows.results ?? []).length && docNum && docNum.includes("-")) {
1876
+ rows = await attempt(docNum.replace(/-\d+$/, "")).all().catch(() => ({ results: [] }));
1877
+ }
1878
+ return rows;
1879
+ };
1846
1880
  let conditionVerdict = null;
1847
1881
  let conditionStandard = null;
1848
1882
  if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
@@ -1851,9 +1885,7 @@ ${summary}` }] : [],
1851
1885
  const stated = quantitiesIn(q.query);
1852
1886
  if (severityWord && Object.keys(stated).length >= 1) {
1853
1887
  const docNum = modelDocHint?.doc_number;
1854
- const sql = docNum ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard LIKE '%' || ?1" : "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set'";
1855
- const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
1856
- const rows = await stmt.all().catch(() => ({ results: [] }));
1888
+ const rows = await modelNodeRows("condition_set", docNum);
1857
1889
  const candidates = (rows.results ?? []).filter((r) => {
1858
1890
  const entry = licensedEntryForPackage(String(r.standard));
1859
1891
  return !entry || (standardKeys?.has(entry.key) ?? false);
@@ -1902,9 +1934,7 @@ ${summary}` }] : [],
1902
1934
  let aggregationStandard = null;
1903
1935
  if (!machineVerdict && !boundModel && !conditionVerdict && P().publisher.features?.model_plane) {
1904
1936
  const docNum = modelDocHint?.doc_number;
1905
- const sql = docNum ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table' AND standard LIKE '%' || ?1" : "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table'";
1906
- const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
1907
- const rows = await stmt.all().catch(() => ({ results: [] }));
1937
+ const rows = await modelNodeRows("table", docNum);
1908
1938
  const candidates = (rows.results ?? []).filter((r) => {
1909
1939
  const entry = licensedEntryForPackage(String(r.standard));
1910
1940
  return !entry || (standardKeys?.has(entry.key) ?? false);
@@ -7,7 +7,7 @@ import {
7
7
  handleMemories,
8
8
  scoreJudge,
9
9
  standardForDocNumber
10
- } from "../../chunk-XHJI73N2.js";
10
+ } from "../../chunk-PBXE2LFD.js";
11
11
  import {
12
12
  buildMessages,
13
13
  citations,
@@ -979,7 +979,7 @@ async function handleMcp(env, ctx, req, tier, key) {
979
979
  // stream:false forces the JSON lane (anon defaults to SSE)
980
980
  body: JSON.stringify({ ...args, stream: false })
981
981
  });
982
- const res = name === "ask" ? await (await import("../../ask-MMJHOZ6Y.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RFKT7ZZY.js")).handleSearch(env, ctx, inner, tier, key);
982
+ const res = name === "ask" ? await (await import("../../ask-4O753Y4T.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-RFKT7ZZY.js")).handleSearch(env, ctx, inner, tier, key);
983
983
  return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
984
984
  });
985
985
  if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
package/package.json CHANGED
@@ -34,7 +34,7 @@
34
34
  "@oimlsmart/oiml-pubid": "^1.2.1"
35
35
  },
36
36
  "dependencies": {},
37
- "version": "0.2.16",
37
+ "version": "0.2.18",
38
38
  "description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
39
39
  "license": "BSD-3-Clause",
40
40
  "type": "module",
@@ -140,19 +140,48 @@ function cellValue(row: string[], cols: Column[], col: Column): number | string
140
140
  return num(raw) ?? String(raw).trim();
141
141
  }
142
142
 
143
- function pickTable(nodes: { node_id: string; content: any }[], queryLower: string): { node_id: string; content: any } | null {
143
+ /** The named class exists as a row VALUE of the table's class column
144
+ * (mpe_tiers' accuracy_class A/B/C/D rows). */
145
+ function classValueExists(rows: string[][], cols: Column[], token: string): boolean {
146
+ const cc = classColumn(cols);
147
+ if (!cc) return false;
148
+ const idx = cols.indexOf(cc);
149
+ return rows.some((r) => String(r[idx] ?? "").trim().toLowerCase() === token);
150
+ }
151
+
152
+ function statedUnits(query: string): Set<string> {
153
+ const out = new Set<string>();
154
+ const re = new RegExp(`(${NUM})\\s*([%°a-zA-Z][a-zA-Z/.%°]*\\b)`,"g");
155
+ for (const m of query.matchAll(re)) {
156
+ if (m[2]) out.add(m[2].replace("\u2062", "").trim());
157
+ }
158
+ return out;
159
+ }
160
+
161
+ function pickTable(nodes: { node_id: string; content: any }[], query: string): { node_id: string; content: any } | null {
162
+ const queryLower = query.toLowerCase();
163
+ const stated = statedUnits(query);
164
+ const cToken = classToken(query);
144
165
  let best: { node: { node_id: string; content: any }; score: number } | null = null;
145
166
  for (const n of nodes) {
146
167
  const c = n.content ?? {};
147
168
  const payload = (c.payload ?? {}) as { columns?: Column[]; rows?: unknown[] };
148
169
  if (!Array.isArray(payload.rows) || !payload.rows.length) continue;
170
+ const cols = (Array.isArray(payload.columns) ? payload.columns : []) as Column[];
171
+ const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r)) as string[][];
149
172
  let score = 0;
150
173
  for (const t of tokens(n.node_id.replace("/table/", ""))) {
151
174
  if (t.length >= 3 && queryLower.includes(t)) score += 2;
152
175
  }
153
- for (const w of String(c.name ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
176
+ // the D1 projection keeps the title under `definition`
177
+ for (const w of String(c.name ?? c.definition ?? "").toLowerCase().split(/[^a-z0-9.]+/)) {
154
178
  if (w.length >= 4 && queryLower.includes(w)) score += 1;
155
179
  }
180
+ // the question's stated unit existing as this table's interval unit,
181
+ // and the named class resolving here, are the strongest signals —
182
+ // a table without either cannot hold the lookup
183
+ if (intervalPairs(cols).some((p) => p.low.unit && stated.has(p.low.unit))) score += 3;
184
+ if (cToken && (classAsColumn(cols, cToken) || classValueExists(rows, cols, cToken))) score += 2;
156
185
  if (!best || score > best.score) best = { node: n, score };
157
186
  }
158
187
  // an un-scored pick across several tables would be a guess: refuse
@@ -177,14 +206,19 @@ export function evaluateAggregation(
177
206
  ? "max"
178
207
  : "lookup";
179
208
  if (!nodes.length) return null;
180
- const node = pickTable(nodes, qLower);
209
+ const node = pickTable(nodes, query);
210
+
181
211
  if (!node) return null;
182
212
  const content = (node.content && typeof node.content === "object" ? node.content : {}) as Record<string, any>;
183
213
  const payload = (content.payload ?? {}) as { columns?: Column[]; rows?: unknown[] };
184
214
  const cols = Array.isArray(payload.columns) ? payload.columns : [];
185
215
  const rows = (Array.isArray(payload.rows) ? payload.rows : []).filter((r) => Array.isArray(r)) as string[][];
186
216
  if (!cols.length || !rows.length) return null;
187
- const tableTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
217
+ const rawTitle = String(content.name ?? content.definition ?? node.node_id.replace("/table/", ""));
218
+ // display titles are cut before the normative parenthetical and at a
219
+ // word boundary — the full definition stays in the payload
220
+ const cut = rawTitle.indexOf(" (");
221
+ const tableTitle = cut > 0 ? rawTitle.slice(0, cut) : rawTitle.slice(0, 120);
188
222
 
189
223
  const cite = (what: string) =>
190
224
  `COMPUTED (${operation}) — ${what}, read from the typed table "${tableTitle}" (${node.node_id}). Present this result and cite the table's clause; the value is machine-computed from the table payload, do not recompute or round it differently.`;
@@ -725,6 +725,22 @@ async function handleAsk(
725
725
  // quantities alongside severity vocabulary; the candidate sets come
726
726
  // from the model node store (kind condition_set), license-gated like
727
727
  // every model lane.
728
+ // the model lanes' node store read: the doc hint joins the STANDARD id
729
+ // (iec-60068-2-78 carries the part; oiml-r60 does not) — when the hint
730
+ // names a part of a part-less package ("R 60-1"), retry on the stem
731
+ const modelNodeRows = async (kind: string, docNum: string | undefined) => {
732
+ const attempt = (num: string | undefined) => {
733
+ const sql = num
734
+ ? `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}' AND standard LIKE '%' || ?1`
735
+ : `SELECT standard, node_id, content FROM model_nodes WHERE kind = '${kind}'`;
736
+ return num ? env.DB.prepare(sql).bind(num) : env.DB.prepare(sql);
737
+ };
738
+ let rows = await attempt(docNum).all().catch(() => ({ results: [] }));
739
+ if (!(rows.results ?? []).length && docNum && docNum.includes("-")) {
740
+ rows = await attempt(docNum.replace(/-\d+$/, "")).all().catch(() => ({ results: [] }));
741
+ }
742
+ return rows;
743
+ };
728
744
  let conditionVerdict: ConditionVerdict | null = null;
729
745
  let conditionStandard: string | null = null;
730
746
  if (!machineVerdict && !boundModel && P().publisher.features?.model_plane) {
@@ -735,11 +751,7 @@ async function handleAsk(
735
751
  const docNum = modelDocHint?.doc_number;
736
752
  // the doc number joins the STANDARD id (iec-60068-2-78), which is
737
753
  // the doc number prefixed with the package-family prefix
738
- const sql = docNum
739
- ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard LIKE '%' || ?1"
740
- : "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set'";
741
- const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
742
- const rows = await stmt.all().catch(() => ({ results: [] }));
754
+ const rows = await modelNodeRows("condition_set", docNum);
743
755
  const candidates = (rows.results ?? []).filter((r: any) => {
744
756
  const entry = licensedEntryForPackage(String(r.standard));
745
757
  return !entry || (standardKeys?.has(entry.key) ?? false);
@@ -801,11 +813,7 @@ async function handleAsk(
801
813
  let aggregationStandard: string | null = null;
802
814
  if (!machineVerdict && !boundModel && !conditionVerdict && P().publisher.features?.model_plane) {
803
815
  const docNum = modelDocHint?.doc_number;
804
- const sql = docNum
805
- ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table' AND standard LIKE '%' || ?1"
806
- : "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'table'";
807
- const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
808
- const rows = await stmt.all().catch(() => ({ results: [] }));
816
+ const rows = await modelNodeRows("table", docNum);
809
817
  const candidates = (rows.results ?? []).filter((r: any) => {
810
818
  const entry = licensedEntryForPackage(String(r.standard));
811
819
  return !entry || (standardKeys?.has(entry.key) ?? false);