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:
Umut CAN
2026-09-24 14:51:29 +03:00
co-authored by Claude Opus 5.5
parent 8c1a6cb27c
commit 835bc4b65e
9 changed files with 338 additions and 70 deletions
+14 -1
View File
@@ -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"),
)
+61
View File
@@ -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
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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,
}