feat(understand): cable_takeoff - one row per load, rounded to the metre
cable_rows reads each load's power, phases, voltage, neutral and VFD from the P&ID's electrical texts near its tag, and its panel from the layout's wiring callouts (else the P&ID's), names normalised CP 1 -> CP-1. The length is the Manhattan distance on the layout from the tag to its panel, and the cable ROUNDUP(length x (1 + allowance)) to the metre, on the length rounded to 1e-6 m so binary noise never buys a metre. Power is never invented: a load with no stated kW keeps an empty cell and an open item. Without a layout the lengths stay empty with an open item per row. Room and panel totals count a panel package's power once, never its children's as well; the rule is on the method sheet. Sections only from the caller's section_rules - no standard table exists here. The workbook gains the cable shape (Metraj, Ozet by room and panel, Kontrol as the open-items list, Metodoloji) in the same three languages; cable_takeoff joins SECTION 27. Inventory regenerated; README snapshot 243 tools (238 advertised); registry snapshot analysis: 14. 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
653535bcc1
commit
459d3b9228
@@ -1,4 +1,5 @@
|
||||
"""pipe_takeoff over the MCP wire, on the synthetic plant pair - never on a client drawing."""
|
||||
"""pipe_takeoff and cable_takeoff over the MCP wire, on the synthetic plant pair - never on a
|
||||
client drawing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -101,3 +102,41 @@ async def test_the_tool_says_it_writes_a_file_and_destroys_nothing(client):
|
||||
annotations = tools["pipe_takeoff"].annotations
|
||||
assert annotations.readOnlyHint is False
|
||||
assert annotations.destructiveHint is False
|
||||
|
||||
|
||||
async def test_cable_takeoff_gives_every_synthetic_load_its_rounded_cable(client, plant, tmp_path):
|
||||
arguments = {
|
||||
"pid_path": plant["pid"],
|
||||
"layout_path": plant["layout"],
|
||||
"output": str(tmp_path / "cables.xlsx"),
|
||||
"lang": "ru",
|
||||
}
|
||||
result = (await client.call_tool("cable_takeoff", arguments)).structured_content
|
||||
rows = {row["tag"]: row for row in result["rows"]}
|
||||
for expected in plant["cable_rows"]:
|
||||
assert rows[expected["tag"]]["cable_m"] == expected["roundup_m"], expected["tag"]
|
||||
assert Path(result["files"]["csv"][0]).name == "cables_metraj.csv"
|
||||
|
||||
|
||||
async def test_cable_takeoff_without_a_layout_leaves_every_length_empty(client, plant):
|
||||
result = (
|
||||
await client.call_tool("cable_takeoff", {"pid_path": plant["pid"]})
|
||||
).structured_content
|
||||
assert result["rows"], "the synthetic P&ID carries electrical loads"
|
||||
assert all(row["cable_m"] is None for row in result["rows"])
|
||||
no_layout = {item["tag"] for item in result["open_items"] if item["item"] == "no_layout"}
|
||||
assert no_layout == {row["tag"] for row in result["rows"]}
|
||||
|
||||
|
||||
async def test_a_malformed_section_rule_is_refused_by_path(client, plant):
|
||||
arguments = {"pid_path": plant["pid"], "section_rules": [{"max_kw": 5}]}
|
||||
result = await client.call_tool("cable_takeoff", arguments, raise_on_error=False)
|
||||
assert result.is_error is True
|
||||
assert "section_rules[0].section: expected a non-empty text" in result.content[0].text
|
||||
|
||||
|
||||
async def test_the_cable_tool_says_it_writes_a_file_and_destroys_nothing(client):
|
||||
tools = {tool.name: tool for tool in await client.list_tools()}
|
||||
annotations = tools["cable_takeoff"].annotations
|
||||
assert annotations.readOnlyHint is False
|
||||
assert annotations.destructiveHint is False
|
||||
|
||||
Reference in New Issue
Block a user