Public leaderboard

Public assessment

Kentucky-ai/opentakeoff (opentakeoff-mcp)

opentakeoff-mcp · v0.9.21 · scanned

What changed in the harness

Selection accuracy 76→90, token cost down 1%, unconfirmed writes 0%→0%.

Category breakdown

Where the score comes from.

Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.

01Safety

0.0 / 30

0.0 out of 30
02Legibility

18.4 / 30

18.4 out of 30
03Economics

6.4 / 20

6.4 out of 20
04Discoverability

10.4 / 20

10.4 out of 20

Highest-impact fix

Estimated gain +30 points

Add explicit identity and permission preflight tools

Expose machine-readable principal/tenant confirmation and a non-mutating permission check so agents can verify both before destructive actions.

Description evidence

Defects and rewrites.

12 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.

Tool Defect types Suggested rewrite
set_scale
params_unexplained
Set the scale for sheet (its sheet key, e.g. "plan.pdf" or "plan.pdf#2", or its title-block number, e.g. "A-101") — pass exactly ONE of: label (a standard scale, e.g. '1/4" = 1'-0"'), upp (real feet per image px), calibrate (two points along a known dimension plus its real feet), or use_detected (adopt the drawn scale note read off the sheet). The detected scale is never applied automatically — setting it is always this explicit call. Returns the resolved upp, label, and source. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
one_click
params_unexplained
One-Click Area: on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"), click at (x, y) — image px — inside a room and the plan's vector linework bounds it — flood fill, contour trace, vertices snapped to true PDF endpoints. SCANNED sheets work too (#154): where vectors can't bound the room (an image-only scan, or a scan wrapper whose only linework is the title block), the flood falls back automatically to the sheet's rendered pixels — same engine the canvas uses — and the reply plus the committed shape's origin carry raster_traced: true so a pixel-bounded ring is never mistaken for a vector-snapped one. Vector always wins where it works; a raster ring's corners are unsnapped, so audit it with view_sheet {overlay: true} before trusting the total. With the sheet's scale set, returns area_sf / perimeter_lf; pass condition (a finish tag, e.g. "CPT-1") to commit the traced shape to the takeoff. Without a scale it returns px-only quantities with a warning and commits nothing. role "deduct" makes the committed shape subtract (default "floor_area" adds it). After committing, LOOK at what landed — view_sheet {overlay: true} — and fix an overshot ring with edit_shape before trusting any total. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
detect_rooms
params_unexplained
Batch room detection on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"): reads every room-number label off the sheet's text layer (e.g. "134", "OFFICE 101") and runs One-Click at each — one call instead of read_sheet_text + reasoning + N one_click calls. An OCR'd scan (text layer, no vector linework) floods the rendered pixels instead (#154), disclosed per room and on origin as raster_traced. A seed is only reported as a room once it survives three gates, and everything skipped is counted and reasoned in `withheld`. With the sheet's scale set, returns area_sf/perimeter_lf per room. TO COMMIT, choose the honest source of the finish tag: assign_from_schedule routes every room through its OWN room-finish schedule row (needs a scale and a room-finish schedule in the working set); condition commits every room under one stated tag (role "deduct" makes them subtract). Without a scale, returns px-only quantities per room and commits nothing. A batch commit is NOT finished until you have LOOKED at it: view_sheet {overlay: true}, audit every ring, fix misses with edit_shape / delete_shape. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
measure_polygon
params_unexplained
Measure a closed polygon you supply on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"): verts is the closed ring (min 3 vertices, image px); returns area_sf and perimeter_lf at the sheet's scale. Requires the scale to be set. Pass condition (a finish tag, e.g. "CPT-1", minted on first use) to commit the shape to the takeoff; role "deduct" makes it subtract (default "floor_area" adds it). Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
measure_line
params_unexplained
Measure an open polyline on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"): pts is the polyline (min 2 points, image px); returns length_lf at the sheet's scale. Requires the scale to be set. Pass condition (a finish tag, e.g. "BASE-1", minted on first use) to commit it as a linear shape (base, transitions, feature strips). Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
measure_surface
params_unexplained
Surface Area — wall SF (#146), on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"): trace an OPEN run along the wall in plan view (pts, min 2 points, image px) and the quantity is traced LF × height. This is how wall tile, wainscot, and wall systems are taken off — the quantity family one_click and measure_polygon cannot produce. Height lives on the CONDITION (the canvas's H knob): pass height_ft to set it on this call, or set it once with edit_condition; with neither, this refuses and mints nothing. The shape snapshots the height it was quantified at. Requires the sheet's scale. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
place_count
params_unexplained
Count markers — EA (#146) — on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101"): one point per marker, one each. Thresholds, stair nosings, floor boxes, entrance mats — the scale-free quantity family. Commits one count shape per point (computed {count: 1}, exactly the canvas's Count tool) under condition (a finish tag, minted on first use), NO scale required, and the whole call is ONE undo step like a detect_rooms sweep. takeoff_summary reports them as ea; the marked set draws each marker. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
symbol_sweep
params_unexplained
Find EVERY instance of a repeated plan symbol from ONE example on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101") — drains, thresholds, fixtures, transition markers: marquee a tight seed_rect around a single instance and the sheet's vector linework is searched for every other placement of that same segment cluster. Deterministic geometry: each placement scores as the length-weighted fraction of the seed's segments reproduced within tolerance_px, under translation plus rotation/mirroring. Score ≥ 0.92 is a match; 0.75–0.92 comes back in `withheld` with a reason. commit: true (requires condition, the finish tag to mint EA markers under) commits every match center as an EA count marker; rotations/mirror toggle whether rotated/mirrored placements count. No scale needed: EA is scale-free. After a batch commit, LOOK at what landed — view_sheet {overlay: true} — before trusting the EA total. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
sheet_context
params_unexplained
The STRUCTURE of sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101") in one call and one frame: the classified vector segments, the positioned text spans, and the hatch-family instances of a region — everything the engine itself floods against, exposed as data instead of pixels. Use it to reason about a region rather than look at it. Hatch families carry a content-derived id so matching a plan region to a legend swatch is comparing two ids. Decimation (min_len_px, max_segments) is declared, ordered, and counted on every reply. A scan returns has_vector_linework: false with empty vectors. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
read_sheet_text
params_unexplained
The text of sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101") with positions — returns items [{str, x, y}] in image px plus the joined text. Optionally restrict to a region {x0, y0, x1, y1}. Use it to read title blocks, room labels, finish schedules, and scale notes. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
find_text
params_unexplained
LOCATE a known string q on sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101") — the complement to read_sheet_text (which returns what a region SAYS; this finds WHERE a string you already know sits). Case-insensitive substring match against each pdf.js text run, so a room label split across runs ("OFFICE" then "134") needs a find_text call per fragment, or read_sheet_text over a region to see it joined. Every hit's center feeds straight into one_click as the seed. Optionally restrict to a region {x0, y0, x1, y1}; results cap at limit (default 200), with count/truncated telling you how much a tighter region or higher limit would recover. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).
view_sheet
params_unexplained
SEE sheet (its sheet key, e.g. "plan.pdf", or title-block number, e.g. "A-101") — render the page (or a crop of it) to a PNG image, returned inline. This is your eyes on the plan, so CROP, DON'T SQUINT: the render downsamples to the px budget (≤2000 long side) — a full-sheet render finds WHERE things are, and only a tight region crop can tell you what the linework and labels actually say. region is in image px, the same space as every other tool, so returned-image pixels map linearly back to it — feed those coordinates straight into one_click, measure_polygon, or read_sheet_text. overlay:true burns the session's committed shapes into the render (human-affirmed ink solid red, unreviewed machine shapes dashed blue). grid burns in a calibrated 1-ft/5-ft measuring grid ("auto" uses the sheet's set scale, or pass the drawing scale as inches-per-foot before set_scale) so dimensions can be counted rather than eyeballed. Rendering needs the optional native canvas (@napi-rs/canvas); where it isn't installed this tool errors cleanly. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space).

Selection evidence

Confusable tool pairs.

16 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.

Tool A Tool B Confidence Why they collide
measure_polygon measure_line medium Both take a sheet + point/vertex list in image px and can commit a condition; a task like 'measure this room's perimeter' or an ambiguous outline could plausibly route to either polygon (area+perimeter) or line (length only).
measure_line measure_surface high Both trace an open polyline on a wall in image px and commit under a condition; 'measure this wall run' is genuinely ambiguous between plain LF (measure_line) and traced-LF×height wall SF (measure_surface), especially since measure_surface's height requirement isn't obvious from a short task description.
measure_polygon measure_surface medium A task like 'take off this wall area' could be misrouted to measure_polygon (closed-area SF) instead of measure_surface, which is the actual tool for wall SF via traced LF × height.
edit_materials edit_condition medium Both mutate a 'condition' by name with no shape_id; a vague task like 'update the CPT-1 condition settings' could target either the coverage-rate materials rows or the waste/multiplier/height knobs.
edit_shape edit_condition medium Both accept a 'condition' parameter with different meaning — edit_shape reassigns a shape to a finish tag, edit_condition edits the tag's own knobs — so 'edit the condition' is ambiguous as to which tool is meant, especially without a shape_id explicitly in mind.
delete_shape edit_shape low Both operate on a committed shape_id; a task to 'remove/clear this shape's geometry' could be misrouted to edit_shape (revise) versus the intended delete_shape (remove), though the verbs are fairly distinct.
export_takeoff export_report high Both are 'export the takeoff' style calls returning JSON; the tools' own descriptions warn about this exact confusion (raw canvas payload with CONFIG materials vs computed report with net quantities and buy list), showing real risk that a generic 'export my takeoff data' request picks the wrong one.
export_report export_marked_pdf medium Both are described as 'the deliverable' of a takeoff and are meant to be paired; a task like 'give me the final takeoff deliverable' could be satisfied by only calling one when both are needed, or the wrong one chosen if only one file is expected.
takeoff_summary export_takeoff medium Both can answer 'give me the totals/takeoff data'; takeoff_summary is compact per-condition totals while export_takeoff is the full raw payload — an underspecified request for 'the takeoff numbers' could trigger either.
find_schedule find_text medium A task like 'find the finish schedule' could be handled by find_schedule(kind='finish') or misrouted to find_text(q='schedule'), since both are 'locate something on the sheet' tools with overlapping vocabulary.
read_sheet_text find_text medium Both read text from a sheet region; a task like 'find the room label text' is ambiguous between read_sheet_text (dump all text in a region) and find_text (locate a specific known string), especially when the exact string isn't yet known to the agent.
sheet_context read_sheet_text medium Both accept sheet+region and return text among other data; a task like 'what does this region contain/say' could be routed to either the full structural sheet_context or the text-only read_sheet_text.
sheet_context view_sheet medium Both take sheet+region and are used to inspect a region before tracing; 'look at this area of the sheet' could plausibly call sheet_context (structured data) instead of view_sheet (actual image), since the descriptions overlap on the 'reason about a region' use case.
sheet_context sheet_graph medium Names are similar and both describe sheet 'structure'; a task like 'give me the sheet's layout info' is ambiguous between sheet_context's per-region vector/text/hatch structure and sheet_graph's whole-document role/schedule/room index.
sheet_info sheet_context low Both return sheet-level structural info (vector presence/segment counts vs classified segment detail); a generic 'tell me about this sheet's linework' request could be misrouted between the two.
list_annotations link_annotation low Similar names and shared 'annotation'/'condition' vocabulary, but list (read) vs link (attach/detach) are distinct verbs, making genuine mis-selection less likely though names could be conflated in a hurried tool pick.

Compare the field

One score is useful.
The evidence makes it actionable.

Back to the leaderboard