feat(understand): the plant pack, lean 65, five-language aliases, eight golden queries and the counts
The three track-H branches are merged; this is the integration that makes the six tools one surface. - TOOL_PACK_NAMES gains "plant" (pipe_takeoff, cable_takeoff); SECTION 26's four readers stay core. Lean gains drawing_understand and reaches 65 (55 with TOOL_PACKS=core). The `understand` and `plant` tags rank after `arch`, so the sections file as `understanding` (4) and `plant` (2): 247 tools in 28 groups. - discovery/aliases.py folds spec section 12's vocabulary into the six records the branches wrote - metraj, metre cetveli, boru / kablo listesi, fark, karsilastir, revizyon farki, olcek, birim in both Turkish spellings, BOQ, takeoff, scale check, COMPARE - without writing a phrase twice. The Russian terms are data only: the BM25 tokenizer keeps [a-z0-9], and no golden case pretends otherwise. - Eight golden tuning cases, four of them Turkish; the shared-command allowlist reconciled with what the merged records claim; the token-suite formula excludes the new pack; the inventory regenerated, the registry snapshot retaken and the README figures taken from check_doc_numbers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8c1a6cb27c
commit
835bc4b65e
@@ -27,7 +27,7 @@ Live through COM on Windows, or headless through ezdxf anywhere — one typed co
|
||||
|
||||
</div>
|
||||
|
||||
> **v1.5 release snapshot:** 247 tools · 8 resources · 5 prompt templates · 4516 collected tests.
|
||||
> **v1.5 release snapshot:** 247 tools · 8 resources · 5 prompt templates · 4942 collected tests.
|
||||
> 247 is the **registered** count; a default install advertises 242 over `tools/list`,
|
||||
> because `ENABLE_3D` is unset. `system_about` is the runtime authority.
|
||||
|
||||
@@ -35,7 +35,7 @@ Live through COM on Windows, or headless through ezdxf anywhere — one typed co
|
||||
|
||||
**A big MCP server is expensive to be connected to.** The full catalog costs a client **78,772 tokens** before it has asked for anything. Discovery mode replaces it with two tools and costs **356**.
|
||||
|
||||
**A drafter searches for `FILLET`, not `entity_fillet`.** Those command names appeared in no tool name or description — `df = 0` against a stock index, not badly ranked but *absent*. The fix was data: an authored corpus of **205 AutoCAD command names and 1318 synonym phrases** covering all 241 tools. A test refuses to let a tool exist without one.
|
||||
**A drafter searches for `FILLET`, not `entity_fillet`.** Those command names appeared in no tool name or description — `df = 0` against a stock index, not badly ranked but *absent*. The fix was data: an authored corpus of **206 AutoCAD command names and 1439 synonym phrases** covering all 247 tools. A test refuses to let a tool exist without one.
|
||||
|
||||
| Advertised surface | Tools seen | Idle cost |
|
||||
|---|---:|---:|
|
||||
@@ -108,7 +108,7 @@ Claude Desktop, Cursor, or any stdio MCP host. For HTTP: `autocad-mcp --transpor
|
||||
| Architecture | one plan model — walls with hosted doors and windows — and one engine: L / T / X junctions resolved, openings cut, poché by material; rooms measured from the faces the walls enclose (never typed) and read back from any plan with a confidence; door / window / room schedules as real TABLEs; a furniture and sanitary catalogue at nominal sizes; structural grid, north arrow, section / level / elevation marks; exterior dimension chains; English or Turkish labels (`lang="tr"`, decimal comma) |
|
||||
| P&ID | catalogue blocks with ports and tags (ISO 10628-2 / ISA-5.1), port-to-port lines with ISA-5.1 classes and line numbers, `pid_graph` reads any P&ID back with confidence, instrument index / line list / equipment list, `pid_from_spec` one-call sheets |
|
||||
| Styles & standards | ISO-25 / ANSI dimension styles, ISOCP / ROMANS text styles, ISO / ANSI leader styles from authored presets; `drawing_apply_standard("iso")` sets all of it plus units and the `mech` layers in one call; `changed` reports only what moved |
|
||||
| Discovery | `search_tools` ranked over an AutoCAD command and synonym corpus — `FILLET`, `BPOLY`, `QSELECT`, `WBLOCK`, `OVERKILL`, `CHSPACE` each rank **#1** of the 236-tool advertised catalog |
|
||||
| Discovery | `search_tools` ranked over an AutoCAD command and synonym corpus — `FILLET`, `BPOLY`, `QSELECT`, `WBLOCK`, `OVERKILL`, `CHSPACE` each rank **#1** of the 242-tool advertised catalog |
|
||||
| Batching | `cad_batch` runs a step list in one round trip; `fields=` projects 11 result-heavy tools |
|
||||
| Paper space | tab lifecycle, viewports, `entity_change_space` (CHSPACE), `page_setup_apply` (ISO 216 / ANSI Y14.1 paper, ctb, scale, device — on both engines), `batch_plot` with every sheet size read back from its PDF's `/MediaBox`, `drawing_export_pdf(layout=…)` |
|
||||
| Page setup & templates | ISO 216 / ANSI Y14.1 sheets, ctb catalog, `batch_plot` verified by the PDF's own `/MediaBox`, five bundled templates built by the server's own tools and pinned reproducible (`drawing_new(template="iso_a3_mech")`, `iso_a1_arch`, `iso_a3_pid`, `ansi_b_mech`, `ansi_d_arch`), save any drawing as a template (`.dwt` on live AutoCAD, `dwt_write` refused headlessly) |
|
||||
@@ -122,7 +122,7 @@ Claude Desktop, Cursor, or any stdio MCP host. For HTTP: `autocad-mcp --transpor
|
||||
| Quality loop | `drawing_preflight` → `drawing_plan` → `drawing_critique` → `drawing_refine` → `drawing_finalize` (0–100 score) |
|
||||
| Delivery | `drawing_deliver`: DXF/PDF/PNG + SHA-256 manifest + reopen-parity checks |
|
||||
|
||||
<sub>247 tools in 26 groups; <code>TOOL_PACKS=core</code> hides the nine <code>pid_*</code> tools, the 22 environment tools, the 14 mechanical tools and the 12 architectural tools (<code>TOOL_PACKS=core,mech</code> keeps the mechanical ones, <code>TOOL_PACKS=core,arch</code> the architectural ones) from a client that needs none of them. Plus 8 resources that cost nothing in the tool budget (<code>autocad://drawing/info</code>, <code>layers</code>, <code>blocks</code>, <code>entities/stats</code>, <code>entities/{layer_name}</code>, <code>system/status</code>, <code>pid/symbols</code>, <code>standards/isa51</code>) and 5 prompt templates.</sub>
|
||||
<sub>247 tools in 28 groups; <code>TOOL_PACKS=core</code> hides the nine <code>pid_*</code> tools, the 22 environment tools, the 14 mechanical tools, the 12 architectural tools and the two plant takeoffs (<code>TOOL_PACKS=core,mech</code> keeps the mechanical ones, <code>TOOL_PACKS=core,arch</code> the architectural ones, <code>TOOL_PACKS=core,plant</code> the takeoffs) from a client that needs none of them. Plus 8 resources that cost nothing in the tool budget (<code>autocad://drawing/info</code>, <code>layers</code>, <code>blocks</code>, <code>entities/stats</code>, <code>entities/{layer_name}</code>, <code>system/status</code>, <code>pid/symbols</code>, <code>standards/isa51</code>) and 5 prompt templates.</sub>
|
||||
|
||||
**Two rules worth knowing.** Every coordinate in and out of a tool is WCS on both engines — the one exception is TEXT `rotation`, which stays in the entity frame because a mirrored TEXT is mirror-imaged and no scalar angle expresses that. And never read vertices back and shoelace them: that loses **28.2%** of the area on a semicircular edge, silently. `analysis_measure_entity(handle)` reads the real geometry and states its own accuracy.
|
||||
|
||||
@@ -234,8 +234,8 @@ Nothing loads a `.env` file — export these, or set them in your MCP client's `
|
||||
|---|---|---|
|
||||
| `AUTOCAD_MCP_BACKEND` | `auto` | `auto`, `com`, or `ezdxf` |
|
||||
| `CAD_PROGID` | `AutoCAD.Application` | COM ProgID the live backend attaches to |
|
||||
| `TOOL_PROFILE` | `full` | `lean` (64 curated tools, 54 with `TOOL_PACKS=core`) or `full` |
|
||||
| `TOOL_PACKS` | `all` | Vertical packs to advertise: `core,pid,settings` (`core` always on) |
|
||||
| `TOOL_PROFILE` | `full` | `lean` (65 curated tools, 55 with `TOOL_PACKS=core`) or `full` |
|
||||
| `TOOL_PACKS` | `all` | Vertical packs to advertise: `core,pid,settings,mech,arch,plant` (`core` always on) |
|
||||
| `DISCOVERY_MODE` | `off` | `search` replaces the catalog with `search_tools` + `call_tool` |
|
||||
| `ENABLE_3D` | `false` | Expose the opt-in `solid_*` tools (COM) |
|
||||
| `LOG_LEVEL` | `INFO` | Python logging level |
|
||||
|
||||
@@ -2580,6 +2580,150 @@ TOOL_ALIASES: dict[str, ToolAliases] = {
|
||||
}
|
||||
|
||||
|
||||
# ── Track H: the five-language vocabulary of spec §12 ──────────────────────
|
||||
#
|
||||
# The six track-H records were written by three branches (SECTION 26 by groups
|
||||
# U and D, SECTION 27 by group N). Spec §12's vocabulary - the words a plant
|
||||
# engineer types when asking for a takeoff, a comparison or a scale check - is
|
||||
# folded into them here, after the merge, so every branch's own phrases stay
|
||||
# and nothing is written twice. A record missing here is a KeyError at import:
|
||||
# a track-H tool without its branch's record is a merge that lost something.
|
||||
#
|
||||
# Turkish carries both spellings, diacritic and ASCII-folded, because the
|
||||
# search tokenizer splits on a non-ASCII letter instead of folding it (the
|
||||
# room records above say the same). The Russian terms are data for a client
|
||||
# that searches this corpus itself: the BM25 tokenizer keeps only [a-z0-9], so
|
||||
# it cannot index Cyrillic yet, and no golden case pretends it can.
|
||||
#
|
||||
# COMPARE is AutoCAD's drawing-compare command (the "DWG Compare" feature);
|
||||
# "dwg compare" / "dwgcompare" are how drafters name the feature, so they are
|
||||
# synonyms, not a command token.
|
||||
_TRACK_H_VOCABULARY: dict[str, tuple[tuple[str, ...], tuple[str, ...]]] = {
|
||||
"drawing_understand": (
|
||||
(),
|
||||
(
|
||||
"what is in this drawing",
|
||||
"what is in this drawing somebody sent me",
|
||||
"understand a foreign drawing",
|
||||
"read a drawing somebody else made",
|
||||
"which units is this drawing in",
|
||||
"drawing units check",
|
||||
"birim",
|
||||
"birim kontrolü",
|
||||
"birim kontrolu",
|
||||
"çizim birimi",
|
||||
"cizim birimi",
|
||||
"çizimi anla",
|
||||
"cizimi anla",
|
||||
"yabancı çizim",
|
||||
"yabanci cizim",
|
||||
),
|
||||
),
|
||||
"drawing_scale_check": (
|
||||
(),
|
||||
(
|
||||
"scale check",
|
||||
"is this drawing to scale",
|
||||
"is the p&id to scale",
|
||||
"schematic or to scale",
|
||||
"compare distances between two drawings",
|
||||
"ölçek",
|
||||
"olcek",
|
||||
"ölçek kontrolü",
|
||||
"ölçek kontrolu",
|
||||
"olcek kontrolu",
|
||||
"ölçekli mi",
|
||||
"olcekli mi",
|
||||
"bu çizim ölçekli mi",
|
||||
"bu cizim olcekli mi",
|
||||
),
|
||||
),
|
||||
"drawing_diff": (
|
||||
("COMPARE",),
|
||||
(
|
||||
"compare",
|
||||
"compare two revisions",
|
||||
"compare two revisions of this drawing",
|
||||
"what changed between revisions",
|
||||
"drawing diff",
|
||||
"dwg compare",
|
||||
"dwgcompare",
|
||||
"fark",
|
||||
"revizyon farkı",
|
||||
"revizyon farki",
|
||||
"karşılaştır",
|
||||
"karsilastir",
|
||||
"iki revizyonu karşılaştır",
|
||||
"iki revizyonu karsilastir",
|
||||
"сравнить чертежи", # compare drawings
|
||||
),
|
||||
),
|
||||
"drawing_topology_check": (
|
||||
(),
|
||||
(
|
||||
"find dangling line ends",
|
||||
"find dangling line ends and gaps",
|
||||
"uçları açık çizgiler",
|
||||
"uclari acik cizgiler",
|
||||
"kopuk hat",
|
||||
),
|
||||
),
|
||||
"pipe_takeoff": (
|
||||
(),
|
||||
(
|
||||
"takeoff",
|
||||
"pipe takeoff",
|
||||
"pipe takeoff by diameter from the p&id",
|
||||
"pipe quantities",
|
||||
"bill of quantities",
|
||||
"boq",
|
||||
"metraj",
|
||||
"metre cetveli",
|
||||
"boru listesi",
|
||||
"boru metrajı",
|
||||
"boru metraji",
|
||||
"boru metrajı çıkar",
|
||||
"boru metraji cikar",
|
||||
"спецификация", # specification: a bill of quantities
|
||||
"ведомость", # statement: a quantity schedule
|
||||
),
|
||||
),
|
||||
"cable_takeoff": (
|
||||
(),
|
||||
(
|
||||
"takeoff",
|
||||
"cable takeoff",
|
||||
"cable schedule",
|
||||
"cable lengths to the panel",
|
||||
"bill of quantities",
|
||||
"boq",
|
||||
"metraj",
|
||||
"metre cetveli",
|
||||
"kablo listesi",
|
||||
"kablo metrajı",
|
||||
"kablo metraji",
|
||||
"kablo metrajı çıkar",
|
||||
"kablo metraji cikar",
|
||||
"кабельная ведомость", # cable schedule
|
||||
"спецификация", # specification: a bill of quantities
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _fold_in(records: dict[str, ToolAliases], extra) -> None:
|
||||
"""Append ``extra``'s commands and phrases to existing records, never twice."""
|
||||
for name, (acad, synonyms) in extra.items():
|
||||
record = records[name]
|
||||
records[name] = ToolAliases(
|
||||
acad=record.acad + tuple(c for c in acad if c not in record.acad),
|
||||
synonyms=record.synonyms + tuple(s for s in synonyms if s not in record.synonyms),
|
||||
)
|
||||
|
||||
|
||||
_fold_in(TOOL_ALIASES, _TRACK_H_VOCABULARY)
|
||||
|
||||
|
||||
def aliases_for(tool_name: str) -> ToolAliases | None:
|
||||
"""Return the alias record for ``tool_name``, or ``None`` if it has none."""
|
||||
return TOOL_ALIASES.get(tool_name)
|
||||
|
||||
+18
-12
@@ -6,10 +6,10 @@
|
||||
"tools": 247,
|
||||
"resources": 8,
|
||||
"prompts": 5,
|
||||
"groups": 26
|
||||
"groups": 28
|
||||
},
|
||||
"groups": {
|
||||
"analysis": 18,
|
||||
"analysis": 12,
|
||||
"architecture": 12,
|
||||
"batch": 3,
|
||||
"blocks": 9,
|
||||
@@ -26,6 +26,7 @@
|
||||
"mechanical": 14,
|
||||
"page_setup": 6,
|
||||
"pid": 9,
|
||||
"plant": 2,
|
||||
"premium": 12,
|
||||
"sheet": 11,
|
||||
"solids": 5,
|
||||
@@ -33,6 +34,7 @@
|
||||
"system": 7,
|
||||
"templates": 2,
|
||||
"transactions": 3,
|
||||
"understanding": 4,
|
||||
"validation": 1,
|
||||
"view": 4
|
||||
},
|
||||
@@ -545,7 +547,7 @@
|
||||
},
|
||||
{
|
||||
"name": "cable_takeoff",
|
||||
"group": "analysis",
|
||||
"group": "plant",
|
||||
"cost": "safe",
|
||||
"summary": "Cable takeoff: one row per load, Manhattan length to its panel, rounded up to the metre.",
|
||||
"tags": [
|
||||
@@ -914,12 +916,13 @@
|
||||
},
|
||||
{
|
||||
"name": "drawing_diff",
|
||||
"group": "analysis",
|
||||
"group": "understanding",
|
||||
"cost": "mutate",
|
||||
"summary": "Compare two revisions of a drawing: what moved, changed, was added or removed.",
|
||||
"tags": [
|
||||
"analysis",
|
||||
"query"
|
||||
"query",
|
||||
"understand"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Drawing: Diff Two Revisions",
|
||||
@@ -1147,12 +1150,13 @@
|
||||
},
|
||||
{
|
||||
"name": "drawing_scale_check",
|
||||
"group": "analysis",
|
||||
"group": "understanding",
|
||||
"cost": "read",
|
||||
"summary": "Is this drawing to scale against a reference? Tag distances, ratios, a verdict.",
|
||||
"tags": [
|
||||
"analysis",
|
||||
"query"
|
||||
"query",
|
||||
"understand"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Understand: Scale Check",
|
||||
@@ -1206,12 +1210,13 @@
|
||||
},
|
||||
{
|
||||
"name": "drawing_topology_check",
|
||||
"group": "analysis",
|
||||
"group": "understanding",
|
||||
"cost": "read",
|
||||
"summary": "Find dangling ends, near misses and unjoined crossings in a pipe or cable network.",
|
||||
"tags": [
|
||||
"analysis",
|
||||
"query"
|
||||
"query",
|
||||
"understand"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Drawing: Topology Check",
|
||||
@@ -1220,12 +1225,13 @@
|
||||
},
|
||||
{
|
||||
"name": "drawing_understand",
|
||||
"group": "analysis",
|
||||
"group": "understanding",
|
||||
"cost": "read",
|
||||
"summary": "Read a foreign drawing: units, extents, clusters, layers, tags, rooms, languages.",
|
||||
"tags": [
|
||||
"analysis",
|
||||
"query"
|
||||
"query",
|
||||
"understand"
|
||||
],
|
||||
"annotations": {
|
||||
"title": "Understand: Describe Drawing",
|
||||
@@ -2597,7 +2603,7 @@
|
||||
},
|
||||
{
|
||||
"name": "pipe_takeoff",
|
||||
"group": "analysis",
|
||||
"group": "plant",
|
||||
"cost": "safe",
|
||||
"summary": "Pipe takeoff from a P&ID and its layout: rows by room, service and diameter.",
|
||||
"tags": [
|
||||
|
||||
@@ -692,6 +692,8 @@ _GROUP_TAG_PRIORITY = (
|
||||
"pid",
|
||||
"mech",
|
||||
"arch",
|
||||
"understand",
|
||||
"plant",
|
||||
"sheet",
|
||||
"style",
|
||||
"pagesetup",
|
||||
@@ -723,6 +725,8 @@ _GROUP_TAG_LABELS = {
|
||||
"pid": "pid",
|
||||
"mech": "mechanical",
|
||||
"arch": "architecture",
|
||||
"understand": "understanding",
|
||||
"plant": "plant",
|
||||
"sheet": "sheet",
|
||||
"style": "styles",
|
||||
"pagesetup": "page_setup",
|
||||
@@ -973,6 +977,12 @@ LEAN_TOOL_NAMES = frozenset(
|
||||
"arch_opening",
|
||||
"arch_room",
|
||||
"arch_plan_from_spec",
|
||||
# Understanding (track H) - the one read a lean client needs before it
|
||||
# touches a drawing somebody else made: units, extents, clusters,
|
||||
# layers and their services, tags and rooms in one call. A core-pack
|
||||
# tool, so TOOL_PACKS=core keeps it; with the four architectural ones
|
||||
# above, lean is 65. The takeoffs are `plant`-pack, full-profile work.
|
||||
"drawing_understand",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -986,7 +996,7 @@ SOLID_TOOL_NAMES = frozenset(
|
||||
# mechanically should not pay for the P&ID surface, and a client that never
|
||||
# talks to a live seat should not pay for the environment surface. Styles and
|
||||
# page setup (SECTION 18/19) are drafting essentials and stay in core.
|
||||
TOOL_PACK_NAMES = ("core", "pid", "settings", "mech", "arch")
|
||||
TOOL_PACK_NAMES = ("core", "pid", "settings", "mech", "arch", "plant")
|
||||
PACK_TOOL_NAMES: dict[str, frozenset[str]] = {
|
||||
"pid": frozenset(
|
||||
{
|
||||
@@ -1070,6 +1080,12 @@ PACK_TOOL_NAMES: dict[str, frozenset[str]] = {
|
||||
"arch_plan_from_spec",
|
||||
}
|
||||
),
|
||||
# SECTION 27 - the plant takeoffs: pipe and cable quantities whose topology
|
||||
# comes from a P&ID and whose lengths come from its layout (track H spec
|
||||
# §8-9, §12). SECTION 26's four readers stay in `core`: understanding,
|
||||
# comparing and checking a drawing is not a vertical. Track D's Plant 3D
|
||||
# readers join this pack.
|
||||
"plant": frozenset({"pipe_takeoff", "cable_takeoff"}),
|
||||
}
|
||||
|
||||
_active_tool_profile: dict | None = None
|
||||
@@ -10569,7 +10585,7 @@ async def arch_plan_from_spec(
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Understand: Describe Drawing", "readOnlyHint": True},
|
||||
tags={"analysis", "query"},
|
||||
tags={"understand", "analysis", "query"},
|
||||
)
|
||||
async def drawing_understand(
|
||||
path: Annotated[
|
||||
@@ -10638,7 +10654,7 @@ async def drawing_understand(
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Understand: Scale Check", "readOnlyHint": True},
|
||||
tags={"analysis", "query"},
|
||||
tags={"understand", "analysis", "query"},
|
||||
)
|
||||
async def drawing_scale_check(
|
||||
reference: Annotated[
|
||||
@@ -10800,7 +10816,7 @@ async def _diff_markup(backend, result: dict, new_snap, *, rev: str, description
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Drawing: Diff Two Revisions", "destructiveHint": False},
|
||||
tags={"analysis", "query"},
|
||||
tags={"understand", "analysis", "query"},
|
||||
)
|
||||
async def drawing_diff(
|
||||
old_path: Annotated[
|
||||
@@ -10932,7 +10948,7 @@ async def drawing_diff(
|
||||
)
|
||||
@mcp.tool(
|
||||
annotations={"title": "Drawing: Topology Check", "readOnlyHint": True},
|
||||
tags={"analysis", "query"},
|
||||
tags={"understand", "analysis", "query"},
|
||||
)
|
||||
async def drawing_topology_check(
|
||||
layers: Annotated[
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Golden query set for tool discovery: English in, one right tool out.
|
||||
|
||||
Ninety-nine cases (84 tuning + 15 holdout) split into a **tuning** set and a
|
||||
One hundred and seven cases (92 tuning + 15 holdout) split into a **tuning** set and a
|
||||
**holdout** set. The split is
|
||||
the point of the file. Ranking work is measured against
|
||||
:data:`TUNING_CASES` only; :data:`HOLDOUT_CASES` were written at the same time,
|
||||
@@ -204,6 +204,19 @@ TUNING_CASES: tuple[GoldenCase, ...] = (
|
||||
GoldenCase("merdiven çiz", "arch_stair", "synonym"),
|
||||
GoldenCase("vaziyet planı çiz", "arch_plan_from_spec", "synonym"),
|
||||
GoldenCase("oda alanlarını hesapla", "arch_rooms_detect", "synonym"),
|
||||
# v1.6 track H - reading drawings somebody else made. Written before the
|
||||
# merged alias records were measured against them; a miss is fixed by
|
||||
# adding vocabulary to that tool's record in discovery/aliases.py, never by
|
||||
# moving the expectation. Half of them Turkish: a takeoff is asked for in
|
||||
# the language of the shop that prices the job.
|
||||
GoldenCase("boru metrajı çıkar", "pipe_takeoff", "synonym"),
|
||||
GoldenCase("kablo metrajı", "cable_takeoff", "synonym"),
|
||||
GoldenCase("iki revizyonu karşılaştır", "drawing_diff", "synonym"),
|
||||
GoldenCase("bu çizim ölçekli mi", "drawing_scale_check", "synonym"),
|
||||
GoldenCase("what is in this drawing somebody sent me", "drawing_understand", "paraphrase"),
|
||||
GoldenCase("pipe takeoff by diameter from the p&id", "pipe_takeoff", "paraphrase"),
|
||||
GoldenCase("compare two revisions of this drawing", "drawing_diff", "paraphrase"),
|
||||
GoldenCase("find dangling line ends and gaps", "drawing_topology_check", "paraphrase"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -208,3 +208,64 @@ def test_alias_text_contains_commands_and_synonyms():
|
||||
for token in (*record.acad, *record.synonyms):
|
||||
assert token in text
|
||||
assert alias_text("no_such_tool") == ""
|
||||
|
||||
|
||||
# ── track H: spec §12's five-language vocabulary ───────────────────────────
|
||||
#
|
||||
# One row per term the spec names, in the record(s) a plant engineer means by
|
||||
# it. Turkish comes in both spellings (diacritic and ASCII-folded): the search
|
||||
# tokenizer splits on a non-ASCII letter instead of folding it.
|
||||
|
||||
TRACK_H_TERMS = {
|
||||
"pipe_takeoff": (
|
||||
"takeoff",
|
||||
"boq",
|
||||
"metraj",
|
||||
"metre cetveli",
|
||||
"boru listesi",
|
||||
"boru metrajı",
|
||||
"boru metraji",
|
||||
"boru metrajı çıkar",
|
||||
"boru metraji cikar",
|
||||
"спецификация", # specification: a bill of quantities
|
||||
"ведомость", # statement: a quantity schedule
|
||||
),
|
||||
"cable_takeoff": (
|
||||
"takeoff",
|
||||
"boq",
|
||||
"metraj",
|
||||
"metre cetveli",
|
||||
"kablo listesi",
|
||||
"kablo metrajı",
|
||||
"kablo metraji",
|
||||
),
|
||||
"drawing_diff": (
|
||||
"compare",
|
||||
"dwgcompare",
|
||||
"fark",
|
||||
"karşılaştır",
|
||||
"karsilastir",
|
||||
"revizyon farkı",
|
||||
"revizyon farki",
|
||||
"iki revizyonu karşılaştır",
|
||||
"iki revizyonu karsilastir",
|
||||
),
|
||||
"drawing_scale_check": (
|
||||
"scale check",
|
||||
"ölçek",
|
||||
"olcek",
|
||||
"bu çizim ölçekli mi",
|
||||
"bu cizim olcekli mi",
|
||||
),
|
||||
"drawing_understand": ("birim", "çizim birimi", "cizim birimi"),
|
||||
}
|
||||
|
||||
|
||||
def test_track_h_records_carry_the_five_language_vocabulary():
|
||||
missing = {
|
||||
tool: [term for term in terms if term not in TOOL_ALIASES[tool].synonyms]
|
||||
for tool, terms in TRACK_H_TERMS.items()
|
||||
}
|
||||
missing = {tool: terms for tool, terms in missing.items() if terms}
|
||||
assert not missing, f"track-H records miss spec §12 vocabulary: {missing}"
|
||||
assert "COMPARE" in TOOL_ALIASES["drawing_diff"].acad
|
||||
|
||||
@@ -163,9 +163,9 @@ def test_the_cache_arithmetic_follows_the_published_multipliers():
|
||||
|
||||
def test_the_idle_lane_covers_the_five_advertised_surfaces(report):
|
||||
"""Default, lean, search - and the two single-vertical clients:
|
||||
TOOL_PACKS=core,mech (the default surface minus exactly the pid and settings
|
||||
packs, and the arch pack) and TOOL_PACKS=core,arch (minus pid, settings and
|
||||
mech)."""
|
||||
TOOL_PACKS=core,mech (the default surface minus exactly the pid, settings,
|
||||
arch and plant packs) and TOOL_PACKS=core,arch (minus pid, settings, mech
|
||||
and plant)."""
|
||||
variants = {row["variant"]: row for row in report["idle"]}
|
||||
assert set(variants) == {"default", "lean", "packs_core_mech", "packs_core_arch", "search"}
|
||||
assert variants["default"]["advertised_tools"] > 100
|
||||
@@ -177,12 +177,14 @@ def test_the_idle_lane_covers_the_five_advertised_surfaces(report):
|
||||
- len(packs["pid"])
|
||||
- len(packs["settings"])
|
||||
- len(packs["arch"])
|
||||
- len(packs["plant"])
|
||||
)
|
||||
assert variants["packs_core_arch"]["advertised_tools"] == (
|
||||
variants["default"]["advertised_tools"]
|
||||
- len(packs["pid"])
|
||||
- len(packs["settings"])
|
||||
- len(packs["mech"])
|
||||
- len(packs["plant"])
|
||||
)
|
||||
for narrowed in ("packs_core_mech", "packs_core_arch"):
|
||||
assert (
|
||||
|
||||
+56
-10
@@ -16,6 +16,13 @@ v1.6 track F adds the `arch` pack: SECTION 25's twelve architectural tools -
|
||||
the wall network, openings, stairs, rooms and their reader, schedules, the
|
||||
grid and symbols, the furniture and sanitary catalogue, the exterior chains
|
||||
and the whole plan from one spec. Lean gains four of them.
|
||||
|
||||
v1.6 track H adds the `plant` pack: SECTION 27's two takeoffs (`pipe_takeoff`,
|
||||
`cable_takeoff`) - quantities for a plant job, the vertical track D's Plant 3D
|
||||
readers join later. SECTION 26's four readers (`drawing_understand`,
|
||||
`drawing_scale_check`, `drawing_diff`, `drawing_topology_check`) stay in
|
||||
`core`: understanding, comparing and checking a drawing is not a vertical.
|
||||
Lean gains `drawing_understand` and reaches 65.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -135,6 +142,20 @@ ARCH_TOOLS = {
|
||||
"arch_plan_from_spec",
|
||||
}
|
||||
|
||||
UNDERSTAND_TOOLS = {
|
||||
"drawing_understand",
|
||||
"drawing_scale_check",
|
||||
"drawing_diff",
|
||||
"drawing_topology_check",
|
||||
}
|
||||
|
||||
PLANT_TOOLS = {
|
||||
"pipe_takeoff",
|
||||
"cable_takeoff",
|
||||
}
|
||||
|
||||
LEAN_UNDERSTAND_ESSENTIALS = {"drawing_understand"}
|
||||
|
||||
LEAN_ARCH_ESSENTIALS = {
|
||||
"arch_wall",
|
||||
"arch_opening",
|
||||
@@ -159,7 +180,7 @@ LEAN_SETTINGS_ESSENTIALS = {
|
||||
}
|
||||
|
||||
#: Declaration order - what `tool_packs["available"]` reports.
|
||||
ALL_PACKS = ["core", "pid", "settings", "mech", "arch"]
|
||||
ALL_PACKS = ["core", "pid", "settings", "mech", "arch", "plant"]
|
||||
#: `tool_packs["enabled"]` is sorted, which is no longer the same list.
|
||||
ALL_PACKS_ENABLED = sorted(ALL_PACKS)
|
||||
|
||||
@@ -172,19 +193,26 @@ async def _restore(monkeypatch):
|
||||
|
||||
|
||||
def test_pack_registry_names_real_tools_and_only_them():
|
||||
assert server.TOOL_PACK_NAMES == ("core", "pid", "settings", "mech", "arch")
|
||||
assert server.TOOL_PACK_NAMES == ("core", "pid", "settings", "mech", "arch", "plant")
|
||||
assert server.PACK_TOOL_NAMES["pid"] == frozenset(PID_TOOLS)
|
||||
assert server.PACK_TOOL_NAMES["mech"] == frozenset(MECH_TOOLS)
|
||||
assert server.PACK_TOOL_NAMES["settings"] == frozenset(SETTINGS_TOOLS)
|
||||
assert server.PACK_TOOL_NAMES["arch"] == frozenset(ARCH_TOOLS)
|
||||
assert server.PACK_TOOL_NAMES["plant"] == frozenset(PLANT_TOOLS)
|
||||
assert len(SETTINGS_TOOLS) == 22
|
||||
assert len(MECH_TOOLS) == 14
|
||||
assert len(ARCH_TOOLS) == 12
|
||||
assert len(PLANT_TOOLS) == 2
|
||||
assert not (server.PACK_TOOL_NAMES["settings"] & server.PACK_TOOL_NAMES["pid"])
|
||||
assert not (server.PACK_TOOL_NAMES["mech"] & server.PACK_TOOL_NAMES["pid"])
|
||||
assert not (server.PACK_TOOL_NAMES["mech"] & server.PACK_TOOL_NAMES["settings"])
|
||||
for pack in ("pid", "settings", "mech"):
|
||||
assert not (server.PACK_TOOL_NAMES["arch"] & server.PACK_TOOL_NAMES[pack]), pack
|
||||
for pack in ("pid", "settings", "mech", "arch"):
|
||||
assert not (server.PACK_TOOL_NAMES["plant"] & server.PACK_TOOL_NAMES[pack]), pack
|
||||
assert not (UNDERSTAND_TOOLS & set().union(*server.PACK_TOOL_NAMES.values())), (
|
||||
"SECTION 26 reads any drawing: core, not a vertical pack"
|
||||
)
|
||||
assert not (SHEET_TOOLS & set().union(*server.PACK_TOOL_NAMES.values())), (
|
||||
"SECTION 24 is core drafting, not a vertical pack"
|
||||
)
|
||||
@@ -205,7 +233,9 @@ async def test_core_only_hides_pid_and_settings_but_keeps_styles_and_page_setup(
|
||||
assert SETTINGS_TOOLS <= disabled
|
||||
assert MECH_TOOLS <= disabled
|
||||
assert ARCH_TOOLS <= disabled
|
||||
assert PLANT_TOOLS <= disabled
|
||||
assert not (SHEET_TOOLS & disabled)
|
||||
assert not (UNDERSTAND_TOOLS & disabled), "the readers of SECTION 26 are core"
|
||||
assert not (STYLE_TOOLS & disabled), "styles are core drafting essentials"
|
||||
assert not (PAGE_SETUP_TOOLS & disabled), "page setup and templates are core"
|
||||
assert "block_define" not in disabled and "entity_set_xdata" not in disabled
|
||||
@@ -221,11 +251,21 @@ async def test_settings_pack_alone_keeps_pid_hidden(monkeypatch):
|
||||
assert info["tool_packs"]["enabled"] == ["core", "settings"]
|
||||
|
||||
|
||||
async def test_the_plant_pack_alone_adds_the_takeoffs_to_core(monkeypatch):
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "plant")
|
||||
info = await server._apply_tool_profile("full")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert not (PLANT_TOOLS & disabled)
|
||||
assert not (UNDERSTAND_TOOLS & disabled)
|
||||
assert PID_TOOLS <= disabled and ARCH_TOOLS <= disabled
|
||||
assert info["tool_packs"]["enabled"] == ["core", "plant"]
|
||||
|
||||
|
||||
async def test_all_is_the_default_and_enables_every_pack(monkeypatch):
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "all")
|
||||
info = await server._apply_tool_profile("full")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert not ((PID_TOOLS | SETTINGS_TOOLS | MECH_TOOLS | ARCH_TOOLS) & disabled)
|
||||
assert not ((PID_TOOLS | SETTINGS_TOOLS | MECH_TOOLS | ARCH_TOOLS | PLANT_TOOLS) & disabled)
|
||||
assert info["tool_packs"]["enabled"] == ALL_PACKS_ENABLED
|
||||
|
||||
|
||||
@@ -254,22 +294,26 @@ async def test_lean_intersects_with_packs(monkeypatch):
|
||||
async def test_lean_carries_the_five_settings_essentials_and_nothing_from_the_settings_pack(
|
||||
monkeypatch,
|
||||
):
|
||||
"""Spec §8.2: lean = 64 (55 + the five mechanical/sheet essentials + track
|
||||
F's four architectural ones). The five settings essentials are core-pack
|
||||
tools, so `TOOL_PACKS=core` leaves them on a lean surface; no environment
|
||||
tool is lean."""
|
||||
"""Spec §8.2: lean = 65 (55 + the five mechanical/sheet essentials + track
|
||||
F's four architectural ones + track H's `drawing_understand`). The five
|
||||
settings essentials and `drawing_understand` are core-pack tools, so
|
||||
`TOOL_PACKS=core` leaves them on a lean surface; no environment tool and no
|
||||
takeoff is lean."""
|
||||
assert LEAN_SETTINGS_ESSENTIALS <= server.LEAN_TOOL_NAMES
|
||||
assert LEAN_MECH_ESSENTIALS <= server.LEAN_TOOL_NAMES
|
||||
assert LEAN_ARCH_ESSENTIALS <= server.LEAN_TOOL_NAMES
|
||||
assert server.LEAN_TOOL_NAMES & ARCH_TOOLS == LEAN_ARCH_ESSENTIALS
|
||||
assert server.LEAN_TOOL_NAMES & UNDERSTAND_TOOLS == LEAN_UNDERSTAND_ESSENTIALS
|
||||
assert not (PLANT_TOOLS & server.LEAN_TOOL_NAMES)
|
||||
assert not (SETTINGS_TOOLS & server.LEAN_TOOL_NAMES)
|
||||
assert len(server.LEAN_TOOL_NAMES) == 64
|
||||
assert len(server.LEAN_TOOL_NAMES) == 65
|
||||
monkeypatch.setattr(config.settings, "tool_packs", "core")
|
||||
info = await server._apply_tool_profile("lean")
|
||||
disabled = set(info["disabled_tools"])
|
||||
assert not (LEAN_SETTINGS_ESSENTIALS & disabled)
|
||||
assert not ({"sheet_frame", "titleblock_apply"} & disabled), "sheet tools are core"
|
||||
assert info["enabled_count"] == 64 - 10, (
|
||||
assert "drawing_understand" not in disabled, "the one-call report is core"
|
||||
assert info["enabled_count"] == 65 - 10, (
|
||||
"lean minus the three pid, three mech and four arch tools"
|
||||
)
|
||||
|
||||
@@ -284,7 +328,9 @@ async def test_the_three_track_e_sections_file_under_their_own_groups():
|
||||
assert set(groups["mechanical"]) == MECH_TOOLS
|
||||
assert set(groups["sheet"]) == SHEET_TOOLS
|
||||
assert set(groups["architecture"]) == ARCH_TOOLS
|
||||
assert len(groups) == 26
|
||||
assert set(groups["understanding"]) == UNDERSTAND_TOOLS
|
||||
assert set(groups["plant"]) == PLANT_TOOLS
|
||||
assert len(groups) == 28
|
||||
|
||||
|
||||
async def test_system_about_reports_packs():
|
||||
|
||||
+13
-33
@@ -495,7 +495,7 @@ async def test_tool_groups_is_byte_identical_to_the_source_declared_grouping():
|
||||
|
||||
|
||||
async def test_tool_group_sizes_are_unchanged():
|
||||
"""Frozen snapshot of the surface (243 tools, 26 groups).
|
||||
"""Frozen snapshot of the surface (247 tools, 28 groups).
|
||||
|
||||
Taken before @cad_tool landed at 131 tools; `batch` moved 2 -> 3 when
|
||||
v1.5.0's `cad_batch` joined `entity_batch_create`/`entity_batch_modify`, and
|
||||
@@ -592,41 +592,19 @@ async def test_tool_group_sizes_are_unchanged():
|
||||
had grown while the tag was unranked on them returned to its
|
||||
pre-track-F value.
|
||||
|
||||
Track H group U opened SECTION 26 (Understanding & QA) with
|
||||
`drawing_understand`, tagged `analysis` and `query`: `analysis` outranks
|
||||
`query` in `_GROUP_TAG_PRIORITY`, so the reader files under `analysis`
|
||||
(12 -> 13) beside the other whole-drawing queries, and
|
||||
`drawing_scale_check`, tagged the same, followed it (13 -> 14). This
|
||||
snapshot is branch-local: the track H merge task recomputes it once the
|
||||
understand, network and diff branches have landed.
|
||||
|
||||
Track H group D opened its copy of SECTION 26 (Understanding & QA) with
|
||||
`drawing_diff` and `drawing_topology_check`, both tagged `analysis` and
|
||||
`query`; `analysis` outranks `query` in `_GROUP_TAG_PRIORITY`, so both file
|
||||
under `analysis` (12 -> 14). This snapshot is branch-local: the track H
|
||||
merge recomputes it once the understand, network and diff branches have
|
||||
landed.
|
||||
|
||||
Merged, the two SECTION 26 halves file all four readers under `analysis`
|
||||
(12 -> 16).
|
||||
|
||||
`analysis` moved 12 -> 13 when track H's SECTION 27 opened with
|
||||
`pipe_takeoff` on the network branch. It is tagged `plant` and
|
||||
`analysis`, and `plant` is not in `_GROUP_TAG_PRIORITY` on that
|
||||
branch, so it files under `analysis`. This snapshot is branch-local:
|
||||
the track H integration task gives `plant` its pack and its group
|
||||
once every branch has landed.
|
||||
|
||||
It moved 13 -> 14 when `cable_takeoff` joined the same section, for
|
||||
the same branch-local reason.
|
||||
|
||||
With SECTION 27 merged beside them, the four readers and the two
|
||||
takeoffs all file under `analysis` (12 -> 18) until the track H
|
||||
integration task ranks `understand` and `plant`.
|
||||
`understanding` and `plant` appeared with v1.6's track H: `understanding`
|
||||
(4, SECTION 26: `drawing_understand`, `drawing_scale_check`,
|
||||
`drawing_diff`, `drawing_topology_check` - the readers of drawings
|
||||
somebody else made, core pack) and `plant` (2, SECTION 27: `pipe_takeoff`
|
||||
and `cable_takeoff`, exactly `PACK_TOOL_NAMES["plant"]`). The three
|
||||
track-H branches tagged the six `analysis` and left the priority list
|
||||
alone, so `analysis` grew 12 -> 18 on them; the integration tagged
|
||||
SECTION 26 `understand` and ranked `understand` and `plant` directly
|
||||
after `arch` in `_GROUP_TAG_PRIORITY`, and `analysis` returned to 12.
|
||||
"""
|
||||
sizes = {label: len(names) for label, names in (await server._tool_groups()).items()}
|
||||
assert sizes == {
|
||||
"analysis": 18,
|
||||
"analysis": 12,
|
||||
"architecture": 12,
|
||||
"batch": 3,
|
||||
"blocks": 9,
|
||||
@@ -643,6 +621,7 @@ async def test_tool_group_sizes_are_unchanged():
|
||||
"mechanical": 14,
|
||||
"page_setup": 6,
|
||||
"pid": 9,
|
||||
"plant": 2,
|
||||
"premium": 12,
|
||||
"sheet": 11,
|
||||
"solids": 5,
|
||||
@@ -650,6 +629,7 @@ async def test_tool_group_sizes_are_unchanged():
|
||||
"system": 7,
|
||||
"templates": 2,
|
||||
"transactions": 3,
|
||||
"understanding": 4,
|
||||
"validation": 1,
|
||||
"view": 4,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user