opentakeoff-mcp 0.9.38 → 0.9.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -107,7 +107,7 @@ includes document text, shape vertices, or result payload content.
107
107
  |---|---|
108
108
  | `load_plan` | Open a plan PDF from disk. Default replaces the whole session; **`merge: true` ADDS the document to the working set** (#152) — plans + schedule + addenda as one takeoff, sheet graph spanning the whole set, marked set covering every worked sheet. Returns per-sheet dims, title-block `sheet_number`, and the detected drawn scale where present. |
109
109
  | `sheet_info` | One sheet's dims, vector segment count, scale status, detected suggestion, committed shape count. |
110
- | `set_scale` | Set a sheet's scale — exactly one of `label`, `upp`, `calibrate {p1, p2, feet}`, `use_detected`. |
110
+ | `set_scale` | Set a sheet's scale — exactly one of `label`, `upp`, `calibrate {p1, p2, feet}`, `use_detected`. **Lands unconfirmed** (`confirmed: false`) until a human confirms in the canvas — see Scale rules. |
111
111
  | `one_click` | One-Click Area at (x, y): the sealed flood engine bounded by the plan linework, traced, vertices snapped — the SAME feet-true arguments the canvas passes at a click (gap sealing up to a door width, door-swing wedge inclusion, the half-foot minimum-passage rule), so an MCP trace and a canvas click at one seed measure the same square footage (pinned against the bench corpus goldens in `test/parity.test.ts`). Every trace carries the engine's account of itself: `confidence` 0–1 with `confidence_factors` naming each deduction (`gap_sealed_px`, `door_wedges`, `min_pass_delta`, …) — a review prioritizer, never a verification; a low score is a `view_sheet {overlay: true}` audit prompt, not a fact. On a SCANNED sheet (no usable linework) the flood falls back automatically to the rendered pixels — same engine as the canvas — with `raster_traced` disclosed on the reply and on the shape's origin (#154). Pass `condition` to commit (the full account stamps `origin` centrally at the commit); `role: "deduct"` subtracts. |
112
112
  | `detect_rooms` | Batch One-Click: reads every room-number label off the sheet's text layer and floods each — one call instead of `read_sheet_text` + reasoning + N `one_click` calls, through the SAME sealed engine per room (confidence + the engine account ride each room and its committed origin). Only cleanly-traced rooms come back; everything skipped is counted and reasoned in `withheld` (degenerate / duplicate / implausible / unresolved), never dropped silently. To commit: `assign_from_schedule: true` routes each room through its OWN room-finish schedule row and commits under the FLOOR finish that row states (rooms the schedule can't answer for return in `unresolved[]` with reasons and re-seedable coordinates); or pass `condition` to commit every room under one stated tag. |
113
113
  | `measure_polygon` | Area + perimeter of a polygon you supply (min 3 verts). Requires scale. |
@@ -216,6 +216,14 @@ space, which makes them usable directly as click targets.
216
216
 
217
217
  - A detected scale is a **suggestion** — it is never applied automatically.
218
218
  Adopting it is always an explicit `set_scale { use_detected: true }`.
219
+ - **Agent proposes, human confirms.** `set_scale` is the agent surface, so a
220
+ scale set here lands **unconfirmed** (`confirmed: false` in the reply).
221
+ Quantities still flow — the gate is a flag, never a refusal — but
222
+ `takeoff_summary` names the affected sheets in
223
+ `scale_unconfirmed`, and the export/report carry `scale_confirmed` so the
224
+ canvas can ask the estimator to confirm (its scale menu grows a
225
+ **Confirm agent-set scale** row on import). Only a human act in the canvas
226
+ clears the flag.
219
227
  - `measure_polygon` and `measure_line` refuse without a scale:
220
228
  `Set the scale for <sheet> first — use set_scale (detected: <label>).`
221
229
  - `one_click` without a scale returns a **px-only preview**
@@ -4199,7 +4199,11 @@ function reportJson({ projectName = "", rows = [], bySheet = [], scaleInfo = [],
4199
4199
  schema: "opentakeoff.report.v1",
4200
4200
  project_name: projectName || null,
4201
4201
  generated_with: "OpenTakeoff",
4202
- sheets: scaleInfo.map((si) => ({ sheet_id: si.sheet_id, sheet: label(si.sheet_id), scale_source: si.scale_source ?? si.source ?? "unknown" })),
4202
+ // scale_confirmed (scale gate): false = an agent set this sheet's scale and
4203
+ // no human confirmed it — the report's consumer should treat those sheets'
4204
+ // quantities as standing on an unverified number. Absent input = true
4205
+ // (human-era payloads predate the flag).
4206
+ sheets: scaleInfo.map((si) => ({ sheet_id: si.sheet_id, sheet: label(si.sheet_id), scale_source: si.scale_source ?? si.source ?? "unknown", scale_confirmed: si.scale_confirmed !== false })),
4203
4207
  // custom-column values APPEND after materials (row key order otherwise
4204
4208
  // untouched). Iterating the DEFINED columns — never raw attrs — naturally
4205
4209
  // drops orphaned colIds; attrValue (the shared assigned-value rule) keeps
@@ -5399,11 +5403,13 @@ var Session = class _Session {
5399
5403
  }
5400
5404
  s.upp = upp;
5401
5405
  s.scaleSource = source === "label" ? "standard" : source === "calibrate" ? "calibrated" : source;
5406
+ s.scaleConfirmed = false;
5402
5407
  return {
5403
5408
  sheet: s.key,
5404
5409
  upp,
5405
5410
  ...label ? { label } : {},
5406
5411
  source,
5412
+ confirmed: false,
5407
5413
  // #153 — several DISTINCT scale notes on one sheet means enlarged plans
5408
5414
  // or details are likely; region measurements will warn when a
5409
5415
  // disagreeing note sits inside them, but say it up front too
@@ -6679,7 +6685,8 @@ var Session = class _Session {
6679
6685
  summary() {
6680
6686
  const rows = conditionTotals(this.conditions, this.shapes, this.seamCtx());
6681
6687
  const lean = rows.map(({ color, fill, hatch, materials, ...rest }) => rest);
6682
- return { conditions: lean, totals: grandTotals(rows) };
6688
+ const unconfirmed = [...this.sheets.values()].filter((s) => s.upp != null && s.scaleConfirmed === false).map((s) => s.key);
6689
+ return { conditions: lean, totals: grandTotals(rows), ...unconfirmed.length ? { scale_unconfirmed: unconfirmed } : {} };
6683
6690
  }
6684
6691
  deleteShape(id) {
6685
6692
  const i = this.shapes.findIndex((x) => x.id === id);
@@ -7458,7 +7465,15 @@ var Session = class _Session {
7458
7465
  schema: ANN_SCHEMA,
7459
7466
  project_name: "",
7460
7467
  units: "imperial",
7461
- sheets: [...this.sheets.values()].filter((s) => s.upp != null).map((s) => ({ sheet_id: s.key, units_per_px: s.upp })),
7468
+ sheets: [...this.sheets.values()].filter((s) => s.upp != null).map((s) => ({
7469
+ sheet_id: s.key,
7470
+ units_per_px: s.upp,
7471
+ // provenance rides the payload (it used to be dropped here): the canvas
7472
+ // hydrates scale_source for its report and scale_confirmed for the
7473
+ // scale gate's confirm affordance — absent = confirmed (pre-flag docs)
7474
+ ...s.scaleSource ? { scale_source: s.scaleSource } : {},
7475
+ ...s.scaleConfirmed === false ? { scale_confirmed: false } : {}
7476
+ })),
7462
7477
  conditions: this.conditions,
7463
7478
  shapes: this.shapes,
7464
7479
  markups: this.markups,
@@ -7488,7 +7503,7 @@ var Session = class _Session {
7488
7503
  projectName,
7489
7504
  rows,
7490
7505
  bySheet: sheetTotals(this.conditions, this.shapes),
7491
- scaleInfo: [...this.sheets.values()].filter((s) => s.upp != null).map((s) => ({ sheet_id: s.key, scale_source: s.scaleSource ?? "unknown" })),
7506
+ scaleInfo: [...this.sheets.values()].filter((s) => s.upp != null).map((s) => ({ sheet_id: s.key, scale_source: s.scaleSource ?? "unknown", scale_confirmed: s.scaleConfirmed !== false })),
7492
7507
  markups: this.markups,
7493
7508
  rfis: [],
7494
7509
  rollGoods: rollReportRows(byCond, rows)
@@ -7722,6 +7737,7 @@ var setScaleOutput = {
7722
7737
  upp: z.number().describe("Real feet per image px at render scale 2.0"),
7723
7738
  label: z.string().optional().describe("The standard scale label, when set by label or detected note"),
7724
7739
  source: z.enum(["label", "upp", "calibrate", "detected"]),
7740
+ confirmed: z.boolean().describe("Always false here: set_scale is the agent surface, and an agent-set scale stays UNCONFIRMED until a human confirms it in the canvas \u2014 quantities still flow, wearing the caveat"),
7725
7741
  warning: z.string().optional().describe("Present when the sheet carries MULTIPLE distinct scale notes (#153) \u2014 enlarged plans/details likely; region measurements under a disagreeing note will warn")
7726
7742
  };
7727
7743
  var oneClickOutput = {
@@ -7888,7 +7904,8 @@ var takeoffSummaryOutput = {
7888
7904
  lf_net: z.number(),
7889
7905
  ea: z.number(),
7890
7906
  sy_net: z.number()
7891
- }).passthrough()
7907
+ }).passthrough(),
7908
+ scale_unconfirmed: z.array(z.string()).optional().describe("Sheets whose scale is agent-set and no human has confirmed \u2014 these totals stand on an unverified scale; verify against a stated dimension or confirm in the canvas")
7892
7909
  };
7893
7910
  var exportTakeoffOutput = {
7894
7911
  schema: z.string(),
@@ -9792,6 +9809,7 @@ async function importTakeoff(session, filePath) {
9792
9809
  if (s && s.upp == null && row.units_per_px > 0) {
9793
9810
  s.upp = row.units_per_px;
9794
9811
  s.scaleSource = row.scale_source ?? "upp";
9812
+ if (row.scale_confirmed === false) s.scaleConfirmed = false;
9795
9813
  }
9796
9814
  }
9797
9815
  const tagKey2 = (t) => String(t || "").trim().toUpperCase();
@@ -10375,7 +10393,7 @@ function registerResources(server, session) {
10375
10393
  // package.json
10376
10394
  var package_default = {
10377
10395
  name: "opentakeoff-mcp",
10378
- version: "0.9.38",
10396
+ version: "0.9.39",
10379
10397
  mcpName: "io.github.Kentucky-ai/opentakeoff",
10380
10398
  type: "module",
10381
10399
  description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentakeoff-mcp",
3
- "version": "0.9.38",
3
+ "version": "0.9.39",
4
4
  "mcpName": "io.github.Kentucky-ai/opentakeoff",
5
5
  "type": "module",
6
6
  "description": "OpenTakeoff MCP server — drive the takeoff engine from your MCP client over stdio.",