@konneal/engine 0.2.10 → 0.2.12
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-PKHGS2DZ.js → ask-J3VYAEMM.js} +1 -1
- package/dist/{chunk-ZAEUQFC2.js → chunk-B4MH5IR2.js} +23 -22
- package/dist/conditions.d.ts +9 -5
- package/dist/worker_public/src/index.js +2 -2
- package/docs/ADOPTION.md +9 -0
- package/package.json +1 -1
- package/workers/worker_public/src/ask.ts +4 -2
- package/workers/worker_public/src/conditions.ts +43 -34
|
@@ -764,39 +764,40 @@ var NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
|
764
764
|
function quantitiesIn(query) {
|
|
765
765
|
const out = {};
|
|
766
766
|
const num2 = (s) => Number(s.replace(",", "."));
|
|
767
|
-
const
|
|
768
|
-
|
|
767
|
+
const put = (kind, stated, stated_unit, si) => {
|
|
768
|
+
if (Number.isFinite(si)) out[kind] = { stated, stated_unit, si };
|
|
769
|
+
};
|
|
770
|
+
const tempC = query.match(new RegExp(`(${NUM})\\s*(?:\xB0\\s*)?C\\b`));
|
|
771
|
+
if (tempC) put("temperature", num2(tempC[1]), "degC", num2(tempC[1]) + 273.15);
|
|
772
|
+
const tempK = query.match(new RegExp(`(${NUM})\\s*K\\b`));
|
|
773
|
+
if (tempK && out.temperature === void 0) put("temperature", num2(tempK[1]), "K", num2(tempK[1]));
|
|
769
774
|
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
770
|
-
if (rh)
|
|
775
|
+
if (rh) put("relative_humidity", num2(rh[1]), "%", num2(rh[1]) / 100);
|
|
771
776
|
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
772
|
-
if (hours)
|
|
777
|
+
if (hours) put("duration", num2(hours[1]), "h", num2(hours[1]) * 3600);
|
|
773
778
|
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
774
|
-
if (days && out.duration === void 0)
|
|
779
|
+
if (days && out.duration === void 0) put("duration", num2(days[1]), "d", num2(days[1]) * 86400);
|
|
775
780
|
return out;
|
|
776
781
|
}
|
|
777
|
-
function entryBand(e) {
|
|
778
|
-
const value = Number(String(e.value).replace(",", "."));
|
|
779
|
-
const tolerance = Number(String(e.tolerance ?? "0").replace(",", "."));
|
|
780
|
-
if (!Number.isFinite(value)) return null;
|
|
781
|
-
return { value, tolerance: Number.isFinite(tolerance) ? tolerance : 0, lo: value - tolerance, hi: value + tolerance };
|
|
782
|
-
}
|
|
783
782
|
function scoreSet(entries, q) {
|
|
784
783
|
const checks = [];
|
|
785
784
|
let distance = 0;
|
|
786
785
|
let stated = 0;
|
|
787
786
|
for (const e of entries) {
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
|
|
787
|
+
const siEntry = e;
|
|
788
|
+
const statedQ = q[e.quantity_kind];
|
|
789
|
+
if (!statedQ || !siEntry?.si) continue;
|
|
790
|
+
const tol = Number(String(siEntry.tolerance ?? "0").replace(",", "."));
|
|
791
|
+
const tolSi = (Number.isFinite(tol) ? tol : 0) * (siEntry.si.unit === "K" ? 1 : siEntry.si.unit === "1" ? 0.01 : 1);
|
|
792
|
+
const in_band = statedQ.si >= siEntry.si.value - tolSi && statedQ.si <= siEntry.si.value + tolSi;
|
|
793
|
+
const gap = Math.max(0, statedQ.si - (siEntry.si.value + tolSi), siEntry.si.value - tolSi - statedQ.si);
|
|
794
|
+
distance += gap / Math.max(tolSi, 1);
|
|
795
795
|
stated += 1;
|
|
796
796
|
checks.push({
|
|
797
797
|
quantity_kind: e.quantity_kind,
|
|
798
|
-
band: `${
|
|
799
|
-
stated:
|
|
798
|
+
band: `${siEntry.si.value} ${siEntry.si.unit} \xB1${tolSi}`,
|
|
799
|
+
stated: statedQ.stated,
|
|
800
|
+
stated_unit: statedQ.stated_unit,
|
|
800
801
|
in_band
|
|
801
802
|
});
|
|
802
803
|
}
|
|
@@ -831,7 +832,7 @@ function evaluateConditionSets(nodes, query) {
|
|
|
831
832
|
verdict: "fail",
|
|
832
833
|
matched: [],
|
|
833
834
|
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
834
|
-
checks:
|
|
835
|
+
checks: nearest.checks,
|
|
835
836
|
note: `VERDICT: FAIL \u2014 no severity set admits the stated combination. The nearest set is ${nearest.node_id} (bands: ${nearest.checks.map((c) => c.band).join("; ")}). Say the combination is outside the menu and name the nearest set; never soften it.`
|
|
836
837
|
};
|
|
837
838
|
}
|
|
@@ -1636,7 +1637,7 @@ ${summary}` }] : [],
|
|
|
1636
1637
|
const stated = quantitiesIn(q.query);
|
|
1637
1638
|
if (severityWord && Object.keys(stated).length >= 1) {
|
|
1638
1639
|
const docNum = modelDocHint?.doc_number;
|
|
1639
|
-
const sql = docNum ? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard
|
|
1640
|
+
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'";
|
|
1640
1641
|
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
1641
1642
|
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
1642
1643
|
const candidates = (rows.results ?? []).filter((r) => {
|
package/dist/conditions.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export interface ConditionCheck {
|
|
|
2
2
|
quantity_kind: string;
|
|
3
3
|
band: string;
|
|
4
4
|
stated: number;
|
|
5
|
+
stated_unit: string;
|
|
5
6
|
in_band: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface ConditionVerdict {
|
|
@@ -15,11 +16,14 @@ export interface ConditionVerdict {
|
|
|
15
16
|
checks: ConditionCheck[];
|
|
16
17
|
note: string;
|
|
17
18
|
}
|
|
18
|
-
/** The question's stated quantities
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
/** The question's stated quantities with their SI normalization
|
|
20
|
+
* (temperature → K, relative_humidity → the ratio unit, duration → s).
|
|
21
|
+
* Units, never words: "12 months" is not a 12 h duration. */
|
|
22
|
+
export declare function quantitiesIn(query: string): Record<string, {
|
|
23
|
+
stated: number;
|
|
24
|
+
stated_unit: string;
|
|
25
|
+
si: number;
|
|
26
|
+
}>;
|
|
23
27
|
/** Evaluate the candidate condition_set nodes against the question's
|
|
24
28
|
* stated quantities. PASS when at least one set admits every stated
|
|
25
29
|
* quantity within its band (the set is named); FAIL names the nearest
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
handleMemories,
|
|
8
8
|
scoreJudge,
|
|
9
9
|
standardForDocNumber
|
|
10
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-B4MH5IR2.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-
|
|
982
|
+
const res = name === "ask" ? await (await import("../../ask-J3VYAEMM.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/docs/ADOPTION.md
CHANGED
|
@@ -42,6 +42,15 @@ request cannot reach internal indexes at all: the public surface holds
|
|
|
42
42
|
no binding to them, and the internal worker re-verifies every session
|
|
43
43
|
itself.
|
|
44
44
|
|
|
45
|
+
## Grounded in the literature
|
|
46
|
+
|
|
47
|
+
The execution pattern (a deterministic evaluator computes; the model
|
|
48
|
+
narrates) adopts the program-aided line — PAL (arXiv:2211.10435) and
|
|
49
|
+
Program of Thoughts (arXiv:2211.12588) — and the structured-numeric
|
|
50
|
+
retrieval lessons of TableRAG (arXiv:2410.04739, arXiv:2506.10380).
|
|
51
|
+
The deployment's research notes map every technique to its source and
|
|
52
|
+
to the code path that implements it.
|
|
53
|
+
|
|
45
54
|
## The proof
|
|
46
55
|
|
|
47
56
|
Your golden suite gates every promotion: content witnesses, refusal
|
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.
|
|
37
|
+
"version": "0.2.12",
|
|
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",
|
|
@@ -732,9 +732,11 @@ async function handleAsk(
|
|
|
732
732
|
const stated = quantitiesIn(q.query);
|
|
733
733
|
if (severityWord && Object.keys(stated).length >= 1) {
|
|
734
734
|
const docNum = modelDocHint?.doc_number;
|
|
735
|
+
// the doc number joins the STANDARD id (iec-60068-2-78), which is
|
|
736
|
+
// the doc number prefixed with the package-family prefix
|
|
735
737
|
const sql = docNum
|
|
736
|
-
? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard
|
|
737
|
-
: "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set'
|
|
738
|
+
? "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set' AND standard LIKE '%' || ?1"
|
|
739
|
+
: "SELECT standard, node_id, content FROM model_nodes WHERE kind = 'condition_set'";
|
|
738
740
|
const stmt = docNum ? env.DB.prepare(sql).bind(docNum) : env.DB.prepare(sql);
|
|
739
741
|
const rows = await stmt.all().catch(() => ({ results: [] }));
|
|
740
742
|
const candidates = (rows.results ?? []).filter((r: any) => {
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// the test-method packages as machine-verifiable membership. A
|
|
3
3
|
// condition_set node's payload.entries carry the kernel's quantity
|
|
4
4
|
// doctrine — value + unit inseparable, tolerance the SPECIFIED band —
|
|
5
|
-
//
|
|
6
|
-
// the
|
|
7
|
-
//
|
|
5
|
+
// and each entry resolves to SI through the package's own quantity
|
|
6
|
+
// register (the export ships si { value, unit }). The stated quantities
|
|
7
|
+
// normalize through the same SI targets, so 313 K and 40 °C are the
|
|
8
|
+
// same stated temperature.
|
|
8
9
|
|
|
9
10
|
export interface ConditionCheck {
|
|
10
11
|
quantity_kind: string;
|
|
11
12
|
band: string;
|
|
12
13
|
stated: number;
|
|
14
|
+
stated_unit: string;
|
|
13
15
|
in_band: boolean;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -26,55 +28,62 @@ interface Entry {
|
|
|
26
28
|
value: string | number;
|
|
27
29
|
unit: string;
|
|
28
30
|
tolerance: string | number;
|
|
31
|
+
si?: { value: number; unit: string };
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const NUM = String.raw`-?\d+(?:[.,]\d+)?`;
|
|
32
35
|
|
|
33
|
-
/** The question's stated quantities
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const out: Record<string, number> = {};
|
|
36
|
+
/** The question's stated quantities with their SI normalization
|
|
37
|
+
* (temperature → K, relative_humidity → the ratio unit, duration → s).
|
|
38
|
+
* Units, never words: "12 months" is not a 12 h duration. */
|
|
39
|
+
export function quantitiesIn(query: string): Record<string, { stated: number; stated_unit: string; si: number }> {
|
|
40
|
+
const out: Record<string, { stated: number; stated_unit: string; si: number }> = {};
|
|
39
41
|
const num = (s: string) => Number(s.replace(",", "."));
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
+
const put = (kind: string, stated: number, stated_unit: string, si: number) => {
|
|
43
|
+
if (Number.isFinite(si)) out[kind] = { stated, stated_unit, si };
|
|
44
|
+
};
|
|
45
|
+
const tempC = query.match(new RegExp(`(${NUM})\\s*(?:°\\s*)?C\\b`));
|
|
46
|
+
if (tempC) put("temperature", num(tempC[1]!), "degC", num(tempC[1]!) + 273.15);
|
|
47
|
+
const tempK = query.match(new RegExp(`(${NUM})\\s*K\\b`));
|
|
48
|
+
if (tempK && out.temperature === undefined) put("temperature", num(tempK[1]!), "K", num(tempK[1]!));
|
|
42
49
|
const rh = query.match(new RegExp(`(${NUM})\\s*%\\s*(?:RH\\b|relative\\s+humidity)?`, "i"));
|
|
43
|
-
if (rh)
|
|
50
|
+
if (rh) put("relative_humidity", num(rh[1]!), "%", num(rh[1]!) / 100);
|
|
44
51
|
const hours = query.match(new RegExp(`(${NUM})\\s*h\\b`, "i"));
|
|
45
|
-
if (hours)
|
|
52
|
+
if (hours) put("duration", num(hours[1]!), "h", num(hours[1]!) * 3600);
|
|
46
53
|
const days = query.match(new RegExp(`(${NUM})\\s*days?\\b`, "i"));
|
|
47
|
-
if (days && out.duration === undefined)
|
|
54
|
+
if (days && out.duration === undefined) put("duration", num(days[1]!), "d", num(days[1]!) * 86400);
|
|
48
55
|
return out;
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
interface SiEntry {
|
|
59
|
+
quantity_kind: string;
|
|
60
|
+
unit: string;
|
|
61
|
+
tolerance: string | number;
|
|
62
|
+
si: { value: number; unit: string };
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
/** Evaluate ONE set: every stated quantity
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
function scoreSet(entries: Entry[], q: Record<string, number>): { checks: ConditionCheck[]; distance: number; stated: number } | null {
|
|
65
|
+
/** Evaluate ONE set: every stated quantity's SI value inside the
|
|
66
|
+
* entry's SI band (the tolerance converts with the factor, never the
|
|
67
|
+
* offset — a difference has no affine part). */
|
|
68
|
+
function scoreSet(entries: (Entry | SiEntry)[], q: Record<string, { stated: number; stated_unit: string; si: number }>): { checks: ConditionCheck[]; distance: number; stated: number } | null {
|
|
62
69
|
const checks: ConditionCheck[] = [];
|
|
63
70
|
let distance = 0;
|
|
64
71
|
let stated = 0;
|
|
65
72
|
for (const e of entries) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
+
const siEntry = e as SiEntry;
|
|
74
|
+
const statedQ = q[e.quantity_kind];
|
|
75
|
+
if (!statedQ || !siEntry?.si) continue;
|
|
76
|
+
const tol = Number(String(siEntry.tolerance ?? "0").replace(",", "."));
|
|
77
|
+
const tolSi = (Number.isFinite(tol) ? tol : 0) * (siEntry.si.unit === "K" ? 1 : siEntry.si.unit === "1" ? 0.01 : 1);
|
|
78
|
+
const in_band = statedQ.si >= siEntry.si.value - tolSi && statedQ.si <= siEntry.si.value + tolSi;
|
|
79
|
+
const gap = Math.max(0, statedQ.si - (siEntry.si.value + tolSi), siEntry.si.value - tolSi - statedQ.si);
|
|
80
|
+
distance += gap / Math.max(tolSi, 1);
|
|
73
81
|
stated += 1;
|
|
74
82
|
checks.push({
|
|
75
83
|
quantity_kind: e.quantity_kind,
|
|
76
|
-
band: `${
|
|
77
|
-
stated:
|
|
84
|
+
band: `${siEntry.si.value} ${siEntry.si.unit} ±${tolSi}`,
|
|
85
|
+
stated: statedQ.stated,
|
|
86
|
+
stated_unit: statedQ.stated_unit,
|
|
78
87
|
in_band,
|
|
79
88
|
});
|
|
80
89
|
}
|
|
@@ -95,7 +104,7 @@ export function evaluateConditionSets(
|
|
|
95
104
|
const scored: { node_id: string; checks: ConditionCheck[]; distance: number }[] = [];
|
|
96
105
|
for (const n of nodes) {
|
|
97
106
|
const c = (n.content && typeof n.content === "object" ? n.content : {}) as Record<string, any>;
|
|
98
|
-
const payload = (c.payload ?? {}) as {
|
|
107
|
+
const payload = (c.payload ?? {}) as { entries?: (Entry | SiEntry)[] };
|
|
99
108
|
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
|
100
109
|
if (!entries.length) continue;
|
|
101
110
|
const s = scoreSet(entries, q);
|
|
@@ -117,7 +126,7 @@ export function evaluateConditionSets(
|
|
|
117
126
|
verdict: "fail",
|
|
118
127
|
matched: [],
|
|
119
128
|
nearest: { node_id: nearest.node_id, distance: Number(nearest.distance.toFixed(2)), bands: nearest.checks.map((c) => c.band) },
|
|
120
|
-
checks:
|
|
129
|
+
checks: nearest.checks,
|
|
121
130
|
note: `VERDICT: FAIL — no severity set admits the stated combination. The nearest set is ${nearest.node_id} (bands: ${nearest.checks.map((c) => c.band).join("; ")}). Say the combination is outside the menu and name the nearest set; never soften it.`,
|
|
122
131
|
};
|
|
123
132
|
}
|