abelien-harness 0.18.0 → 0.19.0
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/package.json +1 -1
- package/skills/abelien-algorithm/contract/host-tools.generated.json +7 -7
- package/skills/abelien-algorithm/scripts/research.mjs +1 -1
- package/skills/abelien-appearance/contract/host-tools.generated.json +15 -3
- package/skills/abelien-appearance/references/actions/appearance.points.generated.md +2 -2
- package/skills/abelien-appearance/references/points.md +3 -3
- package/skills/abelien-appearance/schemas/points-reading-reply.v1.json +100 -0
- package/skills/abelien-appearance/schemas/points-reading.v1.json +7 -3
- package/skills/abelien-appearance/scripts/points.mjs +102 -8
- package/skills/abelien-appearance/scripts/schemas.mjs +1 -1
- package/skills/abelien-clarify/SKILL.md +23 -7
- package/skills/abelien-clarify/references/examples.md +13 -2
- package/skills/abelien-clarify/references/runtime-contract.md +5 -3
- package/skills/abelien-clarify/schemas/skill-reply.v1.json +57 -0
- package/skills/abelien-clarify/schemas/skill-result.v1.json +264 -2
- package/skills/abelien-clarify/scripts/check_result.py +94 -6
- package/skills/abelien-clarify/scripts/dimensions.mjs +141 -0
- package/skills/abelien-clarify/scripts/test_check_result.py +66 -27
- package/skills/abelien-clarify/scripts/validate-result.mjs +32 -9
- package/skills/abelien-hardware/SKILL.md +2 -0
- package/skills/abelien-hardware/assets/AGENTS.md +10 -0
- package/skills/abelien-hardware/assets/category-default-dimensions.v1.json +169 -0
- package/skills/abelien-hardware/domain.json +1 -1
- package/skills/abelien-hardware/references/category-default-dimensions.md +55 -0
- package/skills/abelien-hardware/schemas/category-default-dimensions.v1.json +70 -0
- package/skills/abelien-hardware/scripts/oss-wiki-search.mjs +1 -1
- package/skills/abelien-hardware/scripts/provider-chain-contract.mjs +1 -1
- package/skills/abelien-harness/contracts/README.md +1 -1
- package/skills/abelien-harness/contracts/index.d.mts +4 -2
- package/skills/abelien-harness/contracts/index.mjs +19 -14
- package/skills/abelien-harness/contracts/schemas/cross-domain.v1.json +1 -1
- package/skills/abelien-harness/runtime/README.md +1 -1
- package/skills/abelien-harness/runtime/engine.mjs +2 -2
- package/skills/abelien-harness/runtime/index.mjs +1 -1
- package/skills/abelien-harness/runtime/task-view/README.md +3 -1
- package/skills/abelien-harness/runtime/task-view/index.mjs +117 -0
- package/skills/abelien-harness/scripts/ab.mjs +1 -1
- package/skills/abelien-harness/scripts/calc/index.mjs +1 -0
- package/skills/abelien-harness/scripts/calc/sources.mjs +90 -0
- package/skills/abelien-reference/actions/reference.page.json +1 -1
- package/skills/abelien-reference/contract/host-tools.generated.json +222 -34
- package/skills/abelien-reference/fixtures/restore.final.json +13 -2
- package/skills/abelien-reference/references/actions/reference.finalize.generated.md +2 -2
- package/skills/abelien-reference/references/actions/reference.page.generated.md +6 -3
- package/skills/abelien-reference/references/actions.generated.md +1 -1
- package/skills/abelien-reference/references/research-contract.md +2 -2
- package/skills/abelien-reference/schemas/host-tools.v1.json +5 -3
- package/skills/abelien-reference/schemas/reference-evidence.v1.json +3 -2
- package/skills/abelien-reference/schemas/reference-judgement.v1.json +6 -6
- package/skills/abelien-reference/scripts/finalize.mjs +49 -43
- package/skills/abelien-reference/scripts/research.mjs +1 -1
- package/skills/abelien-structure/SKILL.md +1 -1
- package/skills/abelien-structure/actions/structure.cad-check.json +1 -1
- package/skills/abelien-structure/actions/structure.layout-check.json +6 -1
- package/skills/abelien-structure/contract/host-tools.generated.json +7008 -2234
- package/skills/abelien-structure/flows/user-journey-cad.json +7 -2
- package/skills/abelien-structure/references/actions/structure.assets-request.generated.md +15 -0
- package/skills/abelien-structure/references/actions/structure.cad-assemble.generated.md +49 -6
- package/skills/abelien-structure/references/actions/structure.cad-check.generated.md +45 -1
- package/skills/abelien-structure/references/actions/structure.layout-check.generated.md +44 -1
- package/skills/abelien-structure/references/actions/structure.layout-request.generated.md +82 -9
- package/skills/abelien-structure/references/actions/structure.prelude.generated.md +17 -0
- package/skills/abelien-structure/references/cad-functions.generated.md +76 -23
- package/skills/abelien-structure/references/cad-modules.md +25 -4
- package/skills/abelien-structure/references/skeleton.md +7 -4
- package/skills/abelien-structure/references/structure-reasonableness.md +4 -4
- package/skills/abelien-structure/schemas/cad-assemble-input.v1.json +27 -2
- package/skills/abelien-structure/schemas/cad-build.v1.json +27 -0
- package/skills/abelien-structure/schemas/cad-modules.v1.json +5 -6
- package/skills/abelien-structure/schemas/cad-source.v1.json +6 -1
- package/skills/abelien-structure/schemas/layout-check-result.v1.json +2 -1
- package/skills/abelien-structure/schemas/layout-request-input.v1.json +103 -2
- package/skills/abelien-structure/schemas/layout-request.v1.json +30 -2
- package/skills/abelien-structure/schemas/prelude.v1.json +9 -0
- package/skills/abelien-structure/schemas/skeleton.v1.json +2 -1
- package/skills/abelien-structure/scripts/cad.mjs +359 -37
- package/skills/abelien-structure/scripts/layout.mjs +191 -27
- package/skills/abelien-structure/scripts/source-limits.mjs +5 -0
- package/skills/abelien-structure-worker/domain.json +3 -0
- package/skills/abelien-structure-worker/worker/abelien_cad.py +199 -34
- package/skills/abelien-structure-worker/worker/cad.py +92 -35
- package/skills/abelien-structure-worker/worker/jobs.mjs +5 -2
- package/skills/abelien-structure-worker/worker/main.mjs +5 -3
- package/skills/abelien-structure-worker/worker/source-contract.mjs +26 -19
- package/skills/abelien-workspace/actions/calc.evaluate.json +1 -1
- package/skills/abelien-workspace/assets/creation-start-flow.json +4 -1
- package/skills/abelien-workspace/assets/journey-stage-plan.json +61 -7
- package/skills/abelien-workspace/contract/host-tools.generated.json +1173 -45
- package/skills/abelien-workspace/flows/creation.create.json +73 -17
- package/skills/abelien-workspace/flows/creation.modify.json +73 -17
- package/skills/abelien-workspace/flows/creation.remix.json +4 -2
- package/skills/abelien-workspace/flows/creation.restore.json +73 -17
- package/skills/abelien-workspace/references/actions/calc.evaluate.generated.md +1 -1
- package/skills/abelien-workspace/references/actions/workspace.hardware-reselect-input.generated.md +49 -0
- package/skills/abelien-workspace/references/actions/workspace.intake-complete.generated.md +49 -0
- package/skills/abelien-workspace/references/actions/workspace.intake-route.generated.md +50 -1
- package/skills/abelien-workspace/references/actions/workspace.intake-wait.generated.md +49 -0
- package/skills/abelien-workspace/references/actions.generated.md +1 -1
- package/skills/abelien-workspace/references/creation-intake.md +1 -1
- package/skills/abelien-workspace/references/journey-stage-plan.md +11 -10
- package/skills/abelien-workspace/references/product-flow.md +4 -4
- package/skills/abelien-workspace/schemas/skeleton-model-reply.v1.json +3 -3
- package/skills/abelien-workspace/schemas/skeleton-reply.v1.json +37 -20
- package/skills/abelien-workspace/scripts/calc.mjs +10 -57
- package/skills/abelien-workspace/scripts/creation-intake.mjs +22 -8
- package/skills/abelien-workspace/scripts/creation-tasks.mjs +81 -23
- package/skills/abelien-workspace/scripts/flow-compile.mjs +6 -1
- package/skills/abelien-workspace/scripts/plans.mjs +6 -3
package/package.json
CHANGED
|
@@ -710,7 +710,7 @@
|
|
|
710
710
|
]
|
|
711
711
|
},
|
|
712
712
|
"source": {
|
|
713
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
713
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
714
714
|
"type": "object",
|
|
715
715
|
"oneOf": [
|
|
716
716
|
{
|
|
@@ -996,7 +996,7 @@
|
|
|
996
996
|
]
|
|
997
997
|
},
|
|
998
998
|
"source": {
|
|
999
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
999
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
1000
1000
|
"type": "object",
|
|
1001
1001
|
"oneOf": [
|
|
1002
1002
|
{
|
|
@@ -1282,7 +1282,7 @@
|
|
|
1282
1282
|
]
|
|
1283
1283
|
},
|
|
1284
1284
|
"source": {
|
|
1285
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
1285
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
1286
1286
|
"type": "object",
|
|
1287
1287
|
"oneOf": [
|
|
1288
1288
|
{
|
|
@@ -1568,7 +1568,7 @@
|
|
|
1568
1568
|
]
|
|
1569
1569
|
},
|
|
1570
1570
|
"source": {
|
|
1571
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
1571
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
1572
1572
|
"type": "object",
|
|
1573
1573
|
"oneOf": [
|
|
1574
1574
|
{
|
|
@@ -1854,7 +1854,7 @@
|
|
|
1854
1854
|
]
|
|
1855
1855
|
},
|
|
1856
1856
|
"source": {
|
|
1857
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
1857
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
1858
1858
|
"type": "object",
|
|
1859
1859
|
"oneOf": [
|
|
1860
1860
|
{
|
|
@@ -2140,7 +2140,7 @@
|
|
|
2140
2140
|
]
|
|
2141
2141
|
},
|
|
2142
2142
|
"source": {
|
|
2143
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
2143
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
2144
2144
|
"type": "object",
|
|
2145
2145
|
"oneOf": [
|
|
2146
2146
|
{
|
|
@@ -2438,7 +2438,7 @@
|
|
|
2438
2438
|
]
|
|
2439
2439
|
},
|
|
2440
2440
|
"source": {
|
|
2441
|
-
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input
|
|
2441
|
+
"description": "带值操作数的来源,三选一。result:本步之前某个动作结果里的 JSON 指针;input:本节点任务视图 work 里的 JSON 指针,从 work 的根写起(例如 /fixed_input/tolerances/contact_mm);这两种的值在动作调用时由任务视图装配器核对(错了以可纠正的码拒),回复时由节点定稿复核(verifyCalcReceipts)。choice:模型的选择或假设,写依据;只要有一个操作数是 choice,结果就标为估算。",
|
|
2442
2442
|
"type": "object",
|
|
2443
2443
|
"oneOf": [
|
|
2444
2444
|
{
|
|
@@ -158,7 +158,7 @@ export async function researchAlgorithms({ input, env = {}, fetchImpl, readResou
|
|
|
158
158
|
const begin = await beginPaid(registry, "query", key, body, { waitMs: LIMITS.searchTimeoutMs + 5000 });
|
|
159
159
|
if (begin.hit) {
|
|
160
160
|
entry = { id: `s${index + 1}`, reaction_id: q.reaction_id, component_id: q.component_id, query: q.query, count: q.count, provider: client.provider, request: body, dispatch_state: "registry",
|
|
161
|
-
|
|
161
|
+
status: "completed", error_code: null, outcome: "received", request_id: null, receipt_sha256: null, receipt_blob_ref: null, results: [], registry: { ...registryRef, hit: true } };
|
|
162
162
|
searches[index] = entry;
|
|
163
163
|
keepReceipt(entry, begin.hit.receipt);
|
|
164
164
|
entry.results = webResults(begin.hit.receipt.response);
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
},
|
|
287
287
|
"reading": {
|
|
288
288
|
"title": "abelien.appearance.points-reading.v1",
|
|
289
|
-
"description": "
|
|
289
|
+
"description": "冻结的读图结果:模型回复 points-reading-reply.v1(图像像素里的面四角与特征外接框)由读图节点定稿用代码按单应变换换算成面内比例后存成本形状,模型不写比例或毫米(模型算术 PT-1/PT-2)。报告图上看到的开孔、窗口、按键、接口等特征及其所在面、位置、尺寸、置信度、装饰判断,以及能对上的需求 reaction_id;不填 slot_id——槽位由 appearance.points 按 reaction → 槽位 → 分类确定性配对。image_digest 必须等于本次输入图片引用的摘要。看不清时 outcome 为 unreadable,不带任何坐标。规则见 references/points.md。",
|
|
290
290
|
"type": "object",
|
|
291
291
|
"required": [
|
|
292
292
|
"schema",
|
|
@@ -390,7 +390,7 @@
|
|
|
390
390
|
"type": "array",
|
|
391
391
|
"maxItems": 64,
|
|
392
392
|
"items": {
|
|
393
|
-
"description": "模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back
|
|
393
|
+
"description": "模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back 的边为上边。值是该面宽高的比例,由代码从模型给的像素外接框与面四角换算。",
|
|
394
394
|
"type": "object",
|
|
395
395
|
"required": [
|
|
396
396
|
"point_id",
|
|
@@ -537,7 +537,19 @@
|
|
|
537
537
|
"then": {
|
|
538
538
|
"properties": {
|
|
539
539
|
"coordinate_frame": {
|
|
540
|
-
"
|
|
540
|
+
"description": "读图换算后的坐标系恒为面内比例:原点在该面左上角,scale_basis none。",
|
|
541
|
+
"type": "object",
|
|
542
|
+
"properties": {
|
|
543
|
+
"unit": {
|
|
544
|
+
"const": "normalized"
|
|
545
|
+
},
|
|
546
|
+
"origin": {
|
|
547
|
+
"const": "face_top_left"
|
|
548
|
+
},
|
|
549
|
+
"scale_basis": {
|
|
550
|
+
"const": "none"
|
|
551
|
+
}
|
|
552
|
+
}
|
|
541
553
|
},
|
|
542
554
|
"visible_faces": {
|
|
543
555
|
"minItems": 1
|
|
@@ -23,7 +23,7 @@ appearance.points 的输入:本次读的图片与需求快照的引用、需
|
|
|
23
23
|
| `reaction_ids` | array<Identifier> | 是 | 至多 256 项;不重复 |
|
|
24
24
|
| `slots` | array<Slot> | 否 | 1–128 项 |
|
|
25
25
|
| `slots_ref` | Ref | 否 | 不可变对象引用:id 固定一条谱系,version 固定其中一个版本,digest 是该版本内容的摘要。设计里写的 ref@version 指的就是这个对象;摘要用来区分同版本号下内容不同的对象(例如同一需求修订下重画的图) |
|
|
26
|
-
| `reading` | object | 是 |
|
|
26
|
+
| `reading` | object | 是 | 冻结的读图结果:模型回复 points-reading-reply.v1(图像像素里的面四角与特征外接框)由读图节点定稿用代码按单应变换换算成面内比例后存成本形状,模型不写比例或毫米(模型算术 PT-1/PT-2)。报告图上看到的开孔、窗口、按键、接口等特征及其所在面、位置、尺寸、置信度、装饰判断,以及能对上的需求 reaction_id;不填 slot_id——槽位由 appearance.points 按 reaction → 槽位 → 分类确定性配对。image_digest 必须等于本次输入图片引用的摘要。看不清时 outcome 为 unreadable,不带任何坐标。规则见 references/points.md |
|
|
27
27
|
| `reading.schema` | string | 是 | 固定 `"appearance.points-reading.v1"` |
|
|
28
28
|
| `reading.image_digest` | Digest | 是 | sha256 小写十六进制(64 位,不带 sha256: 前缀)。对 JSON 值一律取 harness scripts/web/common.mjs 的 digest(),即对 canonical()(键排序、无空白)后的字节求 sha256 |
|
|
29
29
|
| `reading.outcome` | string | 是 | read:读出了结果(可以是 0 个点位);unreadable:图看不清或不是产品外观图;取值 `"read"` / `"unreadable"` |
|
|
@@ -109,7 +109,7 @@ string;sha256 小写十六进制(64 位,不带 sha256: 前缀)。对 JSO
|
|
|
109
109
|
|
|
110
110
|
### ReadingPoint
|
|
111
111
|
|
|
112
|
-
模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back
|
|
112
|
+
模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back 的边为上边。值是该面宽高的比例,由代码从模型给的像素外接框与面四角换算。
|
|
113
113
|
|
|
114
114
|
| 参数 | 类型 | 必填 | 说明 |
|
|
115
115
|
| --- | --- | --- | --- |
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
## 第一步:读图节点(模型)
|
|
12
12
|
|
|
13
|
-
输入:图片、它的引用(含摘要)、需求里每条 reaction 的 id 与文字(任务视图的 `reactions
|
|
13
|
+
输入:图片、它的引用(含摘要)、需求里每条 reaction 的 id 与文字(任务视图的 `reactions`);有槽位表时一并给出,帮助理解哪些位置会有零件。回复符合 [points-reading-reply.v1](../schemas/points-reading-reply.v1.json):全部用图像像素写;读图节点定稿用代码把它换算成面内比例,冻结为 [points-reading.v1](../schemas/points-reading.v1.json)。
|
|
14
14
|
|
|
15
15
|
规则:
|
|
16
16
|
|
|
17
|
-
1. **只报看得见的面。** 先写 `
|
|
17
|
+
1. **只报看得见的面。** 先写 `faces`:每个看得见的面一条,带它在图里的四个角;点位只能落在这些面上,看不到的面不猜,写进 `unknowns`。只在某个面的轮廓边缘露出的特征(例如正视图里顶面灯环只在顶缘露出一条亮带)属于边那一侧的面:那个面看得见就写在那个面上,看不见就不给点位,写进 `unknowns`。中心离所在面的边不到面宽高 2% 的特征会被拒收。
|
|
18
18
|
2. **每个特征一个点位。** `kind` 取 camera、display、button、port、speaker、microphone、light、sensor、vent、other 之一;`evidence` 用一句话写看到了什么。分不清是网孔还是纹理时降低 `confidence` 并写进 `unknowns`,不要硬判。
|
|
19
|
-
3.
|
|
19
|
+
3. **只写像素,不做换算。** 所有坐标都在 `image_px` 这个像素网格里(原点在图片左上角,x 向右、y 向下)。每个面的四个角按从外侧看该面时的左上、右上、右下、左下写;top 面以靠 back 的边为上边,bottom 面从下方看、同样以靠 back 的边为上边。圆形产品(圆柱外壳)的 front、back、left、right 取从该方向看到的轮廓的外接框四角,top、bottom 是圆面。每个特征给图里的外接框 `box_px`,看不出大小时只给中心 `center_px`。面内比例由代码按该面四角的单应变换算出:特征越出所在面不超过面宽高的 2% 时截到面内,更多就拒收(多半是面的四角或所在面写错了)。不写比例、不写毫米——单张图没有可靠的尺度。
|
|
20
20
|
4. **装饰与功能。** 饰圈、纹理、logo 这类后面不需要零件的特征标 `decorative: true`,`reaction_id` 写 null。功能特征能对上需求的,`reaction_id` 只能用输入里给的 id;对不上写 null。
|
|
21
21
|
5. **不填槽位。** 读图输出没有 `slot_id`,槽位由下一步确定性配对。
|
|
22
22
|
6. **看不清就说看不清。** 图里看不到产品外观、太暗、只有包装时,`outcome` 写 `unreadable` 并说明原因,不给任何点位或坐标系。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://git.abelien.cn/abelien/abelienharness/skills/abelien-appearance/schemas/points-reading-reply.v1.json",
|
|
4
|
+
"title": "abelien.appearance.points-reading-reply.v1",
|
|
5
|
+
"description": "读图节点(视觉模型)的回复:只报看到的东西,全部用图像像素写,不做任何换算。image_px 是下面所有坐标所在的像素网格(宽、高);faces 是图里看得见的每个面和它的四个角(从外侧看该面的左上、右上、右下、左下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back 的边为上边);每个特征给外接框 box_px,看不出大小时只给中心 center_px。由代码按每个面四角的单应变换换算成面内比例(appearance.points-reading.v1)并做越界检查(模型算术 PT-1/PT-2)。不填 slot_id。image_digest 原样抄输入图片引用的摘要。看不清时 outcome 写 unreadable 并说明原因,不给任何坐标。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schema", "image_digest", "outcome", "faces", "points", "unknowns"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": { "const": "appearance.points-reading-reply.v1" },
|
|
10
|
+
"image_digest": { "$ref": "../../abelien-harness/contracts/schemas/ref.v1.json#/$defs/Digest" },
|
|
11
|
+
"outcome": { "description": "read:读出了结果(可以是 0 个点位);unreadable:图看不清或不是产品外观图。", "enum": ["read", "unreadable"] },
|
|
12
|
+
"unreadable_reason": { "type": "string", "minLength": 1, "maxLength": 500, "pattern": "\\S" },
|
|
13
|
+
"image_px": {
|
|
14
|
+
"description": "坐标所在的像素网格:图片的宽和高(像素)。所有角点、外接框、中心都在这个网格里,原点在图片左上角,x 向右、y 向下。",
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["width", "height"],
|
|
17
|
+
"properties": { "width": { "type": "integer", "minimum": 1, "maximum": 100000 }, "height": { "type": "integer", "minimum": 1, "maximum": 100000 } },
|
|
18
|
+
"additionalProperties": false
|
|
19
|
+
},
|
|
20
|
+
"faces": {
|
|
21
|
+
"description": "图里看得见的面,每面一条;点位只能落在这些面上,看不到的面不写。",
|
|
22
|
+
"type": "array",
|
|
23
|
+
"maxItems": 6,
|
|
24
|
+
"items": { "$ref": "#/$defs/Face" }
|
|
25
|
+
},
|
|
26
|
+
"points": { "type": "array", "maxItems": 64, "items": { "$ref": "#/$defs/ReplyPoint" } },
|
|
27
|
+
"unknowns": {
|
|
28
|
+
"description": "读图时确定不了的事项,例如某个面不可见、某处是网孔还是纹理分不清。",
|
|
29
|
+
"type": "array",
|
|
30
|
+
"maxItems": 32,
|
|
31
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 500, "pattern": "\\S" }
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"allOf": [
|
|
36
|
+
{
|
|
37
|
+
"if": { "properties": { "outcome": { "const": "read" } }, "required": ["outcome"] },
|
|
38
|
+
"then": { "required": ["image_px"], "properties": { "faces": { "minItems": 1 }, "unreadable_reason": false } }
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"if": { "properties": { "outcome": { "const": "unreadable" } }, "required": ["outcome"] },
|
|
42
|
+
"then": { "required": ["unreadable_reason"], "properties": { "image_px": false, "faces": { "maxItems": 0 }, "points": { "maxItems": 0 } } }
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"$defs": {
|
|
46
|
+
"Pixel": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"required": ["x", "y"],
|
|
49
|
+
"properties": { "x": { "type": "number", "minimum": 0 }, "y": { "type": "number", "minimum": 0 } },
|
|
50
|
+
"additionalProperties": false
|
|
51
|
+
},
|
|
52
|
+
"Face": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"required": ["face", "corners_px"],
|
|
55
|
+
"properties": {
|
|
56
|
+
"face": { "enum": ["front", "back", "left", "right", "top", "bottom"] },
|
|
57
|
+
"corners_px": {
|
|
58
|
+
"description": "该面四个角在图像里的位置(从外侧看该面时的左上、右上、右下、左下)。圆柱外壳的 front/back/left/right 取从该方向看到的轮廓的外接框四角。",
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["top_left", "top_right", "bottom_right", "bottom_left"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"top_left": { "$ref": "#/$defs/Pixel" },
|
|
63
|
+
"top_right": { "$ref": "#/$defs/Pixel" },
|
|
64
|
+
"bottom_right": { "$ref": "#/$defs/Pixel" },
|
|
65
|
+
"bottom_left": { "$ref": "#/$defs/Pixel" }
|
|
66
|
+
},
|
|
67
|
+
"additionalProperties": false
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"additionalProperties": false
|
|
71
|
+
},
|
|
72
|
+
"ReplyPoint": {
|
|
73
|
+
"description": "模型看到的一个特征:所在面、图像像素里的外接框(看不出大小时只给中心),以及类别、置信度、装饰判断和能对上的需求 reaction_id。",
|
|
74
|
+
"type": "object",
|
|
75
|
+
"required": ["point_id", "kind", "face", "confidence", "decorative", "reaction_id", "evidence"],
|
|
76
|
+
"properties": {
|
|
77
|
+
"point_id": { "$ref": "../../abelien-harness/contracts/schemas/ref.v1.json#/$defs/Identifier" },
|
|
78
|
+
"kind": { "enum": ["camera", "display", "button", "port", "speaker", "microphone", "light", "sensor", "vent", "other"] },
|
|
79
|
+
"face": { "enum": ["front", "back", "left", "right", "top", "bottom"] },
|
|
80
|
+
"box_px": {
|
|
81
|
+
"description": "特征在图像里的外接框(像素):left < right,top < bottom。",
|
|
82
|
+
"type": "object",
|
|
83
|
+
"required": ["left", "top", "right", "bottom"],
|
|
84
|
+
"properties": {
|
|
85
|
+
"left": { "type": "number", "minimum": 0 }, "top": { "type": "number", "minimum": 0 },
|
|
86
|
+
"right": { "type": "number", "minimum": 0 }, "bottom": { "type": "number", "minimum": 0 }
|
|
87
|
+
},
|
|
88
|
+
"additionalProperties": false
|
|
89
|
+
},
|
|
90
|
+
"center_px": { "description": "看不出特征大小时只给它的中心(像素)。", "$ref": "#/$defs/Pixel" },
|
|
91
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
92
|
+
"decorative": { "description": "模型判断它只是装饰(饰圈、纹理、logo),后面不需要零件。", "type": "boolean" },
|
|
93
|
+
"reaction_id": { "description": "能对上的需求 reaction 的 id(只能用输入里给的 id);对不上或装饰写 null。", "oneOf": [{ "type": "null" }, { "$ref": "../../abelien-harness/contracts/schemas/ref.v1.json#/$defs/Identifier" }] },
|
|
94
|
+
"evidence": { "description": "看到了什么,一句话(例如“正面左下圆形网孔”)。", "type": "string", "minLength": 1, "maxLength": 300, "pattern": "\\S" }
|
|
95
|
+
},
|
|
96
|
+
"additionalProperties": false,
|
|
97
|
+
"oneOf": [{ "required": ["box_px"] }, { "required": ["center_px"] }]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://git.abelien.cn/abelien/abelienharness/skills/abelien-appearance/schemas/points-reading.v1.json",
|
|
4
4
|
"title": "abelien.appearance.points-reading.v1",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "冻结的读图结果:模型回复 points-reading-reply.v1(图像像素里的面四角与特征外接框)由读图节点定稿用代码按单应变换换算成面内比例后存成本形状,模型不写比例或毫米(模型算术 PT-1/PT-2)。报告图上看到的开孔、窗口、按键、接口等特征及其所在面、位置、尺寸、置信度、装饰判断,以及能对上的需求 reaction_id;不填 slot_id——槽位由 appearance.points 按 reaction → 槽位 → 分类确定性配对。image_digest 必须等于本次输入图片引用的摘要。看不清时 outcome 为 unreadable,不带任何坐标。规则见 references/points.md。",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["schema", "image_digest", "outcome", "visible_faces", "coordinate_frame", "points", "unknowns"],
|
|
8
8
|
"properties": {
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"if": { "properties": { "outcome": { "const": "read" } }, "required": ["outcome"] },
|
|
37
37
|
"then": {
|
|
38
38
|
"properties": {
|
|
39
|
-
"coordinate_frame": {
|
|
39
|
+
"coordinate_frame": {
|
|
40
|
+
"description": "读图换算后的坐标系恒为面内比例:原点在该面左上角,scale_basis none。",
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": { "unit": { "const": "normalized" }, "origin": { "const": "face_top_left" }, "scale_basis": { "const": "none" } }
|
|
43
|
+
},
|
|
40
44
|
"visible_faces": { "minItems": 1 },
|
|
41
45
|
"unreadable_reason": false
|
|
42
46
|
}
|
|
@@ -52,7 +56,7 @@
|
|
|
52
56
|
],
|
|
53
57
|
"$defs": {
|
|
54
58
|
"ReadingPoint": {
|
|
55
|
-
"description": "模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back
|
|
59
|
+
"description": "模型看到的一个特征。position 是特征中心,size 是特征外接矩形的宽高(看不出大小时为 null),二者都在所在面的坐标系里:原点在该面左上角(从外侧看),x 向右、y 向下;top 面以靠 back 的边为上边,bottom 面从下方看、也以靠 back 的边为上边。值是该面宽高的比例,由代码从模型给的像素外接框与面四角换算。",
|
|
56
60
|
"type": "object",
|
|
57
61
|
"required": ["point_id", "kind", "face", "position", "size", "confidence", "decorative", "reaction_id", "evidence"],
|
|
58
62
|
"properties": {
|
|
@@ -29,6 +29,99 @@ export const KIND_CATEGORIES = Object.freeze({
|
|
|
29
29
|
const EPSILON = 1e-9;
|
|
30
30
|
const invalidAt = (path, message) => failure("VALIDATION", `${path}: ${message}`, { errors: [{ path, message }] });
|
|
31
31
|
|
|
32
|
+
// ---------- 读图回复(像素)→ 冻结读图(面内比例):模型只给像素,换算由代码做(模型算术 PT-1/PT-2) ----------
|
|
33
|
+
const CORNERS = Object.freeze(["top_left", "top_right", "bottom_right", "bottom_left"]);
|
|
34
|
+
const UNIT_SQUARE = Object.freeze([[0, 0], [1, 0], [1, 1], [0, 1]]);
|
|
35
|
+
/** 特征允许越出所在面的比例(面宽高的 2%,读图的感知误差);越出更多就拒收,越出以内截到面内。 */
|
|
36
|
+
export const FACE_OVERHANG = 0.02;
|
|
37
|
+
/** 面四边形的最小面积(占图片面积的比例),再小就当作退化。 */
|
|
38
|
+
export const MIN_FACE_AREA = 1e-4;
|
|
39
|
+
const round6 = value => Math.round(value * 1e6) / 1e6 + 0;
|
|
40
|
+
|
|
41
|
+
/** 8×8 线性方程组(部分主元高斯消元);奇异时返回 null。 */
|
|
42
|
+
function solve(a, b) {
|
|
43
|
+
const n = b.length, m = a.map((row, i) => [...row, b[i]]);
|
|
44
|
+
for (let col = 0; col < n; col++) {
|
|
45
|
+
let pivot = col;
|
|
46
|
+
for (let row = col + 1; row < n; row++) if (Math.abs(m[row][col]) > Math.abs(m[pivot][col])) pivot = row;
|
|
47
|
+
if (Math.abs(m[pivot][col]) < 1e-12) return null;
|
|
48
|
+
[m[col], m[pivot]] = [m[pivot], m[col]];
|
|
49
|
+
for (let row = 0; row < n; row++) {
|
|
50
|
+
if (row === col) continue;
|
|
51
|
+
const f = m[row][col] / m[col][col];
|
|
52
|
+
for (let k = col; k <= n; k++) m[row][k] -= f * m[col][k];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return m.map((row, i) => row[n] / row[i]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 面四角(像素)→ 单位正方形的单应变换。坐标先按像素网格归一化以免病态;四边形必须按左上、右上、右下、左下顺时针排列、
|
|
60
|
+
* 凸、面积不小于图片的 MIN_FACE_AREA,否则抛 VALIDATION。返回把像素点映到面内比例的函数。
|
|
61
|
+
*/
|
|
62
|
+
function faceMapping(face, image, at) {
|
|
63
|
+
const src = CORNERS.map(key => [face.corners_px[key].x / image.width, face.corners_px[key].y / image.height]);
|
|
64
|
+
for (const key of CORNERS) { const { x, y } = face.corners_px[key];
|
|
65
|
+
if (x > image.width || y > image.height) throw invalidAt(`${at}/corners_px/${key}`, `corner (${x}, ${y}) is outside the ${image.width}×${image.height} pixel grid`); }
|
|
66
|
+
let area = 0;
|
|
67
|
+
for (let i = 0; i < 4; i++) {
|
|
68
|
+
const [p, q, r] = [src[i], src[(i + 1) % 4], src[(i + 2) % 4]];
|
|
69
|
+
const cross = (q[0] - p[0]) * (r[1] - q[1]) - (q[1] - p[1]) * (r[0] - q[0]);
|
|
70
|
+
if (cross <= 0) throw invalidAt(`${at}/corners_px`, "the four corners must be top_left, top_right, bottom_right, bottom_left of a convex face, in that clockwise order");
|
|
71
|
+
area += p[0] * q[1] - q[0] * p[1];
|
|
72
|
+
}
|
|
73
|
+
if (area / 2 < MIN_FACE_AREA) throw invalidAt(`${at}/corners_px`, "the face is degenerate (almost no area in the image)");
|
|
74
|
+
const a = [], b = [];
|
|
75
|
+
src.forEach(([x, y], i) => { const [u, v] = UNIT_SQUARE[i];
|
|
76
|
+
a.push([x, y, 1, 0, 0, 0, -u * x, -u * y]); b.push(u);
|
|
77
|
+
a.push([0, 0, 0, x, y, 1, -v * x, -v * y]); b.push(v); });
|
|
78
|
+
const h = solve(a, b);
|
|
79
|
+
if (!h) throw invalidAt(`${at}/corners_px`, "the face corners do not define a projective frame");
|
|
80
|
+
return ({ x, y }) => {
|
|
81
|
+
const [px, py] = [x / image.width, y / image.height], w = h[6] * px + h[7] * py + 1;
|
|
82
|
+
return [(h[0] * px + h[1] * py + h[2]) / w, (h[3] * px + h[4] * py + h[5]) / w];
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 读图节点模型回复(points-reading-reply.v1)→ 冻结读图(points-reading.v1):每个特征的外接框四角(或中心)按其所在面的单应变换
|
|
88
|
+
* 映到面内比例,取外接矩形为 position(中心)与 size;越出面 FACE_OVERHANG 以内截到面内,更多则拒收。像素必须在 image_px 网格内。
|
|
89
|
+
* 看不清(unreadable)原样转成不带坐标的读图,由调用方报 POINTS_UNREADABLE。
|
|
90
|
+
*/
|
|
91
|
+
export function readingFromPixels(reply) {
|
|
92
|
+
checkSchema("points-reading-reply.v1.json", reply, "points reading reply");
|
|
93
|
+
const base = { schema: "appearance.points-reading.v1", image_digest: reply.image_digest, outcome: reply.outcome };
|
|
94
|
+
if (reply.outcome === "unreadable")
|
|
95
|
+
return { ...base, unreadable_reason: reply.unreadable_reason, visible_faces: [], coordinate_frame: null, points: [], unknowns: [...reply.unknowns] };
|
|
96
|
+
const image = reply.image_px, maps = new Map();
|
|
97
|
+
reply.faces.forEach((face, i) => {
|
|
98
|
+
if (maps.has(face.face)) throw invalidAt(`/faces/${i}/face`, `face ${face.face} is listed twice`);
|
|
99
|
+
maps.set(face.face, faceMapping(face, image, `/faces/${i}`));
|
|
100
|
+
});
|
|
101
|
+
const points = reply.points.map((point, i) => {
|
|
102
|
+
const at = `/points/${i}`, map = maps.get(point.face);
|
|
103
|
+
if (!map) throw invalidAt(`${at}/face`, `face ${point.face} is not among faces; do not guess hidden faces`);
|
|
104
|
+
const pixels = point.box_px ? [{ x: point.box_px.left, y: point.box_px.top }, { x: point.box_px.right, y: point.box_px.top },
|
|
105
|
+
{ x: point.box_px.right, y: point.box_px.bottom }, { x: point.box_px.left, y: point.box_px.bottom }] : [point.center_px];
|
|
106
|
+
if (point.box_px && (point.box_px.left >= point.box_px.right || point.box_px.top >= point.box_px.bottom))
|
|
107
|
+
throw invalidAt(`${at}/box_px`, "left must be below right and top below bottom");
|
|
108
|
+
for (const { x, y } of pixels) if (x > image.width || y > image.height)
|
|
109
|
+
throw invalidAt(`${at}/${point.box_px ? "box_px" : "center_px"}`, `(${x}, ${y}) is outside the ${image.width}×${image.height} pixel grid`);
|
|
110
|
+
const mapped = pixels.map(map), xs = mapped.map(p => p[0]), ys = mapped.map(p => p[1]);
|
|
111
|
+
const beyond = Math.max(-Math.min(...xs), Math.max(...xs) - 1, -Math.min(...ys), Math.max(...ys) - 1);
|
|
112
|
+
if (beyond > FACE_OVERHANG + EPSILON)
|
|
113
|
+
throw invalidAt(`${at}/${point.box_px ? "box_px" : "center_px"}`, `the feature lies ${round6(beyond)} of the face width or height beyond face ${point.face} (at most ${FACE_OVERHANG}); check the face corners or the face`);
|
|
114
|
+
const clamp = value => Math.min(1, Math.max(0, value));
|
|
115
|
+
const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)].map(clamp);
|
|
116
|
+
const size = point.box_px ? { width: round6(x1 - x0), height: round6(y1 - y0) } : null;
|
|
117
|
+
if (size && (size.width <= 0 || size.height <= 0)) throw invalidAt(`${at}/box_px`, "the feature has no extent inside its face");
|
|
118
|
+
return { point_id: point.point_id, kind: point.kind, face: point.face, position: { x: round6((x0 + x1) / 2), y: round6((y0 + y1) / 2) }, size,
|
|
119
|
+
confidence: point.confidence, decorative: point.decorative, reaction_id: point.reaction_id, evidence: point.evidence };
|
|
120
|
+
});
|
|
121
|
+
return checkSchema("points-reading.v1.json", { ...base, visible_faces: reply.faces.map(face => face.face),
|
|
122
|
+
coordinate_frame: { unit: "normalized", origin: "face_top_left", scale_basis: "none" }, points, unknowns: [...reply.unknowns] }, "points reading");
|
|
123
|
+
}
|
|
124
|
+
|
|
32
125
|
/** 槽位的分类路径是否落在某个点位类别可承接的分类下。 */
|
|
33
126
|
export function slotServesKind(slot, kind) {
|
|
34
127
|
const path = slot.category_path.join("/");
|
|
@@ -36,7 +129,7 @@ export function slotServesKind(slot, kind) {
|
|
|
36
129
|
}
|
|
37
130
|
|
|
38
131
|
/**
|
|
39
|
-
* 读图输出里 Schema
|
|
132
|
+
* 读图输出里 Schema 表达不了的数值与引用约束(与槽位无关),含“中心不落在面的轮廓上”。读图节点提交时就校验,错误的读图不冻结;
|
|
40
133
|
* 配对动作再用同一函数校验一次。读图写了 unreadable 时报 POINTS_UNREADABLE,由调用方走降级分支。
|
|
41
134
|
*/
|
|
42
135
|
export function checkReading(reading, input) {
|
|
@@ -47,7 +140,6 @@ export function checkReading(reading, input) {
|
|
|
47
140
|
throw failure("POINTS_UNREADABLE", `the model could not read the image: ${reading.unreadable_reason}`, { reason: reading.unreadable_reason });
|
|
48
141
|
const reactions = new Set(input.reaction_ids);
|
|
49
142
|
const seen = new Set();
|
|
50
|
-
const normalized = reading.coordinate_frame.unit === "normalized";
|
|
51
143
|
reading.points.forEach((point, i) => {
|
|
52
144
|
const at = `/reading/points/${i}`;
|
|
53
145
|
if (seen.has(point.point_id)) throw invalidAt(`${at}/point_id`, `duplicate point_id ${point.point_id}`);
|
|
@@ -55,12 +147,14 @@ export function checkReading(reading, input) {
|
|
|
55
147
|
if (!reading.visible_faces.includes(point.face)) throw invalidAt(`${at}/face`, `face ${point.face} is not among visible_faces; do not guess hidden faces`);
|
|
56
148
|
if (point.reaction_id !== null && !reactions.has(point.reaction_id)) throw invalidAt(`${at}/reaction_id`, `reaction_id ${point.reaction_id} is not in the requirements`);
|
|
57
149
|
if (point.decorative && point.reaction_id !== null) throw invalidAt(`${at}/reaction_id`, "a decorative point serves no reaction");
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
150
|
+
const { x, y } = point.position, w = point.size?.width ?? 0, h = point.size?.height ?? 0;
|
|
151
|
+
if (x > 1 || y > 1) throw invalidAt(`${at}/position`, "normalized coordinates must lie within the face (0..1)");
|
|
152
|
+
if (x - w / 2 < -EPSILON || x + w / 2 > 1 + EPSILON || y - h / 2 < -EPSILON || y + h / 2 > 1 + EPSILON)
|
|
153
|
+
throw invalidAt(`${at}/size`, "the feature extends beyond its face");
|
|
154
|
+
// 中心落在面的轮廓上(离边不到 FACE_OVERHANG)的特征跨在两个面之间,是边那一侧的面上的特征在轮廓处露出的一道。
|
|
155
|
+
// E2 r29 S1:正视图顶缘的一条亮带被读成正面点位,骨架要灯环贴正面、朝正面,装在顶面的灯环两条都做不到。
|
|
156
|
+
const edge = [["left", x], ["right", 1 - x], ["top", y], ["bottom", 1 - y]].find(([, gap]) => gap < FACE_OVERHANG - EPSILON);
|
|
157
|
+
if (edge) throw invalidAt(`${at}/position`, `${point.point_id} is centred on the ${edge[0]} edge of face ${point.face} (${round6(edge[1])} of the face from it): a feature seen only at a face's outline belongs to the face beyond that edge; give it on that face when it is visible, otherwise leave the point out and say what you saw in unknowns`);
|
|
64
158
|
});
|
|
65
159
|
}
|
|
66
160
|
|
|
@@ -6,7 +6,7 @@ import { createValidator } from "../../abelien-harness/contracts/validate.mjs";
|
|
|
6
6
|
import { failure } from "../../abelien-harness/scripts/web/common.mjs";
|
|
7
7
|
|
|
8
8
|
export const SCHEMA_DIR = new URL("../schemas/", import.meta.url);
|
|
9
|
-
export const OWN_SCHEMAS = Object.freeze(["points-reading.v1.json", "points-input.v1.json", "points-result.v1.json",
|
|
9
|
+
export const OWN_SCHEMAS = Object.freeze(["points-reading.v1.json", "points-reading-reply.v1.json", "points-input.v1.json", "points-result.v1.json",
|
|
10
10
|
"points-fallback-input.v1.json", "points-fallback-result.v1.json"]);
|
|
11
11
|
|
|
12
12
|
let cached;
|
|
@@ -28,9 +28,15 @@ compatibility: 适用于 claude-code、hermes、pi 宿主
|
|
|
28
28
|
需求只用以下结构;source 区分用户事实与系统假设:
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
type
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
type Dimensions = { // 尺寸只写原话引文和映射,毫米数由代码换算
|
|
32
|
+
shape: "box" | "cylinder",
|
|
33
|
+
bounds: {
|
|
34
|
+
axes: ("width" | "depth" | "height" | "diameter" | "radius" | "wall")[]; // 盒体 width/depth/height,圆柱 diameter 或 radius、height;wall 单独一项
|
|
35
|
+
bound: "max" | "min" | "exact" | "estimate"; // 尺寸用 max/min/exact,wall 用 min(保证壁厚)或 estimate(估算壁厚)
|
|
36
|
+
quote?: {text: string, index?: number, unit?: "um" | "mm" | "cm" | "m" | "in" | "mil"}; // 用户原话逐字摘录
|
|
37
|
+
choice?: {value: number, unit: "um" | "mm" | "cm" | "m" | "in" | "mil", basis: string}; // 仅 source=system 的估算
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
34
40
|
type Intent = {
|
|
35
41
|
kind: "restore" | "create" | "modify";
|
|
36
42
|
source: "user" | "system";
|
|
@@ -42,7 +48,7 @@ Requirements = {
|
|
|
42
48
|
context?: {text: string, source: "user" | "system"};
|
|
43
49
|
intent?: Intent; // 交回的 result 必须带;只有历史需求可能缺
|
|
44
50
|
reaction: {id: string, text: string, source: "user" | "system"}[];
|
|
45
|
-
limits: {id: string, text: string, source: "user" | "system", strength: "hard" | "preference",
|
|
51
|
+
limits: {id: string, text: string, source: "user" | "system", strength: "hard" | "preference", dimensions?: Dimensions, network?: "offline" | "no_cloud"}[]; // 不写 envelope
|
|
46
52
|
}
|
|
47
53
|
```
|
|
48
54
|
|
|
@@ -52,7 +58,7 @@ Requirements = {
|
|
|
52
58
|
|
|
53
59
|
纯咨询只返回 note;明确修改同时夹带咨询时,result 只承载明确修改,note 回应咨询或说明待核对的能力。“这样可以,颜色改蓝”先交修改,不把前半句当成新版本的确认。“改回去”必须能定位旧内容;否则保留现状并澄清对象。
|
|
54
60
|
|
|
55
|
-
**2. 整理需求。** context 记录落点、使用者与影响选择的场景;reaction 记录完整的事件或状态与反应,保留对象、方向、持续性和时间;limits 记录明确限制或偏好,数值保留单位。明确的尺寸、运动方式等限制写 limits;可确定为盒体或圆柱的尺寸另附同一 limits 项的
|
|
61
|
+
**2. 整理需求。** context 记录落点、使用者与影响选择的场景;reaction 记录完整的事件或状态与反应,保留对象、方向、持续性和时间;limits 记录明确限制或偏好,数值保留单位。明确的尺寸、运动方式等限制写 limits;可确定为盒体或圆柱的尺寸另附同一 limits 项的 dimensions,原 text/id/source/strength 保留,不从正文猜造工程数据。用户原话限制联网时,在同一 limits 项另附 network:完全不能联网(“不能联网”“必须离线”)写 offline,可以连手机或本地网络但不上云、数据不出设备(“不上云”“不用云端服务”)写 network=no_cloud;原话写进 text,没说清是哪一种就按原话登记而不附 network、必要时再问,不从隐私或成本推断。hard 的 network 让后续算法只能在端侧实现,由代码强制。几点、话术和停止规则写进对应 reaction;颜色、灯光等外观配置按用户表达登记到 limits,保留偏好或硬要求的原强度。
|
|
56
62
|
|
|
57
63
|
展示摘要由当前需求生成:一个放在 [落点] 的东西,在 [事件或状态] 下,做出 [可感知的输出]。摘要不代替完整 reaction;标签只用于分类初筛。
|
|
58
64
|
|
|
@@ -74,7 +80,16 @@ ID 是不透明标识,不能按数组位置重新编号;新 ID 不得与现
|
|
|
74
80
|
|
|
75
81
|
能说明拟探索行为的落点、事件/状态和输出,且不存在阻塞这次选择的歧义,就可以交给总控推进。其他行为与未定项继续保留,不生成全局 ready / complete 状态;“可以开始搜索”不代表需求已满足。一个假设会改变用户明确要求、替换保留的底座或突破已有授权时,不能用“可撤销”绕过用户决定:首版之前改走不触碰这些要求的默认方向,把该决定留到首版之后;只有符合“提问时机”例外时才在首版之前提问。
|
|
76
82
|
|
|
77
|
-
尺寸是推导变量,从落点与行为提出可见、可修改的范围;可提出 source=system 的可见估算,必须说明依据且不得冒充用户确认、实测或图片比例尺。
|
|
83
|
+
尺寸是推导变量,从落点与行为提出可见、可修改的范围;可提出 source=system 的可见估算,必须说明依据且不得冒充用户确认、实测或图片比例尺。
|
|
84
|
+
|
|
85
|
+
**尺寸只引原话,不算数。** dimensions 的每个边界给出数从哪来、管哪个轴、是上限还是下限,毫米数由代码换算:不写 envelope,不把英寸、厘米换成毫米,不把半径乘成直径,也不写任何算出来的数。
|
|
86
|
+
|
|
87
|
+
- 用户说的尺寸用 quote:text 是本轮用户消息或卡片答复里的逐字摘录(例如“宽 10 厘米”“直径 4 英寸”“半径 4cm”),代码核对它确实出现在原话里,不在就拒收(`CLARIFY_QUOTE_NOT_IN_MESSAGE`)。摘录里不止一个数时用 index(从 0 起数所有的数)指出取第几个;那个数自己没写单位、单位写在句末共用时(“长 100、宽 80、高 60 毫米”里的 100),unit 填原话里那个单位,数自己写了单位就不填。“10 厘米见方”一条写 width、depth、height 三个轴;“半径”写 radius,代码乘 2。
|
|
88
|
+
- 系统估算用 choice:value 与 unit 照你的选择写,basis 写依据;只能用在 source=system 的限制上(`CLARIFY_CHOICE_ON_USER_LIMIT`),换算结果不带硬筛选权限。
|
|
89
|
+
- 盒体要有 width、depth、height 三个上限(max 或 exact),圆柱要有直径(diameter 或 radius)和 height 的上限;凑不齐就只写 text,不写 dimensions,也不编一个 box。盒体下限三个轴一起给。同一轴同一边界只写一次。
|
|
90
|
+
- wall 的 min 是明确保证的非负壁厚下界,没写时按 0;estimate 只参与风险提示,不硬拒绝。代码换算后检查下限不超过上限、保证壁厚扣除后腔体各轴为正,不成立就拒收(`CLARIFY_ENVELOPE_INVALID`);范围较小端或估算壁厚不可行仍保留供风险评审。
|
|
91
|
+
- 修改时沿用 current_requirements 里未改动的边界原样照抄(不抄它的 envelope),这些边界不再对照本轮原话;新写或改动的边界都要对照本轮原话。
|
|
92
|
+
- 只有 source=user 且 strength=hard 的最大范围具有硬筛选权限;系统估算和偏好不能硬拒绝。未知形状不填写 dimensions。
|
|
78
93
|
|
|
79
94
|
没有明确尺寸要求,不主动索要尺寸;“必须塞进这个格子”缺尺寸且无法合理默认时才问,这正是首版之前允许提问的例外情形。品类默认和器件分类用输入中的数据,不在 skill 固化型号、价位或默认值表。工程资料缺失交给总控补充,不让用户提供接口、料号或测试证明。
|
|
80
95
|
|
|
@@ -106,8 +121,9 @@ python3 scripts/check_result.py - --current current-requirements.json
|
|
|
106
121
|
|
|
107
122
|
`proposal.json` 是上述 SkillResult;`current-requirements.json` 是当前 Requirements 本身,不是整个草稿。脚本只读输入、向标准输出写诊断,不覆盖文件。返回 `valid / errors / changes / review`;这份报告供修正与复核,**不是 SkillResult,也不是工程 Check**。退出码 0 表示结构合法,1 表示契约错误,2 表示文件或 JSON 读取失败。
|
|
108
123
|
|
|
109
|
-
- 检查字段、类型、来源、限制强度、ID 唯一性、intent
|
|
124
|
+
- 检查字段、类型、来源、限制强度、ID 唯一性、intent 结构、dimensions 的轴与上下限映射及题目数量;回复里出现 envelope 即为错误;交回的 result 缺 intent 即为错误,当前需求可以是缺 intent 的历史版本;新增/修改系统假设(含 source=system 的 intent)和撤回项须有 note。
|
|
110
125
|
- 提供当前需求时按稳定 ID 列出前后差异,提醒复核删除、来源和硬要求变化;保留前后值,不自动修复或采用。
|
|
126
|
+
- 脚本不核对引文是否在原话里、不换算单位,这两步由运行层定稿完成。
|
|
111
127
|
- 先修正 errors,再依据原话核对 changes / review。`valid=true` 不证明理解正确、得到授权、没有阻塞问题或可以确认;脚本不会验证原话真假、给问题排序或判断工程可行性。
|
|
112
128
|
|
|
113
129
|
## 边界
|
|
@@ -116,8 +116,19 @@
|
|
|
116
116
|
|
|
117
117
|
## 结构化尺寸与可见估算
|
|
118
118
|
|
|
119
|
-
用户说“必须放进长 100、宽 80、高 60 毫米的格子”,保留原 limit 的 id/text/source=user/strength=hard,并附
|
|
119
|
+
用户说“必须放进长 100、宽 80、高 60 毫米的格子”,保留原 limit 的 id/text/source=user/strength=hard,并附
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{"shape":"box","bounds":[
|
|
123
|
+
{"axes":["width"],"bound":"max","quote":{"text":"长 100、宽 80、高 60 毫米","index":0,"unit":"mm"}},
|
|
124
|
+
{"axes":["depth"],"bound":"max","quote":{"text":"长 100、宽 80、高 60 毫米","index":1,"unit":"mm"}},
|
|
125
|
+
{"axes":["height"],"bound":"max","quote":{"text":"长 100、宽 80、高 60 毫米","index":2}}]}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
作为 `dimensions`。100 和 80 自己没写单位,unit 填句末共用的“毫米”;60 自己写了单位,不填 unit。代码核对摘录出现在本轮原话里,换算出 `envelope.max_mm` 为 100、80、60,回复里不写 envelope。未保证壁厚时不补一个经验下界;省略 wall 相当于保证下界 0。用户之后只改高度时延续同一 ID,宽、深两条边界原样照抄(不再对照新消息),只有新的高度引文要出现在新消息里。
|
|
129
|
+
|
|
130
|
+
用户说“直径 4 英寸、高 10 厘米以内”时写 `{"shape":"cylinder","bounds":[{"axes":["diameter"],"bound":"max","quote":{"text":"直径 4 英寸"}},{"axes":["height"],"bound":"max","quote":{"text":"高 10 厘米"}}]}`,代码换算成 101.6 与 100 毫米;说“半径 4cm”时 axes 写 radius,代码乘 2 得到直径 80 毫米。不要自己换算后再写成毫米的引文。
|
|
120
131
|
|
|
121
132
|
仅从“桌面设备”提出大约 80–120 毫米的探索范围时,应是 source=system 的建议并在 note 写明依据与可修改性;不能让用户确认方案、图片看起来相似或系统默认升级为实测或 user。估算壁厚比容器大只说明估算需要修订,不能用来硬剔除器件。
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
系统估算的圆柱外形写在 source=system 的限制里,每个边界用 choice,例如 `{"axes":["diameter"],"bound":"max","choice":{"value":80,"unit":"mm","basis":"桌面摆件常见直径"}}`,壁厚估算写 `{"axes":["wall"],"bound":"estimate","choice":{"value":2,"unit":"mm","basis":"常见注塑壁厚"}}`。保留原话来源,不混入盒体的轴。完整快照和 ID 差分均须保留 dimensions;脚本 valid 只证明字段合法,不证明引文在原话里、可装下或得到授权。
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Runtime 读取 clarify 契约
|
|
2
2
|
|
|
3
|
-
本包增加只读 `domain.json`,让正式 task-view 读取安装的 `SKILL.md`
|
|
3
|
+
本包增加只读 `domain.json`,让正式 task-view 读取安装的 `SKILL.md`、模型回复契约 `schemas/skill-reply.v1.json` 和定稿后的完整快照契约 `schemas/skill-result.v1.json`。`actions:[]` 只使 Schema 参与跨包解析;clarify 不注册工具、不写草稿、不发布卡片。
|
|
4
4
|
|
|
5
|
-
`scripts/validate-result.mjs` 是 Node runtime 使用的只读校验入口,与现有 Python `scripts/check_result.py` 的 `valid` 和稳定 ID 差异语义一致。JSON Schema
|
|
5
|
+
`scripts/validate-result.mjs` 是 Node runtime 使用的只读校验入口,与现有 Python `scripts/check_result.py` 的 `valid` 和稳定 ID 差异语义一致。JSON Schema 检查形状、来源、意图、选项上限。
|
|
6
6
|
|
|
7
|
-
`
|
|
7
|
+
尺寸(模型算术 CLR-1):模型回复的 limits 只写 `dimensions`(用户原话引文或系统估算的选择、轴、上下限),不写 `envelope`。[scripts/dimensions.mjs](../scripts/dimensions.mjs) 的 `completeRequirements` 在定稿时核对新写或改动的引文逐字出现在本轮用户消息或卡片答复里(NFKC 规范化后比对;与当前需求同 ID 原样沿用的边界不再核对),用共用求值核心(`abelien-harness/scripts/calc`)取量、换算单位、半径乘 2,得到与硬件包 `validateEnvelope` 同形的 `envelope`。存下的需求里 envelope 与 dimensions 同在同缺,`validateRequirements` 每次按 dimensions 重新换算并要求与 envelope 完全一致。拒收码是 `CLARIFY_DIMENSION_CODES`,包在 `TASK_REPLY_INVALID` 的 `details.issues[].code` 里:`CLARIFY_ENVELOPE_FROM_MODEL`、`CLARIFY_QUOTE_NOT_IN_MESSAGE`、`CLARIFY_QUOTE_NO_LENGTH`、`CLARIFY_QUOTE_UNIT`、`CLARIFY_CHOICE_ON_USER_LIMIT`、`CLARIFY_DIMENSIONS_MAPPING`、`CLARIFY_ENVELOPE_INVALID`、`CLARIFY_ENVELOPE_MISMATCH`。Python 脚本只查结构,不查引文出处、不换算。系统假设新增/修改及撤回要求 note。校验成功只代表契约成立,不证明自然语言理解、用户授权或工程可行性。
|
|
8
|
+
|
|
9
|
+
`creation.start` 的受信 finalizer 另检查空需求不得启动、问题必须说明当前阻塞;这是作品入口准入,不改变通用 clarify 输出契约。Python 与 Node 的差异报告语言可以不同;[定向回归](../../../test/creation-intake-runtime.test.mjs) 对有效性和 ID 差异逐项比对,覆盖来源、删除、跨组重复 ID、问题数量和尺寸映射;引文出处与换算的回归在 [clarify-dimensions.test.mjs](../../../test/clarify-dimensions.test.mjs)。
|
|
8
10
|
|
|
9
11
|
详情见 [workspace 的入口与答复绑定](../../abelien-workspace/references/creation-intake.md)。安装时保留声明的 harness 与 hardware 依赖;生产 Node 路径不需要启动 Python。
|