{
  "markdown": "<div align=\"center\">\n\n# OpenTakeoff\n\n**The measurement engine for building plans—built so an AI agent can drive it, and so an estimator wants to.**\n\nA takeoff is the act of measuring quantities off a construction drawing. OpenTakeoff does it\ntwo ways over one engine: **47 MCP tools** for an agent, and a browser canvas for a person.\nAgents and people share the takeoff document and quantity calculations. Each sheet carries\nits calibration; measurements carry geometry, method and authorship. Recalibration updates\nquantities together, incompatible imports report scale conflicts, and agent measurements\ncarry an explicit review status. See the [Phase 1 test guide](docs/PHASE_1_TESTING.md).\n\n[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![Live demo](https://img.shields.io/badge/demo-opentakeoff.kentucky--ai.com-2ea44f.svg)](https://opentakeoff.kentucky-ai.com)\n[![MCP registry](https://img.shields.io/badge/MCP-io.github.Kentucky--ai%2Fopentakeoff-6f42c1.svg)](https://registry.modelcontextprotocol.io)\n[![npm](https://img.shields.io/npm/v/opentakeoff-mcp?label=opentakeoff-mcp)](https://www.npmjs.com/package/opentakeoff-mcp)\n[![Benchmark](https://img.shields.io/badge/benchmark-OpenTakeoff%20Academy-orange.svg)](https://aec.kentucky-ai.com)\n[![OpenArena](https://openarena.to/api/badge/cmsgykvsq0000mkuv7byhlgnl)](https://openarena.to/en/projects/cmsgykvsq0000mkuv7byhlgnl)\n[![Sponsor](https://img.shields.io/github/sponsors/Kentucky-ai?logo=githubsponsors&label=sponsor&color=EA4AAA)](https://github.com/sponsors/Kentucky-ai)\n\n[**For agents**](#for-agents--start-here) · [**Try the canvas**](https://opentakeoff.kentucky-ai.com) · [The engine's contract](#the-contract-that-makes-it-drivable) · [For the person at the canvas](#for-the-person-at-the-canvas) · [The data layer](#the-data-layer--why-this-engine-exists) · [Research](#the-research-program) · [Fork it](#fork-it) · [Contribute](#contributing)\n\n**The two manuals:** [agent manual](docs/AGENT_GUIDE.md) · [user manual](docs/USER_GUIDE.md)\n\n**Read this in:** [日本語](README.ja.md) · [한국어](README.ko.md) · [简体中文](README.zh-Hans.md)\n\n**Watch it:** [an autonomous agent runs a takeoff, live, no cuts (2:47)](https://youtu.be/e--kXxSGv7Y) · [hospital finish plan → report in about a minute (1:14)](https://youtu.be/cNDpPkTLY1k) · [canvas walkthrough (1:10)](https://youtu.be/aHiW8H2TSBs) · [One-Click Area (0:51)](https://youtu.be/YIjWZ-BAhLE)\n\n> **One-Click Area is temporarily gated.** The flood engine is being re-validated against a wider plan corpus. Until that finishes the One-Click tool is off the canvas rail (`O` reports the gate) and the `one_click` / `detect_rooms` MCP verbs are **not registered** (a default build ships <!--tool-count-->52<!--/tool-count--> tools). Trace rooms with **Area** (`A`) in the canvas and `measure_polygon` over MCP; every other tool, sweep and derivation is unchanged. A build lifts the gate with `VITE_ONE_CLICK=1` (canvas) / `OPENTAKEOFF_ONE_CLICK=1` (server). Sections and videos below that show One-Click describe the engine as it returns — see [`docs/design/ONE_CLICK_GATE.md`](docs/design/ONE_CLICK_GATE.md).\n\n<br/>\n\n<img src=\"docs/img/social-card.png\" alt=\"OpenTakeoff — a real takeoff on a floor finish plan, driven the same way by a person or by an AI agent over MCP, with the scale and origin of every measurement\" width=\"820\"/>\n\n</div>\n\n---\n\n## Start here\n\n| You are | Go here |\n|---|---|\n| **An estimator with a bid due** | [Open the canvas](https://opentakeoff.kentucky-ai.com)—drag in a plan, no account, nothing uploads. The [**user manual**](docs/USER_GUIDE.md) gets you from a blank tab to an exported takeoff in five minutes, and its [working order](docs/USER_GUIDE.md#the-working-order-on-a-real-bid) is the sequence to run on a real bid set. |\n| **An AI agent**—or the person wiring one up | `npx -y opentakeoff-mcp`, then the [**agent manual**](docs/AGENT_GUIDE.md): the operating model, the standard finish every takeoff ends with, what the engine refuses to guess, and why. Tool-by-tool reference is [`mcp/README.md`](mcp/README.md). |\n| **A developer building on the engine** | [`AGENTS.md`](AGENTS.md) is the repo map and the ship discipline; [`FEATURES.md`](FEATURES.md) maps every capability to the code that does it. |\n| **A crew that wants its own copy** | [**Fork it**](#fork-it)—your own instance on your own URL in a few minutes, Apache-2.0, nothing phones home. Same path if you're going to send a pull request. |\n\n### Windows, macOS, Linux — all of it\n\nOpenTakeoff is a **client-only browser app**, so the canvas runs the same on Windows, macOS,\nChromeOS and Linux in any current Chrome, Edge, Firefox or Safari. Nothing installs, nothing\nuploads, and no feature is gated on an operating system.\n\n- **Shortcuts are platform-aware.** The app labels modifiers for the keyboard in front of you —\n  `Ctrl` / `Alt` / `Shift` on Windows and Linux, `⌘` / `⌥` / `⇧` on a Mac — and the handlers have\n  always treated `⌘` and `Ctrl` as the same key. Press `?` in the canvas for the current list.\n- **The MCP server is tested on Windows.** `npx -y opentakeoff-mcp` runs on Windows, macOS and\n  Linux, and CI runs the full MCP suite — typecheck, tests, build and the packaged smoke test — on\n  `windows-latest` as well as `ubuntu-latest` on every change.\n- **Optional extras.** The bundled [capture server](capture/) is stdlib Python 3 and runs anywhere\n  Python does — on Windows invoke it with `python capture\\capture_server.py selftest` (or the `py`\n  launcher) rather than `python3`. Neither it nor the optional [`server/`](server/) AI sandbox is\n  needed to use the canvas.\n- **Locked-down enterprise fleets** (MSIX packaging, Windows Sandbox, Intune silent deploy) are\n  tracked in [#226](https://github.com/Kentucky-ai/opentakeoff/issues/226) and not yet built.\n\n## What this is\n\nMeasuring quantities off a plan is the input to every construction bid—how much floor, how\nmuch wall, how many fixtures, at what scale, on which sheet. It happens thousands of times a\nday. Until OpenTakeoff there was **no open-source takeoff engine at all**, web-based or\notherwise, and nothing an autonomous agent could call.\n\nOpenTakeoff is that engine, with two front ends over identical geometry:\n\n- **A stdio MCP server**—`npx -y opentakeoff-mcp`, <!--tool-count-->52<!--/tool-count--> tools, on the\n  [official MCP registry](https://registry.modelcontextprotocol.io). An agent opens a plan,\n  reads the title block, sets the scale, floods the rooms, checks its own work on a rendered\n  overlay, and hands back a marked-up planset PDF.\n- **A browser canvas**—no backend, no account, no upload. An estimator drags in a plan set\n  and traces it, using One-Click room detection, CAD hatches, roll-goods seam layout, a\n  materials buy list, and exports.\n\nNeither is a wrapper around the other. The MCP server imports\n`web/src/lib/{oneclick,sheets,geometry,totals}` directly, so a shape committed by an agent is\nfield-identical to one committed by a hand at the canvas—same flood mask, same corner snap,\nsame waste math, same refusal messages.\n\n**Provenance is the load-bearing part.** Every shape records the scale it was measured at, the\nmethod that produced it (vector flood, raster trace, hand-drawn, agent-proposed), whether a\nhuman corrected it, and the machine's original boundary frozen beside the correction.\nDownstream, that's an audit trail a PM can read. Upstream, it's a labeled\n*(geometry → finish)* pair—the training signal takeoff models have never had at scale. That\nsecond use is not a side effect; see [the data layer](#the-data-layer--why-this-engine-exists).\n\n## Recently shipped\n\n- **Stitched sheets**—a floor split across a match line becomes one working surface; a room\n  that crosses the seam traces as one shape, One-Click included\n  ([#161](https://github.com/Kentucky-ai/opentakeoff/issues/161))\n- **PDF layer roles**—CAD-exported sheets *state* what their ink is, so One-Click reads the\n  layer tree instead of inferring boundaries from hatch, with a Layers panel on the canvas and\n  scored corpus IoU ([#85](https://github.com/Kentucky-ai/opentakeoff/issues/85))\n- **The sheet graph**—an agent asks *\"what finish is in room 134, and how do you know\"* and\n  gets the schedule row with a citation per cell, across continuation sheets, rotated headers,\n  and multi-building keys: `sheet_graph` / `resolve_tag` / `find_schedule`\n  ([#87](https://github.com/Kentucky-ai/opentakeoff/issues/87))\n- **Roll goods**—opt a condition into broadloom or sheet material and the engine figures the\n  seams: lanes, multi-roll splits, cuts drawn to scale over their rooms in cutting order, a\n  to-scale roll diagram with drag-to-reorder, and order footage beside the measured quantities\n  ([#136](https://github.com/Kentucky-ai/opentakeoff/issues/136))\n- **Transitions, at the canvas**—**⟂ Transitions…** in the Takeoffs panel derives the line where\n  two finishes meet from rooms you already measured; `derive_transitions` does the same over MCP.\n  Flood-traced rooms don't share edges, so what's actually there is proximity in two flavors that\n  mean different things: finishes changing inside one open space commit as a butt joint, while\n  rooms parallel across a partition come back **withheld as questions**—the transition is a\n  threshold in a doorway, and nothing in a trace record says where the doorway is. Committing\n  34 LF of threshold because two rooms share 34 LF of wall would be a wrong bid with a machine's\n  confidence behind it ([#202](https://github.com/Kentucky-ai/opentakeoff/issues/202))\n- **`symbol_sweep`**—every instance of a repeated symbol from one marqueed example, crossing\n  scales only by a *stated* ratio, never a searched one\n- **`mark_verdict` / `delete_verdict`**—an agent signs its own work as a graphite `AGENT`\n  diamond; only a human hand mints the green `APPROVED` seal\n- **One-Click accuracy wave**—face extraction and gap tolerance from\n  [RFC #60](https://github.com/Kentucky-ai/opentakeoff/issues/60) (contributed by\n  [@knmurphy](https://github.com/knmurphy)), hairline runs shadowing a heavier wall classified\n  as annotation rather than boundary, and an in-swing door's sector taken behind the leaf\n  instead of the arc\n- **Voice takeoff** (gated off the toolbar by default; `VITE_COMMAND_BOX=1` builds it back)—hold `M` and say `carpet one, waste seven`; recognition is\n  whisper-tiny.en in WebAssembly on your machine, audio never leaves the browser\n  ([docs/VOICE.md](docs/VOICE.md))\n\nFull history: [CHANGELOG.md](CHANGELOG.md) · every capability mapped to its code:\n[FEATURES.md](FEATURES.md)\n\n---\n\n## For agents — start here\n\nPoint any stdio MCP client at the published package. Node 20+, no clone, no build:\n\n```json\n{\n  \"mcpServers\": {\n    \"opentakeoff\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"opentakeoff-mcp\"]\n    }\n  }\n}\n```\n\nIf you carry a GitHub identity, [fork the repository](https://github.com/Kentucky-ai/opentakeoff/fork)\nas well: that's where an engine change you make gets tested—every pull request from a fork runs the\nfull CI with a read-only token—and tested pull requests from agents merge under the same bar as\nanyone else's. The [contributor path](#fork-it) is the same one.\n\nClaude Code: `claude mcp add opentakeoff -- npx -y opentakeoff-mcp`. Claude Desktop users can\ndouble-click the `opentakeoff-mcp.mcpb` bundle from the\n[latest release](https://github.com/Kentucky-ai/opentakeoff/releases) instead—it excludes the\noptional native canvas on purpose, so every JSON tool works everywhere and the rendering\nsurfaces (`view_sheet`, the sheet-image resource) say exactly what's missing where they can't\nrun. Docker and a local clone are both supported: [`mcp/README.md`](mcp/README.md).\n\n<img src=\"docs/img/mcp-live-demo.gif\" alt=\"A real run, real time at 3×: an AI agent in a terminal one-clicks three patient rooms on a VA medical center finish plan over MCP; each export lands in the web app as dashed pencil proposals, and the operator accepts them — 743.64 SF, pencil to ink\" width=\"900\"/>\n\n*A real run (3× speed): the agent takes off patient rooms 161–163 on a federal finish plan,\nexporting after each commit. Every shape lands in the app as a dashed **pencil proposal** and\nbecomes ink only when the operator clicks Accept.* The full run, live and uncut, is\n[on YouTube (2:47)](https://youtu.be/e--kXxSGv7Y).\n\n### The tools\n\n| Group | Tools |\n|---|---|\n| **Open and orient** | `load_plan` · `sheet_info` · `sheet_context` · `get_sheet_vectors` · `read_sheet_text` · `find_text` · `view_sheet` |\n| **Scale** | `set_scale` |\n| **Measure** | `one_click` · `detect_rooms` · `measure_polygon` · `cut_out` · `measure_line` · `measure_surface` · `place_count` |\n| **Repeat and derive** | `symbol_sweep` · `sweep_schedule_row` · `derive_base` · `derive_transitions` · `apply_rules` |\n| **Read the drawing set** | `sheet_graph` · `resolve_tag` · `find_schedule` |\n| **Edit and audit** | `list_shapes` · `edit_shape` · `edit_condition` · `edit_materials` · `duplicate_condition` · `split_condition` · `delete_shape` · `undo_last` |\n| **Mark and sign** | `annotate` · `list_annotations` · `link_annotation` · `mark_verdict` · `delete_verdict` |\n| **Ask** | `create_rfi` · `list_rfis` · `resolve_rfi` · `delete_rfi` |\n| **Hand off** | `takeoff_summary` · `export_takeoff` · `export_report` · `export_marked_pdf` · `export_dxf` · `import_takeoff` |\n\nPlus browsable sheet resources (`takeoff://sheets`) so an agent can *see* the working set, not\nonly act on it. Multi-document sessions are first-class: a bid set is plans **plus** schedule\n**plus** addenda, and `load_plan --merge` adds a document without disturbing existing scales,\nconditions, or shapes—the sheet graph then spans the whole set, so a room tag on one file\nresolves to a schedule row in another. `edit_condition` reaches the waste %, the ×N multiplier,\nand `roll_setup`, so an agent's takeoff doesn't ship with net === gross.\n\n**The agent's manual is [`docs/AGENT_GUIDE.md`](docs/AGENT_GUIDE.md)**—the counterpart to the\nestimator's: the operating model in six facts, the standard finish every takeoff ends with, the\nwithheld-is-the-answer doctrine, what has no agent verb and why, and a refusal-to-next-move table.\nTool-by-tool reference: [`mcp/README.md`](mcp/README.md). The same surface in prose, with the\nsheet-graph and sweep behavior in depth: [`docs/MCP.md`](docs/MCP.md).\n\n### The contract that makes it drivable\n\nMost measurement APIs are hostile to an agent because they let it be confidently wrong. These\nare the rules that make this one safe to hand a model, and why each one exists:\n\n1. **One coordinate frame, stated everywhere.** Image pixels at render scale 2.0—PDF points\n   × 2, origin top-left, y down, the browser canvas's native space. Every sheet payload carries\n   dims in both px and pt. No tool takes a coordinate in units it has to guess.\n2. **Scale is a gate, not a default.** The drawn scale note is *read* off the sheet but never\n   applied silently; adopting it is always an explicit `set_scale`. Measuring an unscaled sheet\n   refuses. Pixels × a wrong scale² is every number wrong at once, so the engine would rather\n   stop than guess. Disagreeing scale notes inside a measured region raise a warning rather\n   than a silent pick.\n3. **The engine traces; the model doesn't invent.** `one_click` returns the ring the wall\n   network produced from a seed point you name. A model cannot hand back a polygon it imagined and have\n   it counted.\n4. **Every record carries how it was made.** Method, seed point, whether hatch filtering\n   engaged, whether it came off scan pixels, confidence factors,\n   and the machine's original ring if a human later moves it.\n5. **Agent work is pencil until a person inks it.** Exports land in the canvas as dashed\n   proposals. `mark_verdict` lets an agent sign its own work as a graphite `AGENT` diamond; the\n   green `APPROVED` seal has exactly one code path and it is the toolbar button under a human\n   hand. No MCP call, no import, mints one.\n6. **The deliverable is a marked-up planset, not JSON.** `export_marked_pdf` burns the work\n   into the drawings as drawn—condition colors, hatches, quantity chips, count markers—behind\n   a legend cover with totals and a tally of how much of the set a person has actually\n   reviewed. A takeoff nobody can check is not a takeoff.\n7. **Refusals are actionable strings.** *\"That space isn't enclosed on the plan linework—the\n   fill spilled\"* tells a model what to do next. A silent zero doesn't. Tools that can't answer\n   withhold with a stated reason rather than returning a plausible number.\n\n### Prove it — OpenTakeoff Academy\n\n[**aec.kentucky-ai.com**](https://aec.kentucky-ai.com) is a standalone open benchmark and\ncertification arena for agents that do takeoff. Bring any model and your own harness; you are\nscored on **operating a real takeoff tool** against geometry you don't control—a wrong\ncalibration yields a wrong area—not on emitting a plausible-looking number. Runs emit a\nsigned bundle with full provenance of every tool call, scoring is against held-out ground truth\nand a human Senior Estimator baseline, and clearing a tier earns a credential that's\nindependently verifiable. The Certified path drives this engine (`opentakeoff-mcp`) behind the\ntask tools. Repo:\n[Kentucky-ai/opentakeoff-academy](https://github.com/Kentucky-ai/opentakeoff-academy).\n\n---\n\n## For the person at the canvas\n\nThe agent path exists because the human path is real. Everything below is the production\nmeasuring engine carved out of a commercial Division 9 estimating system—not a demo\nreimplementation.\n\n```bash\ncd web\nnpm install\nnpm run dev        # http://localhost:5173\n```\n\nOr open the [**live demo**](https://opentakeoff.kentucky-ai.com). Drag in\n`demo/sample-plan.pdf`, accept the detected scale, choose a condition, press **`A`** (Area)\nand click the room's corners. (One-Click Area is temporarily gated — see the note at the top.) Open **Report** for the breakdown and the exports. That whole loop on\nvideo: [walkthrough (1:10)](https://youtu.be/aHiW8H2TSBs) ·\n[One-Click Area (0:51)](https://youtu.be/YIjWZ-BAhLE). The complete\nzero-to-exported walkthrough is the [**user manual**](docs/USER_GUIDE.md).\n\n**What a real bid looks like on it**, in the order an estimator works one:\n\n1. Drag in the whole `.zip` off the bid platform—plans, finish schedule, addenda.\n2. Set the scale on every sheet you'll measure, and **check a dimension** (`K`) on each. Ten\n   seconds a sheet, and it's the only mistake that gets every number at once.\n3. Pull your conditions off the architect's finish schedule instead of typing them, and set waste\n   and materials *before* you trace.\n4. Stitch anything split at a match line, align it, and only then start measuring.\n5. **Trace** the floors room by room — Area (`A`) while One-Click is gated. Derive base and transitions off the rooms you just\n   traced rather than measuring them a second time—and read what the derivation *reports and\n   never counts*, because those are doorway thresholds you still owe.\n6. Walk the set and look at what landed, fix with the grips, save a **revision**.\n7. Export both: the Report for pricing, the **Marked set** PDF for whoever has to check you.\n\nThe full version of that sequence, with the section for each step, is the manual's\n[working order](docs/USER_GUIDE.md#the-working-order-on-a-real-bid). What every term above means\nis in its [glossary](docs/USER_GUIDE.md#18-glossary--what-the-words-mean-here).\n\n### Open anything, instantly\nA plan **PDF**, an **image** (scan, screenshot, photo), or a whole **`.zip` plan set** straight\noff a bid platform. Zips are unpacked and images wrapped to PDF *in your browser*—multi-page,\nmulti-file, up to **4 sheets side-by-side**, with hostile-archive guards so a malformed zip\nfails cleanly instead of ballooning the tab. No upload step, no conversion service, no account.\n\n### A real measuring engine\n**One-Click Area** is the headline — **temporarily gated** while the flood engine is re-validated (see the note at the top); this is what it does when it is on: click inside a room, the linework bounds a flood fill, the\npolygon traces itself, the vertices snap to true corners. **Hatching and poché don't fool it**—tile\ngrids, plank lines, and section fills classify as pattern rather than wall, and the\nescalation is conservative enough that a misread can never come out worse than the strict fill.\n**Scanned sheets work too**: with no vector linework the engine reads rendered pixels—adaptive\nthresholding, polarity detection for blueprint negatives, a gap-bridging pass for\nfaded ink—and badges the result so you verify the edges before committing. On CAD exports\nthat publish a layer tree, One-Click reads the declared roles instead of inferring them.\n\nPlus the full manual kit—**Area, Rectangle, Linear, Curved Line, Surface Area (walls),\nCount**, and **Cut Out** deducts—and a **Zone check** that answers \"what's in this wing?\"\nwithout touching the takeoff.\n\n**⟂ Transitions** derives the line where two finishes meet, from rooms you already measured.\nFinishes changing inside one open space commit as a dashed butt-joint run you accept; rooms\nparallel across a wall are **reported and never counted**, because that transition is a\nthreshold in a doorway no trace can locate—you get its length, the wall thickness, and a link\nthat puts it on screen.\n\n<div align=\"center\">\n<img src=\"docs/img/one-click-area.gif\" alt=\"One-Click Area on a real finish plan: one click inside a patient room and the whole room traces itself wall to wall — 240.7 SF, committed on Enter\" width=\"820\"/>\n</div>\n\n### Drafting aids that behave like drafting aids\n**45°/90° angle lock**: come within a few degrees of square or diagonal and the segment locks\nto the axis—the click commits the *exactly* on-axis point, so walls come out dead square\n(hold `⇧` to force it at any angle). On the canvas the crosshair **is** the cursor: the OS\npointer hides, a star marks the crossing, in-progress work draws in the instrument's own\ncobalt, committed shapes wear their condition color. The lock reads quietly—the star swells,\nthe preview thickens, a chip shows the locked angle and the live segment length. **Snap**\n(beta) pulls onto true PDF endpoints, and a corner beats an axis.\n\n### Scale that matches real plan sets\nAuto-detects the drawn scale note, or **calibrate** from any known dimension. Scale is\nremembered **per sheet**, because plan sets are never one uniform scale and tools that assume\nthey are get the numbers wrong. **Check a dimension** (`K`) is calibrate's read-only twin: pick\na printed dimension string, type what the drawing says, and get a graded verdict (green within\n1%, amber within 5%, red past it) plus a one-click **Recalibrate to this**. Every scale\nacceptance drops an ephemeral calibrated ruler bar on the sheet, so a 2×-off scale is obvious\nbefore anything gets traced. Imperial or metric (m²/m, 1:50-style ratios) is a display toggle—takeoffs\nare stored unit-agnostically, so flipping it never changes a measurement.\n\n### Conditions, materials, and the buy list\nA **condition** is one finish (LVP, carpet, tile, base…), carrying a line/fill color, a **CAD\nhatch pattern** so the canvas reads like the real drawing, a per-condition **waste %**, an\n**×N multiplier**, a default wall **height**, and a **thickness** that turns a linear run into\nborder SF. **Import from schedule** parses the architect's finish table off the sheet into\nconditions behind a verify dialog—you approve what becomes a condition, and the product spec\nrides along as read-only report columns.\n\n**Supporting Materials** is the layer most takeoff tools punt on: per condition, a labor type\nand a subfloor type, plus the consumables that actually go on the order—adhesive, sealer,\nthinset, grout, cove-base adhesive—each with a **coverage rate** and a **basis** (floor SF /\nlinear LF / each / **figured seam LF**). Order quantity derives automatically: measured ÷\ncoverage, **rounded up** to whole units. Adhesive and mortar lines get coverage presets; grout lines get a calculator that\nderives SF/bag from tile size, thickness, joint width, and bag weight. Preset values are\nindustry-typical round numbers—always verify against the product data sheet.\n\n### Roll goods — the seams, figured\nOpt a condition into broadloom or sheet material (material class, roll width, max roll length,\nseam and wall allowances, direction, sell unit) and the engine lays out the cuts: lanes, seam\nplacement, multi-roll splits, and order footage. Cuts draw to scale over their own rooms in\nmaterial-true colors, numbered in cutting order, and slide or resize in an edit mode that's on\nthe undo stack. The docked Roll panel shows those cuts nested **on the roll** with dimensions\nand drag-to-reorder re-packing, and **Roll Order LF**, **Rolls**, and **Seam LF** ride the\nReport, CSV, and Excel next to the measured quantities. Seam LF is the weld-rod / seam-tape\nquantity read straight off that layout—counted between adjacent lanes of the same room, net\nof the wall overage, only where two lanes actually face each other—so a supporting-materials\nline on the **seam LF** basis prices the rod off where the cuts meet instead of off a share of\nthe perimeter. A 20-ft-wide room off a 12-ft roll seams once down its length; the same square\nfootage as two separate 10-ft rooms seams not at all, and no factor on area can tell those\napart. Available headlessly too, through `roll_setup` on `edit_condition`. (The roll-layout engine\nwas contributed by Michael Hartman.)\n\n### Multi-sheet reality\n**Stitching**: a floor split across a match line becomes one working surface—align the joint\nby picking the same drawn point on both sheets, then trace straight across the seam.\n**Levels** group a multi-floor set. A visual **gallery** (`G`) is where you choose and open sheets, and\n**Regroup** restores a side-by-side composition in one click. A trace can't span two *grouped*\nsheets—the gap between panels isn't real distance, so the commit refuses and points you at\nstitching.\n\n### Reports, exports, and revisions\nA per-condition breakdown—**Floor / Wall / Border SF, LF, EA, total SF, SY**, with and\nwithout waste—plus a combined **materials buy list**. Waste applies only in the report's\norder quantity, never to the live measured number, so the takeoff and the buy list stay honest\nabout which is which. Export **CSV**, **JSON**, a real **Excel workbook** (Summary / By-sheet /\nMaterials / Shapes-audit / **By floor × room**, full-precision cells, formula-shaped names kept\ninert text), print,\nor **Marked Set PDF**—a distribution-ready planset built entirely in your browser for a GC\nwho will never install anything.\n\nWhen the addendum lands, **Revisions** makes it data instead of archaeology: save a named\nrevision at each bid revision, then compare any two as quantity deltas per condition, per\nsheet, and on the buy list, with a compare CSV. The compare is deliberately quantity-level\nrather than geometric—it tells you which numbers moved, not which wall did. Restore banks the\nlive takeoff first, so it's never a one-way door.\n\n<div align=\"center\">\n<img src=\"docs/img/report.png\" alt=\"OpenTakeoff report — per-condition breakdown and materials buy list\" width=\"780\"/>\n</div>\n\n### Markups, seals, and RFIs\nA separate layer the totals never count: revision clouds, callouts, text notes, highlighter\nink, **images** (upload a PNG/JPEG, or marquee a region of the plan to drop it back as a\nfloating screenshot—move, resize, and it burns into the marked set), and reusable **stamps**\n(plank direction, seam direction, pattern origin—build your own, or import an `.svg`). **Approval seals** are the estimator's ink: click a committed takeoff to\napprove it, and the Marked Set's cover gains a tally line—*N estimator-approved · N\nagent-marked* — so a PM knows exactly how much of the set a person has looked at. The **RFI\nregister** turns any markup into a tracked question with status, priority, ball-in-court, and\ncost/schedule impact flags, exporting as CSV/JSON and as an RFI schedule page in the marked set.\n\n### The Agent panel, in the browser\nThe same proposer/reviewer split as MCP without leaving the canvas: describe a takeoff in a\nsentence and a model—**yours**, on your key, from your browser—works the sheet with the\napp's own deterministic tools and stages dashed proposals you accept, correct, or reject. It\ncannot invent geometry (`propose_shapes` rejects anything uncited) and it cannot set a scale.\nTo watch the loop with no AI account at all, run the keyless deterministic mock server in\n`scripts/`.\n\n### A vector-sharp canvas\nPast ~1.15× zoom **times your display's pixel ratio**, the visible region re-renders straight\nfrom the PDF vectors at your current zoom rather than magnifying a fixed bitmap, so fine\ncallouts and hatching never blur—and it engages after a pause in the gesture, so a continuous\nzoom stays on the fast base layer while you're still moving. It overlays only what's on screen,\nso there's no full-sheet bitmap to hold. **Dark view** (☾) inverts the sheet pixels\nthemselves—a true negative print, white linework on black, not a CSS filter—with hatches\nretuned, and exports follow it.\n\n### Yours, locally\nEvery drawing, scale, condition, markup, and RFI autosaves to **your browser** (IndexedDB +\nlocalStorage). Nothing is uploaded, there's no account, and there's no server in the default\nbuild. The flip side is stated plainly in the manual: storage is per browser, per origin, and\nclearing site data clears your work.\n\n<details>\n<summary><strong>Optional: team cloud mode (Google sign-in + Drive)</strong></summary>\n\n<br/>\n\nEverything above is the default and it's unchanged: open the page and you're an anonymous,\nlocal-only user. A team on Google Workspace can *optionally* sign in to unlock a shared mode\ninstead: projects live as folders in the team's own Google **Drive**, the project list is\ndeep-linked from an existing **Glide** app, and material costs come from a synced\n`pricing.json`. It's strictly additive—set nothing and it doesn't exist. The security posture\nstays honest: still a plain static site, **no secrets in the bundle**, team-only because the\nGoogle OAuth app is **Internal** to your domain, and the data sits in **your own Drive**. See\n[`docs/GOOGLE_SETUP.md`](docs/GOOGLE_SETUP.md) and\n[`docs/GLIDE_INTEGRATION.md`](docs/GLIDE_INTEGRATION.md). A cloud deployment can also opt into\n**local-first sync** (`VITE_CLOUD_SYNC=1`): annotations stay canonical in the browser and sync\nto Drive in the background, so the canvas is instant and survives a flaky network—[`docs/SYNC_ARCHITECTURE.md`](docs/SYNC_ARCHITECTURE.md).\n\n</details>\n\n<details>\n<summary><strong>Optional: bring your own vision model</strong></summary>\n\n<br/>\n\nOpenTakeoff can ask a vision model **you** provide to read things off the plan—starting with\nthe drawn scale when a sheet's text doesn't state one (scans, rotated notes, image title\nblocks). Click **AI** in the toolbar and point it at an **OpenAI-style** endpoint (the default;\nlocal runtimes on your own machine speak it and need no key) or an **Anthropic-style** one,\nplus a vision-capable model id.\n\n- **What's sent, and only when you click an AI button:** one snapshot of the sheet region in\n  question, plus the question—to *your* endpoint. Never the whole plan file, file names,\n  project names, or your takeoff.\n- **Nothing configured = nothing exists.** Unconfigured builds add zero UI beyond the button\n  and make zero AI network calls. No telemetry either way.\n- The answer is only ever a **suggestion**, landing in the same confirm-to-apply flow as a\n  text-detected scale, with the calibrated guide bar shown on acceptance.\n- The key is stored in this browser's localStorage—use one you can revoke. Deployers:\n  `VITE_AI_ENDPOINT` / `VITE_AI_MODEL` / `VITE_AI_PROVIDER` bake team defaults, but **never set\n  `VITE_AI_KEY` on a public deploy**—Vite inlines it into the shipped bundle.\n\n</details>\n\n## What's in the box\n\n| Area | What you get |\n|---|---|\n| **Ingest** | PDF, image, or `.zip` plan set—unpacked in-browser, multi-page, multi-file, up to 4 sheets side-by-side |\n| **Scale** | Auto-detect the drawn note, calibrate from a known dimension, or verify one with a graded check—per sheet |\n| **Measure** | One-Click Area (vector flood + raster fallback — temporarily gated), Area, Rectangle, Linear, Curved Line, Surface Area, Count, Cut Out deducts, ⟂ Transitions, Zone check—imperial or metric |\n| **Drawing aids** | 45°/90° angle lock with `⇧` hard-lock, live angle + segment-length readout at the cursor, endpoint Snap (beta) |\n| **Conditions** | Color + CAD hatch per finish, waste %, ×N multiplier, wall height, border thickness, schedule import, browser-wide library |\n| **Supporting Materials** | Labor + subfloor type, coverage rate × basis (incl. figured seam LF) → rounded order quantities, trowel/roller presets, grout calculator |\n| **Roll goods** | Per-condition roll setup → lanes, seams, multi-roll splits, to-scale cuts with drag-to-reorder nesting, Roll Order LF + Rolls + figured Seam LF on every export |\n| **Multi-sheet** | Sheet gallery, tabs and side-by-side groups, Regroup, levels, **stitching across a match line**, PDF layer roles |\n| **Report** | Per-condition Floor/Wall/Border SF, LF, EA, SY with and without waste, plus the combined buy list; columns, grouping, saved templates |\n| **Export** | CSV, JSON, **Excel (.xlsx)**, print, **Marked Set PDF**, RFI CSV/JSON |\n| **Revisions** | Save at each bid revision, compare quantity deltas per condition/sheet/buy list, guarded restore |\n| **Markups** | Clouds, callouts, notes, highlighter, **images** (upload or marquee screenshot), stamps, **approval seals**, RFI register—separate layer, never counted |\n| **Voice** | Push-to-talk takeoff commands, recognized on-device in WebAssembly; audio never leaves the browser — gated off the toolbar by default (`VITE_COMMAND_BOX=1`) |\n| **View** | Light or **dark (negative print)**—sheet pixels inverted at draw time, exports follow |\n| **Storage** | IndexedDB + localStorage—client-only, nothing uploaded |\n| **MCP server** | <!--tool-count-->52<!--/tool-count--> tools + browsable sheet resources on stdio, multi-document sessions ([`mcp/`](mcp/README.md)) |\n| **Provenance** | Every shape records its scale, its method, its confidence, and whether a person or an agent made it |\n| **Capture (opt-in)** | Bundled [capture server](capture/README.md) banks each contributed takeoff as (geometry → label) training rows |\n| **Deploy** | One static build—Netlify, Vercel, GitHub Pages, Cloudflare Pages, S3, any static host |\n\n---\n\n## The data layer — why this engine exists\n\nEvery finished takeoff is a set of expert decisions: *this* region gets *this* finish, at\n*this* waste, yielding *these* quantities. Done once, that's a bid. Recorded every time, it's a\n**labeled dataset that does not currently exist**—plan geometry paired with the finish an\nexpert assigned it, which is the exact raw material for training a model that can do takeoff.\nToday that data evaporates the moment the bid goes out.\n\nThe thesis, stated so it can be attacked: **markup is label.** Professional takeoff software\nalready stores every drawn region as vector geometry, and reconstructing those polygons\nreproduces the recorded quantities exactly—so two decades of estimating work is an exact,\n*verifiable* corpus rather than a noisy one. That claim is what the whole research program\ntests, and it's patent pending.\n\nOpenTakeoff is the instrument that produces the corpus, with the collection path opt-in and\nauditable:\n\n- The **Contribute** button in the Report builds a derived-only payload—condition labels,\n  shape roles, quantities, geometry normalized 0-to-1 against the sheet, and per-shape\n  provenance (hand-traced versus machine-proposed, and whether a human corrected it, with the\n  machine's original ring beside the fix). The builder is ~150 audited lines\n  ([`web/src/lib/contribute.js`](web/src/lib/contribute.js)); the normative wire contract is\n  [`docs/CONTRIBUTION_SPEC.md`](docs/CONTRIBUTION_SPEC.md).\n- **Never sent**, enforced by a whitelist in the builder: the PDF or any render of it, file or\n  sheet names, project/client names, markup text, absolute coordinates, scale *values* (only\n  the scale's provenance—calibrated, detected, or standard), and edit timing beyond a\n  creation stamp. One linkage is deliberate and disclosed: shapes carry opaque, locally-minted\n  IDs so a re-contribution after an addendum supersedes rather than duplicates.\n- The bundled **capture server** ([`capture/`](capture/README.md))—one stdlib-only Python\n  file, no pip install—receives it on localhost and banks one training row per labeled shape,\n  hash-gated so re-contributions never duplicate. v2 rows distinguish what the machine got\n  right from what an expert had to fix, which is the signal that actually teaches a takeoff\n  model. Point it at a synced folder with `--mirror` and the corpus rides existing company\n  storage sync, atomically.\n\n```bash\npython3 capture/capture_server.py    # then, in the app's browser console:\n# localStorage.opentakeoff_contribute_endpoint = \"http://localhost:8787/contribute\"\n```\n\nRun OpenTakeoff as-is and none of this exists for you—nothing is captured, nothing leaves\nyour machine. Install it and every takeoff you *choose* to contribute compounds into an asset\nyou own. This is the open edition of the capture layer inside\n[Spline](https://spline.quisutdeus.io), the commercial Division 9 estimating system OpenTakeoff\nwas carved from, where capture runs ambient on autosave and commit instead of behind a button.\nThe row schema and the training angle are in [`capture/README.md`](capture/README.md).\n\n## The research program\n\nOpenTakeoff is the open half of an applied-research program run by a working commercial\nflooring estimator who builds the AI his own department uses\n([Kentucky AI](https://kentucky-ai.com)). The open-core boundary is the same one the better\nopen scientific software draws: **the measurement engine—rendering, scale, geometry, exports,\nthe MCP server—is Apache-2.0 and stays open. The models trained on our own estimating archive\nare proprietary.** You get a real tool with no seat licenses; the part only our data can build\nstays ours.\n\nThe research side is run as a lab, and the receipts are the point:\n\n- **Parameter-efficient tuning, not pretraining.** QLoRA adapters on open-weights bases\n  (~0.1% of parameters trained), specialized from a verified bid archive—cheap enough to\n  retrain when the data says retrain, small enough to ship. The flagship adapter predicts bid\n  totals at **12.3% median absolute percentage error on a 51-project temporal holdout**,\n  against **62.8%** for the untuned base; full method and honest caveats on the\n  [model card](https://huggingface.co/Kentucky-ai/div9-flooring-estimator-gemma4-31b).\n- **Verified labels in.** Before a historical bid becomes training data it passes a\n  dual-document verification gate: totals must reconcile between the bid workbook and the\n  separately filed proposal, change orders only count when corroborated by an actual\n  change-order document, and line-item arithmetic is recomputed and forensically checked.\n  Unverifiable projects don't train.\n- **Verifiable rulers out.** Models are scored against temporally held-out projects—future\n  bids, not a random split—with a geometry scorer whose **own error floor is measured\n  (0.4%)**, so a number can be attributed to model error versus measurement error.\n- **Multi-seed replication.** No result is promoted from a single training run; promotion\n  requires seed replication with paired bootstrap confidence intervals, and the cross-seed\n  spread gets published alongside the best seed.\n- **Negative results are kept.** The experiment ledger records what failed and why—an\n  unfreeze recipe that destroyed detection, a vertical-specialist model that lost to the\n  generalist's cross-vertical transfer—next to what worked.\n- **Leak-audited before release.** Identifiers are replaced *before* training, so the weights\n  never see a real name, and every public artifact passes a differential red-team: adversarial\n  extraction probes against the tuned model with the untuned base as control.\n\nSanitized artifacts—model cards, benchmark specs, papers—publish as they clear review:\n[Hugging Face](https://huggingface.co/Kentucky-ai) ·\n[kentucky-ai.com](https://kentucky-ai.com). The agent-side evaluation lives in\n[OpenTakeoff Academy](https://aec.kentucky-ai.com).\n\n---\n\n## Run it / deploy it\n\nTo use it, all you need is a browser. To self-host, it's one static build you can drop\nanywhere—no backend, no database, no environment to stand up.\n\n```bash\ncd web\nnpm install\nnpm run build      # → web/dist/  (static; host it anywhere)\n```\n\n[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/Kentucky-ai/opentakeoff)\n\nThe repo ships a root `netlify.toml`, so the button is genuinely one-click. The same\n`web/dist/` works on **Vercel, GitHub Pages, Cloudflare Pages, S3**—anywhere that serves\nstatic files. Running your own reverse proxy—nginx, Docker, Tailscale? Check\n[`docs/SELF_HOSTING.md`](docs/SELF_HOSTING.md) first—there's one MIME-type gotcha worth\nknowing about. Deployment notes and the optional AI backend:\n[`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).\n\n## Fork it\n\nApache-2.0: fork it, change it, ship it—for your own crew or as the base of your own product.\nA fork is the unit of ownership here, and it's the unit of contribution: the same three steps\ngive you a private instance and a branch to send back.\n\n1. **[Fork on GitHub](https://github.com/Kentucky-ai/opentakeoff/fork)**, then clone your fork.\n2. **Run it:** `cd web && npm ci && npm run dev` — the canvas is at `localhost:5173`, and\n   `npm run check` is the exact CI gate (typecheck, lint, test, build).\n3. **Put it on your own URL:** the repo carries its [`netlify.toml`](netlify.toml) (base `web`,\n   publish `dist`), so importing your fork into Netlify deploys with no settings; any static host\n   works, and [`docs/SELF_HOSTING.md`](docs/SELF_HOSTING.md) names the one nginx gotcha. Your\n   instance keeps every plan local exactly as the public one does.\n\nPull requests from a fork run the full CI with no secrets and a read-only token\n([`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md)), so a green check on your fork is a green check here.\nThe codebase is deliberately small and readable, and the geometry libraries are pure so you can\nlift them straight out:\n\n| What | Where |\n|---|---|\n| Flood fill, face extraction, corner snap, raster fallback | [`web/src/lib/oneclick.ts`](web/src/lib/oneclick.ts)—pure TS, tested |\n| Scale detection, sheet helpers, polygon area | [`web/src/lib/sheets.ts`](web/src/lib/sheets.ts)—pure TS, tested |\n| Waste, square-yard, coverage → order quantity | [`web/src/lib/totals.js`](web/src/lib/totals.js) |\n| Roll-goods lane and seam layout | [`web/src/lib/rollgoods.js`](web/src/lib/rollgoods.js)—pure, tested |\n| Persistence (IndexedDB + localStorage) | [`web/src/lib/store.js`](web/src/lib/store.js) |\n| PDF / image / zip ingest | [`web/src/lib/ingest.js`](web/src/lib/ingest.js) |\n| The canvas (one large component, ~90% of the app) | [`web/src/pages/TakeoffCanvas.jsx`](web/src/pages/TakeoffCanvas.jsx) |\n| MCP server (imports the same libs) | [`mcp/src/`](mcp/src/) |\n| Design tokens—source of truth for color and spacing | [`web/src/styles/tokens.css`](web/src/styles/tokens.css) |\n\nThird-party integrations and downstream forks run on this engine today.\n\n`cd web && npm run check` is the exact CI gate—typecheck, lint, test, build. Keep\n`oneclick.ts` and `sheets.ts` free of React and DOM; that purity is what makes them reusable and\ntestable. Never commit real construction plans. See [CONTRIBUTING.md](CONTRIBUTING.md) and\n[AGENTS.md](AGENTS.md)—the repo's own instructions for coding agents—plus the\n[user manual](docs/USER_GUIDE.md).\n\n## Contributing\n\nThe open work is architectural, and it's posted as RFCs with a stated finish line rather than a\nmanufactured chore list. Currently open:\n\n- [**RFC #60**—make One-Click Area genuinely great](https://github.com/Kentucky-ai/opentakeoff/issues/60):\n  face extraction, gap tolerance, confidence. Partially landed—a first slice merged in\n  [#179](https://github.com/Kentucky-ai/opentakeoff/pull/179), contributed by\n  [@knmurphy](https://github.com/knmurphy) and credited in the release notes—and the accuracy\n  ceiling is still open.\n- [**RFC #87**—the sheet graph](https://github.com/Kentucky-ai/opentakeoff/issues/87):\n  resolve room tags, schedules, legends, and detail callouts into one queryable graph with a\n  citation per answer. Two phases shipped; revision clouds and detail-callout chains are open.\n- Anything labeled [`rfc`](https://github.com/Kentucky-ai/opentakeoff/labels/rfc) or\n  [`flagship`](https://github.com/Kentucky-ai/opentakeoff/labels/flagship)—a flagship is an\n  open design-and-build challenge where multiple entries are welcome and the best one merges\n  with credit.\n- Smaller, fully-specified entry points are labeled\n  [`good first issue`](https://github.com/Kentucky-ai/opentakeoff/labels/good%20first%20issue)—they\n  name the exact files. Claim one in a comment and go.\n\nGround rules are in [CONTRIBUTING.md](CONTRIBUTING.md). The bar is a green `npm run check` plus\na test for anything touching the geometry libraries; tested PRs merge fast. CI also holds two\nlines `npm run check` doesn't: every relative link and anchor in the docs must resolve\n(`node scripts/check-doc-links.mjs` runs it locally), and `web/bench/results.json` must match\nwhat the engine actually produces—an engine change carries its bench delta in the same PR. External\ncontributions are credited by name in the commit and the release notes—and because\n`opentakeoff-mcp` publishes to npm off a `mcp-v*` tag, engine work you land ships to every\nagent that pulls the package.\n\nFound something exploitable? Report it through\n[private vulnerability reporting](https://github.com/Kentucky-ai/opentakeoff/security/advisories/new)\nrather than a public issue. [SECURITY.md](SECURITY.md) states the threat model up front—worth a\nread before reporting, since it explains what the trust boundary actually is for a client-only app\nand a local stdio MCP server, and what that does and doesn't make a vulnerability.\n\n## Tech stack\n\n- **Frontend:** React 18 + Vite 6, plain JSX\n- **Drawing:** raw HTML5 Canvas + SVG—no charting or canvas frameworks\n- **Geometry:** TypeScript (`oneclick.ts`, `sheets.ts`), pure and unit-tested\n- **PDF rendering:** [pdf.js](https://github.com/mozilla/pdf.js)\n- **Plan-set ingest:** fflate (zip) + pdf-lib (image → PDF), lazy-loaded\n- **Speech:** transformers.js, whisper-tiny.en (q8 encoder + uint8 decoder) in a Web Worker—benchmarked\n  against the alternatives in [`docs/VOICE.md`](docs/VOICE.md)\n- **MCP:** TypeScript stdio server importing the web engine's own libraries\n- **Storage:** IndexedDB + localStorage—no backend required\n- **Tests:** `node --test` + `tsx`\n- **No paid dependencies.** See [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).\n\n## Status\n\nA working tool used on real commercial bids, not a preview. The measuring engine is the\nproduction engine carved out of a commercial estimating system, and the same engine answers to\na person at the canvas or an agent over MCP with the same math, the same scale gate, and the\nsame provenance record. Named limits, so you don't find them the hard way: **Snap** is beta,\nrevision compare is quantity-level rather than geometric, and the translated\nREADMEs lag the English one. Issues and pull requests are welcome.\n\n## Who's building this\n\nI run estimating for a commercial flooring company and build the AI that runs my department.\nOpenTakeoff is the open half of that work: the measuring engine, given to anyone—human or\nagent—who needs to read a building. The models trained on our own estimating archive stay\nours, and the boundary is drawn in public so it can be held to account.\n\nWhat makes the data worth anything is that it comes from bids that were actually submitted, won\nor lost, and reconciled against a separately filed proposal. That's also why the engine had to\nbe free: a corpus is only as good as the number of real takeoffs that flow through the\ninstrument producing it.\n\n— Michael · [Kentucky AI](https://kentucky-ai.com)\n\n**Contact:** research collaborations, data questions, press, or anything that is not a bug —\n[research@kentucky-ai.com](mailto:research@kentucky-ai.com). Bugs and feature requests go in\n[issues](https://github.com/Kentucky-ai/opentakeoff/issues); security reports follow [SECURITY.md](SECURITY.md).\n\n## License\n\n[Apache License 2.0](LICENSE)—use it, [fork it](#fork-it), ship it, build on top of it. See\n[NOTICE](NOTICE) for attribution.\n",
  "bytes": 48917,
  "sha": "af59d339e84897e84b329c57cadea1e85bb9818b8fadcc0ea33f2126a6e458d8",
  "repo_slug": "kentucky-ai/opentakeoff",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kentucky_ai_opentakeoff_5160a266/readme"
}