From 6be23d3ddeeb25d3a0d6d1cd78c67a1e51ab1d42 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 03:00:12 +0000 Subject: [PATCH 01/21] RESIDUAL-DARK: the developer's actual question was built, routed and unreachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other figure on the pro forma runs FORWARD from a land price somebody typed. Site acquisition is the one number a developer negotiates, and the question at the table is "what is the most I can pay for the dirt and still clear my hurdle" — a different solve. FIN-CALC shipped that solve. proforma/residual.py bisects the land line over the same forward solve() every other number comes from, the route serves it, test_fin_calc.py covers it, and ApiClient.residualLand() was written for it. No screen ever called it: the method sat in clientCallers.test.ts's UNCALLED list, reachable only from a script. Same shape as DISC-poché and the MEP browser. THE SUBSTANCE IS THE THREE CAVEATS, NOT THE NUMBER. residual_land_value is careful in exactly the way a panel throws away: * land_value: null — unreachable even at $0 land. Printing a figure would be inventing one; printing nothing reads as a failure. It renders as the finding, quantified with at_zero_land, in the engine's own words: "the deal, not the dirt". * converged: false WITH a figure — the bisection hit its cap, so that figure is a bracket endpoint, not a price. * bounds — the honest answer in that second case, and DECLARED NOWHERE in this client. The route returns it and residual.py's docstring lists it; the client's return type omitted it, so no unread-field audit here could see it, every one of them starting from the declared interfaces. The applied value goes back as the basis the engine SOLVED for. _with_land scales the FIRST category:"land" line and zeroes any others, so the residual is the TOTAL basis; the driver form's "Land $" field is bound to cost_lines.0.amount, true of the default set and not of one adopted from the massing tab. A second land line left standing would make the forward re-solve carry more land than the answer allowed for, and every visible number would still balance. landBasis() / applyLandBasis() address it the engine's way. Thirteen tests, one caveat per `it`, because one test over three caveats passes when two of them work. Mutation-checked: printing a figure when land_value is null, always claiming convergence, ignoring bounds, sending the percentage unscaled, reading cost line 0 instead of the first land line, and dropping the zeroing each red exactly the test written for them. ALSO FIXED — the XSS source pin in proforma.render.test.ts accused a safe line. It required the literal spelling escapeHtml((e as Error).message), and this directory imports the escaper under two names: escapeHtml in massingTab.ts and proforma.ts, `escapeHtml as esc` in testfitTab.ts and now residualLandCard.ts. So a line escaping CORRECTLY under the conventional alias was reported as an unescaped XSS sink. A check keyed on the local NAME cannot see a call that is about the BINDING — and it failed in the direction that costs the most trust: not a miss, a confident accusation about a safe line, telling its author they had an XSS. Renaming the import to suit the regex was the tempting repair and the wrong one: the convention it would bend to is the regex's, not the directory's. The escaper resolves through the file's own ui/feedback import now. And the pin could not tell "no offenders" from "nothing to offend" — it asserted an empty list with no floor on either side, so a refactor moving every sink out of proforma/ would have left it green and meaningless. It now proves it found files and is still looking at sinks before it may report none unescaped. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- apps/web/src/api/clientCallers.test.ts | 4 +- apps/web/src/api/proforma.ts | 10 +- apps/web/src/proforma/proforma.render.test.ts | 81 ++++++- apps/web/src/proforma/proforma.ts | 23 +- .../web/src/proforma/residualLandCard.test.ts | 175 +++++++++++++++ apps/web/src/proforma/residualLandCard.ts | 205 ++++++++++++++++++ 6 files changed, 482 insertions(+), 16 deletions(-) create mode 100644 apps/web/src/proforma/residualLandCard.test.ts create mode 100644 apps/web/src/proforma/residualLandCard.ts diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index fceafcdd..a7f8692d 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -310,7 +310,9 @@ const UNCALLED: readonly string[] = [ "pdfInfo", "permitsTimeline", "preconSnapshot", "proformaRenovation", "proformaRollover", "progressActuals", "progressCaptureDiff", "progressRollup", "raisePlan", "recordDistribution", - "rentRollScrub", "residualLand", "reviewPost", + // `residualLand` left this list 2026-09-25 — `proforma/residualLandCard.ts` is the Feasibility + // tab's inverse solve. The engine and the client method shipped with FIN-CALC; nothing called it. + "rentRollScrub", "reviewPost", "reviewScenario", "reviseDrawing", "runClash", "runMacro", "saveClausePlaybook", "saveDealAuthority", "saveMacros", "saveViewTemplates", "scanDeviation", "scopeRegister", "securitiesPackage", "sendDigest", diff --git a/apps/web/src/api/proforma.ts b/apps/web/src/api/proforma.ts index 3f53c43d..5ed48b1d 100644 --- a/apps/web/src/api/proforma.ts +++ b/apps/web/src/api/proforma.ts @@ -201,11 +201,17 @@ export function withProforma>(Base: TBase) { `/proforma/scenarios/${sid}/review`, { method: "POST", body: JSON.stringify({ action, note: note ?? "" }) }); } - /** FIN-CALC — residual land value: the land price that hits a target return (bisection over the solve). */ + /** FIN-CALC — residual land value: the land price that hits a target return (bisection over the solve). + * + * `bounds` was returned by the route, listed in `proforma/residual.py`'s own docstring, and + * **declared nowhere here** until 2026-09-25 — so no unread-field audit in this tree could see it: + * every one of them starts from the declared interfaces. It is the honest answer in the + * `converged: false` case, where the `land_value` above is a bracket endpoint rather than a price, + * and `proforma/residualLandCard.ts` renders it as the range. */ residualLand(assumptions: unknown, target: string, targetValue: number, maxLand?: number) { return this.json<{ land_value: number | null; achieved: number | null; target: string; target_value: number; iterations: number; converged: boolean; at_zero_land: number | null; - note?: string }>( + bounds?: [number, number]; note?: string }>( `/proforma/residual-land`, { method: "POST", body: JSON.stringify({ assumptions, target, target_value: targetValue, max_land: maxLand ?? null }) }); } diff --git a/apps/web/src/proforma/proforma.render.test.ts b/apps/web/src/proforma/proforma.render.test.ts index 9af16947..6a1a903f 100644 --- a/apps/web/src/proforma/proforma.render.test.ts +++ b/apps/web/src/proforma/proforma.render.test.ts @@ -164,24 +164,81 @@ describe("renderMassing (characterization)", () => { expect(host.textContent).toContain("422"); }); + // The four sinks fixed on 2026-08-02 took `(e as Error).message` into innerHTML unescaped. + // Two are exercised above; the other two live in panels that need a deep api mock to drive, + // so pin ALL of them at the source level, DERIVED not enumerated: an interpolation of an + // error message into innerHTML must go through `escapeHtml` (textContent/setStatus are exempt — + // they don't parse markup). Down-to-zero is the required state; any new unescaped sink fails. + // + // THE ESCAPE FUNCTION IS RESOLVED THROUGH THE FILE'S OWN IMPORT, NOT MATCHED BY NAME. The first + // version required the literal spelling `escapeHtml(...)`, and this directory imports it under two + // names: `escapeHtml` in `massingTab.ts` and `proforma.ts`, `escapeHtml as esc` in `testfitTab.ts` + // and `residualLandCard.ts`. So a line that escapes CORRECTLY under the conventional alias was + // reported as an unescaped XSS sink — and `residualLandCard.ts` is what met it. + // + // *A check keyed on the local NAME cannot see a call that is about the BINDING*, and this one failed + // in the direction that costs the most trust: not a miss, a confident accusation about a safe line. + // Renaming the import to suit the regex was the tempting repair and it is the wrong one — the + // convention it would bend to is the regex's, not the directory's, and the next person to write + // `esc` meets the same false alarm with a message telling them they have an XSS. + /** Local names bound to `ui/feedback`'s `escapeHtml` in this source — `esc` counts, an alias to + * something else does not. Empty when the file imports no escaper, which makes every sink in it an + * offender, as it should. */ + const escapeNames = (src: string): string[] => { + const out: string[] = []; + for (const m of src.matchAll(/import\s*\{([^}]*)\}\s*from\s*["'][^"']*ui\/feedback["']/g)) { + for (const spec of m[1]!.split(",")) { + const parts = spec.trim().split(/\s+as\s+/); + if (parts[0]?.trim() === "escapeHtml") out.push((parts[1] ?? parts[0]).trim()); + } + } + return out; + }; + + /** Lines in `src` taking an error message into innerHTML without passing it through an escaper. */ + const unescapedSinks = (src: string): number[] => { + const names = escapeNames(src); + const hits: number[] = []; + for (const [i, line] of src.split("\n").entries()) { + if (!/innerHTML\s*[+]?=/.test(line) || !/\((e|err) as Error\)\.message/.test(line)) continue; + const escaped = names.some((n) => + new RegExp(`\\b${n}\\(\\((e|err) as Error\\)\\.message\\)`).test(line)); + if (!escaped) hits.push(i + 1); + } + return hits; + }; + + it("SELF-TEST: the source pin resolves the escaper through the import, under either spelling", () => { + const sink = (call: string) => `import { escapeHtml as esc } from "../ui/feedback";\n` + + `x.innerHTML = \`
\${${call}}
\`;\n`; + // An aliased escaper IS escaping. This is the case that misfired on residualLandCard.ts. + expect(unescapedSinks(sink("esc((e as Error).message)")), "an aliased escape read as a sink").toEqual([]); + // The un-aliased spelling, in a file that aliases it, is bound to nothing — flag it. + expect(unescapedSinks(sink("escapeHtml((e as Error).message)")), "an unbound name read as safe").toEqual([2]); + // And the defect itself is still found, which is the only reason the check exists. + expect(unescapedSinks(sink("(e as Error).message")), "the raw sink was missed").toEqual([2]); + // A file importing no escaper at all cannot be escaping. + expect(unescapedSinks("x.innerHTML = `${esc((e as Error).message)}`;\n"), + "a sink in a file with no escaper import read as safe").toEqual([1]); + }); + it("SOURCE PIN: every error-message innerHTML sink in proforma/ stays on the escaped path", async () => { - // The four sinks fixed on 2026-08-02 took `(e as Error).message` into innerHTML unescaped. - // Two are exercised above; the other two live in panels that need a deep api mock to drive, - // so pin ALL of them at the source level, DERIVED not enumerated: an interpolation of an - // error message into innerHTML must go through escapeHtml (textContent/setStatus are exempt — - // they don't parse markup). Down-to-zero is the required state; any new unescaped sink fails. const { readFileSync, readdirSync } = await import("node:fs"); const { join } = await import("node:path"); + const files = readdirSync(__dirname).filter((n) => n.endsWith(".ts") && !n.endsWith(".test.ts")); + // The scan must reach the directory, or "no offenders" is a fact about an empty list. + expect(files.length, "the proforma/ source scan found no files").toBeGreaterThan(4); const offenders: string[] = []; - for (const f of readdirSync(__dirname).filter((n) => n.endsWith(".ts") && !n.endsWith(".test.ts"))) { + let sinks = 0; + for (const f of files) { const src = readFileSync(join(__dirname, f), "utf-8"); - for (const [i, line] of src.split("\n").entries()) { - if (/innerHTML\s*[+]?=/.test(line) && /\((e|err) as Error\)\.message/.test(line) - && !/escapeHtml\(\((e|err) as Error\)\.message\)/.test(line)) { - offenders.push(`${f}:${i + 1}`); - } - } + sinks += src.split("\n").filter((l) => /innerHTML\s*[+]?=/.test(l) + && /\((e|err) as Error\)\.message/.test(l)).length; + offenders.push(...unescapedSinks(src).map((n) => `${f}:${n}`)); } + // …and it must still be looking at sinks. Zero offenders out of zero sinks proves nothing, and + // that is how this pin would quietly stop meaning anything if the panels were refactored. + expect(sinks, "no error-message innerHTML sinks found at all").toBeGreaterThan(4); expect(offenders).toEqual([]); }); }); diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index 06ee8be0..2024b205 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -7,6 +7,7 @@ import { provenanceLine } from "./provenanceLine"; import { drawPackageLines } from "./drawPackage"; import { money, pct } from "./format"; import { renderMassingTab } from "./massingTab"; +import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; import { renderTestFitTab } from "./testfitTab"; import { downloadPostedPdf, camStatementPath } from "../api/downloadPdf"; import { toast } from "../ui/feedback"; @@ -148,7 +149,7 @@ export class ProformaUI { const self = this as unknown as { root: HTMLElement }; const into = (el: HTMLElement, fn: () => void) => { const r = self.root; self.root = el; try { fn(); } finally { self.root = r; } }; this.overviewEl = sections.over; this.renderOverview(); - if (sections.feas) into(sections.feas, () => { this.renderMassing(); this.renderTestFit(); this.renderProperty(); }); + if (sections.feas) into(sections.feas, () => { this.renderMassing(); this.renderTestFit(); this.renderResidualLand(); this.renderProperty(); }); if (sections.cap) into(sections.cap, () => { this.renderBudget(); this.renderSourcesUses(); this.renderSpecialty(); }); const uwSec = sections.uw; if (uwSec) into(uwSec, () => { @@ -882,6 +883,26 @@ export class ProformaUI { renderTestFitTab(this.root, { api: this.api, setStatus: this.setStatus }); } + /** Residual land value — the inverse of this whole tab: the most you can pay for the site and + * still clear a target return. `ApiClient.residualLand()` and the engine behind it shipped with + * FIN-CALC and had no caller until this card. */ + private renderResidualLand() { + renderResidualLandCard(this.root, { + api: this.api, + assumptions: () => this.a, + // Both halves go through the land-basis helpers, which address the basis the way + // `proforma/residual.py` defines it rather than the way the driver form's "Land $" field is + // bound — see their docstrings in residualLandCard.ts. + currentLand: () => landBasis((this.a as { cost_lines?: LandLine[] }).cost_lines ?? []), + applyLandValue: (v) => { + if (!applyLandBasis((this.a as { cost_lines: LandLine[] }).cost_lines ?? [], v)) return; + this.render(); + void this.solve(); + }, + setStatus: this.setStatus, + }); + } + /** Property & tax assumptions: parcel/areas/purchase/taxes; taxes → OPEX, price → acquisition. */ private renderProperty() { const host = document.createElement("div"); host.id = "pf-property"; diff --git a/apps/web/src/proforma/residualLandCard.test.ts b/apps/web/src/proforma/residualLandCard.test.ts new file mode 100644 index 00000000..5600ab25 --- /dev/null +++ b/apps/web/src/proforma/residualLandCard.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, it, vi } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { applyLandBasis, landBasis, renderResidualLandCard } from "./residualLandCard"; + +/** + * FIN-CALC's residual-land solve was built, routed, tested and **unreachable** — `ApiClient.residualLand()` + * sat in `api/clientCallers.test.ts`'s `UNCALLED` list. This pins the card that reaches it, and what it + * pins is not "the number appears": it is the **three caveats the engine returns and a panel can throw + * away**, which is the whole of this repo's SCREEN-VS-REPORT axis. + * + * 1. `land_value: null` — unreachable even at $0 land. Printing a figure here would be inventing one. + * The engine's own note calls it *"the deal, not the dirt"*, and `at_zero_land` is the evidence. + * 2. `converged: false` with a figure — the bisection hit its cap, so that figure is a **bracket + * endpoint, not a price**. A panel that prints the money and drops the flag asserts a precision it + * does not have. + * 3. `bounds` — returned by the route, named in `proforma/residual.py`'s docstring, and **declared + * nowhere in this client** until this change. Every unread-field audit here starts from the + * declared interfaces, so nothing could have seen it. It is the honest answer in case 2. + * + * Each is asserted by its own `it`, because one test over all three passes when two of them work. + */ + +type Res = Awaited>; + +const OK: Res = { + land_value: 5_250_000, achieved: 0.1501, target: "equity_irr", target_value: 0.15, + iterations: 34, converged: true, at_zero_land: 0.31, bounds: [5_249_000, 5_251_000], +}; + +function mount(res: Res, currentLand: number | null = 4_000_000) { + const root = document.createElement("div"); + document.body.replaceChildren(root); + const residualLand = vi.fn().mockResolvedValue(res); + const applyLandValue = vi.fn(); + const setStatus = vi.fn(); + renderResidualLandCard(root, { + api: { residualLand } as unknown as ApiClient, + assumptions: () => ({ cost_lines: [{ category: "land", amount: currentLand }] }), + currentLand: () => currentLand, + applyLandValue, + setStatus, + }); + const host = root.querySelector("#pf-residual-land")!; + const buttons = [...host.querySelectorAll("button")]; + const go = buttons.find((b) => b.textContent === "Solve residual land")!; + const apply = buttons.find((b) => b.textContent === "Apply to the deal")!; + const out = () => host.lastElementChild!.textContent ?? ""; + return { host, go, apply, out, residualLand, applyLandValue, setStatus }; +} + +/** Click Solve and let the mocked promise settle. */ +async function solve(go: HTMLButtonElement) { + go.click(); + await vi.waitFor(() => expect(go.disabled).toBe(false)); +} + +describe("the land basis the residual is solved for", () => { + // `proforma/residual.py::_with_land` scales the FIRST `category: "land"` line and zeroes the rest, + // so "the land basis" is that line and not `cost_lines[0]` — which is merely what the driver form's + // "Land $" field is bound to, true of the default assumption set and not of an adopted one. + it("reads the first land line, wherever it sits", () => { + expect(landBasis([{ category: "hard", amount: 20_000_000 }, + { category: "land", amount: 4_000_000 }])).toBe(4_000_000); + }); + + it("is null when nothing is categorised as land, rather than falling back to line 0", () => { + expect(landBasis([{ category: "hard", amount: 20_000_000 }])).toBeNull(); + }); + + it("writes the residual as the TOTAL basis, zeroing any second land line", () => { + // Left standing, a second land line makes the forward re-solve carry more land than the answer + // allowed for — so the deal would quietly disagree with the number that produced it. + const lines = [{ category: "land", amount: 4_000_000 }, { category: "hard", amount: 20_000_000 }, + { category: "land", amount: 750_000 }]; + expect(applyLandBasis(lines, 5_250_000)).toBe(true); + expect(lines.map((l) => l.amount)).toEqual([5_250_000, 20_000_000, 0]); + }); + + it("refuses when there is no land line to write to", () => { + const lines = [{ category: "hard", amount: 20_000_000 }]; + expect(applyLandBasis(lines, 5_250_000)).toBe(false); + expect(lines[0]!.amount).toBe(20_000_000); + }); +}); + +describe("residual land card", () => { + it("renders the land price, the achieved metric and the delta against the typed land line", async () => { + const m = mount(OK); + await solve(m.go); + const text = m.out(); + expect(text).toContain("$5,250,000"); + expect(text).toContain("15.0%"); // the achieved metric, in the target's own unit + expect(text).toContain("$1,250,000"); // headroom over the $4.0m land line + expect(text).toContain("headroom"); + }); + + it("sends the target in the engine's units — a percent box becomes a fraction", async () => { + const m = mount(OK); + await solve(m.go); + expect(m.residualLand).toHaveBeenCalledWith(expect.anything(), "equity_irr", 0.15, undefined); + }); + + it("sends a MULTIPLE target unscaled, because 1.8x is not 180%", async () => { + const m = mount(OK); + const sel = m.host.querySelector("select")!; + sel.value = "equity_multiple"; sel.dispatchEvent(new Event("change")); + const box = m.host.querySelectorAll("input")[0]!; + box.value = "1.8"; + await solve(m.go); + expect(m.residualLand).toHaveBeenCalledWith(expect.anything(), "equity_multiple", 1.8, undefined); + }); + + // CAVEAT 1 — infeasible. The one case where printing a number would be a false statement. + it("refuses to print a land value when the target is unreachable at $0 land", async () => { + const m = mount({ ...OK, land_value: null, achieved: 0.11, at_zero_land: 0.11, + converged: false, iterations: 0, + note: "target is not achievable even at $0 land — the deal, not the dirt" }); + await solve(m.go); + const text = m.out(); + expect(text).toContain("Not achievable at any land price"); + expect(text).toContain("11.0%"); // at_zero_land, so the shortfall is quantified + expect(text).toContain("the deal, not the dirt"); + expect(text).not.toMatch(/\$[1-9]/); // no dollar figure but the literal "$0" + expect(m.apply.style.display).toBe("none"); // and nothing to apply + }); + + // CAVEAT 2 + 3 — a figure that did not converge is a range, and `bounds` is the range. + it("says a non-converged solve is a range, and reads `bounds` for it", async () => { + const m = mount({ ...OK, converged: false, iterations: 80, bounds: [5_100_000, 5_400_000] }); + await solve(m.go); + const text = m.out(); + expect(text).toContain("Did not converge"); + expect(text).toContain("$5,100,000"); + expect(text).toContain("$5,400,000"); + expect(text).not.toContain("Converged in"); + }); + + it("degrades to 'read this as a range' when the server omits `bounds` entirely", async () => { + const m = mount({ ...OK, converged: false, iterations: 80, bounds: undefined }); + await solve(m.go); + expect(m.out()).toContain("Did not converge"); + expect(m.out()).toContain("range, not a price"); + }); + + it("applies the solved value back into the deal", async () => { + const m = mount(OK); + await solve(m.go); + expect(m.apply.style.display).not.toBe("none"); + m.apply.click(); + expect(m.applyLandValue).toHaveBeenCalledWith(5_250_000); + }); + + it("shows the route's own message when the assumption set has no land line", async () => { + const root = document.createElement("div"); + document.body.replaceChildren(root); + const residualLand = vi.fn().mockRejectedValue( + new Error("assumptions carry no cost line with category 'land'")); + renderResidualLandCard(root, { + api: { residualLand } as unknown as ApiClient, + assumptions: () => ({ cost_lines: [] }), currentLand: () => null, + applyLandValue: vi.fn(), setStatus: vi.fn(), + }); + const go = [...root.querySelectorAll("button")].find((b) => b.textContent === "Solve residual land")!; + await solve(go); + expect(root.textContent).toContain("no cost line with category 'land'"); + }); + + it("marks a residual BELOW the typed land line as over what the target supports", async () => { + const m = mount({ ...OK, land_value: 3_400_000 }); + await solve(m.go); + expect(m.out()).toContain("over what the target supports"); + expect(m.out()).toContain("$600,000"); + }); +}); diff --git a/apps/web/src/proforma/residualLandCard.ts b/apps/web/src/proforma/residualLandCard.ts new file mode 100644 index 00000000..1b1dc2be --- /dev/null +++ b/apps/web/src/proforma/residualLandCard.ts @@ -0,0 +1,205 @@ +/** Residual land value — *what can you pay for the site?* — the inverse of every other number on + * this tab. + * + * FIN-CALC shipped `services/api/src/aec_api/proforma/residual.py` and + * `POST /proforma/residual-land`, and `ApiClient.residualLand()` was written for it. **Nothing ever + * called it.** The engine bisects the land line over the same forward `solve()` the rest of the + * panel reports from, so the answer is consistent with the deal on screen rather than a second + * model — and it was reachable only from a script. Frozen in + * `apps/web/src/api/clientCallers.test.ts`'s `UNCALLED` list until this card. + * + * WHY THIS IS THE CANONICAL DEVELOPER QUESTION. Every other figure here runs forward from a land + * price somebody typed. Site acquisition is the one number a developer actually negotiates, and the + * question at the table is not "what is this deal's IRR" but "what is the most I can pay and still + * clear my hurdle". That is a different solve, not a re-reading of the same one. + * + * THREE CAVEATS THE ENGINE RETURNS, AND WHY ALL THREE ARE RENDERED + * `residual_land_value` is careful in a way a panel can easily throw away, and this repo's + * SCREEN-VS-REPORT axis is precisely the class of defect where it does: + * + * * `land_value: null` — the target is unreachable **even at $0 land**. The engine's own note + * calls this *"the deal, not the dirt"*. Printing a land value here would be inventing one; + * printing nothing would read as a failure. It renders as the finding it is, with + * `at_zero_land` as the evidence. + * * `converged: false` with a number — the bisection hit its cap, so the figure is a **bracket + * endpoint, not a solution**. A panel that prints the money and drops the flag states a + * precision it does not have. + * * `bounds` — **was declared nowhere in this client.** The route returns it, the engine's + * docstring lists it, and `ApiClient.residualLand()`'s return type omitted it, so no + * unread-field audit in this tree could see it: every one of them starts from the declared + * interfaces. It is the honest answer in the un-converged case — the range the land value is + * known to lie in — which is exactly when it matters. Declared now, and read here. + * + * It is deliberately NOT auto-solved on assumption edits. One residual solve is up to ~90 forward + * solves; firing that on every keystroke in the driver form would make the whole tab feel broken. + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; +import { money, pct } from "./format"; + +/** The five targets `proforma/residual.py::_TARGETS` supports, with how each one reads to a human. */ +const TARGETS: { key: string; label: string; unit: "pct" | "x"; placeholder: number }[] = [ + { key: "equity_irr", label: "Equity IRR", unit: "pct", placeholder: 15 }, + { key: "project_irr", label: "Project IRR", unit: "pct", placeholder: 12 }, + { key: "equity_multiple", label: "Equity multiple", unit: "x", placeholder: 1.8 }, + { key: "yield_on_cost", label: "Yield on cost", unit: "pct", placeholder: 6.5 }, + { key: "profit_margin", label: "Profit margin", unit: "pct", placeholder: 15 }, +]; + +export interface ResidualLandCtx { + api: ApiClient; + /** The live assumption set the rest of the tab is solving — read at click time, not captured. */ + assumptions: () => unknown; + /** The land line's current amount, so the answer can be shown as a delta against what is typed. */ + currentLand: () => number | null; + /** Write the solved value back into the land line and re-solve the forward deal. */ + applyLandValue: (v: number) => void; + setStatus: (m: string) => void; +} + +/** One cost line, as far as the land basis is concerned. */ +export interface LandLine { category?: string; amount: number } + +/** The land basis `proforma/residual.py` actually solves for: the FIRST `category: "land"` line. + * + * Not `cost_lines[0]`, which is what the driver form's "Land $" field happens to be bound to — true + * of the default assumption set and not guaranteed of an adopted one. Reading the basis under a + * looser definition than the one it was SOLVED under is how an inverse answer stops reconciling with + * the deal it came from. */ +export function landBasis(lines: readonly LandLine[]): number | null { + const land = lines.find((l) => l.category === "land"); + return typeof land?.amount === "number" ? land.amount : null; +} + +/** Write a solved residual back as the TOTAL land basis, mirroring `_with_land`: the first land line + * takes the value and any others are zeroed. A second land line left standing would make the forward + * re-solve carry more land than the answer allowed for, so the deal would quietly disagree with the + * number that produced it. Returns false when there is no land line to write to. */ +export function applyLandBasis(lines: LandLine[], value: number): boolean { + const land = lines.filter((l) => l.category === "land"); + if (!land.length) return false; + land[0]!.amount = value; + for (const extra of land.slice(1)) extra.amount = 0; + return true; +} + +/** Render one metric value in the unit its target uses — a multiple is not a percentage. */ +function metric(v: number | null, unit: "pct" | "x"): string { + if (v == null) return "n/a"; + return unit === "pct" ? pct(v) : `${v.toFixed(2)}x`; +} + +export function renderResidualLandCard(root: HTMLElement, ctx: ResidualLandCtx): void { + const host = document.createElement("div"); + host.id = "pf-residual-land"; + host.style.cssText = "margin:8px 0;padding:8px 10px;border:1px dashed var(--line);border-radius:8px"; + host.innerHTML = `
🪙 Residual land value — what can you pay for the site?
` + + `
The inverse solve: bisects the land line over the same forward model ` + + `every other number here comes from, until the chosen return hits your target.
`; + + const grid = document.createElement("div"); grid.className = "pf-form"; + const field = (label: string) => { + const w = document.createElement("label"); w.className = "pf-field"; + w.innerHTML = `${esc(label)}`; grid.appendChild(w); return w; + }; + + const tWrap = field("Target"); + const tSel = document.createElement("select"); tSel.className = "portal-filter"; + for (const t of TARGETS) { + const o = document.createElement("option"); o.value = t.key; o.textContent = t.label; tSel.appendChild(o); + } + tWrap.appendChild(tSel); + + const vWrap = field("Target value"); + const vInp = document.createElement("input"); vInp.type = "number"; vInp.step = "any"; + vWrap.appendChild(vInp); + + const mWrap = field("Max land $ (optional)"); + const mInp = document.createElement("input"); mInp.type = "number"; mInp.step = "any"; + mInp.placeholder = "auto"; + mInp.title = "Upper bracket for the search. Left empty the engine widens from 2x non-land cost."; + mWrap.appendChild(mInp); + + /** Keep the value box in the unit the selected target is actually measured in. */ + const syncUnit = () => { + const t = TARGETS.find((x) => x.key === tSel.value) ?? TARGETS[0]!; + vWrap.querySelector("span")!.textContent = t.unit === "pct" ? "Target value (%)" : "Target multiple (x)"; + vInp.placeholder = String(t.placeholder); + if (vInp.value.trim() === "") vInp.value = String(t.placeholder); + }; + tSel.onchange = syncUnit; + syncUnit(); + host.appendChild(grid); + + const out = document.createElement("div"); out.style.cssText = "margin-top:6px"; + const go = document.createElement("button"); go.className = "btn"; go.textContent = "Solve residual land"; + const applyBtn = document.createElement("button"); applyBtn.className = "btn"; + applyBtn.textContent = "Apply to the deal"; applyBtn.style.display = "none"; applyBtn.style.marginLeft = "6px"; + const actions = document.createElement("div"); actions.style.cssText = "margin-top:6px"; + actions.appendChild(go); actions.appendChild(applyBtn); + host.appendChild(actions); host.appendChild(out); + root.appendChild(host); + + go.onclick = async () => { + const t = TARGETS.find((x) => x.key === tSel.value) ?? TARGETS[0]!; + const raw = parseFloat(vInp.value); + if (!isFinite(raw)) { out.innerHTML = `
Enter a target value.
`; return; } + const targetValue = t.unit === "pct" ? raw / 100 : raw; + const maxLand = mInp.value.trim() === "" ? undefined : parseFloat(mInp.value); + go.disabled = true; applyBtn.style.display = "none"; + out.innerHTML = `
bisecting the land line…
`; + ctx.setStatus("solving residual land value…"); + try { + const r = await ctx.api.residualLand(ctx.assumptions(), t.key, targetValue, + isFinite(maxLand as number) ? maxLand : undefined); + const target = `${t.label} ${metric(targetValue, t.unit)}`; + + // INFEASIBLE — the target cannot be met even with free land. The engine says so and the panel + // must not invent a number: what the user needs to know is that the shortfall is in the deal. + if (r.land_value == null) { + out.innerHTML = `
Not achievable at any land price
` + + `
Even at $0 for the land this deal reaches ` + + `${esc(metric(r.at_zero_land, t.unit))} against your ${esc(target)} target — ` + + `so the gap is in the deal, not the dirt. Move rents, cost, exit cap or leverage, then re-solve.
` + + (r.note ? `
${esc(r.note)}
` : ""); + ctx.setStatus(`residual land: ${t.label} unreachable at $0 land`); + return; + } + + const cur = ctx.currentLand(); + const delta = cur == null ? null : r.land_value - cur; + const bounds = r.bounds; + out.innerHTML = + `
${esc(money(r.land_value))}
` + + `
is the most you can pay and still make ${esc(target)}` + + (r.achieved != null ? ` (solve lands on ${esc(metric(r.achieved, t.unit))})` : "") + `.
` + + (delta == null ? "" + : `
Your land line is ${esc(money(cur!))} — ` + + `${delta >= 0 ? "+" : "−"}${esc(money(Math.abs(delta)))} ` + + `${delta >= 0 ? "of headroom" : "over what the target supports"}.
`) + // `converged: false` means the bisection hit its cap, so the figure above is a BRACKET + // endpoint. `bounds` is the honest answer in that case and is the whole reason it is now + // declared on the client type — see this module's header. + + (r.converged ? `
Converged in ${r.iterations} solves.
` + : `
Did not converge in ${r.iterations} solves — ` + + `read this as a range, not a price` + + (bounds && bounds.length === 2 + ? `: between ${esc(money(bounds[0]!))} and ${esc(money(bounds[1]!))}` + : "") + + `. Narrow it with a Max land $ near the figure above.
`); + applyBtn.style.display = ""; + applyBtn.onclick = () => { + ctx.applyLandValue(r.land_value!); + ctx.setStatus(`land set to ${money(r.land_value!)} — re-solving`); + }; + ctx.setStatus(`residual land ${money(r.land_value)} at ${t.label} ${metric(targetValue, t.unit)}`); + } catch (e) { + // The route 400s with the engine's own message when the assumption set has no `land` cost + // line, which is reachable: a set adopted from elsewhere need not carry one. + out.innerHTML = `
${esc((e as Error).message)}
`; + ctx.setStatus("residual land failed"); + } finally { + go.disabled = false; + } + }; +} From 7e39f3439be35d3f3d77e61c742d0e30eecb2351 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 03:00:18 +0000 Subject: [PATCH 02/21] SCHEMA-UNGENERATED: the generated client types were never complete, and "regenerate" did not mean "read the server" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apps/web/src/api/schema.d.ts is generated from the FastAPI spec and COMMITTED, so it is a claim about the server checked in beside the client. Nothing was checking the claim. paths the app serves 947 (1,021 operations) paths schema.d.ts declared 500 (541 operations) operations served but undeclared 482, across 165 of 203 path groups operations declared but not served 2 IT WAS NOT STALENESS, and the name it was filed under ("SCHEMA-STALE") pointed at the one action that could not fix it. Stale implies the file was once right. schema.d.ts and the apps/web/.gitignore line hiding its input were last written in the SAME commit (8432a88, 2026-09-11), and the app has FEWER route decorators today — 1016 against 1022 at that commit — so drift cannot account for a gap running in the other direction. The types were already half the API when they were generated. (The name also collided with services/api/test_schema_stale.py, an unrelated gate about module-record schema versioning.) WHY RE-RUNNING THE GENERATOR REPRODUCED IT. package.json ran "openapi-typescript src/api/openapi.json -o src/api/schema.d.ts", and apps/web/.gitignore ignores src/api/openapi.json. On a fresh clone the command fails for want of an input; on a machine that has one it regenerates from whatever dump is sitting there, prints a green tick and writes the same file. FIXED IN THREE PARTS, because any one alone leaves the defect reachable: * apps/web/scripts/gen-api-types.mjs dumps the spec from aec_api.main:app into a temp file it deletes, so there is no persistent input left to be stale. It finds the venv interpreter on either OS rather than resolving a bare `python` off PATH, and refuses to overwrite a committed file from an implausible spec. It sorts `paths` first: FastAPI emits them in route-registration order, so including a router earlier shuffles thousands of lines — much of why "regenerating churned 38,231 lines" had become a reason not to. Two consecutive runs are byte-identical. * schema.d.ts regenerated by that script rather than by hand: 947 paths, 1,021 operations, all declared. * services/api/test_schema_types_agree.py asserts every live (path, method) is declared and every declared one still served. A generator can only be run; a gate fails. The gate is method-level and that is load-bearing: openapi-typescript emits all eight verbs per path and marks the unserved ones `?: never`, so a path-level check passes a file declaring all 947 URLs and none of their verbs — the shape a half-regenerated file actually has. It fails closed (an unclassifiable path group raises rather than being skipped) and runs five preconditions before any verdict, including that no route is registered under an `if`: the comparison only makes sense if the app serves the same routes here, in CI and on the machine that last ran the generator. Verified against the file as shipped: 482 served-but-undeclared and 2 declared-but-gone. That last precondition's first draft globbed a RELATIVE "src" path. run_tests.py sets cwd=services/api so it would have worked under the runner, and ci.yml invokes `python services/api/run_tests.py` from the repo ROOT, where the same literal names nothing — rglob yields nothing, raises nothing, and the precondition reports a clean tree. A wrong question returns a confident number. Anchored on __file__ now, with the population count floored beside the verdict and the gate run from both working directories. WHY IT WENT UNSEEN, which is worth more than the fix. Seven audits here exempt this file by name — deadFieldScope, docComments, unfiledMap, deadFieldTyped, noRespelledShapes, test_route_reachability, test_file_sizes — each for a good reason of its own. Seven exemptions and no owner is how an artifact stops being checked by anybody. test_route_reachability's is the sharpest: it once counted schema.d.ts as CLIENT code, so 29 routes were "called" by a generated file restating the server's own route table. That is the opposite error; it is only ever a claim about the SERVER. And the item had been parked on a decision that was not load-bearing — commit openapi.json, or produce it in CI? — and needed neither answer, because an input that does not persist needs no home, and the agreement check needs no node and no artifact crossing jobs. An item parked on a decision stays parked until somebody re-derives whether the decision was load-bearing. Registered in test_roadmap_status.py's DONE_WHEN by the change that closed it, which is the thing test_gap_records exists because nothing forces. That file's own verdict line said "the two that HAVE a gate" and was wrong the moment a third registered itself; the counts are derived now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 183 + CLAUDE.md | 45 + apps/web/.gitignore | 7 + apps/web/package.json | 2 +- apps/web/scripts/gen-api-types.mjs | 130 + apps/web/src/api/openapiTypes.ts | 26 +- apps/web/src/api/schema.d.ts | 45698 ++++++++++++++++++---- docs/roadmap.md | 111 +- services/api/run_tests.py | 2 +- services/api/test_roadmap_status.py | 18 +- services/api/test_schema_types_agree.py | 420 + 11 files changed, 39057 insertions(+), 7585 deletions(-) create mode 100644 apps/web/scripts/gen-api-types.mjs create mode 100644 services/api/test_schema_types_agree.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c4f181..5de4cf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,189 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### RESIDUAL-DARK — the developer's actual question was built, routed, tested and unreachable + +Every other figure on the pro forma runs **forward** from a land price somebody typed. Site +acquisition is the one number a developer negotiates, and the question at the table is not "what is +this deal's IRR" but *"what is the most I can pay for the dirt and still clear my hurdle"*. That is a +different solve. + +FIN-CALC shipped it: `services/api/src/aec_api/proforma/residual.py` bisects the land line over the +same forward `solve()` every other number here comes from, `POST /proforma/residual-land` serves it, +`services/api/test_fin_calc.py` covers it, and `ApiClient.residualLand()` was written for it. **No +screen ever called it** — the method sat in `apps/web/src/api/clientCallers.test.ts`'s `UNCALLED` +list, reachable only from a script. Same shape as DISC-poché and the MEP systems browser: complete, +correct, and behind no door. + +### Added + +- **Residual land value on the Feasibility tab** (`apps/web/src/proforma/residualLandCard.ts`). Pick a + target — equity IRR · project IRR · equity multiple · yield on cost · profit margin — give it a + number, and get the land price that hits it, shown against the land line you already typed as + headroom or overpayment. **Apply to the deal** writes it back into the land cost line and re-solves + the forward model, so the inverse answer becomes the deal. +- **The target box changes units with the target**, because 1.8x is not 180%. A multiple is sent + unscaled and a percentage is divided by 100; both directions are asserted, and sending the percent + unscaled reds the test. +- **All three of the engine's caveats are rendered, and that is the substance of the change rather + than the number.** `residual_land_value` is careful in exactly the way a panel throws away — this + repo's SCREEN-VS-REPORT axis: + - `land_value: null` — the target is unreachable **even at $0 land**. Printing a figure here would + be inventing one; printing nothing would read as a failure. It renders as the finding, quantified + with `at_zero_land`, under the engine's own words: *"the deal, not the dirt"*. + - `converged: false` **with** a figure — the bisection hit its cap, so that figure is a bracket + endpoint, not a price. A panel that prints the money and drops the flag asserts a precision it + does not have. + - `bounds` — **declared nowhere in this client.** The route returns it and `residual.py`'s own + docstring lists it, and `ApiClient.residualLand()`'s return type omitted it, so no unread-field + audit in this tree could see it: every one of them starts from the declared interfaces. It is the + honest answer in the un-converged case, which is precisely when it matters. Declared and read now. +- **The applied value goes back as the basis the engine SOLVED for, not the one the form is bound to.** + `proforma/residual.py::_with_land` scales the **first** `category: "land"` line and zeroes any + others, so "the land basis" is that line and the residual is the *total*. The driver form's "Land $" + field is bound to `cost_lines.0.amount`, which is true of the default assumption set and not + guaranteed of one adopted from the massing tab — and a second land line left standing would make the + forward re-solve carry more land than the answer allowed for, so the deal would quietly disagree with + the number that produced it. `landBasis()` / `applyLandBasis()` address it the engine's way, and both + are mutation-checked: reading line 0 instead of the first land line, and dropping the zeroing, each + red their own test. *Applying a number under a looser definition than the one it was solved under is + how an inverse answer stops reconciling.* +- `apps/web/src/proforma/residualLandCard.test.ts` — thirteen tests, one caveat per `it`, because one + test over all three passes when two of them work. **Mutation-checked**: printing a figure when + `land_value` is null, always claiming convergence, ignoring `bounds`, and sending the percentage + unscaled each red exactly the test written for them. + +### Fixed + +- **The XSS source pin in `proforma.render.test.ts` accused a safe line.** It required the literal + spelling `escapeHtml((e as Error).message)`, and this directory imports the escaper under two names + — `escapeHtml` in `massingTab.ts`/`proforma.ts`, `escapeHtml as esc` in `testfitTab.ts` and now + `residualLandCard.ts`. So a line escaping **correctly** under the conventional alias was reported as + an unescaped XSS sink. *A check keyed on the local NAME cannot see a call that is about the + BINDING* — and it failed in the direction that costs the most trust: not a miss, a confident + accusation about a safe line, with a message telling the author they had an XSS. The escaper is now + resolved from the file's own `ui/feedback` import, so either spelling counts and a name bound to + nothing does not. Renaming the import to suit the regex was the tempting repair and the wrong one: + the convention it would bend to is the regex's, not the directory's. +- **And the pin could not tell "no offenders" from "nothing to offend".** It asserted an empty list + with no floor on either side, so a refactor that moved every sink out of `proforma/` would have left + it green and meaningless. It now asserts the directory scan found files and that it is still looking + at sinks, before it may report none unescaped. Mutation-verified both ways: a real unescaped sink in + the new card reds it, and reverting the resolver to name-matching reds it and its self-test. + +### SCHEMA-UNGENERATED — the generated client types were never complete, and "regenerate" did not mean "read the server" + +`apps/web/src/api/schema.d.ts` is generated from the FastAPI spec and **committed**, so it is a claim +about the server checked in beside the client. Nothing was checking the claim. + +| | | +|---|---| +| paths the app serves | **947** (1,021 operations) | +| paths `schema.d.ts` declared | **500** (541 operations) | +| operations served but undeclared | **482**, across 165 of 203 path groups | +| operations declared but no longer served | **2** | + +**It was not staleness, and the name it was filed under pointed at the one action that could not fix +it.** Stale implies the file was once right. `schema.d.ts` and the `apps/web/.gitignore` line hiding +its input were last written in the **same commit** (`8432a88`, 2026-09-11), and the app has *fewer* +route decorators today — 1016 against 1022 at that commit — so drift cannot account for a gap running +in the other direction. The types were already half the API when they were generated. + +**Why re-running the generator reproduced it.** `package.json` ran +`openapi-typescript src/api/openapi.json -o src/api/schema.d.ts`, and `apps/web/.gitignore` ignores +`src/api/openapi.json`. On a fresh clone the command fails for want of an input; on a machine that has +one it regenerates from whatever dump is sitting there, prints a green tick and writes the same file. + +### Fixed + +- **`apps/web/scripts/gen-api-types.mjs` — the generator reads the app, not a file.** It dumps the + spec from `aec_api.main:app` into a temp file under the OS temp directory, generates, and deletes + it, so **there is no persistent input left to be stale**: if the app cannot be imported it exits + non-zero with Python's own traceback instead of falling back to a dump on disk. It finds the venv + interpreter on either OS rather than resolving a bare `python` off PATH — the mistake + `check-vite-version.mjs` documents for vite — and refuses to overwrite a committed file from a spec + reporting an implausible number of paths. +- **`paths` is sorted before generating.** FastAPI emits them in route-registration order, so + including a router earlier shuffles thousands of lines and the diff of a one-route change is + unreviewable. Key order carries no meaning in an OpenAPI document, and this is much of why + *"regenerating churned 38,231 lines"* had become a recorded reason not to regenerate. Two + consecutive runs are now byte-identical. +- **`schema.d.ts` regenerated** — by that script, not by hand: 947 paths, 1,021 operations, all + declared. +- **`apps/web/src/api/openapiTypes.ts`'s header** described the two-step recipe whose second step was + the defect. Corrected to the one command, with the measurement and the gate named. + +### Added + +- **`services/api/test_schema_types_agree.py`** — the seventeenth gate. Every live `(path, method)` + must be declared in the committed `schema.d.ts`, and every declared one must still be served. **A + generator can only be run; a gate fails.** It lives in Python, in `services/api`, because it needs + the live app and does *not* need node — the declared set is parsed out of the committed `schema.d.ts`, so + nothing is generated, no artifact passes between CI jobs, and `api-tests` (which already imports the + app) is the only job involved. +- **Method-level, not path-level, and that is load-bearing.** `openapi-typescript` emits all eight + verbs for every path and marks the unserved ones `?: never`, so "the URL is in the file" and "the + file says this URL answers POST" are different questions and only one is the right one. A + path-level check passes a file that declares all 947 URLs and not one of their verbs — which is the + shape a partially-regenerated file actually has. Mutation-proved in both arms: a deleted path group + must be found, and one verb flipped to `never` must be found. +- **It fails closed.** A path group whose body the parser cannot classify *raises* rather than being + skipped — proved by deleting a verb line and requiring the refusal, because a parser that silently + narrowed would report that path as missing everything, which reads like "regeneration due" rather + than "the parser no longer understands this file". Floors on both sides (≥900 paths, ≥950 + operations) so a parser that matched nothing cannot report a clean tree. +- **Verified against the file as shipped**: 482 served-but-undeclared and 2 declared-but-gone, with + the floor check red as well — detection in both directions, on the real defect, before the fix. +- **Its own parity check was not a check, and mutation is what said so.** `finditer` yields nothing for + a path group whose shape it cannot match — silently — and a path absent from the parse is reported + as *served but undeclared*, which reads as **regenerate** rather than **the parser broke**. So the + path keys are counted independently of the group bodies. The first draft counted them with + `^ "…": \{$`, which **fails in exactly the same way the group regex does** (both anchor the brace + at end of line), so a trailing-whitespace mutation broke both derivations and the counts stayed + equal. *A parity check between two derivations that share a failure mode is not a check.* The key + count uses a deliberately cruder shape now, and both mutations — whitespace after an opening brace, + a closing brace indented one space too far — are folded into the gate and must be refused before it + may report anything. +- **No carve-out for `head`/`options`/`trace`.** An earlier draft excluded them from both sides as + "verbs nobody writes". Measured: the live spec emits **0 of all three**, so the exclusion bought + nothing and silently shrank the population — *a list of known cases is a list somebody stopped + widening*, and an explicit `@router.head` would have been skipped on both sides with a missing + declaration reading as a clean tree. All eight are compared. +- **The comparison's own assumption is asserted, not believed.** It only makes sense if the app serves + the same routes here, in CI and on the machine that last ran the generator — so the gate scans + `src/aec_api` for any route registered under an `if` (measured: 0 sites across 466 modules) and reds + if one appears. Without it, one `if settings.FEATURE:` would make this gate red with *"N served but + undeclared — regenerate"*, a reader would regenerate, and the file would disagree in the other + direction on the next machine. *A check whose failure message can misdiagnose is worse than one that + stays silent, because somebody acts on it.* The scanner is shown finding a synthetic gated + registration before it may report zero. +- **And that scan's first draft asked the question from the wrong place.** It globbed `Path("src")`, + relative to the working directory. `run_tests.py` sets `cwd=services/api`, so it would have worked + under the runner — and `ci.yml` invokes `python services/api/run_tests.py` from the repo **root**, + where the same literal names nothing, `rglob` yields nothing, raises nothing, and the precondition + reports a clean tree. *A wrong question returns a confident number* — the failure + `test_scratch_ignored` paid for twice. The path is anchored on `__file__` now, the module count is + returned rather than discarded and floored beside the verdict, and the gate is verified from both + working directories. + +### Why it went unseen for a fortnight — the part worth more than the fix + +**Seven audits in this tree exempt `schema.d.ts` by name** — `deadFieldScope`, `docComments`, +`unfiledMap`, `deadFieldTyped`, `noRespelledShapes`, `test_route_reachability`, `test_file_sizes` — +each for a good reason of its own. *Seven exemptions and no owner is how an artifact stops being +checked by anybody.* + +`test_route_reachability`'s exemption is the sharpest and belongs beside this one. It used to count +`schema.d.ts` as client code, so **29 routes were "called" by a generated file restating the server's +own route table** (v0.3.1049). That is the opposite error — treating the artifact as evidence about +the *client*. It is only ever a claim about the *server*, which is the one thing this gate reads it as. + +**And the item was parked on a decision that turned out not to be load-bearing.** The entry said the +repair was a build decision — whether `openapi.json` is committed (a large artifact in every diff) or +dumped in CI (a step needing the API importable in the web job) — and it needed neither answer. The +input does not have to be committed *or* produced in CI if it does not persist. *An item parked on a +decision stays parked until somebody re-derives whether the decision was load-bearing.* + ### ROUTE-SHADOW — four routes were registered on a URL another route already owned Starlette matches the **first** route whose path and method fit, so a second registration on the same diff --git a/CLAUDE.md b/CLAUDE.md index baf7e4a3..9f2c4314 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -410,6 +410,51 @@ whose regex carries the prefix by construction, and probe validity is **self-che must match its own route), so an unhandled converter reds the build. *A list of known cases is a list somebody stopped widening; a self-check is not.* +**A seventeenth joined them on 2026-09-25: `services/api/test_schema_types_agree.py`** — does the +committed `apps/web/src/api/schema.d.ts` describe the API this server serves? It is generated from the +FastAPI spec and checked in, so it is a claim about the server sitting beside the client, and nothing +was checking the claim: **500 of 947 paths, 541 of 1,021 operations**, 482 undeclared across 165 of 203 +path groups. **Filed as "SCHEMA-STALE", and the name was the first thing wrong with it** — stale +implies it was once right, and that file and the `.gitignore` line hiding its input were written in the +SAME commit, with the app carrying *fewer* route decorators today (1016 vs 1022) than when it was +generated. *A stale-sounding name sends the next reader at the one action that cannot fix it*, which +here was re-running the generator: `gen:api-types` read `src/api/openapi.json`, **a gitignored file**, +so it regenerated from whatever dump sat on that machine, printed a green tick and wrote the same file. +*"Regenerate the types" did not mean "read the server", and nothing said so* — the generator now dumps +from `aec_api.main:app` into a temp file it deletes, so there is no persistent input left to be stale. +**The rule is method-level and that is load-bearing:** `openapi-typescript` emits all eight verbs per +path and marks the unserved ones `?: never`, so a path-level check passes a file declaring all 947 URLs +and none of their verbs — which is the shape a half-regenerated file actually has. Both arms are +mutation-proved, and the parser REFUSES a group it cannot classify rather than narrowing, because a +silent narrowing reports that path as missing everything, reading as "regeneration due" instead of "the +parser no longer understands this file". +**The reason it went unseen is worth more than the fix: SEVEN audits here exempt this file by name** +(`deadFieldScope`, `docComments`, `unfiledMap`, `deadFieldTyped`, `noRespelledShapes`, +`test_route_reachability`, `test_file_sizes`), each for a good reason of its own. *Seven exemptions and +no owner is how an artifact stops being checked by anybody.* And `test_route_reachability`'s is the +opposite error, still instructive: it once counted this file as CLIENT code, so **29 routes were +"called" by a generated file restating the server's own route table.** It is only ever a claim about +the SERVER. **The item had also been parked on a decision that was not load-bearing** — commit +`openapi.json`, or produce it in CI? — and needed neither answer, because an input that does not +persist needs no home, and the agreement check needs no node and no artifact crossing jobs. *An item +parked on a decision stays parked until somebody re-derives whether the decision was load-bearing.* +**And the gate's own first draft asked its newest precondition from the wrong place.** The +env-independence scan globbed `Path("src")`, relative to the working directory — which `run_tests.py` +sets to `services/api`, while `ci.yml` invokes `python services/api/run_tests.py` from the repo ROOT. +`rglob` over a missing directory yields nothing and raises nothing, so it would have reported a clean +tree under CI and nowhere else. *A wrong question returns a confident number* — the same failure +`test_scratch_ignored` paid for twice, in a file written days after reading its lesson. **A relative +path is a question about where somebody stood**; it is anchored on `__file__` now, the population +count is returned rather than discarded and floored beside the verdict, and the gate is run from both +directories. +**And its parity check was not a check.** A path group whose shape the group regex cannot match is +absent from the parse *silently*, and is then reported as "served but undeclared" — a parser failure +wearing the costume of a stale file — so the path keys are counted independently. The first draft +counted them with a regex that **fails in exactly the same way the group regex does**, so a +trailing-whitespace mutation broke both derivations and the counts stayed equal. *A parity check +between two derivations that share a failure mode is not a check* — the crude count catches what the +strict one cannot, and both mutations are now folded in and must be refused before the gate reports. + "Cite a gate only after `git ls-files` confirms it" is itself a rule held as prose, so it is now `services/api/test_claude_md_gates.py`: every backticked code file named here, in `docs/roadmap-directions.md` **and in `docs/roadmap.md`** must resolve to a tracked path — including diff --git a/apps/web/.gitignore b/apps/web/.gitignore index ac808c27..7f657e7b 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -1,2 +1,9 @@ *.local +# Vestigial, deliberately kept. `gen:api-types` no longer writes here — it dumps the spec into the OS +# temp directory and deletes it, because an input that persists is an input that can be stale, and this +# very line is what hid that: the generator's one documented invocation read a file git was told to +# ignore, so "regenerate the types" silently meant "re-read whatever dump is on this machine". See +# apps/web/scripts/gen-api-types.mjs and services/api/test_schema_types_agree.py. The pattern stays so +# that a leftover dump from before 2026-09-25 does not surface as untracked work in every lane's +# `git status` — the residue problem services/api/test_scratch_ignored.py exists for. src/api/openapi.json diff --git a/apps/web/package.json b/apps/web/package.json index b4ae441b..55263962 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -31,7 +31,7 @@ "cap:sync": "cap sync", "mobile:android": "npm run build:mobile && cap sync android && cap open android", "mobile:ios": "npm run build:mobile && cap sync ios && cap open ios", - "gen:api-types": "openapi-typescript src/api/openapi.json -o src/api/schema.d.ts" + "gen:api-types": "node scripts/gen-api-types.mjs" }, "dependencies": { "@mkkellogg/gaussian-splats-3d": "^0.4.7", diff --git a/apps/web/scripts/gen-api-types.mjs b/apps/web/scripts/gen-api-types.mjs new file mode 100644 index 00000000..8cd42c36 --- /dev/null +++ b/apps/web/scripts/gen-api-types.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +/** + * Regenerate `src/api/schema.d.ts` from the FastAPI app — reading the SERVER, never a file. + * + * WHY THIS SCRIPT EXISTS — the generator's input was a file nobody tracked + * `package.json` used to run, literally: + * + * openapi-typescript src/api/openapi.json -o src/api/schema.d.ts + * + * and `apps/web/.gitignore` ignores `src/api/openapi.json`. So the one documented way to + * "regenerate the types" read an **untracked** intermediate: on a fresh clone the command fails + * outright (no input), and on a machine that has one it regenerates from whatever dump happens to + * be sitting there — which may predate the routes you just added. **It succeeds, prints a green + * tick, and produces the same stale file.** *"Regenerate the types" did not mean "read the + * server", and nothing said so.* + * + * Measured 2026-09-25: the committed `schema.d.ts` declared **500** of the **947** paths the app + * serves, missing 448 across 165 of 203 path groups. That file and the `.gitignore` line hiding + * its input were last written in the SAME commit (`8432a88`, 2026-09-11), and the API has *fewer* + * route decorators today (1016) than it had then (1022) — so drift cannot explain the gap. **The + * types were already half the API on the day they were generated**, which is why the roadmap + * entry naming this "SCHEMA-STALE" was itself wrong: stale implies it was once right, and a + * stale-sounding name sends the next reader to re-run the generator, the one action that does not + * fix it. + * + * WHAT CHANGED + * The spec is dumped from `aec_api.main:app` into a file under the OS temp directory, generated + * from, and deleted. There is **no persistent input to be stale**, so the failure mode above is + * not merely discouraged, it is unreachable: if the app cannot be imported this exits non-zero + * with Python's own traceback, rather than quietly falling back to a dump on disk. + * + * THE AUTHORITY IS A TEST, NOT THIS SCRIPT + * `services/api/test_schema_types_agree.py` asserts every live `(path, method)` is declared in + * the committed `schema.d.ts`. A generator can only be run; a gate fails. This script exists so + * that running it means something — the gate is what makes not running it visible. + */ +import { spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +const WEB = resolve(import.meta.dirname, ".."); +const REPO = resolve(WEB, "..", ".."); +const API = join(REPO, "services", "api"); + +/** The venv interpreter for `services/api`, on either OS — never a bare `python`. */ +function venvPython() { + const candidates = [ + join(API, ".venv", "bin", "python"), + join(API, ".venv", "Scripts", "python.exe"), + ]; + for (const p of candidates) if (existsSync(p)) return p; + console.error( + `gen:api-types — no interpreter at any of:\n ${candidates.join("\n ")}\n` + + `Create it first: the backend venv is what holds fastapi/ifcopenshell, and a bare \`python\`\n` + + `off PATH is the resolution mistake \`check-vite-version.mjs\` documents for vite.`, + ); + process.exit(1); +} + +const DUMP = [ + "import json,sys", + "from aec_api.main import app", + "sys.stdout.write(json.dumps(app.openapi()))", +].join("\n"); + +const py = venvPython(); +const spec = spawnSync(py, ["-c", DUMP], { + cwd: API, + encoding: "utf-8", + maxBuffer: 64 * 1024 * 1024, + env: { ...process.env, PYTHONPATH: ["src", join("..", "data", "src")].join(process.platform === "win32" ? ";" : ":") }, +}); +if (spec.status !== 0) { + console.error(spec.stderr || "gen:api-types — the app could not be imported"); + process.exit(spec.status ?? 1); +} + +// Sanity-check the dump BEFORE overwriting a committed file: a truncated or empty spec would +// otherwise generate a valid-looking `schema.d.ts` that declares nothing. +let parsed; +try { + parsed = JSON.parse(spec.stdout); +} catch (e) { + console.error(`gen:api-types — the spec dump is not JSON (${String(e)})`); + process.exit(1); +} +const paths = Object.keys(parsed?.paths ?? {}); +if (paths.length < 100) { + console.error( + `gen:api-types — the app reports only ${paths.length} paths, which is not a plausible spec for ` + + `this API. Refusing to overwrite src/api/schema.d.ts from it.`, + ); + process.exit(1); +} + +/** `openapi-typescript`'s CLI, wherever npm hoisted it — root or nested under apps/web. */ +function generatorCli() { + const candidates = [ + join(REPO, "node_modules", "openapi-typescript", "bin", "cli.js"), + join(WEB, "node_modules", "openapi-typescript", "bin", "cli.js"), + ]; + for (const p of candidates) if (existsSync(p)) return p; + console.error( + `gen:api-types — openapi-typescript is not installed at either:\n ${candidates.join("\n ")}`, + ); + process.exit(1); +} + +// Sort `paths` before generating. FastAPI emits them in ROUTE REGISTRATION order, so including a +// router earlier shuffles thousands of lines and the diff of a one-route change is unreviewable — +// which is a large part of why "regenerating churned 38,231 lines" became a reason not to regenerate. +// Key order carries no meaning in an OpenAPI document, so sorting costs nothing and makes the next +// regeneration a diff somebody will actually read. +parsed.paths = Object.fromEntries([...Object.entries(parsed.paths)].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))); + +const tmp = mkdtempSync(join(tmpdir(), "massing-openapi-")); +const specFile = join(tmp, "openapi.json"); +const out = join(WEB, "src", "api", "schema.d.ts"); +try { + writeFileSync(specFile, JSON.stringify(parsed)); + const gen = spawnSync(process.execPath, [generatorCli(), specFile, "-o", out], { + cwd: WEB, + stdio: "inherit", + }); + if (gen.status !== 0) process.exit(gen.status ?? 1); +} finally { + rmSync(tmp, { recursive: true, force: true }); +} +console.log(`gen:api-types — ${paths.length} paths from the live app → src/api/schema.d.ts`); diff --git a/apps/web/src/api/openapiTypes.ts b/apps/web/src/api/openapiTypes.ts index 935c9101..7e887be3 100644 --- a/apps/web/src/api/openapiTypes.ts +++ b/apps/web/src/api/openapiTypes.ts @@ -1,18 +1,22 @@ // Typed seam over the OpenAPI-generated `schema.d.ts` (produced by `openapi-typescript`). // `import … from "./schema"` resolves to the generated `schema.d.ts` (there is no `schema.ts`). // -// Regenerate after backend API changes (two steps — dump the FastAPI spec, then generate the types): -// 1. From services/api: PYTHONPATH=src -c \ -// "import json,pathlib;from aec_api.main import app;\ -// pathlib.Path('../../apps/web/src/api/openapi.json').write_text(json.dumps(app.openapi()))" -// 2. From apps/web: npm run gen:api-types -// (schema.d.ts is committed; openapi.json is a gitignored intermediate.) +// Regenerate after backend API changes — ONE step, from apps/web: +// npm run gen:api-types +// It dumps the spec from `aec_api.main:app` into a temp file it then deletes, so the generator reads +// the SERVER and there is no persistent input left to be stale. The two-step recipe that used to live +// here ended in `openapi-typescript src/api/openapi.json`, and `apps/web/.gitignore` ignores +// `src/api/openapi.json` — so step 2 on its own regenerated from whatever untracked dump happened to +// be on the machine, printed a green tick, and wrote the same file. Measured 2026-09-25: the committed +// `schema.d.ts` declared 500 of the 947 paths the app serves, and had done since it was generated. +// `services/api/test_schema_types_agree.py` is the authority now — it asserts every live (path, method) +// is declared here, so forgetting to regenerate reds the build instead of doing nothing. // -// COVERAGE: the backend returns raw dicts on most endpoints (only ~11 of ~540 declare a response -// model), so generated *response* types are precise only where FastAPI has a schema — request bodies, -// path/query params, and those typed responses. As backend endpoints adopt `response_model=`, coverage -// grows automatically on the next regen. Hand-written DTOs in `types.ts` remain the source for untyped -// responses until then. +// COVERAGE: the backend returns raw dicts on most endpoints (only a handful of the ~1,021 operations +// declare a response model), so generated *response* types are precise only where FastAPI has a +// schema — request bodies, path/query params, and those typed responses. As backend endpoints adopt +// `response_model=`, coverage grows automatically on the next regen. Hand-written DTOs in `types.ts` +// remain the source for untyped responses until then. import type { components, operations, paths } from "./schema"; diff --git a/apps/web/src/api/schema.d.ts b/apps/web/src/api/schema.d.ts index 60a453e9..5cc9f09c 100644 --- a/apps/web/src/api/schema.d.ts +++ b/apps/web/src/api/schema.d.ts @@ -4,7 +4,7 @@ */ export interface paths { - "/projects": { + "/admin/baseline": { parameters: { query?: never; header?: never; @@ -12,22 +12,25 @@ export interface paths { cookie?: never; }; /** - * List Projects - * @description Projects the caller can see. Membership is filtered in SQL (one query, not one role lookup - * per project) and the result is paginated — the previous shape loaded every project then ran a - * per-project role query, an N+1 that also briefly materialized other tenants' names. + * Adoption Baseline + * @description R24-BASELINE — the adoption metrics the interface ring is scored against. Admin only. + * + * Cross-project and cross-user by nature, hence admin-gated. Three of the ring's six metrics are + * computed from `record_activity`; the other three return `available: false` **with the reason**. + * That asymmetry is the point — a client-side measure substituted from a server-side proxy reads + * like the target and answers a different question, and this ring exists because R26 shipped a + * shell nobody could score. */ - get: operations["list_projects_projects_get"]; + get: operations["adoption_baseline_admin_baseline_get"]; put?: never; - /** Create Project */ - post: operations["create_project_projects_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/me": { + "/admin/errors": { parameters: { query?: never; header?: never; @@ -35,21 +38,23 @@ export interface paths { cookie?: never; }; /** - * My Membership - * @description The caller's own effective role on this project — drives UI capability gating. No role - * required (a non-member gets role=null). `rbac` tells the client whether gating is enforced; - * when it's off the client should treat the user as fully capable (matching the open API). + * List Errors + * @description Newest-first error feed + a summary header. Admin only. */ - get: operations["my_membership_projects__pid__me_get"]; + get: operations["list_errors_admin_errors_get"]; put?: never; post?: never; - delete?: never; + /** + * Clear Errors + * @description Force-prune to the retention cap (housekeeping). Admin only. + */ + delete: operations["clear_errors_admin_errors_delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/presence": { + "/admin/licenses": { parameters: { query?: never; header?: never; @@ -57,84 +62,120 @@ export interface paths { cookie?: never; }; /** - * Presence Roster - * @description Other users currently viewing this project (heartbeat within the TTL). + * List Licenses + * @description Every licence on record, optionally filtered to one user. Platform-admin only. */ - get: operations["presence_roster_projects__pid__presence_get"]; + get: operations["list_licenses_admin_licenses_get"]; put?: never; /** - * Heartbeat - * @description Heartbeat presence (optionally sharing the current camera viewpoint) and get the live - * roster of other users viewing this project. + * Create License + * @description Record a verified licence for a user. **Platform-admin only, never self-served.** + * + * A licence number is an assertion about a credential a state board issued, so a user attesting to + * their own would carry exactly as much weight as the free-text field this replaces — none. The + * admin who writes the row is recorded in `verified_by`, which is what makes the row evidence. */ - post: operations["heartbeat_projects__pid__presence_post"]; + post: operations["create_license_admin_licenses_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/markup": { + "/admin/licenses/{lid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; + post?: never; /** - * List Markup - * @description Markup pins for a project, optionally filtered to one sheet. + * Delete License + * @description Revoke a licence record (a lapsed or corrected licence). Platform-admin only. */ - get: operations["list_markup_projects__pid__drawings_markup_get"]; - put?: never; - /** Add Markup */ - post: operations["add_markup_projects__pid__drawings_markup_post"]; - delete?: never; + delete: operations["delete_license_admin_licenses__lid__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/markup/bulk": { + "/agent-packs/runs": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Save Markup Bulk - * @description Save the 2D editor's whole markup scene for a sheet. With `replace`, the caller's own prior - * markups for that sheet are cleared first — but markups promoted to an RFI (topic_id set) are kept, - * so a located issue is never silently dropped. + * Agent Pack Runs + * @description R22-AGENT-PACKS — the governance console **across the estate**: every agent run, whoever ran + * it and whichever project it touched. + * + * The per-project route above answers "what ran here". That is the wrong altitude for the question + * an enterprise actually asks before granting an agent access, and after an incident: *whose agent + * ran what, anywhere.* A reviewer who has to open twenty project consoles and add them up is not + * being governed by a console; and a run against a project they forgot to check is invisible. + * + * **Admin-only, and that follows from an existing fact rather than from taste.** These rows come + * from `audit_log`, and `GET /audit` is already admin-only — so a non-admin org-wide view here + * would be a way to read audit rows without being an admin, which is a privilege-escalation + * surface wearing the name of a governance feature. (The per-project route stays at project + * `viewer`, because there the rows are scoped to a project the caller already has access to.) + * + * `by_actor` is the axis that only earns its place here: within one project the interesting + * tally is which tools ran, but across the estate the question is *who ran an agent*, and a tool + * tally cannot answer it. An unattributed run is counted, not dropped. */ - post: operations["save_markup_bulk_projects__pid__drawings_markup_bulk_post"]; + get: operations["agent_pack_runs_agent_packs_runs_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/markup/{mid}": { + "/asset-rights/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Asset Rights Status + * @description Whether sealing a `.mass` with a release manifest is available on this deployment. + * + * The client needs this to decide whether to *offer* the option at all — an inert checkbox that + * silently does nothing is worse than no checkbox. `signing` distinguishes the two honest states: + * with a key the release is signed and a third party can verify it; without one it is hashed and + * tamper-evident but carries no attribution, and the UI must not imply otherwise. + * + * Deliberately **not** `require_role`: that dependency resolves its `pid` from the path *or the + * query string*, so on a route with no `{pid}` a caller supplies their own — the hazard recorded + * on `authorize_pid`. Nothing here is project-scoped, so an authenticated user is the right gate. + * + * **Returns the PUBLIC key, and never the private one.** This docstring used to say "never the key + * itself", which read as a rule against publishing any key and left a signed release unverifiable: + * `public_key_b64`'s own docstring says the public half is *"safe to publish; this is what + * verifiers need"*. Withholding it did not protect anything — the same key is embedded in every + * signed manifest we emit — it just meant a third party had to trust the document's own copy of it, + * which is the one an attacker who rewrote the manifest would have replaced. Empty when signing is + * unavailable, so a caller can tell "no key here" from "key withheld". + */ + get: operations["asset_rights_status_asset_rights_status_get"]; put?: never; post?: never; - /** Delete Markup */ - delete: operations["delete_markup_projects__pid__drawings_markup__mid__delete"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/markup/{mid}/promote": { + "/asset-rights/verify": { parameters: { query?: never; header?: never; @@ -144,88 +185,111 @@ export interface paths { get?: never; put?: never; /** - * Promote Markup - * @description Promote a markup pin to an RFI Topic (Fieldlens/PlanGrid: a located issue on the sheet). + * Asset Rights Verify + * @description Check a release manifest — the `asset_rights.json` from inside a `.mass` container. + * + * **The half the feature exists for.** Sealing a release was reachable and checking one was not, + * which gives a file nobody can verify; a manifest's whole purpose is that *someone else* can + * confirm a release is authentic and unaltered. Findings are reported separately rather than as one + * boolean, because "the content was altered" and "it carries no signature" call for different + * responses — see `verify_release`. + * + * **`public_key` is the caller's to supply, and is NOT defaulted to this deployment's.** That was + * the first design here and it was wrong: a manifest signed by anyone else would then be checked + * against our key, fail, and be reported as `signature_ok: false` — a valid third-party release + * described as a bad signature. Omit it and the signature is checked against the key **embedded in + * the document**, which proves the manifest is internally consistent and proves nothing about who + * wrote it; that is exactly what `trusted_key: false` means. A verifier who already trusts a key + * passes it — ours is on `/asset-rights/status`. + * + * **Do not read the key out of the manifest and pass it back here.** `trusted_key` reports that the + * signature verified against a key *you* supplied, and it cannot see where you got it: echo the + * document's own `verification.public_key` back and the identical evidence that honestly reads + * `trusted_key: false` will read `true`. The API has no trust anchor to check yours against — + * trusting a key is the one part of this a verifier has to do out of band, from the issuer's + * published key or a channel independent of the file. + * + * Not project-scoped and not `require_role`, for the same reason as the status route above: the + * manifest is the input, and the caller may be verifying a release from another deployment + * entirely. It is **`require_identified` and not `current_user`**, though — `current_user` + * *identifies* and does not *authorise*, returning the literal string `"anonymous"` with RBAC on + * and no credential, which would leave this POST reachable unauthenticated. `SEC-GLOBAL-AUTHZ` + * caught that here; three `/jurisdiction/packs` routes shipped that way once. */ - post: operations["promote_markup_projects__pid__drawings_markup__mid__promote_post"]; + post: operations["asset_rights_verify_asset_rights_verify_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/members": { + "/attachments/{aid}/download": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Members */ - get: operations["list_members_projects__pid__members_get"]; + /** Download Attachment */ + get: operations["download_attachment_attachments__aid__download_get"]; put?: never; - /** Add Member */ - post: operations["add_member_projects__pid__members_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/members/{member}": { + "/attachments/{aid}/signed-url": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; - post?: never; /** - * Remove Member - * @description Remove a member from the project. Won't remove the last admin (avoids an orphaned project). + * Sign Attachment + * @description Mint a short-lived signed download URL for an attachment (caller must be a project member). */ - delete: operations["remove_member_projects__pid__members__member__delete"]; + get: operations["sign_attachment_attachments__aid__signed_url_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}": { + "/audit": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get Project */ - get: operations["get_project_projects__pid__get"]; - put?: never; - post?: never; /** - * Delete Project - * @description Delete a project and everything it owns (rows + geometry + attachment blobs). + * Audit Log + * @description Admin read of the audit trail, newest first. Filter by action/actor substring and a + * `since` ISO timestamp. */ - delete: operations["delete_project_projects__pid__delete"]; + get: operations["audit_log_audit_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; - /** Patch Project */ - patch: operations["patch_project_projects__pid__patch"]; + patch?: never; trace?: never; }; - "/projects/{pid}/versions": { + "/auth/cloud/callback": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * List Versions - * @description Model version history — one snapshot per publish (version, element count, +N/-N note). - */ - get: operations["list_versions_projects__pid__versions_get"]; + /** Cloud Callback */ + get: operations["cloud_callback_auth_cloud_callback_get"]; put?: never; post?: never; delete?: never; @@ -234,27 +298,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/versions/diff": { + "/auth/cloud/disconnect": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Diff Versions - * @description Changed elements between two model versions — added / removed GUIDs + unchanged count. + * Cloud Disconnect + * @description Unlink. Revokes at the broker best-effort, then deletes the row — the local account and its + * projects survive; only the cloud credential is destroyed. */ - get: operations["diff_versions_projects__pid__versions_diff_get"]; - put?: never; - post?: never; + post: operations["cloud_disconnect_auth_cloud_disconnect_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/bundle": { + "/auth/cloud/login": { parameters: { query?: never; header?: never; @@ -262,10 +327,11 @@ export interface paths { cookie?: never; }; /** - * Export Bundle - * @description Download the whole project as a portable .mmproj bundle (geometry + all data + blobs). + * Cloud Login + * @description Start the PKCE flow. An opaque **flow id** is sealed into an HttpOnly cookie (never into + * `state`); the verifier is derived from it and never leaves this process. */ - get: operations["export_bundle_projects__pid__bundle_get"]; + get: operations["cloud_login_auth_cloud_login_get"]; put?: never; post?: never; delete?: never; @@ -274,7 +340,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/import-bundle": { + "/auth/cloud/refresh": { parameters: { query?: never; header?: never; @@ -284,174 +350,189 @@ export interface paths { get?: never; put?: never; /** - * Import Bundle - * @description Open a .mmproj bundle as a new project (fresh id) — geometry, data, and blobs restored. + * Cloud Refresh Profile + * @description Re-read `userinfo` and re-apply tier + role. Lets an upgrade or a role change take effect + * without making the user sign out and back in. */ - post: operations["import_bundle_projects_import_bundle_post"]; + post: operations["cloud_refresh_profile_auth_cloud_refresh_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/topics": { + "/auth/cloud/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Topics */ - get: operations["list_topics_projects__pid__topics_get"]; + /** + * Cloud Status + * @description What the UI needs to render the account chip and the library entry point. + * + * **Identify-only on purpose — this is the one cloud route an ANONYMOUS caller must reach.** The + * sign-in modal calls it while signed out to decide whether to offer "Continue with massing.cloud"; + * gating it with `require_identified` would 401 exactly the caller it exists to serve, and the + * button would never appear. Safe because an anonymous or unlinked caller gets only + * `{enabled, linked: false, site_url}` — deployment configuration, no user data and never a token. + * Every route below that reads or mutates a *link* takes `require_identified`, and `test_global_authz` + * flags only mutating routes, so this read is outside that gate by its rule as well as by intent. + */ + get: operations["cloud_status_auth_cloud_status_get"]; put?: never; - /** Create Topic */ - post: operations["create_topic_projects__pid__topics_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/topics/{tid}": { + "/auth/login": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get Topic */ - get: operations["get_topic_projects__pid__topics__tid__get"]; + get?: never; put?: never; - post?: never; + /** Login */ + post: operations["login_auth_login_post"]; delete?: never; options?: never; head?: never; - /** Patch Topic */ - patch: operations["patch_topic_projects__pid__topics__tid__patch"]; + patch?: never; trace?: never; }; - "/projects/{pid}/pins": { + "/auth/logout": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Pins */ - get: operations["list_pins_projects__pid__pins_get"]; + get?: never; put?: never; - post?: never; + /** Logout */ + post: operations["logout_auth_logout_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/topics/{tid}/comments": { + "/auth/logout-all": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Comments */ - get: operations["list_comments_projects__pid__topics__tid__comments_get"]; + get?: never; put?: never; - /** Add Comment */ - post: operations["add_comment_projects__pid__topics__tid__comments_post"]; + /** + * Logout All + * @description Sign out everywhere: revoke every outstanding session for the caller (bump token_epoch), + * then re-mint the current session so this tab stays in. Use after a suspected token leak. + */ + post: operations["logout_all_auth_logout_all_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/topics/{tid}/viewpoints": { + "/auth/me": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Viewpoints */ - get: operations["list_viewpoints_projects__pid__topics__tid__viewpoints_get"]; + /** Me */ + get: operations["me_auth_me_get"]; put?: never; - /** Add Viewpoint */ - post: operations["add_viewpoint_projects__pid__topics__tid__viewpoints_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/topics/{tid}/attachments": { + "/auth/mfa/disable": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Attachments */ - get: operations["list_attachments_projects__pid__topics__tid__attachments_get"]; + get?: never; put?: never; - /** Add Attachment */ - post: operations["add_attachment_projects__pid__topics__tid__attachments_post"]; + /** + * Mfa Disable + * @description Turn MFA off. Requires the account password AND a current TOTP/recovery code, so a merely + * hijacked session can't strip the second factor. + */ + post: operations["mfa_disable_auth_mfa_disable_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/attachments/{aid}/download": { + "/auth/mfa/enable": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Download Attachment */ - get: operations["download_attachment_attachments__aid__download_get"]; + get?: never; put?: never; - post?: never; + /** + * Mfa Enable + * @description Confirm enrollment with a code from the authenticator; on success turn MFA on and return + * one-time recovery codes (shown once — the server stores only their hashes). + */ + post: operations["mfa_enable_auth_mfa_enable_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/attachments/{aid}/signed-url": { + "/auth/mfa/setup": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Sign Attachment - * @description Mint a short-lived signed download URL for an attachment (caller must be a project member). + * Mfa Setup + * @description Begin enrollment: generate (and store, pending) a fresh secret; return it + an otpauth URI + * to show as a QR/manual key. Not active until confirmed at /auth/mfa/enable with a valid code. */ - get: operations["sign_attachment_attachments__aid__signed_url_get"]; - put?: never; - post?: never; + post: operations["mfa_setup_auth_mfa_setup_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/model.frag": { + "/auth/mfa/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Model Frag - * @description Serve the published Fragments tile with HTTP range support + ETag revalidation. The URL is - * stable across republishes, so we revalidate (not immutable): unchanged → 304 (instant re-open), - * republished → fresh bytes. Access: a valid signed URL or (RBAC on) project membership. - */ - get: operations["model_frag_projects__pid__model_frag_get"]; + /** Mfa Status */ + get: operations["mfa_status_auth_mfa_status_get"]; put?: never; post?: never; delete?: never; @@ -460,27 +541,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model.frag/signed-url": { + "/auth/mfa/verify": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Sign Model Frag - * @description Mint a short-lived signed URL for the model tile (e.g. QR share / worker fetch without a session). + * Mfa Verify + * @description Login step 2: exchange the challenge ticket + a TOTP (or one-time recovery) code for a + * session. A used recovery code is burned. */ - get: operations["sign_model_frag_projects__pid__model_frag_signed_url_get"]; - put?: never; - post?: never; + post: operations["mfa_verify_auth_mfa_verify_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/source.ifc": { + "/auth/oauth/{provider}/callback": { parameters: { query?: never; header?: never; @@ -488,10 +570,11 @@ export interface paths { cookie?: never; }; /** - * Source Ifc Download - * @description Download the project's source IFC (Save → Export IFC). + * Oauth Callback + * @description Exchange the code, map the verified email to an account, mint the session, and return + * to the app. SSO accounts are always plain free-tier users (no admin tier for end users). */ - get: operations["source_ifc_download_projects__pid__source_ifc_get"]; + get: operations["oauth_callback_auth_oauth__provider__callback_get"]; put?: never; post?: never; delete?: never; @@ -500,7 +583,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/bcf/export": { + "/auth/oauth/{provider}/login": { parameters: { query?: never; header?: never; @@ -508,10 +591,10 @@ export interface paths { cookie?: never; }; /** - * Bcf Export - * @description Export the project's topics as a .bcfzip. `version` = 2.1 (default) or 3.0. + * Oauth Login + * @description Redirect to the provider's consent screen. */ - get: operations["bcf_export_projects__pid__bcf_export_get"]; + get: operations["oauth_login_auth_oauth__provider__login_get"]; put?: never; post?: never; delete?: never; @@ -520,7 +603,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/bcf/import": { + "/auth/password": { parameters: { query?: never; header?: never; @@ -529,36 +612,43 @@ export interface paths { }; get?: never; put?: never; - /** Bcf Import */ - post: operations["bcf_import_projects__pid__bcf_import_post"]; + /** + * Change Password + * @description Change your own password (requires the current one). Rotating the password revokes every + * other outstanding session (bumps token_epoch); a fresh token is issued so the current tab + * stays signed in. + */ + post: operations["change_password_auth_password_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/coordination/import-xlsx": { + "/auth/providers": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Coordination Import Xlsx - * @description Import a Solibri / Navisworks (or any tabular) clash report XLSX -> one coordination_issue per - * row (GUIDs anchor it on the model; each round-trips to BCF). Sniffs the header + maps aliases. + * Auth Providers + * @description Enabled SSO providers (those with client id + secret configured) — drives the login UI. + * `saml` is true when a SAML IdP is configured AND the tier entitles SSO (its button posts to + * /auth/saml/login). `is_available`, not `is_enabled`: this endpoint decides whether the button is + * rendered, so advertising from configuration alone would offer a button that 402s on click. */ - post: operations["coordination_import_xlsx_projects__pid__coordination_import_xlsx_post"]; + get: operations["auth_providers_auth_providers_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/properties/index": { + "/auth/register": { parameters: { query?: never; header?: never; @@ -567,57 +657,56 @@ export interface paths { }; get?: never; put?: never; - /** - * Upload Index - * @description Upload the props.json produced by the data service (`aec_data.cli index`). Size-gated — - * json.loads of an unbounded upload would parse an arbitrarily large body entirely in RAM. - */ - post: operations["upload_index_projects__pid__properties_index_post"]; + /** Register */ + post: operations["register_auth_register_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/properties/meta": { + "/auth/reset": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Meta */ - get: operations["meta_projects__pid__properties_meta_get"]; + get?: never; put?: never; - post?: never; + /** + * Reset With Token + * @description Unauthenticated: set a new password using a reset token (the token is the credential). + */ + post: operations["reset_with_token_auth_reset_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements": { + "/auth/saml/acs": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * List Elements - * @description Query the property index. `?discipline=` accepts an NCS code or name (e.g. 'S' or 'Structural'); - * each element is returned with its derived `discipline` (Discipline Spine D2). + * Saml Acs + * @description Assertion Consumer Service — verify the signed response, map the email to an account, mint the + * session, and return to the app. Any verification failure is a 403 (never leak crypto detail). */ - get: operations["list_elements_projects__pid__elements_get"]; - put?: never; - post?: never; + post: operations["saml_acs_auth_saml_acs_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements/facets-list": { + "/auth/saml/login": { parameters: { query?: never; header?: never; @@ -625,11 +714,10 @@ export interface paths { cookie?: never; }; /** - * Color Facets - * @description The properties you can colour by: top-level attributes + every pset/qto property present, - * each with its distinct-value count (drives the viewer's 'Color by…' picker). + * Saml Login + * @description Redirect to the IdP's SSO URL with a SAMLRequest (SP-initiated, HTTP-Redirect binding). */ - get: operations["color_facets_projects__pid__elements_facets_list_get"]; + get: operations["saml_login_auth_saml_login_get"]; put?: never; post?: never; delete?: never; @@ -638,7 +726,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/elements/color-by": { + "/auth/saml/metadata": { parameters: { query?: never; header?: never; @@ -646,15 +734,10 @@ export interface paths { cookie?: never; }; /** - * Color By - * @description Bucket every element by a chosen property → colour buckets for the 3D viewer. Numeric - * properties are binned into ranges; categorical ones grouped by value (top 24 + Other). - * - * `ids=true` (default) returns each bucket's element GUIDs (the viewer needs them to colour), gzipped - * on the wire when large. `ids=false` returns only labels + counts — a compact distribution for a - * legend / picker with no per-element payload. + * Saml Metadata + * @description SP metadata XML to register with the IdP (entityID + ACS). Available whenever SAML is on. */ - get: operations["color_by_projects__pid__elements_color_by_get"]; + get: operations["saml_metadata_auth_saml_metadata_get"]; put?: never; post?: never; delete?: never; @@ -663,89 +746,100 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/elements/by-discipline": { + "/auth/step-up": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Elements By Discipline - * @description Model composition by NCS discipline (Discipline Spine D2): element count + a class breakdown per - * discipline, in NCS sheet order. Derived from the property index — one pass, no republish. + * Step Up + * @description Re-prove the account password to obtain a short-lived assertion scoped to ONE action. + * + * Refused for the `api-key` identity outright. That is the substance of this endpoint rather than a + * detail: the api-key is a machine credential with no password and no person behind it, so there is + * nothing it could re-prove. Allowing it would reintroduce exactly the hole this closes — an + * automation emitting sealed documents in a licensee's name. + * + * **Single-use.** Each assertion carries a `jti` and is spent by the operation that consumes it + * (`rbac.consume_stepup`), so it attests "a human confirmed THIS act", not merely "a human re-proved + * this password in the last five minutes". It was time-bounded only until 2026-07-31; the weaker + * form let one captured assertion seal a stack of documents inside its TTL, which is the wrong claim + * for a per-document legal attestation. Expect to mint one per document. */ - get: operations["elements_by_discipline_projects__pid__elements_by_discipline_get"]; - put?: never; - post?: never; + post: operations["step_up_auth_step_up_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements/qa": { + "/auth/users": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + /** List Users */ + get: operations["list_users_auth_users_get"]; + put?: never; /** - * Data Qa - * @description BIM data-completeness check: for each attribute, how many elements have it, and which are - * missing it. The headline compliance % + the 3D highlight use the required rules; recommended - * rules (type, property sets) are reported separately so gaps surface without failing everything. + * Create User + * @description Admin-created account (the open path after bootstrap; /auth/register stays for the + * very first user). */ - get: operations["data_qa_projects__pid__elements_qa_get"]; - put?: never; - post?: never; + post: operations["create_user_auth_users_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements/code-check": { + "/auth/users/{username}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Code Check - * @description Code-readiness check: does the model carry the data a plan review needs (egress door widths, - * fire ratings, space areas/occupancy, egress stairs, classification)? Property-level, not a - * certified code review. Returns per-rule pass/fail + the elements to highlight in 3D. - */ - get: operations["code_check_projects__pid__elements_code_check_get"]; + get?: never; put?: never; post?: never; delete?: never; options?: never; head?: never; - patch?: never; + /** + * Update User + * @description Change a user's role and/or activate/deactivate them. Won't lock out the last admin. + */ + patch: operations["update_user_auth_users__username__patch"]; trace?: never; }; - "/projects/{pid}/elements/{guid}": { + "/auth/users/{username}/password": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Element */ - get: operations["element_projects__pid__elements__guid__get"]; + get?: never; put?: never; - post?: never; + /** + * Reset Password + * @description Admin reset of another user's password. + */ + post: operations["reset_password_auth_users__username__password_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ask": { + "/auth/users/{username}/reset-token": { parameters: { query?: never; header?: never; @@ -755,44 +849,51 @@ export interface paths { get?: never; put?: never; /** - * Ask Model - * @description Ask a plain-English question about the model. Grounds the answer in a snapshot of the property - * index (counts by class/storey, Psets, facets); uses the configured AI provider, and degrades to - * returning the snapshot itself when no AI key is set (so the data is still useful offline). + * Issue Reset Token + * @description Admin issues a single-use, 1-hour reset token for a user to set their own password + * (no email infra needed — hand the token to the user). The token can't be used as a + * bearer token and is invalidated once the password changes. */ - post: operations["ask_model_projects__pid__ask_post"]; + post: operations["issue_reset_token_auth_users__username__reset_token_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/exports/qto.xlsx": { + "/auth/users/{username}/revoke-sessions": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Export Qto */ - get: operations["export_qto_projects__pid__exports_qto_xlsx_get"]; + get?: never; put?: never; - post?: never; + /** + * Revoke Sessions + * @description Admin: force-revoke all of a user's outstanding tokens (e.g. offboarding / lost device). + * They must sign in again; deactivating the account (active=false) blocks re-login entirely. + */ + post: operations["revoke_sessions_auth_users__username__revoke_sessions_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/exports/cobie.xlsx": { + "/bcf/2.1/auth": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Export Cobie */ - get: operations["export_cobie_projects__pid__exports_cobie_xlsx_get"]; + /** + * Bcf Auth + * @description Auth discovery — the platform uses Bearer tokens (see /auth/login); no separate OAuth flow. + */ + get: operations["bcf_auth_bcf_2_1_auth_get"]; put?: never; post?: never; delete?: never; @@ -801,15 +902,18 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/exports/spaces.xlsx": { + "/bcf/2.1/projects": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Export Spaces */ - get: operations["export_spaces_projects__pid__exports_spaces_xlsx_get"]; + /** + * Bcf Projects + * @description Projects the caller can access, in BCF-API shape. + */ + get: operations["bcf_projects_bcf_2_1_projects_get"]; put?: never; post?: never; delete?: never; @@ -818,36 +922,37 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/exports/model.gbxml": { + "/bcf/2.1/projects/{pid}/topics": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + /** Bcf Topics */ + get: operations["bcf_topics_bcf_2_1_projects__pid__topics_get"]; + put?: never; /** - * Export Gbxml - * @description gbXML (Green Building XML) — spaces + areas/volumes from the IFC geometry, for OpenStudio / - * EnergyPlus / IES energy modelling. Simplified (building-level envelope, not per-space surfaces). + * Bcf Create Topic + * @description Create a topic from a BCF-API payload (title required; topic_type / topic_status / priority / + * labels / assigned_to / description optional). */ - get: operations["export_gbxml_projects__pid__exports_model_gbxml_get"]; - put?: never; - post?: never; + post: operations["bcf_create_topic_bcf_2_1_projects__pid__topics_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/exports/schedule.xlsx": { + "/bcf/2.1/projects/{pid}/topics/{guid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Export Schedule */ - get: operations["export_schedule_projects__pid__exports_schedule_xlsx_get"]; + /** Bcf Topic */ + get: operations["bcf_topic_bcf_2_1_projects__pid__topics__guid__get"]; put?: never; post?: never; delete?: never; @@ -856,74 +961,66 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/closeout/package.zip": { + "/bcf/2.1/projects/{pid}/topics/{guid}/comments": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Closeout Package - * @description Turnover deliverable in one ZIP: the as-built IFC, COBie / QTO / space-schedule workbooks, - * the status-report PDF, and a JSON manifest of the closeout records (commissioning, O&M, - * warranties, as-builts, asset register, completion certificate, punchlist). - */ - get: operations["closeout_package_projects__pid__closeout_package_zip_get"]; + /** Bcf Comments */ + get: operations["bcf_comments_bcf_2_1_projects__pid__topics__guid__comments_get"]; put?: never; - post?: never; + /** Bcf Create Comment */ + post: operations["bcf_create_comment_bcf_2_1_projects__pid__topics__guid__comments_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/clash": { + "/bcf/2.1/projects/{pid}/topics/{guid}/viewpoints": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Bcf Viewpoints */ + get: operations["bcf_viewpoints_bcf_2_1_projects__pid__topics__guid__viewpoints_get"]; put?: never; /** - * Run Clash - * @description Detect clashes between two IFC-class groups (comma-separated in `a` / `b`). - * narrow=true runs the mesh boolean-intersection narrow phase (exact penetration volume). - * With create_topics=true, the top clashes become BCF `clash` topics (pins/issues). + * Bcf Create Viewpoint + * @description Create a viewpoint from a BCF-API payload (perspective_camera + components + snapshot). */ - post: operations["run_clash_projects__pid__clash_post"]; + post: operations["bcf_create_viewpoint_bcf_2_1_projects__pid__topics__guid__viewpoints_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/clash/federated": { + "/bcf/2.1/projects/{pid}/topics/{guid}/viewpoints/{vguid}/snapshot": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Run Clash Federated - * @description Cross-discipline (federated) clash across 2+ models. Intra-model overlaps are excluded. - * If no `disciplines` map is given, it's built from the project's own models — the primary source - * IFC + any appended discipline models (POST /projects/{pid}/models). create_topics=true turns the - * top clashes into BCF clash topics (→ pins / Issues). + * Bcf Viewpoint Snapshot + * @description The viewpoint's PNG snapshot (BCF managers fetch it separately). 404 if none stored. */ - post: operations["run_clash_federated_projects__pid__clash_federated_post"]; + get: operations["bcf_viewpoint_snapshot_bcf_2_1_projects__pid__topics__guid__viewpoints__vguid__snapshot_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/models/georeferencing": { + "/bcf/3.0/auth": { parameters: { query?: never; header?: never; @@ -931,12 +1028,10 @@ export interface paths { cookie?: never; }; /** - * Model Georeferencing - * @description Shared-coordinates / setout basis for the project's source model — full IfcMapConversion - * (eastings/northings/height, true-north bearing, scale) + IfcProjectedCRS (EPSG, datums) + LoGeoRef - * level. The survey basis a coordinator needs for federation and BIM-to-field layout. 409 if no IFC. + * Bcf3 Auth + * @description Auth discovery (3.0) — Bearer tokens via /auth/login; no separate OAuth flow. */ - get: operations["model_georeferencing_projects__pid__models_georeferencing_get"]; + get: operations["bcf3_auth_bcf_3_0_auth_get"]; put?: never; post?: never; delete?: never; @@ -945,29 +1040,24 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/scan/deviation": { + "/bcf/3.0/projects": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Bcf3 Projects */ + get: operations["bcf3_projects_bcf_3_0_projects_get"]; put?: never; - /** - * Scan Deviation - * @description Scan-to-BIM deviation — compare an uploaded as-built point cloud (XYZ/CSV) against the source - * model's surface and report % within tolerance + a deviation histogram (the QA/QC as-built check). - * 409 if no source IFC; 400 on a point cloud we can't read. - */ - post: operations["scan_deviation_projects__pid__scan_deviation_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ai-readiness": { + "/bcf/3.0/projects/{pid}/documents": { parameters: { query?: never; header?: never; @@ -975,11 +1065,10 @@ export interface paths { cookie?: never; }; /** - * Ai Readiness Scorecard - * @description AI / data-readiness scorecard — grades the project 0-100 on single-source-of-truth, information - * completeness, model integrity and governance ("can an agent act on this data yet?"). + * Bcf3 Documents + * @description Every document in the project — the union of its topics' attachments, newest first. */ - get: operations["ai_readiness_scorecard_projects__pid__ai_readiness_get"]; + get: operations["bcf3_documents_bcf_3_0_projects__pid__documents_get"]; put?: never; post?: never; delete?: never; @@ -988,7 +1077,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/models/qa": { + "/bcf/3.0/projects/{pid}/documents/{doc_guid}": { parameters: { query?: never; header?: never; @@ -996,12 +1085,10 @@ export interface paths { cookie?: never; }; /** - * Model Qa Report - * @description Model integrity / hygiene scan of the source IFC — duplicate GUIDs, orphaned (no-storey) - * elements, overlapping duplicates, unenclosed spaces and blank names. Complements the LOIN/IDS - * data-quality checks. 409 if the project has no source IFC. + * Bcf3 Document Download + * @description Download one document's bytes (project-scoped, so a guid from another project 404s). */ - get: operations["model_qa_report_projects__pid__models_qa_get"]; + get: operations["bcf3_document_download_bcf_3_0_projects__pid__documents__doc_guid__get"]; put?: never; post?: never; delete?: never; @@ -1010,42 +1097,36 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/models/alignment": { + "/bcf/3.0/projects/{pid}/topics": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + /** Bcf3 Topics */ + get: operations["bcf3_topics_bcf_3_0_projects__pid__topics_get"]; + put?: never; /** - * Model Alignment - * @description Federation alignment report — do the project's discipline models share the same storey scheme - * and georeferenced origin? The #1 coordination problem is models on different origins/levels; this - * reads each model's storey elevations + IfcMapConversion and flags mismatches (a lightweight - * companion to federated clash). Reads the models read-only. + * Bcf3 Create Topic + * @description Create a topic from a 3.0 payload — the 2.1 creator, re-serialized in the 3.0 shape. */ - get: operations["model_alignment_projects__pid__models_alignment_get"]; - put?: never; - post?: never; + post: operations["bcf3_create_topic_bcf_3_0_projects__pid__topics_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/quantities/disciplines": { + "/bcf/3.0/projects/{pid}/topics/{guid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Discipline Quantities - * @description Discipline quantity roll-up — reinforcement tonnage, MEP linear runs (duct/pipe/cable) + fitting - * counts, and structural element volume, from the IFC (Qto psets with a geometry fallback). - */ - get: operations["discipline_quantities_projects__pid__quantities_disciplines_get"]; + /** Bcf3 Topic */ + get: operations["bcf3_topic_bcf_3_0_projects__pid__topics__guid__get"]; put?: never; post?: never; delete?: never; @@ -1054,39 +1135,55 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/energy": { + "/bcf/3.0/projects/{pid}/topics/{guid}/comments": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Energy - * @description Envelope energy analysis (UA + degree-day) computed from the model geometry. - * Construction U-values and climate degree-days are overridable via query params. - */ - get: operations["energy_projects__pid__energy_get"]; + /** Bcf3 Comments */ + get: operations["bcf3_comments_bcf_3_0_projects__pid__topics__guid__comments_get"]; put?: never; - post?: never; + /** Bcf3 Create Comment */ + post: operations["bcf3_create_comment_bcf_3_0_projects__pid__topics__guid__comments_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/mep": { + "/bcf/3.0/projects/{pid}/topics/{guid}/document_references": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + /** Bcf3 Document Refs */ + get: operations["bcf3_document_refs_bcf_3_0_projects__pid__topics__guid__document_references_get"]; + put?: never; /** - * Mep - * @description MEP systems inventory from the model. + * Bcf3 Add Document + * @description Attach a document to a topic over the BCF API (the coordinator's tool uploads the PDF/photo + * directly). Same storage path and filename hardening as the native attachment route. */ - get: operations["mep_projects__pid__mep_get"]; + post: operations["bcf3_add_document_bcf_3_0_projects__pid__topics__guid__document_references_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/bcf/3.0/projects/{pid}/topics/{guid}/viewpoints": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Bcf3 Viewpoints */ + get: operations["bcf3_viewpoints_bcf_3_0_projects__pid__topics__guid__viewpoints_get"]; put?: never; post?: never; delete?: never; @@ -1095,7 +1192,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/ids": { + "/bcf/versions": { parameters: { query?: never; header?: never; @@ -1103,51 +1200,39 @@ export interface paths { cookie?: never; }; /** - * Get Project Ids - * @description Whether a project IDS is pinned (+ its size); `?download=1` streams the .ids back. - */ - get: operations["get_project_ids_projects__pid__ids_get"]; - /** - * Put Project Ids - * @description Pin the project's IDS. Subsequent `/validate` calls (with no uploaded file) run against it. + * Bcf Versions + * @description BCF-API version negotiation. Advertises 2.1 and 3.0; a manager picks the newest it speaks. */ - put: operations["put_project_ids_projects__pid__ids_put"]; + get: operations["bcf_versions_bcf_versions_get"]; + put?: never; post?: never; - /** Delete Project Ids */ - delete: operations["delete_project_ids_projects__pid__ids_delete"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/validate": { + "/benchmarks": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Run Validate - * @description Validate the source IFC against an IDS. Precedence: an **uploaded** `.ids` wins; otherwise - * `ids=auto` (default) uses the project's **pinned** IDS when one exists, else the built-in QA - * specs. `ids=stored` forces the pinned IDS (404 if none); `ids=default` forces the built-in specs. - * - * `format=json` (default) returns the per-specification pass/fail summary. `format=bcf` returns a - * **.bcfzip punch list of the non-conformances** — one topic per failing specification, its failing - * elements selected as components — so an IDS audit round-trips into Solibri / ACC / BIMcollab like - * any other coordination issue. + * Get Benchmarks + * @description Citable benchmark ranges (cost/sf, cap rates, productivity, lean PPC) for grounding defaults (R5). */ - post: operations["run_validate_projects__pid__validate_post"]; + get: operations["get_benchmarks_benchmarks_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set": { + "/benchmarks/costs": { parameters: { query?: never; header?: never; @@ -1155,11 +1240,10 @@ export interface paths { cookie?: never; }; /** - * Get Drawing Set - * @description Controlled drawing-set register from the `drawing` records: current set (latest revision per - * sheet), superseded revisions, sheet index + discipline rollup + issuance (new vs revised). + * Cost Benchmarks + * @description Actual-cost distribution (low/p25/median/p75/high) per cost code across your projects. */ - get: operations["get_drawing_set_projects__pid__drawing_set_get"]; + get: operations["cost_benchmarks_benchmarks_costs_get"]; put?: never; post?: never; delete?: never; @@ -1168,7 +1252,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/transmittal.pdf": { + "/benchmarks/pull-planning": { parameters: { query?: never; header?: never; @@ -1176,10 +1260,11 @@ export interface paths { cookie?: never; }; /** - * Drawing Set Transmittal - * @description A transmittal PDF of the controlled current set (recipients via `to`, comma-separated). + * Pull Planning + * @description Pull-planning reliability across your projects: PPC + Tasks-Made-Ready % distribution vs the + * ≥80% target — so a plan can be judged against the team's own portfolio. */ - get: operations["drawing_set_transmittal_projects__pid__drawing_set_transmittal_pdf_get"]; + get: operations["pull_planning_benchmarks_pull_planning_get"]; put?: never; post?: never; delete?: never; @@ -1188,7 +1273,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issuance-purposes": { + "/benchmarks/response-rates": { parameters: { query?: never; header?: never; @@ -1196,10 +1281,10 @@ export interface paths { cookie?: never; }; /** - * Issuance Purposes - * @description The AIA/CD issuance purposes (SD/DD/CD/Permit/Bid/IFC/Addendum/Conformed/Record). + * Response Rates + * @description RFI + submittal turnaround and overdue % across your projects (ball-in-court accountability). */ - get: operations["issuance_purposes_projects__pid__drawing_set_issuance_purposes_get"]; + get: operations["response_rates_benchmarks_response_rates_get"]; put?: never; post?: never; delete?: never; @@ -1208,7 +1293,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issuances": { + "/benchmarks/space-utilization": { parameters: { query?: never; header?: never; @@ -1216,10 +1301,12 @@ export interface paths { cookie?: never; }; /** - * Drawing Issuances - * @description The issuance history — every release, its purpose, date, sheet count, recipients. + * Space Utilization + * @description SPACE-UTIL benchmarking — capacity/utilization across your modelled projects (space count, + * total area, capacity at the given m²/person standard, m² per space vs the portfolio median). + * Bounded to 12 models per call (newest first; skips are counted, never silent). */ - get: operations["drawing_issuances_projects__pid__drawing_set_issuances_get"]; + get: operations["space_utilization_benchmarks_space_utilization_get"]; put?: never; post?: never; delete?: never; @@ -1228,28 +1315,38 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issue": { + "/benchmarks/unit-rates": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Issue Drawing Set - * @description Issue the current drawing set for a purpose — snapshots every current sheet + its revision. - * Body: `{purpose, date?, description?, recipients?}`. + * Unit Rates + * @description Actual **unit** rates per cost code — `direct_cost` divided by installed + * `production_quantity` — distributed across your projects. + * + * The sibling of `/benchmarks/costs`, and the one that needs the field's own quantity records: + * that endpoint says what a cost code has cost, this one says what it cost **per unit**, which is + * the difference between "this project was expensive" and "concrete is dear". + * + * Each rate is ONE project's cost over that project's quantity, and the distribution is over + * projects. `pooled_rate` — the portfolio-wide blend — is reported beside it and answers a + * different question; one large project sets it. Units are grouped, never converted, and a project + * that records one cost code under two units is excluded rather than split. Everything excluded + * comes back with a count and a reason. */ - post: operations["issue_drawing_set_projects__pid__drawing_set_issue_post"]; + get: operations["unit_rates_benchmarks_unit_rates_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issuance-matrix": { + "/benchmarks/vendors": { parameters: { query?: never; header?: never; @@ -1257,10 +1354,21 @@ export interface paths { cookie?: never; }; /** - * Drawing Issuance Matrix - * @description The sheet-index × issuance grid — each sheet's revision in each issuance (the front-of-set matrix). + * Vendor Scorecards + * @description Each trade partner's record with your firm, **across projects** — the third part of + * R22-PROCURE-DEPTH, and the one `prequalification.score_project` could not reach because it takes + * a single project. + * + * Commercial and compliance history from the six registers that carry a vendor: subcontracts, + * commitments, invoices, COIs, lien waivers and warranties. Reported **beside** the prequalification + * Q-score, never blended into it — one is what the sub told you about themselves, the other is what + * they did, and they deserve different trust. + * + * A vendor with no recorded history is `no_history`, **not** `clear`. And `attributable` states the + * limit plainly: `ncr` and `inspection` carry no vendor field, so quality and schedule performance + * are not in this scorecard and a clean record here is silence on that subject, not a pass. */ - get: operations["drawing_issuance_matrix_projects__pid__drawing_set_issuance_matrix_get"]; + get: operations["vendor_scorecards_benchmarks_vendors_get"]; put?: never; post?: never; delete?: never; @@ -1269,7 +1377,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issuances/{iid}/transmittal.pdf": { + "/bridge/rvt/status": { parameters: { query?: never; header?: never; @@ -1277,10 +1385,11 @@ export interface paths { cookie?: never; }; /** - * Issuance Transmittal - * @description A transmittal PDF for one issuance, stamped with its purpose + date and the sheets released. + * Rvt Bridge Status + * @description Is the optional paid Revit (.rvt) → IFC bridge available? The UI checks this before offering + * the import, and shows the cost warning + the free IFC-export alternative. */ - get: operations["issuance_transmittal_projects__pid__drawing_set_issuances__iid__transmittal_pdf_get"]; + get: operations["rvt_bridge_status_bridge_rvt_status_get"]; put?: never; post?: never; delete?: never; @@ -1289,7 +1398,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/issuances/{iid}/sealed.pdf": { + "/bsdd/class": { parameters: { query?: never; header?: never; @@ -1297,12 +1406,11 @@ export interface paths { cookie?: never; }; /** - * Issuance Sealed - * @description The issuance transmittal digitally **sealed** (PAdES) by the professional of record — the - * tamper-evident electronic seal for permit/IFC submittal. Unsealed if e-sign isn't configured - * (X-Sealed: false). + * Bsdd Class + * @description Fetch one bSDD class (with its properties) by full `uri`. 404 when the class + * isn't found; 502 when bSDD is unreachable. */ - get: operations["issuance_sealed_projects__pid__drawing_set_issuances__iid__sealed_pdf_get"]; + get: operations["bsdd_class_bsdd_class_get"]; put?: never; post?: never; delete?: never; @@ -1311,29 +1419,29 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawings/{drawing_id}/revise": { + "/bsdd/search": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Revise Drawing - * @description Record a revision (delta) on a sheet — appends to its revision block, bumps the current - * revision, and optionally cites the driving change instrument (ASI/CCD/Addendum/Bulletin). - * Body: `{rev, description?, date?, instrument_type?, instrument_ref?}`. + * Bsdd Search + * @description Free-text search the buildingSMART Data Dictionary for classes matching `q` + * (optionally scoped to one ?dictionary= URI). Reference-data lookup, not + * project-scoped. A bSDD outage surfaces as 502, not 500. */ - post: operations["revise_drawing_projects__pid__drawings__drawing_id__revise_post"]; + get: operations["bsdd_search_bsdd_search_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/revisions": { + "/capabilities": { parameters: { query?: never; header?: never; @@ -1341,11 +1449,11 @@ export interface paths { cookie?: never; }; /** - * Drawing Revisions - * @description The cross-sheet revision register — every delta on every sheet (newest first) with the driving - * change instrument. The 'what changed, when, why' log. + * Capabilities + * @description Which optional integrations are wired (for at-a-glance status badges). Not sensitive — + * just feature flags + the configured SSO provider ids. */ - get: operations["drawing_revisions_projects__pid__drawing_set_revisions_get"]; + get: operations["capabilities_capabilities_get"]; put?: never; post?: never; delete?: never; @@ -1354,7 +1462,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/plan": { + "/classifications": { parameters: { query?: never; header?: never; @@ -1362,12 +1470,10 @@ export interface paths { cookie?: never; }; /** - * Drawing Set Plan - * @description Preview the discipline sheet set that would be generated (no records created): one series per - * discipline with NCS sheet numbers (M-/FA-/S-/…), a plan per level, plus sections/details/schedules. - * `disciplines` is a comma-separated list of designators or names; `all=true` forces the full set. + * List Classifications + * @description Regional classification systems available for estimate coding / GAEB export. */ - get: operations["drawing_set_plan_projects__pid__drawing_set_plan_get"]; + get: operations["list_classifications_classifications_get"]; put?: never; post?: never; delete?: never; @@ -1376,7 +1482,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawing-set/generate": { + "/client-errors": { parameters: { query?: never; header?: never; @@ -1386,120 +1492,121 @@ export interface paths { get?: never; put?: never; /** - * Generate Drawing Set - * @description Generate the discipline sheet set as `drawing` records — one sheet series per discipline with - * its own NCS designator (A-/S-/M-/E-/P-/FP-/FA-/T-/…), a plan per building level, and the usual - * sections/details/schedules. Body: `{disciplines?:[…], all?:bool, max_levels?:int}`. Idempotent — - * existing sheet numbers are skipped. Flows straight into the drawing-set register + transmittal. + * Report Client Error + * @description Record a browser-side error (window.onerror / unhandledrejection / a failed fetch). Any signed-in + * user; best-effort so a reporting failure never disrupts the app. Body: {message, kind?, path?, + * level?, detail?}. */ - post: operations["generate_drawing_set_projects__pid__drawing_set_generate_post"]; + post: operations["report_client_error_client_errors_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/info": { + "/cloud/library/models/{model_id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Pdf Info - * @description Page count + flags for an uploaded PDF. + * Library Model + * @description A model record plus its signed `download_url`. + * + * The URL is handed to the browser deliberately: it is short-lived, model-scoped, and carries its + * own token, so proxying the bytes through this app would add a hop without adding a check. It + * must be fetched **without** an Authorization header. */ - post: operations["pdf_info_pdf_info_post"]; + get: operations["library_model_cloud_library_models__model_id__get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/merge": { + "/cloud/library/projects": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Library Projects */ + get: operations["library_projects_cloud_library_projects_get"]; put?: never; - /** - * Pdf Merge - * @description Concatenate several uploaded PDFs into one (order = upload order). - */ - post: operations["pdf_merge_pdf_merge_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/split": { + "/cloud/library/projects/{project_id}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Library Project */ + get: operations["library_project_cloud_library_projects__project_id__get"]; put?: never; - /** - * Pdf Split - * @description Split an uploaded PDF into one PDF per page, returned as a .zip. - */ - post: operations["pdf_split_pdf_split_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/extract": { + "/codes/adoptions": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Pdf Extract - * @description A new PDF of just the given pages (`pages` = '1,3,5-7', 1-based). + * Code Adoptions + * @description CODE-1: resolve a jurisdiction (USPS state code, e.g. `CA`) to its adopted code editions — falls + * back to the national baseline when not seeded. Always carries a 'verify with the AHJ' note; adoption + * facts change each cycle. Pass no jurisdiction for the baseline; the seed list is at `/codes/seeded`. */ - post: operations["pdf_extract_pdf_extract_post"]; + get: operations["code_adoptions_codes_adoptions_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/rotate": { + "/codes/ebc/pathways": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Pdf Rotate - * @description Rotate pages by `angle` (multiple of 90). `pages` (1-based, '1,3-5') limits it; blank = all. + * Ebc Pathways + * @description CODE-EBC: the IEBC existing-building reference catalog — the three compliance methods and the + * Work-Area classifications (Repair · Alteration 1/2/3 · Change of Occupancy · Addition) with citations. + * Facts of law; verify the edition + classification with the AHJ. */ - post: operations["pdf_rotate_pdf_rotate_post"]; + get: operations["ebc_pathways_codes_ebc_pathways_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/stamps/library": { + "/codes/families": { parameters: { query?: never; header?: never; @@ -1507,11 +1614,11 @@ export interface paths { cookie?: never; }; /** - * Stamps Library - * @description The A/E/C stamp template library — review (EJCDC + CSI), inspection, status, and seal templates. - * The client renders the picker and preview from this; the server is the source of truth. + * Code Families + * @description CODE-1: the model-code family + edition catalog (IBC/IRC/IECC/… on their 3-year cycle) plus the + * documented national baseline. Reference facts — not jurisdiction-specific. */ - get: operations["stamps_library_stamps_library_get"]; + get: operations["code_families_codes_families_get"]; put?: never; post?: never; delete?: never; @@ -1520,67 +1627,69 @@ export interface paths { patch?: never; trace?: never; }; - "/pdf/stamp": { + "/codes/ids": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Pdf Stamp - * @description Composite a review / inspection / status stamp onto a page (1-based). (x,y) = top-left of the - * stamp in PDF points from the page's top-left. `values` = JSON object of field values. + * Codes Ids + * @description CODE-5: the applicable code requirements as a buildingSMART **IDS 1.0** file (the machine-checkable + * subset) from a project description — validate an IFC against it in any IDS checker. `download=true` + * returns the `.ids` XML attachment; otherwise JSON with the fired code topics + `ids_xml`. */ - post: operations["pdf_stamp_pdf_stamp_post"]; + get: operations["codes_ids_codes_ids_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/pdf/seal": { + "/codes/seeded": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Pdf Seal - * @description Render a *visible* professional seal + signature block, then apply a tamper-evident PAdES - * signature LAST (unless `sign=false`). `profile` = JSON {name,license_no,state,expiration,date}. - * The self-signed platform cert is demonstration / tamper-evidence, not board-accepted sealing. + * Code Seeded + * @description CODE-1: the USPS state codes that have a specific adoption seed (everything else → baseline). */ - post: operations["pdf_seal_pdf_seal_post"]; + get: operations["code_seeded_codes_seeded_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/storeys": { + "/compute/graph": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Storeys */ - get: operations["list_storeys_projects__pid__drawings_storeys_get"]; + get?: never; put?: never; - post?: never; + /** + * Compute Run + * @description Run a Dynamo/Hypar-style node graph: {nodes, edges} → each node's outputs, in dependency order (M4). + */ + post: operations["compute_run_compute_graph_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/model/grid": { + "/compute/nodes": { parameters: { query?: never; header?: never; @@ -1588,11 +1697,10 @@ export interface paths { cookie?: never; }; /** - * Model Grid - * @description Drafting reference frame: the grid (real IfcGrid axes, else derived from IfcColumn centres) + - * its snap intersections + the storey levels — for the web Draft panel to render and snap against. + * Compute Nodes + * @description Node palette for the computational graph — zero-touch nodes over the pure engines (M4). */ - get: operations["model_grid_projects__pid__model_grid_get"]; + get: operations["compute_nodes_compute_nodes_get"]; put?: never; post?: never; delete?: never; @@ -1601,58 +1709,66 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawings/plan.svg": { + "/connections": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Plan */ - get: operations["plan_projects__pid__drawings_plan_svg_get"]; + /** + * List Connections + * @description Built-in local DB (with live status) + registered external connections (status on demand). + */ + get: operations["list_connections_connections_get"]; put?: never; - post?: never; + /** Create Connection */ + post: operations["create_connection_connections_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/section.svg": { + "/connections/test": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Section */ - get: operations["section_projects__pid__drawings_section_svg_get"]; + get?: never; put?: never; - post?: never; + /** + * Test Config + * @description Test a posted config (used by the add/edit form before saving). + */ + post: operations["test_config_connections_test_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/elevation.svg": { + "/connections/{cid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Elevation */ - get: operations["elevation_projects__pid__drawings_elevation_svg_get"]; - put?: never; + get?: never; + /** Update Connection */ + put: operations["update_connection_connections__cid__put"]; post?: never; - delete?: never; + /** Delete Connection */ + delete: operations["delete_connection_connections__cid__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/model/export.gltf": { + "/connections/{cid}/acc/projects/{project_id}/issues": { parameters: { query?: never; header?: never; @@ -1660,13 +1776,10 @@ export interface paths { cookie?: never; }; /** - * Export Gltf - * @description Export the model geometry as a self-contained glTF 2.0 file (interchange — Blender / Three.js / - * any DCC). Triangulated meshes merged per IFC class with per-class colours; Z-up→Y-up. The viewer - * itself streams Fragments — this is the portable geometry-out path. Geometry tessellation runs off - * the event loop. + * Acc Issues + * @description Read an ACC project's issues (browse the issues data plane for an Autodesk connection). */ - get: operations["export_gltf_projects__pid__model_export_gltf_get"]; + get: operations["acc_issues_connections__cid__acc_projects__project_id__issues_get"]; put?: never; post?: never; delete?: never; @@ -1675,7 +1788,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/step-summary": { + "/connections/{cid}/erp/{entity}": { parameters: { query?: never; header?: never; @@ -1683,11 +1796,10 @@ export interface paths { cookie?: never; }; /** - * Model Step Summary - * @description Fast model summary — header + entity-type histogram from a streaming STEP scan, without a full - * ifcopenshell parse. Instant 'what's in this IFC' for large files. + * Erp Read + * @description Read accounts / vendors / bills from a Sage or Viewpoint (generic REST ERP) connection. */ - get: operations["model_step_summary_projects__pid__model_step_summary_get"]; + get: operations["erp_read_connections__cid__erp__entity__get"]; put?: never; post?: never; delete?: never; @@ -1696,16 +1808,24 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawings/sheet.svg": { + "/connections/{cid}/mappings": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Sheet Svg */ - get: operations["sheet_svg_projects__pid__drawings_sheet_svg_get"]; - put?: never; + /** + * Get Mappings + * @description Editable Procore→module field mapping: per kind, each module field with its default and + * current Procore source path. Drives the field-mapping editor. + */ + get: operations["get_mappings_connections__cid__mappings_get"]; + /** + * Put Mappings + * @description Save per-field Procore source-path overrides ({kind: {field: path}}) on the connection. + */ + put: operations["put_mappings_connections__cid__mappings_put"]; post?: never; delete?: never; options?: never; @@ -1713,24 +1833,27 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawings/sheet.pdf": { + "/connections/{cid}/query": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Sheet Pdf */ - get: operations["sheet_pdf_projects__pid__drawings_sheet_pdf_get"]; + get?: never; put?: never; - post?: never; + /** + * Connection Query + * @description Run a read-only SELECT against a SQL connection (local / Postgres / Supabase). + */ + post: operations["connection_query_connections__cid__query_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/types": { + "/connections/{cid}/quickbooks/{entity}": { parameters: { query?: never; header?: never; @@ -1738,11 +1861,10 @@ export interface paths { cookie?: never; }; /** - * List Types - * @description Catalog of placeable types ("families") in the project's source IFC, for the place-family - * picker. Deduped by (class, name). + * Quickbooks Read + * @description Read the QuickBooks chart of accounts / vendors / bills (financial-backbone data plane). */ - get: operations["list_types_projects__pid__types_get"]; + get: operations["quickbooks_read_connections__cid__quickbooks__entity__get"]; put?: never; post?: never; delete?: never; @@ -1751,7 +1873,7 @@ export interface paths { patch?: never; trace?: never; }; - "/families/catalog": { + "/connections/{cid}/tables": { parameters: { query?: never; header?: never; @@ -1759,12 +1881,10 @@ export interface paths { cookie?: never; }; /** - * Family Catalog - * @description Starter IFC family library (furniture / sanitary / appliances / plants) you can add to any - * model — generated parametrically, so it's available even for a from-scratch massing model. Place - * one via the `add_family` edit recipe (POST /projects/{id}/edit, recipe='add_family'). + * Connection Tables + * @description List the connection's tables (SQL) or projects (Procore) — the data-plane browse entrypoint. */ - get: operations["family_catalog_families_catalog_get"]; + get: operations["connection_tables_connections__cid__tables_get"]; put?: never; post?: never; delete?: never; @@ -1773,7 +1893,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/families/import": { + "/connections/{cid}/test": { parameters: { query?: never; header?: never; @@ -1783,19 +1903,17 @@ export interface paths { get?: never; put?: never; /** - * Import Families - * @description Import external IFC **type content** (manufacturer / 3rd-party families) from an uploaded IFC - * into the project's source IFC, saving a new version. Imported types become placeable via the - * place-family picker (GET /projects/{id}/types). GUIDs of existing elements are preserved. + * Test Connection + * @description Test a saved connection (uses the stored secret) + return its info payload. */ - post: operations["import_families_projects__pid__families_import_post"]; + post: operations["test_connection_connections__cid__test_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/families/library": { + "/content/catalog": { parameters: { query?: never; header?: never; @@ -1803,12 +1921,12 @@ export interface paths { cookie?: never; }; /** - * Family Library - * @description The shippable IFC family library: the generated parametric catalog (grouped by category) plus - * any curated external `.ifc` files dropped in services/data/families/external. The generated - * `library.ifc` is real openBIM content that also imports into any project via /families/import. + * Content Catalog + * @description CONTENT-1: the curated content catalog — logistics / furniture / landscaping parts, each mapped to the + * right IFC class + phase + classification. Place an item via `POST /projects/{pid}/edit` with the + * `place_content` recipe ({category, point, verts?, faces?}). */ - get: operations["family_library_families_library_get"]; + get: operations["content_catalog_content_catalog_get"]; put?: never; post?: never; delete?: never; @@ -1817,28 +1935,27 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/families/place": { + "/contractor-statements/portfolio": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Place Family - * @description Place a library family into the project's source IFC (new GUID-stable occurrence, new version). - * Thin wrapper over the `add_family` authoring recipe. + * Contractor Statements Portfolio + * @description Company-wide contractor statements — the POC P&L and contract position summed across your jobs. */ - post: operations["place_family_projects__pid__families_place_post"]; + get: operations["contractor_statements_portfolio_contractor_statements_portfolio_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/edit": { + "/convert": { parameters: { query?: never; header?: never; @@ -1848,18 +1965,18 @@ export interface paths { get?: never; put?: never; /** - * Edit - * @description Apply an authoring recipe (set_pset | batch_tag | place_type) to the source IFC, - * saving a new version. GUIDs of existing elements are preserved. + * Convert + * @description Convert an uploaded proprietary model to Fragments. RVT via APS (paid) when configured; + * DWG/NWC require the paid APS/ODA bridge. Returns .frag bytes on success. */ - post: operations["edit_projects__pid__edit_post"]; + post: operations["convert_convert_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/edit-preview": { + "/convert/citygml": { parameters: { query?: never; header?: never; @@ -1869,41 +1986,38 @@ export interface paths { get?: never; put?: never; /** - * Edit Preview - * @description Author just this element into a one-element IFC + convert it to a small preview fragment (fast), - * so the viewer can show real geometry immediately while the full model republishes in the - * background. Fail-open: 503 when the source/converter is unavailable, so the client just keeps its - * optimistic proxy and waits for the normal publish. + * Convert Citygml + * @description CityGML (city/site context — the OGC standard behind 3D City Database / Cesium city tiles) → + * GeoJSON building footprints, which load as a GIS reference layer in the viewer. Fully offline. */ - post: operations["edit_preview_projects__pid__edit_preview_post"]; + post: operations["convert_citygml_convert_citygml_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/publish": { + "/convert/e57/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Publish - * @description Re-run the pipeline on the current source IFC (convert to .frag + reindex), off the - * request thread. Returns immediately; poll GET publish/status for completion. + * E57 Status + * @description Whether server-side E57 → .xyz point-cloud conversion is available (needs optional `pye57`). */ - post: operations["publish_projects__pid__publish_post"]; + get: operations["e57_status_convert_e57_status_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/source-ifc": { + "/convert/vim/inspect": { parameters: { query?: never; header?: never; @@ -1913,18 +2027,19 @@ export interface paths { get?: never; put?: never; /** - * Upload Source Ifc - * @description Upload a project's source IFC (enables authoring + republish). Saves a local copy - * the converter can read plus a durable copy in object storage, then publishes. + * Inspect Vim + * @description Inspect an uploaded VIM / G3D (Ara3D/VIM binary family) — schema/version, buffer inventory and + * geometry stats (vertex/index counts + bounding box) via a pure-Python BFAST reader. Fully offline; + * data-layer inspection (full VIM entity decode + viewer streaming are a follow-up). */ - post: operations["upload_source_ifc_projects__pid__source_ifc_post"]; + post: operations["inspect_vim_convert_vim_inspect_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/models": { + "/cost/datasets": { parameters: { query?: never; header?: never; @@ -1932,23 +2047,19 @@ export interface paths { cookie?: never; }; /** - * List Project Models - * @description Discipline models layered on the project beyond the primary source IFC (for federated clash). + * Cost Datasets + * @description Installed cost-database vintages + what the offline public importer can build (COST-DB). */ - get: operations["list_project_models_projects__pid__models_get"]; + get: operations["cost_datasets_cost_datasets_get"]; put?: never; - /** - * Add Project Model - * @description Append a discipline IFC (STR / MEP / ARCH …) so it can take part in federated clash. - */ - post: operations["add_project_model_projects__pid__models_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/raise-plan": { + "/cost/datasets/import": { parameters: { query?: never; header?: never; @@ -1958,20 +2069,20 @@ export interface paths { get?: never; put?: never; /** - * Raise Plan To Bim - * @description 2D -> BIM raise: turn an uploaded DXF floor plan into a real IFC4 model (walls extruded from - * the line-work, IfcSpaces from closed room polygons). `preview=true` just parses and returns the - * detected wall/room counts without writing anything. Otherwise the raised IFC is registered as a - * '2D Raise' discipline model (usable in the viewer + federated clash). 400 on an unreadable DXF. + * Cost Import + * @description Build (import) a cost vintage. `{"vintage": 2025 | "latest", "quarter": null, "source": "public"}`. + * Offline **public** importer only for now — a `"source": "cloud"` request warns and falls back to the + * public build (the massing.cloud importer is a later build-order step). Idempotent; sets it as latest. + * **Platform-admin only**: importing flips the global `is_latest`, repricing every unpinned project. */ - post: operations["raise_plan_to_bim_projects__pid__raise_plan_post"]; + post: operations["cost_import_cost_datasets_import_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/models/{mid}": { + "/cost/datasets/import-custom": { parameters: { query?: never; header?: never; @@ -1980,15 +2091,24 @@ export interface paths { }; get?: never; put?: never; - post?: never; - /** Delete Project Model */ - delete: operations["delete_project_model_projects__pid__models__mid__delete"]; + /** + * Cost Import Custom + * @description Import a firm's **own** cost book as a `custom`-origin vintage — so a project prices through the + * firm's historical/negotiated rates, not the shipped benchmark. Body: + * `{"vintage": 2025, "quarter": null, "name": "…", "rates": {"IfcWall": 180, …}}` (a flat class→rate map) + * or `{"rows": [{"ifc_class": "IfcWall", "total_cost": 180, "description": "…", "uom": "m2"}, …]}`. + * Re-importing the same (year, quarter) replaces that custom vintage in place. Sets it latest. + * **Platform-admin only**: a lone viewer must not be able to reprice every unpinned project's estimate + * (`is_latest` is a global flag — the audit's cross-project pricing-corruption scenario). + */ + post: operations["cost_import_custom_cost_datasets_import_custom_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/bridge/rvt/status": { + "/energy/benchmark-status": { parameters: { query?: never; header?: never; @@ -1996,11 +2116,11 @@ export interface paths { cookie?: never; }; /** - * Rvt Bridge Status - * @description Is the optional paid Revit (.rvt) → IFC bridge available? The UI checks this before offering - * the import, and shows the cost warning + the free IFC-export alternative. + * Benchmark Status + * @description Whether an external benchmarking sync (EPA Portfolio Manager) is configured; local EUI/trends + * work without it. */ - get: operations["rvt_bridge_status_bridge_rvt_status_get"]; + get: operations["benchmark_status_energy_benchmark_status_get"]; put?: never; post?: never; delete?: never; @@ -2009,7 +2129,7 @@ export interface paths { patch?: never; trace?: never; }; - "/interop/speckle/status": { + "/esign/status": { parameters: { query?: never; header?: never; @@ -2017,11 +2137,10 @@ export interface paths { cookie?: never; }; /** - * Speckle Status - * @description Is the optional (open-source, self-hostable) Speckle interoperability bridge configured? When - * on, this verifies live connectivity to the Speckle server. IFC/Fragments stay the source of truth. + * Esign Status + * @description Digital-signature capability: built-in PAdES (always available) + the optional 3rd-party bridge. */ - get: operations["speckle_status_interop_speckle_status_get"]; + get: operations["esign_status_esign_status_get"]; put?: never; post?: never; delete?: never; @@ -2030,7 +2149,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/interop/speckle/send": { + "/esign/webhook": { parameters: { query?: never; header?: never; @@ -2040,60 +2159,78 @@ export interface paths { get?: never; put?: never; /** - * Speckle Send - * @description Send the project's model/data to a Speckle stream (requires the bridge configured). + * Esign Webhook + * @description Receive a provider completion webhook (e.g. DocuSeal form.completed). + * + * Anonymous by necessity — the provider cannot hold a user credential — and the payload carries no + * authority: nothing here marks a contract signed, it only records a normalized completion for + * reconciliation. That design is what keeps a forged callback from executing a contract, and it is + * worth stating because the obvious fear is the wrong one. + * + * The real exposure was the audit table itself. This wrote a row per request with + * caller-controlled `submission_id`, `event` and `signer` of unbounded length, from the internet, + * unauthenticated and unthrottled. That is write access to the record the platform relies on to say + * who did what: flood it and real entries are buried in noise (and the table grows without limit), + * which is indistinguishable from tampering when someone later needs the trail. An audit log an + * anonymous caller can write to is not an audit log. + * + * So: verify an HMAC-SHA256 signature over the RAW body when a secret is configured, throttle per + * caller, and bound every stored string. When no secret is set the endpoint stays open, because + * silently dropping callbacks would break an operator who is relying on it today — but it is then + * only rate-limited and bounded, and `signature_verified` records which posture produced the row so + * an unverified entry can never be read as a verified one. */ - post: operations["speckle_send_projects__pid__interop_speckle_send_post"]; + post: operations["esign_webhook_esign_webhook_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/import/rvt": { + "/estimate/assemblies": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Import Rvt - * @description Import a native Revit .rvt by converting it to IFC via the paid APS bridge, then treat it like - * any source IFC (authoring / drawings / analysis / proforma all flow from it). Gated twice: the - * bridge must be configured (else 501 → use free IFC export), and the caller must `confirm_cost` - * (else 402) because Autodesk bills per conversion. + * Estimate Assemblies + * @description EST-ASSEMBLIES: the starter cost-assembly library — each a unit rate built up from labour / + * material / equipment component resources (pre-computed rate + component count). */ - post: operations["import_rvt_projects__pid__import_rvt_post"]; + get: operations["estimate_assemblies_estimate_assemblies_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/publish/status": { + "/estimate/assembly/price": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Publish Status - * @description Poll the async publish job: idle | running | done | error (+ detail). + * Estimate Assembly Price + * @description Build up an assembly's unit rate and extend it over a take-off quantity. Body: + * `{assembly_id | components:[{resource,kind,qty,unit,unit_cost,waste_pct?}], quantity?, overrides?}`. + * `overrides` maps a component `resource` → a new `unit_cost` (re-cost when a wage/price moves). */ - get: operations["publish_status_projects__pid__publish_status_get"]; - put?: never; - post?: never; + post: operations["estimate_assembly_price_estimate_assembly_price_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/modules": { + "/estimate/conceptual/catalog": { parameters: { query?: never; header?: never; @@ -2101,10 +2238,10 @@ export interface paths { cookie?: never; }; /** - * List Modules - * @description Module catalog (drives dynamic UI). Returns each module.json. + * Conceptual Catalog + * @description Building-type + region reference tables for the conceptual estimator. */ - get: operations["list_modules_modules_get"]; + get: operations["conceptual_catalog_estimate_conceptual_catalog_get"]; put?: never; post?: never; delete?: never; @@ -2113,49 +2250,48 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/sync/procore": { + "/estimate/labor/rates": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Sync Procore - * @description Import a Procore project's RFIs / submittals / change events into the matching modules - * (idempotent). Uses a saved Procore connection's token. Editor+ (it writes records). + * Labor Rates + * @description EST-1: the productivity-rate catalog (man-hours/unit by trade) + condition loading factors. */ - post: operations["sync_procore_projects__pid__sync_procore_post"]; + get: operations["labor_rates_estimate_labor_rates_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/sync/procore/push": { + "/estimate/resources/catalog": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Push Procore - * @description Two-way: push locally-resolved records (v1: RFI status + answer) back to Procore. Only - * records imported from Procore are pushed; idempotent. + * Resource Catalog + * @description The resource-based estimating reference: labor/material/equipment resources + assemblies + * (each with its built-up unit cost and L/M/E split) + the default IFC-class→assembly map. */ - post: operations["push_procore_projects__pid__sync_procore_push_post"]; + get: operations["resource_catalog_estimate_resources_catalog_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/sync/schedules": { + "/families/catalog": { parameters: { query?: never; header?: never; @@ -2163,99 +2299,111 @@ export interface paths { cookie?: never; }; /** - * List Schedules - * @description Auto-sync schedules for this project (Procore → modules on an interval). + * Family Catalog + * @description Starter IFC family library (furniture / sanitary / appliances / plants) you can add to any + * model — generated parametrically, so it's available even for a from-scratch massing model. Place + * one via the `add_family` edit recipe (POST /projects/{id}/edit, recipe='add_family'). */ - get: operations["list_schedules_projects__pid__sync_schedules_get"]; + get: operations["family_catalog_families_catalog_get"]; put?: never; - /** Create Schedule */ - post: operations["create_schedule_projects__pid__sync_schedules_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/sync/schedules/{sid}": { + "/families/coverage": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - /** Update Schedule */ - put: operations["update_schedule_projects__pid__sync_schedules__sid__put"]; + /** + * Family Coverage + * @description FAMILY-COMPLETE: can the installed shelf actually model a building of each kind? + * + * Pack and family counts say how much content there is, not whether it is *enough*. This checks the + * shelf against the IFC type classes each building system needs — structure, envelope, openings, + * circulation, HVAC, plumbing, electrical, fire and site for every building, plus what distinguishes + * a hospital from a warehouse — and names the missing classes when a system is short. + */ + get: operations["family_coverage_families_coverage_get"]; + put?: never; post?: never; - /** Delete Schedule */ - delete: operations["delete_schedule_projects__pid__sync_schedules__sid__delete"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/sync/schedules/{sid}/run-now": { + "/families/library": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Family Library + * @description The shippable IFC family library: the generated parametric catalog (grouped by category) plus + * any curated external `.ifc` files dropped in services/data/families/external. The generated + * `library.ifc` is real openBIM content that also imports into any project via /families/import. + */ + get: operations["family_library_families_library_get"]; put?: never; - /** Run Schedule Now */ - post: operations["run_schedule_now_projects__pid__sync_schedules__sid__run_now_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ai/draft-rfi": { + "/families/{key}/types": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Draft Rfi - * @description Draft an RFI (subject/question/discipline/priority) from a selected element's IFC context. - * Uses Claude when ANTHROPIC_API_KEY is set; otherwise returns a deterministic template draft. - * Reviewer+ (same gate as creating RFIs; avoids anonymous LLM-token burn). + * Family Types + * @description FAMILY-DEPTH — the named type catalog for one family: the curated sizes a firm actually places + * ("Desk 1600 × 800"), or the base dims as 'Standard'. Place one by passing `type_name` to the + * `add_family` recipe / the place route. 404 unknown family. */ - post: operations["draft_rfi_projects__pid__ai_draft_rfi_post"]; + get: operations["family_types_families__key__types_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ai/triage-rfi": { + "/fca/portfolio": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Triage Rfi - * @description Triage an RFI — auto-categorize (discipline / category / urgency), name the ball-in-court party, - * and draft a response. Pass `rid` to triage an existing RFI record, or `rfi` data directly. Uses - * Claude when configured; a deterministic template otherwise. + * Fca Portfolio + * @description Facility Condition Index per project across the portfolio, worst-first — the capital- + * prioritization view (fund the highest-FCI buildings first). Scoped to the caller's projects. */ - post: operations["triage_rfi_projects__pid__ai_triage_rfi_post"]; + get: operations["fca_portfolio_fca_portfolio_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/my-work": { + "/firm/rules": { parameters: { query?: never; header?: never; @@ -2263,11 +2411,30 @@ export interface paths { cookie?: never; }; /** - * My Work - * @description Cross-module work queue for the current user (assigned + ball-in-court). + * Firm Rules Get + * @description R27-FIRM-MEMORY — the firm's standard rules, the ones that do not change per project. + * + * Not project-scoped, deliberately: a firm's standards are precisely what survives a job. Today they + * get re-authored per project, which turns one standard into forty slightly different ones with no + * way to say which is the standard. */ - get: operations["my_work_projects__pid__my_work_get"]; - put?: never; + get: operations["firm_rules_get_firm_rules_get"]; + /** + * Firm Rules Put + * @description Replace the firm library. **Platform**-admin only, not project-admin. + * + * This route was gated `require_role("admin")`, which on a path with no `{pid}` turns `pid` into a + * caller-supplied QUERY parameter — so the caller picked which project's role was checked. Any user + * could register, create a throwaway project they own, and `PUT /firm/rules?pid=`; + * verified returning `200 {"saved": 1}`. Since this replaces the library, it also deleted the firm's + * real standards, leaving every project's rule-check to pass against nothing. + * + * A project editor may override a firm standard on their + * own job (that is legitimate and stays visible), but changing what the firm stands for is not a + * per-project act — which is exactly what the old gate made it. Validated by the same QUERY-DSL check a project save uses: a rule invalid on a + * project must not be accepted merely because it was authored at the firm level. + */ + put: operations["firm_rules_put_firm_rules_put"]; post?: never; delete?: never; options?: never; @@ -2275,27 +2442,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/notifications": { + "/generate/massing/preview": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Notifications - * @description Recent activity relevant to the caller (assigned / ball-in-court), newest first. + * Preview Massing + * @description Compute the program + proforma WITHOUT writing an IFC or touching a project — for the + * 'what would this lot yield?' form before committing to a model. Stateless, instant. */ - get: operations["notifications_projects__pid__notifications_get"]; - put?: never; - post?: never; + post: operations["preview_massing_generate_massing_preview_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/due-feed": { + "/health": { parameters: { query?: never; header?: never; @@ -2303,10 +2471,10 @@ export interface paths { cookie?: never; }; /** - * Due Feed - * @description Cross-module SLA feed — open records past or near their due date (overdue / due-soon). + * Health + * @description Liveness — the process is up and serving. Cheap, no dependencies; for restart probes. */ - get: operations["due_feed_projects__pid__due_feed_get"]; + get: operations["health_health_get"]; put?: never; post?: never; delete?: never; @@ -2315,27 +2483,27 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/notifications/digest/preview": { + "/ids/build": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Digest Preview - * @description Preview the per-member digests (no send) — also reports whether SMTP is configured. + * Build Ids + * @description Build a standards-valid IDS 1.0 XML from a use case or explicit specs → downloadable .ids file. */ - get: operations["digest_preview_projects__pid__notifications_digest_preview_get"]; - put?: never; - post?: never; + post: operations["build_ids_ids_build_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/notifications/digest": { + "/ids/eir": { parameters: { query?: never; header?: never; @@ -2345,19 +2513,21 @@ export interface paths { get?: never; put?: never; /** - * Send Digest - * @description Send each member with open items a work-queue digest email. No-op-but-logged per - * recipient when SMTP is unconfigured (status 'disabled'); members without an email are - * skipped. Returns a per-recipient result summary. + * Build Eir + * @description Generate an Exchange Information Requirements (EIR) markdown document for the BIM contract. + * + * An unknown use case is a 422 here because `eir_for_use_case` now raises `ValueError` like its + * sibling. It raised a bare `KeyError` until this route started calling it — so the consolidation + * had to fix that first, or it would have swapped a working 422 for a 500. */ - post: operations["send_digest_projects__pid__notifications_digest_post"]; + post: operations["build_eir_ids_eir_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/notifications/stream": { + "/ids/templates": { parameters: { query?: never; header?: never; @@ -2365,12 +2535,10 @@ export interface paths { cookie?: never; }; /** - * Notifications Stream - * @description Server-sent events: pushes the notification feed to the client and re-pushes when - * the relevant activity count changes (polled server-side every few seconds). Uses a - * fresh DB session per poll since the generator outlives the request scope. + * Ids Templates + * @description The authoring catalog: element requirement templates + use-case bundles. */ - get: operations["notifications_stream_projects__pid__notifications_stream_get"]; + get: operations["ids_templates_ids_templates_get"]; put?: never; post?: never; delete?: never; @@ -2379,25 +2547,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/views": { + "/interop/speckle/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Views */ - get: operations["list_views_projects__pid__modules__key__views_get"]; + /** + * Speckle Status + * @description Is the optional (open-source, self-hostable) Speckle interoperability bridge configured? When + * on, this verifies live connectivity to the Speckle server. IFC/Fragments stay the source of truth. + */ + get: operations["speckle_status_interop_speckle_status_get"]; put?: never; - /** Save View */ - post: operations["save_view_projects__pid__modules__key__views_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/views/alerts": { + "/jurisdiction/packs": { parameters: { query?: never; header?: never; @@ -2405,19 +2576,33 @@ export interface paths { cookie?: never; }; /** - * View Alerts - * @description Saved-search alert feed: each of my saved views with its total + new-since-last-seen counts. + * List Packs + * @description Every data-requirement pack available, optionally filtered to one jurisdiction. + * + * The built-in `example` pack asserts nothing about any real place — it is there to show the shape. + * Real packs are imported by someone who can point at the document they came from, which is why + * `authority`, `edition` and `source` are required to store one. */ - get: operations["view_alerts_projects__pid__views_alerts_get"]; + get: operations["list_packs_jurisdiction_packs_get"]; put?: never; - post?: never; + /** + * Import Pack + * @description Import a data-requirement pack from an authority. + * + * Refused without `authority`, `edition` and `source`. That is not bureaucracy: a requirement + * nobody can trace is indistinguishable from one somebody made up, and this pack will be used to + * fail other people's models. Selectors are validated with the same parser that will evaluate + * them, so a requirement cannot store cleanly and then silently never match — which reads as a + * pass. + */ + post: operations["import_pack_jurisdiction_packs_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/views/{vid}/seen": { + "/jurisdiction/packs/{pack_id}": { parameters: { query?: never; header?: never; @@ -2426,18 +2611,36 @@ export interface paths { }; get?: never; put?: never; + post?: never; + /** Delete Pack */ + delete: operations["delete_pack_jurisdiction_packs__pack_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/license": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; /** - * Mark View Seen - * @description Mark a saved view as seen now — clears its 'new' alert count. + * License State + * @description The Massing licence state — plan tier, per-tier feature entitlements, and whether a valid key + * is recorded (key is masked, never returned in full). Drives the Settings licence panel. */ - post: operations["mark_view_seen_projects__pid__modules__key__views__vid__seen_post"]; + get: operations["license_state_license_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/views/{vid}": { + "/license/cloud-check": { parameters: { query?: never; header?: never; @@ -2446,15 +2649,20 @@ export interface paths { }; get?: never; put?: never; - post?: never; - /** Delete View */ - delete: operations["delete_view_projects__pid__modules__key__views__vid__delete"]; + /** + * License Cloud Check + * @description CLOUD-BRIDGE: validate the recorded licence key against massing.cloud (when online validation is + * enabled + the shared secret is configured) and apply the returned plan. Admin-only. Offline / not + * configured → a no-op result (the locally recorded tier is untouched). The secret is never returned. + */ + post: operations["license_cloud_check_license_cloud_check_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/enum-options": { + "/licenses/mine": { parameters: { query?: never; header?: never; @@ -2462,10 +2670,10 @@ export interface paths { cookie?: never; }; /** - * List Enum Options - * @description E1 — project-level custom select options, nested {module: {field: [values]}}. + * My Licenses + * @description The caller's OWN verified licences — what the seal dialog offers, instead of a free-text field. */ - get: operations["list_enum_options_projects__pid__enum_options_get"]; + get: operations["my_licenses_licenses_mine_get"]; put?: never; post?: never; delete?: never; @@ -2474,27 +2682,27 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/enum/{field}": { + "/lifecycle/reference": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Add Enum Option - * @description E1 — add a custom option to a module field's select enum (no JSON edit). + * Reference + * @description The canonical RIBA↔AIA phase definitions + soft-cost taxonomy (for the UI, no project needed). */ - post: operations["add_enum_option_projects__pid__modules__key__enum__field__post"]; + get: operations["reference_lifecycle_reference_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/search": { + "/market/snapshot": { parameters: { query?: never; header?: never; @@ -2502,10 +2710,10 @@ export interface paths { cookie?: never; }; /** - * Search - * @description Cross-module full-text search (ref / title / field data). + * Market Snapshot + * @description The market table — regions (escalation / labour / location index) + the warm/cold sector board. */ - get: operations["search_projects__pid__search_get"]; + get: operations["market_snapshot_market_snapshot_get"]; put?: never; post?: never; delete?: never; @@ -2514,7 +2722,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/bulk": { + "/massing/optioneer": { parameters: { query?: never; header?: never; @@ -2524,43 +2732,56 @@ export interface paths { get?: never; put?: never; /** - * Bulk Action - * @description Apply transition / assign / delete to many records at once. + * Massing Optioneer + * @description MASSING-OPT (R16) — the layout optioneer: sweep the massing levers (floor-to-floor, core + * efficiency, coverage strategy, unit size) over the zoning envelope, score each option for developer + * yield, and return the ranked set + a Pareto (cost vs. profit) frontier. Deterministic + stateless — + * no IFC is written (author the winner via the recipe chain); feeds a 🧮-style comparison panel. */ - post: operations["bulk_action_projects__pid__modules__key__bulk_post"]; + post: operations["massing_optioneer_massing_optioneer_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}": { + "/massing/optioneer/recipes": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Records */ - get: operations["list_records_projects__pid__modules__key__get"]; + get?: never; put?: never; - /** Create Record */ - post: operations["create_record_projects__pid__modules__key__post"]; + /** + * Massing Option Recipes + * @description MASSING-OPT phase 2 — emit ONE ranked option as the executable authoring chain: the blank-model + * bootstrap (levels at the option's floor-to-floor) + a GUID-stable edit-recipe step list (slab + + * perimeter walls + core box per storey) ready for POST /projects/{pid}/edit/batch. The optioneer is + * re-run server-side (deterministic), so the emitted chain is authoritative for the given envelope + + * levers; unknown option id → 404. + */ + post: operations["massing_option_recipes_massing_optioneer_recipes_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/export.csv": { + "/mcp/tools": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Export Csv */ - get: operations["export_csv_projects__pid__modules__key__export_csv_get"]; + /** + * Mcp Tool Catalog + * @description The tool catalog the MCP server exposes to external AI agents (name, description, input + * schema). The stdio server (services/api/mcp_server.py) drives these against a project. + */ + get: operations["mcp_tool_catalog_mcp_tools_get"]; put?: never; post?: never; delete?: never; @@ -2569,7 +2790,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/import-template.csv": { + "/metrics": { parameters: { query?: never; header?: never; @@ -2577,10 +2798,16 @@ export interface paths { cookie?: never; }; /** - * Import Template - * @description A header-only CSV of the module's importable fields — fill it in and re-upload to bulk-import. + * Prometheus Metrics + * @description Prometheus text exposition (request counts, latencies, in-flight, uptime). Open by default; + * gate behind the AEC_API_KEY bearer by setting AEC_METRICS_AUTH=1. + * + * JOB-STALL-VISIBLE appends the queue gauges. The DB read is deliberately wrapped: a scrape that + * 500s because the database blinked loses the request metrics too, which are in-process and were + * perfectly fine. On failure the queue block reports `aec_jobs_stats_ok 0` and omits the rest — + * that is why the ok-gauge exists, so "could not measure" never renders as "nothing queued". */ - get: operations["import_template_projects__pid__modules__key__import_template_csv_get"]; + get: operations["prometheus_metrics_metrics_get"]; put?: never; post?: never; delete?: never; @@ -2589,28 +2816,50 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/import/preview": { + "/metrics/budget": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Import Preview - * @description Step 1 of a generic Excel/CSV import: parse the sheet, auto-suggest a column->field mapping, - * coerce a sample, and flag unmapped required fields. No records are created. + * Perf Budget Report + * @description R24-PERF-BUDGET — the stated performance budgets, and which of them anything measures. + * + * Three budgets are stated: request p95 < 100 ms, click echo < 100 ms, panel load < 1 s. As of + * v0.3.1063 **two of the three are measured**: the first from the live server histogram, and + * `click_echo` by browser beacon into `metrics.observe_client` — nothing here can see the interval + * between a click and the paint answering it. `panel_load` is still `unmeasured`, and its reason + * changed rather than vanished: the beacon it was waiting for exists, but this app has no single + * moment where a panel becomes usable, so there is nothing honest to time yet. + * + * Any budget still lacking a measurement is returned as `unmeasured` **with the reason**, and a + * measurable one with nothing reported comes back `no_observations`, never omitted. A report that + * lists three budgets and quietly evaluates one is how a green result comes to imply more than was + * tested; a budget that vanishes when its beacon breaks is the same failure arriving later. + * + * The p95 is a histogram bucket UPPER bound, not an interpolated point — and a None quantile has + * two opposite causes: no observations, or a tail beyond the largest bucket. The second is a + * FAILURE, because reading None as "no problem" would make this pass hardest exactly when latency + * is worst. That rule is applied to the client budgets by the SAME code, not a gentler copy. + * + * **`within_budget` is now an AND across every MEASURED budget**, so a slow client fails it. + * Whoever watches this should know the client figure is per-process and survivor-weighted — a + * browser that hung hard enough never to beacon is absent from it. + * + * Same gate as /metrics: open by default, behind the bearer with AEC_METRICS_AUTH=1. */ - post: operations["import_preview_projects__pid__modules__key__import_preview_post"]; + get: operations["perf_budget_report_metrics_budget_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/import": { + "/metrics/client": { parameters: { query?: never; header?: never; @@ -2620,30 +2869,52 @@ export interface paths { get?: never; put?: never; /** - * Import Records - * @description Step 2: import the sheet using a column->field mapping (JSON {source_header: field_name}). - * Validates required fields + coerces types per row; one bad row never aborts the batch. + * Record Client Interval + * @description R24-PERF-BUDGET — one client-side interval, reported by the browser beacon. + * + * The two client budgets (click echo, panel load) describe things only a browser can see, so the + * only way to measure them is to let the browser say. This is the sink; the beacon is + * `apps/web/src/ui/perfBeacon.ts`. + * + * **Not behind `_guard_metrics`.** That gate protects READING the metrics surface and is satisfied + * by an operator bearer no browser has. Writing needs the opposite test — is this one of our + * signed-in users — so it takes `require_identified`. Leaving it open would hand anyone on the + * internet a way to shift a percentile an operator makes decisions from: not a data breach, a way + * to make the instrument lie, which is harder to notice. + * + * **`require_identified`, not `Depends(current_user)`.** `current_user` IDENTIFIES and does not + * AUTHORISE — with RBAC on it returns the literal string "anonymous", so depending on it is a name + * rather than a gate. Routes have shipped with exactly that mistake twice, and the first draft of + * this one made it a third: it took `current_user` and hand-rolled the anonymous check in the body, + * which works but is invisible to the static walker in `test_global_authz` and would have gone into + * the baseline as an unguarded global route. + * + * **The budget name is matched against `BUDGETS`, never used as a key directly.** It arrives from a + * browser, and `observe_client` would happily create a histogram for any string it is given — so an + * unvalidated name is an unbounded, caller-controlled dict of series in a long-lived process. + * + * Out-of-range and non-numeric values are DROPPED rather than clamped to the boundary, and the + * response says so. Clamping a hostile 9,999 s to 10 s would file it in the slowest real bucket and + * quietly move the p95; dropping it leaves the percentile describing only intervals a browser could + * actually have produced. The same reasoning as the load-timing sink, which clamps because its rows + * are read individually — here they are only ever read as an aggregate. */ - post: operations["import_records_projects__pid__modules__key__import_post"]; + post: operations["record_client_interval_metrics_client_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/log.pdf": { + "/module-attachments/{att_id}/download": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Module Log - * @description Printable register (log) of every record in a module — the RFI log, submittal log, - * change-order log, etc., all from the same engine. - */ - get: operations["module_log_projects__pid__modules__key__log_pdf_get"]; + /** Download Attachment */ + get: operations["download_attachment_module_attachments__att_id__download_get"]; put?: never; post?: never; delete?: never; @@ -2652,7 +2923,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/board": { + "/modules": { parameters: { query?: never; header?: never; @@ -2660,10 +2931,10 @@ export interface paths { cookie?: never; }; /** - * Module Board - * @description Records grouped by workflow state — kanban board. + * List Modules + * @description Module catalog (drives dynamic UI). Returns each module.json. */ - get: operations["module_board_projects__pid__modules__key__board_get"]; + get: operations["list_modules_modules_get"]; put?: never; post?: never; delete?: never; @@ -2672,34 +2943,52 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}": { + "/modules/graph": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get Record */ - get: operations["get_record_projects__pid__modules__key___rid__get"]; - put?: never; - post?: never; /** - * Delete Record - * @description Delete a record (editor+). Removes its activity/comments too. + * Modules Graph + * @description The module-relations graph: one node per module, one edge per cross-module link (reference + + * rollup fields). Optionally scope to a `workspace` (keeps its modules + the targets they reference). + * Drives a node-canvas relations view — see how the ~180 config modules actually wire together. */ - delete: operations["delete_record_projects__pid__modules__key___rid__delete"]; + get: operations["modules_graph_modules_graph_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; + patch?: never; + trace?: never; + }; + "/openbim/capabilities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; /** - * Update Record - * @description Partial-update a record. Pass ?expected_modified_at= to opt into the - * optimistic lock: a concurrent edit returns 409 (with the current modified_at) instead of a silent - * overwrite. + * Openbim Capabilities + * @description The openBIM standards + version matrix this platform speaks — for each standard (IFC, BCF, IDS, + * bSDD, COBie, ISO 19650 CDE), which versions we can read and write. Derived from the live engines + * (BCF versions, IFC schemas), so it never drifts from what's actually implemented; a consumer/agent + * can ask 'do you read BCF 3.0?' without guessing. */ - patch: operations["update_record_projects__pid__modules__key___rid__patch"]; + get: operations["openbim_capabilities_openbim_capabilities_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/related": { + "/opendata/permit-cities": { parameters: { query?: never; header?: never; @@ -2707,10 +2996,10 @@ export interface paths { cookie?: never; }; /** - * Related Records - * @description Outgoing references + incoming records that point at this one. + * Permit Cities + * @description Cities whose building-permit open data we can read, and whether radius search is supported. */ - get: operations["related_records_projects__pid__modules__key___rid__related_get"]; + get: operations["permit_cities_opendata_permit_cities_get"]; put?: never; post?: never; delete?: never; @@ -2719,7 +3008,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/revise": { + "/parcels/analyze": { parameters: { query?: never; header?: never; @@ -2729,34 +3018,40 @@ export interface paths { get?: never; put?: never; /** - * Revise Record - * @description Create a tracked revision of a record (revisable modules only); re-opens the workflow. + * Analyze + * @description PARCEL-IMPORT — cadastral parcel geometry ingest (upload-driven GeoJSON/WKT, no gov scraping) → + * area / perimeter / centroid / bbox, and — with `zoning` + `proposal` — FAR / lot-coverage / height + * compliance with per-axis slack. Body: `{geojson?|wkt?, parcel_id?, zoning?: {max_far, max_coverage, + * max_height_m}, proposal?: {gfa_m2, footprint_m2, height_m}}`. Bad boundary → 422. */ - post: operations["revise_record_projects__pid__modules__key___rid__revise_post"]; + post: operations["analyze_parcels_analyze_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/transition": { + "/parcels/data-status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Data Status + * @description Whether a nationwide parcel/comps data provider is connected (else screening uses your parcels). + */ + get: operations["data_status_parcels_data_status_get"]; put?: never; - /** Transition */ - post: operations["transition_projects__pid__modules__key___rid__transition_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/link": { + "/parcels/screen": { parameters: { query?: never; header?: never; @@ -2765,32 +3060,40 @@ export interface paths { }; get?: never; put?: never; - /** Link Record */ - post: operations["link_record_projects__pid__modules__key___rid__link_post"]; + /** + * Screen + * @description Filter + rank a parcel set by size/zoning/flood/utilities, each with a max-buildable envelope + + * conceptual cost (screen → envelope → proforma). Body: {parcels:[...], criteria:{...}}. + */ + post: operations["screen_parcels_screen_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/comments": { + "/payments/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Payments Status + * @description Whether payment disbursement is configured. Off by default — Massing never moves money itself; + * it tracks the pay-app/lien-waiver workflow and gates release on waiver coverage. + */ + get: operations["payments_status_payments_status_get"]; put?: never; - /** Add Comment */ - post: operations["add_comment_projects__pid__modules__key___rid__comments_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/assign": { + "/pdf/extract": { parameters: { query?: never; header?: never; @@ -2800,17 +3103,17 @@ export interface paths { get?: never; put?: never; /** - * Assign Record - * @description Set (or clear) the record's assignee — drives the cross-module work queue. + * Pdf Extract + * @description A new PDF of just the given pages (`pages` = '1,3,5-7', 1-based). */ - post: operations["assign_record_projects__pid__modules__key___rid__assign_post"]; + post: operations["pdf_extract_pdf_extract_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/elements": { + "/pdf/info": { parameters: { query?: never; header?: never; @@ -2820,18 +3123,23 @@ export interface paths { get?: never; put?: never; /** - * Tag Elements - * @description Tie model elements (IFC GlobalIds) to a record (mode: add | remove | set). For a schedule - * activity this hard-ties the exact elements it builds, so the 4D scrub is precise (not trade-based). + * Pdf Info + * @description Page count + flags for an uploaded PDF, including its pre-ingest sanity report. + * + * `sanity` is where the active content `_read_pdf` declines to refuse becomes visible: this is the + * route whose whole job is to describe a PDF, so it is the one that should say the file carries + * JavaScript or an embedded file. Returned in the JSON body rather than a response header — the + * client would then have to be added to `expose_headers`, and `test_cors_expose_headers.py` exists + * because that coupling is invisible until it bites cross-origin in a browser. */ - post: operations["tag_elements_projects__pid__modules__key___rid__elements_post"]; + post: operations["pdf_info_pdf_info_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/attachments": { + "/pdf/merge": { parameters: { query?: never; header?: never; @@ -2841,17 +3149,25 @@ export interface paths { get?: never; put?: never; /** - * Upload Attachment - * @description Attach a file to a record (stored in object storage / MinIO). + * Pdf Merge + * @description Concatenate several uploaded PDFs into one (order = upload order). + * + * Bounded by COUNT and by RUNNING TOTAL, not only per file. `_read_pdf` caps each upload at + * `_PDF_MAX_MB`, and on its own that is a per-file cap a merge multiplies: twenty files each just + * under the limit are twenty acceptances. `bodycap.MaxBodySizeMiddleware` already bounds the whole + * request (measured, not declared, so a chunked upload cannot skip it), which is why this is a + * smaller hole than it looks — but its own docstring records the half it does not close: *"it does + * not stop a handler materialising the body it did receive"*. Every byte here is held at once and + * handed to pypdf, so the merge is where that back half bites hardest. Raised in review on #374. */ - post: operations["upload_attachment_projects__pid__modules__key___rid__attachments_post"]; + post: operations["pdf_merge_pdf_merge_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/attachments/bulk": { + "/pdf/rotate": { parameters: { query?: never; header?: never; @@ -2861,40 +3177,59 @@ export interface paths { get?: never; put?: never; /** - * Upload Attachments Bulk - * @description Attach **many** files at once — the field reality (a super dumps a batch of site photos rather - * than uploading them one by one). Each is stored like a single upload; returns all created + a count. + * Pdf Rotate + * @description Rotate pages by `angle` (multiple of 90). `pages` (1-based, '1,3-5') limits it; blank = all. */ - post: operations["upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post"]; + post: operations["pdf_rotate_pdf_rotate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/bcf/export": { + "/pdf/seal": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Export Module Bcf - * @description Export a module's records as a BCF .bcfzip (coordination issues round-trip with Solibri / ACC / - * BIMcollab). Pinned / element-tied records carry a viewpoint (components + camera). `version` = 2.1 - * (default) or 3.0. + * Pdf Seal + * @description Render a *visible* professional seal + signature block, then apply a tamper-evident PAdES + * signature LAST (unless `sign=false`). The self-signed platform cert is demonstration / + * tamper-evidence, not board-accepted sealing. + * + * **A seal is not an authorisation, it is a personal legal attestation** that a named licensed human + * was in responsible charge of the work. Responsible charge cannot be delegated to software, and a + * licensee whose seal is applied to work they did not supervise is the specific thing state boards + * discipline. So this route deliberately asks more than "is this request authenticated?": + * + * 1. `step_up` — a fresh, single-action assertion from `POST /auth/step-up` that the account + * password was just re-proved. A bearer token cannot answer this: any process holding one can + * replay it, so an automation driving this API with a user's token would emit documents under + * that user's seal and the audit row would faithfully record a human act that never happened. + * 2. The `api-key` identity is refused outright — a machine credential has no person behind it. + * 3. `license_id` selects one of the CALLER'S OWN verified licences (`GET /licenses/mine`) and the + * seal text is built server-side from that row. An expired licence refuses with 409. + * + * History, because the shape recurs: this route originally guarded with `Depends(current_user)`, + * which identifies without authorising — an anonymous caller could upload any PDF and receive it + * bearing a rendered PE or RA seal with a name and licence number of their choosing, verified end to + * end. Gating it fixed anonymity but not impersonation, because `profile` was still free text; and + * binding to the account would still not have fixed automation, because a token is not a person. + * Each fix looked complete and left the next layer open. */ - get: operations["export_module_bcf_projects__pid__modules__key__bcf_export_get"]; - put?: never; - post?: never; + post: operations["pdf_seal_pdf_seal_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/bcf/import": { + "/pdf/split": { parameters: { query?: never; header?: never; @@ -2904,52 +3239,69 @@ export interface paths { get?: never; put?: never; /** - * Import Module Bcf - * @description Import a BCF .bcfzip from another BIM tool as records in this module (each topic → a record, - * carrying its pinned components + camera). Returns the count created. + * Pdf Split + * @description Split an uploaded PDF into one PDF per page, returned as a .zip. */ - post: operations["import_module_bcf_projects__pid__modules__key__bcf_import_post"]; + post: operations["pdf_split_pdf_split_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/module-attachments/{att_id}/download": { + "/pdf/stamp": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Download Attachment */ - get: operations["download_attachment_module_attachments__att_id__download_get"]; + get?: never; put?: never; - post?: never; + /** + * Pdf Stamp + * @description Composite a review / inspection / status stamp onto a page (1-based). (x,y) = top-left of the + * stamp in PDF points from the page's top-left. `values` = JSON object of field values. + */ + post: operations["pdf_stamp_pdf_stamp_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/pdf": { + "/pipeline/allocate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Record Pdf */ - get: operations["record_pdf_projects__pid__modules__key___rid__pdf_get"]; + get?: never; put?: never; - post?: never; + /** + * Pipeline Allocate Route + * @description R31-PIPELINE-ALLOCATE: given candidate projects with a cost and a value, and a capital + * constraint, **which subset** — an exact integer optimum, not a ranking. + * + * The route above ranks projects worst-first and advises funding those first. That is a greedy + * heuristic, and greedy loses whenever one high-ratio project crowds out two smaller ones that + * together beat it. This returns the optimal set, what it displaced, and what greedy would have + * chosen, so the difference between the two decisions is visible rather than asserted. + * + * Body: `{candidates: [{id, name?, cost, value}], capital: number}`. **`value` is supplied by the + * caller** — NPV, profit, risk-adjusted return, whatever the committee is maximising. This endpoint + * does not infer it from project data: a value nobody stated is the one number that must not be + * invented here, since it decides what gets funded. + */ + post: operations["pipeline_allocate_route_pipeline_allocate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/module-pins": { + "/pipeline/funnel": { parameters: { query?: never; header?: never; @@ -2957,10 +3309,26 @@ export interface paths { cookie?: never; }; /** - * Module Pins - * @description Every anchored GC record across pinnable modules — for the 3D viewer overlay. + * Pipeline Funnel + * @description R22-PIPELINE: the acquisition funnel across the caller's projects — stage counts and value, + * conversion, probability-weighted value, cycle time and the data-quality tells. + * + * Acquisition is a funnel, not a project. `/portfolio/executive` answers "how are the deals we + * *won* doing?"; this answers the question upstream of it — what is in the book, how much of it + * historically closes, and how long it takes. + * + * **The weighted value is derived from this firm's own closed deals, never from a textbook + * ladder.** A stage without enough closed history reports `insufficient_history` and its deals are + * excluded from the headline **and counted**, with `coverage` stating what fraction of the book the + * number actually covers. That is the whole point: a weighted pipeline figure is a guess about + * conversion multiplied by real money and read as a forecast, so a partial number that says it is + * partial beats a complete one that quietly used somebody else's conversion rates. + * + * Cycle time is reported as closed-deal duration *beside* open-deal age and never blended — the + * deals still open are the slow ones, so a closed-only mean understates exactly when it is being + * used to promise a closing date. */ - get: operations["module_pins_projects__pid__module_pins_get"]; + get: operations["pipeline_funnel_pipeline_funnel_get"]; put?: never; post?: never; delete?: never; @@ -2969,15 +3337,20 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cost/g703": { + "/plugins": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** G703 */ - get: operations["g703_projects__pid__cost_g703_get"]; + /** + * Plugins Status + * @description The plugin registry: host API version, whether discovery is enabled (`AEC_PLUGINS_ENABLED=1`), + * every loaded plugin with its namespaced recipes, and every refusal WITH its reason — a half-loaded + * plugin set is visible, never silent. + */ + get: operations["plugins_status_plugins_get"]; put?: never; post?: never; delete?: never; @@ -2986,24 +3359,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cost/g702": { + "/plugins/reload": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** G702 */ - get: operations["g702_projects__pid__cost_g702_get"]; + get?: never; put?: never; - post?: never; + /** + * Plugins Reload + * @description Re-discover + reload all plugins (idempotent — previous registrations are replaced). Platform + * operation: plugins execute Python at load and their recipes become available to every project. + */ + post: operations["plugins_reload_plugins_reload_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/px-summary": { + "/portfolio/construction": { parameters: { query?: never; header?: never; @@ -3011,12 +3388,11 @@ export interface paths { cookie?: never; }; /** - * Px Summary - * @description The project-executive health view: on-schedule (SPI, % complete, critical path, lookahead, - * milestones) next to on-budget (GMP, EAC, variance-at-completion, buyout, cash flow), with an - * overall status. The single 'are we on schedule and on budget' answer. + * Construction Portfolio + * @description Owner / program view: construction health across all projects — cost over/under (flags + * forecast overruns), open risks + cost exposure, recordable incidents, open RFIs. */ - get: operations["px_summary_projects__pid__px_summary_get"]; + get: operations["construction_portfolio_portfolio_construction_get"]; put?: never; post?: never; delete?: never; @@ -3025,7 +3401,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cost/g702.pdf": { + "/portfolio/deal-memory": { parameters: { query?: never; header?: never; @@ -3033,11 +3409,25 @@ export interface paths { cookie?: never; }; /** - * G702 Pdf - * @description The owner pay application as a signable PDF — G702 certificate + G703 continuation sheet, - * drawn from the budget-seeded Schedule of Values. + * Portfolio Deal Memory + * @description R35-DEAL-MEMORY: this operator's own closed projects as a comp set, scoped to their projects. + * + * The least commoditised layer in an underwriting stack, because it is made of the firm's own + * history and cannot be bought. It is also the easiest place here to manufacture a flattering + * number, so the engine reports only measurements that sit in a **different record** from the plan + * they are compared against — cost budget vs actual, planned finish vs actual finish. + * + * **`exit_cap_achieved` and `lease_up_months` are returned as `no_recorded_source`, not computed.** + * Nothing in this system records a realised exit cap or a lease-up actual; the only exit cap + * present is the ASSUMED one on a scenario. Reading that back as the realised value would compare + * a number to itself and report perfect underwriting accuracy forever — and that figure would then + * be used to justify the next deal. The refusal names what would have to be captured instead. + * + * Under the sample floor a metric reports `insufficient_history` rather than a distribution, and a + * project with no actual spend is excluded and counted rather than averaged in as zero variance, + * which would drag every distribution toward "we were exactly right". */ - get: operations["g702_pdf_projects__pid__cost_g702_pdf_get"]; + get: operations["portfolio_deal_memory_portfolio_deal_memory_get"]; put?: never; post?: never; delete?: never; @@ -3046,58 +3436,90 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cost/pay-app/invoice": { + "/portfolio/executive": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Payapp Invoice - * @description Create an owner-invoice record from the current pay application — amount = G702 current payment - * due — so each draw produces its owner invoice, linked to the prime contract. Closes the loop: - * budget → SOV → G702/G703 → owner invoice. + * Executive Portfolio + * @description Cross-project executive roll-up — every project's on-schedule (SPI, % complete, lookahead, + * late milestones) next to on-budget (GMP, EAC, variance-at-completion) with its overall status, + * plus portfolio totals and a status tally. The 'how's the whole book doing?' view, built on the + * same px-summary each project's dashboard shows. */ - post: operations["payapp_invoice_projects__pid__cost_pay_app_invoice_post"]; + get: operations["executive_portfolio_portfolio_executive_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/advance-period": { + "/portfolio/prioritization": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Advance Period - * @description Close the current pay period (C1) — roll each SOV line's completed-this into completed-previous - * so the next pay application starts a fresh period. + * Portfolio Prioritization + * @description Ranked portfolio prioritization — scores each accessible project 0–100 on return / on-budget / + * on-schedule / delivery-risk and ranks them, reusing the executive-portfolio rows (and its + * membership scoping). Weights default to a return-leaning blend. */ - post: operations["advance_period_projects__pid__cost_advance_period_post"]; + get: operations["portfolio_prioritization_portfolio_prioritization_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/summary": { + "/portfolio/resourcing": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Summary */ - get: operations["summary_projects__pid__cost_summary_get"]; + /** + * Portfolio Resourcing + * @description R22-PIPELINE — weekly resource demand per trade, summed **across** projects. + * + * `/projects/{pid}/schedule/resource-loading` answers one project, and a trade committed to three + * jobs in the same week looks comfortable on every one of them. This sums concurrent demand over + * the book, so `?cap=` flags the weeks where a single trade is over-committed **across** projects + * and names which projects are competing for it. + * + * **"By department" is a grouping you declare, not a field we hold.** Repeat + * `?group=Name:trade1,trade2` to roll the book up by it — `?group=Structure:ironworker,concrete` + * `&group=MEP:electrician,plumber` — and `?group_cap=` flags the weeks a whole group is + * over-committed. `resource_assignment.trade` is labelled "Trade / **discipline**", so a design + * firm's axis is already the field's value; a GC's departments (preconstruction, estimating, + * operations, safety) are office functions whose people are not in this data at all. The industry + * tools model it the same way: Deltek Vantagepoint ships no `department` dimension, only a + * firm-configured organisation breakdown whose labels the firm chooses. With no `?group=`, the + * install-wide default from the **Portfolio resource grouping** setting is used, so a firm + * declares its axis once instead of every caller repeating it; a malformed *configured* value + * degrades to the ungrouped book and reports `groups_error`, where a malformed *query* one is a + * 422 — the error goes to whoever can act on it. + * + * The rollup refuses a trade claimed by two groups (the totals would exceed the book) and an empty + * group (a confident zero), and it reports `ungrouped_trades` — demand no group claimed — beside + * `unknown_trades`, a group naming a trade the book does not have. Neither is silent, because a + * group total that quietly omits work looks complete. + * + * Fidelity is reported, not blended: a project with no `resource_assignment` records falls back to + * `schedule_activity.crew_size`, which is a crew count rather than a resourced plan, and + * `fidelity` says how much of the book is which. + */ + get: operations["portfolio_resourcing_portfolio_resourcing_get"]; put?: never; post?: never; delete?: never; @@ -3106,7 +3528,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/wip": { + "/portfolio/risk": { parameters: { query?: never; header?: never; @@ -3114,12 +3536,19 @@ export interface paths { cookie?: never; }; /** - * Wip Schedule - * @description Work-in-Progress schedule: percentage-of-completion (cost-to-cost) → earned revenue vs billed → - * over-/under-billing (contract liability / asset), retainage, gross profit and backlog. The - * accounting twin to the earned-value module. + * Portfolio Risk + * @description R22-PIPELINE — the **portfolio risk heat map**: every accessible project down, the five risk + * engines across, severity-weighted intensity in the cell. + * + * `/portfolio/executive` and `/portfolio/construction` roll up performance; neither answers *which + * engine is hot on which project*. Cells come from `risk_board.board` unchanged, so a cell and the + * project's own risk panel cannot disagree — which is why the sweep is bounded by `limit` (each + * project is a full board, Monte-Carlo included) and reports `truncated` rather than quietly + * scanning a prefix. That prefix is by project name, not by risk: ranking is what the sweep + * produces, so it cannot choose what to sweep. A cell whose engine could not run reads `error`, + * never 0. */ - get: operations["wip_schedule_projects__pid__wip_get"]; + get: operations["portfolio_risk_portfolio_risk_get"]; put?: never; post?: never; delete?: never; @@ -3128,7 +3557,7 @@ export interface paths { patch?: never; trace?: never; }; - "/wip/portfolio": { + "/pricing/status": { parameters: { query?: never; header?: never; @@ -3136,10 +3565,10 @@ export interface paths { cookie?: never; }; /** - * Wip Portfolio - * @description WIP across all projects — one row each, worst cash position (largest under-billing) first. + * Pricing Status + * @description Whether a live pricing feed is configured (else the built-in book is used). */ - get: operations["wip_portfolio_wip_portfolio_get"]; + get: operations["pricing_status_pricing_status_get"]; put?: never; post?: never; delete?: never; @@ -3148,7 +3577,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/contractor-statements": { + "/procurement/rfq-status": { parameters: { query?: never; header?: never; @@ -3156,11 +3585,10 @@ export interface paths { cookie?: never; }; /** - * Contractor Statements - * @description Contractor financial statements: percentage-of-completion income statement (revenue earned, not - * billed) + the contract-position balance-sheet section (contract asset/liability, retainage, AP). + * Rfq Status + * @description Whether RFQ dispatch to suppliers is configured (else quote leveling + 3-way match still work). */ - get: operations["contractor_statements_projects__pid__contractor_statements_get"]; + get: operations["rfq_status_procurement_rfq_status_get"]; put?: never; post?: never; delete?: never; @@ -3169,112 +3597,118 @@ export interface paths { patch?: never; trace?: never; }; - "/contractor-statements/portfolio": { + "/proforma/compare": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Contractor Statements Portfolio - * @description Company-wide contractor statements — the POC P&L and contract position summed across jobs. + * Compare + * @description Side-by-side metrics for several scenarios. */ - get: operations["contractor_statements_portfolio_contractor_statements_portfolio_get"]; - put?: never; - post?: never; + post: operations["compare_proforma_compare_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/traceability": { + "/proforma/entitlement-risk": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Cost Traceability - * @description Cost traceability coverage — how much cost is tied to IFC model elements by GlobalId, per cost code. + * Run Approval Risk + * @description R22-ENTITLE-RISK — Monte Carlo over the approval coin, the entitlement duration, and any + * other drivers. + * + * A denied entitlement is **not** modelled as a bad IRR. If approval is refused the building is + * never built: there is no NOI, no exit and no multiple, so the draw is not solved at all. It is + * recorded as a terminal branch with its own economics — pursuit sunk, carry spent to the date of + * refusal, land recovered at whatever it fetches — and reported as a separate population. + * + * Consequently the returns here are labelled `metrics_given_approval`, and + * `probability_of_clearing` counts **every** iteration in its denominator: a refused entitlement + * does not clear a 15% hurdle. Only NPV is blended into an expected value, because only NPV is a + * currency amount that can be added across the two branches. */ - get: operations["cost_traceability_projects__pid__cost_traceability_get"]; - put?: never; - post?: never; + post: operations["run_approval_risk_proforma_entitlement_risk_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements/{guid}/costs": { + "/proforma/financials": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Element Costs - * @description Every cost line (budget / commitment / direct cost / sub invoice) tagged to this IFC element. + * Financials Stateless + * @description Three financial statements + tax for a deal, without persisting: income statement (NOI → + * depreciation → interest → tax → net income), balance sheet (balances), GAAP cash-flow statement, + * depreciation/tax schedule with sale recapture + capital gains, after-tax returns, two-sided budget. */ - get: operations["element_costs_projects__pid__elements__guid__costs_get"]; - put?: never; - post?: never; + post: operations["financials_stateless_proforma_financials_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/subcontractor-billing": { + "/proforma/forecast": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Subcontractor Billing - * @description Subcontractor billing — the GC-pays-subs mirror of owner billing. Each subcontract's pay - * applications (sub_invoice records) rolled up: contract value vs billed-to-date (approved/paid), - * retainage held, paid, and remaining-to-bill. Ties sub draws to the same cost codes and the GMP - * direct-cost actual, so what subs bill the GC reconciles against what the GC bills the owner. - */ - get: operations["subcontractor_billing_projects__pid__subcontractor_billing_get"]; + get?: never; put?: never; - post?: never; + /** Forecast Stateless */ + post: operations["forecast_stateless_proforma_forecast_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/elements/{guid}/5d": { + "/proforma/monte-carlo": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Element 5D - * @description 5D for a model element: click a GUID in the 3D view → its schedule activity (with %-complete, - * dates, whether it's hard-tied or matched by trade) and its cost code's budget vs committed vs - * actual. Ties the BIM model to the GC schedule + budget — the same relational data, by element. + * Run Monte Carlo + * @description Probabilistic risk analysis: sample the given drivers, solve each draw, and return the + * distribution (percentiles, mean/std, P[≥target], histogram) of each output metric. */ - get: operations["element_5d_projects__pid__elements__guid__5d_get"]; - put?: never; - post?: never; + post: operations["run_monte_carlo_proforma_monte_carlo_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/5d/heatmap": { + "/proforma/portfolio": { parameters: { query?: never; header?: never; @@ -3282,13 +3716,12 @@ export interface paths { cookie?: never; }; /** - * Elements 5D Map - * @description Batch 5D for the whole model — bucket every element's GUID for a 3D heatmap. `by=progress` - * buckets by its schedule activity's %-complete (complete / in_progress / not_started); `by=cost` - * by its cost-code variance (over / on_under). Same hard-tied-or-by-trade resolution as the - * per-element 5D. Drives 'color the building by progress / cost status'. + * Portfolio + * @description Multi-deal roll-up across all solved scenarios: total capitalization, equity-weighted + * blended IRR, aggregate equity multiple, and per-deal metrics. Scoped to the caller's projects + * so the roll-up never blends other tenants' deals. */ - get: operations["elements_5d_map_projects__pid__5d_heatmap_get"]; + get: operations["portfolio_proforma_portfolio_get"]; put?: never; post?: never; delete?: never; @@ -3297,7 +3730,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/budget/gmp": { + "/proforma/portfolio/compare": { parameters: { query?: never; header?: never; @@ -3305,13 +3738,11 @@ export interface paths { cookie?: never; }; /** - * Gmp Budget - * @description Full GC project budget (GMP): direct trade work (by CSI division + bid package) + General - * Conditions / Requirements (incl. staffing projections) + Overhead + Fee + Contingency, each - * tracked budget vs committed vs actual vs variance. Reconciles to the prime-contract value and to - * the developer proforma's construction hard-cost line — the PX's on-budget view, under Schedule. + * Portfolio Compare + * @description FIN-PORTFOLIO: the latest scenario per project side by side — returns + governance state — + * with a best/worst spread per metric. RBAC on → only the caller's member projects. */ - get: operations["gmp_budget_projects__pid__budget_gmp_get"]; + get: operations["portfolio_compare_proforma_portfolio_compare_get"]; put?: never; post?: never; delete?: never; @@ -3320,7 +3751,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/budget/baseline": { + "/proforma/provenance": { parameters: { query?: never; header?: never; @@ -3330,103 +3761,111 @@ export interface paths { get?: never; put?: never; /** - * Set Budget Baseline - * @description Snapshot the current GMP budget as the **baseline** (computed total + per-category + per-line). - * Budget variance is then measured against this — re-baseline after an approved change. One per project. - */ - post: operations["set_budget_baseline_projects__pid__budget_baseline_post"]; - /** - * Clear Budget Baseline - * @description Remove the budget baseline. + * Proforma Provenance + * @description R24-TRACE-UI ② — which assumptions behind each headline figure the CALLER declared, and which the + * engine defaulted. + * + * Note what is passed: `declared_from(a)`, i.e. `model_dump(exclude_unset=True)`. By the time + * `solve()` sees `a.model_dump()` pydantic has filled every default, and deriving from *that* would + * report every input as declared — a record that answers the reviewer's question with fiction. No + * figure carries an element link; the proforma holds no GlobalId, and inventing a terminus is the one + * failure worse than having none. */ - delete: operations["clear_budget_baseline_projects__pid__budget_baseline_delete"]; + post: operations["proforma_provenance_proforma_provenance_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/budget/variance": { + "/proforma/provenance/admissibility": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Budget Variance - * @description Movement of the GMP budget vs the baseline: total delta + per-category and per-line deltas - * (positive = grown since baseline). 409 if no baseline is set. Shows how the budget has drifted - * from the plan of record — the on-budget tracking a PX reports. + * Proforma Admissibility + * @description R22-PROVENANCE — one admissibility verdict across all three provenance legs. + * + * Body: `{assumptions_provenance, estimate_ledger, agent_answers}` — each the OUTPUT of the engine + * that owns it (`/proforma/provenance` or `assumption_provenance.provenance`, `boe_ledger.ledger`, + * a list of `cited_answer.claim` results). Nothing is re-derived here, so this cannot disagree with + * the engine it summarises. + * + * Each leg already answers for itself; nothing answered the question the ring is actually for — + * **is this deal's output admissible in an IC memo or a claim?** + * + * **There is deliberately no blended provenance score.** A single 0–100 lets a well-cited proforma + * hide an uncited estimate, and a memo is not admissible because it scored 82. The verdict is + * per-leg and the blockers are NAMED — a count gets quoted in a memo, a list gets fixed. + * + * **An empty leg reports `no_data`, never coverage.** The two engines disagree about zero in + * opposite directions — an empty `boe_ledger` reports `pct_documented` 1.0 ("perfectly documented") + * while empty assumptions report `coverage_pct` 0.0 — and both describe the same absence. A project + * with no estimate lines is not a project with a perfectly documented estimate, so an absent leg is + * excluded from the verdict and the deal reads `incomplete` rather than `admissible`. */ - get: operations["budget_variance_projects__pid__budget_variance_get"]; - put?: never; - post?: never; + post: operations["proforma_admissibility_proforma_provenance_admissibility_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/budget/cashflow": { + "/proforma/residual-land": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Budget Cashflow - * @description Cost-loaded schedule → monthly cash-flow / draw curve. Spreads each schedule activity's - * budgeted cost across its start→finish months for the cumulative construction S-curve — where the - * Schedule and Budget destinations meet (the PX's monthly cash need). + * Residual Land + * @description FIN-CALC: the residual-land inverse — the land price that hits a target return, by + * deterministic bisection over the same forward solve every other number comes from. */ - get: operations["budget_cashflow_projects__pid__budget_cashflow_get"]; - put?: never; - post?: never; + post: operations["residual_land_proforma_residual_land_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/sov/from-budget": { + "/proforma/scenarios": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** List Scenarios */ + get: operations["list_scenarios_proforma_scenarios_get"]; put?: never; - /** - * Sov From Budget - * @description Seed the owner pay-app **Schedule of Values** from the GMP budget — one SOV line per cost-code - * budget line (carrying its cost-code link), plus General Conditions / Requirements / Overhead / - * Fee / Contingency, each at its GMP value. So the G702/G703 the owner is billed on draws from the - * same relational budget the PX manages. Idempotent: no-op if the SOV already has lines unless - * `?replace=true` rebuilds it. Retainage comes from the prime contract. - */ - post: operations["sov_from_budget_projects__pid__cost_sov_from_budget_post"]; + /** Create Scenario */ + post: operations["create_scenario_proforma_scenarios_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/estimate/from-model": { + "/proforma/scenarios/{sid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Estimate From Model - * @description Conceptual estimate from the IFC quantity takeoff × unit rates — priced line items by element - * class + a grand total (feeds the budget / proforma hard cost). 409 if no source IFC. - */ - get: operations["estimate_from_model_projects__pid__estimate_from_model_get"]; - put?: never; + /** Get Scenario */ + get: operations["get_scenario_proforma_scenarios__sid__get"]; + /** Update Scenario */ + put: operations["update_scenario_proforma_scenarios__sid__put"]; post?: never; delete?: never; options?: never; @@ -3434,50 +3873,46 @@ export interface paths { patch?: never; trace?: never; }; - "/estimate/resources/catalog": { + "/proforma/scenarios/{sid}/clone": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Resource Catalog - * @description The resource-based estimating reference: labor/material/equipment resources + assemblies - * (each with its built-up unit cost and L/M/E split) + the default IFC-class→assembly map. - */ - get: operations["resource_catalog_estimate_resources_catalog_get"]; + get?: never; put?: never; - post?: never; + /** Clone Scenario */ + post: operations["clone_scenario_proforma_scenarios__sid__clone_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/estimate/resource-based": { + "/proforma/scenarios/{sid}/draw-package": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Estimate Resource Based - * @description Resource-based (assembly) estimate from the IFC takeoff: each element class priced by building - * the cost UP from labor + material + equipment, returning the L/M/E split and total crew-hours - * (which feed resource loading + the schedule), not just a blended $/unit. 409 if no source IFC. + * Draw Package + * @description Bridge underwriting → construction draws: turn the scenario's cost tree + actuals into + * Schedule-of-Values records on a GC project, then produce the AIA G702/G703 pay app — + * so the IRR you underwrote and the lender draw run off the SAME cost tree. */ - get: operations["estimate_resource_based_projects__pid__estimate_resource_based_get"]; - put?: never; - post?: never; + post: operations["draw_package_proforma_scenarios__sid__draw_package_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/takeoff/dxf": { + "/proforma/scenarios/{sid}/forecast": { parameters: { query?: never; header?: never; @@ -3487,20 +3922,17 @@ export interface paths { get?: never; put?: never; /** - * Takeoff Dxf - * @description Quantity takeoff from an uploaded 2D CAD drawing (.dxf) — linear metres, enclosed area and - * block counts per layer, so estimating isn't IFC-only. DWG must be converted to DXF first. The - * upload is parsed in a temp file (never persisted to the source tree) and discarded. 400 on a file - * that isn't readable DXF. + * Forecast Scenario + * @description Re-forecast the underwritten returns against actuals drawn to date (Phase 5 bridge). */ - post: operations["takeoff_dxf_projects__pid__takeoff_dxf_post"]; + post: operations["forecast_scenario_proforma_scenarios__sid__forecast_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/classifications": { + "/proforma/scenarios/{sid}/provenance": { parameters: { query?: never; header?: never; @@ -3508,10 +3940,20 @@ export interface paths { cookie?: never; }; /** - * List Classifications - * @description Regional classification systems available for estimate coding / GAEB export. + * Scenario Provenance + * @description Which of this scenario's MATERIAL assumptions carry a source, and which do not. + * + * Citations are keyed by the same dotted paths `sensitivity` and `monte_carlo` already use + * (`operations.potential_rent_annual`, `exit.exit_cap`, …), so the driver a sensitivity table + * varies is addressed identically to the citation that justifies it. + * + * Coverage is over numeric drivers only — switches are excluded because citing "american" is not a + * provenance claim about a number, and cost lines are excluded because `boe_ledger` already owns + * their sources. `uncited` is NAMED, not merely counted: an uncited assumption is the absence of a + * source, neither a finding that the value is wrong nor a pass. Pass `revision` to have citations + * against an older revision counted as stale. */ - get: operations["list_classifications_classifications_get"]; + get: operations["scenario_provenance_proforma_scenarios__sid__provenance_get"]; put?: never; post?: never; delete?: never; @@ -3520,72 +3962,76 @@ export interface paths { patch?: never; trace?: never; }; - "/reference/disciplines": { + "/proforma/scenarios/{sid}/review": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Reference Disciplines - * @description The Discipline Spine vocabularies: NCS disciplines (with their default MasterFormat divisions + - * Uniformat groups), the MasterFormat division master, and the Uniformat↔MasterFormat crosswalk. - * Drives the discipline/division selects and the model→sheets→specs→bid→budget joins. + * Review Scenario + * @description FIN-GOV: move a scenario through draft → in_review → approved → published + * (reject/reopen walk back to draft). Approved/published assumptions are immutable in place. */ - get: operations["reference_disciplines_reference_disciplines_get"]; - put?: never; - post?: never; + post: operations["review_scenario_proforma_scenarios__sid__review_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/estimate/gaeb.x83": { + "/proforma/scenarios/{sid}/share": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Estimate Gaeb - * @description Export the model estimate as a GAEB DA XML 3.2 Bill of Quantities (X83), coded to a regional - * classification (din276 / nrm1 / masterformat). 409 if the project has no source IFC. + * Share Scenario + * @description Grant an LP (or any party) read access to this scenario. + * + * **The body's `user` is the person being GRANTED access; `actor` is the person doing the + * granting.** They were the same parameter until 2026-08-13, and there was no `current_user` + * dependency at all — so the route had no notion of who was calling, and any authenticated caller + * could share any scenario with anyone. Sharing is a write, so it takes the write guard. + * + * The wire key stays `user` via `alias`, so no caller changes; only the code stops conflating two + * different people under one name. */ - get: operations["estimate_gaeb_projects__pid__estimate_gaeb_x83_get"]; - put?: never; - post?: never; + post: operations["share_scenario_proforma_scenarios__sid__share_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/qto/by-floor": { + "/proforma/sensitivity": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Qto By Floor - * @description Quantity takeoff + cost broken down by floor (storey) and discipline (IFC class) — quantities - * and dollars mapped to where they sit in the building, with a per-floor total + a discipline - * roll-up. 409 if no source IFC. + * Run Sensitivity + * @description Two-variable data table: the metric solved across the x×y grid of two drivers. */ - get: operations["qto_by_floor_projects__pid__qto_by_floor_get"]; - put?: never; - post?: never; + post: operations["run_sensitivity_proforma_sensitivity_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/tm": { + "/proforma/solve": { parameters: { query?: never; header?: never; @@ -3595,17 +4041,18 @@ export interface paths { get?: never; put?: never; /** - * Price Tm - * @description Price T&M line items from the rate tables and write the totals back onto the eTicket. + * Solve Stateless + * @description Solve a deal without persisting — full S&U, cash flows, returns, waterfall, plus underwriting + * guardrails (U5) that flag returns outside typical market bands. */ - post: operations["price_tm_projects__pid__cost_tm_post"]; + post: operations["solve_stateless_proforma_solve_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/lien-waiver": { + "/projects": { parameters: { query?: never; header?: never; @@ -3613,82 +4060,98 @@ export interface paths { cookie?: never; }; /** - * Lien Waiver - * @description A statutory lien waiver / release to accompany a pay app (C1). `kind`: conditional_progress | - * unconditional_progress | conditional_final | unconditional_final. + * List Projects + * @description Projects the caller can see. Membership is filtered in SQL (one query, not one role lookup + * per project) and the result is paginated — the previous shape loaded every project then ran a + * per-project role query, an N+1 that also briefly materialized other tenants' names. */ - get: operations["lien_waiver_projects__pid__cost_lien_waiver_get"]; + get: operations["list_projects_projects_get"]; put?: never; - /** - * Lien Waiver From Payapp - * @description Generate a lien-waiver record for the current pay application — amount = the G702 current - * payment due — so each draw produces its waiver automatically. - */ - post: operations["lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post"]; + /** Create Project */ + post: operations["create_project_projects_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/lien-waiver.pdf": { + "/projects/import-bundle": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Lien Waiver Pdf */ - get: operations["lien_waiver_pdf_projects__pid__cost_lien_waiver_pdf_get"]; + get?: never; put?: never; - post?: never; + /** + * Import Bundle + * @description Open a **`.mass`** container as a new project (fresh id) — geometry, data and blobs + * restored. Legacy `.mmproj` files (format v1) are still accepted; a container written by a + * NEWER build is refused rather than imported partially. + */ + post: operations["import_bundle_projects_import_bundle_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/esign/status": { + "/projects/preview-bundle": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Esign Status - * @description Digital-signature capability: built-in PAdES (always available) + the optional 3rd-party bridge. + * Preview Bundle + * @description R28-BUNDLE — what a container holds, WITHOUT importing it. + * + * Export has always stated what it leaves behind; import had no counterpart, so the only way to + * learn a bundle's contents was to import it — which creates a project. "Open it to find out what + * is in it" is not a choice a user can decline. + * + * Reads the manifest only: no extraction, no writes, no project. It reuses the importer's own + * validation, so a container that previews cleanly is one that will import, and one written by a + * newer build is refused HERE — before the user commits — rather than at import. + * + * The response repeats what will NOT arrive (accounts, audit log, settings, connections), because + * that is the part a user is most likely to assume travelled, and the moment to say so is before + * the import rather than in a manifest they may never open. */ - get: operations["esign_status_esign_status_get"]; - put?: never; - post?: never; + post: operations["preview_bundle_projects_preview_bundle_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/scope-library": { + "/projects/{pid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Scope Library List - * @description The scope-of-work clause library used to compose Exhibit A (ids + titles, grouped by category). - */ - get: operations["scope_library_list_scope_library_get"]; + /** Get Project */ + get: operations["get_project_projects__pid__get"]; put?: never; post?: never; - delete?: never; + /** + * Delete Project + * @description Delete a project and everything it owns (rows + geometry + attachment blobs). + */ + delete: operations["delete_project_projects__pid__delete"]; options?: never; head?: never; - patch?: never; + /** Patch Project */ + patch: operations["patch_project_projects__pid__patch"]; trace?: never; }; - "/projects/{pid}/contracts/{key}/{rid}/document.pdf": { + "/projects/{pid}/5d/element-costs": { parameters: { query?: never; header?: never; @@ -3696,13 +4159,12 @@ export interface paths { cookie?: never; }; /** - * Contract Document - * @description Render a contract/change document for a record. doc = agreement | prime | co | exhibit | asi - * (G710) | bulletin | ccd (G714, from a directive record). `clauses` is a comma-separated list of - * scope_library ids for Exhibit A (defaults to the record's trade). With attach=1 the PDF is also - * saved as an attachment on the record. + * Element Costs 5D + * @description 5D-BIND: the GUID-keyed cost (+carbon) table off the **live** property index — element quantity + * (per the rate's basis) × class rate, so a GUID-stable edit + republish reprices automatically. + * Carbon rides the same row where the material matches. 404 until a model is loaded. */ - get: operations["contract_document_projects__pid__contracts__key___rid__document_pdf_get"]; + get: operations["element_costs_5d_projects__pid__5d_element_costs_get"]; put?: never; post?: never; delete?: never; @@ -3711,94 +4173,90 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/contracts/{key}/{rid}/sign": { + "/projects/{pid}/5d/heatmap": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Sign Contract - * @description Record a party's signature on a contract/CO (typed name + date) on the record `data`, audited. - * One signature per party (re-signing replaces). Advancing the workflow state is a separate - * transition — sign captures the executed signature; the UI calls /transition to move the record. + * Elements 5D Map + * @description Batch 5D for the whole model — bucket every element's GUID for a 3D heatmap. `by=progress` + * buckets by its schedule activity's %-complete (complete / in_progress / not_started); `by=cost` + * by its cost-code variance (over / on_under). Same hard-tied-or-by-trade resolution as the + * per-element 5D. Drives 'color the building by progress / cost status'. */ - post: operations["sign_contract_projects__pid__contracts__key___rid__sign_post"]; + get: operations["elements_5d_map_projects__pid__5d_heatmap_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/contracts/{key}/{rid}/send-for-signature": { + "/projects/{pid}/accounting/bills.iif": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Send For Signature - * @description Route a contract/CO document through the configured 3rd-party e-signature provider (DocuSeal et - * al.) for legally-binding multi-party signing. `signers` is a list of {email, name?, party?}. Stores - * the submission id + per-signer signing URLs on the record `data.esign_submission` (audited). + * Bills Iif + * @description QuickBooks IIF bills file (AP bills from subcontractor invoices). */ - post: operations["send_for_signature_projects__pid__contracts__key___rid__send_for_signature_post"]; + get: operations["bills_iif_projects__pid__accounting_bills_iif_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/esign/webhook": { + "/projects/{pid}/accounting/chart-of-accounts": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Esign Webhook - * @description Receive a provider completion webhook (e.g. DocuSeal form.completed). Anonymous surface — the - * payload carries no authority; we only log the normalized completion for audit/reconciliation. + * Chart Of Accounts + * @description The standard construction chart of accounts (code, name, type, normal balance). */ - post: operations["esign_webhook_esign_webhook_post"]; + get: operations["chart_of_accounts_projects__pid__accounting_chart_of_accounts_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/contracts/{key}/{rid}/digital-sign": { + "/projects/{pid}/accounting/gl.csv": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Digital Sign - * @description Apply a certificate-based PAdES digital signature to the contract/CO document — tamper-evident, - * self-validating. Renders the document, signs it, attaches the signed PDF, and records the signer + - * cert fingerprint on the record (audit). Falls back cleanly if signing isn't available. + * Gl Csv + * @description Double-entry general-ledger CSV (universal import for QuickBooks / Sage / Xero). */ - post: operations["digital_sign_projects__pid__contracts__key___rid__digital_sign_post"]; + get: operations["gl_csv_projects__pid__accounting_gl_csv_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/reports": { + "/projects/{pid}/accounting/journal": { parameters: { query?: never; header?: never; @@ -3806,10 +4264,10 @@ export interface paths { cookie?: never; }; /** - * Report Catalog - * @description The available reports (id, name, group) for the Reports panel. + * Journal + * @description Preview the flattened GL/AP entries (sub invoices + posted direct costs) as JSON. */ - get: operations["report_catalog_reports_get"]; + get: operations["journal_projects__pid__accounting_journal_get"]; put?: never; post?: never; delete?: never; @@ -3818,32 +4276,41 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/reports/{report}.pdf": { + "/projects/{pid}/accounting/journal-batch": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Report Pdf */ - get: operations["report_pdf_projects__pid__reports__report__pdf_get"]; + get?: never; put?: never; - post?: never; + /** + * Create Journal Batch + * @description Freeze the current books into an approval-gated journal batch. The batch then moves + * submit → approve → export via the standard workflow (the config engine gates each step by party); + * export emits the CSV/IIF from the frozen snapshot, so the accountant imports the approved figures. + */ + post: operations["create_journal_batch_projects__pid__accounting_journal_batch_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/reports/{report}.xlsx": { + "/projects/{pid}/accounting/journal-batch/{bid}/export": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Report Xlsx */ - get: operations["report_xlsx_projects__pid__reports__report__xlsx_get"]; + /** + * Export Journal Batch + * @description Download an **approved** batch's frozen GL: fmt=gl → CSV, fmt=iif → QuickBooks IIF. + * 409 until the batch is approved (nothing exports to the books without passing the gate). + */ + get: operations["export_journal_batch_projects__pid__accounting_journal_batch__bid__export_get"]; put?: never; post?: never; delete?: never; @@ -3852,7 +4319,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/schedule/cpm": { + "/projects/{pid}/accounting/journal-entries": { parameters: { query?: never; header?: never; @@ -3860,11 +4327,10 @@ export interface paths { cookie?: never; }; /** - * Cpm - * @description Critical Path Method analysis of the schedule_activity records — early/late dates, total + - * free float, and the critical path (FS dependencies via each activity's `predecessors`). + * Journal Entries + * @description Balanced double-entry journal from job cost + billing + the WIP percentage-of-completion adjustment. */ - get: operations["cpm_projects__pid__schedule_cpm_get"]; + get: operations["journal_entries_projects__pid__accounting_journal_entries_get"]; put?: never; post?: never; delete?: never; @@ -3873,7 +4339,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/schedule/resource-loading": { + "/projects/{pid}/accounting/trial-balance": { parameters: { query?: never; header?: never; @@ -3881,12 +4347,10 @@ export interface paths { cookie?: never; }; /** - * Resource Loading Endpoint - * @description Resource-loaded schedule — weekly resource histogram (by trade/type), cumulative units + **cost** - * S-curves, peak, and (against an optional ?cap= availability) over-allocation flags. Prefers - * `resource_assignment` records (activity + cost code + units + rate); falls back to activity crew_size. + * Trial Balance + * @description Trial balance — debits and credits per account (must tie), from the double-entry journal. */ - get: operations["resource_loading_endpoint_projects__pid__schedule_resource_loading_get"]; + get: operations["trial_balance_projects__pid__accounting_trial_balance_get"]; put?: never; post?: never; delete?: never; @@ -3895,7 +4359,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/schedule/resource-leveling": { + "/projects/{pid}/action-items/tracker": { parameters: { query?: never; header?: never; @@ -3903,12 +4367,10 @@ export interface paths { cookie?: never; }; /** - * Resource Leveling Endpoint - * @description Resource-leveling advisory against a `cap` availability: over-allocated work that still has CPM - * total float can be **smoothed** (shifted within float) to shave the peak without moving the finish. - * Advisory only — never mutates the schedule. + * Action Tracker + * @description Meeting & action-item tracker — open/overdue by assignee & priority, completion, meeting log. */ - get: operations["resource_leveling_endpoint_projects__pid__schedule_resource_leveling_get"]; + get: operations["action_tracker_projects__pid__action_items_tracker_get"]; put?: never; post?: never; delete?: never; @@ -3917,7 +4379,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/productivity/summary": { + "/projects/{pid}/agent-packs": { parameters: { query?: never; header?: never; @@ -3925,10 +4387,25 @@ export interface paths { cookie?: never; }; /** - * Productivity Summary - * @description Field labor productivity — units installed per man-hour per entry, rolled up by trade. + * Project Agent Packs + * @description R22-AGENT-PACKS — the governance console: which named agents exist, what each runs, which can + * write, and what has actually been run against this project. + * + * A pack is a **named view over tools that already exist** — "Submittal Review Agent" rather than + * `standards_check`. It grants nothing: every tool in every pack is already in `mcp_tools.TOOLS`, + * and `dispatch` applies its own membership and role checks regardless of which pack a caller came + * through. A pack that could widen access would be a privilege-escalation surface wearing the name + * of a governance feature. + * + * **Packs that can modify the project say so**, naming the write tools — a console that lists + * agents without distinguishing read from write is not one anybody can govern with. + * + * `runs` is the per-run history the ring entry calls the gating factor for enterprise adoption. + * Until R22-AGENT-PACKS only `run_recipe` wrote an audit row, so 16 of 18 tools ran with no trail. + * Every dispatch now records one — **including failures**, because a history of successes cannot + * answer what an agent *attempted*, which is the question asked after an incident. */ - get: operations["productivity_summary_projects__pid__productivity_summary_get"]; + get: operations["project_agent_packs_projects__pid__agent_packs_get"]; put?: never; post?: never; delete?: never; @@ -3937,7 +4414,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cv-progress/status": { + "/projects/{pid}/ai-readiness": { parameters: { query?: never; header?: never; @@ -3945,10 +4422,11 @@ export interface paths { cookie?: never; }; /** - * Cv Progress Status - * @description Status of the (external, feature-flagged) computer-vision site-progress bridge. + * Ai Readiness Scorecard + * @description AI / data-readiness scorecard — grades the project 0-100 on single-source-of-truth, information + * completeness, model integrity and governance ("can an agent act on this data yet?"). */ - get: operations["cv_progress_status_projects__pid__cv_progress_status_get"]; + get: operations["ai_readiness_scorecard_projects__pid__ai_readiness_get"]; put?: never; post?: never; delete?: never; @@ -3957,7 +4435,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cv-progress/ingest": { + "/projects/{pid}/ai/ask": { parameters: { query?: never; header?: never; @@ -3967,18 +4445,18 @@ export interface paths { get?: never; put?: never; /** - * Cv Progress Ingest - * @description Accept one external CV progress estimate (no-op unless AEC_CV_BRIDGE is enabled). When enabled, - * `activity` (a schedule_activity id or name) is resolved and the estimate written to its percent. + * Ai Ask + * @description Ask a natural-language question about the project; answered (by Claude when configured) + * against a live snapshot of KPIs, costs and open items. Degrades to returning the snapshot. */ - post: operations["cv_progress_ingest_projects__pid__cv_progress_ingest_post"]; + post: operations["ai_ask_projects__pid__ai_ask_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cv-progress/ingest-batch": { + "/projects/{pid}/ai/audit": { parameters: { query?: never; header?: never; @@ -3988,87 +4466,98 @@ export interface paths { get?: never; put?: never; /** - * Cv Progress Ingest Batch - * @description Accept a batch of CV progress estimates — `{"estimates": [{activity, percent}, …]}` — the shape a - * vision service produces per photo sweep. Each valid item is written to its activity; returns per-item - * outcomes + a summary. No-op unless AEC_CV_BRIDGE is enabled. + * Ai Audit + * @description NL-QA "audit + suggest fixes": the ranked decision-readiness gaps with an **executable fix + * step** attached to every gap a deterministic recipe can close — the returned `fix_steps` drop + * straight into `POST /projects/{pid}/edit/batch` (one version, one undo). Read-only: nothing is + * applied here. Needs a source IFC. */ - post: operations["cv_progress_ingest_batch_projects__pid__cv_progress_ingest_batch_post"]; + post: operations["ai_audit_projects__pid__ai_audit_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/alerts": { + "/projects/{pid}/ai/author": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Schedule Alerts - * @description Predictive schedule alerts — overdue work, late/at-risk starts (incomplete predecessor), - * behind-schedule SPI, and a procurement-risk proxy — from the cost-loaded schedule + CPM. + * Ai Author + * @description Natural-language authoring — map a plain-English instruction ("add a 3 m wall from 0,0 to 5,0", + * "a 5x4 m room at 0,0", "window in the selected wall") to a validated **plan** of {recipe, params}. + * Interpretation only: nothing is written. The client shows the plan for confirmation, then applies each + * step via the normal POST /edit path (GUID-stable, audited). Claude does multi-step planning when an + * Anthropic API key is set; otherwise a deterministic keyword baseline — no key required. */ - get: operations["schedule_alerts_projects__pid__schedule_alerts_get"]; - put?: never; - post?: never; + post: operations["ai_author_projects__pid__ai_author_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/optimize": { + "/projects/{pid}/ai/draft-rfi": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Schedule Optimize - * @description Schedule-acceleration ADVISORY off the CPM critical path — crash candidates (longest critical - * activities), fast-track candidates (consecutive critical activities to overlap), and near-critical - * watch. Rule-based + an optional AI narrative; it never rewrites the schedule. + * Draft Rfi + * @description Draft an RFI (subject/question/discipline/priority) from a selected element's IFC context. + * Uses Claude when ANTHROPIC_API_KEY is set; otherwise returns a deterministic template draft. + * Reviewer+ (same gate as creating RFIs; avoids anonymous LLM-token burn). */ - get: operations["schedule_optimize_projects__pid__schedule_optimize_get"]; - put?: never; - post?: never; + post: operations["draft_rfi_projects__pid__ai_draft_rfi_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/gantt.svg": { + "/projects/{pid}/ai/estimate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Gantt */ - get: operations["gantt_projects__pid__schedule_gantt_svg_get"]; + get?: never; put?: never; - post?: never; + /** + * Ai Estimate + * @description Draft a Bill of Quantities from a plain-text project description (Claude when configured; + * a graceful stub otherwise — never fabricates numbers without the model). + */ + post: operations["ai_estimate_projects__pid__ai_estimate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/lob.svg": { + "/projects/{pid}/ai/risk-summary": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Lob */ - get: operations["lob_projects__pid__schedule_lob_svg_get"]; + /** + * Risk Summary + * @description AI/rules risk read over the project dashboard (owner/PM reporting). + */ + get: operations["risk_summary_projects__pid__ai_risk_summary_get"]; put?: never; post?: never; delete?: never; @@ -4077,78 +4566,74 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/schedule/lookahead": { + "/projects/{pid}/ai/triage-rfi": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Lookahead - * @description Short-interval **lookahead** (the field's 3- / 6-week plan): activities active in the window - * [`start`, `start`+`weeks`), grouped by ISO week. An activity is in-window if it overlaps it - * (starts before the end and finishes on/after the start). Each carries trade, %-complete, and a - * field status (not_started / in_progress / late / complete). Defaults to a 3-week window today. + * Triage Rfi + * @description Triage an RFI — auto-categorize (discipline / category / urgency), name the ball-in-court party, + * and draft a response. Pass `rid` to triage an existing RFI record, or `rfi` data directly. Uses + * Claude when configured; a deterministic template otherwise. */ - get: operations["lookahead_projects__pid__schedule_lookahead_get"]; - put?: never; - post?: never; + post: operations["triage_rfi_projects__pid__ai_triage_rfi_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/baseline": { + "/projects/{pid}/analytical": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; - /** - * Set Baseline - * @description Snapshot the current schedule as the **baseline** (each activity's planned start/finish + budget, - * keyed by id). Variance is then measured against this — re-run to re-baseline after an approved - * change. One baseline per project. - */ - post: operations["set_baseline_projects__pid__schedule_baseline_post"]; /** - * Clear Baseline - * @description Remove the schedule baseline. + * Analytical Summary + * @description W10-7: the structural analytical model (analysis models, curve/surface members, point connections, + * load cases) derived alongside the physical frame. Build/refresh it with the `derive_analytical` + * recipe via POST /edit. */ - delete: operations["clear_baseline_projects__pid__schedule_baseline_delete"]; + get: operations["analytical_summary_projects__pid__analytical_get"]; + put?: never; + post?: never; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/variance": { + "/projects/{pid}/answer/cited-query": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Variance - * @description Per-activity slip vs the baseline: **finish_var**\/start_var in days (positive = later than - * baseline = slipped), plus added/removed activities. Surfaces how far the job has drifted from - * the plan of record. 409 if no baseline has been set. + * Answer Cited Query + * @description CITED-ANSWER — a deterministic model query that returns a **CitedAnswer**: every claim traces to the + * GUIDs it is derived from, with a coverage %, an uncited-claim guard, and source-conflict surfacing. The + * first producer of the provenance contract shared by the AI command bar / RFI-QA / KG answers. Body: + * `{query: , property?: }`. Bad query → 422. */ - get: operations["variance_projects__pid__schedule_variance_get"]; - put?: never; - post?: never; + post: operations["answer_cited_query_projects__pid__answer_cited_query_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/earned-value": { + "/projects/{pid}/appraisal": { parameters: { query?: never; header?: never; @@ -4156,67 +4641,68 @@ export interface paths { cookie?: never; }; /** - * Earned Value - * @description Schedule **earned value** over the activities that carry a budgeted cost. For each: - * BAC = Σ budget; **EV (BCWP)** = Σ %·budget (work earned); **PV (BCWS)** = Σ planned-fraction·budget - * (where today sits in [start, finish]). **SPI** = EV/PV and the schedule variance **SV** = EV−PV - * tell you, in dollars, whether the job is ahead of or behind plan. AC/CPI need cost actuals and are - * left to the cost engine. + * Get Appraisal + * @description Tri-approach valuation. Saved overrides (project.dev_property.appraisal) merge with any query + * overrides (query wins): depreciation_pct, land_value, replacement_cost_new, stabilized_noi, + * cap_rate, subject_sqft, weight_income, weight_cost, weight_sales. */ - get: operations["earned_value_projects__pid__schedule_earned_value_get"]; + get: operations["get_appraisal_projects__pid__appraisal_get"]; put?: never; - post?: never; + /** + * Save Appraisal + * @description Persist appraisal overrides (depreciation, land value, weights, …) on the project. + */ + post: operations["save_appraisal_projects__pid__appraisal_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/evm": { + "/projects/{pid}/ask": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Evm Snapshot - * @description Full Earned Value Management snapshot (ANSI/EIA-748-aligned): joins schedule earned value with - * cost actuals **by cost code (control account)**. Returns PV/EV/AC/BAC, CV/SV/CPI/SPI with health - * bands, the EAC/ETC/VAC/TCPI **forecast family**, a per-control-account table, and per-activity EV. - * `data_date` (YYYY-MM-DD) sets the reporting cut-off; defaults to today. + * Ask Model + * @description Ask a plain-English question about the model. Grounds the answer in a snapshot of the property + * index (counts by class/storey, Psets, facets); uses the configured AI provider, and degrades to + * returning the snapshot itself when no AI key is set (so the data is still useful offline). */ - get: operations["evm_snapshot_projects__pid__evm_get"]; - put?: never; - post?: never; + post: operations["ask_model_projects__pid__ask_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/evm/earned-schedule": { + "/projects/{pid}/assistant": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Evm Earned Schedule - * @description **Earned Schedule** (time-based EVM): ES, SV(t), SPI(t), IEAC(t) → forecast finish, in `week` or - * `month` periods, plus the PV baseline curve. Stays meaningful at completion, unlike dollar SPI. + * Project Assistant + * @description Ask about the project in plain English ('how many open RFIs?', 'what's the SPI?', 'occupancy?'). + * Grounded in a live project snapshot; returns the snapshot when no AI key is configured. */ - get: operations["evm_earned_schedule_projects__pid__evm_earned_schedule_get"]; - put?: never; - post?: never; + post: operations["project_assistant_projects__pid__assistant_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/evm/scurve": { + "/projects/{pid}/assistant/snapshot": { parameters: { query?: never; header?: never; @@ -4224,11 +4710,10 @@ export interface paths { cookie?: never; }; /** - * Evm Scurve - * @description The EVM **S-curve**: cumulative PV (full baseline) + EV + AC to the data date, over week/month - * buckets, for the three-line performance chart. + * Assistant Snapshot + * @description The grounded project snapshot the assistant uses (module tallies, schedule, budget, risk). */ - get: operations["evm_scurve_projects__pid__evm_scurve_get"]; + get: operations["assistant_snapshot_projects__pid__assistant_snapshot_get"]; put?: never; post?: never; delete?: never; @@ -4237,7 +4722,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/evm/model-ev": { + "/projects/{pid}/authoring/capabilities": { parameters: { query?: never; header?: never; @@ -4245,12 +4730,11 @@ export interface paths { cookie?: never; }; /** - * Evm Model Ev - * @description **Model-based EV**: earned value from physically-installed model elements (field-verified GUIDs) - * × BAC — the units-complete method sourced from the model. Cross-checks the schedule EV to catch - * over-reported / front-loaded progress. + * Authoring Capabilities + * @description Which optional/gated authoring capabilities are enabled on this server (so the UI can hide what's + * off). `execute_ifc_code` (A1) is the sandboxed Python escape hatch — off unless `AEC_ALLOW_IFC_CODE=1`. */ - get: operations["evm_model_ev_projects__pid__evm_model_ev_get"]; + get: operations["authoring_capabilities_projects__pid__authoring_capabilities_get"]; put?: never; post?: never; delete?: never; @@ -4259,7 +4743,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/evm/trend": { + "/projects/{pid}/bcf/export": { parameters: { query?: never; header?: never; @@ -4267,11 +4751,10 @@ export interface paths { cookie?: never; }; /** - * Evm Trend - * @description CPI/SPI **performance-index trend** across captured `evm_snapshot` records (oldest-first) — shows - * whether cost/schedule efficiency is improving or deteriorating over the reporting periods. + * Bcf Export + * @description Export the project's topics as a .bcfzip. `version` = 2.1 (default) or 3.0. */ - get: operations["evm_trend_projects__pid__evm_trend_get"]; + get: operations["bcf_export_projects__pid__bcf_export_get"]; put?: never; post?: never; delete?: never; @@ -4280,7 +4763,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/evm/snapshot": { + "/projects/{pid}/bcf/import": { parameters: { query?: never; header?: never; @@ -4289,19 +4772,15 @@ export interface paths { }; get?: never; put?: never; - /** - * Evm Capture - * @description Capture the current EVM state as a dated `evm_snapshot` baseline, so CPI/SPI can be trended over - * reporting periods. Capture one per period (weekly/monthly). - */ - post: operations["evm_capture_projects__pid__evm_snapshot_post"]; + /** Bcf Import */ + post: operations["bcf_import_projects__pid__bcf_import_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/milestones": { + "/projects/{pid}/bep": { parameters: { query?: never; header?: never; @@ -4309,12 +4788,13 @@ export interface paths { cookie?: never; }; /** - * Milestones - * @description **Milestone schedule**: the key dates — activities typed `Milestone` (or zero-duration), - * sorted by date, each with a status (met / due_soon / upcoming / late). `met` = 100% complete; - * `late` = past its date and not complete; `due_soon` = within 14 days. + * Bep Generate + * @description BEP-GEN: the **BIM Execution Plan** generated from the project's live configuration (ISO 19650) — + * standards + classification, information requirements (EIR/BEP/AIR + IDS), the RACI responsibility + * matrix, CDE container state, the model's exchange formats, and the model-quality acceptance gates. + * Always current — it reflects present state, not a stale point-in-time document. 404 if no project. */ - get: operations["milestones_projects__pid__schedule_milestones_get"]; + get: operations["bep_generate_projects__pid__bep_get"]; put?: never; post?: never; delete?: never; @@ -4364,6 +4844,30 @@ export interface paths { patch?: never; trace?: never; }; + "/projects/{pid}/bidding/scope-gap": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scope Gap Analysis + * @description SCOPE-GAP — does every element in the model land in a bid package? Maps the model's takeoff (by + * NCS discipline) against the defined `bid_package` records and flags **gaps** — disciplines present in + * the model with no covering package, i.e. quantities not in any bid (with sample GUIDs to highlight) — + * plus packages whose discipline has no model elements. Distinct from the ITB bid-response coverage. + * 409 without a source IFC. + */ + get: operations["scope_gap_analysis_projects__pid__bidding_scope_gap_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/projects/{pid}/bids/leveling": { parameters: { query?: never; @@ -4406,25 +4910,28 @@ export interface paths { patch?: never; trace?: never; }; - "/templates": { + "/projects/{pid}/bim-kpi/scorecard": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Templates */ - get: operations["list_templates_templates_get"]; + /** + * Bim Kpi Scorecard + * @description The 10-category BIM KPI scorecard, graded from the CDE, model quality and the issue / asset / + * closeout records (categories with no inputs show 'n/a'). + */ + get: operations["bim_kpi_scorecard_projects__pid__bim_kpi_scorecard_get"]; put?: never; - /** Create Template */ - post: operations["create_template_templates_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/templates/{tid}": { + "/projects/{pid}/budget/baseline": { parameters: { query?: never; header?: never; @@ -4433,55 +4940,68 @@ export interface paths { }; get?: never; put?: never; - post?: never; - /** Delete Template */ - delete: operations["delete_template_templates__tid__delete"]; + /** + * Set Budget Baseline + * @description Snapshot the current GMP budget as the **baseline** (computed total + per-category + per-line). + * Budget variance is then measured against this — re-baseline after an approved change. One per project. + */ + post: operations["set_budget_baseline_projects__pid__budget_baseline_post"]; + /** + * Clear Budget Baseline + * @description Remove the budget baseline. + */ + delete: operations["clear_budget_baseline_projects__pid__budget_baseline_delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/save-template": { + "/projects/{pid}/budget/cashflow": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Save Template - * @description Capture the project's current records for `key` as a reusable template (data only). + * Budget Cashflow + * @description Cost-loaded schedule → monthly cash-flow / draw curve. Spreads each schedule activity's + * budgeted cost across its start→finish months for the cumulative construction S-curve — where the + * Schedule and Budget destinations meet (the PX's monthly cash need). */ - post: operations["save_template_projects__pid__modules__key__save_template_post"]; + get: operations["budget_cashflow_projects__pid__budget_cashflow_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/apply-template/{tid}": { + "/projects/{pid}/budget/gmp": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Apply Template - * @description Instantiate a template into the project — one new record per item. + * Gmp Budget + * @description Full GC project budget (GMP): direct trade work (by CSI division + bid package) + General + * Conditions / Requirements (incl. staffing projections) + Overhead + Fee + Contingency, each + * tracked budget vs committed vs actual vs variance. Reconciles to the prime-contract value and to + * the developer proforma's construction hard-cost line — the PX's on-budget view, under Schedule. */ - post: operations["apply_template_projects__pid__modules__key__apply_template__tid__post"]; + get: operations["gmp_budget_projects__pid__budget_gmp_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/dashboard": { + "/projects/{pid}/budget/two-sided": { parameters: { query?: never; header?: never; @@ -4489,10 +5009,11 @@ export interface paths { cookie?: never; }; /** - * Get Dashboard - * @description Dashboard tailored to `party` (defaults to the caller's project party role). + * Two Sided Budget + * @description The development budget as Uses (left) vs Sources (right) — from the latest scenario if one is + * saved, else built from the project's cost budget + the supplied debt/equity params. */ - get: operations["get_dashboard_projects__pid__dashboard_get"]; + get: operations["two_sided_budget_projects__pid__budget_two_sided_get"]; put?: never; post?: never; delete?: never; @@ -4501,7 +5022,7 @@ export interface paths { patch?: never; trace?: never; }; - "/portfolio/construction": { + "/projects/{pid}/budget/variance": { parameters: { query?: never; header?: never; @@ -4509,11 +5030,12 @@ export interface paths { cookie?: never; }; /** - * Construction Portfolio - * @description Owner / program view: construction health across all projects — cost over/under (flags - * forecast overruns), open risks + cost exposure, recordable incidents, open RFIs. + * Budget Variance + * @description Movement of the GMP budget vs the baseline: total delta + per-category and per-line deltas + * (positive = grown since baseline). 409 if no baseline is set. Shows how the budget has drifted + * from the plan of record — the on-budget tracking a PX reports. */ - get: operations["construction_portfolio_portfolio_construction_get"]; + get: operations["budget_variance_projects__pid__budget_variance_get"]; put?: never; post?: never; delete?: never; @@ -4522,7 +5044,7 @@ export interface paths { patch?: never; trace?: never; }; - "/portfolio/executive": { + "/projects/{pid}/bundle": { parameters: { query?: never; header?: never; @@ -4530,13 +5052,18 @@ export interface paths { cookie?: never; }; /** - * Executive Portfolio - * @description Cross-project executive roll-up — every project's on-schedule (SPI, % complete, lookahead, - * late milestones) next to on-budget (GMP, EAC, variance-at-completion) with its overall status, - * plus portfolio totals and a status tally. The 'how's the whole book doing?' view, built on the - * same px-summary each project's dashboard shows. + * Export Bundle + * @description Download the whole project as a portable **`.mass` container** (geometry + all data + + * blobs). It is a ZIP that documents itself: a README inside explains the layout, and the + * manifest inventories every entry and names what was deliberately excluded. + * + * `asset_rights=true` additionally seals the container with a signed release manifest + * (`asset_rights.json`). It is **off by default** and is a choice made when the file is created: + * a container exported without it is unchanged from what this endpoint has always produced, and + * the project gains no `asset_id`. The option is inert unless an operator has switched the + * capability on (`AEC_ASSET_RIGHTS_ENABLED`). */ - get: operations["executive_portfolio_portfolio_executive_get"]; + get: operations["export_bundle_projects__pid__bundle_get"]; put?: never; post?: never; delete?: never; @@ -4545,7 +5072,7 @@ export interface paths { patch?: never; trace?: never; }; - "/portfolio/prioritization": { + "/projects/{pid}/cam/reconciliation": { parameters: { query?: never; header?: never; @@ -4553,12 +5080,11 @@ export interface paths { cookie?: never; }; /** - * Portfolio Prioritization - * @description Ranked portfolio prioritization — scores each accessible project 0–100 on return / on-budget / - * on-schedule / delivery-risk and ranks them, reusing the executive-portfolio rows (and its - * membership scoping). Weights default to a return-leaning blend. + * Cam Reconciliation + * @description CAM true-up for an operating year: recoverable pool (variable lines grossed up to the stated + * occupancy), per-tenant pro-rata share vs estimated payments, balance due/credit. */ - get: operations["portfolio_prioritization_portfolio_prioritization_get"]; + get: operations["cam_reconciliation_projects__pid__cam_reconciliation_get"]; put?: never; post?: never; delete?: never; @@ -4567,28 +5093,30 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/safety/metrics": { + "/projects/{pid}/cam/statement/{rid}.pdf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Safety Metrics - * @description Safety analytics: incidents by OSHA class, recordable/lost-time counts, lost days, and - * TRIR/DART (per 200k hours) using `hours` or hours summed from timesheets + manpower logs. + * Cam Statement + * @description Per-tenant CAM reconciliation statement (PDF) for the lease record `rid`. + * + * POST so the audit commit is not a cookie-bearing GET. SameSite=Lax sends the + * session cookie on a top-level GET from another origin; it withholds it on POST. */ - get: operations["safety_metrics_projects__pid__safety_metrics_get"]; - put?: never; - post?: never; + post: operations["cam_statement_projects__pid__cam_statement__rid__pdf_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/capabilities": { + "/projects/{pid}/cap-table": { parameters: { query?: never; header?: never; @@ -4596,11 +5124,10 @@ export interface paths { cookie?: never; }; /** - * Capabilities - * @description Which optional integrations are wired (for at-a-glance status badges). Not sensitive — - * just feature flags + the configured SSO provider ids. + * Cap Table + * @description Investor cap table — ownership by commitment + contributed/distributed/unreturned totals. */ - get: operations["capabilities_capabilities_get"]; + get: operations["cap_table_projects__pid__cap_table_get"]; put?: never; post?: never; delete?: never; @@ -4609,28 +5136,28 @@ export interface paths { patch?: never; trace?: never; }; - "/license": { + "/projects/{pid}/capital-call": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * License State - * @description The Massing licence state — plan tier, per-tier feature entitlements, and whether a valid key - * is recorded (key is masked, never returned in full). Drives the Settings licence panel. + * Capital Call + * @description Allocate a capital call pro-rata by commitment. persist=true posts it to each investor's + * contributed total; otherwise it's a preview. */ - get: operations["license_state_license_get"]; - put?: never; - post?: never; + post: operations["capital_call_projects__pid__capital_call_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ai/risk-summary": { + "/projects/{pid}/carbon": { parameters: { query?: never; header?: never; @@ -4638,10 +5165,11 @@ export interface paths { cookie?: never; }; /** - * Risk Summary - * @description AI/rules risk read over the project dashboard (owner/PM reporting). + * Project Carbon + * @description Embodied carbon (A1-A3) from `production_quantity` records: per-line kgCO2e, total tCO2e, and + * rollups by material + cost code. Built-in EPD factors (design-stage signal, not a certified LCA). */ - get: operations["risk_summary_projects__pid__ai_risk_summary_get"]; + get: operations["project_carbon_projects__pid__carbon_get"]; put?: never; post?: never; delete?: never; @@ -4650,7 +5178,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/risk-digest": { + "/projects/{pid}/carbon/compliance": { parameters: { query?: never; header?: never; @@ -4658,10 +5186,12 @@ export interface paths { cookie?: never; }; /** - * Risk Digest - * @description Project risk digest across cost + schedule + open items + safety, with a prioritized narrative. + * Carbon Compliance Report + * @description CARBON-EC3: the compliance view — the Buy Clean GWP-limit check per material category (a fail = + * "this category needs a product EPD", the procurement action the program forces) + the LEED-v5-style + * A1–A3 inventory (mandatory for projects registering after 2026-07-01). 404 until a model is loaded. */ - get: operations["risk_digest_projects__pid__risk_digest_get"]; + get: operations["carbon_compliance_report_projects__pid__carbon_compliance_get"]; put?: never; post?: never; delete?: never; @@ -4670,49 +5200,51 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/ai/ask": { + "/projects/{pid}/carbon/elements": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Ai Ask - * @description Ask a natural-language question about the project; answered (by Claude when configured) - * against a live snapshot of KPIs, costs and open items. Degrades to returning the snapshot. + * Carbon Elements + * @description CARBON-EC3: per-element A1–A3 straight off the loaded model — material category from each + * element's name/type/material psets, quantity from its own Qto sets, carbon keyed by GlobalId + * (hotspots click through to 3D). Honest coverage %: unmatched elements are excluded, never guessed. + * 404 until a model is loaded. */ - post: operations["ai_ask_projects__pid__ai_ask_post"]; + get: operations["carbon_elements_projects__pid__carbon_elements_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ai/estimate": { + "/projects/{pid}/cde/exchange-acceptance": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Ai Estimate - * @description Draft a Bill of Quantities from a plain-text project description (Claude when configured; - * a graceful stub otherwise — never fabricates numbers without the model). + * Cde Exchange Acceptance + * @description ISO 19650-6 information-exchange acceptance — each exchanged (non-WIP) container reviewed against + * completeness / suitability / authorization / traceability, with the non-conforming ones flagged. */ - post: operations["ai_estimate_projects__pid__ai_estimate_post"]; + get: operations["cde_exchange_acceptance_projects__pid__cde_exchange_acceptance_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/report.pdf": { + "/projects/{pid}/cde/status": { parameters: { query?: never; header?: never; @@ -4720,10 +5252,12 @@ export interface paths { cookie?: never; }; /** - * Status Report - * @description One-page project status report (KPIs, cost, open items by module, ball-in-court) as a PDF. + * Cde Status + * @description CDE container rollup (ISO 19650): state distribution WIP/Shared/Published/Archived, + * suitability spread, and CDE-discipline metrics (revision control, approval-status coverage, + * metadata completeness). */ - get: operations["status_report_projects__pid__report_pdf_get"]; + get: operations["cde_status_projects__pid__cde_status_get"]; put?: never; post?: never; delete?: never; @@ -4732,117 +5266,113 @@ export interface paths { patch?: never; trace?: never; }; - "/proforma/solve": { + "/projects/{pid}/change-orders/log": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Solve Stateless - * @description Solve a deal without persisting — full S&U, cash flows, returns, waterfall, plus underwriting - * guardrails (U5) that flag returns outside typical market bands. + * Co Log + * @description Change-order log — CO value pipeline (pending/approved/executed), reason mix, schedule exposure. */ - post: operations["solve_stateless_proforma_solve_post"]; + get: operations["co_log_projects__pid__change_orders_log_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/financials": { + "/projects/{pid}/ci/latest": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Financials Stateless - * @description Three financial statements + tax for a deal, without persisting: income statement (NOI → - * depreciation → interest → tax → net income), balance sheet (balances), GAAP cash-flow statement, - * depreciation/tax schedule with sale recapture + capital gains, after-tax returns, two-sided budget. + * Ci Latest + * @description The project's last MODEL-CI report (the badge source). */ - post: operations["financials_stateless_proforma_financials_post"]; + get: operations["ci_latest_projects__pid__ci_latest_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/financials": { + "/projects/{pid}/ci/run": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Project Financials - * @description Financial statements for the project's latest saved scenario (income statement · balance sheet · - * cash flow · tax · after-tax returns · two-sided budget). + * Ci Run + * @description MODEL-CI — run the model check pack (rule library, completeness, clash, pinned IDS, quantity + * drift) → a pass/warn/fail report + badge, persisted as the project's latest CI result. With + * `create_topics=true`, each FAILING check becomes an open coordination Topic (BCF-model), so a CI + * failure round-trips to Solibri / ACC / BIMcollab like any other issue. */ - get: operations["project_financials_projects__pid__financials_get"]; - put?: never; - post?: never; + post: operations["ci_run_projects__pid__ci_run_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/budget/two-sided": { + "/projects/{pid}/clash": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Two Sided Budget - * @description The development budget as Uses (left) vs Sources (right) — from the latest scenario if one is - * saved, else built from the project's cost budget + the supplied debt/equity params. + * Run Clash + * @description Detect clashes between two element groups: IFC-class lists (comma-separated `a`/`b`) and/or + * QUERY-DSL selectors (`a_q`/`b_q`, e.g. `IfcDuctSegment & storey=L3`) — selectors scope a side to + * the matching GUID set, composing with the class filter. narrow=true runs the mesh + * boolean-intersection narrow phase. With create_topics=true, top clashes become BCF topics. */ - get: operations["two_sided_budget_projects__pid__budget_two_sided_get"]; - put?: never; - post?: never; + post: operations["run_clash_projects__pid__clash_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/dev-budget": { + "/projects/{pid}/clash/analyze": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Dev Budget - * @description The project's developer cost budget (line-item hard/soft/acquisition + contingencies) plus a - * computed summary. Returns a starter budget if none is saved yet. - */ - get: operations["get_dev_budget_projects__pid__dev_budget_get"]; - /** - * Put Dev Budget - * @description Save the developer cost budget; returns the recomputed summary. + * Analyze Clashes + * @description Preview grouping + severity for a clash result set WITHOUT writing any issues (dry run). */ - put: operations["put_dev_budget_projects__pid__dev_budget_put"]; - post?: never; + post: operations["analyze_clashes_projects__pid__clash_analyze_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/specialty": { + "/projects/{pid}/clash/clearance-rules": { parameters: { query?: never; header?: never; @@ -4850,16 +5380,12 @@ export interface paths { cookie?: never; }; /** - * Get Specialty - * @description Specialty assets (on-site energy + vertical-farm/PFAL) params + computed summary (capex, - * annual revenue/opex/energy-offset). Starter params if none saved. - */ - get: operations["get_specialty_projects__pid__specialty_get"]; - /** - * Put Specialty - * @description Save specialty-asset params; returns the recomputed summary + proforma deltas. + * Clash Clearance Rules + * @description The soft-clash clearance rules — each with the code or manufacturer basis it came from, so a + * finding can be argued with rather than merely asserted. */ - put: operations["put_specialty_projects__pid__specialty_put"]; + get: operations["clash_clearance_rules_projects__pid__clash_clearance_rules_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -4867,74 +5393,78 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/property": { + "/projects/{pid}/clash/coordinate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Property - * @description Property & tax assumptions + computed summary (totals, per-SF ratios, proforma deltas). - */ - get: operations["get_property_projects__pid__property_get"]; - /** - * Put Property - * @description Save property & tax assumptions; returns the recomputed summary. + * Coordinate Clashes + * @description Group + score + reconcile a clash result set into tracked coordination issues. Body: + * `{clashes:[{a_guid,b_guid,a_class,b_class,a_model?,b_model?,volume,point:{x,y,z}}, …], label?}`. + * Lets a client run detection (federated or single) and post the results for the intelligence pass. */ - put: operations["put_property_projects__pid__property_put"]; - post?: never; + post: operations["coordinate_clashes_projects__pid__clash_coordinate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/sources-uses": { + "/projects/{pid}/clash/federated": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Get Sources Uses - * @description Sources & Uses built from the project's cost budget — grouped Uses (acquisition/hard/soft/ - * contingency + construction-loan interest) vs sized Sources (senior debt by LTC + LP/GP equity). + * Run Clash Federated + * @description Cross-discipline (federated) clash across 2+ models. Intra-model overlaps are excluded. + * If no `disciplines` map is given, it's built from the project's own models — the primary source + * IFC + any appended discipline models (POST /projects/{pid}/models). `create_topics=true` dumps the + * top clashes as raw BCF clash topics; **`coordinate=true`** instead runs the intelligence layer — + * grouping the raw clashes into tracked `coordination_issue`s, scoring severity, and reconciling + * against the prior run (new / active / resolved / reappeared). */ - get: operations["get_sources_uses_projects__pid__sources_uses_get"]; - put?: never; - post?: never; + post: operations["run_clash_federated_projects__pid__clash_federated_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/investment-memo.pdf": { + "/projects/{pid}/clash/matrix": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Investment Memo - * @description Confidential investment memorandum (PDF) composed from live project data — executive summary, - * Sources & Uses, the development cost budget, returns (from the latest solved scenario), and a - * risk read. The 'generate a presentation with financials' deliverable. + * Clash Matrix + * @description R21-SOFT-CLASH — the discipline-pair coordination matrix. + * + * Every pair is `clashes`, `clean` or **`untested`**, and the third is the point: a pair nobody ran + * is reported as untested and never folded into the clean count, so "clash-free" cannot be claimed + * over a partial matrix. `coordinated` is true only at full coverage with zero findings. */ - get: operations["investment_memo_projects__pid__investment_memo_pdf_get"]; - put?: never; - post?: never; + post: operations["clash_matrix_projects__pid__clash_matrix_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/investment-deck.pdf": { + "/projects/{pid}/clash/metrics": { parameters: { query?: never; header?: never; @@ -4942,10 +5472,10 @@ export interface paths { cookie?: never; }; /** - * Investment Deck - * @description Pitch-deck (slide) variant of the investment memo — landscape, big numbers, the ask. + * Clash Metrics + * @description Clash coordination KPIs — status mix, worst discipline pairs, severity, aging, run burn-down. */ - get: operations["investment_deck_projects__pid__investment_deck_pdf_get"]; + get: operations["clash_metrics_projects__pid__clash_metrics_get"]; put?: never; post?: never; delete?: never; @@ -4954,7 +5484,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/dev-budget/gmp-reconciliation": { + "/projects/{pid}/clash/sequence": { parameters: { query?: never; header?: never; @@ -4962,12 +5492,20 @@ export interface paths { cookie?: never; }; /** - * Gmp Reconciliation - * @description Tie the developer's construction **hard cost** to the GC's actual **GMP**: the proforma was - * underwritten with a hard-cost line; the GC manages a live GMP (buyout + GC/GR + OH/fee). This - * shows them side by side so the developer sees whether construction is tracking the underwriting. + * Clash Sequence + * @description R21-4D-CLASH — space contention plus install-before-support. + * + * Space contention: two trades scheduled into one location in one window. Nothing in the model is + * wrong when this happens — every element clears every other — which is why a geometric clash run + * cannot find it. Same-trade overlap is not a finding (one trade sequencing its own crews is + * planning). Activities without a location or dates are skipped and counted, so a clean result is + * never claimed over a schedule that was half unreadable. + * + * Install-before-support reads directed pairs from the source IFC (`support_graph`) and the + * activities' `element_guids`. A project with a schedule but no IFC still returns space contention; + * the support half reports as checked-with-zero-pairs rather than inventing geometry. */ - get: operations["gmp_reconciliation_projects__pid__dev_budget_gmp_reconciliation_get"]; + get: operations["clash_sequence_projects__pid__clash_sequence_get"]; put?: never; post?: never; delete?: never; @@ -4976,52 +5514,59 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/dev-budget/sync-gmp": { + "/projects/{pid}/classify/coverage": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Sync Gmp To Hard - * @description Set the developer budget's construction hard cost to the GC's GMP — one click ties the - * underwriting to the live construction number. Replaces hard lines with a single synced GMP line; - * soft / acquisition / contingency are untouched. Returns the recomputed budget summary. + * Classify Coverage + * @description How much of the model actually carries a classification code, and what the rest would need. + * + * **The percentage counts only what the model declares.** Derived codes are proposals and do not + * raise it. A low figure on a client's model is the true starting point of a cost exercise — the + * alternative is the implicit 100% you get from a class map that silently returns its default + * bucket for every unmapped class. */ - post: operations["sync_gmp_to_hard_projects__pid__dev_budget_sync_gmp_post"]; + get: operations["classify_coverage_projects__pid__classify_coverage_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/loan-draws": { + "/projects/{pid}/classify/plan": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Loan Draws - * @description Construction-loan draw status from the GC's actual billing: owner invoices are the developer's - * draws to pay the GC, funded equity-first then debt. Returns the sized loan/equity (from Sources & - * Uses) vs drawn-to-date, the equity/loan split, and remaining loan availability — so the developer - * tracks the capital stack against what the contractor has actually billed. + * Classify Plan + * @description Turn an explicit list of accepted GUIDs into `set_classification` recipe steps. + * + * Returns a plan; it does not apply it. Accepting a proposal is a deliberate act with an author, + * and a classification that appeared on elements because somebody opened a report is + * indistinguishable a week later from one a quantity surveyor decided. Anything that cannot be + * applied comes back in `skipped` **with a reason** rather than being dropped — a plan that + * silently covers 40 of the 60 elements somebody ticked is worse than one that refuses, because + * the other 20 look done. */ - get: operations["loan_draws_projects__pid__loan_draws_get"]; - put?: never; - post?: never; + post: operations["classify_plan_projects__pid__classify_plan_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/loan-draws/request.pdf": { + "/projects/{pid}/classify/proposals": { parameters: { query?: never; header?: never; @@ -5029,11 +5574,15 @@ export interface paths { cookie?: never; }; /** - * Loan Draw Request Pdf - * @description The lender draw-request as a PDF — this draw (the GC pay-app amount due) against the - * construction loan, with cumulative draws, equity/loan split, balance, and availability. + * Classify Proposals + * @description One row per element: its code and the evidence for it. + * + * `basis` says what kind of claim each row is. `class_map` rows are true of the element's CLASS and + * therefore identical for every element of that class — including the ones they are wrong for — so + * they are worth reviewing as a group rather than one at a time. `unmapped` rows have no code at + * all; those are the ones an estimate would silently price in the default bucket. */ - get: operations["loan_draw_request_pdf_projects__pid__loan_draws_request_pdf_get"]; + get: operations["classify_proposals_projects__pid__classify_proposals_get"]; put?: never; post?: never; delete?: never; @@ -5042,7 +5591,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/construction-draws": { + "/projects/{pid}/client-decisions": { parameters: { query?: never; header?: never; @@ -5050,12 +5599,11 @@ export interface paths { cookie?: never; }; /** - * Construction Draws - * @description The developer's construction draw schedule, sourced from the GC's cost-loaded schedule (the - * same monthly S-curve behind on-schedule × on-budget) and actual owner invoices billed to date — - * so the developer's draw projection is the contractor's real plan, not a generic curve. + * List Client Decisions + * @description PORTAL-TXN — the project's client-decision feed (approve/acknowledge/decline recorded through share + * tokens), newest first (editor only). */ - get: operations["construction_draws_projects__pid__construction_draws_get"]; + get: operations["list_client_decisions_projects__pid__client_decisions_get"]; put?: never; post?: never; delete?: never; @@ -5064,7 +5612,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/dev-budget/cost-lines": { + "/projects/{pid}/closeout/package.zip": { parameters: { query?: never; header?: never; @@ -5072,10 +5620,12 @@ export interface paths { cookie?: never; }; /** - * Dev Budget Cost Lines - * @description The budget rolled into proforma cost_lines (the seed the Finance view applies). + * Closeout Package + * @description Turnover deliverable in one ZIP: the as-built IFC, COBie / QTO / space-schedule workbooks, + * the status-report PDF, and a JSON manifest of the closeout records (commissioning, O&M, + * warranties, as-builts, asset register, completion certificate, punchlist). */ - get: operations["dev_budget_cost_lines_projects__pid__dev_budget_cost_lines_get"]; + get: operations["closeout_package_projects__pid__closeout_package_zip_get"]; put?: never; post?: never; delete?: never; @@ -5084,7 +5634,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/proforma/model-metrics": { + "/projects/{pid}/closeout/summary": { parameters: { query?: never; header?: never; @@ -5092,11 +5642,10 @@ export interface paths { cookie?: never; }; /** - * Proforma Model Metrics - * @description Metrics from the project's source IFC, so the proforma can underwrite against the real - * model (areas → hard cost / rent, etc.) instead of hand-keyed numbers. 409 if no source IFC. + * Closeout Summary + * @description Closeout analytics — punchlist completion/ball-in-court, commissioning, certificates, warranties, O&M. */ - get: operations["proforma_model_metrics_projects__pid__proforma_model_metrics_get"]; + get: operations["closeout_summary_projects__pid__closeout_summary_get"]; put?: never; post?: never; delete?: never; @@ -5105,7 +5654,7 @@ export interface paths { patch?: never; trace?: never; }; - "/proforma/sensitivity": { + "/projects/{pid}/cmms/generate-pm": { parameters: { query?: never; header?: never; @@ -5115,66 +5664,59 @@ export interface paths { get?: never; put?: never; /** - * Run Sensitivity - * @description Two-variable data table: the metric solved across the x×y grid of two drivers. + * Generate Pm + * @description Create preventive work orders for every active PM schedule that's due (idempotent per cycle: + * a schedule with an open PM work order is skipped); advances each schedule's next-due date. */ - post: operations["run_sensitivity_proforma_sensitivity_post"]; + post: operations["generate_pm_projects__pid__cmms_generate_pm_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/monte-carlo": { + "/projects/{pid}/cmms/kpis": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Run Monte Carlo - * @description Probabilistic risk analysis: sample the given drivers, solve each draw, and return the - * distribution (percentiles, mean/std, P[≥target], histogram) of each output metric. + * Cmms Kpis + * @description Maintenance KPIs: open by priority/type, overdue, PM compliance %, MTTR (days). */ - post: operations["run_monte_carlo_proforma_monte_carlo_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/proforma/scenarios": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List Scenarios */ - get: operations["list_scenarios_proforma_scenarios_get"]; + get: operations["cmms_kpis_projects__pid__cmms_kpis_get"]; put?: never; - /** Create Scenario */ - post: operations["create_scenario_proforma_scenarios_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/scenarios/{sid}": { + "/projects/{pid}/code/amendments": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Get Scenario */ - get: operations["get_scenario_proforma_scenarios__sid__get"]; - /** Update Scenario */ - put: operations["update_scenario_proforma_scenarios__sid__put"]; + /** + * Get Code Amendments + * @description CODE-4: the project's local-amendment overlay + the resolved code context with it applied — + * edition overrides show `source: "amendment"`, recorded section amendments ride along so sheets + * and citations can flag locally-amended sections. Set via PUT; empty when none recorded. + */ + get: operations["get_code_amendments_projects__pid__code_amendments_get"]; + /** + * Put Code Amendments + * @description CODE-4: record the AHJ's local amendments for this project. Each entry: `family` (required — + * IBC/IECC/…), optional `edition` (a published edition year — overrides the statewide adoption for + * every code check), optional `section` + `note` (a recorded section amendment). Validated hard — + * 422 lists every problem. An empty list clears the overlay. + */ + put: operations["put_code_amendments_projects__pid__code_amendments_put"]; post?: never; delete?: never; options?: never; @@ -5182,7 +5724,7 @@ export interface paths { patch?: never; trace?: never; }; - "/proforma/scenarios/{sid}/share": { + "/projects/{pid}/codecheck": { parameters: { query?: never; header?: never; @@ -5192,54 +5734,62 @@ export interface paths { get?: never; put?: never; /** - * Share Scenario - * @description Grant an LP (or any party) read access to this scenario. + * Code Check + * @description Applicable IBC/ADA/IECC provisions (code + section + requirement) for the described project. + * Claude when an API key is set; a deterministic IBC checklist otherwise. Always confirm with the AHJ. */ - post: operations["share_scenario_proforma_scenarios__sid__share_post"]; + post: operations["code_check_projects__pid__codecheck_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/scenarios/{sid}/clone": { + "/projects/{pid}/codecheck/analysis": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Code Analysis + * @description W11 D1: the IBC **code-analysis summary** for the G-series code sheet — occupancy classification, + * construction type, gross area + stories, computed occupant load + egress, and the governing sections + * for allowable area/height and fire ratings. Pre-check assist; verify allowable area with the AHJ. + */ + get: operations["code_analysis_projects__pid__codecheck_analysis_get"]; put?: never; - /** Clone Scenario */ - post: operations["clone_scenario_proforma_scenarios__sid__clone_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/scenarios/{sid}/forecast": { + "/projects/{pid}/codecheck/approvability": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Forecast Scenario - * @description Re-forecast the underwritten returns against actuals drawn to date (Phase 5 bridge). + * Approvability + * @description W11 D8: a **plan-reviewer pre-flight checklist** — egress traced, doors at clear width, occupancy + * classified, fire-rated assemblies substantiated — with a readiness score. Pre-check assist; NOT a + * certified review. Needs a source IFC. */ - post: operations["forecast_scenario_proforma_scenarios__sid__forecast_post"]; + get: operations["approvability_projects__pid__codecheck_approvability_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/forecast": { + "/projects/{pid}/codecheck/approvability/bcf": { parameters: { query?: never; header?: never; @@ -5248,37 +5798,46 @@ export interface paths { }; get?: never; put?: never; - /** Forecast Stateless */ - post: operations["forecast_stateless_proforma_forecast_post"]; + /** + * Approvability To Bcf + * @description D8: promote every **failed approvability check** (egress, occupancy classification, fire-rated + * substantiation, the COMcheck WWR/U-value layer, accessible entrance) to a **BCF topic** — the + * plan-review punchlist becomes trackable, GUID-anchored issues that round-trip with clashes/RFIs. + * Idempotent: re-running clears prior approvability topics first. `info` checks ride along at + * normal priority (they need a documented answer, not a fix). + */ + post: operations["approvability_to_bcf_projects__pid__codecheck_approvability_bcf_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/scenarios/{sid}/draw-package": { + "/projects/{pid}/codecheck/ebc": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Draw Package - * @description Bridge underwriting → construction draws: turn the scenario's cost tree + actuals into - * Schedule-of-Values records on a GC project, then produce the AIA G702/G703 pay app — - * so the IRR you underwrote and the lender draw run off the SAME cost tree. + * Codecheck Ebc + * @description CODE-EBC: classify an existing-building scope under the IEBC Work Area Compliance Method → + * Repair · Alteration Level 1/2/3 · Change of Occupancy · Addition, with the driving citations and the + * jurisdiction's adopted IEBC edition. Pass explicit scope flags; with `infer=true` the scope is + * first-guessed from the model's phasing (existing vs new/demolish) and any flags you pass override the + * guess. Preliminary classification — the AHJ makes the determination. */ - post: operations["draw_package_proforma_scenarios__sid__draw_package_post"]; + get: operations["codecheck_ebc_projects__pid__codecheck_ebc_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/proforma/portfolio": { + "/projects/{pid}/codecheck/egress": { parameters: { query?: never; header?: never; @@ -5286,12 +5845,14 @@ export interface paths { cookie?: never; }; /** - * Portfolio - * @description Multi-deal roll-up across all solved scenarios: total capitalization, equity-weighted - * blended IRR, aggregate equity multiple, and per-deal metrics. Scoped to the caller's projects - * so the roll-up never blends other tenants' deals. + * Codecheck Egress + * @description W9-2: COMPUTED occupancy load (IBC 1004) + egress capacity (IBC 1005) from the model's + * IfcSpaces/IfcDoors — the depth layer above the presence-only /elements/code-check. Reads spaces + * straight from the source IFC (they aren't in the physical-element index). CODE-3: the IBC + * **edition auto-resolves from the project's jurisdiction** (set it via PATCH /projects/{pid}); + * unset → national baseline. Pre-check assist with cited IBC sections; NOT a certified review. */ - get: operations["portfolio_proforma_portfolio_get"]; + get: operations["codecheck_egress_projects__pid__codecheck_egress_get"]; put?: never; post?: never; delete?: never; @@ -5300,7 +5861,7 @@ export interface paths { patch?: never; trace?: never; }; - "/proforma/compare": { + "/projects/{pid}/codecheck/egress/bcf": { parameters: { query?: never; header?: never; @@ -5310,59 +5871,76 @@ export interface paths { get?: never; put?: never; /** - * Compare - * @description Side-by-side metrics for several scenarios. + * Egress To Bcf + * @description W9-2b: promote the computed egress/code findings to **BCF topics** — so a below-min door or an + * egress shortfall becomes a trackable issue that round-trips with clashes/RFIs (keyed by GlobalId). + * Idempotent-ish: re-running adds fresh topics, so run once per review. */ - post: operations["compare_proforma_compare_post"]; + post: operations["egress_to_bcf_projects__pid__codecheck_egress_bcf_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/generate/massing": { + "/projects/{pid}/collab": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Generate Massing - * @description Generate an IFC massing model from a zoning envelope, set it as the project's source IFC, - * publish it (off-thread), and return the buildable program + a starter acquisition proforma. + * Collab Snapshot + * @description COLLAB-1: the live co-editing picture — the current model signature (bumps on every publish) plus + * the roster of other users present. A second client polls/streams this to know when to reload the + * model and who else is in the session. */ - post: operations["generate_massing_projects__pid__generate_massing_post"]; + get: operations["collab_snapshot_projects__pid__collab_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/structure/recommend": { + "/projects/{pid}/commercial-drift": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Structure Recommend - * @description Recommend a structural system + rough member sizes + load path for a building's scale (R3, - * Salvadori). Stateless; the same advisor drives the generated frame's member sizing. + * Commercial Drift Walk + * @description R41-COMMERCIAL-DRIFT — diff the money across DOCUMENTS: bid → executed contract → invoiced, + * walked along references the registers already carry (`subcontract.awarded_from`, + * `sub_invoice.subcontract`). One row per subcontract, largest movement first. + * + * **This is the diff `/margin/by-costcode` and `/cost-spine` structurally cannot do.** Both measure + * per cost code, and a roll-up adds before it compares — two subcontracts can net to the right code + * total while one award drifted 15% up and another 15% down. Read this for the individual award. + * + * Two things are deliberately NOT called drift. **Change orders** are money somebody signed for, so + * they sit in the contract→invoiced hop as part of the agreed sum and never in bid→contract; + * counting them as drift would flag every project that has a CO, which is every project. And an + * **unaccepted alternate** was never bought, so the comparable bid figure is base bid plus accepted + * alternates, with the row stating which basis it used. + * + * A hop missing a figure on either side is `incomparable` and counted separately — that is not the + * same as a zero-dollar difference. */ - post: operations["structure_recommend_structure_recommend_post"]; + get: operations["commercial_drift_walk_projects__pid__commercial_drift_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/test-fit/compare": { + "/projects/{pid}/comparables/import": { parameters: { query?: never; header?: never; @@ -5372,75 +5950,80 @@ export interface paths { get?: never; put?: never; /** - * Test Fit Compare - * @description Compare unit-mix schemes on a floor plate — yield metrics (units, efficiency, NSF/GSF, mix) - * + parking — ranked so you can find the scheme that pencils. Stateless; the rects also feed the - * IFC massing generator (unit_layout='corridor'). + * Import Comparables + * @description Bulk-import comparables from CSV (`{csv}`) or a RESO array (`{reso|rows}`) into the `comparable` + * module — feeds the sales-comparison appraisal. Forgiving header mapping; rows without an address + * are skipped. Returns the created count + the parsed rows. */ - post: operations["test_fit_compare_test_fit_compare_post"]; + post: operations["import_comparables_projects__pid__comparables_import_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/test-fit/optimize": { + "/projects/{pid}/compliance/expiring": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Test Fit Optimize - * @description Generative design — sweep unit-mix × parking presets, filter by targets, rank by yield-on-cost - * (or another objective). With `pid`, seed the econ from the project's real land price + cost budget - * so the ranking reflects the actual deal, not a generic proxy (U6). + * Compliance Expiring + * @description Insurance certificates (COI) and permits expiring within `within_days`, plus any already + * expired — so a super sees lapsing compliance before it bites. Both key off the canonical + * `expires` date; closed permits are ignored. */ - post: operations["test_fit_optimize_test_fit_optimize_post"]; + get: operations["compliance_expiring_projects__pid__compliance_expiring_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/generate/massing/preview": { + "/projects/{pid}/comps/tiered": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Preview Massing - * @description Compute the program + proforma WITHOUT writing an IFC or touching a project — for the - * 'what would this lot yield?' form before committing to a model. Stateless, instant. + * Get Tiered Comps + * @description CRE-COMP-TIER (R20) — comps ranked by **source tier** (recorded sale > party-verified > + * vendor-confirmed > vendor estimate > listing > broker package > unattributed). + * + * Comps describing the same address are resolved by tier rather than averaged, with the overruled + * values kept beside the winner; and every derived band reports the **weakest tier it rests on**, + * so a median carried by one asking price can never read like one carried by six recorded sales. */ - post: operations["preview_massing_generate_massing_preview_post"]; + get: operations["get_tiered_comps_projects__pid__comps_tiered_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/concept-render/status": { + "/projects/{pid}/concept-render/ingest": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Concept Render Status - * @description Status of the (external, feature-flagged) AI concept-render bridge. + * Concept Render Ingest + * @description Ingest a generated image reference from the external service → stored as a `concept_render` record. + * No-op unless AEC_RENDER_BRIDGE is enabled. */ - get: operations["concept_render_status_projects__pid__concept_render_status_get"]; - put?: never; - post?: never; + post: operations["concept_render_ingest_projects__pid__concept_render_ingest_post"]; delete?: never; options?: never; head?: never; @@ -5468,49 +6051,62 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/concept-render/ingest": { + "/projects/{pid}/concept-render/status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Concept Render Ingest - * @description Ingest a generated image reference from the external service → stored as a `concept_render` record. - * No-op unless AEC_RENDER_BRIDGE is enabled. + * Concept Render Status + * @description Status of the (external, feature-flagged) AI concept-render bridge. */ - post: operations["concept_render_ingest_projects__pid__concept_render_ingest_post"]; + get: operations["concept_render_status_projects__pid__concept_render_status_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/program/summary": { + "/projects/{pid}/constraints/solve": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Program Summary - * @description Concept space-program rollup + adjacency graph: total/net/gross area, mix by use, the node/edge - * graph, unmet adjacency preferences, and the massing hints (gross area + use mix) it feeds. + * Constraints Solve + * @description W10-9 / R23-CONSTRAINTS — solve dimensional locks, or name what stopped them. + * + * Body: `{variables: {name: value}, constraints: [{kind, a, b?, value?, distance?, offset?, vars?, + * min?, strength?, id?}]}`. Kinds: `fix` · `equal` · `distance` · `offset` · `equal_spacing` · + * `min_clearance`. Strengths are **tiers, not weights** — `required` · `strong` · `medium` · `weak` + * — each satisfied exactly and frozen before the next may move what is left, so a preference can + * never bend a hard lock by a millimetre. + * + * Three things it refuses to do quietly. An **over-constrained** system names the constraints that + * cannot hold rather than satisfying whichever was reached first. An **under-constrained** one + * reports its remaining degrees of freedom instead of silently picking one of infinitely many + * solutions. And clearances are **checked, never enforced** — sliding geometry to satisfy a code + * minimum would move something somebody placed on purpose. + * + * Shipped as an engine in v0.3.701 and left unreachable until v0.3.711; it solved nothing in + * between. Pure computation over caller-supplied values — it neither reads nor writes the model. */ - get: operations["program_summary_projects__pid__program_summary_get"]; - put?: never; - post?: never; + post: operations["constraints_solve_projects__pid__constraints_solve_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/lifecycle": { + "/projects/{pid}/construction-draws": { parameters: { query?: never; header?: never; @@ -5518,11 +6114,12 @@ export interface paths { cookie?: never; }; /** - * Lifecycle - * @description The project's design phases (RIBA 0–7 ↔ AIA) with gate state, deliverables, ISO-19650 status, - * and the phase-allocated A/E design fee from the itemized soft costs. + * Construction Draws + * @description The developer's construction draw schedule, sourced from the GC's cost-loaded schedule (the + * same monthly S-curve behind on-schedule × on-budget) and actual owner invoices billed to date — + * so the developer's draw projection is the contractor's real plan, not a generic curve. */ - get: operations["lifecycle_projects__pid__lifecycle_get"]; + get: operations["construction_draws_projects__pid__construction_draws_get"]; put?: never; post?: never; delete?: never; @@ -5531,7 +6128,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/lifecycle/seed": { + "/projects/{pid}/content/import": { parameters: { query?: never; header?: never; @@ -5541,17 +6138,20 @@ export interface paths { get?: never; put?: never; /** - * Seed - * @description Seed the eight design-phase records on a project (idempotent). + * Content Import + * @description CONTENT-1 (import): import a detailed mesh (glTF / GLB / OBJ / STL / PLY) and place it as the **right + * IFC** — auto-detect the catalog category from the filename (or pass `category=`), parse the mesh + * (recentred, glTF Y-up → IFC Z-up), and author it via `place_content` (correct IFC class + phase + + * classification). License-vet the source asset before importing. Versioned + undo-able + republished. */ - post: operations["seed_projects__pid__lifecycle_seed_post"]; + post: operations["content_import_projects__pid__content_import_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/lifecycle/reference": { + "/projects/{pid}/contractor-statements": { parameters: { query?: never; header?: never; @@ -5559,10 +6159,11 @@ export interface paths { cookie?: never; }; /** - * Reference - * @description The canonical RIBA↔AIA phase definitions + soft-cost taxonomy (for the UI, no project needed). + * Contractor Statements + * @description Contractor financial statements: percentage-of-completion income statement (revenue earned, not + * billed) + the contract-position balance-sheet section (contract asset/liability, retainage, AP). */ - get: operations["reference_lifecycle_reference_get"]; + get: operations["contractor_statements_projects__pid__contractor_statements_get"]; put?: never; post?: never; delete?: never; @@ -5571,7 +6172,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/resilience/flood": { + "/projects/{pid}/contracts/playbook": { parameters: { query?: never; header?: never; @@ -5579,12 +6180,16 @@ export interface paths { cookie?: never; }; /** - * Resilience Flood - * @description Flood risk (ASCE 24 / FEMA): the Design Flood Elevation (BFE + freeboard) and the flood-proof-MEP - * check — asset-register items installed below the DFE, flagged to be elevated or flood-proofed. + * Get Clause Playbook + * @description CRE-CLAUSE (R20) — the stored clause-position playbook, plus the starter standard. */ - get: operations["resilience_flood_projects__pid__resilience_flood_get"]; - put?: never; + get: operations["get_clause_playbook_projects__pid__contracts_playbook_get"]; + /** + * Put Clause Playbook + * @description Replace the playbook (validated atomically: uniquely named clauses, a known severity, and a + * REQUIRED red line per clause — a clause with no refuse position is not a standard). + */ + put: operations["put_clause_playbook_projects__pid__contracts_playbook_put"]; post?: never; delete?: never; options?: never; @@ -5592,49 +6197,53 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/resilience/stormwater": { + "/projects/{pid}/contracts/review": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Resilience Stormwater - * @description Stormwater (Rational Method): peak runoff Q = C·i·A per catchment plus a first-order detention - * volume, so drainage is sized against a real design storm. + * Post Clause Review + * @description CRE-CLAUSE — record one document's review against the playbook. + * + * Every playbook clause with no finding comes back **not_reviewed**, never assumed acceptable: + * an unread clause is an open risk. Reading the contract stays a human job; this records what the + * reading found so two reviews are comparable. Not legal advice. */ - get: operations["resilience_stormwater_projects__pid__resilience_stormwater_get"]; - put?: never; - post?: never; + post: operations["post_clause_review_projects__pid__contracts_review_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/resilience/weather": { + "/projects/{pid}/contracts/{key}/{rid}/digital-sign": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Resilience Weather - * @description Weather-sequenced construction: weather-sensitive schedule activities, the site-weather-risk - * register, and weather-delay days rolled up from the daily reports. + * Digital Sign + * @description Apply a certificate-based PAdES digital signature to the contract/CO document — tamper-evident, + * self-validating. Renders the document, signs it, attaches the signed PDF, and records the signer + + * cert fingerprint on the record (audit). Falls back cleanly if signing isn't available. */ - get: operations["resilience_weather_projects__pid__resilience_weather_get"]; - put?: never; - post?: never; + post: operations["digital_sign_projects__pid__contracts__key___rid__digital_sign_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/resilience/climate-risk": { + "/projects/{pid}/contracts/{key}/{rid}/document.pdf": { parameters: { query?: never; header?: never; @@ -5642,11 +6251,13 @@ export interface paths { cookie?: never; }; /** - * Resilience Climate Risk - * @description Physical climate-risk rollup for ESG — flood exposure + stormwater load + site-weather hazards + - * logged weather delays folded into a single scored rating with the driving factors. + * Contract Document + * @description Render a contract/change document for a record. doc = agreement | prime | co | exhibit | asi + * (G710) | bulletin | ccd (G714, from a directive record). `clauses` is a comma-separated list of + * scope_library ids for Exhibit A (defaults to the record's trade). With attach=1 the PDF is also + * saved as an attachment on the record. */ - get: operations["resilience_climate_risk_projects__pid__resilience_climate_risk_get"]; + get: operations["contract_document_projects__pid__contracts__key___rid__document_pdf_get"]; put?: never; post?: never; delete?: never; @@ -5655,7 +6266,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/spine/traceability": { + "/projects/{pid}/contracts/{key}/{rid}/exhibit.docx": { parameters: { query?: never; header?: never; @@ -5663,12 +6274,18 @@ export interface paths { cookie?: never; }; /** - * Spine Traceability - * @description Discipline Spine traceability — trace discipline → sheets → specs → bid packages → cost codes → - * budget, with per-discipline rollups and the coverage gaps (unpackaged specs, unbudgeted packages, - * un-specced sheets) so scope can't fall between the model, the documents and the money. + * Contract Exhibit Docx + * @description Exhibit A as an editable Word document — the copy a subcontractor redlines and sends back. + * + * Same clause selection and same merge context as `document.pdf?doc=exhibit`; only the container + * differs. The PDF is the signed instrument, this is the negotiating copy, and scope negotiation is + * redlining — a PDF is the wrong object to hand somebody who has to strike what they do not hold. + * + * A separate path rather than `document.pdf?fmt=docx` because the extension is what a browser, a + * mail client and a document management system all key on; a `.pdf` URL that returns a Word file is + * the kind of thing that arrives at a subcontractor as a file their machine refuses to open. */ - get: operations["spine_traceability_projects__pid__spine_traceability_get"]; + get: operations["contract_exhibit_docx_projects__pid__contracts__key___rid__exhibit_docx_get"]; put?: never; post?: never; delete?: never; @@ -5677,90 +6294,100 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/design/options/compare": { + "/projects/{pid}/contracts/{key}/{rid}/send-for-signature": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Design Options Compare - * @description Compare the project's design options / variants apples-to-apples — program + economics per - * option, best-in-class per metric, deltas vs the selected option. The selected option's drawing set - * is the project's current documentation (2D regenerates live from the model). + * Send For Signature + * @description Route a contract/CO document through the configured 3rd-party e-signature provider (DocuSeal et + * al.) for legally-binding multi-party signing. `signers` is a list of {email, name?, party?}. Stores + * the submission id + per-signer signing URLs on the record `data.esign_submission` (audited). */ - get: operations["design_options_compare_projects__pid__design_options_compare_get"]; - put?: never; - post?: never; + post: operations["send_for_signature_projects__pid__contracts__key___rid__send_for_signature_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/design/standards": { + "/projects/{pid}/contracts/{key}/{rid}/sign": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Design Standards Ruleset - * @description The design-standards ruleset — approved / preferred / prohibited assemblies, materials, products. + * Sign Contract + * @description Record a party's signature on a contract/CO (typed name + date) on the record `data`, audited. + * One signature per party (re-signing replaces). Advancing the workflow state is a separate + * transition — sign captures the executed signature; the UI calls /transition to move the record. */ - get: operations["design_standards_ruleset_projects__pid__design_standards_get"]; - put?: never; - post?: never; + post: operations["sign_contract_projects__pid__contracts__key___rid__sign_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/design/standards/check": { + "/projects/{pid}/coordination/import-xlsx": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Design Standards Check - * @description Audit the loaded model against the design-standards ruleset (prohibited / non-approved - * type + material use). Returns the ruleset only when no model is loaded. + * Coordination Import Xlsx + * @description Import a Solibri / Navisworks (or any tabular) clash report XLSX -> one coordination_issue per + * row (GUIDs anchor it on the model; each round-trips to BCF). Sniffs the header + maps aliases. */ - get: operations["design_standards_check_projects__pid__design_standards_check_get"]; - put?: never; - post?: never; + post: operations["coordination_import_xlsx_projects__pid__coordination_import_xlsx_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/mep/schedule": { + "/projects/{pid}/coordination/import-xml": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Mep Schedule - * @description The MEP equipment schedule from the register + a per-system capacity rollup. + * Coordination Import Xml + * @description CLASH-TRIAGE: import a **native Navisworks clash-report XML** export → one coordination_issue per + * clash (name → subject, its clash test → discipline, type/distance/status → description; GUIDs anchor + * it on the model and each round-trips to BCF). Untrusted XML parsed with defusedxml. + * + * Enterprise-entitled when enforcement is on, no-op in open mode. `TIER_FEATURES` has marked + * `navisworks` Enterprise-only since the tiers were written and the Settings panel renders that + * table, but nothing consulted the entitlement — the same defect as `sso` in v0.3.1118. The sibling + * `/coordination/import-xlsx` is deliberately NOT gated: it reads Solibri and any tabular clash + * report, so it is not the Navisworks capability the table is selling. */ - get: operations["mep_schedule_projects__pid__mep_schedule_get"]; - put?: never; - post?: never; + post: operations["coordination_import_xml_projects__pid__coordination_import_xml_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/mep/model-extract": { + "/projects/{pid}/coordination/stale": { parameters: { query?: never; header?: never; @@ -5768,10 +6395,11 @@ export interface paths { cookie?: never; }; /** - * Mep Model Extract - * @description MEP elements read off the loaded model (by IFC class) — complements the register schedule. + * Coordination Stale + * @description SMART-VIEWS (clash freshness): open clash/coordination issues whose referenced elements changed + * between versions a→b — the ones likely resolved (or worsened) and worth a re-check. */ - get: operations["mep_model_extract_projects__pid__mep_model_extract_get"]; + get: operations["coordination_stale_projects__pid__coordination_stale_get"]; put?: never; post?: never; delete?: never; @@ -5780,28 +6408,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/capabilities": { + "/projects/{pid}/coordination/stale/recheck": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Model Capabilities - * @description IFC read-schema capabilities + the detected schema of this project's loaded model (IFC5/IFCX - * is detected and reported, not yet parsed). + * Coordination Stale Recheck + * @description Flag each stale clash topic with a `model-changed` label + a re-verify comment (idempotent; + * never auto-closes — the coordinator decides). */ - get: operations["model_capabilities_projects__pid__model_capabilities_get"]; - put?: never; - post?: never; + post: operations["coordination_stale_recheck_projects__pid__coordination_stale_recheck_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/drawings/sync-status": { + "/projects/{pid}/cost-spine": { parameters: { query?: never; header?: never; @@ -5809,12 +6437,16 @@ export interface paths { cookie?: never; }; /** - * Drawings Sync Status - * @description Model fingerprint + version for 2D staleness detection — the client compares `version` / - * `signature` across renders to know when the on-demand drawings need regenerating. `version` bumps - * every time a new model is published (see /drawings/stream for the push equivalent). + * Cost Spine Trace + * @description COST-SPINE — does one cost code carry the same scope from budget through commitment, actual and + * invoice? Reports **presence, not just amounts**: which stages each code reaches, where its chain + * first breaks, spend booked against codes nobody budgeted, records carrying no cost code at all, + * and codes used on records but missing from the project's cost-code register. + * + * The headline is `traceability_pct` — the share of committed+actual money sitting on a budgeted + * code. The margin report at /margin/by-costcode inherits that coverage, so read this first. */ - get: operations["drawings_sync_status_projects__pid__drawings_sync_status_get"]; + get: operations["cost_spine_trace_projects__pid__cost_spine_get"]; put?: never; post?: never; delete?: never; @@ -5823,7 +6455,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/drawings/stream": { + "/projects/{pid}/cost-vintage": { parameters: { query?: never; header?: never; @@ -5831,42 +6463,45 @@ export interface paths { cookie?: never; }; /** - * Drawings Stream - * @description Server-sent events: pushes the model `version` and re-pushes the instant it changes (a new model - * is published), so open 2D drawing views regenerate themselves — live propagation from the model - * without polling or an external event bus. + * Get Cost Vintage + * @description The cost vintage a project's estimate resolves through — its pinned dataset, else the latest. */ - get: operations["drawings_stream_projects__pid__drawings_stream_get"]; + get: operations["get_cost_vintage_projects__pid__cost_vintage_get"]; put?: never; - post?: never; + /** + * Set Cost Vintage + * @description Pin a project's estimate to a cost vintage. `{"dataset_id": "…"}` — null/absent = follow the latest + * installed vintage. Reproducibility: the estimate always prices through the pinned vintage. + */ + post: operations["set_cost_vintage_projects__pid__cost_vintage_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/mep/size": { + "/projects/{pid}/cost/advance-period": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Mep Size - * @description First-pass MEP sizing. kind = duct (flow=CFM, velocity=fpm) | pipe (flow=GPM, velocity=fps) | - * cooling (load=BTU/h) | hanger (hanger_kind=duct|pipe_steel|pipe_copper, size=in). + * Advance Period + * @description Close the current pay period (C1) — roll each SOV line's completed-this into completed-previous + * so the next pay application starts a fresh period. */ - get: operations["mep_size_projects__pid__mep_size_get"]; - put?: never; - post?: never; + post: operations["advance_period_projects__pid__cost_advance_period_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/envelope/audit": { + "/projects/{pid}/cost/calibration": { parameters: { query?: never; header?: never; @@ -5874,11 +6509,31 @@ export interface paths { cookie?: never; }; /** - * Envelope Audit - * @description Envelope code-compliance — every envelope assembly checked against IECC 2021 climate-zone - * minimums (opaque R-value / fenestration U-factor), with a compliance rollup. + * Cost Calibration + * @description COST-AGENT — **learn from this project's own history**: compare the model's takeoff estimate + * against what the project has actually **committed** (awarded subcontract values) and **spent** + * (posted direct costs), and derive a calibration factor (clamped 0.5–2.0). Reported, never + * silently applied — the estimator decides. Needs a source IFC; committed/actuals are optional + * (factor null without them). + * + * **There is no parameter that applies this, and that is stated rather than implied.** This + * docstring and `apply_hint` both used to say "pass `benchmark_factor` to + * `estimate_from_takeoff`". Two things were wrong with that. No HTTP surface accepts a + * caller-supplied `benchmark_factor` — every call site derives it from the project's cost vintage. + * And `benchmark_factor` is already spoken for: it puts the GFA benchmark in the SAME DOLLAR-YEAR + * as the model total, multiplying `gfa_sf * psf` and never the estimate. Feeding a calibration + * ratio into it would raise the benchmark, and since `estimate.estimate_from_takeoff` decides + * `trustworthy = element_count >= 10 and total >= 0.4 * benchmark`, that makes the model LESS + * likely to be trusted — flipping `recommended` to the GFA figure, now inflated by a ratio that + * has nothing to do with dollar-years. *Following the hint corrupted the one comparison the + * parameter it named exists to keep honest.* + * + * **The clamp is a disclosure problem, not a safety feature.** `max(0.5, min(2.0, ratio))` turns a + * job with one posted invoice against a whole-building estimate into a confident-looking `0.5`. + * All three totals are returned so a caller can recover the raw ratio and tell a measurement from + * a boundary; `apps/web/src/portal/panels/costCalibration.ts` is where that is done. */ - get: operations["envelope_audit_projects__pid__envelope_audit_get"]; + get: operations["cost_calibration_projects__pid__cost_calibration_get"]; put?: never; post?: never; delete?: never; @@ -5887,40 +6542,44 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/envelope/check": { + "/projects/{pid}/cost/estimate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Envelope Check - * @description Check a single assembly against IECC 2021 (element_type + climate_zone + r_value or u_factor). + * Cost Estimate + * @description 5D — price the model through the QUERY-DSL selector spine. + * + * Body: `{rules: [{selector, code, description?, basis, unit_cost}], quantities?: {guid: {basis: n}}}`. + * Rules layer: **later rules win**, and every element records which rule priced it, so a line traces + * back to the selector that produced it. + * + * Two numbers matter as much as the total: `unpriced` (elements no rule matched — never silently + * zeroed, because the total would still look like a total) and `missing_quantity` (a rate with + * nothing to multiply is an unknown cost, not a cost of nothing). `complete` is false whenever + * either is non-empty, which makes the total a floor rather than an estimate. */ - get: operations["envelope_check_projects__pid__envelope_check_get"]; - put?: never; - post?: never; + post: operations["cost_estimate_projects__pid__cost_estimate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/diligence/readiness": { + "/projects/{pid}/cost/g702": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Diligence Readiness - * @description Pre-acquisition go/no-go rollup: due-diligence items by category/state (cleared vs flagged vs - * open, high-risk flags) + entitlement applications by status (approved vs pending vs denied, - * approvals nearing expiration). The screen a developer reads before releasing contingencies. - */ - get: operations["diligence_readiness_projects__pid__diligence_readiness_get"]; + /** G702 */ + get: operations["g702_projects__pid__cost_g702_get"]; put?: never; post?: never; delete?: never; @@ -5929,7 +6588,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/documents/template": { + "/projects/{pid}/cost/g702.pdf": { parameters: { query?: never; header?: never; @@ -5937,10 +6596,11 @@ export interface paths { cookie?: never; }; /** - * Documents Template - * @description The standard folder taxonomy (static) — folders, owner roles, disciplines, required flags. + * G702 Pdf + * @description The owner pay application as a signable PDF — G702 certificate + G703 continuation sheet, + * drawn from the budget-seeded Schedule of Values. */ - get: operations["documents_template_projects__pid__documents_template_get"]; + get: operations["g702_pdf_projects__pid__cost_g702_pdf_get"]; put?: never; post?: never; delete?: never; @@ -5949,18 +6609,15 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/documents/tree": { + "/projects/{pid}/cost/g703": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Documents Tree - * @description The project's folder tree with per-folder file counts, required-doc gaps and owner roles. - */ - get: operations["documents_tree_projects__pid__documents_tree_get"]; + /** G703 */ + get: operations["g703_projects__pid__cost_g703_get"]; put?: never; post?: never; delete?: never; @@ -5969,7 +6626,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/documents/folder": { + "/projects/{pid}/cost/lien-waiver": { parameters: { query?: never; header?: never; @@ -5977,31 +6634,33 @@ export interface paths { cookie?: never; }; /** - * Documents Folder - * @description Files in one folder (the file-manager's right pane). `superseded=true` shows old revisions. + * Lien Waiver + * @description A statutory lien waiver / release to accompany a pay app (C1). `kind`: conditional_progress | + * unconditional_progress | conditional_final | unconditional_final. */ - get: operations["documents_folder_projects__pid__documents_folder_get"]; + get: operations["lien_waiver_projects__pid__cost_lien_waiver_get"]; put?: never; - post?: never; + /** + * Lien Waiver From Payapp + * @description Generate a lien-waiver record for the current pay application — amount = the G702 current + * payment due — so each draw produces its waiver automatically. + */ + post: operations["lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/documents/by-role": { + "/projects/{pid}/cost/lien-waiver.pdf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Documents By Role - * @description Folders owned by a given role (PM / Superintendent / Architect / Engineer / QS) — the role-based - * view of the tree. - */ - get: operations["documents_by_role_projects__pid__documents_by_role_get"]; + /** Lien Waiver Pdf */ + get: operations["lien_waiver_pdf_projects__pid__cost_lien_waiver_pdf_get"]; put?: never; post?: never; delete?: never; @@ -6010,7 +6669,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/documents/upload": { + "/projects/{pid}/cost/pay-app/invoice": { parameters: { query?: never; header?: never; @@ -6020,18 +6679,34 @@ export interface paths { get?: never; put?: never; /** - * Documents Upload - * @description Upload a document into a standard folder. Auto-named to the information standard; a new upload of - * the same document supersedes the prior revision (never overwritten — the old one is archived). + * Payapp Invoice + * @description Create the owner pay application — the FROZEN document, not a receipt for its total. + * + * Closes the loop budget → SOV → G702/G703 → owner invoice, and since PAY-APP it does so through + * the one builder, `cost.build_application`. This route used to assemble its own record from + * `g702()` line 8 and nothing else. Two things were wrong with that and only one was cosmetic: + * + * * **It over-billed.** `cost._certified_to_date` reads `current_payment_due` off submitted + * applications to fill line 7. The thin record had no such key, so the sum was 0.0 rather than + * `None` — and `None` is the value that means "fall back to the reconstruction". Submitting a + * draw therefore drove line 7 to zero and made the NEXT application re-bill everything earned to + * date. Reproduced before the fix: line 7 45,000 → 0, line 8 18,000 → 63,000. + * * **It always said "App 1".** `app_no` defaulted to 1 rather than to None, and the web client + * passed 1 explicitly, so every application in the register carried the same number. `app_no` is + * now optional and `build_application` numbers from what already exists when it is omitted. + * + * `GET /cost/g702` remains the live view — the right thing for "where do we stand today". This is + * the other thing: the claim as made on its date, with its numbers and its continuation sheet + * frozen into it. */ - post: operations["documents_upload_projects__pid__documents_upload_post"]; + post: operations["payapp_invoice_projects__pid__cost_pay_app_invoice_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/documents/{fid}/move": { + "/projects/{pid}/cost/sov": { parameters: { query?: never; header?: never; @@ -6041,17 +6716,34 @@ export interface paths { get?: never; put?: never; /** - * Documents Move - * @description Move a file to another standard folder. + * Cost Sov + * @description R27-SOV-LOOP — build a schedule of values **from** the estimate rather than re-keying it. + * + * Body: the same `{rules, quantities?}` as `/cost/estimate`, plus `markup_pct` (default 0) and + * `grouping` (`code` | `division` | `line`). + * + * Every other link in this chain already existed — the estimate measures and prices the model, + * `cost.g703` reads SOV records into a continuation sheet, and the pay-app PDF renders them — but + * nothing joined the first to the second, so the numbers were typed in again and the trace from a + * billed line back to a model element was lost at the one seam where somebody is asking to be paid. + * + * Read three fields before using the result. `covers_whole_model` is false whenever any scope could + * not be priced, and those elements are listed in `excluded` rather than dropped. `at_cost` is true + * when no markup was supplied — correct for cost-plus, an under-bill on lump sum. And `reconciles` + * proves the regrouping lost no money, **not** that the estimate was right. + * + * This does not write records: it returns the items for review. Committing them is a separate, + * deliberate act — an SOV that appeared in the register as a side effect of pricing is one nobody + * decided to sign. */ - post: operations["documents_move_projects__pid__documents__fid__move_post"]; + post: operations["cost_sov_projects__pid__cost_sov_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/documents/{fid}": { + "/projects/{pid}/cost/sov/from-budget": { parameters: { query?: never; header?: never; @@ -6060,29 +6752,30 @@ export interface paths { }; get?: never; put?: never; - post?: never; /** - * Documents Delete - * @description Delete a file (soft by default — keeps the audit trail; `hard=true` removes the blob). + * Sov From Budget + * @description Seed the owner pay-app **Schedule of Values** from the GMP budget — one SOV line per cost-code + * budget line (carrying its cost-code link), plus General Conditions / Requirements / Overhead / + * Fee / Contingency, each at its GMP value. So the G702/G703 the owner is billed on draws from the + * same relational budget the PX manages. Idempotent: no-op if the SOV already has lines unless + * `?replace=true` rebuilds it. Retainage comes from the prime contract. */ - delete: operations["documents_delete_projects__pid__documents__fid__delete"]; + post: operations["sov_from_budget_projects__pid__cost_sov_from_budget_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/documents/{fid}/download": { + "/projects/{pid}/cost/summary": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Documents Download - * @description Download a file's bytes. - */ - get: operations["documents_download_projects__pid__documents__fid__download_get"]; + /** Summary */ + get: operations["summary_projects__pid__cost_summary_get"]; put?: never; post?: never; delete?: never; @@ -6091,28 +6784,34 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/documents/health": { + "/projects/{pid}/cost/tm": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Documents Health - * @description Document-control health: naming compliance, required-folder coverage, revision control, CDE - * spread, orphans. + * Price Tm + * @description TM-RATES — price this eTicket's labour/material/equipment lines from the project rate + * registers, and report what could not be priced. + * + * Fills a blank rate from `labor_rate`/`material_rate`/`equipment_rate` and recomputes each row's + * amount; leaves a rate somebody typed alone and reports the difference; leaves a line the + * register does not know alone and names it; never invents an overtime or idle rate, because + * neither register holds one. Totals are DERIVED from the lines by MOD-TOTALS, so the figures + * returned here are re-read from the stored record rather than computed alongside it. */ - get: operations["documents_health_projects__pid__documents_health_get"]; - put?: never; - post?: never; + post: operations["price_tm_projects__pid__cost_tm_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/documents/phase-gaps": { + "/projects/{pid}/cost/traceability": { parameters: { query?: never; header?: never; @@ -6120,10 +6819,10 @@ export interface paths { cookie?: never; }; /** - * Documents Phase Gaps - * @description Required-document gaps for a design phase (AIA SD / DD / CD / CA / CLOSEOUT). + * Cost Traceability + * @description Cost traceability coverage — how much cost is tied to IFC model elements by GlobalId, per cost code. */ - get: operations["documents_phase_gaps_projects__pid__documents_phase_gaps_get"]; + get: operations["cost_traceability_projects__pid__cost_traceability_get"]; put?: never; post?: never; delete?: never; @@ -6132,69 +6831,50 @@ export interface paths { patch?: never; trace?: never; }; - "/market/snapshot": { + "/projects/{pid}/cv-progress/ingest": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Market Snapshot - * @description The market table — regions (escalation / labour / location index) + the warm/cold sector board. + * Cv Progress Ingest + * @description Accept one external CV progress estimate (no-op unless AEC_CV_BRIDGE is enabled). When enabled, + * `activity` (a schedule_activity id or name) is resolved and the estimate written to its percent. */ - get: operations["market_snapshot_market_snapshot_get"]; - put?: never; - post?: never; + post: operations["cv_progress_ingest_projects__pid__cv_progress_ingest_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/market/context": { + "/projects/{pid}/cv-progress/ingest-batch": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Market Context - * @description The project's market read: regional economics + sector temperature + the escalation factor to its - * construction midpoint. Query params override the project's `market_assumption` record. - */ - get: operations["market_context_projects__pid__market_context_get"]; + get?: never; put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/projects/{pid}/market/escalate": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; /** - * Market Escalate - * @description Escalate `amount` to the construction midpoint (or `to_year`) using the region's annual rate — - * for adjusting a base estimate / proforma hard cost to when it will actually be built. + * Cv Progress Ingest Batch + * @description Accept a batch of CV progress estimates — `{"estimates": [{activity, percent}, …]}` — the shape a + * vision service produces per photo sweep. Each valid item is written to its activity; returns per-item + * outcomes + a summary. No-op unless AEC_CV_BRIDGE is enabled. */ - get: operations["market_escalate_projects__pid__market_escalate_get"]; - put?: never; - post?: never; + post: operations["cv_progress_ingest_batch_projects__pid__cv_progress_ingest_batch_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/market/exists": { + "/projects/{pid}/cv-progress/status": { parameters: { query?: never; header?: never; @@ -6202,10 +6882,10 @@ export interface paths { cookie?: never; }; /** - * Market Exists - * @description Whether the project exists (cheap guard used by the panel). + * Cv Progress Status + * @description Status of the (external, feature-flagged) computer-vision site-progress bridge. */ - get: operations["market_exists_projects__pid__market_exists_get"]; + get: operations["cv_progress_status_projects__pid__cv_progress_status_get"]; put?: never; post?: never; delete?: never; @@ -6214,7 +6894,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/turnover/readiness": { + "/projects/{pid}/cx/dossier": { parameters: { query?: never; header?: never; @@ -6222,10 +6902,11 @@ export interface paths { cookie?: never; }; /** - * Readiness - * @description Punch-list rollup + latest model version — is the project ready for a G704 certification? + * Cx Dossier + * @description CX-1: the per-system turnover dossier — assets, tests by phase, FPT expected values from the + * MEP register, and best-effort punch mentions. */ - get: operations["readiness_projects__pid__turnover_readiness_get"]; + get: operations["cx_dossier_projects__pid__cx_dossier_get"]; put?: never; post?: never; delete?: never; @@ -6234,7 +6915,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/turnover/status": { + "/projects/{pid}/cx/matrix": { parameters: { query?: never; header?: never; @@ -6242,10 +6923,10 @@ export interface paths { cookie?: never; }; /** - * Status - * @description Turnover package status: substantial-completion cert (signed?), record model, punch readiness. + * Cx Matrix + * @description CX-1: the system × phase completion matrix (total/tested/accepted/pass/fail per cell). */ - get: operations["status_projects__pid__turnover_status_get"]; + get: operations["cx_matrix_projects__pid__cx_matrix_get"]; put?: never; post?: never; delete?: never; @@ -6254,7 +6935,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/turnover/certify": { + "/projects/{pid}/cx/seed": { parameters: { query?: never; header?: never; @@ -6264,41 +6945,40 @@ export interface paths { get?: never; put?: never; /** - * Certify - * @description Architect certifies substantial completion on a completion_certificate record: gate on a prepared - * punch list, record the Architect (certifying) + Owner/Contractor signatures, stamp the record model - * version, and issue the certificate. Render the G704 via .../contracts/completion_certificate/{rid}/ - * document.pdf?doc=g704. + * Cx Seed + * @description CX-1: seed the commissioning registry from the published model — equipment classes become + * GUID-keyed asset_register records (deduped), and (with `checklists`, the default) every systemed + * asset gets its Pre-Functional + Functional commissioning records, the Functional stamped with + * the system's MEP expected values (FPT). */ - post: operations["certify_projects__pid__turnover_certify_post"]; + post: operations["cx_seed_projects__pid__cx_seed_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/schedule/takt": { + "/projects/{pid}/daily-reports/summary": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Schedule Takt - * @description Takt / line-of-balance plan — trades flow floor-to-floor at a steady production rate, with a - * just-in-time delivery plan (R2, the Empire State 'vertical assembly line'). + * Field Log Summary + * @description Field-log rollup — manpower trend, weather-impact lost-days, reporting coverage. */ - post: operations["schedule_takt_schedule_takt_post"]; + get: operations["field_log_summary_projects__pid__daily_reports_summary_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/schedule/takt.svg": { + "/projects/{pid}/dashboard": { parameters: { query?: never; header?: never; @@ -6306,10 +6986,10 @@ export interface paths { cookie?: never; }; /** - * Schedule Takt Svg - * @description Line-of-balance (takt) chart as SVG — floors vs days, one line per trade (R2). + * Get Dashboard + * @description Dashboard tailored to `party` (defaults to the caller's project party role). */ - get: operations["schedule_takt_svg_schedule_takt_svg_get"]; + get: operations["get_dashboard_projects__pid__dashboard_get"]; put?: never; post?: never; delete?: never; @@ -6318,7 +6998,7 @@ export interface paths { patch?: never; trace?: never; }; - "/benchmarks": { + "/projects/{pid}/deal-memory/beside": { parameters: { query?: never; header?: never; @@ -6326,10 +7006,31 @@ export interface paths { cookie?: never; }; /** - * Get Benchmarks - * @description Citable benchmark ranges (cost/sf, cap rates, productivity, lean PPC) for grounding defaults (R5). + * Project Deal Memory Beside + * @description R35-DEAL-MEMORY: this firm's own realised $/SF, beside the hard cost being underwritten. + * + * `deal_memory.comps` has been routed since the engine shipped and `beside()` — the function whose + * docstring says it is *"the shape the underwriting screen wants"* — had **no caller anywhere**, + * server or client. A module can be reachable and its whole reason for existing still be + * unreachable; `read_p6xml_all` was the same shape one ring over. + * + * **Only `cost_per_sf` is offered, and that is a refusal about the other two rather than an + * oversight.** Of the three metrics `comps` reports: + * + * * `cost_per_sf` — the proforma enters a hard cost and this project has a GFA, so the entered + * value can be put in the metric's own units. Offered. + * * `cost_variance_pct` — a realised over/under against a budget. The nearest thing a proforma + * enters is a contingency, and "your contingency should cover our historical overrun" is a claim + * about what the product asserts in an underwriting, not a unit conversion. **Not offered here; + * it needs the same domain call `/schedule/eot` is waiting on.** + * * `schedule_variance_days` — a variance, not a duration. Comparing it to an entered + * `construction_months` is a category error wearing matching units. + * + * GFA comes from `energy.project_gfa_sf`, the one definition, because deriving $/SF client-side + * would put a second one in the tree. It needs a loaded properties index, so `no_gfa` is a real and + * common answer and is reported as itself rather than as "no history". */ - get: operations["get_benchmarks_benchmarks_get"]; + get: operations["project_deal_memory_beside_projects__pid__deal_memory_beside_get"]; put?: never; post?: never; delete?: never; @@ -6338,7 +7039,7 @@ export interface paths { patch?: never; trace?: never; }; - "/compute/nodes": { + "/projects/{pid}/deal-room/authority": { parameters: { query?: never; header?: never; @@ -6346,11 +7047,21 @@ export interface paths { cookie?: never; }; /** - * Compute Nodes - * @description Node palette for the computational graph — zero-touch nodes over the pure engines (M4). + * Get Deal Authority + * @description CRE-AUTHORITY (R20) — the deal-room authority table with its gate. + * + * Authority is declared **per fact type**, not per file: one authoritative document each for the + * rent roll, the operating statement, tax, insurance and so on, with its date, freshness + * threshold and supersedes chain. Required fact types that are missing, stale, or + * superseded-but-still-active **block** downstream analysis rather than being annotated after. */ - get: operations["compute_nodes_compute_nodes_get"]; - put?: never; + get: operations["get_deal_authority_projects__pid__deal_room_authority_get"]; + /** + * Put Deal Authority + * @description Replace the authority table (validated atomically: known fact types, a real as_of date on + * every entry, and exactly ONE authoritative document per fact type). + */ + put: operations["put_deal_authority_projects__pid__deal_room_authority_put"]; post?: never; delete?: never; options?: never; @@ -6358,7 +7069,7 @@ export interface paths { patch?: never; trace?: never; }; - "/compute/graph": { + "/projects/{pid}/decision-gate": { parameters: { query?: never; header?: never; @@ -6368,17 +7079,23 @@ export interface paths { get?: never; put?: never; /** - * Compute Run - * @description Run a Dynamo/Hypar-style node graph: {nodes, edges} → each node's outputs, in dependency order (M4). + * Post Decision Gate + * @description CRE-DECISION-GATE (R20) — the pre-committee readiness gate. + * + * Seven deterministic gates over evidence the other engines produce: citation coverage, comp + * source tiers, the T-12 tie-out, the rent-roll scrub, the deal-room authority table, required + * exhibits, and a **named** sign-off. **A gate whose evidence was not supplied is `unknown`, and + * unknown blocks** — absent evidence must never read as a pass. The response carries the actions + * to take, not merely the list of what failed. */ - post: operations["compute_run_compute_graph_post"]; + post: operations["post_decision_gate_projects__pid__decision_gate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/import-xer": { + "/projects/{pid}/design/options/board.pdf": { parameters: { query?: never; header?: never; @@ -6388,29 +7105,19 @@ export interface paths { get?: never; put?: never; /** - * Import Xer - * @description Import a Primavera P6 export — **.xer** (tab-delimited) or **.xml (PMXML)**, auto-detected from - * the content. Parses the tasks/activities and **upserts each as an - * editable `schedule_activity` record** (matched to the prior import by P6 activity code), so the - * GC can keep updating, adding, and re-sequencing tasks after import — imported and hand-entered - * activities live in one editable schedule that drives Gantt / Line-of-Balance / CPM / the 4D - * scrub. Re-importing updates the same records (preserving GC edits to others); zero-duration tasks - * are tagged as Milestones. Also keeps the start→finish window for the takt 4D date overlay. - * Returns counts (created/updated) + the date range + a small preview. - */ - post: operations["import_xer_projects__pid__schedule_import_xer_post"]; - /** - * Clear Xer - * @description Remove an imported P6 schedule: deletes the activity records this import created (by its - * code→id index) and the date-window blob. Hand-entered activities are untouched. + * Design Options Board + * @description BOARDS: score the option set and render it as a styled one-page **design-option deck PDF** — + * title + recommendation, the comparison table, and composite score bars. The client-facing + * artifact of a GEN-SCORE run. */ - delete: operations["clear_xer_projects__pid__schedule_import_xer_delete"]; + post: operations["design_options_board_projects__pid__design_options_board_pdf_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/schedule/4d": { + "/projects/{pid}/design/options/carbon": { parameters: { query?: never; header?: never; @@ -6418,19 +7125,17 @@ export interface paths { cookie?: never; }; /** - * Schedule 4D - * @description 4D construction sequence (C3): scrubable timeline frames (cumulative % built per day) over the - * published model's elements. + * Design Options Carbon + * @description Embodied carbon (A1–A3) per design option, ranked by total and by intensity per m² GFA. * - * Source is **relational by default** (`source=auto`): when the GC **`schedule_activity`** records - * exist they drive the sequence (`source:"gc"`) — each element gets its real calendar finish date - * from the activity that tags its GUID, else from its trade's activities by floor — so the model - * plays the *actual* schedule the team maintains in the portal (the same activities behind the - * Gantt / Line-of-Balance / CPM views). Otherwise it falls back to a takt plan derived from the - * storey count; if a Primavera **P6 .xer** was imported, takt frames carry interpolated calendar - * dates (`source:"p6"`). Force a source with `?source=gc|takt`. + * Every row states its `basis`: `declared` (a figure recorded on the option), `benchmark` (gross area + * × a whole-building intensity for its building type), or `unavailable`. Options with no basis are + * listed and NOT ranked — an option that could not be measured is not an option with zero carbon, + * and ranking it first is how a scheme gets chosen on a number nobody computed. + * + * Note this is embodied carbon, distinct from the option card's `energy_eui`, which is operational. */ - get: operations["schedule_4d_projects__pid__schedule_4d_get"]; + get: operations["design_options_carbon_projects__pid__design_options_carbon_get"]; put?: never; post?: never; delete?: never; @@ -6439,7 +7144,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/lean/ppc": { + "/projects/{pid}/design/options/compare": { parameters: { query?: never; header?: never; @@ -6447,10 +7152,12 @@ export interface paths { cookie?: never; }; /** - * Lean Ppc - * @description Last-Planner Plan Percent Complete + reasons for non-completion from the weekly-plan module (R4). + * Design Options Compare + * @description Compare the project's design options / variants apples-to-apples — program + economics per + * option, best-in-class per metric, deltas vs the selected option. The selected option's drawing set + * is the project's current documentation (2D regenerates live from the model). */ - get: operations["lean_ppc_projects__pid__lean_ppc_get"]; + get: operations["design_options_compare_projects__pid__design_options_compare_get"]; put?: never; post?: never; delete?: never; @@ -6459,7 +7166,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/pull-plan/board": { + "/projects/{pid}/design/options/economics": { parameters: { query?: never; header?: never; @@ -6467,12 +7174,18 @@ export interface paths { cookie?: never; }; /** - * Pull Plan Board - * @description The Last Planner phase pull-plan board: trade swimlanes × weeks, the hand-off sequence, the - * make-ready constraint log, and readiness / commitment / PPC. Every stakeholder edits the - * `pull_plan_task` records; pass ?milestone= to focus one phase. + * Design Options Economics + * @description Each design option's cost and levered IRR, **solved from the proforma scenario it names**. + * + * Every figure states its `basis`: `derived` (the option's scenario was solved), `declared` (typed + * on the option and shown as such), `unlinked` (the project has scenarios but this option names + * none — reported with the candidates and deliberately not resolved, because attributing another + * scheme's deal to this massing would manufacture provenance), or `unavailable`. + * + * A declared figure is never promoted to derived, and a derived one never overwrites what was + * typed — `declared_disagrees_with_derived` is the list worth reading first. */ - get: operations["pull_plan_board_projects__pid__pull_plan_board_get"]; + get: operations["design_options_economics_projects__pid__design_options_economics_get"]; put?: never; post?: never; delete?: never; @@ -6481,29 +7194,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/pull-plan/metrics": { + "/projects/{pid}/design/options/generate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Pull Plan Metrics - * @description Last Planner reliability metrics beyond PPC: Tasks-Made-Ready %, make-ready runway, - * perfect-handoff %, PPC trend by week, and the variance-reason Pareto — the learning-loop signals - * a pull-planning team improves week over week. + * Design Options Generate + * @description GEN-SCORE: the generative grid — massing variants around a `base` zoning envelope (FAR utilisation + * steps × building types). Deterministic; each option is ready to feed straight into /options/score. */ - get: operations["pull_plan_metrics_projects__pid__pull_plan_metrics_get"]; - put?: never; - post?: never; + post: operations["design_options_generate_projects__pid__design_options_generate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/pull-plan/stream": { + "/projects/{pid}/design/options/record": { parameters: { query?: never; header?: never; @@ -6511,13 +7223,22 @@ export interface paths { cookie?: never; }; /** - * Pull Plan Stream - * @description Server-sent events for the collaborative pull board: polls a cheap board signature (row count + - * latest modified_at) server-side every few seconds and pushes it when it changes, so every trade's - * board live-refreshes the moment anyone edits a sticky note. Uses a fresh DB session per poll since - * the generator outlives the request scope (mirrors the notifications stream). + * Design Options Record + * @description Each design option as ONE comparable record — geometry · unit mix · cost · carbon · returns — + * **so no massing is evaluated without its returns.** + * + * The three routes above each own one part of that and nothing joined them, so a reader compared + * schemes by opening three screens and reconciling them by eye. This re-derives nothing: every + * number comes from the engine that already owns it, so this cannot disagree with a screen opened + * beside it. + * + * Two refusals. A missing axis is `absent` — never a zero and never a default: `option_score` once + * coerced a missing `cost_per_sf` to 0.0 and, on a lower-is-better axis, that scored 100, the best + * possible mark awarded for having no data. And this does NOT rank; `option_score` owns ranking and + * does it honestly. `comparable_count` is the number to read — if it is below the option count, + * some massing is being evaluated without its returns, and `incomparable` says which and why. */ - get: operations["pull_plan_stream_projects__pid__pull_plan_stream_get"]; + get: operations["design_options_record_projects__pid__design_options_record_get"]; put?: never; post?: never; delete?: never; @@ -6526,27 +7247,29 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/pull-plan/board.pdf": { + "/projects/{pid}/design/options/score": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Pull Plan Pdf - * @description The pull-plan board as a printable PDF (trade × week matrix + constraint log + PPC). + * Design Options Score + * @description GEN-SCORE: rank candidate massing options through the platform's own engines — conceptual $/SF + * (cost), whole-building embodied-carbon benchmarks (carbon), net sellable area (yield), zoning + * FAR/height checks (compliance) — weighted composite, non-compliant options never recommended. */ - get: operations["pull_plan_pdf_projects__pid__pull_plan_board_pdf_get"]; - put?: never; - post?: never; + post: operations["design_options_score_projects__pid__design_options_score_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/responsibility": { + "/projects/{pid}/design/standards": { parameters: { query?: never; header?: never; @@ -6554,11 +7277,10 @@ export interface paths { cookie?: never; }; /** - * Get Matrix - * @description The full RACI/DACI grid: role columns × activity rows, with per-row validation - * (exactly one Accountable, at least one Responsible) and role-load summary. + * Design Standards Ruleset + * @description The design-standards ruleset — approved / preferred / prohibited assemblies, materials, products. */ - get: operations["get_matrix_projects__pid__responsibility_get"]; + get: operations["design_standards_ruleset_projects__pid__design_standards_get"]; put?: never; post?: never; delete?: never; @@ -6567,7 +7289,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/responsibility/templates": { + "/projects/{pid}/design/standards/check": { parameters: { query?: never; header?: never; @@ -6575,10 +7297,11 @@ export interface paths { cookie?: never; }; /** - * Get Templates - * @description Starter matrices for the common construction phases (design delivery, buyout, execution, closeout). + * Design Standards Check + * @description Audit the loaded model against the design-standards ruleset (prohibited / non-approved + * type + material use). Returns the ruleset only when no model is loaded. */ - get: operations["get_templates_projects__pid__responsibility_templates_get"]; + get: operations["design_standards_check_projects__pid__design_standards_check_get"]; put?: never; post?: never; delete?: never; @@ -6587,19 +7310,20 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/responsibility/config": { + "/projects/{pid}/detailing/rules/validate": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; /** - * Put Config - * @description Set the project's role columns and the matrix mode (RACI or DACI). + * Validate Detailing + * @description W11 D3: IDS-style QA — for every element a seed rule applies to, report the ones missing their + * required keynote/spec code (the 'components missing a keynote' pre-flight). Read-only. */ - put: operations["put_config_projects__pid__responsibility_config_put"]; + get: operations["validate_detailing_projects__pid__detailing_rules_validate_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -6607,78 +7331,96 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/responsibility/apply-template": { + "/projects/{pid}/detailing/{guid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Apply Template - * @description Seed the matrix from a named starter template (also sets the default role columns + mode). + * Element Detailing + * @description W11 Track D: one element's attached carriers — classification codes (UniFormat/MasterFormat/ + * OmniClass keynote+spec codes) and documents (details/installation instructions). Written by the + * `classify` and `attach_document` recipes; consumed by keynote/schedule/spec/drawing generation. */ - post: operations["apply_template_projects__pid__responsibility_apply_template_post"]; + get: operations["element_detailing_projects__pid__detailing__guid__get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/review/contract": { + "/projects/{pid}/dev-budget": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; - /** Review Contract Ep */ - post: operations["review_contract_ep_projects__pid__review_contract_post"]; + /** + * Get Dev Budget + * @description The project's developer cost budget (line-item hard/soft/acquisition + contingencies) plus a + * computed summary. Returns a starter budget if none is saved yet. + */ + get: operations["get_dev_budget_projects__pid__dev_budget_get"]; + /** + * Put Dev Budget + * @description Save the developer cost budget; returns the recomputed summary. + */ + put: operations["put_dev_budget_projects__pid__dev_budget_put"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/review/scope": { + "/projects/{pid}/dev-budget/cost-lines": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Dev Budget Cost Lines + * @description The budget rolled into proforma cost_lines (the seed the Finance view applies). + */ + get: operations["dev_budget_cost_lines_projects__pid__dev_budget_cost_lines_get"]; put?: never; - /** Review Scope Ep */ - post: operations["review_scope_ep_projects__pid__review_scope_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/review/ask": { + "/projects/{pid}/dev-budget/gmp-reconciliation": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Gmp Reconciliation + * @description Tie the developer's construction **hard cost** to the GC's actual **GMP**: the proforma was + * underwritten with a hard-cost line; the GC manages a live GMP (buyout + GC/GR + OH/fee). This + * shows them side by side so the developer sees whether construction is tracking the underwriting. + */ + get: operations["gmp_reconciliation_projects__pid__dev_budget_gmp_reconciliation_get"]; put?: never; - /** Review Ask Ep */ - post: operations["review_ask_ep_projects__pid__review_ask_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/draft/rfi": { + "/projects/{pid}/dev-budget/sync-from-model": { parameters: { query?: never; header?: never; @@ -6687,15 +7429,22 @@ export interface paths { }; get?: never; put?: never; - /** Draft Rfi Ep */ - post: operations["draft_rfi_ep_projects__pid__draft_rfi_post"]; + /** + * Sync Model To Hard + * @description Set the developer budget's construction hard cost from the **model takeoff estimate** — one click + * ties the underwriting to the real IFC quantities × unit rates (priced through the project's pinned + * cost vintage), instead of a flat GFA×$/sf assumption. Replaces the hard lines with **per-discipline** + * model lines (S/A/M/E/P/…); soft / acquisition / contingency are untouched. 409 if no source IFC. + * Returns the recomputed budget + the estimate total it used. Closes the model↔proforma disconnect. + */ + post: operations["sync_model_to_hard_projects__pid__dev_budget_sync_from_model_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/draft/submittal-summary": { + "/projects/{pid}/dev-budget/sync-gmp": { parameters: { query?: never; header?: never; @@ -6704,32 +7453,48 @@ export interface paths { }; get?: never; put?: never; - /** Draft Submittal Ep */ - post: operations["draft_submittal_ep_projects__pid__draft_submittal_summary_post"]; + /** + * Sync Gmp To Hard + * @description Set the developer budget's construction hard cost to the GC's GMP — one click ties the + * underwriting to the live construction number. Replaces hard lines with a single synced GMP line; + * soft / acquisition / contingency are untouched. Returns the recomputed budget summary. + */ + post: operations["sync_gmp_to_hard_projects__pid__dev_budget_sync_gmp_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/draft/scope": { + "/projects/{pid}/digest": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Project Digest + * @description A deterministic, Merkle-hashed digest of the project's source IFC. + * + * `scale` prunes the tree (`storey` is the useful default for a stored baseline — kilobytes, and it + * still carries the hashes that prove what the full digest would have said). `geometry=true` meshes + * elements with no base quantities: slow, and it changes what the numbers mean, so a digest taken + * that way can only be diffed against another taken the same way — the refusal is enforced in + * `diff`, not left to the caller to remember. + * + * `summary=true` returns the one-screen form: project hash, element count, per-storey hashes. + */ + get: operations["project_digest_projects__pid__digest_get"]; put?: never; - /** Draft Scope Ep */ - post: operations["draft_scope_ep_projects__pid__draft_scope_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/extract/sheets": { + "/projects/{pid}/digest/diff": { parameters: { query?: never; header?: never; @@ -6739,19 +7504,28 @@ export interface paths { get?: never; put?: never; /** - * Extract Sheets Ep - * @description Extract a drawing-sheet index (number / title / discipline) from an uploaded PDF or pasted - * sheet list — deterministic over the PDF text layer, honest when a scan has no text. With - * create=true the extracted sheets become `drawing` records. + * Project Digest Diff + * @description Diff the project's **current** source IFC against a baseline digest. + * + * Returns `compatible: false` with a reason when the two were not produced the same way, rather + * than reporting a configuration change as a change to the building. + * + * The current digest inherits the baseline's `scale`, so the common case — post back exactly what + * you were given — is compatible by construction. It does **not** inherit the baseline's + * measurement mode. Mirroring that too would be more convenient and is the wrong trade twice over: + * it lets a field in caller-supplied JSON decide whether this request meshes every element in the + * model (minutes of CPU the caller never asked for), and it disarms the one guard that keeps a + * measurement change from being reported as a change to the building. A geometry-measured baseline + * therefore needs an explicit `?geometry=true`, and the refusal says exactly why. */ - post: operations["extract_sheets_ep_projects__pid__extract_sheets_post"]; + post: operations["project_digest_diff_projects__pid__digest_diff_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/benchmarks/costs": { + "/projects/{pid}/diligence/readiness": { parameters: { query?: never; header?: never; @@ -6759,10 +7533,12 @@ export interface paths { cookie?: never; }; /** - * Cost Benchmarks - * @description Actual-cost distribution (low/p25/median/p75/high) per cost code across all projects. + * Diligence Readiness + * @description Pre-acquisition go/no-go rollup: due-diligence items by category/state (cleared vs flagged vs + * open, high-risk flags) + entitlement applications by status (approved vs pending vs denied, + * approvals nearing expiration). The screen a developer reads before releasing contingencies. */ - get: operations["cost_benchmarks_benchmarks_costs_get"]; + get: operations["diligence_readiness_projects__pid__diligence_readiness_get"]; put?: never; post?: never; delete?: never; @@ -6771,27 +7547,28 @@ export interface paths { patch?: never; trace?: never; }; - "/benchmarks/response-rates": { + "/projects/{pid}/distribution": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Response Rates - * @description RFI + submittal turnaround and overdue % across all projects (ball-in-court accountability). + * Distribution + * @description Allocate a distribution pro-rata by commitment. persist=true posts it to each investor's + * distributed total; otherwise it's a preview. */ - get: operations["response_rates_benchmarks_response_rates_get"]; - put?: never; - post?: never; + post: operations["distribution_projects__pid__distribution_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/benchmarks/pull-planning": { + "/projects/{pid}/doc-graph": { parameters: { query?: never; header?: never; @@ -6799,11 +7576,11 @@ export interface paths { cookie?: never; }; /** - * Pull Planning - * @description Pull-planning reliability across every project: PPC + Tasks-Made-Ready % distribution vs the - * ≥80% target — so a plan can be judged against the team's own portfolio. + * Doc Graph + * @description W9-4 (harder half): the document / specification graph — spec sections (classification codes) and + * attached documents (with sheet refs) linked to the elements they govern. The cited-source layer. */ - get: operations["pull_planning_benchmarks_pull_planning_get"]; + get: operations["doc_graph_projects__pid__doc_graph_get"]; put?: never; post?: never; delete?: never; @@ -6812,7 +7589,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/prequal/scores": { + "/projects/{pid}/doctext": { parameters: { query?: never; header?: never; @@ -6820,40 +7597,46 @@ export interface paths { cookie?: never; }; /** - * Prequal Scores - * @description Q-score (0-100) + risk band + factor breakdown for every prequalified sub, worst first. - * Pass `project_size` to weight financial/experience factors against this job's value. + * Doctext Catalog + * @description The project's ingested documents (chunk + section counts). */ - get: operations["prequal_scores_projects__pid__prequal_scores_get"]; + get: operations["doctext_catalog_projects__pid__doctext_get"]; put?: never; - post?: never; + /** + * Doctext Ingest + * @description W9-4: **ingest a spec/code document's text** for cited NL answers. Send JSON + * `{name, text}`, or a raw PDF body with `?name=` (extracted via pypdf). Chunks split at + * spec-section headers / numbered headings with page tracking; re-ingesting a name replaces it. + */ + post: operations["doctext_ingest_projects__pid__doctext_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/prequal/coi-expiry": { + "/projects/{pid}/doctext/ask": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Coi Expiry - * @description Certificates of insurance expired or expiring within `soon_days`. + * Doctext Ask + * @description W9-4: a **cited, extractive answer** from the ingested documents' own text — the answer IS the + * source's words (document · section · page named), never a paraphrase; no match says so honestly. */ - get: operations["coi_expiry_projects__pid__prequal_coi_expiry_get"]; - put?: never; - post?: never; + post: operations["doctext_ask_projects__pid__doctext_ask_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/procurement/gate": { + "/projects/{pid}/doctext/search": { parameters: { query?: never; header?: never; @@ -6861,11 +7644,10 @@ export interface paths { cookie?: never; }; /** - * Procurement Gate Check - * @description Compliance gate for a vendor: can they bid (approved prequal + active insurance) and can they - * bill (executed subcontract + active insurance), with the specific blockers. + * Doctext Search + * @description Top-k cited chunks across the ingested documents (token overlap; section numbers boosted). */ - get: operations["procurement_gate_check_projects__pid__procurement_gate_get"]; + get: operations["doctext_search_projects__pid__doctext_search_get"]; put?: never; post?: never; delete?: never; @@ -6874,7 +7656,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/procurement/compliance-feed": { + "/projects/{pid}/doctext/{doc_id}/source": { parameters: { query?: never; header?: never; @@ -6882,11 +7664,16 @@ export interface paths { cookie?: never; }; /** - * Procurement Compliance Feed - * @description Outbound nudge list — vendors with an expiring/expired/missing COI or an unapproved prequal, - * before it blocks a bid invitation or a pay application. + * Doctext Source + * @description R31-CITE-HIGHLIGHT — fetch the document a citation cites, so the citation can be a link. + * + * Serves only the copy this module stored (`source_kind == "doctext-pdf"`). A `file` source is a + * key into the document manager and is served by ITS route with ITS permissions — resolving one + * here would be a second door onto the same object with a gate this router maintains separately, + * which is the shape that has produced authz drift twice in this repo. A text-only ingest has no + * document and gets a 404 that says so rather than an empty PDF. */ - get: operations["procurement_compliance_feed_projects__pid__procurement_compliance_feed_get"]; + get: operations["doctext_source_projects__pid__doctext__doc_id__source_get"]; put?: never; post?: never; delete?: never; @@ -6895,7 +7682,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/payapp/lien-exposure": { + "/projects/{pid}/documents/by-role": { parameters: { query?: never; header?: never; @@ -6903,11 +7690,11 @@ export interface paths { cookie?: never; }; /** - * Lien Exposure - * @description Per-vendor billed / paid / waiver coverage and lien exposure (money paid without an - * unconditional waiver on file), worst first, with a project rollup. + * Documents By Role + * @description Folders owned by a given role (PM / Superintendent / Architect / Engineer / QS) — the role-based + * view of the tree. */ - get: operations["lien_exposure_projects__pid__payapp_lien_exposure_get"]; + get: operations["documents_by_role_projects__pid__documents_by_role_get"]; put?: never; post?: never; delete?: never; @@ -6916,28 +7703,32 @@ export interface paths { patch?: never; trace?: never; }; - "/payments/status": { + "/projects/{pid}/documents/file-model": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Payments Status - * @description Whether payment disbursement is configured. Off by default — Massing never moves money itself; - * it tracks the pay-app/lien-waiver workflow and gates release on waiver coverage. + * Documents File Model + * @description R32-MODEL-IN-TREE — file the project's current `source.ifc` into `12_Model/IFC` as a revision. + * + * Deliberately an explicit act rather than a hook on the storage write: `source.ifc` is rewritten by + * every edit recipe, so filing on save would mint a revision per keystroke and make the chain + * meaningless. Re-filing under the same `title` supersedes the prior revision, so the as-issued model + * stays recoverable and the current one is what the file manager shows. */ - get: operations["payments_status_payments_status_get"]; - put?: never; - post?: never; + post: operations["documents_file_model_projects__pid__documents_file_model_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/accounting/journal": { + "/projects/{pid}/documents/folder": { parameters: { query?: never; header?: never; @@ -6945,10 +7736,10 @@ export interface paths { cookie?: never; }; /** - * Journal - * @description Preview the flattened GL/AP entries (sub invoices + posted direct costs) as JSON. + * Documents Folder + * @description Files in one folder (the file-manager's right pane). `superseded=true` shows old revisions. */ - get: operations["journal_projects__pid__accounting_journal_get"]; + get: operations["documents_folder_projects__pid__documents_folder_get"]; put?: never; post?: never; delete?: never; @@ -6957,7 +7748,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/accounting/chart-of-accounts": { + "/projects/{pid}/documents/health": { parameters: { query?: never; header?: never; @@ -6965,10 +7756,11 @@ export interface paths { cookie?: never; }; /** - * Chart Of Accounts - * @description The standard construction chart of accounts (code, name, type, normal balance). + * Documents Health + * @description Document-control health: naming compliance, required-folder coverage, revision control, CDE + * spread, orphans. */ - get: operations["chart_of_accounts_projects__pid__accounting_chart_of_accounts_get"]; + get: operations["documents_health_projects__pid__documents_health_get"]; put?: never; post?: never; delete?: never; @@ -6977,7 +7769,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/accounting/journal-entries": { + "/projects/{pid}/documents/model-history": { parameters: { query?: never; header?: never; @@ -6985,10 +7777,11 @@ export interface paths { cookie?: never; }; /** - * Journal Entries - * @description Balanced double-entry journal from job cost + billing + the WIP percentage-of-completion adjustment. + * Documents Model History + * @description Every filed revision of the model, newest first, **including superseded ones** — the point of + * filing the model is that the as-issued version stays recoverable. */ - get: operations["journal_entries_projects__pid__accounting_journal_entries_get"]; + get: operations["documents_model_history_projects__pid__documents_model_history_get"]; put?: never; post?: never; delete?: never; @@ -6997,7 +7790,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/accounting/trial-balance": { + "/projects/{pid}/documents/phase-gaps": { parameters: { query?: never; header?: never; @@ -7005,10 +7798,10 @@ export interface paths { cookie?: never; }; /** - * Trial Balance - * @description Trial balance — debits and credits per account (must tie), from the double-entry journal. + * Documents Phase Gaps + * @description Required-document gaps for a design phase (AIA SD / DD / CD / CA / CLOSEOUT). */ - get: operations["trial_balance_projects__pid__accounting_trial_balance_get"]; + get: operations["documents_phase_gaps_projects__pid__documents_phase_gaps_get"]; put?: never; post?: never; delete?: never; @@ -7017,7 +7810,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/accounting/gl.csv": { + "/projects/{pid}/documents/template": { parameters: { query?: never; header?: never; @@ -7025,10 +7818,10 @@ export interface paths { cookie?: never; }; /** - * Gl Csv - * @description Double-entry general-ledger CSV (universal import for QuickBooks / Sage / Xero). + * Documents Template + * @description The standard folder taxonomy (static) — folders, owner roles, disciplines, required flags. */ - get: operations["gl_csv_projects__pid__accounting_gl_csv_get"]; + get: operations["documents_template_projects__pid__documents_template_get"]; put?: never; post?: never; delete?: never; @@ -7037,7 +7830,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/accounting/bills.iif": { + "/projects/{pid}/documents/tree": { parameters: { query?: never; header?: never; @@ -7045,10 +7838,10 @@ export interface paths { cookie?: never; }; /** - * Bills Iif - * @description QuickBooks IIF bills file (AP bills from subcontractor invoices). + * Documents Tree + * @description The project's folder tree with per-folder file counts, required-doc gaps and owner roles. */ - get: operations["bills_iif_projects__pid__accounting_bills_iif_get"]; + get: operations["documents_tree_projects__pid__documents_tree_get"]; put?: never; post?: never; delete?: never; @@ -7057,28 +7850,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/carbon": { + "/projects/{pid}/documents/upload": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Project Carbon - * @description Embodied carbon (A1-A3) from `production_quantity` records: per-line kgCO2e, total tCO2e, and - * rollups by material + cost code. Built-in EPD factors (design-stage signal, not a certified LCA). + * Documents Upload + * @description Upload a document into a standard folder. Auto-named to the information standard; a new upload of + * the same document supersedes the prior revision (never overwritten — the old one is archived). */ - get: operations["project_carbon_projects__pid__carbon_get"]; - put?: never; - post?: never; + post: operations["documents_upload_projects__pid__documents_upload_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/codecheck": { + "/projects/{pid}/documents/{fid}": { parameters: { query?: never; header?: never; @@ -7087,19 +7880,18 @@ export interface paths { }; get?: never; put?: never; + post?: never; /** - * Code Check - * @description Applicable IBC/ADA/IECC provisions (code + section + requirement) for the described project. - * Claude when an API key is set; a deterministic IBC checklist otherwise. Always confirm with the AHJ. + * Documents Delete + * @description Delete a file (soft by default — keeps the audit trail; `hard=true` removes the blob). */ - post: operations["code_check_projects__pid__codecheck_post"]; - delete?: never; + delete: operations["documents_delete_projects__pid__documents__fid__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/ids/templates": { + "/projects/{pid}/documents/{fid}/download": { parameters: { query?: never; header?: never; @@ -7107,10 +7899,10 @@ export interface paths { cookie?: never; }; /** - * Ids Templates - * @description The authoring catalog: element requirement templates + use-case bundles. + * Documents Download + * @description Download a file's bytes. */ - get: operations["ids_templates_ids_templates_get"]; + get: operations["documents_download_projects__pid__documents__fid__download_get"]; put?: never; post?: never; delete?: never; @@ -7119,7 +7911,7 @@ export interface paths { patch?: never; trace?: never; }; - "/ids/build": { + "/projects/{pid}/documents/{fid}/move": { parameters: { query?: never; header?: never; @@ -7129,17 +7921,17 @@ export interface paths { get?: never; put?: never; /** - * Build Ids - * @description Build a standards-valid IDS 1.0 XML from a use case or explicit specs → downloadable .ids file. + * Documents Move + * @description Move a file to another standard folder. */ - post: operations["build_ids_ids_build_post"]; + post: operations["documents_move_projects__pid__documents__fid__move_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/ids/eir": { + "/projects/{pid}/draft/rfi": { parameters: { query?: never; header?: never; @@ -7148,18 +7940,15 @@ export interface paths { }; get?: never; put?: never; - /** - * Build Eir - * @description Generate an Exchange Information Requirements (EIR) markdown document for the BIM contract. - */ - post: operations["build_eir_ids_eir_post"]; + /** Draft Rfi Ep */ + post: operations["draft_rfi_ep_projects__pid__draft_rfi_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/procurement/level-quotes": { + "/projects/{pid}/draft/scope": { parameters: { query?: never; header?: never; @@ -7168,40 +7957,32 @@ export interface paths { }; get?: never; put?: never; - /** - * Level Quotes - * @description Level competing material quotes into an apples-to-apples grid + low price per line + best supplier. - * Body: {quotes:[{supplier, lines:[{item, qty, unit, unit_price}]}]}. - */ - post: operations["level_quotes_projects__pid__procurement_level_quotes_post"]; + /** Draft Scope Ep */ + post: operations["draft_scope_ep_projects__pid__draft_scope_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/procurement/three-way-match": { + "/projects/{pid}/draft/submittal-summary": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Three Way Match - * @description Reconcile each PO (commitment) against its deliveries and invoices — flags over-billing, - * pay-before-receipt, and un-invoiced deliveries. - */ - get: operations["three_way_match_projects__pid__procurement_three_way_match_get"]; + get?: never; put?: never; - post?: never; + /** Draft Submittal Ep */ + post: operations["draft_submittal_ep_projects__pid__draft_submittal_summary_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/procurement/rfq-status": { + "/projects/{pid}/drawing-set": { parameters: { query?: never; header?: never; @@ -7209,10 +7990,11 @@ export interface paths { cookie?: never; }; /** - * Rfq Status - * @description Whether RFQ dispatch to suppliers is configured (else quote leveling + 3-way match still work). + * Get Drawing Set + * @description Controlled drawing-set register from the `drawing` records: current set (latest revision per + * sheet), superseded revisions, sheet index + discipline rollup + issuance (new vs revised). */ - get: operations["rfq_status_procurement_rfq_status_get"]; + get: operations["get_drawing_set_projects__pid__drawing_set_get"]; put?: never; post?: never; delete?: never; @@ -7221,7 +8003,7 @@ export interface paths { patch?: never; trace?: never; }; - "/estimate/conceptual/catalog": { + "/projects/{pid}/drawing-set/compiled.pdf": { parameters: { query?: never; header?: never; @@ -7229,10 +8011,12 @@ export interface paths { cookie?: never; }; /** - * Conceptual Catalog - * @description Building-type + region reference tables for the conceptual estimator. + * Drawing Set Compiled + * @description The **whole drawing set compiled into one multi-page PDF** — a cover / sheet-index, a floor plan per + * storey (tall towers sample evenly, capped by `max_sheets`), and the door/window/room schedules. The + * single-file handover deliverable. Needs a source IFC. */ - get: operations["conceptual_catalog_estimate_conceptual_catalog_get"]; + get: operations["drawing_set_compiled_projects__pid__drawing_set_compiled_pdf_get"]; put?: never; post?: never; delete?: never; @@ -7241,7 +8025,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/estimate/conceptual": { + "/projects/{pid}/drawing-set/file-drawing-set": { parameters: { query?: never; header?: never; @@ -7251,18 +8035,21 @@ export interface paths { get?: never; put?: never; /** - * Conceptual - * @description Conceptual (Class 5) cost from building type + GFA + units — low/base/high, escalated for - * region/year, with $/SF, $/unit, $/key metrics for the proforma. + * File Drawing Set Route + * @description R32-FILE-GENERATED — compile the current set to one PDF and file it into `02_Drawings`. + * + * A deliberate act, not a side effect of issuing: this re-renders every sheet and merges them. Each + * call supersedes the previous one as the next revision of a single "Drawing Set" document, so the + * document tree — not a second register — answers which set is current. */ - post: operations["conceptual_projects__pid__estimate_conceptual_post"]; + post: operations["file_drawing_set_route_projects__pid__drawing_set_file_drawing_set_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/ifc/classify": { + "/projects/{pid}/drawing-set/generate": { parameters: { query?: never; header?: never; @@ -7272,39 +8059,40 @@ export interface paths { get?: never; put?: never; /** - * Classify - * @description Suggest IfcClass reclassifications for generic/proxy or loosely-named elements (improves QTO + - * carbon). Uses posted `elements`, or the project's loaded property index when none are given. + * Generate Drawing Set + * @description Generate the discipline sheet set as `drawing` records — one sheet series per discipline with + * its own NCS designator (A-/S-/M-/E-/P-/FP-/FA-/T-/…), a plan per building level, and the usual + * sections/details/schedules. Body: `{disciplines?:[…], all?:bool, max_levels?:int}`. Idempotent — + * existing sheet numbers are skipped. Flows straight into the drawing-set register + transmittal. */ - post: operations["classify_projects__pid__ifc_classify_post"]; + post: operations["generate_drawing_set_projects__pid__drawing_set_generate_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/parcels/screen": { + "/projects/{pid}/drawing-set/issuance-matrix": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Screen - * @description Filter + rank a parcel set by size/zoning/flood/utilities, each with a max-buildable envelope + - * conceptual cost (screen → envelope → proforma). Body: {parcels:[...], criteria:{...}}. + * Drawing Issuance Matrix + * @description The sheet-index × issuance grid — each sheet's revision in each issuance (the front-of-set matrix). */ - post: operations["screen_parcels_screen_post"]; + get: operations["drawing_issuance_matrix_projects__pid__drawing_set_issuance_matrix_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/parcels/data-status": { + "/projects/{pid}/drawing-set/issuance-purposes": { parameters: { query?: never; header?: never; @@ -7312,10 +8100,10 @@ export interface paths { cookie?: never; }; /** - * Data Status - * @description Whether a nationwide parcel/comps data provider is connected (else screening uses your parcels). + * Issuance Purposes + * @description The AIA/CD issuance purposes (SD/DD/CD/Permit/Bid/IFC/Addendum/Conformed/Record). */ - get: operations["data_status_parcels_data_status_get"]; + get: operations["issuance_purposes_projects__pid__drawing_set_issuance_purposes_get"]; put?: never; post?: never; delete?: never; @@ -7324,7 +8112,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/pricing/reconcile": { + "/projects/{pid}/drawing-set/issuances": { parameters: { query?: never; header?: never; @@ -7332,11 +8120,10 @@ export interface paths { cookie?: never; }; /** - * Reconcile - * @description Price the project's takeoff (production_quantity) against the unit price book / live feed, with - * per-line variance vs any estimated unit price. + * Drawing Issuances + * @description The issuance history — every release, its purpose, date, sheet count, recipients. */ - get: operations["reconcile_projects__pid__pricing_reconcile_get"]; + get: operations["drawing_issuances_projects__pid__drawing_set_issuances_get"]; put?: never; post?: never; delete?: never; @@ -7345,7 +8132,7 @@ export interface paths { patch?: never; trace?: never; }; - "/pricing/status": { + "/projects/{pid}/drawing-set/issuances/{iid}/sealed.pdf": { parameters: { query?: never; header?: never; @@ -7353,10 +8140,12 @@ export interface paths { cookie?: never; }; /** - * Pricing Status - * @description Whether a live pricing feed is configured (else the built-in book is used). + * Issuance Sealed + * @description The issuance transmittal digitally **sealed** (PAdES) by the professional of record — the + * tamper-evident electronic seal for permit/IFC submittal. Unsealed if e-sign isn't configured + * (X-Sealed: false). */ - get: operations["pricing_status_pricing_status_get"]; + get: operations["issuance_sealed_projects__pid__drawing_set_issuances__iid__sealed_pdf_get"]; put?: never; post?: never; delete?: never; @@ -7365,7 +8154,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/warranties/expiring": { + "/projects/{pid}/drawing-set/issuances/{iid}/transmittal.pdf": { parameters: { query?: never; header?: never; @@ -7373,11 +8162,10 @@ export interface paths { cookie?: never; }; /** - * Warranties Expiring - * @description Warranties expiring within `within_days` (and any already expired) — turnover tracking so - * expiries don't lapse silently. Reads the `expires` date on each warranty record. + * Issuance Transmittal + * @description A transmittal PDF for one issuance, stamped with its purpose + date and the sheets released. */ - get: operations["warranties_expiring_projects__pid__warranties_expiring_get"]; + get: operations["issuance_transmittal_projects__pid__drawing_set_issuances__iid__transmittal_pdf_get"]; put?: never; post?: never; delete?: never; @@ -7386,115 +8174,121 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/compliance/expiring": { + "/projects/{pid}/drawing-set/issue": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Compliance Expiring - * @description Insurance certificates (COI) and permits expiring within `within_days`, plus any already - * expired — so a super sees lapsing compliance before it bites. Both key off the canonical - * `expires` date; closed permits are ignored. + * Issue Drawing Set + * @description Issue the current drawing set for a purpose — snapshots every current sheet + its revision. + * Body: `{purpose, date?, description?, recipients?, enforce?}`. The **pre-flight issuance gate** + * runs automatically and its verdict is stamped on the issuance record; with `enforce: true` a HOLD + * verdict blocks the issue (409 listing the blocking checks). */ - get: operations["compliance_expiring_projects__pid__compliance_expiring_get"]; - put?: never; - post?: never; + post: operations["issue_drawing_set_projects__pid__drawing_set_issue_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cost/pay-app/advance": { + "/projects/{pid}/drawing-set/plan": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Payapp Advance - * @description Close the current pay-app period: roll each SOV line's `completed_this` into - * `completed_prev` and zero `completed_this`, so the next G702/G703 application starts a fresh - * period with the prior work correctly shown as previous certificates. Returns the next app no. + * Drawing Set Plan + * @description Preview the discipline sheet set that would be generated (no records created): one series per + * discipline with NCS sheet numbers (M-/FA-/S-/…), a plan per level, plus sections/details/schedules. + * `disciplines` is a comma-separated list of designators or names; `all=true` forces the full set. */ - post: operations["payapp_advance_projects__pid__cost_pay_app_advance_post"]; + get: operations["drawing_set_plan_projects__pid__drawing_set_plan_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/convert/citygml": { + "/projects/{pid}/drawing-set/qa": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Convert Citygml - * @description CityGML (city/site context — the OGC standard behind 3D City Database / Cesium city tiles) → - * GeoJSON building footprints, which load as a GIS reference layer in the viewer. Fully offline. + * Drawing Set Qa + * @description QA-AGENT: the drawing-set QA review — set integrity (duplicates/gaps/titleblock), issuance + * hygiene, and model cross-checks (plans-per-storey, schedule-vs-model counts, discipline coverage), + * every finding cited to its sheet. Computed from the structured register + model source — no raster + * interpretation. Runs without a model (register checks only) and adds the cross-checks when one exists. */ - post: operations["convert_citygml_convert_citygml_post"]; + get: operations["drawing_set_qa_projects__pid__drawing_set_qa_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/convert": { + "/projects/{pid}/drawing-set/references": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Convert - * @description Convert an uploaded proprietary model to Fragments. RVT via APS (paid) when configured; - * DWG/NWC require the paid APS/ODA bridge. Returns .frag bytes on success. + * Get Detail References + * @description The section↔detail cross-reference graph, with its two defects reported separately. + * + * `dangling` = a callout points at a detail that does not exist (the reader follows it and finds + * nothing); `orphans` = a detail nothing points at. They are split further by reason because the fix + * differs — an unknown sheet is a typo or an unissued sheet, while a known sheet missing the detail + * number is almost always a renumber that was not propagated. */ - post: operations["convert_convert_post"]; + get: operations["get_detail_references_projects__pid__drawing_set_references_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/convert/vim/inspect": { + "/projects/{pid}/drawing-set/revisions": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Inspect Vim - * @description Inspect an uploaded VIM / G3D (Ara3D/VIM binary family) — schema/version, buffer inventory and - * geometry stats (vertex/index counts + bounding box) via a pure-Python BFAST reader. Fully offline; - * data-layer inspection (full VIM entity decode + viewer streaming are a follow-up). + * Drawing Revisions + * @description The cross-sheet revision register — every delta on every sheet (newest first) with the driving + * change instrument. The 'what changed, when, why' log. */ - post: operations["inspect_vim_convert_vim_inspect_post"]; + get: operations["drawing_revisions_projects__pid__drawing_set_revisions_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/convert/e57/status": { + "/projects/{pid}/drawing-set/transmittal.pdf": { parameters: { query?: never; header?: never; @@ -7502,10 +8296,10 @@ export interface paths { cookie?: never; }; /** - * E57 Status - * @description Whether server-side E57 → .xyz point-cloud conversion is available (needs optional `pye57`). + * Drawing Set Transmittal + * @description A transmittal PDF of the controlled current set (recipients via `to`, comma-separated). */ - get: operations["e57_status_convert_e57_status_get"]; + get: operations["drawing_set_transmittal_projects__pid__drawing_set_transmittal_pdf_get"]; put?: never; post?: never; delete?: never; @@ -7514,41 +8308,44 @@ export interface paths { patch?: never; trace?: never; }; - "/auth/register": { + "/projects/{pid}/drawings/elevation.dxf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Elevation Dxf + * @description Projected elevation outlines as a downloadable **DXF** (R12). + */ + get: operations["elevation_dxf_projects__pid__drawings_elevation_dxf_get"]; put?: never; - /** Register */ - post: operations["register_auth_register_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/login": { + "/projects/{pid}/drawings/elevation.svg": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Elevation */ + get: operations["elevation_projects__pid__drawings_elevation_svg_get"]; put?: never; - /** Login */ - post: operations["login_auth_login_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/logout": { + "/projects/{pid}/drawings/layout.pdf": { parameters: { query?: never; header?: never; @@ -7557,15 +8354,18 @@ export interface paths { }; get?: never; put?: never; - /** Logout */ - post: operations["logout_auth_logout_post"]; + /** + * Layout Pdf + * @description SHEET-VIEWPORTS: the paper-space viewport sheet as a submittable PDF. + */ + post: operations["layout_pdf_projects__pid__drawings_layout_pdf_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/mfa/verify": { + "/projects/{pid}/drawings/layout.svg": { parameters: { query?: never; header?: never; @@ -7575,26 +8375,31 @@ export interface paths { get?: never; put?: never; /** - * Mfa Verify - * @description Login step 2: exchange the challenge ticket + a TOTP (or one-time recovery) code for a - * session. A used recovery code is burned. + * Layout Svg + * @description SHEET-VIEWPORTS: compose paper-space viewports — each with its view (plan/section/elevation), an + * optional FIXED drawing scale (true 1:N on paper, geometry clipped to the viewport rect — crop, not + * shrink), and an optional per-viewport class freeze — rendered through the shared titleblock. */ - post: operations["mfa_verify_auth_mfa_verify_post"]; + post: operations["layout_svg_projects__pid__drawings_layout_svg_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/mfa/status": { + "/projects/{pid}/drawings/layout/presets": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Mfa Status */ - get: operations["mfa_status_auth_mfa_status_get"]; + /** + * Layout Presets + * @description SHEET-VIEWPORTS: the named paper-space viewport arrangements (fraction rects) a client can start + * from — override any field per viewport before posting to layout.svg/.pdf. + */ + get: operations["layout_presets_projects__pid__drawings_layout_presets_get"]; put?: never; post?: never; delete?: never; @@ -7603,28 +8408,28 @@ export interface paths { patch?: never; trace?: never; }; - "/auth/mfa/setup": { + "/projects/{pid}/drawings/markup": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Mfa Setup - * @description Begin enrollment: generate (and store, pending) a fresh secret; return it + an otpauth URI - * to show as a QR/manual key. Not active until confirmed at /auth/mfa/enable with a valid code. + * List Markup + * @description Markup pins for a project, optionally filtered to one sheet. */ - post: operations["mfa_setup_auth_mfa_setup_post"]; + get: operations["list_markup_projects__pid__drawings_markup_get"]; + put?: never; + /** Add Markup */ + post: operations["add_markup_projects__pid__drawings_markup_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/mfa/enable": { + "/projects/{pid}/drawings/markup/bulk": { parameters: { query?: never; header?: never; @@ -7634,109 +8439,130 @@ export interface paths { get?: never; put?: never; /** - * Mfa Enable - * @description Confirm enrollment with a code from the authenticator; on success turn MFA on and return - * one-time recovery codes (shown once — the server stores only their hashes). + * Save Markup Bulk + * @description Save the 2D editor's whole markup scene for a sheet. With `replace`, the caller's own prior + * markups for that sheet are cleared first — but markups promoted to an RFI (topic_id set) are kept, + * so a located issue is never silently dropped. */ - post: operations["mfa_enable_auth_mfa_enable_post"]; + post: operations["save_markup_bulk_projects__pid__drawings_markup_bulk_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/mfa/disable": { + "/projects/{pid}/drawings/markup/stream": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Mfa Disable - * @description Turn MFA off. Requires the account password AND a current TOTP/recovery code, so a merely - * hijacked session can't strip the second factor. + * Markup Stream + * @description MARKUP-2d (live co-markup): server-sent events for the shared sheet — polls a cheap signature + * (markup row count + latest created_at for the project) every few seconds and pushes it on change, + * so every open sheet live-refreshes the moment anyone saves a markup. Fresh DB session per poll + * (the generator outlives the request scope — mirrors the pull-plan/notifications streams). */ - post: operations["mfa_disable_auth_mfa_disable_post"]; + get: operations["markup_stream_projects__pid__drawings_markup_stream_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/providers": { + "/projects/{pid}/drawings/markup/{mid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Auth Providers - * @description Enabled SSO providers (those with client id + secret configured) — drives the login UI. - * `saml` is true when a SAML IdP is configured (its button posts to /auth/saml/login). - */ - get: operations["auth_providers_auth_providers_get"]; + get?: never; put?: never; post?: never; - delete?: never; + /** Delete Markup */ + delete: operations["delete_markup_projects__pid__drawings_markup__mid__delete"]; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/oauth/{provider}/login": { + "/projects/{pid}/drawings/markup/{mid}/promote": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Oauth Login - * @description Redirect to the provider's consent screen. + * Promote Markup + * @description Promote a markup pin to an RFI Topic (Fieldlens/PlanGrid: a located issue on the sheet). */ - get: operations["oauth_login_auth_oauth__provider__login_get"]; - put?: never; - post?: never; + post: operations["promote_markup_projects__pid__drawings_markup__mid__promote_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/oauth/{provider}/callback": { + "/projects/{pid}/drawings/markups/rekey-storeys": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Oauth Callback - * @description Exchange the code, map the verified email to an account, mint the session, and return - * to the app. SSO accounts are always plain free-tier users (no admin tier for end users). + * Rekey Storey Markups + * @description Move pre-v0.3.1106 storey markups from `plan:` onto `plan:`. + * + * Storey plan markups were keyed on the storey NAME until v0.3.1106, so renaming a level orphaned + * every pin on it. New markups are GUID-keyed and the old key is still READ, so nothing disappeared + * — but a markup left under the old key still orphans on rename. Rekeying needs a name → GlobalId + * map, which only the source IFC holds; that is why it is this route and not a migration. + * + * **`dry_run` defaults to TRUE.** This rewrites rows, and the mapping depends on a model that may + * have been re-uploaded since the markups were made. The caller sees exactly what would move, and + * what would not, before anything does. + * + * Two things are refused rather than guessed, and both are reported by name: + * + * * **an unmatched name** — the markup's storey no longer exists in the model (renamed already, or + * a different model). There is nothing to map it to; + * * **an AMBIGUOUS name** — two storeys share it, so the name does not identify one GlobalId. This + * is the case the whole change exists to prevent, and picking either would be the same guess in + * the other direction. + * + * Idempotent: markups already on a GUID key match no storey name and are left alone, so running it + * twice moves nothing the second time. */ - get: operations["oauth_callback_auth_oauth__provider__callback_get"]; - put?: never; - post?: never; + post: operations["rekey_storey_markups_projects__pid__drawings_markups_rekey_storeys_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/me": { + "/projects/{pid}/drawings/plan.dxf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Me */ - get: operations["me_auth_me_get"]; + /** + * Plan Dxf + * @description Plan linework as a downloadable **DXF** (R12) for CAD interchange — any CAD tool opens it. + */ + get: operations["plan_dxf_projects__pid__drawings_plan_dxf_get"]; put?: never; post?: never; delete?: never; @@ -7745,29 +8571,36 @@ export interface paths { patch?: never; trace?: never; }; - "/auth/password": { + "/projects/{pid}/drawings/plan.svg": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Change Password - * @description Change your own password (requires the current one). Rotating the password revokes every - * other outstanding session (bumps token_epoch); a fresh token is issued so the current tab - * stays signed in. + * Plan + * @description Schematic plan (SVG) cut at `elevation + cut_height`. VIEW-RANGE: pass `view_depth` (metres below + * the cut) to also draw the footprint of anything under the cut but within that depth — foundations/ + * footings show as dashed hidden lines, the Revit Top/Cut/Bottom/View-Depth model rather than one cut_z. + * DISC-poché: `by_discipline=true` strokes each element's linework with its discipline color + legend. + * + * `storey` names a level and overrides `elevation` (R38-SYNC-SELECT). This route had no such + * parameter, and the plan pane was SENDING one the whole time — FastAPI drops unknown query params, + * so every "storey-synced" plan was silently cut at elevation 0. An unknown name is a 404 carrying + * the real names, not a silent default: cutting the wrong level while echoing the requested label + * is the confident-wrong shape. */ - post: operations["change_password_auth_password_post"]; + get: operations["plan_projects__pid__drawings_plan_svg_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/logout-all": { + "/projects/{pid}/drawings/received-regions": { parameters: { query?: never; header?: never; @@ -7777,101 +8610,113 @@ export interface paths { get?: never; put?: never; /** - * Logout All - * @description Sign out everywhere: revoke every outstanding session for the caller (bump token_epoch), - * then re-mint the current session so this tab stays in. Use after a suspected token leak. + * Received Sheet Regions + * @description R27-LAYOUT ①(b) — the layout layer of a sheet we did **not** draw. + * + * `sheet-regions` above answers "what occupies which rectangle" for our own sheets, by keeping the + * numbers `compose_viewports` computed. Every consultant sheet had no such answer: `sheet_extract` + * regexes the text layer with no notion of *where on the page* anything sits, so a note, a takeoff + * or a revision could only attach to a page number. This reads the rectangles out of the page's own + * content stream and classifies them. + * + * Each region reports `basis: "vector"`. `to_page` is **null for every region** — the page↔world + * mapping cannot be recovered from a received sheet, and an identity would silently report page + * points as metres. Any scale printed on the sheet comes back as `scale_denom_proposed`, for a + * calibration step to accept: a takeoff auto-calibrated wrong looks finished, which is worse than + * one nobody calibrated. A page whose vectors are gone (a scan) returns a stated **unknown** region + * rather than an empty list — an empty list is a claim about the drawing, unknown is a claim about us. */ - post: operations["logout_all_auth_logout_all_post"]; + post: operations["received_sheet_regions_projects__pid__drawings_received_regions_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/users": { + "/projects/{pid}/drawings/schedule.csv": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Users */ - get: operations["list_users_auth_users_get"]; - put?: never; /** - * Create User - * @description Admin-created account (the open path after bootstrap; /auth/register stays for the - * very first user). + * Schedule Csv + * @description W10-6: the computed schedule(s) as a CSV download — `kind` (doors|windows|rooms) for one, or omit + * for all three. For spreadsheets / procurement / submittals. */ - post: operations["create_user_auth_users_post"]; + get: operations["schedule_csv_projects__pid__drawings_schedule_csv_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/users/{username}": { + "/projects/{pid}/drawings/schedule.pdf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** + * Schedule Pdf + * @description W11 C6: the computed schedules laid out on an issuable ARCH-D **sheet** (border + titleblock) as a + * submittable PDF. `kinds` is a comma list of doors|windows|rooms. + */ + get: operations["schedule_pdf_projects__pid__drawings_schedule_pdf_get"]; put?: never; post?: never; delete?: never; options?: never; head?: never; - /** - * Update User - * @description Change a user's role and/or activate/deactivate them. Won't lock out the last admin. - */ - patch: operations["update_user_auth_users__username__patch"]; + patch?: never; trace?: never; }; - "/auth/users/{username}/password": { + "/projects/{pid}/drawings/schedule.svg": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Reset Password - * @description Admin reset of another user's password. + * Schedule Svg + * @description W11 C4: one schedule (doors|windows|rooms) rendered as an SVG table. */ - post: operations["reset_password_auth_users__username__password_post"]; + get: operations["schedule_svg_projects__pid__drawings_schedule_svg_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/users/{username}/revoke-sessions": { + "/projects/{pid}/drawings/schedules": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Revoke Sessions - * @description Admin: force-revoke all of a user's outstanding tokens (e.g. offboarding / lost device). - * They must sign in again; deactivating the account (active=false) blocks re-login entirely. + * Drawing Schedules + * @description W11 C4: computed door / window / room schedules from the model (marks, sizes, types, levels, areas) + * — the tabular half of a CD set. */ - post: operations["revoke_sessions_auth_users__username__revoke_sessions_post"]; + get: operations["drawing_schedules_projects__pid__drawings_schedules_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/users/{username}/reset-token": { + "/projects/{pid}/drawings/schedules/calc": { parameters: { query?: never; header?: never; @@ -7881,19 +8726,21 @@ export interface paths { get?: never; put?: never; /** - * Issue Reset Token - * @description Admin issues a single-use, 1-hour reset token for a user to set their own password - * (no email infra needed — hand the token to the user). The token can't be used as a - * bearer token and is invalidated once the password changes. + * Drawing Schedules Calc + * @description SCHED-CALC (R18): the computed door/window/room schedules extended with **calculated fields** — + * deterministic formula columns over the schedule's own values (`width * height`, `"D-" + mark`, + * conditionals; field names are the normalized column titles, e.g. ``width_m``). Body: + * `{doors|windows|rooms: [{name, expr}, …]}` (any subset). A bad expression 422s at definition time; + * a bad ROW yields an empty cell, never a dead column. No scripting runtime — AST whitelist only. */ - post: operations["issue_reset_token_auth_users__username__reset_token_post"]; + post: operations["drawing_schedules_calc_projects__pid__drawings_schedules_calc_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/settings/integrations": { + "/projects/{pid}/drawings/section.dxf": { parameters: { query?: never; header?: never; @@ -7901,17 +8748,11 @@ export interface paths { cookie?: never; }; /** - * Get Integrations - * @description Integration config for the Settings panel. Secret values are never returned — only - * whether each is configured. - */ - get: operations["get_integrations_settings_integrations_get"]; - /** - * Put Integrations - * @description Set/clear integration settings. A value here overrides the matching env var; an empty - * string clears it. Keys are validated against the catalog; secret values are not echoed back. + * Section Dxf + * @description Vertical section linework as a downloadable **DXF** (R12); omit `offset` to auto-centre the cut. */ - put: operations["put_integrations_settings_integrations_put"]; + get: operations["section_dxf_projects__pid__drawings_section_dxf_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -7919,28 +8760,36 @@ export interface paths { patch?: never; trace?: never; }; - "/settings/integrations/test": { + "/projects/{pid}/drawings/section.svg": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Test Integration - * @description Live 'Test connection' for one integration group (by its catalog name) → {ok, message}. - * Gives a non-technical admin instant confirmation a key actually works. + * Section + * @description Vertical section SVG. `offset` = world coordinate (m) of the cut on the axis perpendicular to + * `axis` (x|y); omit it to auto-centre the cut through the model. + * + * Annotated by default — material hatch on cut material, level datums, grid bubbles and a + * floor-to-floor dimension chain. `lod` (coarse|fine|line) sets how much tone survives when an + * element is too thin to hatch; `hatch=false` falls back to flat poché tones; `annotate=false` + * returns bare linework for CAD hand-off. + * + * `keynotes` (default on) adds the assembly-annotation column: one leader per DISTINCT assembly, + * not per cut polygon, so a wall repeated at every storey is named once. */ - post: operations["test_integration_settings_integrations_test_post"]; + get: operations["section_projects__pid__drawings_section_svg_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/audit": { + "/projects/{pid}/drawings/sheet-regions": { parameters: { query?: never; header?: never; @@ -7948,11 +8797,19 @@ export interface paths { cookie?: never; }; /** - * Audit Log - * @description Admin read of the audit trail, newest first. Filter by action/actor substring and a - * `since` ISO timestamp. + * Sheet Regions Endpoint + * @description R27-LAYOUT ①(a) — the sheet's **layout layer**: what occupies which rectangle, in page points. + * + * This is the producer for the `layout` that `POST /takeoff/2d` accepts. Wiring the consumer without + * it (v0.3.706) left that route asking for something no caller could obtain — the same one-way + * asymmetry `R25-TASK-BIND` existed to close, reintroduced while closing a different instance of it. + * + * Each region carries `basis: "authored"` — these **are** the numbers the sheet was drawn with, not a + * recovery from rendered output — plus the exact page↔world affine, so a takeoff scoped to a viewport + * needs no calibration step at all. A viewport with no geometry reports `to_page: null`: the mapping + * is **unknown**, never an identity, since an identity would silently report page points as metres. */ - get: operations["audit_log_audit_get"]; + get: operations["sheet_regions_endpoint_projects__pid__drawings_sheet_regions_get"]; put?: never; post?: never; delete?: never; @@ -7961,7 +8818,7 @@ export interface paths { patch?: never; trace?: never; }; - "/webhooks/deliveries": { + "/projects/{pid}/drawings/sheet.dxf": { parameters: { query?: never; header?: never; @@ -7969,11 +8826,11 @@ export interface paths { cookie?: never; }; /** - * Webhook Deliveries - * @description Recent outbound-webhook delivery attempts (newest first) — url, event, ok, status, attempts, - * error — plus whether HMAC signing is configured. Process-local ring; for 'did my hook fire?'. + * Sheet Dxf + * @description DXF-EXPORT — the composed sheet (viewports + annotations + titleblock) as editable R12 CAD + * linework, not just paper. Same composition as sheet.svg/pdf; layers BORDER/VIEW-n/ANNO/TITLEBLOCK. */ - get: operations["webhook_deliveries_webhooks_deliveries_get"]; + get: operations["sheet_dxf_projects__pid__drawings_sheet_dxf_get"]; put?: never; post?: never; delete?: never; @@ -7982,27 +8839,24 @@ export interface paths { patch?: never; trace?: never; }; - "/auth/reset": { + "/projects/{pid}/drawings/sheet.pdf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Sheet Pdf */ + get: operations["sheet_pdf_projects__pid__drawings_sheet_pdf_get"]; put?: never; - /** - * Reset With Token - * @description Unauthenticated: set a new password using a reset token (the token is the credential). - */ - post: operations["reset_with_token_auth_reset_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/scim/v2/ServiceProviderConfig": { + "/projects/{pid}/drawings/sheet.svg": { parameters: { query?: never; header?: never; @@ -8010,10 +8864,12 @@ export interface paths { cookie?: never; }; /** - * Scim Config - * @description Advertise the supported feature set (RFC 7643 §5). We support filter + PATCH, not bulk/sort/etag. + * Sheet Svg + * @description A composed **key-plan sheet** (representative plans + section + elevation in a titleblock). `storey` + * renders one named level's sheet; omitted, it samples up to a few levels (a tall tower gets one sheet per + * level in the full set — cramming every plan on a page is neither fast nor legible). */ - get: operations["scim_config_scim_v2_ServiceProviderConfig_get"]; + get: operations["sheet_svg_projects__pid__drawings_sheet_svg_get"]; put?: never; post?: never; delete?: never; @@ -8022,15 +8878,15 @@ export interface paths { patch?: never; trace?: never; }; - "/scim/v2/ResourceTypes": { + "/projects/{pid}/drawings/storeys": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Scim Resource Types */ - get: operations["scim_resource_types_scim_v2_ResourceTypes_get"]; + /** List Storeys */ + get: operations["list_storeys_projects__pid__drawings_storeys_get"]; put?: never; post?: never; delete?: never; @@ -8039,7 +8895,7 @@ export interface paths { patch?: never; trace?: never; }; - "/scim/v2/Users": { + "/projects/{pid}/drawings/stream": { parameters: { query?: never; header?: never; @@ -8047,74 +8903,65 @@ export interface paths { cookie?: never; }; /** - * Scim List Users - * @description List (or, with `filter=userName eq "x"`, look up) provisioned users. 1-based paging (RFC 7644). + * Drawings Stream + * @description Server-sent events: pushes the model `version` and re-pushes the instant it changes (a new model + * is published), so open 2D drawing views regenerate themselves — live propagation from the model + * without polling or an external event bus. */ - get: operations["scim_list_users_scim_v2_Users_get"]; + get: operations["drawings_stream_projects__pid__drawings_stream_get"]; put?: never; - /** - * Scim Create User - * @description Provision a new SSO-only account. Idempotent-ish: re-activating an existing username updates it - * (some IdPs POST on re-provision) rather than 409-ing, so a rehire cleanly reactivates. - */ - post: operations["scim_create_user_scim_v2_Users_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/scim/v2/Users/{user_id}": { + "/projects/{pid}/drawings/sync-status": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** Scim Get User */ - get: operations["scim_get_user_scim_v2_Users__user_id__get"]; /** - * Scim Replace User - * @description Full replace of the mutable attributes (userName is immutable — the id). + * Drawings Sync Status + * @description Model fingerprint + version for 2D staleness detection — the client compares `version` / + * `signature` across renders to know when the on-demand drawings need regenerating. `version` bumps + * every time a new model is published (see /drawings/stream for the push equivalent). */ - put: operations["scim_replace_user_scim_v2_Users__user_id__put"]; + get: operations["drawings_sync_status_projects__pid__drawings_sync_status_get"]; + put?: never; post?: never; - /** - * Scim Delete User - * @description De-provision. Soft-delete: deactivate + revoke tokens (keeps the audit trail / record authorship - * intact) rather than hard-deleting the row. A subsequent POST reactivates (rehire). - */ - delete: operations["scim_delete_user_scim_v2_Users__user_id__delete"]; + delete?: never; options?: never; head?: never; - /** - * Scim Patch User - * @description Partial update (RFC 7644 §3.5.2). The common case is deactivation on offboarding. - */ - patch: operations["scim_patch_user_scim_v2_Users__user_id__patch"]; + patch?: never; trace?: never; }; - "/auth/saml/metadata": { + "/projects/{pid}/drawings/{drawing_id}/revise": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Saml Metadata - * @description SP metadata XML to register with the IdP (entityID + ACS). Available whenever SAML is on. + * Revise Drawing + * @description Record a revision (delta) on a sheet — appends to its revision block, bumps the current + * revision, and optionally cites the driving change instrument (ASI/CCD/Addendum/Bulletin). + * Body: `{rev, description?, date?, instrument_type?, instrument_ref?}`. */ - get: operations["saml_metadata_auth_saml_metadata_get"]; - put?: never; - post?: never; + post: operations["revise_drawing_projects__pid__drawings__drawing_id__revise_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/auth/saml/login": { + "/projects/{pid}/due-feed": { parameters: { query?: never; header?: never; @@ -8122,10 +8969,10 @@ export interface paths { cookie?: never; }; /** - * Saml Login - * @description Redirect to the IdP's SSO URL with a SAMLRequest (SP-initiated, HTTP-Redirect binding). + * Due Feed + * @description Cross-module SLA feed — open records past or near their due date (overdue / due-soon). */ - get: operations["saml_login_auth_saml_login_get"]; + get: operations["due_feed_projects__pid__due_feed_get"]; put?: never; post?: never; delete?: never; @@ -8134,7 +8981,7 @@ export interface paths { patch?: never; trace?: never; }; - "/auth/saml/acs": { + "/projects/{pid}/edit": { parameters: { query?: never; header?: never; @@ -8144,39 +8991,51 @@ export interface paths { get?: never; put?: never; /** - * Saml Acs - * @description Assertion Consumer Service — verify the signed response, map the email to an account, mint the - * session, and return to the app. Any verification failure is a 403 (never leak crypto detail). + * Edit + * @description Apply an authoring recipe (set_pset | batch_tag | place_type) to the source IFC, + * saving a new version. GUIDs of existing elements are preserved. + * + * `want_guid` makes the newly created element adopt a GlobalId the caller already has — which is + * how an `edit-preview` fragment and the committed element end up being the SAME element. Without + * it the two runs mint different GUIDs, and R42-COMMIT-DELTA (which keeps the preview fragment on + * screen) would leave the user looking at a shape whose GUID is in no index. 400 if the id is + * malformed, already taken, or the recipe did not create exactly one element. + * + * COLLAB-1 optimistic lock: pass `base_source` (the model signature the client last loaded, from + * `GET .../collab`) and the edit is rejected **409** if another user has published since — so a + * concurrent edit surfaces a 'model changed — reload' instead of silently overwriting their work. */ - post: operations["saml_acs_auth_saml_acs_post"]; + post: operations["edit_projects__pid__edit_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections": { + "/projects/{pid}/edit-preview": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * List Connections - * @description Built-in local DB (with live status) + registered external connections (status on demand). + * Edit Preview + * @description Author just this element into a one-element IFC + convert it to a small preview fragment (fast), + * so the viewer can show real geometry immediately while the full model republishes in the + * background. Fail-open: 503 when the source/converter is unavailable, so the client just keeps its + * optimistic proxy and waits for the normal publish. */ - get: operations["list_connections_connections_get"]; - put?: never; - /** Create Connection */ - post: operations["create_connection_connections_post"]; + post: operations["edit_preview_projects__pid__edit_preview_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}": { + "/projects/{pid}/edit/batch": { parameters: { query?: never; header?: never; @@ -8184,17 +9043,22 @@ export interface paths { cookie?: never; }; get?: never; - /** Update Connection */ - put: operations["update_connection_connections__cid__put"]; - post?: never; - /** Delete Connection */ - delete: operations["delete_connection_connections__cid__delete"]; + put?: never; + /** + * Edit Batch + * @description S4 — apply a **sequence of authoring steps as ONE version** (`steps: [{recipe, params}, …]`): + * the model opens once, every step runs in memory, one file is written, one edit-history entry is + * pushed — so a multi-step NL command **undoes as a single step**. All-or-nothing: every step is + * guard-prechecked before anything runs. Honors the COLLAB-1 optimistic lock via `base_source`. + */ + post: operations["edit_batch_projects__pid__edit_batch_post"]; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/test": { + "/projects/{pid}/edit/graph": { parameters: { query?: never; header?: never; @@ -8204,57 +9068,61 @@ export interface paths { get?: never; put?: never; /** - * Test Config - * @description Test a posted config (used by the add/edit form before saving). + * Edit Graph + * @description AUTH-VS: execute a **recipe graph** — a set of authoring-recipe nodes wired by data dependencies + * (one node's created GUID feeds the next) — as a single GUID-stable authoring pass, saving one new + * version. Body: {graph:{nodes,edges}, publish?, base_source?}. Honors the COLLAB-1 optimistic lock. */ - post: operations["test_config_connections_test_post"]; + post: operations["edit_graph_projects__pid__edit_graph_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}/test": { + "/projects/{pid}/edit/history": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Test Connection - * @description Test a saved connection (uses the stored secret) + return its info payload. + * Edit History State + * @description S4: whether the project's model can be undone / redone, and the stack depths. */ - post: operations["test_connection_connections__cid__test_post"]; + get: operations["edit_history_state_projects__pid__edit_history_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}/tables": { + "/projects/{pid}/edit/precheck": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Connection Tables - * @description List the connection's tables (SQL) or projects (Procore) — the data-plane browse entrypoint. + * Edit Precheck + * @description W11 E8: validate an edit's params against the authoring **guardrails** WITHOUT applying it — + * {ok, errors, warnings}. The client calls this before committing so a novice is told about a broken + * edit (zero-length wall, non-positive size, missing host) before it ever touches the model. */ - get: operations["connection_tables_connections__cid__tables_get"]; - put?: never; - post?: never; + post: operations["edit_precheck_projects__pid__edit_precheck_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}/query": { + "/projects/{pid}/edit/redo": { parameters: { query?: never; header?: never; @@ -8264,37 +9132,40 @@ export interface paths { get?: never; put?: never; /** - * Connection Query - * @description Run a read-only SELECT against a SQL connection (local / Postgres / Supabase). + * Edit Redo + * @description S4: **redo** an undone edit — restore the next model version, republishing only when `publish` + * (the default). */ - post: operations["connection_query_connections__cid__query_post"]; + post: operations["edit_redo_projects__pid__edit_redo_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}/acc/projects/{project_id}/issues": { + "/projects/{pid}/edit/undo": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Acc Issues - * @description Read an ACC project's issues (browse the issues data plane for an Autodesk connection). + * Edit Undo + * @description S4: **undo** the last authoring edit — restore the prior model version, republishing only when + * `publish` (the default). GUID-stable + * (pins/RFIs/clashes keyed by GlobalId survive). */ - get: operations["acc_issues_connections__cid__acc_projects__project_id__issues_get"]; - put?: never; - post?: never; + post: operations["edit_undo_projects__pid__edit_undo_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/connections/{cid}/quickbooks/{entity}": { + "/projects/{pid}/egress/plan.svg": { parameters: { query?: never; header?: never; @@ -8302,10 +9173,11 @@ export interface paths { cookie?: never; }; /** - * Quickbooks Read - * @description Read the QuickBooks chart of accounts / vendors / bills (financial-backbone data plane). + * Egress Plan Svg + * @description The routed egress / life-safety plan as SVG — walls, exits, and each space's actual path to its + * nearest exit, coloured by outcome so a failing space is findable at a glance. */ - get: operations["quickbooks_read_connections__cid__quickbooks__entity__get"]; + get: operations["egress_plan_svg_projects__pid__egress_plan_svg_get"]; put?: never; post?: never; delete?: never; @@ -8314,7 +9186,7 @@ export interface paths { patch?: never; trace?: never; }; - "/connections/{cid}/erp/{entity}": { + "/projects/{pid}/egress/routes": { parameters: { query?: never; header?: never; @@ -8322,10 +9194,18 @@ export interface paths { cookie?: never; }; /** - * Erp Read - * @description Read accounts / vendors / bills from a Sage or Viewpoint (generic REST ERP) connection. + * Egress Routes + * @description D2 — **routed** travel distance to the nearest exit, per space. + * + * IBC 1017 limits travel distance measured *along the path of egress travel*. The existing + * straight-line check ignores every wall between a space and its exit, so it reports a shorter + * distance than anyone can walk and passes plans that do not comply. This routes over the actual + * floor plate and reports both numbers plus their ratio, so the understatement is visible. + * + * `max_travel_m` defaults to 61 m (200 ft, IBC 1017.2 sprinklered Business) — occupancy- and + * sprinkler-dependent, so it is a parameter rather than a constant. */ - get: operations["erp_read_connections__cid__erp__entity__get"]; + get: operations["egress_routes_projects__pid__egress_routes_get"]; put?: never; post?: never; delete?: never; @@ -8334,7 +9214,7 @@ export interface paths { patch?: never; trace?: never; }; - "/connections/{cid}/mappings": { + "/projects/{pid}/element-connections": { parameters: { query?: never; header?: never; @@ -8342,16 +9222,12 @@ export interface paths { cookie?: never; }; /** - * Get Mappings - * @description Editable Procore→module field mapping: per kind, each module field with its default and - * current Procore source path. Drives the field-mapping editor. - */ - get: operations["get_mappings_connections__cid__mappings_get"]; - /** - * Put Mappings - * @description Save per-field Procore source-path overrides ({kind: {field: path}}) on the connection. + * Element Connections + * @description B5: the element-to-element connection graph (`IfcRelConnectsElements`) — connected pairs + per-element + * degree. Author edges with the `connect_elements` recipe (`POST /edit` with `{guid_a, guid_b}`). */ - put: operations["put_mappings_connections__cid__mappings_put"]; + get: operations["element_connections_projects__pid__element_connections_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -8359,7 +9235,7 @@ export interface paths { patch?: never; trace?: never; }; - "/opendata/permit-cities": { + "/projects/{pid}/elements": { parameters: { query?: never; header?: never; @@ -8367,10 +9243,11 @@ export interface paths { cookie?: never; }; /** - * Permit Cities - * @description Cities whose building-permit open data we can read, and whether radius search is supported. + * List Elements + * @description Query the property index. `?discipline=` accepts an NCS code or name (e.g. 'S' or 'Structural'); + * each element is returned with its derived `discipline` (Discipline Spine D2). */ - get: operations["permit_cities_opendata_permit_cities_get"]; + get: operations["list_elements_projects__pid__elements_get"]; put?: never; post?: never; delete?: never; @@ -8379,7 +9256,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/opendata/permits": { + "/projects/{pid}/elements/by-discipline": { parameters: { query?: never; header?: never; @@ -8387,11 +9264,11 @@ export interface paths { cookie?: never; }; /** - * Query Permits - * @description Nearby / matching municipal filings — owner, architect, GC, units, cost, status — for - * acquisition intel and to seed the project's own permit log. + * Elements By Discipline + * @description Model composition by NCS discipline (Discipline Spine D2): element count + a class breakdown per + * discipline, in NCS sheet order. Derived from the property index — one pass, no republish. */ - get: operations["query_permits_projects__pid__opendata_permits_get"]; + get: operations["elements_by_discipline_projects__pid__elements_by_discipline_get"]; put?: never; post?: never; delete?: never; @@ -8400,7 +9277,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/opendata/permits.geojson": { + "/projects/{pid}/elements/code-check": { parameters: { query?: never; header?: never; @@ -8408,11 +9285,13 @@ export interface paths { cookie?: never; }; /** - * Permits Geojson - * @description The same filings as a GeoJSON FeatureCollection for the viewer's GIS overlay (points only). - */ - get: operations["permits_geojson_projects__pid__opendata_permits_geojson_get"]; - put?: never; + * Code Check + * @description Code-readiness check: does the model carry the data a plan review needs (egress door widths, + * fire ratings, space areas/occupancy, egress stairs, classification)? Property-level, not a + * certified code review. Returns per-rule pass/fail + the elements to highlight in 3D. + */ + get: operations["code_check_projects__pid__elements_code_check_get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -8420,28 +9299,32 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/opendata/permits/import": { + "/projects/{pid}/elements/color-by": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Import Permits - * @description Pull a city's filings for the site and create `permit` records on the GC side, source-tagged so - * they don't duplicate on re-import. Body: {city, lat?, lon?, radius?, address?, q?, max?}. + * Color By + * @description Bucket every element by a chosen property → colour buckets for the 3D viewer. Numeric + * properties are binned into ranges; categorical ones grouped by value (top 24 + Other). + * + * `ids=true` (default) returns each bucket's element GUIDs (the viewer needs them to colour), gzipped + * on the wire when large. `ids=false` returns only labels + counts — a compact distribution for a + * legend / picker with no per-element payload. */ - post: operations["import_permits_projects__pid__opendata_permits_import_post"]; + get: operations["color_by_projects__pid__elements_color_by_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cap-table": { + "/projects/{pid}/elements/facets-list": { parameters: { query?: never; header?: never; @@ -8449,10 +9332,11 @@ export interface paths { cookie?: never; }; /** - * Cap Table - * @description Investor cap table — ownership by commitment + contributed/distributed/unreturned totals. + * Color Facets + * @description The properties you can colour by: top-level attributes + every pset/qto property present, + * each with its distinct-value count (drives the viewer's 'Color by…' picker). */ - get: operations["cap_table_projects__pid__cap_table_get"]; + get: operations["color_facets_projects__pid__elements_facets_list_get"]; put?: never; post?: never; delete?: never; @@ -8461,49 +9345,41 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/capital-call": { + "/projects/{pid}/elements/freshness": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Capital Call - * @description Allocate a capital call pro-rata by commitment. persist=true posts it to each investor's - * contributed total; otherwise it's a preview. + * Elements Freshness + * @description Whether the element index still matches the model, and by how much. + * + * The index is a snapshot written at publish time; the model is the IFC on disk. Authoring edits + * move the IFC and leave the snapshot behind until the next publish, so `/elements` can return a + * fraction of what the takeoff sees — and used to do it in complete silence. Nothing was wrong with + * either number; they were answers about different moments, and only one of them said which. + * + * A stale index is not an error and is not repaired here: rebuilding it means reconverting geometry, + * which is far too expensive to trigger from a read. What it must do is **say so**, so a browser can + * show "5 elements added since this list was built — republish to see them" instead of quietly + * looking like a smaller building. + * + * `stale` is None, not False, when either side has no fingerprint — an index written before this was + * stamped, or a project with no source file. Unknown and current are different claims and this + * endpoint refuses to conflate them. */ - post: operations["capital_call_projects__pid__capital_call_post"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/projects/{pid}/distribution": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; + get: operations["elements_freshness_projects__pid__elements_freshness_get"]; put?: never; - /** - * Distribution - * @description Allocate a distribution pro-rata by commitment. persist=true posts it to each investor's - * distributed total; otherwise it's a preview. - */ - post: operations["distribution_projects__pid__distribution_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/waterfall": { + "/projects/{pid}/elements/properties": { parameters: { query?: never; header?: never; @@ -8513,19 +9389,27 @@ export interface paths { get?: never; put?: never; /** - * Waterfall Scenario - * @description Run a distribution / equity-waterfall scenario over the cap table. Body: {distributable:[..], - * dates:[..]} or {exit_amount, contribution_date, exit_date}; optional pref_rate/tiers/style/clawback. - * Returns LP/GP totals, IRR/EM, period splits, and the per-investor allocation. + * Elements Properties + * @description Property sets for many elements in one round trip: `{"guids": [...]}` → a list, one per hit. + * + * **A guid that is not in the index is simply absent from the response.** It is not an error and it + * is not an empty record — the caller has to be able to tell "this element has no property sets" + * from "there is no such element", and answering for everything destroys exactly that difference. + * Which means an empty list is a legitimate answer to a well-formed request. + * + * One POST rather than a GET per element, because a property panel over a multi-selection is where + * per-element round trips actually hurt. The list is bounded and an over-long one is REFUSED rather + * than truncated, since a silent truncation would report existing elements as missing — the one + * failure this endpoint's contract is shaped to prevent. */ - post: operations["waterfall_scenario_projects__pid__waterfall_post"]; + post: operations["elements_properties_projects__pid__elements_properties_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/investors/{iid}/statement.pdf": { + "/projects/{pid}/elements/qa": { parameters: { query?: never; header?: never; @@ -8533,10 +9417,12 @@ export interface paths { cookie?: never; }; /** - * Investor Statement - * @description A one-page investor capital-account statement PDF. + * Data Qa + * @description BIM data-completeness check: for each attribute, how many elements have it, and which are + * missing it. The headline compliance % + the 3D highlight use the required rules; recommended + * rules (type, property sets) are reported separately so gaps surface without failing everything. */ - get: operations["investor_statement_projects__pid__investors__iid__statement_pdf_get"]; + get: operations["data_qa_projects__pid__elements_qa_get"]; put?: never; post?: never; delete?: never; @@ -8545,28 +9431,24 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/investors/{iid}/share": { + "/projects/{pid}/elements/{guid}": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; + /** Element */ + get: operations["element_projects__pid__elements__guid__get"]; put?: never; - /** - * Investor Statement Share - * @description Mint a signed, expiring link to an investor's statement PDF — the investor opens it with no - * session (the LP-portal share). Default TTL 30 days. The signature authorizes exactly that path. - */ - post: operations["investor_statement_share_projects__pid__investors__iid__share_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/investors/{iid}/statement.public.pdf": { + "/projects/{pid}/elements/{guid}/5d": { parameters: { query?: never; header?: never; @@ -8574,11 +9456,12 @@ export interface paths { cookie?: never; }; /** - * Investor Statement Public - * @description Read-only investor statement behind a valid signed link (HMAC) — no session required. - * Publishes only this investor's own capital-account statement. + * Element 5D + * @description 5D for a model element: click a GUID in the 3D view → its schedule activity (with %-complete, + * dates, whether it's hard-tied or matched by trade) and its cost code's budget vs committed vs + * actual. Ties the BIM model to the GC schedule + budget — the same relational data, by element. */ - get: operations["investor_statement_public_projects__pid__investors__iid__statement_public_pdf_get"]; + get: operations["element_5d_projects__pid__elements__guid__5d_get"]; put?: never; post?: never; delete?: never; @@ -8587,7 +9470,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/rent-roll": { + "/projects/{pid}/elements/{guid}/costs": { parameters: { query?: never; header?: never; @@ -8595,11 +9478,10 @@ export interface paths { cookie?: never; }; /** - * Get Rent Roll - * @description Operating rent roll — occupancy, WALT, lease-expiration schedule + in-place income from the - * `lease` module (the hold phase). Feeds the appraisal income approach (`/appraisal?rentroll=1`). + * Element Costs + * @description Every cost line (budget / commitment / direct cost / sub invoice) tagged to this IFC element. */ - get: operations["get_rent_roll_projects__pid__rent_roll_get"]; + get: operations["element_costs_projects__pid__elements__guid__costs_get"]; put?: never; post?: never; delete?: never; @@ -8608,7 +9490,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/leases/management": { + "/projects/{pid}/elements/{guid}/lifecycle": { parameters: { query?: never; header?: never; @@ -8616,11 +9498,19 @@ export interface paths { cookie?: never; }; /** - * Lease Management - * @description Lease-management depth — renewal/expiration pipeline, forward rent-escalation schedule, and - * CAM/expense-recovery reconciliation (pass ?recoverable_opex= for the recovery ratio + gap). + * Element Lifecycle + * @description R26-INSPECTOR — the six-state strip for one element: designed · checked · priced · scheduled · + * installed · verified. + * + * Every state is a fact the platform already holds against this same GlobalId; the strip is what + * makes "one model, one key" visible instead of spread across six panels. + * + * A state the platform was not able to consult reads **`unknown`**, never `none` — an unpriced + * element and an unexamined one are different facts. `reached` is the furthest CONTIGUOUS state, so + * a later one cannot flatter an incomplete earlier one, and any out-of-order combination is reported + * in `inconsistencies` rather than smoothed away. */ - get: operations["lease_management_projects__pid__leases_management_get"]; + get: operations["element_lifecycle_projects__pid__elements__guid__lifecycle_get"]; put?: never; post?: never; delete?: never; @@ -8629,7 +9519,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/listings/autofill": { + "/projects/{pid}/elements/{guid}/records": { parameters: { query?: never; header?: never; @@ -8637,10 +9527,12 @@ export interface paths { cookie?: never; }; /** - * Listing Autofill - * @description Pre-populated listing fields from the project's proforma + model (the off-plan advantage). + * Element Records + * @description Reverse deep-link — every record across all pinnable modules (RFIs, coordination issues, change + * orders, field verifications, schedule activities, …) tied to this IFC element by GlobalId. Closes the + * round-trip with the portal's record→element "show in model" direction. */ - get: operations["listing_autofill_projects__pid__listings_autofill_get"]; + get: operations["element_records_projects__pid__elements__guid__records_get"]; put?: never; post?: never; delete?: never; @@ -8649,29 +9541,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/comparables/import": { + "/projects/{pid}/elements/{guid}/sources": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Import Comparables - * @description Bulk-import comparables from CSV (`{csv}`) or a RESO array (`{reso|rows}`) into the `comparable` - * module — feeds the sales-comparison appraisal. Forgiving header mapping; rows without an address - * are skipped. Returns the created count + the parsed rows. + * Element Sources + * @description The cited provenance of one element — its governing spec sections, attached documents (with sheet + * refs), and spatial container. Every fact tagged with its source; the substrate for RFI-0 NL-QA. */ - post: operations["import_comparables_projects__pid__comparables_import_post"]; + get: operations["element_sources_projects__pid__elements__guid__sources_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/appraisal": { + "/projects/{pid}/energy": { parameters: { query?: never; header?: never; @@ -8679,25 +9570,20 @@ export interface paths { cookie?: never; }; /** - * Get Appraisal - * @description Tri-approach valuation. Saved overrides (project.dev_property.appraisal) merge with any query - * overrides (query wins): depreciation_pct, land_value, replacement_cost_new, stabilized_noi, - * cap_rate, subject_sqft, weight_income, weight_cost, weight_sales. + * Energy + * @description Envelope energy analysis (UA + degree-day) computed from the model geometry. + * Construction U-values and climate degree-days are overridable via query params. */ - get: operations["get_appraisal_projects__pid__appraisal_get"]; + get: operations["energy_projects__pid__energy_get"]; put?: never; - /** - * Save Appraisal - * @description Persist appraisal overrides (depreciation, land value, weights, …) on the project. - */ - post: operations["save_appraisal_projects__pid__appraisal_post"]; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/listings/{lid}/reso": { + "/projects/{pid}/energy/actual": { parameters: { query?: never; header?: never; @@ -8705,10 +9591,12 @@ export interface paths { cookie?: never; }; /** - * Listing Reso - * @description The RESO Data Dictionary payload for a listing — the shape a bridge POSTs to WPRealWise / MLS. + * Energy Summary + * @description Operational (metered) energy rollup: site kBtu + cost by utility, monthly trend, water, and EUI + * (kBtu/sf/yr) using the model's GFA when loaded — or pass ?gfa_sf= explicitly. Distinct from + * GET /projects/{pid}/energy, which is the design-model simulation. */ - get: operations["listing_reso_projects__pid__listings__lid__reso_get"]; + get: operations["energy_summary_projects__pid__energy_actual_get"]; put?: never; post?: never; delete?: never; @@ -8717,7 +9605,7 @@ export interface paths { patch?: never; trace?: never; }; - "/re-syndication/status": { + "/projects/{pid}/energy/export.gbxml": { parameters: { query?: never; header?: never; @@ -8725,10 +9613,11 @@ export interface paths { cookie?: never; }; /** - * Re Syndication Status - * @description Whether the WPRealWise / MLS syndication bridge is configured (off unless REALWISE_URL+key set). + * Energy Gbxml + * @description ENERGY phase 1 — gbXML envelope export (Campus / Building / Space / Surface + Construction / + * Layer / Material). Geometry and constructions only: no HVAC, schedules or loads. */ - get: operations["re_syndication_status_re_syndication_status_get"]; + get: operations["energy_gbxml_projects__pid__energy_export_gbxml_get"]; put?: never; post?: never; delete?: never; @@ -8737,49 +9626,51 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/listings/{lid}/syndicate": { + "/projects/{pid}/energy/export.idf": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Listing Syndicate - * @description Push a listing (RESO-serialized) into WPRealWise / an MLS. Requires the bridge to be configured; - * 422 with an actionable message otherwise. The RESO export endpoint works regardless. + * Energy Idf + * @description ENERGY phase 1 — EnergyPlus IDF envelope export (Building / Zone / Material / Construction / + * BuildingSurface:Detailed). Add HVAC, schedules and loads in the simulation setup. */ - post: operations["listing_syndicate_projects__pid__listings__lid__syndicate_post"]; + get: operations["energy_idf_projects__pid__energy_export_idf_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/listings/{lid}/share": { + "/projects/{pid}/energy/model": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Listing Share - * @description Mint a signed, expiring URL to the public listing JSON (for a QR / shared link). The signature - * authorizes exactly that path until it expires — no session needed by the recipient. + * Energy Model + * @description ENERGY phase 1 — the thermal model extracted from the IFC: zones, zero-thickness surfaces + * (each tagged `exact` or `bbox` so a consumer knows how far to trust its polygon), and + * constructions computed from the model's own layered assemblies. The intermediate both the + * gbXML and IDF writers serialize, so the two exports can never disagree. */ - post: operations["listing_share_projects__pid__listings__lid__share_post"]; + get: operations["energy_model_projects__pid__energy_model_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/listings/{lid}/public": { + "/projects/{pid}/entitlements/condition-checks": { parameters: { query?: never; header?: never; @@ -8787,12 +9678,28 @@ export interface paths { cookie?: never; }; /** - * Listing Public - * @description Read-only public listing — the only intentionally-anonymous surface. Requires a valid signed - * URL (HMAC) regardless of RBAC; publishes only listing-safe fields (no internal financials beyond - * what the owner put in the public description / asking price). + * Entitlement Condition Checks + * @description R22-ENTITLEMENT ② — the project's approval conditions checked against its model. + * + * Slice ① made conditions individually tracked; this is the ring entry's actual clause, conditions + * carried into the model as constraints. It checks the two topics the model can genuinely answer — + * **height** (from the storey elevations) and **parking** (a count of parking spaces) — and refuses + * the rest. A setback check without a surveyed property line would be a number dressed as a + * compliance finding. + * + * **Units are converted explicitly and both are reported.** A condition reads "45 feet"; the model + * is metric. Comparing 45 against metres is how a 45 ft height limit silently becomes a 45 m one — + * a three-fold error that reads as a pass. Every comparison returns the condition value as written, + * the converted value, the model value, and the unit of each. + * + * **A height limit is a maximum; a parking condition is a minimum.** Reading one as the other + * inverts the finding, so the direction is stated per check. + * + * Anything unevaluable — an unsupported topic, a missing quantity, or a model fact the file does + * not answer — is `not_checkable`, **never a pass**. A condition reported satisfied because nobody + * could evaluate it is a building put up out of compliance while the report said it was fine. */ - get: operations["listing_public_projects__pid__listings__lid__public_get"]; + get: operations["entitlement_condition_checks_projects__pid__entitlements_condition_checks_get"]; put?: never; post?: never; delete?: never; @@ -8801,15 +9708,33 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/verification": { + "/projects/{pid}/entitlements/conditions": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** List Verifications */ - get: operations["list_verifications_projects__pid__verification_get"]; + /** + * Entitlement Conditions + * @description R22-ENTITLEMENT — an approval's conditions as tracked items rather than one paragraph. + * + * `entitlement.conditions` is a single textarea, so an approval carrying fourteen conditions is one + * blob and nobody can say which have been discharged. This splits it, tags each condition by topic, + * and extracts quantities **beside the sentence they came from** — parsing an agency's prose is + * extraction, not authority, and a reading a human cannot check is a constraint nobody chose. + * + * **Nothing here is ever marked satisfied.** A condition whose topic and quantity cannot be read is + * `unparsed`, and an approval condition silently treated as met is a building put up out of + * compliance while the report said it was fine. Discharge is a human act, recorded — and an + * `unparsed` condition cannot be discharged at all, because signing off text nobody read is the + * same failure wearing a signature. + * + * An **approved** entitlement with an empty conditions field reports `unrecorded`, not + * unconditioned: approvals almost always carry conditions, so a blank field far more often means + * nobody typed them than that the agency imposed none. + */ + get: operations["entitlement_conditions_projects__pid__entitlements_conditions_get"]; put?: never; post?: never; delete?: never; @@ -8818,7 +9743,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/verification/coverage": { + "/projects/{pid}/entitlements/review-cycles": { parameters: { query?: never; header?: never; @@ -8826,11 +9751,31 @@ export interface paths { cookie?: never; }; /** - * Coverage - * @description Install-coverage summary: of the model's elements, how many are installed/verified, plus the - * deviation count. `total` comes from the uploaded property index (0 if none yet). + * Entitlement Review Cycles + * @description R22-ENTITLEMENT -- the review rounds, and WHOSE COURT the time sat in. + * + * Both approval registers already existed and neither modelled **rounds**: `permit` has a single + * `under_review` state, so a third review round is indistinguishable from a first and the only + * recoverable duration is applied -> issued. One number, for a process that is a back-and-forth. + * + * That number cannot settle the argument the process generates. When a permit takes seven months + * the question is never "how long" but *whose court did it sit in* -- an agency holding three + * rounds for forty days and an applicant taking fifty-five to answer produce the same elapsed time, + * a different conversation, and a different remedy. `days_with_agency` / `days_with_applicant` / + * `agency_share_pct` are that split. + * + * **An open round is counted and NAMED, never scored.** A round the agency still holds has no + * comments-received date, and treating that absence as zero would report a submission held ninety + * days as instantaneous -- the most flattering possible lie about the party you are arguing with. + * + * **Days are CALENDAR days, and the response says so.** A statutory review period does not pause + * for a weekend. Construction durations elsewhere here are working days; the axis is stated because + * mixing them silently is how a delay analysis produces a confident wrong number. + * + * Distinct from `permit_timeline`, which forecasts days-to-issue from OTHER projects' public permit + * feeds before you apply. This measures this project's actual rounds, after. */ - get: operations["coverage_projects__pid__verification_coverage_get"]; + get: operations["entitlement_review_cycles_projects__pid__entitlements_review_cycles_get"]; put?: never; post?: never; delete?: never; @@ -8839,7 +9784,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/verification/deviations": { + "/projects/{pid}/enum-options": { parameters: { query?: never; header?: never; @@ -8847,10 +9792,10 @@ export interface paths { cookie?: never; }; /** - * Deviations - * @description The deviation log — elements flagged as not matching design (for the punch / ops handover). + * List Enum Options + * @description E1 — project-level custom select options, nested {module: {field: [values]}}. */ - get: operations["deviations_projects__pid__verification_deviations_get"]; + get: operations["list_enum_options_projects__pid__enum_options_get"]; put?: never; post?: never; delete?: never; @@ -8859,7 +9804,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/verification/{guid}": { + "/projects/{pid}/env/wind": { parameters: { query?: never; header?: never; @@ -8867,40 +9812,43 @@ export interface paths { cookie?: never; }; get?: never; + put?: never; /** - * Set Status - * @description Set an element's field-verification status (installed / verified / deviation / pending). - * Upserts by (project, guid); stamps ifc_class/storey from the property index when available. + * Env Wind Screen + * @description ENV-1 — pedestrian **wind-comfort screen** at massing stage (approximate, offline): corner + * acceleration, downwash, and channelling graded on the Lawson comfort categories, with the + * standard mitigations. Pass `{height_m, width_m, depth_m, wind_ms?, gap_m?, podium_height_m?}`; + * omit the dims to derive height/width/depth from the source model's bounding box. NOT CFD. */ - put: operations["set_status_projects__pid__verification__guid__put"]; - post?: never; + post: operations["env_wind_screen_projects__pid__env_wind_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/verification/{guid}/photo": { + "/projects/{pid}/envelope/audit": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Upload Photo - * @description Attach a field photo to an element's verification (deviation evidence / install proof). + * Envelope Audit + * @description Envelope code-compliance — every envelope assembly checked against IECC 2021 climate-zone + * minimums (opaque R-value / fenestration U-factor), with a compliance rollup. */ - post: operations["upload_photo_projects__pid__verification__guid__photo_post"]; + get: operations["envelope_audit_projects__pid__envelope_audit_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/payroll": { + "/projects/{pid}/envelope/check": { parameters: { query?: never; header?: never; @@ -8908,10 +9856,10 @@ export interface paths { cookie?: never; }; /** - * Weekly Payroll - * @description Weekly certified-payroll summary (per worker hours, OT, rate, gross) for the week ending. + * Envelope Check + * @description Check a single assembly against IECC 2021 (element_type + climate_zone + r_value or u_factor). */ - get: operations["weekly_payroll_projects__pid__payroll_get"]; + get: operations["envelope_check_projects__pid__envelope_check_get"]; put?: never; post?: never; delete?: never; @@ -8920,7 +9868,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/payroll/wh347.pdf": { + "/projects/{pid}/escalations": { parameters: { query?: never; header?: never; @@ -8928,10 +9876,11 @@ export interface paths { cookie?: never; }; /** - * Wh347 Pdf - * @description The WH-347 certified-payroll PDF for the week. + * Escalations Scan + * @description WORKFLOW-ENGINE — read-only escalation preview: every overdue record with its computed + * escalation level, the level already applied, and whether it still needs escalating. */ - get: operations["wh347_pdf_projects__pid__payroll_wh347_pdf_get"]; + get: operations["escalations_scan_projects__pid__escalations_get"]; put?: never; post?: never; delete?: never; @@ -8940,7 +9889,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/assistant": { + "/projects/{pid}/escalations/run": { parameters: { query?: never; header?: never; @@ -8950,18 +9899,18 @@ export interface paths { get?: never; put?: never; /** - * Project Assistant - * @description Ask about the project in plain English ('how many open RFIs?', 'what's the SPI?', 'occupancy?'). - * Grounded in a live project snapshot; returns the snapshot when no AI key is configured. + * Escalations Run + * @description Apply the overdue-escalation pass now: each record past its due rung gets an `escalation:L{n}` + * activity that surfaces to its ball-in-court party + assignee. Idempotent (level-guarded). */ - post: operations["project_assistant_projects__pid__assistant_post"]; + post: operations["escalations_run_projects__pid__escalations_run_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/assistant/snapshot": { + "/projects/{pid}/esg": { parameters: { query?: never; header?: never; @@ -8969,10 +9918,11 @@ export interface paths { cookie?: never; }; /** - * Assistant Snapshot - * @description The grounded project snapshot the assistant uses (module tallies, schedule, budget, risk). + * Esg Summary + * @description Asset ESG rollup: metered energy (EUI), GHG Scope 1/2 from the local factor table, water, + * certification tracking, and the POE actual-vs-design EUI comparison. */ - get: operations["assistant_snapshot_projects__pid__assistant_snapshot_get"]; + get: operations["esg_summary_projects__pid__esg_get"]; put?: never; post?: never; delete?: never; @@ -8981,7 +9931,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/modules/{key}/{rid}/distribution": { + "/projects/{pid}/estimate/bands": { parameters: { query?: never; header?: never; @@ -8989,10 +9939,13 @@ export interface paths { cookie?: never; }; /** - * Record Distribution - * @description Resolve a record's distribution (CC) field against the contact directory → recipients + emails. + * Estimate Bands + * @description EST-BANDS — a **range estimate**: three-point (low / likely / high) cost bands per priced line + * from design-stage cost uncertainty by discipline, rolled to a bid range two ways — a fully-correlated + * envelope and an independent probabilistic P10/P50/P90 range (CLT over the per-line triangular + * distributions). Prices the current model's takeoff. 409 without a source IFC. */ - get: operations["record_distribution_projects__pid__modules__key___rid__distribution_get"]; + get: operations["estimate_bands_projects__pid__estimate_bands_get"]; put?: never; post?: never; delete?: never; @@ -9001,27 +9954,30 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/stakeholders/analysis": { + "/projects/{pid}/estimate/boe": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Stakeholder Analysis - * @description Power/interest (Mendelow) grid + stance read of the project's stakeholder register. + * Estimate Boe + * @description BOE-LEDGER — the Basis-of-Estimate assumption ledger: the normalized BoE + documentation completeness; + * with `prev`, the **assumption drift** between estimate versions (qty re-based · unit cost moved · + * escalation/contingency shifted · source upgraded); with `actuals`, the assumption→actual variance + * **decomposed exactly** into qty vs price effects. Body: `{lines, prev?, actuals?}`. */ - get: operations["stakeholder_analysis_projects__pid__stakeholders_analysis_get"]; - put?: never; - post?: never; + post: operations["estimate_boe_projects__pid__estimate_boe_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/tm-summary": { + "/projects/{pid}/estimate/cbs": { parameters: { query?: never; header?: never; @@ -9029,10 +9985,13 @@ export interface paths { cookie?: never; }; /** - * Tm Summary - * @description Time & Material (eTicket) cost rollup — labor/material/equipment, billed vs unbilled. + * Estimate Cbs + * @description CBS-1 — a **Cost Breakdown Structure** over the model's construction estimate: the takeoff-priced + * direct cost layered through indirect / general conditions → contingency (known risks) → management + * reserve (unknown-unknowns) → overhead & profit → taxes, each with its amount, rate and share of the + * total. Rates overridable via query. 409 without a source IFC. */ - get: operations["tm_summary_projects__pid__tm_summary_get"]; + get: operations["estimate_cbs_projects__pid__estimate_cbs_get"]; put?: never; post?: never; delete?: never; @@ -9041,110 +10000,124 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/tm-by-change-event": { + "/projects/{pid}/estimate/concept-budget": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Tm By Change Event - * @description T&M (eTicket) cost rolled up by the change event each ticket is linked to. + * Estimate Concept Budget + * @description CONCEPT-BUDGET — a conceptual budget from massing inputs priced against the firm's OWN completed + * -project history: `history` → per-type $/area rates (escalated to `to_year`), `program` (use · gfa · + * stories) priced at the own-history median with a p25–p75 range, `default_rate` where a use has no + * history, UNPRICED surfaced rather than guessed. Body: `{program, history?, rates?, default_rate?, + * to_year?, escalation_pct?, contingency_pct?}`. */ - get: operations["tm_by_change_event_projects__pid__tm_by_change_event_get"]; - put?: never; - post?: never; + post: operations["estimate_concept_budget_projects__pid__estimate_concept_budget_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/change-orders/log": { + "/projects/{pid}/estimate/conceptual": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Co Log - * @description Change-order log — CO value pipeline (pending/approved/executed), reason mix, schedule exposure. + * Conceptual + * @description Conceptual (Class 5) cost from building type + GFA + units — low/base/high, escalated for + * region/year, with $/SF, $/unit, $/key metrics for the proforma. */ - get: operations["co_log_projects__pid__change_orders_log_get"]; - put?: never; - post?: never; + post: operations["conceptual_projects__pid__estimate_conceptual_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/action-items/tracker": { + "/projects/{pid}/estimate/confidence": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Action Tracker - * @description Meeting & action-item tracker — open/overdue by assignee & priority, completion, meeting log. + * Estimate Confidence + * @description EST-CONFIDENCE — score each estimate line's **maturity + confidence** from its source (measured/quote > + * parametric/assembly > allowance/manual) modulated by design phase (CD > DD > SD) → a cost-weighted project + * confidence, a **'% of budget still assumption-based'** KPI, and the worst-value least-grounded lines to + * firm up. Body: `{lines:[{description, qty, unit_cost|cost, source, phase, contingency_pct, cost_code}]}`. */ - get: operations["action_tracker_projects__pid__action_items_tracker_get"]; - put?: never; - post?: never; + post: operations["estimate_confidence_projects__pid__estimate_confidence_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/specs/submittal-log": { + "/projects/{pid}/estimate/diff": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Specs Submittal Log - * @description Spec-driven submittal log — required submittals derived per spec section vs the submittals - * actually logged, with missing-submittal gaps (the CSI Part 1 'Submittals' → log reconciliation). + * Estimate Diff Route + * @description R25-ESTIMATE-DIFF — diff two `/estimate` payloads by GlobalId. + * + * Body: `{before: , after: }`. Every dollar of the change is attributed to one + * of four causes — `added` / `removed` (scope), `requantified` (the design changed) and `repriced` + * (the estimate changed, not the building) — plus `both` when quantity and rate moved together, + * which is reported as one change rather than split into two half-truths. + * + * `reconciles` says whether the attributed deltas add back to the difference in totals. A diff whose + * parts do not sum to the whole is worse than no diff: it looks authoritative while losing money. */ - get: operations["specs_submittal_log_projects__pid__specs_submittal_log_get"]; - put?: never; - post?: never; + post: operations["estimate_diff_route_projects__pid__estimate_diff_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/specs/extract-submittals": { + "/projects/{pid}/estimate/from-model": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - get?: never; - put?: never; /** - * Specs Extract Submittals - * @description Extract a typed submittal list from pasted spec text (AI when configured, rules fallback offline). - * Body: {text, create?: bool}. With create=true, logs each item as a `submittal` record (and a - * `spec_section` if a section number is present), building the submittal log from the spec book. + * Estimate From Model + * @description Conceptual estimate from the IFC quantity takeoff × unit rates — priced line items by element + * class + a grand total (feeds the budget / proforma hard cost). 409 if no source IFC. */ - post: operations["specs_extract_submittals_projects__pid__specs_extract_submittals_post"]; + get: operations["estimate_from_model_projects__pid__estimate_from_model_get"]; + put?: never; + post?: never; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/feasibility": { + "/projects/{pid}/estimate/gaeb.x83": { parameters: { query?: never; header?: never; @@ -9152,12 +10125,11 @@ export interface paths { cookie?: never; }; /** - * Site Feasibility - * @description Site feasibility / zoning envelope — max buildable GFA (binding min of FAR vs. the physical - * envelope), unit yield, parking demand + required open space, reconciled against the model's - * actual GFA. Pass ?gfa= to override the actual GFA, else it's read from the source IFC if present. + * Estimate Gaeb + * @description Export the model estimate as a GAEB DA XML 3.2 Bill of Quantities (X83), coded to a regional + * classification (din276 / nrm1 / masterformat). 409 if the project has no source IFC. */ - get: operations["site_feasibility_projects__pid__feasibility_get"]; + get: operations["estimate_gaeb_projects__pid__estimate_gaeb_x83_get"]; put?: never; post?: never; delete?: never; @@ -9166,7 +10138,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/feasibility/compare": { + "/projects/{pid}/estimate/labor": { parameters: { query?: never; header?: never; @@ -9174,11 +10146,16 @@ export interface paths { cookie?: never; }; /** - * Feasibility Compare - * @description Scenario comparison — rank every zoning scheme by buildable yield (units, then GFA), with - * deltas vs. the top scheme. One zoning record = one scheme (e.g. 'Scheme A FAR 6' vs 'B FAR 8'). + * Labor Estimate + * @description EST-1: a **cost + duration** estimate derived from the model's quantities via the + * productivity-rate library — man-hours → crew-days → cost per activity, condition-loaded, plus a + * **schedule duration** (crew-days roll up by trade → working/calendar days; `crews` = crews per trade + * running in parallel, which shortens each trade). Quantities come from the **real measured QTO + * takeoff** (Qto psets + geometry fallback, cached) by default; `qto=false` falls back to the rough + * element-dimension parse. With `full=true` it adds **material + equipment** cost lines. A starting + * point the estimator refines; excludes overhead/profit. Needs a source IFC. */ - get: operations["feasibility_compare_projects__pid__feasibility_compare_get"]; + get: operations["labor_estimate_projects__pid__estimate_labor_get"]; put?: never; post?: never; delete?: never; @@ -9187,7 +10164,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/estimate-continuity": { + "/projects/{pid}/estimate/resource-based": { parameters: { query?: never; header?: never; @@ -9195,11 +10172,12 @@ export interface paths { cookie?: never; }; /** - * Precon Estimate Continuity - * @description Preconstruction estimate continuity — per-milestone totals + $/SF, milestone-to-milestone cost - * drift, and the gap vs the project budget/GMP (pass ?budget= to override the GMP baseline). + * Estimate Resource Based + * @description Resource-based (assembly) estimate from the IFC takeoff: each element class priced by building + * the cost UP from labor + material + equipment, returning the L/M/E split and total crew-hours + * (which feed resource loading + the schedule), not just a blended $/unit. 409 if no source IFC. */ - get: operations["precon_estimate_continuity_projects__pid__precon_estimate_continuity_get"]; + get: operations["estimate_resource_based_projects__pid__estimate_resource_based_get"]; put?: never; post?: never; delete?: never; @@ -9208,7 +10186,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/decisions": { + "/projects/{pid}/evm": { parameters: { query?: never; header?: never; @@ -9216,10 +10194,13 @@ export interface paths { cookie?: never; }; /** - * Precon Decisions - * @description Preconstruction decision log — by status/alignment + open cost & schedule exposure. + * Evm Snapshot + * @description Full Earned Value Management snapshot (ANSI/EIA-748-aligned): joins schedule earned value with + * cost actuals **by cost code (control account)**. Returns PV/EV/AC/BAC, CV/SV/CPI/SPI with health + * bands, the EAC/ETC/VAC/TCPI **forecast family**, a per-control-account table, and per-activity EV. + * `data_date` (YYYY-MM-DD) sets the reporting cut-off; defaults to today. */ - get: operations["precon_decisions_projects__pid__precon_decisions_get"]; + get: operations["evm_snapshot_projects__pid__evm_get"]; put?: never; post?: never; delete?: never; @@ -9228,7 +10209,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/assumptions": { + "/projects/{pid}/evm/earned-schedule": { parameters: { query?: never; header?: never; @@ -9236,10 +10217,11 @@ export interface paths { cookie?: never; }; /** - * Precon Assumptions - * @description Assumptions & clarifications register — by status/category + open allowance exposure. + * Evm Earned Schedule + * @description **Earned Schedule** (time-based EVM): ES, SV(t), SPI(t), IEAC(t) → forecast finish, in `week` or + * `month` periods, plus the PV baseline curve. Stays meaningful at completion, unlike dollar SPI. */ - get: operations["precon_assumptions_projects__pid__precon_assumptions_get"]; + get: operations["evm_earned_schedule_projects__pid__evm_earned_schedule_get"]; put?: never; post?: never; delete?: never; @@ -9248,7 +10230,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/ve": { + "/projects/{pid}/evm/model-ev": { parameters: { query?: never; header?: never; @@ -9256,10 +10238,12 @@ export interface paths { cookie?: never; }; /** - * Precon Ve - * @description Value-engineering cycle — proposed/accepted/rejected savings; pass ?target= for gap-to-close. + * Evm Model Ev + * @description **Model-based EV**: earned value from physically-installed model elements (field-verified GUIDs) + * × BAC — the units-complete method sourced from the model. Cross-checks the schedule EV to catch + * over-reported / front-loaded progress. */ - get: operations["precon_ve_projects__pid__precon_ve_get"]; + get: operations["evm_model_ev_projects__pid__evm_model_ev_get"]; put?: never; post?: never; delete?: never; @@ -9268,7 +10252,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/alignment": { + "/projects/{pid}/evm/scurve": { parameters: { query?: never; header?: never; @@ -9276,11 +10260,11 @@ export interface paths { cookie?: never; }; /** - * Precon Alignment - * @description Calibrate-style preconstruction alignment — estimate-vs-budget, VE coverage of any gap, - * open decisions/assumptions — as per-domain RAG + an alignment score. + * Evm Scurve + * @description The EVM **S-curve**: cumulative PV (full baseline) + EV + AC to the data date, over week/month + * buckets, for the three-line performance chart. */ - get: operations["precon_alignment_projects__pid__precon_alignment_get"]; + get: operations["evm_scurve_projects__pid__evm_scurve_get"]; put?: never; post?: never; delete?: never; @@ -9289,7 +10273,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/precon/snapshot": { + "/projects/{pid}/evm/snapshot": { parameters: { query?: never; header?: never; @@ -9299,18 +10283,18 @@ export interface paths { get?: never; put?: never; /** - * Precon Snapshot - * @description One-click: price the current model (IFC takeoff × unit rates) and save it as an estimate set - * tagged with the given design milestone. 409 if the project has no source IFC yet. + * Evm Capture + * @description Capture the current EVM state as a dated `evm_snapshot` baseline, so CPI/SPI can be trended over + * reporting periods. Capture one per period (weekly/monthly). */ - post: operations["precon_snapshot_projects__pid__precon_snapshot_post"]; + post: operations["evm_capture_projects__pid__evm_snapshot_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/health": { + "/projects/{pid}/evm/trend": { parameters: { query?: never; header?: never; @@ -9318,10 +10302,11 @@ export interface paths { cookie?: never; }; /** - * Project Health - * @description Executive project-health rollup — per-domain status, overall score, ranked attention items. + * Evm Trend + * @description CPI/SPI **performance-index trend** across captured `evm_snapshot` records (oldest-first) — shows + * whether cost/schedule efficiency is improving or deteriorating over the reporting periods. */ - get: operations["project_health_projects__pid__health_get"]; + get: operations["evm_trend_projects__pid__evm_trend_get"]; put?: never; post?: never; delete?: never; @@ -9330,7 +10315,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/closeout/summary": { + "/projects/{pid}/export/subset.ifc": { parameters: { query?: never; header?: never; @@ -9338,10 +10323,14 @@ export interface paths { cookie?: never; }; /** - * Closeout Summary - * @description Closeout analytics — punchlist completion/ball-in-court, commissioning, certificates, warranties, O&M. + * Export Subset Ifc + * @description SUBSET-EXPORT: download a standalone IFC of just the elements matching a QUERY-DSL selector + * (e.g. `IfcDuctSegment | IfcPipeSegment` or `discipline=Structural`) — the discipline / scope slice + * you hand a consultant. The spatial skeleton (project/site/building/storey/space) and shared + * units/contexts are preserved so the slice is a valid, correctly-contained IFC with GUIDs unchanged. + * 409 without a source IFC; 422 on a bad selector or an empty match. */ - get: operations["closeout_summary_projects__pid__closeout_summary_get"]; + get: operations["export_subset_ifc_projects__pid__export_subset_ifc_get"]; put?: never; post?: never; delete?: never; @@ -9350,19 +10339,15 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/safety/summary": { + "/projects/{pid}/exports/cobie.xlsx": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Safety Summary - * @description Safety analytics — OSHA TRIR/DART/LTIFR, observation mix, toolbox coverage, violations. - * Pass ?hours= for exact rates; otherwise estimated from daily-report manpower. - */ - get: operations["safety_summary_projects__pid__safety_summary_get"]; + /** Export Cobie */ + get: operations["export_cobie_projects__pid__exports_cobie_xlsx_get"]; put?: never; post?: never; delete?: never; @@ -9371,7 +10356,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/daily-reports/summary": { + "/projects/{pid}/exports/model.gbxml": { parameters: { query?: never; header?: never; @@ -9379,10 +10364,11 @@ export interface paths { cookie?: never; }; /** - * Field Log Summary - * @description Field-log rollup — manpower trend, weather-impact lost-days, reporting coverage. + * Export Gbxml + * @description gbXML (Green Building XML) — spaces + areas/volumes from the IFC geometry, for OpenStudio / + * EnergyPlus / IES energy modelling. Simplified (building-level envelope, not per-space surfaces). */ - get: operations["field_log_summary_projects__pid__daily_reports_summary_get"]; + get: operations["export_gbxml_projects__pid__exports_model_gbxml_get"]; put?: never; post?: never; delete?: never; @@ -9391,18 +10377,15 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/rfi/register": { + "/projects/{pid}/exports/qto.xlsx": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Rfi Register - * @description RFI register — ball-in-court, overdue, response turnaround, cost/schedule-impact exposure. - */ - get: operations["rfi_register_projects__pid__rfi_register_get"]; + /** Export Qto */ + get: operations["export_qto_projects__pid__exports_qto_xlsx_get"]; put?: never; post?: never; delete?: never; @@ -9411,18 +10394,15 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/quality/summary": { + "/projects/{pid}/exports/schedule.xlsx": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Quality Summary - * @description Quality dashboard — inspection pass-rate KPIs, NCR disposition/close loop, deficiency ball-in-court. - */ - get: operations["quality_summary_projects__pid__quality_summary_get"]; + /** Export Schedule */ + get: operations["export_schedule_projects__pid__exports_schedule_xlsx_get"]; put?: never; post?: never; delete?: never; @@ -9431,18 +10411,15 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/submittals/register": { + "/projects/{pid}/exports/spaces.xlsx": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; - /** - * Submittal Register - * @description Spec-section submittal register — turnaround, ball-in-court, overdue flags. - */ - get: operations["submittal_register_projects__pid__submittals_register_get"]; + /** Export Spaces */ + get: operations["export_spaces_projects__pid__exports_spaces_xlsx_get"]; put?: never; post?: never; delete?: never; @@ -9451,7 +10428,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cmms/generate-pm": { + "/projects/{pid}/extract/sheets": { parameters: { query?: never; header?: never; @@ -9461,81 +10438,88 @@ export interface paths { get?: never; put?: never; /** - * Generate Pm - * @description Create preventive work orders for every active PM schedule that's due (idempotent per cycle: - * a schedule with an open PM work order is skipped); advances each schedule's next-due date. + * Extract Sheets Ep + * @description Extract a drawing-sheet index (number / title / discipline) from an uploaded PDF or pasted + * sheet list — deterministic over the PDF text layer, honest when a scan has no text. With + * create=true the extracted sheets become `drawing` records. */ - post: operations["generate_pm_projects__pid__cmms_generate_pm_post"]; + post: operations["extract_sheets_ep_projects__pid__extract_sheets_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/cmms/kpis": { + "/projects/{pid}/families/import": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Cmms Kpis - * @description Maintenance KPIs: open by priority/type, overdue, PM compliance %, MTTR (days). + * Import Families + * @description Import external IFC **type content** (manufacturer / 3rd-party families) from an uploaded IFC + * into the project's source IFC, saving a new version. Imported types become placeable via the + * place-family picker (GET /projects/{id}/types). GUIDs of existing elements are preserved. */ - get: operations["cmms_kpis_projects__pid__cmms_kpis_get"]; - put?: never; - post?: never; + post: operations["import_families_projects__pid__families_import_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/energy/actual": { + "/projects/{pid}/families/import-pack": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Energy Summary - * @description Operational (metered) energy rollup: site kBtu + cost by utility, monthly trend, water, and EUI - * (kBtu/sf/yr) using the model's GFA when loaded — or pass ?gfa_sf= explicitly. Distinct from - * GET /projects/{pid}/energy, which is the design-model simulation. + * Import Family Pack + * @description Import a family pack **already on the server's external shelf** (services/data/families/external) + * into the project's source IFC — the same `import_types_from_ifc` path as the upload endpoint, minus + * the download-and-re-upload round trip that was previously the only way to use a shipped pack. + * + * `pack` is a plain file name on the shelf (see GET /families/library → `external.packs`); paths and + * parent references are refused. The audit record carries the pack's sha256, so an import can be tied + * back to exact content later. */ - get: operations["energy_summary_projects__pid__energy_actual_get"]; - put?: never; - post?: never; + post: operations["import_family_pack_projects__pid__families_import_pack_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/twin/readiness": { + "/projects/{pid}/families/place": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Twin Readiness - * @description Digital-twin + Digital Product Passport readiness: asset↔system linkage, sensor mapping, - * product-passport completeness, and the building-system graph. + * Place Family + * @description Place a library family into the project's source IFC (new GUID-stable occurrence, new version). + * Thin wrapper over the `add_family` authoring recipe. */ - get: operations["twin_readiness_projects__pid__twin_readiness_get"]; - put?: never; - post?: never; + post: operations["place_family_projects__pid__families_place_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/energy/benchmark-status": { + "/projects/{pid}/fca/index": { parameters: { query?: never; header?: never; @@ -9543,11 +10527,12 @@ export interface paths { cookie?: never; }; /** - * Benchmark Status - * @description Whether an external benchmarking sync (EPA Portfolio Manager) is configured; local EUI/trends - * work without it. + * Fca Index + * @description Facility Condition Index: FCI = (deferred maintenance + capital renewal) / current replacement + * value, with the band, the deferred/renewal split, and breakdowns by UNIFORMAT group, condition + * rating, worst elements, and recommended-year forecast — over the `fca_element` records. */ - get: operations["benchmark_status_energy_benchmark_status_get"]; + get: operations["fca_index_projects__pid__fca_index_get"]; put?: never; post?: never; delete?: never; @@ -9556,7 +10541,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/esg": { + "/projects/{pid}/feasibility": { parameters: { query?: never; header?: never; @@ -9564,11 +10549,12 @@ export interface paths { cookie?: never; }; /** - * Esg Summary - * @description Asset ESG rollup: metered energy (EUI), GHG Scope 1/2 from the local factor table, water, - * certification tracking, and the POE actual-vs-design EUI comparison. + * Site Feasibility + * @description Site feasibility / zoning envelope — max buildable GFA (binding min of FAR vs. the physical + * envelope), unit yield, parking demand + required open space, reconciled against the model's + * actual GFA. Pass ?gfa= to override the actual GFA, else it's read from the source IFC if present. */ - get: operations["esg_summary_projects__pid__esg_get"]; + get: operations["site_feasibility_projects__pid__feasibility_get"]; put?: never; post?: never; delete?: never; @@ -9577,7 +10563,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/reserves/study": { + "/projects/{pid}/feasibility/compare": { parameters: { query?: never; header?: never; @@ -9585,12 +10571,11 @@ export interface paths { cookie?: never; }; /** - * Reserve Study - * @description Reserve study: recurring replacement events (asset register install + expected life + - * replacement cost, plus open capital-plan items), year-by-year balance trajectory, first - * underfunded year, and the suggested level annual contribution. + * Feasibility Compare + * @description Scenario comparison — rank every zoning scheme by buildable yield (units, then GFA), with + * deltas vs. the top scheme. One zoning record = one scheme (e.g. 'Scheme A FAR 6' vs 'B FAR 8'). */ - get: operations["reserve_study_projects__pid__reserves_study_get"]; + get: operations["feasibility_compare_projects__pid__feasibility_compare_get"]; put?: never; post?: never; delete?: never; @@ -9599,29 +10584,51 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/fca/index": { + "/projects/{pid}/feasibility/lot-supply": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Fca Index - * @description Facility Condition Index: FCI = (deferred maintenance + capital renewal) / current replacement - * value, with the band, the deferred/renewal split, and breakdowns by UNIFORMAT group, condition - * rating, worst elements, and recommended-year forecast — over the `fca_element` records. + * Feasibility Lot Supply + * @description LOT-SUPPLY-INDEX — months of supply = VDL ÷ monthly absorption, as an index vs a balanced-market + * target (100 = equilibrium · > 125 oversupplied · < 75 undersupplied). Body: `{vdl, monthly_absorption, + * equilibrium_months?}`. 404 for a missing project. */ - get: operations["fca_index_projects__pid__fca_index_get"]; + post: operations["feasibility_lot_supply_projects__pid__feasibility_lot_supply_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/feasibility/sellout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; put?: never; - post?: never; + /** + * Feasibility Sellout + * @description ABSORPTION-SELLOUT — phase revenue by absorption rate → the monthly sell-out curve, months-to-sellout + * (the carry driver), total revenue + carry. Body: `{units, absorption_per_month, avg_price, + * monthly_carry?, start_month?}`. 404 for a missing project. + */ + post: operations["feasibility_sellout_projects__pid__feasibility_sellout_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/fca/portfolio": { + "/projects/{pid}/ffe-bom": { parameters: { query?: never; header?: never; @@ -9629,11 +10636,12 @@ export interface paths { cookie?: never; }; /** - * Fca Portfolio - * @description Facility Condition Index per project across the portfolio, worst-first — the capital- - * prioritization view (fund the highest-FCI buildings first). Scoped to the caller's projects. + * Ffe Bom + * @description W9-6b: the **FF&E / furnishings bill of materials** from the model's placed furniture — count each + * item (by name) with its IFC class and the levels it appears on. An owner/vendor order starting point. + * 409 without a source IFC. */ - get: operations["fca_portfolio_fca_portfolio_get"]; + get: operations["ffe_bom_projects__pid__ffe_bom_get"]; put?: never; post?: never; delete?: never; @@ -9642,7 +10650,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cam/reconciliation": { + "/projects/{pid}/finance/imports": { parameters: { query?: never; header?: never; @@ -9650,11 +10658,14 @@ export interface paths { cookie?: never; }; /** - * Cam Reconciliation - * @description CAM true-up for an operating year: recoverable pool (variable lines grossed up to the stated - * occupancy), per-tenant pro-rata share vs estimated payments, balance due/credit. + * Finance Imports + * @description FIN-INGEST lineage: the project's audit-logged import batches (file, module, counts, + * who, when), newest first — where the numbers came from. + * + * Returns an envelope rather than a bare list: `import_total` and `truncated` sit beside the + * rows, because `limit` is a window and a window that cannot say so reads as the whole history. */ - get: operations["cam_reconciliation_projects__pid__cam_reconciliation_get"]; + get: operations["finance_imports_projects__pid__finance_imports_get"]; put?: never; post?: never; delete?: never; @@ -9663,7 +10674,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cam/statement/{rid}.pdf": { + "/projects/{pid}/finance/lock": { parameters: { query?: never; header?: never; @@ -9671,11 +10682,17 @@ export interface paths { cookie?: never; }; /** - * Cam Statement - * @description Per-tenant CAM reconciliation statement (PDF) for the lease record `rid`. + * Get Finance Lock + * @description The project's locked reporting period (books closed through lock_date, or open). */ - get: operations["cam_statement_projects__pid__cam_statement__rid__pdf_get"]; - put?: never; + get: operations["get_finance_lock_projects__pid__finance_lock_get"]; + /** + * Put Finance Lock + * @description Close the books through a month (YYYY-MM / YYYY-MM-DD), or clear with null. Finance-module + * mutations dated into a closed month are refused with a 409 everywhere (routes, imports, + * internal flows) — adjustments post into open periods. + */ + put: operations["put_finance_lock_projects__pid__finance_lock_put"]; post?: never; delete?: never; options?: never; @@ -9683,7 +10700,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cde/status": { + "/projects/{pid}/finance/reconcile": { parameters: { query?: never; header?: never; @@ -9691,12 +10708,11 @@ export interface paths { cookie?: never; }; /** - * Cde Status - * @description CDE container rollup (ISO 19650): state distribution WIP/Shared/Published/Archived, - * suitability spread, and CDE-discipline metrics (revision control, approval-status coverage, - * metadata completeness). + * Finance Reconcile + * @description FIN-INGEST: budget ↔ actuals matched BOTH ways on the cost-code spine (matched / + * budget-only / actuals-only, never netted) + the uncoded actual records, highest value first. */ - get: operations["cde_status_projects__pid__cde_status_get"]; + get: operations["finance_reconcile_projects__pid__finance_reconcile_get"]; put?: never; post?: never; delete?: never; @@ -9705,7 +10721,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/info-requirements/register": { + "/projects/{pid}/financials": { parameters: { query?: never; header?: never; @@ -9713,11 +10729,11 @@ export interface paths { cookie?: never; }; /** - * Requirements Register - * @description The information-requirements register (OIR/AIR/PIR/EIR/BEP/MIDP/TIDP) with issued/draft - * counts and core-document coverage (EIR, BEP, AIR). + * Project Financials + * @description Financial statements for the project's latest saved scenario (income statement · balance sheet · + * cash flow · tax · after-tax returns · two-sided budget). */ - get: operations["requirements_register_projects__pid__info_requirements_register_get"]; + get: operations["project_financials_projects__pid__financials_get"]; put?: never; post?: never; delete?: never; @@ -9726,29 +10742,28 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/info-requirements/cascade": { + "/projects/{pid}/generate/massing": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Requirements Cascade - * @description The ISO 19650 requirement flow-down — OIR → PIR/AIR → EIR → MIDP/TIDP linked by each record's - * `derives_from` — as a tiered tree plus cascade health (orphans that don't trace up to - * organizational intent; links pointing the wrong way). + * Generate Massing + * @description Generate an IFC massing model from a zoning envelope, set it as the project's source IFC, + * publish it (off-thread), and return the buildable program + a starter acquisition proforma. */ - get: operations["requirements_cascade_projects__pid__info_requirements_cascade_get"]; - put?: never; - post?: never; + post: operations["generate_massing_projects__pid__generate_massing_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/info-requirements/delivery-plan": { + "/projects/{pid}/georeference": { parameters: { query?: never; header?: never; @@ -9756,12 +10771,19 @@ export interface paths { cookie?: never; }; /** - * Requirements Delivery Plan - * @description The MIDP/TIDP delivery plan — information requirements against their programme dates with - * overdue / due-soon status, a per-month roll-up, the next deliverable, and LOIN-specification - * coverage (EN 17412 Level of Information Need). + * Project Georeference + * @description Where this model sits on the earth, in the kernel's `GeoReference` shape. + * + * `method` matters as much as the coordinates: `map-conversion` is a survey transform out of IFC4's + * `IfcMapConversion`, `site-coordinates` is only `IfcSite` lat/long (all IFC2X3 can carry), and + * `null` means the model is not georeferenced at all. Those differ by orders of magnitude in what + * they can be used for — a site coordinate will put a pin on a map and must not be used to set out. + * + * A model with no georeferencing reports `georeferenced: false` rather than defaulting to (0, 0): + * null island is a real place, and a wrong answer shaped like a right one is the failure this + * codebase keeps finding. */ - get: operations["requirements_delivery_plan_projects__pid__info_requirements_delivery_plan_get"]; + get: operations["project_georeference_projects__pid__georeference_get"]; put?: never; post?: never; delete?: never; @@ -9770,7 +10792,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/cde/exchange-acceptance": { + "/projects/{pid}/golden-thread": { parameters: { query?: never; header?: never; @@ -9778,11 +10800,14 @@ export interface paths { cookie?: never; }; /** - * Cde Exchange Acceptance - * @description ISO 19650-6 information-exchange acceptance — each exchanged (non-WIP) container reviewed against - * completeness / suitability / authorization / traceability, with the non-conforming ones flagged. + * Golden Thread Summary + * @description GOLDEN-THREAD — the compliance **evidence ledger** rollup: how complete the requirement→evidence + * →sign-off thread is (signed-off %), the outcome/category spread, and the **broken-thread list** — + * requirements still missing evidence or a sign-off (a failed/pending item with no evidence ranks + * highest). Rolls up the `compliance_evidence` records; extends the point-in-time preflight/code + * checks into an auditable, sign-off-tracked record. */ - get: operations["cde_exchange_acceptance_projects__pid__cde_exchange_acceptance_get"]; + get: operations["golden_thread_summary_projects__pid__golden_thread_get"]; put?: never; post?: never; delete?: never; @@ -9791,28 +10816,31 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/openbim/quality": { + "/projects/{pid}/golden-thread/seed": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Openbim Quality Scan - * @description openBIM quality of the loaded model: LOIN per element, IFC export health, bSDD alignment, and - * (when ?use_case= names an IDS use case) IDS rule-compliance scoring. Needs a loaded model. + * Golden Thread Seed + * @description GOLDEN-THREAD seed — populate the evidence ledger from the latest **model-CI** report: each check + * becomes a tracked ``compliance_evidence`` requirement (outcome mapped from its status), so the thread + * starts from the checks already run instead of a blank slate. Idempotent — requirements already in the + * ledger are skipped, so re-seeding after a fresh CI run only adds what's new. Each seeded row still + * needs evidence + a sign-off to close. */ - get: operations["openbim_quality_scan_projects__pid__openbim_quality_get"]; - put?: never; - post?: never; + post: operations["golden_thread_seed_projects__pid__golden_thread_seed_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/lod/matrix": { + "/projects/{pid}/graph": { parameters: { query?: never; header?: never; @@ -9820,11 +10848,11 @@ export interface paths { cookie?: never; }; /** - * Lod Matrix - * @description The target LOD matrix (stage x discipline x element category -> LOD 100..500), or the RIBA/AIA - * stage defaults when the register carries none. + * Model Graph + * @description Graph stats — node + edge counts by relationship type (contained_in / aggregates / bounds / + * has_opening / fills / serves), built from the model's own IfcRel* relationships. */ - get: operations["lod_matrix_projects__pid__lod_matrix_get"]; + get: operations["model_graph_projects__pid__graph_get"]; put?: never; post?: never; delete?: never; @@ -9833,7 +10861,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/lod/assessment": { + "/projects/{pid}/graph/neighbors": { parameters: { query?: never; header?: never; @@ -9841,11 +10869,11 @@ export interface paths { cookie?: never; }; /** - * Lod Assessment - * @description Achieved-LOD assessment of the loaded model (inferred from LOIN facet completeness) against the - * target matrix. Returns targets only when no model is loaded. + * Model Graph Neighbors + * @description The connected subgraph around an element out to `depth` hops — every related node cited by GUID + + * the relationship path that reaches it. The multi-hop, relational answer the property index can't give. */ - get: operations["lod_assessment_projects__pid__lod_assessment_get"]; + get: operations["model_graph_neighbors_projects__pid__graph_neighbors_get"]; put?: never; post?: never; delete?: never; @@ -9854,7 +10882,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/naming/conventions": { + "/projects/{pid}/groups": { parameters: { query?: never; header?: never; @@ -9862,10 +10890,12 @@ export interface paths { cookie?: never; }; /** - * Naming Conventions - * @description The document/container filename + drawing sheet-ID naming conventions the validator enforces. + * List Groups + * @description W10-3: every IfcGroup (named set / selection) and IfcElementAssembly (part-of whole) in the + * model, with member counts. Create/array/ungroup go through POST /edit with the create_group | + * create_assembly | array_element | ungroup recipes (versioned + GUID-stable). */ - get: operations["naming_conventions_projects__pid__naming_conventions_get"]; + get: operations["list_groups_projects__pid__groups_get"]; put?: never; post?: never; delete?: never; @@ -9874,7 +10904,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/naming/validate": { + "/projects/{pid}/groups/{guid}": { parameters: { query?: never; header?: never; @@ -9882,10 +10912,10 @@ export interface paths { cookie?: never; }; /** - * Naming Validate - * @description Validate a single name against the convention. kind = container | sheet. + * Group Detail + * @description W10-3 inspector: the members/parts of one group or assembly. */ - get: operations["naming_validate_projects__pid__naming_validate_get"]; + get: operations["group_detail_projects__pid__groups__guid__get"]; put?: never; post?: never; delete?: never; @@ -9894,7 +10924,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/naming/audit": { + "/projects/{pid}/handover/acceptance": { parameters: { query?: never; header?: never; @@ -9902,10 +10932,11 @@ export interface paths { cookie?: never; }; /** - * Naming Audit - * @description Audit the CDE containers + drawing register for naming-convention compliance. + * Handover Acceptance + * @description Handover data-drop acceptance gate — the owner's checklist against the AIR (requirements, + * asset tags, as-builts, O&M, accepted completion certificate). */ - get: operations["naming_audit_projects__pid__naming_audit_get"]; + get: operations["handover_acceptance_projects__pid__handover_acceptance_get"]; put?: never; post?: never; delete?: never; @@ -9914,7 +10945,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/query/views": { + "/projects/{pid}/health": { parameters: { query?: never; header?: never; @@ -9922,10 +10953,10 @@ export interface paths { cookie?: never; }; /** - * Model Query Views - * @description The saved model-analytics views (count by discipline / class / storey / type). + * Project Health + * @description Executive project-health rollup — per-domain status, overall score, ranked attention items. */ - get: operations["model_query_views_projects__pid__model_query_views_get"]; + get: operations["project_health_projects__pid__health_get"]; put?: never; post?: never; delete?: never; @@ -9934,7 +10965,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/query": { + "/projects/{pid}/hero": { parameters: { query?: never; header?: never; @@ -9942,19 +10973,51 @@ export interface paths { cookie?: never; }; /** - * Model Query Run - * @description Analytics query over the loaded model — a saved ?view=, or ad-hoc group_by / agg=sum&quantity=. + * Get Hero + * @description The project's hero image (404 when none captured). */ - get: operations["model_query_run_projects__pid__model_query_get"]; - put?: never; + get: operations["get_hero_projects__pid__hero_get"]; + /** + * Put Hero + * @description 3D-HERO: pin a captured viewer screenshot (PNG/JPEG) as the project's hero image — it becomes + * page 2 of the client project package. 10 MB cap; magic-byte checked. + */ + put: operations["put_hero_projects__pid__hero_put"]; post?: never; + /** Delete Hero */ + delete: operations["delete_hero_projects__pid__hero_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/hold-sell": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Post Hold Sell + * @description CRE-HOLDSELL (R20) — hold versus sell, decided on one engine. + * + * Sell-now is today's reversion net of costs and payoff; each hold year is measured as + * **incremental** cash flows against the proceeds declined today, because holding costs you the + * cheque you did not take. Cap drift is an explicit input — "hold" silently bets on the exit cap, + * and a bet nobody wrote down is the one that loses money. Returns the breakeven hold period, or + * says plainly that none inside the horizon clears the hurdle. + */ + post: operations["post_hold_sell_projects__pid__hold_sell_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/model/export.csv": { + "/projects/{pid}/ids": { parameters: { query?: never; header?: never; @@ -9962,30 +11025,81 @@ export interface paths { cookie?: never; }; /** - * Model Export Csv - * @description Export the model element table as CSV (columnar, one row per element). + * Get Project Ids + * @description Whether a project IDS is pinned (+ its size); `?download=1` streams the .ids back. */ - get: operations["model_export_csv_projects__pid__model_export_csv_get"]; - put?: never; + get: operations["get_project_ids_projects__pid__ids_get"]; + /** + * Put Project Ids + * @description Pin the project's IDS. Subsequent `/validate` calls (with no uploaded file) run against it. + */ + put: operations["put_project_ids_projects__pid__ids_put"]; post?: never; + /** Delete Project Ids */ + delete: operations["delete_project_ids_projects__pid__ids_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/ifc/classify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Classify + * @description Suggest IfcClass reclassifications for generic/proxy or loosely-named elements (improves QTO + + * carbon). Uses posted `elements`, or the project's loaded property index when none are given. + */ + post: operations["classify_projects__pid__ifc_classify_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/projects/{pid}/model/export.jsonld": { + "/projects/{pid}/import/rvt": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Model Export Jsonld - * @description Export the model elements as a JSON-LD graph (bSDD-style vocab, GlobalId as @id). + * Import Rvt + * @description Import a native Revit .rvt by converting it to IFC via the paid APS bridge, then treat it like + * any source IFC (authoring / drawings / analysis / proforma all flow from it). Gated twice: the + * bridge must be configured (else 501 → use free IFC export), and the caller must `confirm_cost` + * (else 402) because Autodesk bills per conversion. */ - get: operations["model_export_jsonld_projects__pid__model_export_jsonld_get"]; + post: operations["import_rvt_projects__pid__import_rvt_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/info-requirements/cascade": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Requirements Cascade + * @description The ISO 19650 requirement flow-down — OIR → PIR/AIR → EIR → MIDP/TIDP linked by each record's + * `derives_from` — as a tiered tree plus cascade health (orphans that don't trace up to + * organizational intent; links pointing the wrong way). + */ + get: operations["requirements_cascade_projects__pid__info_requirements_cascade_get"]; put?: never; post?: never; delete?: never; @@ -9994,7 +11108,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/export.parquet": { + "/projects/{pid}/info-requirements/delivery-plan": { parameters: { query?: never; header?: never; @@ -10002,12 +11116,12 @@ export interface paths { cookie?: never; }; /** - * Model Export Parquet - * @description Export the model element table as Apache Parquet (columnar analytics — DuckDB / pandas / Polars). - * - * Needs the optional `pyarrow` dependency; returns 503 with a clear message when it isn't installed. + * Requirements Delivery Plan + * @description The MIDP/TIDP delivery plan — information requirements against their programme dates with + * overdue / due-soon status, a per-month roll-up, the next deliverable, and LOIN-specification + * coverage (EN 17412 Level of Information Need). */ - get: operations["model_export_parquet_projects__pid__model_export_parquet_get"]; + get: operations["requirements_delivery_plan_projects__pid__info_requirements_delivery_plan_get"]; put?: never; post?: never; delete?: never; @@ -10016,7 +11130,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/columnar/stats": { + "/projects/{pid}/info-requirements/register": { parameters: { query?: never; header?: never; @@ -10024,11 +11138,11 @@ export interface paths { cookie?: never; }; /** - * Model Columnar Stats - * @description Interning/columnar efficiency stats for the loaded model — dedup ratio + estimated RAM saved by - * the BimOpenSchema-style string-interned columnar form vs the per-element JSON index. + * Requirements Register + * @description The information-requirements register (OIR/AIR/PIR/EIR/BEP/MIDP/TIDP) with issued/draft + * counts and core-document coverage (EIR, BEP, AIR). */ - get: operations["model_columnar_stats_projects__pid__model_columnar_stats_get"]; + get: operations["requirements_register_projects__pid__info_requirements_register_get"]; put?: never; post?: never; delete?: never; @@ -10037,18 +11151,38 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/columnar/aggregate": { + "/projects/{pid}/interop/speckle/send": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Model Columnar Aggregate - * @description Columnar count group-by over the element table via pyarrow compute (vectorised, no row loop). + * Speckle Send + * @description Send the project's model/data to a Speckle stream (requires the bridge configured). */ - get: operations["model_columnar_aggregate_projects__pid__model_columnar_aggregate_get"]; + post: operations["speckle_send_projects__pid__interop_speckle_send_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/investment-deck.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Investment Deck + * @description Pitch-deck (slide) variant of the investment memo — landscape, big numbers, the ask. + */ + get: operations["investment_deck_projects__pid__investment_deck_pdf_get"]; put?: never; post?: never; delete?: never; @@ -10057,7 +11191,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/model/export/params.parquet": { + "/projects/{pid}/investment-memo.pdf": { parameters: { query?: never; header?: never; @@ -10065,11 +11199,12 @@ export interface paths { cookie?: never; }; /** - * Model Export Params Parquet - * @description Export the model's property/quantity set as an EAV Parquet table (the analytics-friendly store — - * query in DuckDB/pandas). Needs pyarrow; 503 if absent. + * Investment Memo + * @description Confidential investment memorandum (PDF) composed from live project data — executive summary, + * Sources & Uses, the development cost budget, returns (from the latest solved scenario), and a + * risk read. The 'generate a presentation with financials' deliverable. */ - get: operations["model_export_params_parquet_projects__pid__model_export_params_parquet_get"]; + get: operations["investment_memo_projects__pid__investment_memo_pdf_get"]; put?: never; post?: never; delete?: never; @@ -10078,19 +11213,39 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/bim-kpi/scorecard": { + "/projects/{pid}/investors/{iid}/share": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Bim Kpi Scorecard - * @description The 10-category BIM KPI scorecard, graded from the CDE, model quality and the issue / asset / - * closeout records (categories with no inputs show 'n/a'). + * Investor Statement Share + * @description Mint a signed, expiring link to an investor's statement PDF — the investor opens it with no + * session (the LP-portal share). Default TTL 30 days. The signature authorizes exactly that path. */ - get: operations["bim_kpi_scorecard_projects__pid__bim_kpi_scorecard_get"]; + post: operations["investor_statement_share_projects__pid__investors__iid__share_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/investors/{iid}/statement.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Investor Statement + * @description A one-page investor capital-account statement PDF. + */ + get: operations["investor_statement_projects__pid__investors__iid__statement_pdf_get"]; put?: never; post?: never; delete?: never; @@ -10099,7 +11254,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/handover/acceptance": { + "/projects/{pid}/investors/{iid}/statement.public.pdf": { parameters: { query?: never; header?: never; @@ -10107,11 +11262,11 @@ export interface paths { cookie?: never; }; /** - * Handover Acceptance - * @description Handover data-drop acceptance gate — the owner's checklist against the AIR (requirements, - * asset tags, as-builts, O&M, accepted completion certificate). + * Investor Statement Public + * @description Read-only investor statement behind a valid signed link (HMAC) — no session required. + * Publishes only this investor's own capital-account statement. */ - get: operations["handover_acceptance_projects__pid__handover_acceptance_get"]; + get: operations["investor_statement_public_projects__pid__investors__iid__statement_public_pdf_get"]; put?: never; post?: never; delete?: never; @@ -10120,7 +11275,7 @@ export interface paths { patch?: never; trace?: never; }; - "/projects/{pid}/standards/check": { + "/projects/{pid}/jobs": { parameters: { query?: never; header?: never; @@ -10128,20 +11283,25 @@ export interface paths { cookie?: never; }; /** - * Standards Check - * @description Standards-compliance check (iso19650 | cobie | ids | uniclass) against the project's own data: - * findings with the clause each references, recommendations, and a 0–100 readiness score. + * List Jobs + * @description The project's jobs, newest first (bounded). */ - get: operations["standards_check_projects__pid__standards_check_get"]; + get: operations["list_jobs_projects__pid__jobs_get"]; put?: never; - post?: never; + /** + * Enqueue Job + * @description Queue a background job for this project (editor — jobs do real work against the model/records; + * kinds in `_KIND_MIN_ROLE` need more). `kind` must be registered (400 with the registered list + * otherwise). Poll GET /projects/{pid}/jobs/{id}. + */ + post: operations["enqueue_job_projects__pid__jobs_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/bsdd/search": { + "/projects/{pid}/jobs/{job_id}": { parameters: { query?: never; header?: never; @@ -10149,12 +11309,10 @@ export interface paths { cookie?: never; }; /** - * Bsdd Search - * @description Free-text search the buildingSMART Data Dictionary for classes matching `q` - * (optionally scoped to one ?dictionary= URI). Reference-data lookup, not - * project-scoped. A bSDD outage surfaces as 502, not 500. + * Job Status + * @description One job's state + result/error. 404 when it doesn't exist or belongs to another project. */ - get: operations["bsdd_search_bsdd_search_get"]; + get: operations["job_status_projects__pid__jobs__job_id__get"]; put?: never; post?: never; delete?: never; @@ -10163,7 +11321,7 @@ export interface paths { patch?: never; trace?: never; }; - "/bsdd/class": { + "/projects/{pid}/jobs/{job_id}/artifact": { parameters: { query?: never; header?: never; @@ -10171,11 +11329,12 @@ export interface paths { cookie?: never; }; /** - * Bsdd Class - * @description Fetch one bSDD class (with its properties) by full `uri`. 404 when the class - * isn't found; 502 when bSDD is unreachable. + * Job Artifact + * @description Download a finished job's binary artifact (e.g. the compiled drawing-set PDF). Artifact jobs + * park their output in object storage and put `artifact_key` in the result; this streams it back. + * 409 while the job is still queued/running; 404 when the job has no artifact. */ - get: operations["bsdd_class_bsdd_class_get"]; + get: operations["job_artifact_projects__pid__jobs__job_id__artifact_get"]; put?: never; post?: never; delete?: never; @@ -10184,21 +11343,57 @@ export interface paths { patch?: never; trace?: never; }; - "/openbim/capabilities": { + "/projects/{pid}/jobs/{job_id}/deliver": { parameters: { query?: never; header?: never; path?: never; cookie?: never; }; + get?: never; + put?: never; /** - * Openbim Capabilities - * @description The openBIM standards + version matrix this platform speaks — for each standard (IFC, BCF, IDS, - * bSDD, COBie, ISO 19650 CDE), which versions we can read and write. Derived from the live engines - * (BCF versions, IFC schemas), so it never drifts from what's actually implemented; a consumer/agent - * can ask 'do you read BCF 3.0?' without guessing. + * Deliver Artifact + * @description Email a finished job's artifact to named recipients — the "shared, not just downloaded" half. + * + * Mirrors `job_artifact` exactly on lookup and refusal (404 wrong project, 409 while queued/running, + * 404 when the job produced no artifact), because a caller should not have to learn two different + * answers to "is this artifact ready". Delivery then adds two refusals of its own: an empty + * recipient list is 422 rather than a silent no-op, and an artifact over 15 MB is 413 rather than a + * per-recipient "error" from a server that would have rejected it anyway. + * + * Returns a per-recipient status map (`sent` / `disabled` / `error`) in the same shape as the + * notification digest, so an unconfigured deployment reports `disabled` instead of failing. + * + * **The body of this is in `artifact_delivery`, because the worker needs it too.** A scheduled + * report package is assembled with nobody watching, and it has to reach its recipients by the same + * caps, the same de-duplication and the same audit record — a second copy of those would be a + * second thing to keep in step. What stays here is only the part that is about HTTP: the readiness + * refusals above, and turning a `DeliveryRefused` back into the status it has always carried. */ - get: operations["openbim_capabilities_openbim_capabilities_get"]; + post: operations["deliver_artifact_projects__pid__jobs__job_id__deliver_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/jurisdiction/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Check + * @description Check the project's model against the data requirements for its jurisdiction. + * + * Every result carries the pack's authority and edition. A compliance figure detached from whose + * rules it measured is the kind of number that gets quoted at somebody, and `is_example` rides + * along so a run against the demonstration pack can never be mistaken for a real one. + */ + get: operations["project_check_projects__pid__jurisdiction_check_get"]; put?: never; post?: never; delete?: never; @@ -10207,7 +11402,7 @@ export interface paths { patch?: never; trace?: never; }; - "/mcp/tools": { + "/projects/{pid}/jurisdiction/requirements": { parameters: { query?: never; header?: never; @@ -10215,11 +11410,15 @@ export interface paths { cookie?: never; }; /** - * Mcp Tool Catalog - * @description The tool catalog the MCP server exposes to external AI agents (name, description, input - * schema). The stdio server (services/api/mcp_server.py) drives these against a project. + * Project Requirements + * @description The data requirements that apply to this project, resolved from its jurisdiction. + * + * A project with no jurisdiction set gets an empty list and the reason — never a default pack. A + * requirement set from the wrong authority is not a conservative approximation of the right one; + * it is a different answer that looks exactly like it, and it would fail a model against rules + * nobody imposed. Pass `?pack=` to apply one explicitly instead. */ - get: operations["mcp_tool_catalog_mcp_tools_get"]; + get: operations["project_requirements_projects__pid__jurisdiction_requirements_get"]; put?: never; post?: never; delete?: never; @@ -10228,7 +11427,7 @@ export interface paths { patch?: never; trace?: never; }; - "/admin/errors": { + "/projects/{pid}/k1-pack": { parameters: { query?: never; header?: never; @@ -10236,23 +11435,48 @@ export interface paths { cookie?: never; }; /** - * List Errors - * @description Newest-first error feed + a summary header. Admin only. + * K1 Pack + * @description R31-K1-PACK — the capital-movement input an accountant needs to prepare Schedule K-1s. + * + * **Not a K-1 and not a tax document**, and the payload says so in `is_tax_document: false` plus a + * `not_included` list naming what the platform cannot supply (chiefly the §704(b) income allocation — + * there is no income statement here). The honest half is machine-readable; the missing half is named + * rather than approximated, because a tax document is relied on and filed, not re-derived. */ - get: operations["list_errors_admin_errors_get"]; + get: operations["k1_pack_projects__pid__k1_pack_get"]; put?: never; post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/layers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; /** - * Clear Errors - * @description Force-prune to the retention cap (housekeeping). Admin only. + * Get Layers + * @description The project's property-override layer stack (composes over the model without mutating the IFC). */ - delete: operations["clear_errors_admin_errors_delete"]; + get: operations["get_layers_projects__pid__layers_get"]; + /** + * Put Layers + * @description Replace the layer stack. Pure data — nothing is written to the IFC until baked. + */ + put: operations["put_layers_projects__pid__layers_put"]; + post?: never; + delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/client-errors": { + "/projects/{pid}/layers/bake": { parameters: { query?: never; header?: never; @@ -10262,19 +11486,18 @@ export interface paths { get?: never; put?: never; /** - * Report Client Error - * @description Record a browser-side error (window.onerror / unhandledrejection / a failed fetch). Any signed-in - * user; best-effort so a reporting failure never disrupts the app. Body: {message, kind?, path?, - * level?, detail?}. + * Bake Layers + * @description Flatten the resolved composition into the IFC (each effective override -> a GUID-stable pset + * edit), producing a new version. Republishes so pins/RFIs/clashes survive. */ - post: operations["report_client_error_client_errors_post"]; + post: operations["bake_layers_projects__pid__layers_bake_post"]; delete?: never; options?: never; head?: never; patch?: never; trace?: never; }; - "/health": { + "/projects/{pid}/layers/resolve": { parameters: { query?: never; header?: never; @@ -10282,10 +11505,11 @@ export interface paths { cookie?: never; }; /** - * Health - * @description Liveness — the process is up and serving. Cheap, no dependencies; for restart probes. + * Resolve Layers + * @description Compose the enabled layers into effective values, with provenance + cross-layer conflicts — + * the data-world twin of clash detection. Non-destructive; the IFC is untouched until baked. */ - get: operations["health_health_get"]; + get: operations["resolve_layers_projects__pid__layers_resolve_get"]; put?: never; post?: never; delete?: never; @@ -10294,7 +11518,7 @@ export interface paths { patch?: never; trace?: never; }; - "/ready": { + "/projects/{pid}/layout.dxf": { parameters: { query?: never; header?: never; @@ -10302,15 +11526,10 @@ export interface paths { cookie?: never; }; /** - * Ready - * @description Readiness — the process can serve real traffic (DB reachable). Pings the DB with a - * trivial `SELECT 1`; returns 503 if it's unreachable so a load balancer / orchestrator stops - * routing to (or restarts) this instance instead of serving 500s. Kept separate from /health - * so a DB blip doesn't kill a still-live process. The ping runs under a hard wall-clock timeout - * so a black-holed DB (paused host / network partition) yields a prompt 503 instead of hanging - * the probe itself. + * Layout Dxf + * @description Layered DXF layout drawing (points + labels) for floor printers (Dusty-style). */ - get: operations["ready_ready_get"]; + get: operations["layout_dxf_projects__pid__layout_dxf_get"]; put?: never; post?: never; delete?: never; @@ -10319,7 +11538,7 @@ export interface paths { patch?: never; trace?: never; }; - "/metrics": { + "/projects/{pid}/layout/points": { parameters: { query?: never; header?: never; @@ -10327,10 +11546,11 @@ export interface paths { cookie?: never; }; /** - * Prometheus Metrics - * @description Prometheus text exposition (request counts, latencies, in-flight, uptime). + * Layout Points + * @description Preview the model's field-layout setout points (georeferenced) — grids + column/footing/opening/ + * wall placements, each with its IFC GlobalId. `classes` optionally narrows the IFC classes. */ - get: operations["prometheus_metrics_metrics_get"]; + get: operations["layout_points_projects__pid__layout_points_get"]; put?: never; post?: never; delete?: never; @@ -10339,1118 +11559,12115 @@ export interface paths { patch?: never; trace?: never; }; -} -export type webhooks = Record; -export interface components { - schemas: { - /** Actual */ - Actual: { - /** - * Actual To Date - * @default 0 - */ - actual_to_date: number; - /** - * Committed - * @default 0 - */ - committed: number; - /** Cost To Complete */ - cost_to_complete?: number | null; - }; - /** Assumptions */ - Assumptions: { - timing: components["schemas"]["Timing"]; - /** Cost Lines */ - cost_lines: components["schemas"]["CostLine"][]; - debt: components["schemas"]["Debt"]; - equity: components["schemas"]["Equity"]; - operations: components["schemas"]["Ops"]; - exit: components["schemas"]["Exit"]; - waterfall: components["schemas"]["Waterfall"]; - /** - * Discount Rate - * @default 0.1 - */ - discount_rate: number; - tax?: components["schemas"]["Tax"] | null; + "/projects/{pid}/layout/points.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** AttachmentOut */ - AttachmentOut: { - /** Id */ - id: string; - /** Topic Id */ - topic_id: string; - /** Filename */ - filename: string; - /** Content Type */ - content_type: string | null; - /** Size */ - size: number; - /** Kind */ - kind: string; - /** Storage Key */ - storage_key: string; - /** - * Created At - * Format: date-time - */ - created_at: string; + /** + * Layout Csv + * @description PENZD / PNEZD points file for total-station / marking-robot import (configurable column order). + */ + get: operations["layout_csv_projects__pid__layout_points_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/layout/verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Axis */ - Axis: { - /** Path */ - path: string; - /** Values */ - values: number[]; + get?: never; + put?: never; + /** + * Layout Verify + * @description Compare as-installed total-station shots to the design setout: `{measured:[{number,e,n,z}, …], + * tolerance_m?, classes?}` → per-point 3-D deviation, flagging points out of tolerance (each anchored + * to its element GlobalId for a field-verification issue). + */ + post: operations["layout_verify_projects__pid__layout_verify_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lean/ppc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_add_attachment_projects__pid__topics__tid__attachments_post */ - Body_add_attachment_projects__pid__topics__tid__attachments_post: { - /** - * Kind - * @default file - */ - kind: string; - /** File */ - file: string; + /** + * Lean Ppc + * @description Last-Planner Plan Percent Complete + reasons for non-completion from the weekly-plan module (R4). + */ + get: operations["lean_ppc_projects__pid__lean_ppc_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/leases/management": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_add_comment_projects__pid__modules__key___rid__comments_post */ - Body_add_comment_projects__pid__modules__key___rid__comments_post: { - /** Text */ - text: string; + /** + * Lease Management + * @description Lease-management depth — renewal/expiration pipeline, forward rent-escalation schedule, and + * CAM/expense-recovery reconciliation (pass ?recoverable_opex= for the recovery ratio + gap). + */ + get: operations["lease_management_projects__pid__leases_management_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lifecycle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_add_enum_option_projects__pid__modules__key__enum__field__post */ - Body_add_enum_option_projects__pid__modules__key__enum__field__post: { - /** Value */ - value: string; + /** + * Lifecycle + * @description The project's design phases (RIBA 0–7 ↔ AIA) with gate state, deliverables, ISO-19650 status, + * and the phase-allocated A/E design fee from the itemized soft costs. + */ + get: operations["lifecycle_projects__pid__lifecycle_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lifecycle/seed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_add_project_model_projects__pid__models_post */ - Body_add_project_model_projects__pid__models_post: { - /** File */ - file: string; - /** - * Discipline - * @default Model - */ - discipline: string; + get?: never; + put?: never; + /** + * Seed + * @description Seed the eight design-phase records on a project (idempotent). + */ + post: operations["seed_projects__pid__lifecycle_seed_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/listings/autofill": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_apply_template_projects__pid__responsibility_apply_template_post */ - Body_apply_template_projects__pid__responsibility_apply_template_post: { - /** Key */ - key: string; - /** - * Mode - * @default RACI - */ - mode: string; + /** + * Listing Autofill + * @description Pre-populated listing fields from the project's proforma + model (the off-plan advantage). + */ + get: operations["listing_autofill_projects__pid__listings_autofill_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/listings/{lid}/public": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_assign_record_projects__pid__modules__key___rid__assign_post */ - Body_assign_record_projects__pid__modules__key___rid__assign_post: { - /** Assignee */ - assignee?: string | null; + /** + * Listing Public + * @description Read-only public listing — the only intentionally-anonymous surface. Requires a valid signed + * URL (HMAC) regardless of RBAC; publishes only listing-safe fields (no internal financials beyond + * what the owner put in the public description / asking price). + */ + get: operations["listing_public_projects__pid__listings__lid__public_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/listings/{lid}/reso": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_bcf_import_projects__pid__bcf_import_post */ - Body_bcf_import_projects__pid__bcf_import_post: { - /** File */ - file: string; + /** + * Listing Reso + * @description The RESO Data Dictionary payload for a listing — the shape a bridge POSTs to WPRealWise / MLS. + */ + get: operations["listing_reso_projects__pid__listings__lid__reso_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/listings/{lid}/share": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_bulk_action_projects__pid__modules__key__bulk_post */ - Body_bulk_action_projects__pid__modules__key__bulk_post: { - /** Ids */ - ids: string[]; - /** Action */ - action: string; - /** Value */ - value?: string | null; + get?: never; + put?: never; + /** + * Listing Share + * @description Mint a signed, expiring URL to the public listing JSON (for a QR / shared link). The signature + * authorizes exactly that path until it expires — no session needed by the recipient. + */ + post: operations["listing_share_projects__pid__listings__lid__share_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/listings/{lid}/syndicate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_capital_call_projects__pid__capital_call_post */ - Body_capital_call_projects__pid__capital_call_post: { - /** Amount */ - amount: number; - /** - * Persist - * @default false - */ - persist: boolean; + get?: never; + put?: never; + /** + * Listing Syndicate + * @description Push a listing (RESO-serialized) into WPRealWise / an MLS. Requires the bridge to be configured; + * 422 with an actionable message otherwise. The RESO export endpoint works regardless. + */ + post: operations["listing_syndicate_projects__pid__listings__lid__syndicate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/loads/defaults": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_certify_projects__pid__turnover_certify_post */ - Body_certify_projects__pid__turnover_certify_post: { - /** Cert Rid */ - cert_rid: string; - /** Architect */ - architect: string; - /** Owner */ - owner?: string | null; - /** Contractor */ - contractor?: string | null; - /** Occupancy Date */ - occupancy_date?: string | null; + /** + * Loads Defaults + * @description Pre-fill the load takedown from the model — storey names/count + interior-column count. + */ + get: operations["loads_defaults_projects__pid__loads_defaults_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/loads/takedown": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_change_password_auth_password_post */ - Body_change_password_auth_password_post: { - /** Current */ - current: string; - /** New */ - new: string; + get?: never; + put?: never; + /** + * Loads Takedown + * @description Preliminary gravity load takedown for a typical interior column → per-storey rows + accumulated + * service/factored (ASCE 7) axial to the footing. Body: explicit `storeys:[{name,area_sf,occupancy, + * roof?}]`, OR `{storey_count, floor_area_sf, occupancy}` to auto-build uniform storeys (top = roof); + * plus `sdl_psf?, slab_thickness_in?, column_count?, concrete_pcf?`. Missing counts fall back to the + * model. **Preliminary only — not a substitute for a licensed structural engineer.** + */ + post: operations["loads_takedown_projects__pid__loads_takedown_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/loan-draws": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_classify_projects__pid__ifc_classify_post */ - Body_classify_projects__pid__ifc_classify_post: { - /** Elements */ - elements?: { - [key: string]: unknown; - }[] | null; + /** + * Loan Draws + * @description Construction-loan draw status from the GC's actual billing: owner invoices are the developer's + * draws to pay the GC, funded equity-first then debt. Returns the sized loan/equity (from Sources & + * Uses) vs drawn-to-date, the equity/loan split, and remaining loan availability — so the developer + * tracks the capital stack against what the contractor has actually billed. + */ + get: operations["loan_draws_projects__pid__loan_draws_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/loan-draws/request.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_clone_scenario_proforma_scenarios__sid__clone_post */ - Body_clone_scenario_proforma_scenarios__sid__clone_post: { - /** Name */ - name: string; + /** + * Loan Draw Request Pdf + * @description The lender draw-request as a PDF — this draw (the GC pay-app amount due) against the + * construction loan, with cumulative draws, equity/loan split, balance, and availability. + */ + get: operations["loan_draw_request_pdf_projects__pid__loan_draws_request_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/loan/covenants": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_code_check_projects__pid__codecheck_post */ - Body_code_check_projects__pid__codecheck_post: { - /** - * Description - * @default - */ - description: string; - /** Context */ - context?: string | null; + get?: never; + put?: never; + /** + * Post Loan Covenants + * @description CRE-COVENANT (R20) — the loan covenant + reporting-obligation register. + * + * Body: `{loan: {name, lender, holidays?, obligations: [{name, days, day_basis, clock_start, + * lender_notice_date?, received_date?, period_end?, delivered_date?}], covenants: [{name, metric, + * direction, threshold, cure_days?}]}, actuals?: {metric: value}}`. + * + * **Timing alone can breach a loan**, so `day_basis` (calendar vs business days) and `clock_start` + * (lender's notice vs our receipt) are first-class: every due date shows its anchor, basis, count + * and the non-working days it skipped, and an obligation whose two clock readings disagree is + * flagged with both dates. Covenants with no supplied actual are reported **untested**, never as + * passing; a breach inside an open cure window is reported separately from one outside it. + */ + post: operations["post_loan_covenants_projects__pid__loan_covenants_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lod": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_connection_query_connections__cid__query_post */ - Body_connection_query_connections__cid__query_post: { - /** Sql */ - sql: string; - /** - * Limit - * @default 200 - */ - limit: number; + /** + * Lod Summary + * @description W11 F0: element LOD-stage distribution (100/200/300/350/400/500/unset). Advance elements with the + * `set_lod` recipe; `ensure_contexts` establishes the view-keyed representation contexts the drawing + * pipeline needs. LOD is element maturity, not a geometry mode — the same GUID carries it as it refines. + */ + get: operations["lod_summary_projects__pid__lod_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lod/assessment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_convert_citygml_convert_citygml_post */ - Body_convert_citygml_convert_citygml_post: { - /** File */ - file: string; + /** + * Lod Assessment + * @description Achieved-LOD assessment of the loaded model (inferred from LOIN facet completeness) against the + * target matrix. Returns targets only when no model is loaded. + */ + get: operations["lod_assessment_projects__pid__lod_assessment_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lod/handover-readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_convert_convert_post */ - Body_convert_convert_post: { - /** File */ - file: string; + /** + * Lod Handover Readiness + * @description LOD 500 handover readiness as a **work list**, not a percentage. + * + * LOD 500 is a field-verified as-built assertion — it is earned by someone going and looking, never + * by adding geometry. This reports which elements are short of it and what to do about each: + * unverified, verified-but-outside-tolerance (a finding, not a handover), verified with no stated + * accuracy, or thin on information for turnover. + */ + get: operations["lod_handover_readiness_projects__pid__lod_handover_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lod/matrix": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post */ - Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post: { - /** File */ - file: string; + /** + * Lod Matrix + * @description The target LOD matrix (stage x discipline x element category -> LOD 100..500), or the RIBA/AIA + * stage defaults when the register carries none. + */ + get: operations["lod_matrix_projects__pid__lod_matrix_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/lod500": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_create_user_auth_users_post */ - Body_create_user_auth_users_post: { - /** Username */ - username: string; - /** Password */ - password: string; - /** - * Role - * @default user - */ - role: string; - /** Email */ - email?: string | null; - }; - /** Body_distribution_projects__pid__distribution_post */ - Body_distribution_projects__pid__distribution_post: { - /** Amount */ - amount: number; - /** - * Persist - * @default false - */ - persist: boolean; - }; - /** Body_documents_move_projects__pid__documents__fid__move_post */ - Body_documents_move_projects__pid__documents__fid__move_post: { - /** Path */ - path: string; - }; - /** Body_documents_upload_projects__pid__documents_upload_post */ - Body_documents_upload_projects__pid__documents_upload_post: { - /** Path */ - path: string; - /** File */ - file: string; - /** Title */ - title?: string | null; - /** Discipline */ - discipline?: string | null; - /** Doc Type */ - doc_type?: string | null; - /** Cde State */ - cde_state?: string | null; - /** Revision */ - revision?: string | null; + /** + * Asbuilt Summary + * @description W11 G1: LOD-500 readiness — the share of the model that is **field-verified as-built** (the + * reliability attribute BIMForum actually defines as LOD 500; it has no geometric requirement). Counts + * elements with `Massing_AsBuilt.Status==VERIFIED`, by verification method. Stamp elements with the + * `verify_asbuilt` recipe (POST /edit). + */ + get: operations["asbuilt_summary_projects__pid__lod500_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/logistics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_draft_rfi_ep_projects__pid__draft_rfi_post */ - Body_draft_rfi_ep_projects__pid__draft_rfi_post: { - /** - * Note - * @default - */ - note: string; - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Get Logistics + * @description Site-logistics resources (cranes / laydown / gates …) with schedule windows — pure data, drawn + * as time-phased overlays on the 4D timeline. + */ + get: operations["get_logistics_projects__pid__logistics_get"]; + /** + * Put Logistics + * @description Replace the site-logistics resource list. + */ + put: operations["put_logistics_projects__pid__logistics_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/logistics/clash": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_draft_rfi_projects__pid__ai_draft_rfi_post */ - Body_draft_rfi_projects__pid__ai_draft_rfi_post: { - /** - * Element - * @default {} - */ - element: { - [key: string]: unknown; - }; - /** Note */ - note?: string | null; + /** + * Logistics Clash + * @description W9-5: **swept crane-reach clash** over the logistics plan — crane pairs whose swing discs + * intersect while both are on site (closest approach sampled along any motion paths, with the + * worst date) plus static resources parked under a hook. Plan-level screen, not a jib simulation. + */ + get: operations["logistics_clash_projects__pid__logistics_clash_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/logistics/state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_draft_scope_ep_projects__pid__draft_scope_post */ - Body_draft_scope_ep_projects__pid__draft_scope_post: { - /** - * Trade - * @default General - */ - trade: string; - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Logistics State + * @description Which logistics resources are active on `date` (blank = the whole plan) — drives the time-phased + * overlay as the 4D slider moves. + */ + get: operations["logistics_state_projects__pid__logistics_state_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/macros": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post */ - Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post: { - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Macros List + * @description The project's saved recipe-macros (falls back to the starter set until one is saved). Each macro = + * a name + declared params + an ordered list of authoring-recipe steps referencing ``${param}``. + */ + get: operations["macros_list_projects__pid__macros_get"]; + /** + * Macros Put + * @description Replace the project's macro library. Every step's recipe name is validated against the edit + * engine's registry before anything is written — a bad macro rejects the whole save with 422. + */ + put: operations["macros_put_projects__pid__macros_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/macros/{macro_id}/expand": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_edit_preview_projects__pid__edit_preview_post */ - Body_edit_preview_projects__pid__edit_preview_post: { - /** Recipe */ - recipe: string; - /** - * Params - * @default {} - */ - params: { - [key: string]: unknown; - }; + get?: never; + put?: never; + /** + * Macros Expand + * @description Preview a macro's concrete step list for the given args WITHOUT touching the model — defaults fill + * omitted params, ``${name}`` placeholders resolve. Lets a client review/validate before running. + */ + post: operations["macros_expand_projects__pid__macros__macro_id__expand_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/macros/{macro_id}/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_edit_projects__pid__edit_post */ - Body_edit_projects__pid__edit_post: { - /** Recipe */ - recipe: string; - /** - * Params - * @default {} - */ - params: { - [key: string]: unknown; - }; - /** - * Publish - * @default false - */ - publish: boolean; + get?: never; + put?: never; + /** + * Macros Run + * @description Expand a saved macro with ``args`` and apply the whole chain as ONE GUID-stable version — the same + * read→apply→pointer-swap the ``/edit/batch`` route uses (one edit-history entry, so the macro undoes as + * a single step; honors the COLLAB-1 optimistic lock via ``base_source``). + */ + post: operations["macros_run_projects__pid__macros__macro_id__run_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/margin/by-costcode": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_evm_capture_projects__pid__evm_snapshot_post */ - Body_evm_capture_projects__pid__evm_snapshot_post: { - /** Data Date */ - data_date?: string | null; - /** Period Label */ - period_label?: string | null; - /** Notes */ - notes?: string | null; + /** + * Margin By Costcode + * @description MARGIN-CBS — per-cost-code money reconciliation: budget vs committed (subcontracts/POs) vs actual + * (direct costs) vs billed (sub invoices), with the projected **buyout margin** (budget − committed), + * the **cost variance** (budget − actual), and over-committed / over-budget flags, worst-margin first. + */ + get: operations["margin_by_costcode_projects__pid__margin_by_costcode_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/market/context": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_extract_sheets_ep_projects__pid__extract_sheets_post */ - Body_extract_sheets_ep_projects__pid__extract_sheets_post: { - /** File */ - file?: string | null; - /** Text */ - text?: string | null; - /** - * Create - * @default false - */ - create: boolean; + /** + * Market Context + * @description The project's market read: regional economics + sector temperature + the escalation factor to its + * construction midpoint. Query params override the project's `market_assumption` record. + */ + get: operations["market_context_projects__pid__market_context_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/market/escalate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_forecast_stateless_proforma_forecast_post */ - Body_forecast_stateless_proforma_forecast_post: { - assumptions: components["schemas"]["Assumptions"]; - /** Actuals */ - actuals: components["schemas"]["Actual"][]; - /** - * As Of Month - * @default 0 - */ - as_of_month: number; + /** + * Market Escalate + * @description Escalate `amount` to the construction midpoint (or `to_year`) using the region's annual rate — + * for adjusting a base estimate / proforma hard cost to when it will actually be built. + */ + get: operations["market_escalate_projects__pid__market_escalate_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/market/exists": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_heartbeat_projects__pid__presence_post */ - Body_heartbeat_projects__pid__presence_post: { - /** Viewpoint */ - viewpoint?: { - [key: string]: unknown; - } | null; + /** + * Market Exists + * @description Whether the project exists (cheap guard used by the panel). + */ + get: operations["market_exists_projects__pid__market_exists_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/master-builder/brief": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_bundle_projects_import_bundle_post */ - Body_import_bundle_projects_import_bundle_post: { - /** File */ - file: string; - /** Name */ - name?: string | null; + /** + * Master Builder Brief + * @description MASTER-BUILDER — the whole project in one view: runs the 8-step Master Builder Protocol (place → + * program/HBU → feasibility → regulatory → design-integration → delivery → risk → handover) over the + * project's own data, grounds it in the project's jurisdiction, and reports a readiness status + the + * concrete gap per step (each linking to the tool that closes it). A readiness synthesis over the data + * on hand — not a substitute for licensed judgment, a plan check, or committed underwriting. + */ + get: operations["master_builder_brief_projects__pid__master_builder_brief_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/master-builder/brief.md": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_families_projects__pid__families_import_post */ - Body_import_families_projects__pid__families_import_post: { - /** File */ - file: string; + /** + * Master Builder Brief Md + * @description MASTER-BUILDER brief as a shareable Markdown document — the printable one-page project-readiness + * brief (place grounding, per-step readiness + gaps, hazards to verify, the honest-status boundary). + */ + get: operations["master_builder_brief_md_projects__pid__master_builder_brief_md_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/materials/apply": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_module_bcf_projects__pid__modules__key__bcf_import_post */ - Body_import_module_bcf_projects__pid__modules__key__bcf_import_post: { - /** File */ - file: string; + get?: never; + put?: never; + /** + * Apply Material Palette + * @description Re-colour the model with the saved palette overrides and republish it: load the source IFC, + * re-run the M1 material/surface-style assignment with the merged palette, write it back, and kick + * the convert→fragments + reindex so the viewer shows the new colours. No-op message if no model. + */ + post: operations["apply_material_palette_projects__pid__materials_apply_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/materials/palette": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_preview_projects__pid__modules__key__import_preview_post */ - Body_import_preview_projects__pid__modules__key__import_preview_post: { - /** File */ - file: string; + /** + * Get Material Palette + * @description The material palette for the project (M1): the default class→material/colour table, the saved + * per-project overrides, and the **effective** palette (default with overrides applied) — what the + * model actually renders. Drives the material-editor UI. + */ + get: operations["get_material_palette_projects__pid__materials_palette_get"]; + /** + * Put Material Palette + * @description Save per-project material overrides (class → {name, category, color:[r,g,b], transparency}). + * Only the classes you change need be present; the rest fall back to the default palette. Persisted + * to project storage; call `…/materials/apply` to re-colour + republish the model. + */ + put: operations["put_material_palette_projects__pid__materials_palette_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_records_projects__pid__modules__key__import_post */ - Body_import_records_projects__pid__modules__key__import_post: { - /** File */ - file: string; - /** - * Mapping - * @default {} - */ - mapping: string; + /** + * My Membership + * @description The caller's own effective role on this project — drives UI capability gating. No role + * required (a non-member gets role=null). `rbac` tells the client whether gating is enforced; + * when it's off the client should treat the user as fully capable (matching the open API). + */ + get: operations["my_membership_projects__pid__me_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/members": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_rvt_projects__pid__import_rvt_post */ - Body_import_rvt_projects__pid__import_rvt_post: { - /** File */ - file: string; + /** List Members */ + get: operations["list_members_projects__pid__members_get"]; + put?: never; + /** Add Member */ + post: operations["add_member_projects__pid__members_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/members/{member}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_import_xer_projects__pid__schedule_import_xer_post */ - Body_import_xer_projects__pid__schedule_import_xer_post: { - /** File */ - file: string; + get?: never; + put?: never; + post?: never; + /** + * Remove Member + * @description Remove a member from the project. Won't remove the last admin (avoids an orphaned project). + */ + delete: operations["remove_member_projects__pid__members__member__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_inspect_vim_convert_vim_inspect_post */ - Body_inspect_vim_convert_vim_inspect_post: { - /** File */ - file: string; + /** + * Mep Summary + * @description W11 B6: MEP system browser — each IfcDistributionSystem with its segment/fitting/terminal + * breakdown + a connectivity signal (elements with unconnected ports), plus segments/fittings not + * yet assigned to any system. Add fittings with the `add_mep_fitting` recipe. + */ + get: operations["mep_summary_projects__pid__mep_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/connectivity": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post */ - Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post: { - /** Companies */ - companies: string[]; + /** + * Mep Connectivity + * @description W10-4: MEP connectivity validation — ports connected vs open, port-to-port connection count, and the + * **dangling** (floating) elements whose ports are all unconnected. Wire elements with the `connect_mep` + * recipe (`POST /edit` with `{guid_a, guid_b}`). + */ + get: operations["mep_connectivity_projects__pid__mep_connectivity_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/fittings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_level_quotes_projects__pid__procurement_level_quotes_post */ - Body_level_quotes_projects__pid__procurement_level_quotes_post: { - /** Quotes */ - quotes: { - [key: string]: unknown; - }[]; - }; - /** Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post */ - Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post: { - /** - * App No - * @default 1 - */ - app_no: number; - /** - * Vendor - * @default - */ - vendor: string; - /** - * Waiver Type - * @default Conditional Progress - */ - waiver_type: string; - }; - /** Body_login_auth_login_post */ - Body_login_auth_login_post: { - /** Username */ - username: string; - /** Password */ - password: string; + /** + * Mep Fittings + * @description MEP-FITTINGS: the **implied fittings** over the port graph — tee/cross at branch nodes, reducer at a + * nominal-size step, elbow at a run direction change (from placement origins). Deterministic, no CV; the + * counts roll into QTO as EA lines so buyout/estimate see the fittings the model implies, not just the + * segments drawn. 409 without a source IFC. + */ + get: operations["mep_fittings_projects__pid__mep_fittings_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/graph": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_mfa_disable_auth_mfa_disable_post */ - Body_mfa_disable_auth_mfa_disable_post: { - /** Password */ - password: string; - /** - * Code - * @default - */ - code: string; + /** + * Mep Graph + * @description MEP-GRAPH: the port connectivity graph over IfcDistributionPort — connected **runs** with their + * endpoints, branch points and the **longest linear path** (the index-run backbone), plus the count of + * isolated (unconnected) elements. A first-class network view over the connect_mep edges, the + * foundation for real path-based pressure-loss. 409 without a source IFC. + */ + get: operations["mep_graph_projects__pid__mep_graph_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/model-extract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_mfa_enable_auth_mfa_enable_post */ - Body_mfa_enable_auth_mfa_enable_post: { - /** Code */ - code: string; + /** + * Mep Model Extract + * @description MEP elements read off the loaded model (by IFC class) — complements the register schedule. + */ + get: operations["mep_model_extract_projects__pid__mep_model_extract_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/pressure-loss": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_mfa_verify_auth_mfa_verify_post */ - Body_mfa_verify_auth_mfa_verify_post: { - /** Mfa Token */ - mfa_token: string; - /** Code */ - code: string; + /** + * Mep Pressure Loss + * @description MEP depth: friction (pressure) loss per authored duct/pipe run (empirical round-duct + Hazen- + * Williams rates from the sizing pset's size + flow + length) with per-system series-sum totals and + * the **index run** a balancing engineer hunts first. Rates checked against the equal-friction + * budgets. **Preliminary — no branch topology/fittings/diversity; final balancing by a PE.** + */ + get: operations["mep_pressure_loss_projects__pid__mep_pressure_loss_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/schedule": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post */ - Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post: { - /** - * App No - * @default 1 - */ - app_no: number; - /** Period */ - period?: string | null; - /** - * Release Retainage - * @default false - */ - release_retainage: boolean; + /** + * Mep Schedule + * @description The MEP equipment schedule from the register + a per-system capacity rollup. + */ + get: operations["mep_schedule_projects__pid__mep_schedule_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/size": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_extract_pdf_extract_post */ - Body_pdf_extract_pdf_extract_post: { - /** File */ - file: string; - /** Pages */ - pages: string; + /** + * Mep Size + * @description First-pass MEP sizing. kind = duct (flow=CFM, velocity=fpm) | pipe (flow=GPM, velocity=fps) | + * cooling (load=BTU/h) | block_cooling (gfa_sf, sf_per_ton) | hanger (hanger_kind=duct|pipe_steel| + * pipe_copper, size=in). + * + * **R37-TESTED-UNWIRED — `block_cooling` is the new one, and it is not a duplicate of `cooling`.** + * `cooling` converts a load somebody already has into tons; `block_cooling` *estimates* the load + * from gross area, which is the earlier question and the one asked when no load exists yet. It was + * the only function in `mep.py` this dispatcher did not call: `size_duct`, `size_pipe`, + * `size_cooling` and `hanger_spacing` were all here, and `block_cooling_load` was reachable by + * nothing. One of five missing from a five-branch dispatcher is a gap, not a design. + * + * `gfa_sf` is optional and defaults to the project's own GFA from `energy.project_gfa_sf` — the one + * definition of gross area in this codebase, so deriving a second one here would guarantee two + * answers to one question. A caller may still pass `gfa_sf` to size a massing that is not the + * loaded model. + * + * **It REFUSES rather than returning a plausible zero.** `mep.block_cooling_load` clamps with + * `max(gfa, 0.0)`, so an unloaded project would come back `tons: 0.0` — a number an engineer could + * read straight into a plant schedule. Same for a non-finite or non-positive `sf_per_ton`, which + * the engine clamps to 1.0 and would answer with twelve times the tonnage anyone intended. + */ + get: operations["mep_size_projects__pid__mep_size_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/sizing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_info_pdf_info_post */ - Body_pdf_info_pdf_info_post: { - /** File */ - file: string; + /** + * Mep Sizing + * @description MEP-SIZE: engineering size checks over authored MEP — computes flow velocity in each duct/pipe from + * the design size + flow (`Pset_Massing_MEPSizing`) and checks it against accepted limits (ASHRAE + * low-velocity air, erosion-limit water, NEC 392 tray fill), pass/fail like the IBC checks. Elevates MEP + * from *modeled* to *engineered*. **Preliminary — not a substitute for a licensed MEP engineer.** + */ + get: operations["mep_sizing_projects__pid__mep_sizing_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/sprinkler-coverage": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_merge_pdf_merge_post */ - Body_pdf_merge_pdf_merge_post: { - /** Files */ - files: string[]; + /** + * Sprinkler Coverage + * @description MEP-FP: a sprinkler coverage pre-check — SPRINKLER head count vs the number NFPA 13 would require for + * the model's protected floor area (IfcSpace `NetFloorArea`) at the given hazard class (`light` / + * `ordinary` / `extra`). A planning assist, not a hydraulic design. + */ + get: operations["sprinkler_coverage_projects__pid__mep_sprinkler_coverage_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/thermal-loads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_rotate_pdf_rotate_post */ - Body_pdf_rotate_pdf_rotate_post: { - /** File */ - file: string; - /** - * Angle - * @default 90 - */ - angle: number; - /** - * Pages - * @default - */ - pages: string; + /** + * Mep Thermal Loads + * @description MEP depth: space-by-space cooling-load screen (W/sf method) — people/lighting/equipment + * densities by space type + a flat envelope allowance per IfcSpace, summed to tons and compared to + * the block `GFA ÷ 350` estimate so the team sees WHERE the load lives. **A screen, not an ASHRAE + * heat-balance calc — design loads by a licensed mechanical engineer.** + */ + get: operations["mep_thermal_loads_projects__pid__mep_thermal_loads_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/mep/tray-fill": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_seal_pdf_seal_post */ - Body_pdf_seal_pdf_seal_post: { - /** File */ - file: string; - /** Template Id */ - template_id: string; - /** - * Page - * @default 1 - */ - page: number; - /** - * X - * @default 36 - */ - x: number; - /** - * Y - * @default 36 - */ - y: number; - /** - * Sign - * @default true - */ - sign: boolean; - /** Profile */ - profile: string; + /** + * Mep Tray Fill + * @description MEP depth: per-conductor NEC 392.22 cable-tray fill — computed from the actual authored + * IfcCableSegment diameters on each tray's distribution system vs the Table 392.22(A) allowable + * (7 in² per 6 in of width), instead of a supplied ratio. **Preliminary pre-check, not a PE design.** + */ + get: operations["mep_tray_fill_projects__pid__mep_tray_fill_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model.frag": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_split_pdf_split_post */ - Body_pdf_split_pdf_split_post: { - /** File */ - file: string; + /** + * Model Frag + * @description Serve the published Fragments tile with HTTP range support + ETag revalidation. The URL is + * stable across republishes, so we revalidate (not immutable): unchanged → 304 (instant re-open), + * republished → fresh bytes. Access: a valid signed URL or (RBAC on) project membership. + */ + get: operations["model_frag_projects__pid__model_frag_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model.frag/signed-url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_pdf_stamp_pdf_stamp_post */ - Body_pdf_stamp_pdf_stamp_post: { - /** File */ - file: string; - /** Template Id */ - template_id: string; - /** - * Page - * @default 1 - */ - page: number; - /** - * X - * @default 36 - */ - x: number; - /** - * Y - * @default 36 - */ - y: number; - /** - * Disposition - * @default - */ - disposition: string; - /** - * Values - * @default - */ - values: string; + /** + * Sign Model Frag + * @description Mint a short-lived signed URL for the model tile (e.g. QR share / worker fetch without a session). + */ + get: operations["sign_model_frag_projects__pid__model_frag_signed_url_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/adjacency": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_place_family_projects__pid__families_place_post */ - Body_place_family_projects__pid__families_place_post: { - /** Family */ - family: string; - /** Position */ - position?: number[] | null; - /** Storey */ - storey?: string | null; - /** - * Publish - * @default false - */ - publish: boolean; + get?: never; + put?: never; + /** + * Model Adjacency + * @description TESTFIT-ADJ — adjacency + dimensional-compliance over the model's IfcSpaces: which spaces physically + * touch (bboxes within a wall gap on the same storey), scored against a program's `required_adjacent` / + * `forbidden` type-pairs, plus a dimensional rule pack (`min_room_dim` · `min_area` · `min_ceiling_height`, + * global or `by_type`). Deterministic, no OCC (footprints from the extruded profiles). 409 without a + * source IFC. Body: {required_adjacent, forbidden, dimensional}. + */ + post: operations["model_adjacency_projects__pid__model_adjacency_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/assembly-thermal": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_price_tm_projects__pid__cost_tm_post */ - Body_price_tm_projects__pid__cost_tm_post: { - /** Eticket Id */ - eticket_id: string; - /** Lines */ - lines: { - [key: string]: unknown; - }[]; + /** + * Model Assembly Thermal + * @description WALL-ASSEMBLY thermal: every distinct IfcMaterialLayerSet in the model → its **R/U-value computed + * from the layers** (thickness ÷ design k per category + surface films), the elements using it, and a + * per-layer material takeoff (thickness × face area). The bridge from the authored assemblies to the + * envelope code-check. 409 without a source IFC. + */ + get: operations["model_assembly_thermal_projects__pid__model_assembly_thermal_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/assets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_push_procore_projects__pid__sync_procore_push_post */ - Body_push_procore_projects__pid__sync_procore_push_post: { - /** Connection Id */ - connection_id: string; - /** Procore Project Id */ - procore_project_id: string; - /** - * Kinds - * @default [ - * "rfi" - * ] - */ - kinds: string[]; + /** + * Model Assets + * @description ASSET-REG — the maintainable-asset register derived straight from the IFC: serviceable equipment / + * terminals / controls / transport (subtype-resolved; ducts/pipes/fittings excluded), GUID-keyed, tagged + * with discipline + storey + type, with per-discipline / per-category / per-class tallies. 409 if the + * project has no source IFC. + */ + get: operations["model_assets_projects__pid__model_assets_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/assets/seed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_put_config_projects__pid__responsibility_config_put */ - Body_put_config_projects__pid__responsibility_config_put: { - /** Roles */ - roles: string[]; - /** - * Mode - * @default RACI - */ - mode: string; + get?: never; + put?: never; + /** + * Model Assets Seed + * @description Seed the `asset_register` module from the model-derived assets (idempotent by tag) — turns the IFC + * into a populated FM register in one call, ready for pm_schedule + warranty/serial per asset. + */ + post: operations["model_assets_seed_projects__pid__model_assets_seed_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/blank": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_put_integrations_settings_integrations_put */ - Body_put_integrations_settings_integrations_put: { - /** Values */ - values: { - [key: string]: unknown; - }; + get?: never; + put?: never; + /** + * Create Blank Model + * @description Create a **blank authoring model** for the project — base IFC + `storeys` levels + a + * ground-reference datum, no building geometry — set it as the project's source IFC and publish it. + * The from-scratch starting point for the modeler; everything else is authored via the edit recipes + * (add_wall, add_column, add_family, …). Unblocks authoring without generating a whole massing first. + */ + post: operations["create_blank_model_projects__pid__model_blank_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/capabilities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_put_mappings_connections__cid__mappings_put */ - Body_put_mappings_connections__cid__mappings_put: { - /** Mappings */ - mappings: { - [key: string]: unknown; - }; + /** + * Model Capabilities + * @description IFC read-schema capabilities + the detected schema of this project's loaded model (IFC5/IFCX + * is detected and reported, not yet parsed). + */ + get: operations["model_capabilities_projects__pid__model_capabilities_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/columnar/aggregate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_put_project_ids_projects__pid__ids_put */ - Body_put_project_ids_projects__pid__ids_put: { - /** File */ - file: string; - }; - /** Body_raise_plan_to_bim_projects__pid__raise_plan_post */ - Body_raise_plan_to_bim_projects__pid__raise_plan_post: { - /** File */ - file: string; - /** - * Wall Height - * @default 3 - */ - wall_height: number; - /** - * Wall Thickness - * @default 0.2 - */ - wall_thickness: number; - /** - * Preview - * @default false - */ - preview: boolean; - }; - /** Body_register_auth_register_post */ - Body_register_auth_register_post: { - /** Username */ - username: string; - /** Password */ - password: string; - /** - * Role - * @default user - */ - role: string; + /** + * Model Columnar Aggregate + * @description Columnar count group-by over the element table via pyarrow compute (vectorised, no row loop). + */ + get: operations["model_columnar_aggregate_projects__pid__model_columnar_aggregate_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/columnar/stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_reset_password_auth_users__username__password_post */ - Body_reset_password_auth_users__username__password_post: { - /** Password */ - password: string; + /** + * Model Columnar Stats + * @description Interning/columnar efficiency stats for the loaded model — dedup ratio + estimated RAM saved by + * the BimOpenSchema-style string-interned columnar form vs the per-element JSON index. + */ + get: operations["model_columnar_stats_projects__pid__model_columnar_stats_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/connections": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_reset_with_token_auth_reset_post */ - Body_reset_with_token_auth_reset_post: { - /** Token */ - token: string; - /** New */ - new: string; + /** + * Model Connections + * @description R21-4D-CLASH phase 2 — what the model STATES about which elements are joined. + * + * Install-before-support needs to know that B holds up A. This reports only what the IFC actually + * encodes, graded by what it licenses you to conclude, and **refuses to infer support from + * geometry**: two elements touching says nothing about load path — a ceiling touches a wall it does + * not rest on. Proximity would produce confident findings a structural engineer would reject, on + * the question where being wrong is dearest. + * + * `connected` edges carry `direction: "unstated"`, because IFC's Relating/Related pair is an + * authoring order and not a load direction. Only `structural` edges, which come from an analysis + * model, claim a direction. A model with no connection relations returns `stated: false` rather + * than an empty graph — an absence of DATA, not an absence of conflicts. + */ + get: operations["model_connections_projects__pid__model_connections_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/constraints": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_review_ask_ep_projects__pid__review_ask_post */ - Body_review_ask_ep_projects__pid__review_ask_post: { - /** Question */ - question: string; - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Model Constraints + * @description AUTH-CONSTRAINTS ① (R18) — validate the model's OWN constraint graph (RelVoids/RelFills hosts, + * storey containment): broken hosts and dangling fills are **errors**, missing containment and + * level/elevation disagreements are **warnings**, bare openings and unhosted inserts informational. + * Placement checks (an insert outside its host wall's extent) are attribute-based, no OCC; anything + * unmeasurable is skipped and counted, never guessed. 409 without a source IFC. + */ + get: operations["model_constraints_projects__pid__model_constraints_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/design-metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_review_contract_ep_projects__pid__review_contract_post */ - Body_review_contract_ep_projects__pid__review_contract_post: { - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Model Design Metrics + * @description DESIGN-METRICS — program-efficiency numbers over the model (floors · GFA · net floor area · + * net-to-gross · unit count · area by space type) + a deterministic **average-daylight-factor estimate** + * from the model's actual windows (glazed area vs net floor area, CIBSE formula — not ray-traced). + * 409 if the project has no source IFC. + */ + get: operations["model_design_metrics_projects__pid__model_design_metrics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/element/{guid}/effective-props": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_review_scope_ep_projects__pid__review_scope_post */ - Body_review_scope_ep_projects__pid__review_scope_post: { - /** File */ - file?: string | null; - /** Text */ - text?: string | null; + /** + * Element Effective Props + * @description FAMILY-DEPTH ② (R18) — the effective property view for one element: every pset/property + * with its effective value, its source (`instance` | `type`), the shadowed type value where an + * occurrence override is in play, and the override count. The properties panel's type-vs-instance + * answer, straight from the model. + */ + get: operations["element_effective_props_projects__pid__model_element__guid__effective_props_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/ensure": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_run_clash_federated_projects__pid__clash_federated_post */ - Body_run_clash_federated_projects__pid__clash_federated_post: { - /** - * Disciplines - * @default {} - */ - disciplines: { - [key: string]: unknown; - }; + get?: never; + put?: never; + /** + * Ensure Model + * @description R28-UNIFY — opening a project guarantees there is something to draw on. + * + * Creating a blank model already existed as `POST /model/blank`, but only as an explicit user + * action, so a project with no model still opened into an empty viewer with nothing to author + * against. This is the same capability made **idempotent** so an open path can call it every time. + * + * Three outcomes, and they are deliberately distinct because two of them look identical from the + * outside and only one is safe to act on: + * + * * `found` — a readable model is already there. **Nothing is written.** Idempotence is the whole + * safety property: an ensure that could overwrite is a data-loss endpoint wearing a helpful name. + * * `created` — the project had no model, and a blank authorable one now exists. + * * `broken_reference` — `source_ifc` is SET but the file is not readable. **Refused.** This is the + * dangerous case: it is not "no model", it is a model that has gone missing, and quietly replacing + * it with an empty one would destroy the only pointer to something that may be recoverable — and + * would report success while doing it. The caller is told what path is missing so somebody can go + * and look for it. + */ + post: operations["ensure_model_projects__pid__model_ensure_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/equipment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_run_validate_projects__pid__validate_post */ - Body_run_validate_projects__pid__validate_post: { - /** File */ - file?: string | null; + /** + * Model Equipment + * @description MEP-EQUIP — the procurement equipment schedule derived from the IFC: procurable MEP units grouped by + * class + type into RFQ line-items with a quantity + representative spec (from the model's Psets); + * ducts/pipes/fittings + controls excluded. 409 if the project has no source IFC. + */ + get: operations["model_equipment_projects__pid__model_equipment_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/equipment/budget-lines": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_saml_acs_auth_saml_acs_post */ - Body_saml_acs_auth_saml_acs_post: { - /** Samlresponse */ - SAMLResponse: string; - /** - * Relaystate - * @default - */ - RelayState: string; + /** + * Model Equipment Budget Lines + * @description MEP-EQUIP tie — the equipment schedule as **budget-suggestion rows** (qty EA per type), priced + * from the project's own price-observation ledger where it has seen the type (median), else unpriced + * for a manual allowance. Read-only. 409 if no source IFC. + */ + get: operations["model_equipment_budget_lines_projects__pid__model_equipment_budget_lines_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/equipment/spec-check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_save_template_projects__pid__modules__key__save_template_post */ - Body_save_template_projects__pid__modules__key__save_template_post: { - /** Name */ - name: string; + get?: never; + put?: never; + /** + * Model Equipment Spec Check + * @description MEP-EQUIP SPEC-CONFLICT — cross-check the scheduled equipment against a specified-requirement set + * (`{ifc_class: {spec_key: expected}}`) → the mismatches (a modelled Pset value disagreeing with the + * spec) + missing specified properties. Deterministic; no spec-document scanning. 409 if no source IFC. + */ + post: operations["model_equipment_spec_check_projects__pid__model_equipment_spec_check_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/equipment/starter-requirements": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_save_view_projects__pid__modules__key__views_post */ - Body_save_view_projects__pid__modules__key__views_post: { - /** Name */ - name: string; - /** - * Config - * @default {} - */ - config: { - [key: string]: unknown; - }; + /** + * Model Equipment Starter + * @description MEP-EQUIP — the curated starter requirement set for the spec-check: the properties an engineer + * expects every unit of the common equipment classes to carry before buyout (`"*"` = presence-required). + * Feed it to `/model/equipment/spec-check` as-is or edited. + */ + get: operations["model_equipment_starter_projects__pid__model_equipment_starter_requirements_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/equipment/to-submittals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_scan_deviation_projects__pid__scan_deviation_post */ - Body_scan_deviation_projects__pid__scan_deviation_post: { - /** File */ - file: string; + get?: never; + put?: never; + /** + * Model Equipment To Submittals + * @description MEP-EQUIP tie — mint one **product-data submittal** per scheduled equipment type (idempotent by + * title: re-running after a model change only adds the NEW types). The procurement schedule and the + * submittal log stay one thread. 409 if no source IFC. + */ + post: operations["model_equipment_to_submittals_projects__pid__model_equipment_to_submittals_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_screen_parcels_screen_post */ - Body_screen_parcels_screen_post: { - /** Parcels */ - parcels: { - [key: string]: unknown; - }[]; - /** Criteria */ - criteria?: { - [key: string]: unknown; - } | null; + /** + * Model Export Csv + * @description Export the model element table as CSV (columnar, one row per element). + */ + get: operations["model_export_csv_projects__pid__model_export_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.glb": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_share_scenario_proforma_scenarios__sid__share_post */ - Body_share_scenario_proforma_scenarios__sid__share_post: { - /** User */ - user: string; + /** + * Export Glb + * @description Export the model geometry as a binary **glTF (.glb)** — the compact single-file form Blender / + * three.js / game engines import directly (vs. the JSON `.gltf`). Same per-class meshes + colours; + * tessellation runs off the event loop. + */ + get: operations["export_glb_projects__pid__model_export_glb_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.gltf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_sync_procore_projects__pid__sync_procore_post */ - Body_sync_procore_projects__pid__sync_procore_post: { - /** Connection Id */ - connection_id: string; - /** Procore Project Id */ - procore_project_id: string; - /** - * Kinds - * @default [ - * "rfi", - * "submittal", - * "change_event" - * ] - */ - kinds: string[]; + /** + * Export Gltf + * @description Export the model geometry as a self-contained glTF 2.0 file (interchange — Blender / Three.js / + * any DCC). Triangulated meshes merged per IFC class with per-class colours; Z-up→Y-up. The viewer + * itself streams Fragments — this is the portable geometry-out path. Geometry tessellation runs off + * the event loop. + */ + get: operations["export_gltf_projects__pid__model_export_gltf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.ifc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_tag_elements_projects__pid__modules__key___rid__elements_post */ - Body_tag_elements_projects__pid__modules__key___rid__elements_post: { - /** Guids */ - guids: string[]; - /** - * Mode - * @default add - */ - mode: string; + /** + * Export Ifc + * @description First-class **IFC re-export** — stream the project's current authored source IFC (edits republish + * it in place, so this is the live model), not only inside the closeout bundle zip. The GUID-stable + * source of truth a coordinator can round-trip through any openBIM tool. + */ + get: operations["export_ifc_projects__pid__model_export_ifc_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.ifcx": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_takeoff_dxf_projects__pid__takeoff_dxf_post */ - Body_takeoff_dxf_projects__pid__takeoff_dxf_post: { - /** File */ - file: string; + /** + * Model Export Ifcx + * @description Export the model's element+property layer as IFC5 JSON. `flavor`: `ifcjson` (buildingSMART + * ifcJSON, default, full-fidelity round-trip) or `ifcx` (OpenUSD-style IFCX node list). Geometry is + * out of scope until web-ifc / Fragments add IFC5 upstream — this is the data write path. + */ + get: operations["model_export_ifcx_projects__pid__model_export_ifcx_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.jsonld": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_transition_projects__pid__modules__key___rid__transition_post */ - Body_transition_projects__pid__modules__key___rid__transition_post: { - /** Action */ - action: string; - /** Note */ - note?: string | null; + /** + * Model Export Jsonld + * @description Export the model elements as a JSON-LD graph (bSDD-style vocab, GlobalId as @id). + */ + get: operations["model_export_jsonld_projects__pid__model_export_jsonld_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export.parquet": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_triage_rfi_projects__pid__ai_triage_rfi_post */ - Body_triage_rfi_projects__pid__ai_triage_rfi_post: { - /** Rid */ - rid?: string | null; - /** - * Rfi - * @default {} - */ - rfi: { - [key: string]: unknown; - }; + /** + * Model Export Parquet + * @description Export the model element table as Apache Parquet (columnar analytics — DuckDB / pandas / Polars). + * + * Needs the optional `pyarrow` dependency; returns 503 with a clear message when it isn't installed. + */ + get: operations["model_export_parquet_projects__pid__model_export_parquet_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/export/params.parquet": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_update_schedule_projects__pid__sync_schedules__sid__put */ - Body_update_schedule_projects__pid__sync_schedules__sid__put: { - /** Enabled */ - enabled?: boolean | null; - /** Interval Minutes */ - interval_minutes?: number | null; - /** Kinds */ - kinds?: string[] | null; - /** Push */ - push?: boolean | null; + /** + * Model Export Params Parquet + * @description Export the model's property/quantity set as an EAV Parquet table (the analytics-friendly store — + * query in DuckDB/pandas). Needs pyarrow; 503 if absent. + */ + get: operations["model_export_params_parquet_projects__pid__model_export_params_parquet_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/fill-matrix": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Body_upload_attachment_projects__pid__modules__key___rid__attachments_post */ - Body_upload_attachment_projects__pid__modules__key___rid__attachments_post: { - /** File */ - file: string; - }; - /** Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post */ - Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post: { - /** Files */ - files: string[]; - }; - /** Body_upload_index_projects__pid__properties_index_post */ - Body_upload_index_projects__pid__properties_index_post: { - /** File */ - file: string; - }; - /** Body_upload_photo_projects__pid__verification__guid__photo_post */ - Body_upload_photo_projects__pid__verification__guid__photo_post: { - /** File */ - file: string; - }; - /** Body_upload_source_ifc_projects__pid__source_ifc_post */ - Body_upload_source_ifc_projects__pid__source_ifc_post: { - /** File */ - file: string; + /** + * Model Fill Matrix + * @description FILL-MATRIX — a category × property fill-rate pivot over the model: per IFC class, which pset field is + * systematically blank, with the **blank GUIDs** per property (feed them + a value to a bulk edit — the + * analytics → selection → bulk-write loop) + the worst partially-filled fields. Empty result without a + * loaded model. + */ + get: operations["model_fill_matrix_projects__pid__model_fill_matrix_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/grid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** CommentIn */ - CommentIn: { - /** Author */ - author?: string | null; - /** Text */ - text: string; - /** Viewpoint Id */ - viewpoint_id?: string | null; + /** + * Model Grid + * @description Drafting reference frame: the grid (real IfcGrid axes, else derived from IfcColumn centres) + + * its snap intersections + the storey levels — for the web Draft panel to render and snap against. + */ + get: operations["model_grid_projects__pid__model_grid_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/load-timing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** CommentOut */ - CommentOut: { - /** Id */ - id: string; - /** Topic Id */ - topic_id: string; - /** Author */ - author: string | null; - /** Text */ - text: string; - /** Viewpoint Id */ - viewpoint_id: string | null; - /** - * Created At - * Format: date-time - */ - created_at: string; + get?: never; + put?: never; + /** + * Record Load Timing + * @description R39-VIEWER-OBS — record one viewer model-load. Best-effort; the client drops the promise. + * + * `require_role` is not optional here even though the payload is uninteresting: this is a + * `/projects/{pid}` route, and a project-scoped route without a role check takes its project from + * whoever calls it. That is the exact shape of the privilege side-door this repo has already found. + * + * Every field is clamped rather than trusted. The client is a browser and its numbers are + * attacker-controlled: an unclamped `total_ms` poisons every percentile this table exists to + * report, which is a quiet way to make the instrument lie. + */ + post: operations["record_load_timing_projects__pid__model_load_timing_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/load-timings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** ConnectionIn */ - ConnectionIn: { - /** - * Name - * @default - */ - name: string; - /** Type */ - type: string; - /** - * Config - * @default {} - */ - config: { - [key: string]: unknown; - }; + /** + * Load Timings + * @description R39-VIEWER-OBS — p50/p95 of the load journey, by model-size band. + * + * Percentiles are computed in Python, not SQL: SQLite has no `percentile_cont`, and this runs on + * both dialects. The row cap below bounds that in memory. + * + * **`ok_rate` is reported beside every percentile on purpose.** Percentiles are computed over the + * loads that finished, so a band whose loads mostly stall would otherwise show an excellent p95 + * drawn from its handful of survivors. The rate is what stops that reading as good news. + */ + get: operations["load_timings_projects__pid__model_load_timings_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/lod/census": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** CostLine */ - CostLine: { - /** - * Category - * @enum {string} - */ - category: "land" | "hard" | "soft" | "contingency" | "fee"; - /** Name */ - name: string; - /** - * Amount - * @default 0 - */ - amount: number; - /** - * Curve - * @default scurve - * @enum {string} - */ - curve: "scurve" | "linear" | "upfront"; - /** - * Start Month - * @default 0 - */ - start_month: number; - /** - * End Month - * @default 0 - */ - end_month: number; - /** Csi Code */ - csi_code?: string | null; + /** + * Model Lod Census + * @description R23-STOREY-LOD — where this model's triangle budget actually goes, and what a proxy would save. + * + * The archived Phase-2 audit says frustum culling means no custom LOD is needed. That is accurate + * about geometry which is OFF-SCREEN and says nothing about density that is on-screen — a whole + * building in view, every element in frustum. This answers the question with a measurement instead: + * on `school_str.ifc`, `IfcReinforcingBar` alone is **55.2%** of the triangles from a class nobody + * can resolve at building scale. + * + * `max_elements` caps the mesh pass and the cap is REPORTED — a saving measured under a cap is a + * LOWER BOUND, because the classes that dominate a model may sort late in iteration order. A small + * number here is not evidence that a proxy is not worth building. + */ + get: operations["model_lod_census_projects__pid__model_lod_census_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/lod/proxy": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Debt */ - Debt: { - /** Ltc */ - ltc: number; - /** Rate */ - rate: number; - /** - * Points - * @default 0 - */ - points: number; - /** - * Funding - * @default equity_first - * @enum {string} - */ - funding: "equity_first" | "pari_passu" | "loan_first"; - /** Max Ltv */ - max_ltv?: number | null; - /** Min Dscr */ - min_dscr?: number | null; - /** Min Debt Yield */ - min_debt_yield?: number | null; + get?: never; + put?: never; + /** + * Model Lod Proxy + * @description Generate the coarse per-storey proxy and store it beside the model as `model.lod.ifc`. + * + * An ordinary IFC, so it reaches the viewer through the converter we already ship — which is why the + * recorded "no Fragments writer" blocker does not apply to a server-side proxy. + * + * **Every box declares itself** by name prefix and by an `AEC_LOD` pset saying it is not authored + * geometry and must not be measured, scheduled or priced: a stand-in mistaken for real geometry is + * worse than no stand-in. **Nothing to proxy stores nothing** — an empty artefact would read as a + * successful export of nothing — and the response says so rather than returning a 200 over silence. + */ + post: operations["model_lod_proxy_projects__pid__model_lod_proxy_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/maintenance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** DevBudgetIn */ - DevBudgetIn: { - /** Lines */ - lines?: { - [key: string]: unknown; - }[]; - /** Contingency */ - contingency?: { - [key: string]: number; - }; + /** + * Model Maintenance Scan + * @description IFCPATCH-LIB: a dry-run maintenance report — how many entities each cleanup recipe would remove + * (orphaned property sets, empty groups) without mutating anything. Run a recipe via the edit path + * (`POST /projects/{pid}/edit` with `recipe: purge_orphan_psets | purge_empty_groups`) to apply + + * republish (GUID-stable for kept elements). + */ + get: operations["model_maintenance_scan_projects__pid__model_maintenance_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/options": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Distribution */ - Distribution: { - /** - * Kind - * @enum {string} - */ - kind: "normal" | "uniform" | "triangular"; - /** Mean */ - mean?: number | null; - /** Std */ - std?: number | null; - /** Low */ - low?: number | null; - /** High */ - high?: number | null; - /** Mode */ - mode?: number | null; - /** Min */ - min?: number | null; - /** Max */ - max?: number | null; + /** + * Option List + * @description E6: the project's model-option branches; the one byte-identical to the current source is + * flagged `current`. + */ + get: operations["option_list_projects__pid__model_options_get"]; + put?: never; + /** + * Option Snapshot + * @description E6: **branch the current model** as a named design option — the recipe log is the undo stack; + * this makes it branchable ("Scheme A — steel frame"). Re-using a name overwrites that branch head. + * Switch with `/model/options/{slug}/activate` (itself undoable); compare with `.../diff`. + */ + post: operations["option_snapshot_projects__pid__model_options_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/options/{slug}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** DrawPackageIn */ - DrawPackageIn: { - /** Project Id */ - project_id: string; - /** Actuals */ - actuals: components["schemas"]["Actual"][]; - /** - * As Of Month - * @default 0 - */ - as_of_month: number; - /** - * Retainage Pct - * @default 5 - */ - retainage_pct: number; - /** - * App No - * @default 1 - */ - app_no: number; + get?: never; + put?: never; + post?: never; + /** + * Option Delete + * @description E6: drop an option branch (the edit history is untouched). + */ + delete: operations["option_delete_projects__pid__model_options__slug__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/options/{slug}/activate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Equity */ - Equity: { - /** Lp Pct */ - lp_pct: number; - /** Gp Pct */ - gp_pct: number; + get?: never; + put?: never; + /** + * Option Activate + * @description E6: switch the project to an option branch. Goes through the same edit-history push as any + * edit — so the switch itself is one undo step — and republishes the viewer geometry. + */ + post: operations["option_activate_projects__pid__model_options__slug__activate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/options/{slug}/diff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** Exit */ - Exit: { - /** Exit Cap */ - exit_cap: number; - /** - * Selling Cost Pct - * @default 0 - */ - selling_cost_pct: number; + /** + * Option Diff + * @description E6: what separates the current model from an option branch — added/removed element GUIDs and + * per-class count deltas (GUID-set level; use the version history's fingerprint diff within a + * branch). + */ + get: operations["option_diff_projects__pid__model_options__slug__diff_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** ForecastIn */ - ForecastIn: { - /** Actuals */ - actuals: components["schemas"]["Actual"][]; - /** - * As Of Month - * @default 0 - */ - as_of_month: number; + /** + * Model Query Run + * @description Analytics query over the loaded model — a saved ?view=, or ad-hoc group_by / agg=sum&quantity=. + */ + get: operations["model_query_run_projects__pid__model_query_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/query/views": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** HTTPValidationError */ - HTTPValidationError: { - /** Detail */ - detail?: components["schemas"]["ValidationError"][]; + /** + * Model Query Views + * @description The saved model-analytics views (count by discipline / class / storey / type). + */ + get: operations["model_query_views_projects__pid__model_query_views_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/roundtrip": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; /** - * MarkupBulkIn - * @description Persist a whole sheet's markup scene from the 2D editor. `replace` clears the caller's own prior - * markups for that sheet first (kept: any already promoted to an RFI). + * Model Roundtrip + * @description INTEROP-RT (R19) — the round-trip fidelity gauntlet on the project model: serialize → + * fresh parse → compare (GUID stability · class · name · containment · type · psets). Unmatched + * reported both ways; `fidelity_ok` is the verdict. */ - MarkupBulkIn: { - /** Sheet Id */ - sheet_id: string; - /** - * Replace - * @default true - */ - replace: boolean; - /** Markups */ - markups: components["schemas"]["MarkupItemIn"][]; + get: operations["model_roundtrip_projects__pid__model_roundtrip_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/roundtrip.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - /** MarkupIn */ - MarkupIn: { - /** Sheet Id */ - sheet_id: string; - /** X */ - x: number; - /** Y */ - y: number; - /** Note */ - note?: string | null; - /** - * Kind - * @default pin - */ - kind: string; - /** Data */ - data?: { - [key: string]: unknown; - } | null; + /** + * Roundtrip Export + * @description XLSX-ROUNDTRIP — the GUID-keyed property table for editing in Excel/Sheets: one row per + * element, columns = guid, ifc_class, name + the requested `Pset.Prop` columns (comma-separated). + * Re-import via POST /model/roundtrip/diff → the set_props_by_guid recipe. + */ + get: operations["roundtrip_export_projects__pid__model_roundtrip_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/roundtrip/diff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; + get?: never; + put?: never; /** - * MarkupItemIn - * @description One markup within a bulk save — the sheet_id comes from the parent, not each item. + * Roundtrip Diff + * @description XLSX-ROUNDTRIP — DRY-RUN diff of an edited CSV/XLSX against the live property index: which + * cells would change (`{guid, pset, prop, old, new, dtype}`), which GUIDs are unknown. Nothing is + * written — apply the returned `changes` via the `set_props_by_guid` edit recipe (which republishes). + * Sheet parsing lives in `_sheet_rows`, the per-row cell diff in `_diff_row`. */ - MarkupItemIn: { - /** X */ - x: number; - /** Y */ - y: number; - /** Note */ - note?: string | null; - /** - * Kind - * @default pin - */ - kind: string; - /** Data */ - data?: { - [key: string]: unknown; - } | null; + post: operations["roundtrip_diff_projects__pid__model_roundtrip_diff_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/select": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; /** - * MassingIn - * @description Zoning envelope (metres) + acquisition assumptions for the starter proforma. + * Model Select + * @description QUERY-DSL — select elements with a selector string (`IfcWall & Pset_WallCommon.FireRating=2HR & + * storey=L3`) → matching GUIDs + the parsed predicates. One grammar scopes clash runs, view filters, + * schedules, bulk edits, and MCP tools. Bad query → 422. */ - MassingIn: { - /** - * Name - * @default Massing Study + get: operations["model_select_projects__pid__model_select_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/setup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Setup + * @description MODEL-SETUP — the project's length unit and how far its geometry sits from the file origin. + * + * The read half of two repairs that were already reachable through `POST /projects/{pid}/edit` and + * that no screen offered: `convert_length_unit` and `rebase_origin`. Both were listed in + * `authoring_matrix.UNREACHED`, which that module calls a defect rather than a gap. + * + * A read, not a report on the repair: `targets` is the converter's own accepted list so a client + * cannot offer a unit the recipe would 400 on, and `distance_from_origin` is measured over the same + * root placements `rebase_origin` shifts, so the number shown is the number the repair acts on. + */ + get: operations["model_setup_projects__pid__model_setup_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/space-demand": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Model Space Demand + * @description SPACE-UTIL — a headcount `program` (`{space_type: headcount}`) vs. the modelled inventory → required + * vs. supplied area + the gap (deficit/surplus) per type. Deterministic supply/demand planner. + */ + post: operations["model_space_demand_projects__pid__model_space_demand_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/space-utilization": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Space Utilization + * @description SPACE-UTIL — occupancy capacity per `IfcSpace` at the given area-per-person standard, rolled up by + * space type + totals. Pure arithmetic over the modelled net floor areas. 409 if no source IFC. + */ + get: operations["model_space_utilization_projects__pid__model_space_utilization_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/split-plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Split Plan + * @description IFCPATCH-LIB — plan a per-storey split: which element GUIDs land in each storey slice, plus + * the unassigned remainder. Read-only; hand a slice's GUIDs to the subset export to write it. + */ + get: operations["model_split_plan_projects__pid__model_split_plan_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/step-summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Step Summary + * @description Fast model summary — header + entity-type histogram from a streaming STEP scan, without a full + * ifcopenshell parse. Instant 'what's in this IFC' for large files. + */ + get: operations["model_step_summary_projects__pid__model_step_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Stream + * @description COLLAB-1: server-sent events that re-emit the collab snapshot whenever the model version OR the + * presence roster changes — so an open viewer live-reloads the geometry after another user publishes an + * edit, and shows who's in the session. Polls a change signature server-side; fresh DB session per poll + * (the generator outlives the request). + */ + get: operations["model_stream_projects__pid__model_stream_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/model/wall-joins": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Wall Joins + * @description AUTH-CONSTRAINTS ③ (R18) — detect L/T wall joins (endpoint coincidence / endpoint-on-axis, + * same storey, non-parallel): the corner point, the through/stub classification, and counts. + * Resolution is the `resolve_wall_joins` edit recipe (GUID-stable butt joins, idempotent). + */ + get: operations["model_wall_joins_projects__pid__model_wall_joins_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Project Models + * @description Discipline models layered on the project beyond the primary source IFC (for federated clash). + */ + get: operations["list_project_models_projects__pid__models_get"]; + put?: never; + /** + * Add Project Model + * @description Append a discipline IFC (STR / MEP / ARCH …) so it can take part in federated clash. + */ + post: operations["add_project_model_projects__pid__models_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/alignment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Alignment + * @description Federation alignment report — do the project's discipline models share the same storey scheme + * and georeferenced origin? The #1 coordination problem is models on different origins/levels; this + * reads each model's storey elevations + IfcMapConversion and flags mismatches (a lightweight + * companion to federated clash). Reads the models read-only. + */ + get: operations["model_alignment_projects__pid__models_alignment_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/export-qa": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Export Qa + * @description IFC-QA · **export round-trip fidelity** — writes the source IFC out and reopens it, then compares + * schema / units / entity counts / GlobalId set / storeys / property payload. Proves the re-export is + * lossless (the #1 openBIM complaint is silent loss on export). 409 if the project has no source IFC. + */ + get: operations["model_export_qa_projects__pid__models_export_qa_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/footprint.geojson": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Footprint Geojson + * @description GIS-OUT — the building footprint + site point as a **WGS84 GeoJSON FeatureCollection**, anchored + * on the model's IfcSite reference lat/long (equirectangular local-tangent transform; building-scale, + * not survey-grade). Drops the model onto a web map / GIS. `available` is false when the model carries + * no site lat/long. 409 if there's no source IFC. + */ + get: operations["model_footprint_geojson_projects__pid__models_footprint_geojson_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/from-upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Add Project Model From Upload + * @description R41-UPLOAD-WARK — register a discipline model from an upload that was already assembled. + * + * The consumer the resumable handshake was missing. `POST /projects/{pid}/uploads/…/complete` leaves + * a finished object in storage and returns its key; this turns that key into a `ProjectModel`, so a + * large IFC can be uploaded resumably — and a re-upload of an unchanged file transfers nothing — + * while ending in exactly the state the multipart route above produces. + * + * **The key is checked against THIS project's prefix, and that check is the whole security of the + * route.** The client supplies the key, and an assembled upload lives at + * `projects/{pid}/uploads/{uid}`; without the check a caller who is an editor on their own project + * could name another project's object and have its contents registered as their model. That is the + * caller-supplied-identifier shape this repo has already found twice — on `/firm/rules`, where a + * `require_role` dependency with no `{pid}` in the path made the project a query parameter, and in + * the `enqueue_job` params merge. A role check answers "may you write HERE"; it says nothing about + * whether the object you named is yours. + * + * The copy is streamed both ways — `storage.get_chunks` in, `stream_to_path`/`put_stream` out — so + * an assembled 200 MB IFC is never whole in memory. Registering it through `get()` would have undone + * the reason the handshake exists. + */ + post: operations["add_project_model_from_upload_projects__pid__models_from_upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/georeferencing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Georeferencing + * @description Shared-coordinates / setout basis for the project's source model — full IfcMapConversion + * (eastings/northings/height, true-north bearing, scale) + IfcProjectedCRS (EPSG, datums) + LoGeoRef + * level. The survey basis a coordinator needs for federation and BIM-to-field layout. 409 if no IFC. + */ + get: operations["model_georeferencing_projects__pid__models_georeferencing_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Health Scorecard + * @description Composite **Model Health** scorecard — one 0–100 score over the model-quality checks (integrity/ + * hygiene, ISO 19650 KPIs, clash coordination, verified-as-built), each lens linking to its tool. Opens + * the source IFC for the hygiene lens when present; the data/coordination/verified lenses work from the + * records + published index, so it still scores without a parsed model. + */ + get: operations["model_health_scorecard_projects__pid__models_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/norm-valid": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Norm Valid + * @description NORM-VALID · **normative openBIM conformance** — a validation gauntlet in the spirit of the + * buildingSMART validation service: a recognised FILE_SCHEMA + populated header, a single IfcProject + * with units + a geometric context, valid & unique 22-char GlobalIds, OwnerHistory presence (required + * in IFC2X3), and no physical element outside the spatial structure. Each check reports pass/warn/fail; + * `passed` is true when nothing fails. Complements model_qa (authoring quality) and IDS (data). 409 if + * the project has no source IFC. + */ + get: operations["model_norm_valid_projects__pid__models_norm_valid_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/qa": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Qa Report + * @description Model integrity / hygiene scan of the source IFC — duplicate GUIDs, orphaned (no-storey) + * elements, overlapping duplicates, unenclosed spaces and blank names. Complements the LOIN/IDS + * data-quality checks. 409 if the project has no source IFC. + */ + get: operations["model_qa_report_projects__pid__models_qa_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/schema-diag": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Schema Diag + * @description R31-SCHEMA-DIAG · **structural validity against the IFC SCHEMA**, not against a spec. + * + * The neighbouring endpoints all score the model against *rules*: `/models/qa` is hygiene (duplicate + * GlobalIds, orphans, unenclosed spaces), `/models/norm-valid` is the normative gauntlet, IDS is data + * quality. None of them answers *is this file structurally a valid IFC* — an entity type not declared + * in the schema, a `#12345` that resolves to nothing, an ABSTRACT type instantiated directly, a `$` + * in a slot the schema declares mandatory, an attribute list of the wrong length. + * + * That is a different failure class, and it matters because we **write** IFC: a model can be 100% + * IDS-compliant and still be rejected on import by another tool. It is also the class a viewer hides + * — the geometry renders, so the file looks fine until somebody else opens it. + * + * Unlike every sibling here this reads the file as **text and never loads a model**, so it still + * reports on a file `ifcopenshell` cannot open — which is precisely the case a load-based diagnostic + * cannot speak about. Findings carry the instance id, so a fault is locatable in a million-line file. + * + * 409 if the project has no source IFC. + */ + get: operations["model_schema_diag_projects__pid__models_schema_diag_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/warnings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Warnings Feed + * @description WARN-1 · **unified model-warnings feed** — every individual defect the hygiene (model_qa) and + * normative-conformance (norm_valid) lenses surface, flattened into one worst-first punch list (fails + * before warns, each with its offender sample for zoom-to-GUID). Where the model-CI badge says pass/warn/ + * fail, this is the actionable list behind it. 409 if the project has no source IFC. + */ + get: operations["model_warnings_feed_projects__pid__models_warnings_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/{mid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Project Model */ + delete: operations["delete_project_model_projects__pid__models__mid__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/models/{mid}/alignment-fit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Model Alignment Fit + * @description R41-MODEL-ALIGN — propose a yaw correction for a discipline model that arrived rotated. + * + * The report above says the project's models DISAGREE; this says what to do about one of them. It + * fits a yaw-only oriented box to the model's footprint and proposes the rotation that puts the + * building back on its own axes — accepted only when the oriented box saves at least 20% of the + * axis-aligned area. + * + * **That threshold is the feature, not a tuning constant.** A true minimum-area rectangle sat 37° + * off a real building's own walls to buy 14% — arithmetically optimal and visibly wrong, because + * the walls are what a person sees. Refusing the margin is what buys a wall-parallel answer rather + * than merely the smallest one. + * + * **A PROPOSAL, never an edit.** The source IFC is opened read-only and nothing is written. Applying + * an alignment means storing a transform against the model, which is a separate change — so + * `applied` is always false here, and says so rather than leaving a caller to assume. + */ + get: operations["model_alignment_fit_projects__pid__models__mid__alignment_fit_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/module-pins": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Module Pins + * @description Every anchored GC record across pinnable modules — for the 3D viewer overlay. + */ + get: operations["module_pins_projects__pid__module_pins_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/backfill-references": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Backfill References + * @description MOD-BACKFILL — fill empty reference fields from the text twin already on the record. + * + * The field sweep added 54 references BESIDE their text fields rather than converting in place; + * this populates them. Exact match after normalisation, unique matches only, existing values never + * overwritten — and **`apply` defaults to False**, so the default call is a report. + * + * A wrong auto-link is worse than an empty one: it resolves, opens a real record and shows a + * plausible name, so nobody questions it. An empty reference is visibly empty and gets filled by + * the next person to look. Every skip is returned with its reason rather than left as a silent + * shortfall. + * + * `admin` because it writes across every register in the project in one call. + */ + post: operations["backfill_references_projects__pid__modules_backfill_references_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Records + * @description List a module's records, narrowed by state, full text, and any number of per-field filters. + * + * MOD-FILTER: before this, a register could be narrowed by `q` and `workflow_state` and nothing else, + * and sorting happened in the browser over whichever page had been fetched — so "sort by amount" on a + * 500-row register ordered 200 rows and presented it as the answer. Filtering and sorting now happen + * in SQL, before the limit. + */ + get: operations["list_records_projects__pid__modules__key__get"]; + put?: never; + /** Create Record */ + post: operations["create_record_projects__pid__modules__key__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/aggregate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Aggregate Records + * @description R22-REPORT-BUILDER — count/sum/avg/min/max a module's records, grouped by a declared field. + * + * `?group_by=discipline&agg=sum&agg_field=amount`, with the SAME `?f.[.]=` filters the + * list route takes, parsed by the same `_parse_filters` — so a report and the register it came from + * cannot disagree about which rows they describe. + * + * This is the difference between a saved list and a report. Until now the only `group_by` anywhere + * in the module path was hardcoded to `workflow_state`, so "cost by discipline" or "RFIs by month" + * had no expression and the answer was an export into a spreadsheet. + */ + get: operations["aggregate_records_projects__pid__modules__key__aggregate_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/apply-template/{tid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Apply Template + * @description Instantiate a template into the project — one new record per item. + */ + post: operations["apply_template_projects__pid__modules__key__apply_template__tid__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/bcf/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export Module Bcf + * @description Export a module's records as a BCF .bcfzip (coordination issues round-trip with Solibri / ACC / + * BIMcollab). Pinned / element-tied records carry a viewpoint (components + camera). `version` = 2.1 + * (default) or 3.0. + */ + get: operations["export_module_bcf_projects__pid__modules__key__bcf_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/bcf/import": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Module Bcf + * @description Import a BCF .bcfzip from another BIM tool as records in this module (each topic → a record, + * carrying its pinned components + camera). Returns the count created. + */ + post: operations["import_module_bcf_projects__pid__modules__key__bcf_import_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/board": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Module Board + * @description Records grouped by workflow state — kanban board. + */ + get: operations["module_board_projects__pid__modules__key__board_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/bulk": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Bulk Action + * @description Apply transition / assign / delete to many records at once. + */ + post: operations["bulk_action_projects__pid__modules__key__bulk_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/calc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Calc Records + * @description SCHED-CALC (R18): **calculated fields over module records** — deterministic formula columns + * evaluated against each record's field map (+ `ref`/`title`/`workflow_state`), e.g. + * `qty * unit_cost`, `text(ref) + " — " + status`. Body: `{calcs: [{name, expr}, …], state?, q?, + * limit?}`. AST-whitelist evaluation only (no attribute access, no scripting); a bad expression + * 422s; a bad record yields an empty cell. Returns `{columns, rows:[{id, ref, values}]}`. + */ + post: operations["calc_records_projects__pid__modules__key__calc_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/enum/{field}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Add Enum Option + * @description E1 — add a custom option to a module field's select enum (no JSON edit). + */ + post: operations["add_enum_option_projects__pid__modules__key__enum__field__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/export.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Export Csv */ + get: operations["export_csv_projects__pid__modules__key__export_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/import": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Records + * @description Step 2: import the sheet using a column->field mapping (JSON {source_header: field_name}). + * Validates required fields + coerces types per row; one bad row never aborts the batch. + */ + post: operations["import_records_projects__pid__modules__key__import_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/import-template.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Import Template + * @description A header-only CSV of the module's importable fields — fill it in and re-upload to bulk-import. + */ + get: operations["import_template_projects__pid__modules__key__import_template_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/import/preview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Preview + * @description Step 1 of a generic Excel/CSV import: parse the sheet, auto-suggest a column->field mapping, + * coerce a sample, and flag unmapped required fields. No records are created. + */ + post: operations["import_preview_projects__pid__modules__key__import_preview_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/log.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Module Log + * @description Printable register (log) of every record in a module — the RFI log, submittal log, + * change-order log, etc., all from the same engine. + */ + get: operations["module_log_projects__pid__modules__key__log_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/save-template": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Save Template + * @description Capture the project's current records for `key` as a reusable template (data only). + */ + post: operations["save_template_projects__pid__modules__key__save_template_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/views": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Views + * @description My saved views for this module, plus the ones shared with the project. + * + * R22-REPORT-BUILDER item 4 — this used to return `SavedView.user == user` and nothing else, which + * is what made the builder a personal filter rather than a report. A `project`-scoped view is + * readable by anyone who can already read this project's registers (`require_role("viewer")`, and + * every clause below is bounded by `pid`), so sharing shows nobody a row they could not already + * list. + * + * `mine` is returned per row rather than inferred from `owner == me` by the client, because the two + * can disagree the moment a display name is not the identity key — and the UI uses it to decide + * whether to offer Delete, which must match what the server will actually allow. + */ + get: operations["list_views_projects__pid__modules__key__views_get"]; + put?: never; + /** + * Save View + * @description Create or replace one of MY saved views for this module. + * + * R22-REPORT-BUILDER item 3 — `config` is VALIDATED and normalised before it is stored. It used to + * be written verbatim, so a saved view was whatever a client happened to POST: a schema change + * broke views silently, a typo in a key was indistinguishable from a feature, and nothing could + * migrate what nothing had defined. `validate_view_config` resolves every field name through + * `_resolve_field` and every operator through `FILTER_OPS` — the same two the list route uses, + * because a second validator would be a second answer to "what is a field". + */ + post: operations["save_view_projects__pid__modules__key__views_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/views/{vid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Delete View + * @description Delete one of MY saved views. `deleted` reports what actually happened. + * + * It previously returned `bool(v)` — truthy whenever the row EXISTED, including when it belonged to + * somebody else and was therefore not deleted. A caller asking to remove another user's view was + * told "deleted: true" and the view stayed, which is the worst kind of answer: confidently wrong, + * and it trains a UI to stop re-reading. Nothing asserted it because no test covered this route. + * + * Not-mine and doesn't-exist both return `deleted: false` rather than different statuses — + * distinguishing them would confirm the existence of another user's view id to someone who cannot + * read it. + * + * SCOPED TO `pid` AND `key`, AND THE SECOND HALF WAS MISSED THE FIRST TIME. The `pid` check landed + * on its own, copied from `mark_view_seen` — which does not check `key` either, so copying it + * inherited the same gap. Both segments of the path addressed the row and only one of them was + * verified: a view saved under `rfi` really was deletable through `/modules/submittal/views/{vid}`, + * and `mark_view_seen` really would clear the alert on a view belonging to another module. + * + * Same user, same project, so nothing crosses a privilege boundary — this is a correctness fix, not + * a second security one. What it costs is smaller and duller: the module segment of the URL meant + * nothing, so any id typo that happened to name a real view of yours resolved instead of 404ing. + */ + delete: operations["delete_view_projects__pid__modules__key__views__vid__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/views/{vid}/seen": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Mark View Seen + * @description Mark a saved view as seen **by this user** now — clears their 'new' alert count. + * + * Per-viewer since v0.3.1109. Marking a shared view seen used to require being its author, so the + * only person who could clear an alert was the one person the feed showed it to. Now anyone who may + * READ the view may record their own visit, and doing so touches nobody else's count. + * + * **`viewer`, matching the alert feed that shows the view.** This was `reviewer` while + * `/views/alerts` was `viewer`, a mismatch that stayed unreachable as long as a viewer's feed only + * ever held their own views. Per-viewer alerts made it reachable: a viewer-role member would see + * shared views in their feed and get a 403 trying to clear them, leaving a permanent "N new" badge. + * The row written here is a private note about *this* user's own reading — it grants no access and + * changes nobody else's number — so it belongs at the level that can see the feed. + */ + post: operations["mark_view_seen_projects__pid__modules__key__views__vid__seen_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Record */ + get: operations["get_record_projects__pid__modules__key___rid__get"]; + put?: never; + post?: never; + /** + * Delete Record + * @description Delete a record (editor+). Removes its activity/comments too. + */ + delete: operations["delete_record_projects__pid__modules__key___rid__delete"]; + options?: never; + head?: never; + /** + * Update Record + * @description Partial-update a record. Pass ?expected_modified_at= to opt into the + * optimistic lock: a concurrent edit returns 409 (with the current modified_at) instead of a silent + * overwrite. + */ + patch: operations["update_record_projects__pid__modules__key___rid__patch"]; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/assign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Assign Record + * @description Set (or clear) the record's assignee — drives the cross-module work queue. + */ + post: operations["assign_record_projects__pid__modules__key___rid__assign_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/attachments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Attachment + * @description Attach a file to a record (stored in object storage / MinIO). + */ + post: operations["upload_attachment_projects__pid__modules__key___rid__attachments_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/attachments/bulk": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Attachments Bulk + * @description Attach **many** files at once — the field reality (a super dumps a batch of site photos rather + * than uploading them one by one). Each is stored like a single upload; returns all created + a count. + */ + post: operations["upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/comments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Add Comment */ + post: operations["add_comment_projects__pid__modules__key___rid__comments_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/comments/{cid}/promote": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Promote Comment + * @description R22-ENTITLEMENT ⑤ — promote a review comment into an RFI (or punch item) somebody owns. + * + * An agency's review comment is the one input that must leave the thread: it has to be assigned, + * tracked and closed. Mints a Topic carrying the comment text, the source record's ref and its + * `element_guids`, and writes a back-link so a second promote 409s rather than minting a duplicate. + * Mirrors `POST /projects/{pid}/drawings/markup/{mid}/promote` rather than inventing a second idiom. + */ + post: operations["promote_comment_projects__pid__modules__key___rid__comments__cid__promote_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/distribution": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Record Distribution + * @description Resolve a record's distribution (CC) field against the contact directory → recipients + emails. + */ + get: operations["record_distribution_projects__pid__modules__key___rid__distribution_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/elements": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Tag Elements + * @description Tie model elements (IFC GlobalIds) to a record (mode: add | remove | set). For a schedule + * activity this hard-ties the exact elements it builds, so the 4D scrub is precise (not trade-based). + */ + post: operations["tag_elements_projects__pid__modules__key___rid__elements_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/link": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Link Record */ + post: operations["link_record_projects__pid__modules__key___rid__link_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Record Pdf */ + get: operations["record_pdf_projects__pid__modules__key___rid__pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/related": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Related Records + * @description Outgoing references + incoming records that point at this one. + */ + get: operations["related_records_projects__pid__modules__key___rid__related_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/revise": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Revise Record + * @description Create a tracked revision of a record (revisable modules only); re-opens the workflow. + */ + post: operations["revise_record_projects__pid__modules__key___rid__revise_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/modules/{key}/{rid}/transition": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Transition */ + post: operations["transition_projects__pid__modules__key___rid__transition_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/my-work": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * My Work + * @description Cross-module work queue for the current user (assigned + ball-in-court). + */ + get: operations["my_work_projects__pid__my_work_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/naming/audit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Naming Audit + * @description Audit the CDE containers + drawing register for naming-convention compliance. + */ + get: operations["naming_audit_projects__pid__naming_audit_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/naming/conventions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Naming Conventions + * @description The document/container filename + drawing sheet-ID naming conventions the validator enforces. + */ + get: operations["naming_conventions_projects__pid__naming_conventions_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/naming/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Naming Validate + * @description Validate a single name against the convention. kind = container | sheet. + */ + get: operations["naming_validate_projects__pid__naming_validate_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Notice Register + * @description The notice register: every triggering event in the field record against every notice provision + * of the adopted contract family, expired first. + * + * With no family adopted this returns an empty register and says why rather than picking one — a + * deadline computed under the wrong contract looks exactly like a deadline computed under the right + * one. + */ + get: operations["notice_register_projects__pid__notices_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notices/clauses": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Notice Clauses + * @description The notice-provision library: contract families, their clauses, periods and citations. + * + * These are the standard provisions of the unamended forms. They exist so a project can adopt one + * and see what it is being measured against — not as a statement of what any particular contract + * says. + */ + get: operations["notice_clauses_projects__pid__notices_clauses_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notices/draft": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Notice Draft + * @description Draft the notice letter for one register item. + * + * Stateless — post back the item you were given, the same shape the register returns. It quotes the + * clause rather than paraphrasing it, leaves the signature block unfilled, and when the item's date + * is a *reported* date rather than a recorded occurrence it says so inside the letter, because the + * date in a notice is the part that gets tested. + */ + post: operations["notice_draft_projects__pid__notices_draft_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Notifications + * @description Recent activity relevant to the caller (assigned / ball-in-court), newest first. + */ + get: operations["notifications_projects__pid__notifications_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notifications/digest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Send Digest + * @description Send each member with open items a work-queue digest email. No-op-but-logged per + * recipient when SMTP is unconfigured (status 'disabled'); members without an email are + * skipped. Returns a per-recipient result summary. + */ + post: operations["send_digest_projects__pid__notifications_digest_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notifications/digest/preview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Digest Preview + * @description Preview the per-member digests (no send) — also reports whether SMTP is configured. + */ + get: operations["digest_preview_projects__pid__notifications_digest_preview_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/notifications/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Notifications Stream + * @description Server-sent events: pushes the notification feed to the client and re-pushes when + * the relevant activity count changes (polled server-side every few seconds). Uses a + * fresh DB session per poll since the generator outlives the request scope. + */ + get: operations["notifications_stream_projects__pid__notifications_stream_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/openbim/quality": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Openbim Quality Scan + * @description openBIM quality of the loaded model: LOIN per element, IFC export health, bSDD alignment, and + * (when ?use_case= names an IDS use case) IDS rule-compliance scoring. Needs a loaded model. + */ + get: operations["openbim_quality_scan_projects__pid__openbim_quality_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/opendata/permits": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query Permits + * @description Nearby / matching municipal filings — owner, architect, GC, units, cost, status — for + * acquisition intel and to seed the project's own permit log. + */ + get: operations["query_permits_projects__pid__opendata_permits_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/opendata/permits.geojson": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Permits Geojson + * @description The same filings as a GeoJSON FeatureCollection for the viewer's GIS overlay (points only). + */ + get: operations["permits_geojson_projects__pid__opendata_permits_geojson_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/opendata/permits/import": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Permits + * @description Pull a city's filings for the site and create `permit` records on the GC side, source-tagged so + * they don't duplicate on re-import. Body: {city, lat?, lon?, radius?, address?, q?, max?}. + */ + post: operations["import_permits_projects__pid__opendata_permits_import_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/payapp/lien-exposure": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Lien Exposure + * @description Per-vendor billed / paid / waiver coverage and lien exposure (money paid without an + * unconditional waiver on file), worst first, with a project rollup. + */ + get: operations["lien_exposure_projects__pid__payapp_lien_exposure_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/payroll": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Weekly Payroll + * @description Weekly certified-payroll summary (per worker hours, OT, rate, gross) for the week ending. + */ + get: operations["weekly_payroll_projects__pid__payroll_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/payroll/wh347.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Wh347 Pdf + * @description The WH-347 certified-payroll PDF for the week. + */ + get: operations["wh347_pdf_projects__pid__payroll_wh347_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/permit/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Permit Readiness + * @description PERMIT-CHECK: permit-submission readiness — the intake report a permit tech would produce. + * Composes the computed egress check (rejection-grade), the approvability pre-flight, the code-analysis + * summary (jurisdiction edition), and the drawing register's required sheet series into one checklist + + * ranked deficiency list with a READY / NOT-READY verdict. Pre-check assist; the AHJ rules. + */ + get: operations["permit_readiness_projects__pid__permit_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/permits/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Permits Timeline + * @description PERMIT-TIMELINE — days-to-issue analytics (p25 / median / p75 by jurisdiction × type × valuation band) + * + a seasonal profile over the cached permit records, and — with a `target` — the pro-forma estimate + * (median = expected entitlement duration, p75 = the conservative carry). Body: `{permits?, target?: + * {jurisdiction, type, valuation}}`; falls back to the project's `permit` records when `permits` is omitted. + * 409 without any permit data. + */ + post: operations["permits_timeline_projects__pid__permits_timeline_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/phasing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Phasing Summary + * @description W10-8: element phase/status distribution (new · existing · demolish · temporary · unset) over the + * model. Tag elements with the `set_phase` recipe (POST /edit); colour the model by + * `Massing_Phasing.Status` via the existing colour-by-property view. + */ + get: operations["phasing_summary_projects__pid__phasing_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pins": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Pins */ + get: operations["list_pins_projects__pid__pins_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pins/all": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List All Pins + * @description Every pin, however it was attached — the union the sheet already draws. + * + * `GET /pins` above returns only Topics with an explicit anchor, which is what the viewer has always + * consumed. It misses the case a user is most likely to create: an RFI raised in the register and + * tied to a wall. That record has the workflow and reaches `closed`; it just never had a position, + * so nothing showed it. Here it does. + * + * Positions for element-tied issues are derived from the model, so a pin follows its element instead + * of pointing at where the element used to be. `unlocated` counts what could not be placed, because + * a list that silently returns fewer pins than exist is the failure this endpoint was added to end. + */ + get: operations["list_all_pins_projects__pid__pins_all_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/alignment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Precon Alignment + * @description Calibrate-style preconstruction alignment — estimate-vs-budget, VE coverage of any gap, + * open decisions/assumptions — as per-domain RAG + an alignment score. + */ + get: operations["precon_alignment_projects__pid__precon_alignment_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/assumptions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Precon Assumptions + * @description Assumptions & clarifications register — by status/category + open allowance exposure. + */ + get: operations["precon_assumptions_projects__pid__precon_assumptions_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/decisions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Precon Decisions + * @description Preconstruction decision log — by status/alignment + open cost & schedule exposure. + */ + get: operations["precon_decisions_projects__pid__precon_decisions_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/estimate-continuity": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Precon Estimate Continuity + * @description Preconstruction estimate continuity — per-milestone totals + $/SF, milestone-to-milestone cost + * drift, and the gap vs the project budget/GMP (pass ?budget= to override the GMP baseline). + */ + get: operations["precon_estimate_continuity_projects__pid__precon_estimate_continuity_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/snapshot": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Precon Snapshot + * @description One-click: price the current model (IFC takeoff × unit rates) and save it as an estimate set + * tagged with the given design milestone. 409 if the project has no source IFC yet. + */ + post: operations["precon_snapshot_projects__pid__precon_snapshot_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/precon/ve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Precon Ve + * @description Value-engineering cycle — proposed/accepted/rejected savings; pass ?target= for gap-to-close. + */ + get: operations["precon_ve_projects__pid__precon_ve_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/prefab/kits": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Prefab Register + * @description Every prefab kit on the project, worst first, each with its scope, BOM, scope drift and the + * specific things blocking it. + * + * Kits sort by the severity of their worst blocker rather than by date: a kit whose released scope + * has **drifted** from what the selector now matches outranks one that is merely late, because a + * late kit is a known problem and a drifted one is an unknown wrong one. + */ + get: operations["prefab_register_projects__pid__prefab_kits_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/prefab/kits/{rid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Prefab Kit Detail + * @description One kit, fully resolved. + */ + get: operations["prefab_kit_detail_projects__pid__prefab_kits__rid__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/prefab/kits/{rid}/freeze": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Prefab Freeze + * @description Freeze the kit's scope: resolve the selector once and write the GlobalId list onto the record. + * + * This is what makes a released kit a document rather than a query. From here the shop builds the + * frozen list; the selector is only the record of how that list was arrived at, and any later + * divergence between the two is reported by the register instead of silently changing what is being + * fabricated. + * + * Refuses to freeze an empty, erroring or truncated result — a released kit with nothing frozen is + * precisely the failure this whole mechanism exists to prevent, and it would be indistinguishable + * from a correctly released one on every screen. + */ + post: operations["prefab_freeze_projects__pid__prefab_kits__rid__freeze_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/preflight": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Preflight Gate + * @description Pre-flight **issuance gate** — one PASS/HOLD verdict + a pre-issue checklist composing model health + * (hygiene · clash · code-readiness · verified-as-built), **discipline-classification completeness**, + * **keynote/spec completeness**, **drawing-set QA**, the **pinned-IDS validation**, and **open + * high-priority issues** (a hard blocker). Each check deep-links to its tool. Run it before issuing — + * `POST /drawing-set/issue` runs it automatically and stamps the verdict on the issuance. + */ + get: operations["preflight_gate_projects__pid__preflight_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/prequal/coi-expiry": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Coi Expiry + * @description Certificates of insurance expired or expiring within `soon_days`. + */ + get: operations["coi_expiry_projects__pid__prequal_coi_expiry_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/prequal/scores": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Prequal Scores + * @description Q-score (0-100) + risk band + factor breakdown for every prequalified sub, worst first. + * Pass `project_size` to weight financial/experience factors against this job's value. + */ + get: operations["prequal_scores_projects__pid__prequal_scores_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/presence": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Presence Roster + * @description Other users currently viewing this project (heartbeat within the TTL). + */ + get: operations["presence_roster_projects__pid__presence_get"]; + put?: never; + /** + * Heartbeat + * @description Heartbeat presence (optionally sharing the current camera viewpoint) and get the live + * roster of other users viewing this project. + */ + post: operations["heartbeat_projects__pid__presence_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pricing/reconcile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Reconcile + * @description Price the project's takeoff (production_quantity) against the unit price book / live feed, with + * per-line variance vs any estimated unit price. + */ + get: operations["reconcile_projects__pid__pricing_reconcile_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/buyout-packages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Buyout Packages + * @description PROCURE-LEVEL: group QTO line items into buyout packages (each with an RFQ scope to send out). + * Body: {qto_lines:[{item, qty, unit, trade?/csi?/material_class?, unit_price?, cost?}], by?}. + */ + post: operations["buyout_packages_projects__pid__procurement_buyout_packages_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/buyout-schedule": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Buyout Schedule + * @description BUYOUT-SCHED — a time-phased buyout schedule joining QTO lines to their installing schedule activity + * (by activity id / cost code / trade) → the **last-responsible-order date** (= install start − lead time), + * sorted soonest-order first; with `as_of`, each line is overdue / urgent / upcoming / ok. Body: + * `{qto_lines, activities, lead_times?, as_of?, default_lead_days?}`. + */ + post: operations["buyout_schedule_projects__pid__procurement_buyout_schedule_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/compliance-feed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Procurement Compliance Feed + * @description Outbound nudge list — vendors with an expiring/expired/missing COI or an unapproved prequal, + * before it blocks a bid invitation or a pay application. + */ + get: operations["procurement_compliance_feed_projects__pid__procurement_compliance_feed_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/gate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Procurement Gate Check + * @description Compliance gate for a vendor: can they bid (approved prequal + active insurance) and can they + * bill (executed subcontract + active insurance), with the specific blockers. + */ + get: operations["procurement_gate_check_projects__pid__procurement_gate_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/level": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Level + * @description PROCURE-LEVEL: score returned quotes for one buyout package against its RFQ scope on a normalized + * basis — price (extended over scope qty), coverage completeness, and lead time → a composite [0,1] score + * ranking the suppliers, with each one's scope gaps. Body: {scope:[{item, qty, unit}], + * quotes:[{supplier, lead_time_days?, lines:[{item, qty, unit, unit_price}]}], weights?}. + */ + post: operations["level_projects__pid__procurement_level_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/level-quotes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Level Quotes + * @description Level competing material quotes into an apples-to-apples grid + low price per line + best supplier. + * Body: {quotes:[{supplier, lines:[{item, qty, unit, unit_price}]}]}. With `record=true` (editor), + * every priced line is also written to the price-observation ledger (source="quote"). + */ + post: operations["level_quotes_projects__pid__procurement_level_quotes_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/material-request/suggest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Material Request Suggest + * @description PROC-LOOP: turn a model selection into per-class material-request suggestions from the QTO + * takeoff (volume → m3, area → m2, else count). Body: `{q: ""}` and/or + * `{guids: [...]}`; omit both to suggest over the whole model. `create=true` also creates + * `material_request` records (state `requested`) keyed to the GUIDs. + */ + post: operations["material_request_suggest_projects__pid__procurement_material_request_suggest_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/packages/save": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Save Buyout Packages + * @description PROCURE-LEVEL persistence: group the QTO lines into buyout packages AND persist each one as a + * **Buyout Packages** (`procurement_package`) record — name/trade/CSI, estimated cost, line count and + * the RFQ scope (JSON) — so buyout stops being a one-shot payload and becomes trackable through the + * draft → rfq_sent → quotes_in → awarded workflow. Body: `{qto_lines, by?, rfq_due?}`. + */ + post: operations["save_buyout_packages_projects__pid__procurement_packages_save_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/packages/{rid}/send-rfq": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Send Rfq + * @description PROCURE-LEVEL send-RFQ bridge: mint a **Bid Solicitation** (ITB) record from a stored buyout + * package (name/trade/due carried over) and advance the package's workflow draft → rfq_sent — the + * procurement side and the bidding side stay one thread. Body: `{due_date?}`. + */ + post: operations["send_rfq_projects__pid__procurement_packages__rid__send_rfq_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/price-history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Price History + * @description PROC-LOOP: the price-observation ledger per material — min/median/avg/max, the latest + * observation, vendors seen, latest-vs-median drift, and a spark series. + */ + get: operations["price_history_projects__pid__procurement_price_history_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/procurement/three-way-match": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Three Way Match + * @description Reconcile each PO (commitment) against its deliveries and invoices — flags over-billing, + * pay-before-receipt, and un-invoiced deliveries. + */ + get: operations["three_way_match_projects__pid__procurement_three_way_match_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/productivity/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Productivity Summary + * @description Field labor productivity — units installed per man-hour per entry, rolled up by trade. + */ + get: operations["productivity_summary_projects__pid__productivity_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/income-basis": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Income Basis + * @description PF-INCOME-BASIS — the operating income a proforma should use, and where it came from. + * + * A proforma's `potential_rent_annual` is one blended number for the whole asset. This derives it + * from the property's OWN lease records instead — in-place base rent, expense recoveries and the + * concession load, each kept as a separate line — and, when the deal also declares a figure, + * reports **both** with the gap between them. + * + * That disagreement is the point. A deal underwritten at a figure its own rent roll contradicts is + * not missing a number; it is reporting one whose provenance nobody can see. Pass `declared_annual` + * to compare against the deal's stated income. + * + * An empty rent roll returns `unavailable`, never an income of zero: a property with no lease + * records has UNKNOWN income, and zero is the one value that reads as a measurement. + */ + get: operations["project_income_basis_projects__pid__proforma_income_basis_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/live": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Proforma Live + * @description PROFORMA-LIVE — the finance numbers that follow the model as you author: the current model + * version's **takeoff-priced construction cost** (cached per published version — cheap to poll after + * a reload), slab-derived **GFA**, cost/m², and the **delta vs the developer budget's hard cost**. + * The client refreshes this whenever the collab stream reports a new model version. + */ + get: operations["proforma_live_projects__pid__proforma_live_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/model-metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Proforma Model Metrics + * @description Metrics from the project's source IFC, so the proforma can underwrite against the real + * model (areas → hard cost / rent, etc.) instead of hand-keyed numbers. 409 if no source IFC. + */ + get: operations["proforma_model_metrics_projects__pid__proforma_model_metrics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/renovation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Project Renovation + * @description PF-RENOVATION — a value-add renovation programme, unit by unit, over time. + * + * Body: `{unit_types: [{type, count, current_rent_monthly, renovated_rent_monthly, + * renovation_cost}], assumptions: {units_per_month, downtime_months_per_unit}, months?}`. + * + * A unit earns its in-place rent until it starts, **nothing** while it is being renovated, and the + * renovated rent only from the month it comes back online. Applying the premium from day one is the + * standard way a value-add deal is overstated — it produces a smooth, plausible income curve that is + * simply too high, too early, for the whole hold. + * + * `units_per_month` and `downtime_months_per_unit` are required and NOT defaulted: without them the + * model either renovates everything instantly or costs only its capex, and either result is + * indistinguishable from a correct one. Stating zero downtime is allowed — it is then a choice. + */ + post: operations["project_renovation_projects__pid__proforma_renovation_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/rollover": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Rollover + * @description PF-ROLLOVER — what the property's lease expiries actually cost over the hold. + * + * A rollover is not a date on a calendar: it is downtime plus tenant improvements plus a leasing + * commission, weighted by whether the tenant renews. Without it a proforma carries in-place rent + * forever, which is optimistic in three directions at once and compounds on every turn. + * + * **`renewal_probability` and `downtime_months` are required and are NOT defaulted.** Assuming every + * tenant renews, or that a vacated suite re-lets instantly, removes the exact risk being priced — + * and the result is indistinguishable from a deal that genuinely has neither. Omit them and this + * returns `assumptions_incomplete` naming what is missing and what defaulting would have hidden. + * + * Downtime is charged to the non-renewal branch only, because a renewing tenant does not vacate. + */ + get: operations["project_rollover_projects__pid__proforma_rollover_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/proforma/solve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Solve For Project + * @description Same solve, but the underwriting guardrails also validate the **exit cap against the project's + * own sale comps** (U3) — flags a going-out cap the market doesn't support. Falls back to the plain + * band checks when the project has no `comparable` records with cap rates. + */ + post: operations["solve_for_project_projects__pid__proforma_solve_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/program/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Program Summary + * @description Concept space-program rollup + adjacency graph: total/net/gross area, mix by use, the node/edge + * graph, unmet adjacency preferences, and the massing hints (gross area + use mix) it feeds. + */ + get: operations["program_summary_projects__pid__program_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/progress/actuals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Project Prod Actuals + * @description PROD-ACTUALS: the productivity actuals loop — roll installed-quantity actuals up per activity into + * the **installed rate** (qty ÷ productive/cycle hours) + **crew utilization** (productive ÷ productive+ + * idle), compared to the planned rate → ahead / on-track / behind + a remaining-hours projection at the + * current rate. With an empty `actuals` list, the project's stored **Productivity Actuals** module + * records (`progress_actual` — the field crew's persisted log) are analyzed instead, so the loop runs + * off the record of authority, not a one-shot payload. + */ + post: operations["project_prod_actuals_projects__pid__progress_actuals_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/progress/capture-diff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Progress Capture Diff + * @description SCAN-4D — the diff between two capture timestamps: newly installed per class/level, elements that + * *disappeared* (re-scan/rework flag), the progress delta and a daily rate. Body: `{installed_t1, + * installed_t2, t1?, t2?, elements?}`; elements derive from the model's property index when omitted. + */ + post: operations["progress_capture_diff_projects__pid__progress_capture_diff_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/progress/reconciliation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Progress Reconciliation + * @description R22-PRODUCTION: field-installed quantity reconciled against the **model** takeoff, per cost code. + * + * The sibling route above compares an installed *rate* against a planned rate **the caller supplies in + * the request body** — so "are we ahead or behind" was answered against a typed-in number, never + * against the model. This one sources the planned side from `qto.takeoff()` itself. + * + * It reads `production_quantity`, not the `progress_actual` module the sibling uses, because + * `progress_actual` has **no `cost_code` field** — the model's join key exists on only one of the two + * field modules, which is why this reconciliation was never possible rather than merely never wired. + * + * `force_geometry` meshes elements lacking declared quantities: more complete and materially slower, + * so it is opt-in and off by default. + */ + get: operations["project_progress_reconciliation_projects__pid__progress_reconciliation_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/progress/rollup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Progress Rollup + * @description PROGRESS-ROLLUP — percent-complete from as-built element presence: given the design element set and + * the installed GUIDs, roll up % complete by IFC class · discipline · level · overall, **by count and by + * value**. Body: `{installed_guids, elements?}`; when `elements` is omitted the design set is derived from + * the model's property index. + */ + post: operations["progress_rollup_projects__pid__progress_rollup_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/project-package.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Package + * @description The **shareable project package** — one PDF a GC or architect hands to a client: a cover, a visual + * overview (plan · section · elevation), the drawing set, and a cost & feasibility summary (model-takeoff + * estimate by discipline + the developer budget's capital stack). Needs a source IFC. + */ + get: operations["project_package_projects__pid__project_package_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/project-package/contents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Package Contents + * @description What the shareable project package will contain for this project (model / budget availability). + */ + get: operations["project_package_contents_projects__pid__project_package_contents_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/properties/index": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Index + * @description Upload the props.json produced by the data service (`aec_data.cli index`). Size-gated — + * json.loads of an unbounded upload would parse an arbitrarily large body entirely in RAM. + */ + post: operations["upload_index_projects__pid__properties_index_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/properties/meta": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Meta */ + get: operations["meta_projects__pid__properties_meta_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/property": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Property + * @description Property & tax assumptions + computed summary (totals, per-SF ratios, proforma deltas). + */ + get: operations["get_property_projects__pid__property_get"]; + /** + * Put Property + * @description Save property & tax assumptions; returns the recomputed summary. + */ + put: operations["put_property_projects__pid__property_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/propmap/detect": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Propmap Detect + * @description W9-1: every (pset, property) actually present on the model's elements — the 'source' side a + * user normalizes FROM — with occurrence counts + a sample value. Feeds the property-mapping UI. + */ + get: operations["propmap_detect_projects__pid__propmap_detect_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/propmap/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Propmap Plan + * @description W9-1 dry-run: how many elements each remap rule would touch, with before/after samples. No + * mutation — apply for real via POST /edit {recipe: "map_properties", params: {rules}}. + */ + post: operations["propmap_plan_projects__pid__propmap_plan_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/provenance/admissibility": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Admissibility + * @description R22-PROVENANCE — the admissibility verdict for a project, from what this system PERSISTS. + * + * The stateless sibling takes all three legs in a request body. This one gathers them, and the + * honest answer is that **only one of the three is gatherable**: + * + * * **assumptions** — real, from the project's own scenarios (most recent unless `scenario_id`); + * * **estimate** — `not_captured`. The `estimate` register stores line_items as + * code/description/qty/unit/unit_cost/amount and captures no `source`, `quote_ref` or + * `basis_date`, which are precisely the fields a basis-of-estimate ledger checks; + * * **answers** — `not_captured`. Agent answers are never persisted; `cited_answer` is an + * in-flight contract and no store keeps the claims or citations after the response returns. + * + * **`not_captured` is deliberately distinct from `no_data`.** "You have not filled this in" and + * "this system has nowhere to put it" send a reader to completely different places — one fills a + * field, the other changes a schema — and a verdict that conflates them wastes the time of whoever + * acts on it. Each un-gatherable leg names the change that would make it gatherable. + * + * Consequently a project verdict cannot read `admissible` today, and it says so rather than + * quietly scoring only the leg that happens to work. + */ + get: operations["project_admissibility_projects__pid__provenance_admissibility_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/publish": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Publish + * @description Re-run the pipeline on the current source IFC, off the request thread. Returns immediately; + * poll GET publish/status for completion. + * + * `reconvert=false` rebuilds the properties index and the version snapshot but SKIPS the fragment + * convert — so every GUID-keyed reader (selection, LOD, QTO, pins) is correct while the rendered + * geometry stays at the previous publish. That is a deliberately PARTIAL state and callers must + * treat it as one: the model on screen is behind the model in the index. It exists because the two + * halves have genuinely different costs, measured on a 52 MB source: reindex 6.6s, convert 10.2s. + * + * The flag was accepted and ignored until v0.3.906 — `run_publish` called `_publish(p)` and took + * the default. + */ + post: operations["publish_projects__pid__publish_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/publish/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Publish Status + * @description Poll the async publish job: idle | running | done | error (+ detail). + */ + get: operations["publish_status_projects__pid__publish_status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pull-plan/board": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Pull Plan Board + * @description The Last Planner phase pull-plan board: trade swimlanes × weeks, the hand-off sequence, the + * make-ready constraint log, and readiness / commitment / PPC. Every stakeholder edits the + * `pull_plan_task` records; pass ?milestone= to focus one phase. + */ + get: operations["pull_plan_board_projects__pid__pull_plan_board_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pull-plan/board.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Pull Plan Pdf + * @description The pull-plan board as a printable PDF (trade × week matrix + constraint log + PPC). + */ + get: operations["pull_plan_pdf_projects__pid__pull_plan_board_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pull-plan/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Pull Plan Metrics + * @description Last Planner reliability metrics beyond PPC: Tasks-Made-Ready %, make-ready runway, + * perfect-handoff %, PPC trend by week, and the variance-reason Pareto — the learning-loop signals + * a pull-planning team improves week over week. + */ + get: operations["pull_plan_metrics_projects__pid__pull_plan_metrics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pull-plan/stream": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Pull Plan Stream + * @description Server-sent events for the collaborative pull board: polls a cheap board signature (row count + + * latest modified_at) server-side every few seconds and pushes it when it changes, so every trade's + * board live-refreshes the moment anyone edits a sticky note. Uses a fresh DB session per poll since + * the generator outlives the request scope (mirrors the notifications stream). + */ + get: operations["pull_plan_stream_projects__pid__pull_plan_stream_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/pulse": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Pulse + * @description PROJECT PULSE — five optional cards as `PulseInput`. Mapping lives here so the + * home shell does not re-derive `score` / `variancePct` / `floatDays` from engine + * shapes that do not use those names. Fail-open per card; 404 only if the project + * itself is missing. + */ + get: operations["project_pulse_projects__pid__pulse_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/px-summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Px Summary + * @description The project-executive health view: on-schedule (SPI, % complete, critical path, lookahead, + * milestones) next to on-budget (GMP, EAC, variance-at-completion, buyout, cash flow), with an + * overall status. The single 'are we on schedule and on budget' answer. + */ + get: operations["px_summary_projects__pid__px_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/qto/by-floor": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Qto By Floor + * @description Quantity takeoff + cost broken down by floor (storey) and discipline (IFC class) — quantities + * and dollars mapped to where they sit in the building, with a per-floor total + a discipline + * roll-up. 409 if no source IFC. + */ + get: operations["qto_by_floor_projects__pid__qto_by_floor_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/quality/chain": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Quality Chain Route + * @description The quality evidence chain **per element** — ITP hold/witness points, NCRs and inspections + * resolved against the GlobalIds their records are attached to. + * + * Each element is `clear`, `outstanding`, or `unrecorded`. **`unrecorded` is not a pass**: an + * element nobody inspected is the absence of evidence, not evidence of conformance. `any_attached` + * and `coverage_pct` distinguish "this building has a problem" from "nobody has linked records yet". + * + * `guids` is an optional comma-separated list; omitted, it reports the elements quality records + * actually name rather than every GlobalId in the model. + */ + get: operations["quality_chain_route_projects__pid__quality_chain_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/quality/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Quality Summary + * @description Quality dashboard — inspection pass-rate KPIs, NCR disposition/close loop, deficiency ball-in-court. + */ + get: operations["quality_summary_projects__pid__quality_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/quality/turnover-readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Quality Turnover Readiness + * @description Whether the quality evidence supports closeout — the seam the G704 turnover package never had. + * + * An element with no quality record counts AGAINST readiness, not for it: otherwise a project + * nobody inspected reads as perfectly clean. + */ + get: operations["quality_turnover_readiness_projects__pid__quality_turnover_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/quantities/disciplines": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Discipline Quantities + * @description Discipline quantity roll-up — reinforcement tonnage, MEP linear runs (duct/pipe/cable) + fitting + * counts, and structural element volume, from the IFC (Qto psets with a geometry fallback). + */ + get: operations["discipline_quantities_projects__pid__quantities_disciplines_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Query Elements + * @description W11: power selection via the IfcOpenShell **selector DSL** — e.g. `IfcWall`, + * `IfcWall, IfcDoor`, `IfcSpace, Pset_SpaceCommon.IsExternal=TRUE`, `IfcWall, material=concrete`. + * Returns the matched elements (guid/name/class/storey). Feeds selection sets, bulk edits, schedule + * scoping, and rule-driven detail/spec attachment. 400 on invalid query syntax. + */ + get: operations["query_elements_projects__pid__query_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/raise-plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Raise Plan To Bim + * @description 2D -> BIM raise: turn an uploaded DXF floor plan into a real IFC4 model (walls extruded from + * the line-work, IfcSpaces from closed room polygons). `preview=true` just parses and returns the + * detected wall/room counts without writing anything. Otherwise the raised IFC is registered as a + * '2D Raise' discipline model (usable in the viewer + federated clash). 400 on an unreadable DXF. + */ + post: operations["raise_plan_to_bim_projects__pid__raise_plan_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rebar/bbs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rebar Bbs + * @description REBAR-RULES: the bar bending schedule — every authored IfcReinforcingBar grouped into marks + * (size · shape · cut length) with unit mass and total tonnage (the fabricator/5D quantity). + */ + get: operations["rebar_bbs_projects__pid__rebar_bbs_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rebar/bbs.csv": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rebar Bbs Csv + * @description The bar bending schedule as CSV (fabricator import). + */ + get: operations["rebar_bbs_csv_projects__pid__rebar_bbs_csv_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rebar/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rebar Check + * @description REBAR-RULES: verify the authored cage on a column against the ACI 318 envelope — + * longitudinal bar count + tie spacing min(16·d_bar, 48·d_tie, least dimension). A bare column + * is a finding, not an error; a non-column GUID is a 422. + */ + get: operations["rebar_check_projects__pid__rebar_check_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/recipes/diff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Recipe Log Diff + * @description Compare two log entries by their index in the export (oldest-first) order. + * + * Only meaningful for two runs of the same recipe, and that is checked rather than assumed — the + * parameter names of `add_wall` and `set_pset` are not the same vocabulary, and a key-by-key + * comparison across them would report every field as changed and mean nothing. + */ + get: operations["recipe_log_diff_projects__pid__recipes_diff_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/recipes/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Recipe Log Export + * @description The whole log as a portable document, **oldest first** — the provenance artifact. + * + * Oldest-first because it is meant to be read as a sequence of operations, and because that is the + * order a replay needs. `/recipes/log` is newest-first because that is the order a person reads a + * history in. Both orders are stated in their payloads rather than left to be discovered. + */ + get: operations["recipe_log_export_projects__pid__recipes_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/recipes/log": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Recipe Log Read + * @description The edit-recipe log, newest first — recipe, parameters, actor, time, and the source files it + * ran between. + * + * The parameters are the part that did not exist before: the audit log records an edit's *outputs* + * and the undo stack records *file paths*, so nothing kept what an edit was actually asked to do. + * Without the inputs there is no replay, no diff and no provenance trail worth the name. + */ + get: operations["recipe_log_read_projects__pid__recipes_log_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/recipes/replay-plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Recipe Replay Plan + * @description Ordered `{recipe, params}` steps to re-run the logged edits against another model. + * + * Returns a **plan**, and does not execute it. Re-running a sequence of authoring edits is exactly + * the kind of thing that should carry one author and one audit row, and a log that could re-run + * itself would carry neither — apply the steps through `POST /projects/{pid}/edit/batch`. + * + * Refuses if any selected entry had parameters elided for size. Replaying with the stored + * descriptor in place of the value would call the recipe with something that *resembles* the + * original argument and is not it, which is the one outcome a provenance feature must never + * produce. The refusal names the entries so the caller knows which edits cannot be reproduced. + */ + post: operations["recipe_replay_plan_projects__pid__recipes_replay_plan_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rent-roll": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Rent Roll + * @description Operating rent roll — occupancy, WALT, lease-expiration schedule + in-place income from the + * `lease` module (the hold phase). Feeds the appraisal income approach (`/appraisal?rentroll=1`). + */ + get: operations["get_rent_roll_projects__pid__rent_roll_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rent-roll/net-effective": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Net Effective + * @description CRE-NER (R20) — **net effective rent**: what the rent roll is worth after concessions. + * + * Face rent is what a broker quotes; NER is what underwriting and agency lenders use, because + * concessions come out of gross potential rent before effective gross income. Returns both the + * straight-line and the **discounted** form (the latter prices *when* the free rent and the TI + * cheque land, not just how big they are), the concession load, and the leases whose face rent + * overstates them most. Leasing commission is only included when `lc_pct` is supplied — it is + * never invented; leases missing the fields the maths needs are named, not silently dropped. + */ + get: operations["get_net_effective_projects__pid__rent_roll_net_effective_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rent-roll/scrub": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Post Rent Scrub + * @description CRE-RRSCRUB (R20) — reconcile the rent roll against the income statement and unit inventory. + * + * Seven checks (scheduled rent vs gross potential rent at the 5% diligence threshold, occupied + * units with no lease, vacant units carrying a receivable, monotonically rising arrears, bad debt + * rising against flat occupancy, stated rent vs executed terms, expired leases still active). + * **A check that lacks its inputs reports `applicable: false` with what it needed — never a + * pass** — because a clean report built on absent data is worse than no report. + */ + post: operations["post_rent_scrub_projects__pid__rent_roll_scrub_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/report.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Status Report + * @description One-page project status report (KPIs, cost, open items by module, ball-in-court) as a PDF. + */ + get: operations["status_report_projects__pid__report_pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/reports/catalog": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Report Catalog + * @description Every report available in THIS project — built-in and saved — in one list. + * + * R22-REPORT-BUILDER item 5. `reports.REPORTS` and the saved-view layer were two registries that + * knew nothing about each other, rendered in different panels. The entry's worry was that growing + * the module query surface would ship *"a second way to make a report, sitting beside the one users + * already have"*, and asked for them to be unified **or** for the separation to be a deliberate, + * recorded decision. + * + * **The decision is: they stay separate implementations behind ONE surface**, because they are not + * the same kind of thing and pretending otherwise would cost more than it buys. + * + * * A built-in report is **code** — Earned Value, the WIP schedule, a tri-approach appraisal. These + * compute things no query builder expresses; folding them into saved views would mean either a + * query language that can do EVM (it cannot) or 56 rows of config that secretly dispatch to + * Python (a registry with extra steps). + * * A saved view is **data** — a user's query, authored without an engineering ticket, and the + * whole point of R22-REPORT-BUILDER is that it needs no code to exist. + * + * What was genuinely wrong was never the two implementations; it was that a user asking *"what + * reports do I have here?"* got two unrelated answers from two panels. This route is the one answer. + * `kind` is explicit rather than inferred, because the two differ in what a caller may do with them + * — a built-in renders to PDF at a fixed path, a saved view is replayed against its module — and a + * UI that guesses that from the shape of an id will guess wrong. + */ + get: operations["project_report_catalog_projects__pid__reports_catalog_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/reports/{report}.pdf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Report Pdf */ + get: operations["report_pdf_projects__pid__reports__report__pdf_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/reports/{report}.xlsx": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Report Xlsx */ + get: operations["report_xlsx_projects__pid__reports__report__xlsx_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/reserves/study": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Reserve Study + * @description Reserve study: recurring replacement events (asset register install + expected life + + * replacement cost, plus open capital-plan items), year-by-year balance trajectory, first + * underfunded year, and the suggested level annual contribution. + */ + get: operations["reserve_study_projects__pid__reserves_study_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/resilience/climate-risk": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resilience Climate Risk + * @description Physical climate-risk rollup for ESG — flood exposure + stormwater load + site-weather hazards + + * logged weather delays folded into a single scored rating with the driving factors. + */ + get: operations["resilience_climate_risk_projects__pid__resilience_climate_risk_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/resilience/flood": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resilience Flood + * @description Flood risk (ASCE 24 / FEMA): the Design Flood Elevation (BFE + freeboard) and the flood-proof-MEP + * check — asset-register items installed below the DFE, flagged to be elevated or flood-proofed. + */ + get: operations["resilience_flood_projects__pid__resilience_flood_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/resilience/stormwater": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resilience Stormwater + * @description Stormwater (Rational Method): peak runoff Q = C·i·A per catchment plus a first-order detention + * volume, so drainage is sized against a real design storm. + */ + get: operations["resilience_stormwater_projects__pid__resilience_stormwater_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/resilience/weather": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resilience Weather + * @description Weather-sequenced construction: weather-sensitive schedule activities, the site-weather-risk + * register, and weather-delay days rolled up from the daily reports. + */ + get: operations["resilience_weather_projects__pid__resilience_weather_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/responsibility": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Matrix + * @description The full RACI/DACI grid: role columns × activity rows, with per-row validation + * (exactly one Accountable, at least one Responsible) and role-load summary. + */ + get: operations["get_matrix_projects__pid__responsibility_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/responsibility/apply-template": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Apply Template + * @description Seed the matrix from a named starter template (also sets the default role columns + mode). + */ + post: operations["apply_template_projects__pid__responsibility_apply_template_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/responsibility/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Put Config + * @description Set the project's role columns and the matrix mode (RACI or DACI), migrating the cells to + * match **in one transaction**. + * + * `rename` maps an old column name to its new one, `drop` names columns whose cells should be + * cleared from every row; both are optional and describe how the cells move, since `roles` alone + * cannot distinguish a rename from a remove-plus-add. A mode change remaps the doer letter (R↔D) + * on its own. Nothing is written unless all of it can be. + */ + put: operations["put_config_projects__pid__responsibility_config_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/responsibility/templates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Templates + * @description Starter matrices for the common construction phases (design delivery, buyout, execution, closeout). + */ + get: operations["get_templates_projects__pid__responsibility_templates_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/review/ask": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Review Ask Ep */ + post: operations["review_ask_ep_projects__pid__review_ask_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/review/contract": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Review Contract Ep */ + post: operations["review_contract_ep_projects__pid__review_contract_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/review/scope": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Review Scope Ep */ + post: operations["review_scope_ep_projects__pid__review_scope_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rfi/qa": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Rfi Qa Ask + * @description RFI-0 NL-QA: ask a plain-language question and get a **cited** answer from the model's own data — + * 'what governs ?', 'what's blocking approval?', 'what is spec section 05 12 00?'. Routes to + * the doc-graph / decision-readiness; every claim carries a citation (GUID · spec section · document + * sheet). Body: {question}. Needs a source IFC. + */ + post: operations["rfi_qa_ask_projects__pid__rfi_qa_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rfi/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rfi Readiness + * @description RFI-0: the **decision-readiness audit** — the proactive inverse of the RFI. Scans the model for the + * information gaps a builder would have to ask about (failed code checks, missing details/keynotes, + * model-data gaps, open clashes), ranked, as one resolve-before-issue list. Composes the approvability + * pre-flight + detail-rule validator + model-hygiene + clash coordination. Needs a source IFC. + */ + get: operations["rfi_readiness_projects__pid__rfi_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rfi/readiness/bcf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Readiness To Bcf + * @description RFI-0: promote the **decision-readiness gaps** to BCF topics — every information gap a builder would + * otherwise have to raise an RFI about (failed code checks, missing details, model-data holes, open + * clashes) becomes a trackable, GUID-anchored issue that round-trips with clashes/RFIs. One topic per gap, + * priority from the gap's severity. Idempotent: re-running clears prior readiness topics first. + */ + post: operations["readiness_to_bcf_projects__pid__rfi_readiness_bcf_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rfi/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rfi Register + * @description RFI register — ball-in-court, overdue, response turnaround, cost/schedule-impact exposure. + */ + get: operations["rfi_register_projects__pid__rfi_register_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/risk-board": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Risk Board Endpoint + * @description RISK-BOARD — one ranked register unifying every computed risk signal: Monte-Carlo schedule + * risk (P80 buffer + top delay driver) · predictive schedule alerts · EVM cost/schedule indices · + * pre-flight issuance blockers · overdue coordination issues. Each item deep-links to the engine + * that computed it; a broken lane drops out (reported in `lanes`), never the board. + */ + get: operations["risk_board_endpoint_projects__pid__risk_board_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/risk-digest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Risk Digest + * @description Project risk digest across cost + schedule + open items + safety, with a prioritized narrative. + */ + get: operations["risk_digest_projects__pid__risk_digest_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/routines/due": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Routines Due + * @description R22-ROUTINES — which of THIS project's stored routines should be enqueued now. + * + * The persisted half of `/routines/due`: routines live in the `routine` register, and each carries + * its own `last_run`, so recurrence survives a restart — which is the point of storing them at all. + * + * **Only `active` routines are evaluated.** A `draft` was never switched on and a `retired` one was + * switched off deliberately; firing either would be the scheduler inventing intent. The ones it + * skipped for that reason are counted in `stored` vs `evaluated` rather than vanishing. + * + * Every refusal from the stateless endpoint still applies: catch-up is reported and never replayed + * (a routine three windows behind fires ONCE with `missed_windows: 3`), an unknown cadence is + * refused rather than defaulted, and skips come back with their reasons. + */ + get: operations["project_routines_due_projects__pid__routines_due_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/routines/run-due": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Project Routines Run Due + * @description R22-ROUTINES — enqueue the routines that are due. The step that makes this infrastructure. + * + * `/routines/due` answers what *should* run and returns it; nothing acted on that answer, which is + * the entry's own complaint — AI as a tool you remember to use rather than something that happens. + * This enqueues one job per due routine and stamps the window as consumed. + * + * **`in_flight` is derived from the jobs table, not assumed empty.** `routines.from_project` takes it + * as a parameter and no caller supplied one, so the "previous run has not finished" refusal could + * never fire: a monthly report taking an hour would be re-enqueued on every sweep for that hour. + * + * Three refusals a scheduler is worthless without: + * + * * **one job per DUE routine, never one per missed window.** A routine dormant for a year fires + * once when switched back on, with the missed count reported — not twelve jobs at once. + * * **a routine naming an unregistered kind is listed under `refused`**, and does not abort the + * sweep for every correctly-configured routine beside it. + * * **the window is consumed at enqueue, with the `job_id` recorded.** Consuming it on success would + * re-fire a failing routine every sweep until it passed — a retry storm dressed as a schedule. The + * cost is that a failed run waits for the next window, which is why the job id is on the row. + */ + post: operations["project_routines_run_due_projects__pid__routines_run_due_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rules": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rules Get + * @description RULE-LIB — the project's user-authored parametric rule library (falls back to the starter set + * until one is saved). Each rule = a scope selector + a require selector + a severity. + */ + get: operations["rules_get_projects__pid__rules_get"]; + /** + * Rules Put + * @description Replace the project's rule library. Every rule's selectors are validated (QUERY-DSL) before + * anything is written — a bad selector rejects the whole save with 422. + */ + put: operations["rules_put_projects__pid__rules_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rules/effective": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rules Effective + * @description The rules that actually apply here: the firm's, with this project's layered over them by rule id. + * + * Every rule reports its `source`, and a project rule that displaces a firm one carries the version + * it replaced under `overrides`. Overriding is legitimate — a client standard differing from the + * firm's is normal — but it has to be **visible**, because the failure mode is not a wrong answer, + * it is a firm discovering its standards were quietly optional. + */ + get: operations["rules_effective_projects__pid__rules_effective_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rules/geometry/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Rules Geometry Run + * @description Geometric/relational rule checks over the model's baked AABBs (the clash broad-phase path): + * `clearance` (approach space along the thin axis), `escape_distance` (straight-line to the + * nearest exit), `clear_width` (accessible opening width). Body `{checks: [{kind, scope, …}]}` + * with QUERY-DSL selector strings for scope/exits/obstructions; omit for the starter set. + * Editor role — this bakes geometry, like /clash. + */ + post: operations["rules_geometry_run_projects__pid__rules_geometry_run_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rules/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Rules Run + * @description Check the loaded model against the rule library → per-rule pass/fail + offending GUIDs + a + * by-severity rollup. When a space rule pack is stored and the project has a source IFC, the + * geometric space checks (dimensional / daylight / wet-wall, via the adjacency engine) run too and + * fold into the SAME rollup as `space:*` rows — one rule spine for elements AND spaces. + */ + get: operations["rules_run_projects__pid__rules_run_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/rules/space-pack": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Space Pack Get + * @description RULE-PACK FOLD — the project's per-IfcSpace rule pack (dimensional thresholds · needs-daylight · + * needs-wet-wall), stored beside the rule library and folded into /rules/run. Empty until saved. + */ + get: operations["space_pack_get_projects__pid__rules_space_pack_get"]; + /** + * Space Pack Put + * @description Replace the space rule pack. Validated (sections, severities, numeric ranges, type caps) + * before anything is written — a bad pack rejects the whole save with 422. + */ + put: operations["space_pack_put_projects__pid__rules_space_pack_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/safety/metrics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Safety Metrics + * @description Safety analytics: incidents by OSHA class, recordable/lost-time counts, lost days, and + * TRIR/DART (per 200k hours) using `hours` or hours summed from timesheets + manpower logs. + */ + get: operations["safety_metrics_projects__pid__safety_metrics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/safety/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Safety Summary + * @description Safety analytics — OSHA TRIR/DART/LTIFR, observation mix, toolbox coverage, violations. + * Pass ?hours= for exact rates; otherwise estimated from daily-report manpower. + */ + get: operations["safety_summary_projects__pid__safety_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scan/deviation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Scan Deviation + * @description Scan-to-BIM deviation — compare an uploaded as-built point cloud (XYZ/CSV) against the source + * model's surface and report % within tolerance + a deviation histogram (the QA/QC as-built check). + * 409 if no source IFC; 400 on a point cloud we can't read. + */ + post: operations["scan_deviation_projects__pid__scan_deviation_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scan/verify-lod500": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Scan Verify Lod500 + * @description Scan → **LOD 500**: attribute an uploaded point cloud to individual elements and stamp the ones + * that verify. + * + * `/scan/deviation` compares the cloud to the whole model and returns one aggregate — useful for QA, + * useless for verification, because it cannot say *which* element is right. This runs the query per + * element and turns the result into three outcomes: within tolerance → stamped as field-verified with + * its measured deviation (so the assertion states an accuracy); outside tolerance → returned as a + * finding and deliberately not stamped; never scanned → reported uncovered, because absence of + * points is not evidence. + * + * `apply=false` returns the same plan without writing, so a team can see what a scan would assert. + */ + post: operations["scan_verify_lod500_projects__pid__scan_verify_lod500_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scene-digest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scene Digest + * @description A4: a compact, LLM-friendly digest of the model — element counts by class, storeys, spaces, MEP + * systems + disciplines, phasing, LOD, and hygiene, plus a one-paragraph `prose` overview. Grounds the + * AI command bar and gives a one-glance model summary. + */ + get: operations["scene_digest_projects__pid__scene_digest_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scene/manifest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scene Manifest + * @description SCENE-PKG: the model's semantic half as a portable manifest — GlobalId-keyed nodes, property + * sets, typed relationships, and **precomputed by-class / by-level indexes** — with geometry left out. + * + * This is the engine-facing view. Our own web client already receives class and storey per element + * from `/elements`, so **this endpoint makes nothing in this app load faster** and no such claim is + * made for it. What it serves is every consumer that is not this app — an engine importer, a Blender + * addon, a CI check, a native viewer — whose only alternative today is parsing the IFC themselves, + * which is the one thing this platform tells people not to do. + * + * Deterministic: `generatedAt` comes from the source IFC's own mtime, not the clock, so an unchanged + * model yields a byte-identical manifest and its content hash means what the format says it means. + * + * Products IFC declares but that hang outside the spatial hierarchy are reported in `_warnings` + * rather than silently omitted — a package quietly containing fewer elements than the model reads + * as a complete answer. 409 if the project has no accessible source IFC. + */ + get: operations["scene_manifest_projects__pid__scene_manifest_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scene/package": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scene Package Zip + * @description SCENE-PKG: the manifest above plus the `.frag` payload it names, as one zip. + * + * Geometry is carried as **Fragments**, not re-encoded — re-encoding would mean decoding geometry + * the engine decodes better and discarding the per-element addressing Fragments already has. The + * payload is referenced by id and hash from the manifest, so a consumer parses a small JSON document + * first and streams the bytes only if it is going to draw them. + * + * If the pipeline has not produced a `.frag` yet the package is still valid and simply names no + * payload — a manifest referencing bytes the archive does not carry would be a promise the caller + * cannot keep, and the writer rejects that rather than shipping it. 409 if there is no source IFC. + */ + get: operations["scene_package_zip_projects__pid__scene_package_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/4d": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule 4D + * @description 4D construction sequence (C3): scrubable timeline frames (cumulative % built per day) over the + * published model's elements. + * + * Source is **relational by default** (`source=auto`): when the GC **`schedule_activity`** records + * exist they drive the sequence (`source:"gc"`) — each element gets its real calendar finish date + * from the activity that tags its GUID, else from its trade's activities by floor — so the model + * plays the *actual* schedule the team maintains in the portal (the same activities behind the + * Gantt / Line-of-Balance / CPM views). Otherwise it falls back to a takt plan derived from the + * storey count; if a Primavera **P6 .xer** was imported, takt frames carry interpolated calendar + * dates (`source:"p6"`). Force a source with `?source=gc|takt`. + */ + get: operations["schedule_4d_projects__pid__schedule_4d_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/alerts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Alerts + * @description Predictive schedule alerts — overdue work, late/at-risk starts (incomplete predecessor), + * behind-schedule SPI, and a procurement-risk proxy — from the cost-loaded schedule + CPM. + */ + get: operations["schedule_alerts_projects__pid__schedule_alerts_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/baseline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Set Baseline + * @description Snapshot the current schedule as the **baseline** (each activity's planned start/finish + budget, + * keyed by id). Variance is then measured against this — re-run to re-baseline after an approved + * change. One baseline per project. + */ + post: operations["set_baseline_projects__pid__schedule_baseline_post"]; + /** + * Clear Baseline + * @description Remove the schedule baseline. + */ + delete: operations["clear_baseline_projects__pid__schedule_baseline_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/baselines": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Baselines + * @description The project's named-baseline library (metadata only), newest first. + */ + get: operations["list_baselines_projects__pid__schedule_baselines_get"]; + put?: never; + /** + * Capture Baseline + * @description Snapshot the current schedule as a new NAMED baseline (e.g. "GMP", "Recovery"). Unlike the + * singular baseline, several coexist so drift can be tracked against each. + */ + post: operations["capture_baseline_projects__pid__schedule_baselines_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/baselines/{bid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Delete Baseline + * @description Remove a named baseline from the library. + */ + delete: operations["delete_baseline_projects__pid__schedule_baselines__bid__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/baselines/{bid}/variance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Named Baseline Variance + * @description Per-activity slip of the live schedule vs a chosen NAMED baseline (`bid`, or `latest`). + */ + get: operations["named_baseline_variance_projects__pid__schedule_baselines__bid__variance_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/collapsed": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Collapsed Endpoint + * @description R46 -- collapsed as-built (AACE 29R-03 MIP 3.9): remove the delay events from the AS-BUILT and + * reschedule the remainder -- the "but-for" programme. + * + * Subtractive. It answers "when would this have finished without them". + * + * **It requires the events to already BE activities in the as-built network**, and today ours are + * not: `notice_clock` detects events from the field record, and the `schedule_activity` register + * does not carry them as tasks. The refusal names which are missing rather than working around it, + * because inserting the events and then removing them is impacted as-planned wearing this method's + * name -- and a report that did that could not say what it had done. + * + * Body: `{events: [{id, name, days, impacts, ...}]}`. + */ + post: operations["schedule_collapsed_endpoint_projects__pid__schedule_collapsed_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/compare": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Compare Endpoint + * @description R45-SCHED-REACH -- diff the live schedule against a named baseline and attribute the slip. + * + * Not variance. `/schedule/variance` answers "did this activity's dates move"; this re-schedules the + * baseline through the CPM engine and answers **why the finish moved**, apportioning the move across + * duration growth, added logic, lag growth, constraints, progress and levelling -- with the + * contributions summing to the finish move exactly, because a delay analysis whose parts do not sum + * to the whole is an opinion with numbers attached. + * + * **A baseline captured before v0.3.961 is refused** with `available: false`. Those snapshots hold + * dates but no logic, and rebuilding one produces a fully-parallel plan that finishes on day one -- + * which diffs into a large, confidently-attributed delay caused by logic nobody removed. Variance + * against those baselines is unaffected; capture a new baseline for an attribution. + */ + get: operations["schedule_compare_endpoint_projects__pid__schedule_compare_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/compress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Compress Endpoint + * @description R46 -- what finishing `target_days` earlier would take, and cost. + * + * **Not `/schedule/optimize`**, which is a rule-based advisory that never re-schedules: it reports + * `duration x 0.25` for each long critical activity, a fixed fraction of the activity's own length. + * That figure cannot see the path behind the activity. Measured on a four-activity network with a + * near-parallel path, the advisory says **5 days** and the finish actually moves **3** -- and the + * error grows with the number of near-parallel paths. + * + * This re-schedules after every single day of compression, so `days_saved` is what the PROJECT + * finish moved, not what came off the activity, and the plan is built cheapest-useful-day-first. + * + * `cost_per_day` and `max_days` are **required per activity**. `max_days` is a fact about the work + * -- a pour cures in the time it cures -- and a default of "as far as you like" produces a plan + * that finishes on any date somebody asks for. A plan reaching eight of ten days asked for is + * returned as eight, with `meets_target: false`. + * + * Body: `{target_days, costs: [{activity_id, cost_per_day, max_days}], fast_trackable?: [[a, b]]}`. + */ + post: operations["schedule_compress_endpoint_projects__pid__schedule_compress_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/cpm": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Cpm + * @description Critical Path Method analysis of the schedule_activity records — early/late dates, total + + * free float, and the critical path (FS dependencies via each activity's `predecessors`). + */ + get: operations["cpm_projects__pid__schedule_cpm_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/earned": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Earned Endpoint + * @description R46 -- Earned Schedule: how far along in TIME, where SPI stops working. + * + * `evm.py` computes the ANSI/EIA-748 set and its own docstring defers this to "later phases". + * Classic `SPI = EV / PV` is a cost ratio used as a time signal, and it has a terminal defect: EV + * converges on PV at completion whatever the dates did, so **SPI returns to exactly 1.0 on a + * project that finished a year late**. It does not degrade -- it arrives at "perfectly on schedule". + * + * `SPI(t) = ES / AT` compares two durations and stays below 1.0 at completion on a late project. + * Both ship; `evm.py` is untouched. + * + * **This is the one baseline method that works on a pre-v0.3.961 snapshot**, because it needs only + * dates. `compare`, `windows` and `impacted` all need frozen logic and refuse those. + * + * Working days throughout. `performance_index` is `null` when no time has passed -- never 1.0, + * which would say a project that has not started is exactly on schedule. + */ + get: operations["schedule_earned_endpoint_projects__pid__schedule_earned_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/earned-value": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Earned Value + * @description Schedule **earned value** over the activities that carry a budgeted cost. For each: + * BAC = Σ budget; **EV (BCWP)** = Σ %·budget (work earned); **PV (BCWS)** = Σ planned-fraction·budget + * (where today sits in [start, finish]). **SPI** = EV/PV and the schedule variance **SV** = EV−PV + * tell you, in dollars, whether the job is ahead of or behind plan. AC/CPI need cost actuals and are + * left to the cost engine. + */ + get: operations["earned_value_projects__pid__schedule_earned_value_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/eot": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Eot + * @description R40-EOT — an extension of time, with the method it was computed by attached to it. + * + * Body: `{method, baseline_finish, actual_finish?, events:[{id, kind, days, activity_id?, start?, + * entitlement?}]}`. Activities come from the project's own `schedule_activity` records, so the float + * that decides whether a delay reaches completion is this schedule's float, not a supplied number. + * + * **`method` is required and is a closed set** — `as_planned_vs_as_built`, `impacted_as_planned`, + * `time_impact`, `windows`. The published taxonomies (AACE 29R-03, SCL Delay & Disruption Protocol) + * exist because the same facts give different answers under different methods; an EOT figure without + * its method cannot be weighed by whoever reads it, and this one ends up in a claim. Omitting it + * returns `method_required` **listing the methods** rather than picking one. + * + * **The method now changes the number (v0.3.971), which it did not until then.** All four were + * validated, echoed back in three fields and then never read: every branch computed the same + * additive sum, so a claim could carry the words "windows analysis" over arithmetic that was + * nothing of the kind. Today: + * + * * `impacted_as_planned` sums each event's impact beyond float — unbounded by what the job did, + * which is the published criticism of the method and is reported as `over_claimed_days`; + * * `as_planned_vs_as_built` caps that at the movement of the completion date, needs + * `actual_finish`, and refuses without it rather than falling back to the additive figure; + * * `windows` and `time_impact` need a dated SERIES of schedules, which this route is not given. + * They return `method_needs_schedule_updates`; windows names + * `GET /projects/{pid}/schedule/windows`, and time-impact names nothing because nothing performs + * it yet. + * + * Two further refusals, both of which a claim reviewer depends on: + * + * * **concurrency is named, never apportioned.** Overlapping employer-risk and contractor-risk delay + * is the most contested question in the discipline and the protocols disagree; splitting it + * silently would present a fabrication as arithmetic. + * * **float absorbs, and "absorbed" is reported as absorbed** — not as zero delay. Those are + * different findings and only the second reads as though nothing happened. + */ + post: operations["schedule_eot_projects__pid__schedule_eot_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/eot/sourced": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Eot Sourced + * @description R40-EOT ② — the same entitlement, built from the project's OWN baseline and events. + * + * Body: `{method, baseline_id?, events?, baseline_finish?, actual_finish?}`. The sibling endpoint + * above takes `baseline_finish` and the whole event list from the caller; **the baseline is the most + * contested input in a delay claim, and as a typed parameter it is unauditable** — two people can + * produce different EOTs from one project by typing different dates, with every careful refusal in + * the engine resting on an input nobody can check. + * + * Slip is measured against a **named, captured baseline** (`schedule_baselines`), so the quantum is + * re-derivable and attributable to an artefact. Causes come from `notice_clock` detection, which + * carries the record each event came from. + * + * Two gaps between those sources are REPORTED rather than filled: + * + * * **a detected event is not a quantified delay.** Detection establishes that an event occurred and + * carries no duration at all. Events without stated days are `needs_duration`, listed, and left + * out of the figure rather than handed the slip they sit near. + * * **slip with no matching cause is `unattributed`, never `non_excusable`.** Defaulting unexplained + * slip to contractor risk would hand one party an entitlement finding nobody demonstrated. + * + * Matching is by explicit `activity_id` only — proximity is not causation, and causation is the + * contested half of every claim. Returns `baseline_required` with the available baselines when none + * is captured, rather than falling back to a typed date. + */ + post: operations["schedule_eot_sourced_projects__pid__schedule_eot_sourced_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export Schedule + * @description SCHED-P6 — export the live schedule for **round-trip** into a scheduler's tool. `fmt=xer` → + * Primavera P6 **.xer**; `fmt=msp` → **MS-Project XML (MSPDI)**. Both carry the P6 activity code + * (task_code / ``), so re-importing a scheduler's updated file matches the same records by code + * — the GC's web edits go out, the scheduler's updates come back, no GUID drift. Reflects the current + * edited state of every `schedule_activity` (imported + hand-entered), not the frozen import. + */ + get: operations["export_schedule_projects__pid__schedule_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/flowline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Flowline Endpoint + * @description R45-SCHED-REACH -- location-based (linear) scheduling: line of balance. + * + * CPM answers "when can this activity start". It cannot answer "where is each crew, and does anyone + * get in anyone else's way", and the thing it structurally cannot express is **crew continuity** -- + * a forward pass gives every activity its earliest start, which is exactly what fragments a gang + * into work-a-floor-then-wait. `continuity_cost_days` is the price of keeping each crew whole, per + * trade, and it is the number that decides whether this view is worth using on a given job. + * + * Built from the `trade` and `location` fields the schedule already carries, so it needs no extra + * data. Reads `available` first: a project whose activities carry no location, or only one, comes + * back `available: false` with a reason rather than a drawn-but-meaningless diagram. + */ + get: operations["schedule_flowline_endpoint_projects__pid__schedule_flowline_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/from-estimate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule From Estimate + * @description EST-1 (the CPM half): write the QTO-driven labour estimate's **crew-day durations into the + * schedule** — one `schedule_activity` per trade group (WBS `EST`, chained FS in trade order), so + * CPM / Gantt / lookahead immediately reflect the model-derived durations. Re-running **upserts** + * the same EST activities (durations refresh, no duplicates; manual activities untouched). + * Body: `{loading?, rate?, crews?, work_week?}`. + */ + post: operations["schedule_from_estimate_projects__pid__schedule_from_estimate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/gantt.svg": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Gantt */ + get: operations["gantt_projects__pid__schedule_gantt_svg_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Health Endpoint + * @description R45-SCHED-REACH -- DCMA 14-point schedule quality assessment. + * + * The fourteen checks are the closest thing the industry has to a shared definition of "is this + * schedule trustworthy?", and owners and lenders ask for them by name. + * + * Reads `available` before `grade`: a project with no activities and a schedule with a logic loop + * both come back `available: false` with `grade: null`, because neither is a *failing* schedule -- + * one has not been planned and the other has no computed dates for a check to read. The engine + * also excludes checks it could not run from the score's denominator, so a clean schedule reads + * 100 over its runnable checks rather than a diluted number over all fourteen. + */ + get: operations["schedule_health_endpoint_projects__pid__schedule_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/impacted": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Impacted Endpoint + * @description R46 -- impacted as-planned (AACE 29R-03 MIP 3.6): insert the delay events into the CAPTURED + * BASELINE and reschedule. + * + * Additive and prospective. It answers "what should this delay have cost" and is silent on what + * happened afterwards. Runs against the baseline and refuses if there is none -- impacting a + * progressed schedule is a different method with a different name. + * + * **Concurrency is measured, not asserted.** `concurrency_days` is how much the individual impacts + * exceed their combined impact: two five-day delays running concurrently move the finish five days, + * not ten, and that overlap is the five nobody is entitled to twice. + * + * `days` per event comes from the CALLER and the response says so (`days_source`). `notice_clock` + * detects that an event happened and never what it cost, deliberately. `responsibility` is carried + * through untouched -- whose delay it was is a contractual question. + * + * Body: `{events: [{id, name, days, impacts, onset?, responsibility?}], baseline_id?}`. + */ + post: operations["schedule_impacted_endpoint_projects__pid__schedule_impacted_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/import-xer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Import Xer + * @description Import a Primavera P6 export — **.xer** (tab-delimited) or **.xml (PMXML)**, auto-detected from + * the content. Parses the tasks/activities and **upserts each as an + * editable `schedule_activity` record** (matched to the prior import by P6 activity code), so the + * GC can keep updating, adding, and re-sequencing tasks after import — imported and hand-entered + * activities live in one editable schedule that drives Gantt / Line-of-Balance / CPM / the 4D + * scrub. Re-importing updates the same records (preserving GC edits to others); zero-duration tasks + * are tagged as Milestones. Also keeps the start→finish window for the takt 4D date overlay. + * **PMXML with baselines:** a P6 XML export carries baselines as additional `` elements — + * the reason the format is worth having, since XER cannot carry them at all. Only one project can + * be imported at a time, so a multi-project document reports every project it found in + * `report.projects` and logs a `PMXML_MULTI_PROJECT` **error** naming the ones it did not import. + * Pass `project_id` (an id from that list) to import a specific one. Previously the first project + * was imported and the rest were dropped in silence. + * + * Returns counts (created/updated) + the date range + a small preview. + */ + post: operations["import_xer_projects__pid__schedule_import_xer_post"]; + /** + * Clear Xer + * @description Remove an imported P6 schedule: deletes the activity records this import created (by its + * code→id index) and the date-window blob. Hand-entered activities are untouched. + */ + delete: operations["clear_xer_projects__pid__schedule_import_xer_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/level": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Level Endpoint + * @description R45-SCHED-DEDUPE -- deterministic resource levelling by serial schedule generation. + * + * POST because it takes `caps` -- `{"caps": {"Carpentry": 8}, "horizon": "within_float"}` -- and a + * crew limit is too long and too structured to live in a query string. Advisory only: the engine + * never writes, it returns the moves for a caller to accept. + * + * Distinct from `resource_loading.level()`, which shifts non-critical work inside its CPM float to + * shave a peak and gives up when float runs out. This places every activity at the earliest instant + * its resources are genuinely free, so it resolves conflicts the smoother can only report. + * + * `horizon` is the trade-off and has no safe default: `within_float` never moves the finish and + * **reports** what it therefore could not solve; `extend_finish` solves everything and accepts a + * later finish. A job with liquidated damages wants the first; one that has already blown its float + * wants the truth of the second. + */ + post: operations["schedule_level_endpoint_projects__pid__schedule_level_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/lob.svg": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Lob */ + get: operations["lob_projects__pid__schedule_lob_svg_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/lookahead": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Lookahead + * @description Short-interval **lookahead** (the field's 3- / 6-week plan): activities active in the window + * [`start`, `start`+`weeks`), grouped by ISO week. An activity is in-window if it overlaps it + * (starts before the end and finishes on/after the start). Each carries trade, %-complete, and a + * field status (not_started / in_progress / late / complete). Defaults to a 3-week window today. + */ + get: operations["lookahead_projects__pid__schedule_lookahead_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/make-ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Make Ready + * @description READY-AGENT — every activity starting within `days`, its preconditions checked with **cited + * evidence** (incomplete predecessors by ref + % complete · open submittals by ref/state) and a + * ready/blocked verdict — 'can next week's work actually start?', answered proactively. + */ + get: operations["schedule_make_ready_projects__pid__schedule_make_ready_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/milestones": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Milestones + * @description **Milestone schedule**: the key dates — activities typed `Milestone` (or zero-duration), + * sorted by date, each with a status (met / due_soon / upcoming / late). `met` = 100% complete; + * `late` = past its date and not complete; `due_soon` = within 14 days. + */ + get: operations["milestones_projects__pid__schedule_milestones_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/montecarlo": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Montecarlo Endpoint + * @description R45-SCHED-DEDUPE -- Monte Carlo schedule risk on the **real** network. + * + * **The only Monte Carlo left.** "/schedule/risk" and "aec_api/schedule_risk.py" were deleted in + * v0.3.972 rather than kept beside this one, because the older simulator was wrong twice on the same + * records: it converted durations to dates with plain CALENDAR arithmetic (37 days adrift of this + * route on a 100-working-day chain -- it counts Saturdays), and its predecessor index was built from + * `ref`/`wbs` only, so logic written with record ids -- which `schedule_cpm` resolves -- vanished and + * the whole network simulated as fully PARALLEL, with no error and a P80 four months early. + * + * This runs the same `Task`/`Link` network the CPM uses, so it honours every relation type, lag and + * work calendar. + * + * Reports two things the older endpoint cannot: `confidence_in_deterministic` (the share of runs that + * met the programme date -- the direct answer to "how likely is this date, really?") and + * `duration_sensitivity` per activity (whether an activity's duration actually *moves* the finish, + * as opposed to merely sitting on the critical path often). + * + * `ppc_pct` carries the team's Last Planner reliability through to the pessimistic tail: below 80% + * widens it, above narrows it. Explicit per-activity estimates are never overridden by it. + */ + get: operations["schedule_montecarlo_endpoint_projects__pid__schedule_montecarlo_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/optimize": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Optimize + * @description Schedule-acceleration ADVISORY off the CPM critical path — crash candidates (longest critical + * activities), fast-track candidates (consecutive critical activities to overlap), and near-critical + * watch. Rule-based + an optional AI narrative; it never rewrites the schedule. + */ + get: operations["schedule_optimize_projects__pid__schedule_optimize_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/optioneer": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Optioneer + * @description SCHED-OPT (SPRINT B) — deterministic **schedule optioneering** over the Takt line-of-balance model: + * permute crew loading (a 2nd crew on the bottleneck trades) and work-face zoning across a bounded grid, + * score every scenario on makespan / cost / peak-congestion, and return the ranked list with the Pareto + * frontier + a recommended option. + * + * Body (all optional): `floors`, `trades:[{name,takt_days}]`, `crew_day_rate`, `max_crew_trades`, + * `zone_options:[…]`, `overlap_options:[…]`, `permute_sequence`, `weight_time`, `weight_cost`, + * `critical_path` (a list of trade names, or `"auto"` to take it from the project's CPM — crew + * doubling is then only offered on trades that actually govern the finish). When + * `trades` is absent the train is **derived from the project's own schedule** (trades = the activity + * trades, per-floor takt = each trade's total duration ÷ floors), falling back to the residential takt + * train; absent `floors` are derived from the model's storey count (else 1). The response's + * `trade_source` reports which of body / schedule / default was used. + */ + post: operations["schedule_optioneer_projects__pid__schedule_optioneer_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/portfolio": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Portfolio Endpoint + * @description R46 -- several projects, the links between them, and ONE pass over one network. + * + * A programme above a certain size is not one schedule. Scheduling the projects in sequence + * propagates a delay only in whichever order somebody listed them -- enabling then fit-out picks up + * the slip, the other order does not, and neither is more correct. Merging first is the only way + * the direction of the arrow decides it rather than the order of the list. + * + * An external link is kept as its own kind of thing: an internal link is a sequencing decision one + * team can change, an external one is a commitment between two parties, and a report that cannot + * tell them apart cannot say which delays crossed a boundary. + * + * **Membership is checked on EVERY project named, not just `{pid}`.** A portfolio view that + * required access to one project and returned dates from four would be a cross-tenant read wearing + * a feature's name. + * + * Body: `{project_ids: [...], external?: [{predecessor_project, predecessor_id, + * successor_project, successor_id, type?, lag_days?}]}`. `{pid}` is always included. + */ + post: operations["schedule_portfolio_endpoint_projects__pid__schedule_portfolio_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/progress-report": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Progress Endpoint + * @description R45-SCHED-DEDUPE -- progress of the SCHEDULE against a baseline: BEI, variance, slippage. + * + * Not the same measurement as `progress_rollup`, which is percent-complete of the **building** from + * as-built element presence. A tower can be 60% erected and four weeks late; neither number + * substitutes for the other. + * + * `baseline_id` defaults to the most recent capture. A project with no baseline is **refused** — + * measuring a schedule against its own current dates reports every activity perfectly on programme. + * + * `baseline_execution_index` is `null` when nothing was due, never `1.0`: an empty ratio is no + * information, and a green tile on a project that has not started is worse than a blank one. + */ + get: operations["schedule_progress_endpoint_projects__pid__schedule_progress_report_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/reliability": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Reliability Endpoint + * @description R45-SCHED-DEDUPE -- Last Planner reliability: PPC by week, with the reasons. + * + * **A third PPC, and deliberately so.** `lean.ppc` divides by every record, so mid-week the + * unanswered commitments read as failures; `pull_plan.metrics` divides by the assessed ones only, so + * one answered commitment out of twenty can read 100%. This one freezes the denominator when the + * week is committed and returns **`null` until every commitment has an answer** -- a week in progress + * is unmeasurable, not perfect and not failing. + * + * Consolidating the three is a domain decision, not a cleanup: a GC reports PPC to an owner. So all + * three still exist, and `test_ppc_divergence.py` pins the disagreement so it cannot widen unnoticed. + * + * No lifetime average: `trend` is the series, because one number across six months hides the month + * it collapsed. + */ + get: operations["schedule_reliability_endpoint_projects__pid__schedule_reliability_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/resource-leveling": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resource Leveling Endpoint + * @description Resource-leveling advisory against a `cap` availability: over-allocated work that still has CPM + * total float can be **smoothed** (shifted within float) to shave the peak without moving the finish. + * Advisory only — never mutates the schedule. + */ + get: operations["resource_leveling_endpoint_projects__pid__schedule_resource_leveling_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/resource-leveling/apply": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Resource Leveling Apply + * @description RESOURCE-LEVEL-2 — APPLY one leveling round: shift over-allocated activities forward within + * their CPM float (week-granular, most-float-first, finish never moves). Mutates the schedule — + * the UI gates this behind an explicit confirm. Returns moves + before/after peak. + */ + post: operations["resource_leveling_apply_projects__pid__schedule_resource_leveling_apply_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/resource-loading": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Resource Loading Endpoint + * @description Resource-loaded schedule — weekly resource histogram (by trade/type), cumulative units + **cost** + * S-curves, peak, and (against an optional ?cap= availability) over-allocation flags. Prefers + * `resource_assignment` records (activity + cost code + units + rate); falls back to activity crew_size. + */ + get: operations["resource_loading_endpoint_projects__pid__schedule_resource_loading_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/risk": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Risk Alias + * @deprecated + * @description DEPRECATED alias for `/schedule/montecarlo`. Kept serving; deliberately not removed. + * + * The engine behind this path was deleted in v0.3.972 because it was wrong twice -- it added plain + * CALENDAR days (37 adrift on a 100-working-day chain) and its predecessor index read `ref`/`wbs` + * only, so logic written with record ids vanished and the network simulated fully parallel. The + * PATH stays, because the roadmap records retiring it as **the user's call**, and an endpoint + * disappearing is a different kind of change from an endpoint being corrected. Deleting it was + * taken back for exactly that reason. + * + * So this returns what `/schedule/montecarlo` returns, and says so on the response. The response + * SHAPE therefore changed: dates (`p50`, `p80`) rather than day counts (`p50_days`, `p80_days`). + * That is unavoidable -- the old shape was the wrong answer's shape -- and it is a smaller change + * than a 404 while the removal is undecided. + */ + get: operations["schedule_risk_alias_projects__pid__schedule_risk_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Status Endpoint + * @description SCHED-STATUS — what is late, as of a **data date**. + * + * Body: `{data_date: "YYYY-MM-DD"}`. The data date is P6's: the line everything remaining is + * measured from. **It is required to say anything about lateness** — without one every activity + * comes back `unknown`, because an activity with no recorded start may be future work sitting + * comfortably ahead of its date or work that should have begun three weeks ago, and assuming today + * would quietly paint a job that finished two years ago entirely red. + * + * Distinguishes *late to start* (should have begun, has not) from *overdue* (past its planned finish + * and not complete). An activity that finished LATE is complete, not overdue — its slip already + * happened. And `expected_finish`, the date whoever is doing the work expects to hit, is a **claim** + * rather than a measurement, so forecast slip is reported separately and never added to actual slip. + */ + post: operations["schedule_status_endpoint_projects__pid__schedule_status_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/takt-train": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Takt Train Endpoint + * @description R45-SCHED-DEDUPE -- takt planning: a fixed rhythm through the zones, and what it costs. + * + * **Not the same thing as `/schedule/flowline`, and the difference is a decision.** Line of balance + * lets every trade run at its own pace and shifts the lines apart until nobody trespasses. Takt does + * the opposite: every wagon occupies exactly one zone for exactly one takt, and the crew sizes move + * so the durations do not. `W` wagons through `Z` zones is `(W + Z - 1)` takts, always, readable + * before any of the work is estimated -- which is what lets a trade be told to arrive Monday and + * believed. + * + * It is paid for in idle capacity, and `utilisation` reports it per wagon per zone, unrounded. Omit + * `takt_days` to get the shortest feasible rhythm plus the wagon that sets it: shortening any other + * trade changes nothing, so naming the constraint is the actionable half. + */ + get: operations["schedule_takt_train_endpoint_projects__pid__schedule_takt_train_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/takt.svg": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Takt Svg + * @description Line-of-balance chart for the project with the **actual ascent overlaid** (dashed) on the plan. + */ + get: operations["project_takt_svg_projects__pid__schedule_takt_svg_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/takt/progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Project Takt Progress + * @description Project actual-vs-takt: derive per-trade floors-complete from the GC `schedule_activity` + * records and compare to the takt plan sized from the model's storey count. Also returns PPC so a + * dashboard card can show plan health + Last-Planner reliability together (R2/R4). + */ + get: operations["project_takt_progress_projects__pid__schedule_takt_progress_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/variance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Variance + * @description Per-activity slip vs the baseline: **finish_var**\/start_var in days (positive = later than + * baseline = slipped), plus added/removed activities. Surfaces how far the job has drifted from + * the plan of record. 409 if no baseline has been set. + */ + get: operations["variance_projects__pid__schedule_variance_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/weather": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Weather Endpoint + * @description R46 -- the weather allowance a programme already carries, made visible. + * + * Weather loss is almost never modelled; it is padded into durations, where a five-day pour becomes + * seven and nobody afterwards can say which two days were weather. That matters when the weather + * arrives, because a contractor claiming an extension has to show the allowance was exceeded. + * + * Modelled as non-working days on the calendar, which is what it is. **No allowance is invented** -- + * days per month come from the contract, a met-office table or the specification, and a request + * with none is refused rather than defaulted. The days added are listed, not just counted, because + * an allowance is argued with. + * + * Body: `{days_by_month: {"3": 3, "apr": 2}, start?, finish?}`. + */ + post: operations["schedule_weather_endpoint_projects__pid__schedule_weather_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/schedule/windows": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Windows Endpoint + * @description R46 -- contemporaneous windows analysis (AACE 29R-03 MIP 3.3): where the time went, period by + * period. + * + * **`eot.py` offered four AACE methods and performed none of them** — measured on one input, all + * four returned the same number, because the method was recorded as a label and the arithmetic was + * events-minus-float in every branch. This performs one of them for real, and since v0.3.971 + * `POST /schedule/eot` REFUSES the windows method and names this route instead. + * + * The series comes from the captured baseline library -- up to twelve dated snapshots, each carrying + * the logic to be re-scheduled since v0.3.961. A pre-v0.3.961 baseline is EXCLUDED and named: it + * holds dates but no predecessors, and re-scheduling one would put a fully-parallel plan inside a + * single window, moving time into a period it did not happen in. + * + * Windows sum to the total slip exactly, and acceleration is reported as a negative rather than + * dropped -- a claim that counts only the slips overstates itself. + */ + get: operations["schedule_windows_endpoint_projects__pid__schedule_windows_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/scope/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Scope Register + * @description SCOPE-REG — the scope register + gap analysis: each scope item resolves its quantity/value (QTO by + * cost code), owner (responsible/package), and schedule window (activity by id or cost code); the register + * surfaces **unquantified / unallocated / unscheduled** scope, gaps first. The connective spine across + * QTO · CBS · responsibility · schedule. Body: `{scope_items, qto_lines?, activities?}`. + */ + post: operations["scope_register_projects__pid__scope_register_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Search + * @description Cross-module full-text search (ref / title / field data). + */ + get: operations["search_projects__pid__search_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/securities/package": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Securities Package + * @description The syndication package (cap table → neutral investor-platform schema). This is the payload the + * capital-markets bridge pushes; it exports the same JSON regardless of whether the bridge is wired. + */ + get: operations["securities_package_projects__pid__securities_package_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/securities/syndicate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Securities Syndicate + * @description Sync the cap table into the configured investor / digital-securities platform (positions only — + * no funds move). Requires the bridge to be configured; 422 with an actionable message otherwise. The + * package export endpoint works regardless. + */ + post: operations["securities_syndicate_projects__pid__securities_syndicate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/selections/push-change-events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Selections Push Change Events + * @description Push the over-allowance selections into `change_event` records (reason 'Allowance Reconciliation', + * ROM = the overage) so the deltas flow into the change-order/budget chain. Idempotent — an overage + * already tracked as a change event is skipped. + */ + post: operations["selections_push_change_events_projects__pid__selections_push_change_events_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/selections/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Selections Summary + * @description SELECTIONS — the owner selections & allowances money rollup: total allowance vs. actual, the net + * over/under, per-category deltas, approval status, and the over-allowance items that are change-order + * candidates (delta = actual − allowance; over is an add to the owner, under a credit). + */ + get: operations["selections_summary_projects__pid__selections_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/share-tokens": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Share Tokens + * @description List the project's share tokens with their view counts (editor only). + */ + get: operations["list_share_tokens_projects__pid__share_tokens_get"]; + put?: never; + /** + * Create Share Token + * @description Mint a revocable, read-only share token for the project (owner/editor only). The returned token + * string is the shareable credential — anyone with it can read the project's readiness digest. + * `show_payments: true` is the explicit OPT-IN for THIS token's digest to carry the owner-invoice + * payment schedule (display only); the default digest exposes no financials. + * + * `show_model: true` is the separate OPT-IN that lets THIS token fetch the project's geometry + * fragment. Both default to false, and they are independent: a token may carry payments, or + * geometry, or neither, and granting one never implies the other. + */ + post: operations["create_share_token_projects__pid__share_tokens_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/share-tokens/{token}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** + * Revoke Share Token + * @description Revoke a share token — access stops immediately (editor only). + */ + delete: operations["revoke_share_token_projects__pid__share_tokens__token__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/shared-params": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Shared Params + * @description FAMILY-DEPTH ④ (R18): the project's shared-parameter registry — named, typed properties in + * a declared pset applying to declared IFC classes. Registered parameters appear as columns on + * the computed door/window/room schedules (and are therefore reachable by SCHED-CALC formulas); + * values are written through the normal edit recipes. + */ + get: operations["get_shared_params_projects__pid__shared_params_get"]; + /** + * Put Shared Params + * @description Replace the registry (validated atomically: unique letter-first names, ptype text|number|bool, + * 1–20 IFC classes each, ≤100 definitions). + */ + put: operations["put_shared_params_projects__pid__shared_params_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/site-context": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Site Context + * @description OSM site context around the project — building footprints (height/levels), roads, land-use — + * as GeoJSON for the viewer's reference layer. Cached after the first fetch (offline afterwards); + * `refresh=true` re-queries. ODbL attribution ships in the payload. 409 when no coordinates are + * available (pass lat/lon, or georeference the model's IfcSite). + */ + get: operations["site_context_projects__pid__site_context_get"]; + put?: never; + post?: never; + /** + * Clear Site Context + * @description Drop the cached site context (next GET re-fetches). + */ + delete: operations["clear_site_context_projects__pid__site_context_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/smart-views": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Smart Views Get + * @description The project's saved smart views (property-driven view presets for the viewer). + */ + get: operations["smart_views_get_projects__pid__smart_views_get"]; + /** + * Smart Views Put + * @description Replace the project's smart views. Every view's selector is validated (QUERY-DSL) before + * anything is written — a bad selector rejects the whole save with 422. + */ + put: operations["smart_views_put_projects__pid__smart_views_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/smart-views/{vid}/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Smart Views Run + * @description Resolve a saved view's selector to the matching GUIDs (the viewer isolates / colours / hides + * them). 404 if the view id isn't saved. + */ + get: operations["smart_views_run_projects__pid__smart_views__vid__run_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/source-ifc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Source Ifc + * @description Upload a project's source IFC (enables authoring + republish). Saves a local copy + * the converter can read plus a durable copy in object storage, then publishes. + */ + post: operations["upload_source_ifc_projects__pid__source_ifc_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/source.ifc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Source Ifc Download + * @description Download the project's source IFC (Save → Export IFC). + */ + get: operations["source_ifc_download_projects__pid__source_ifc_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sources-uses": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Sources Uses + * @description Sources & Uses built from the project's cost budget — grouped Uses (acquisition/hard/soft/ + * contingency + construction-loan interest) vs sized Sources (senior debt by LTC + LP/GP equity). + */ + get: operations["get_sources_uses_projects__pid__sources_uses_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/spatial-tree": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spatial Tree + * @description The IFC spatial hierarchy — IfcProject → Site → Building → Storey → Space — as one root node. + * + * Built at index time from the file's own `IfcRelAggregates` chain, never from the `storey` name + * string each element carries. A name-grouped tree has no GlobalIds in it, and a node nothing can + * address by GlobalId is a label rather than a node; it is also wrong on the model where a tree + * matters most, the one where two buildings each have a "Level 2". + * + * **Refuses rather than degrades on an index written before `index_schema: 2`.** Such an index has + * no `spatial` key, and so does a model that genuinely has no spatial structure — the version + * number is what separates them, and only one of the two is fixed by re-publishing. Answering the + * first with `null` would report a perfectly-structured building as having no storeys. + */ + get: operations["spatial_tree_projects__pid__spatial_tree_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/spec-links": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spec Links + * @description W11 SpecLink: the model's `Pset_Massing_SpecLink` breadcrumbs rolled up — each linked spec + * section (MasterFormat number + title) with its element tally, plus the unlinked count. Stamp + * links with the `set_spec_link` recipe (guids + section + optional title/url). + */ + get: operations["spec_links_projects__pid__spec_links_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/spec/manual": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spec Manual + * @description W11 D6: the 3-part **project manual** seeded from the model — elements grouped into + * divisions/tables → sections, each in SectionFormat Part 1/2/3 (Products from element + * types+materials, Execution from attached install docs). The spec book that accompanies the drawings. + * + * `system` is OPTIONAL and defaults to the classification system **the model actually uses**. It used + * to be absent, which meant `project_manual`'s own MasterFormat default applied to every project — + * and none of this repository's 58 tracked IFC files declares MasterFormat (57 Uniclass, 1 + * OmniClass). The response answered nothing, for every model, without reporting an error. The + * response now carries `system` and `available_systems` so a caller can offer the choice rather + * than infer it. + */ + get: operations["spec_manual_projects__pid__spec_manual_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/spec/manual.txt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spec Manual Text + * @description W11 D6: the project manual rendered as a downloadable plain-text spec outline. + * + * `system` behaves exactly as on the JSON route above — omit it and the model's own system is used. + */ + get: operations["spec_manual_text_projects__pid__spec_manual_txt_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specialty": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Specialty + * @description Specialty assets (on-site energy + vertical-farm/PFAL) params + computed summary (capex, + * annual revenue/opex/energy-offset). Starter params if none saved. + */ + get: operations["get_specialty_projects__pid__specialty_get"]; + /** + * Put Specialty + * @description Save specialty-asset params; returns the recomputed summary + proforma deltas. + */ + put: operations["put_specialty_projects__pid__specialty_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specialty/blended": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Post Specialty Blended + * @description Blend the specialty business into the deal's **equity** cash flows: solve the real-estate + * proforma from `a`, fold the ramped specialty nets + capex + terminal into the equity stream, and + * report **real-estate-only vs blended IRR** and the lift (U4). Uses the project's saved specialty + * params. Answers 'does the farm/energy actually move the deal return, net of its risk discount?' + */ + post: operations["post_specialty_blended_projects__pid__specialty_blended_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specialty/monte-carlo": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Run Specialty Monte Carlo + * @description Monte-Carlo the **specialty risk discount** (U4): sample the haircut / produce prices on the + * saved specialty params, blend into the deal's equity each draw, and return the distribution of + * **blended deal IRR** and **specialty-only IRR** (percentiles, P[≥target], histogram). Quantifies + * 'how much does the farm/energy actually help once its risk is uncertain — and how often it hurts?' + */ + post: operations["run_specialty_monte_carlo_projects__pid__specialty_monte_carlo_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specialty/proforma": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Specialty Proforma + * @description Multi-year specialty P&L with a production ramp + a specialty-only IRR (U4 depth). The energy/ + * farm business ramps to full output over `ramp_years`; opex runs full from year 1, so early years + * earn less — a credible operating-business view rather than a single stabilised year. + */ + get: operations["get_specialty_proforma_projects__pid__specialty_proforma_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specs/extract-submittals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Specs Extract Submittals + * @description Extract a typed submittal list from pasted spec text (AI when configured, rules fallback offline). + * Body: {text, create?: bool}. With create=true, logs each item as a `submittal` record (and a + * `spec_section` if a section number is present), building the submittal log from the spec book. + */ + post: operations["specs_extract_submittals_projects__pid__specs_extract_submittals_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/specs/submittal-log": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Specs Submittal Log + * @description Spec-driven submittal log — required submittals derived per spec section vs the submittals + * actually logged, with missing-submittal gaps (the CSI Part 1 'Submittals' → log reconciliation). + */ + get: operations["specs_submittal_log_projects__pid__specs_submittal_log_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/spine/traceability": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Spine Traceability + * @description Discipline Spine traceability — trace discipline → sheets → specs → bid packages → cost codes → + * budget, with per-discipline rollups and the coverage gaps (unpackaged specs, unbudgeted packages, + * un-specced sheets) so scope can't fall between the model, the documents and the money. + */ + get: operations["spine_traceability_projects__pid__spine_traceability_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/stakeholders/analysis": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stakeholder Analysis + * @description Power/interest (Mendelow) grid + stance read of the project's stakeholder register. + */ + get: operations["stakeholder_analysis_projects__pid__stakeholders_analysis_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/standards/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Standards Check + * @description Standards-compliance check (iso19650 | cobie | ids | uniclass) against the project's own data: + * findings with the clause each references, recommendations, and a 0–100 readiness score. + */ + get: operations["standards_check_projects__pid__standards_check_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/structure/code-aster.mail": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Structure Code Aster + * @description SOLVER-OUT: the analytical frame as a Code_Aster mesh (.mail, ASTER text, SI metres) — COOR_3D + * nodes, SEG2 elements, a BASE node group (supports) and a FRAME element group — a second independent + * solver exchange beside the OpenSees .tcl. 409 if there's no analytical model yet. + */ + get: operations["structure_code_aster_projects__pid__structure_code_aster_mail_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/structure/lateral": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Structure Lateral + * @description STRUCT-LATERAL: ASCE 7 lateral analysis — seismic Equivalent Lateral Force (§12.8) + simplified + * directional MWFRS wind — base shear distributed to per-story forces / shears / overturning, with the + * governing case flagged, plus a preliminary §12.12 story-drift screen (allowable Δa always; pass/fail + * when `elastic_drift_ratio` is supplied). Story weights estimated from floor area × `dead_psf`. + * **Preliminary — not a substitute for a licensed structural engineer** (no modal or P-delta). + */ + get: operations["structure_lateral_projects__pid__structure_lateral_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/structure/opensees.tcl": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Structure Opensees + * @description FEM-EXPORT: the analytical frame as an OpenSees (.tcl) model — nodes, base restraints, and one + * elasticBeamColumn per member (nominal sections, kip-inch-ksi) — a runnable skeleton an engineer + * re-sections + loads to independently verify the solver. 409 if there's no analytical model yet. + */ + get: operations["structure_opensees_projects__pid__structure_opensees_tcl_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/structure/solve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Structure Solve + * @description STRUCT-SOLVE: apply an ASCE 7 gravity load case (dead + live by occupancy) to the W10-7 analytical + * curve members and run a determinate member-by-member statics solve — per-beam reactions, max shear/ + * moment, indicative deflection + shear/moment/deflection diagrams, plus per-column tributary axial. + * **Preliminary only — not a substitute for a licensed structural engineer.** Requires an analytical + * model (run the `derive_analytical` recipe first). + */ + get: operations["structure_solve_projects__pid__structure_solve_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/subcontractor-billing": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Subcontractor Billing + * @description Subcontractor billing — the GC-pays-subs mirror of owner billing. Each subcontract's pay + * applications (sub_invoice records) rolled up: contract value vs billed-to-date (approved/paid), + * retainage held, paid, and remaining-to-bill. Ties sub draws to the same cost codes and the GMP + * direct-cost actual, so what subs bill the GC reconciles against what the GC bills the owner. + */ + get: operations["subcontractor_billing_projects__pid__subcontractor_billing_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/submittals/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Submittal Register + * @description Spec-section submittal register — turnaround, ball-in-court, overdue flags. + */ + get: operations["submittal_register_projects__pid__submittals_register_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/supply/competitive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Post Competitive Supply + * @description CRE-SUPPLY (R20) — competitive supply weighted by **recorded evidence**, not status label. + * + * Filters the pipeline to the subject's delivery-and-lease-up window and product type, then + * discounts each project's units by what is actually recorded about it (construction loan + * recorded > permit issued > planning filed > announced). Rumored supply is reported separately + * and never blended into the certain count; excluded projects are listed with the reason, so a + * thin competitive set is visible rather than implied. With `monthly_absorption` it also returns + * the evidence-weighted months-of-supply index beside the raw one — the gap IS the argument. + */ + post: operations["post_competitive_supply_projects__pid__supply_competitive_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sync/procore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Sync Procore + * @description Import a Procore project's RFIs / submittals / change events into the matching modules + * (idempotent). Uses a saved Procore connection's token. Editor+ (it writes records). + */ + post: operations["sync_procore_projects__pid__sync_procore_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sync/procore/push": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Push Procore + * @description Two-way: push locally-resolved records (v1: RFI status + answer) back to Procore. Only + * records imported from Procore are pushed; idempotent. + */ + post: operations["push_procore_projects__pid__sync_procore_push_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sync/schedules": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Schedules + * @description Auto-sync schedules for this project (Procore → modules on an interval). + */ + get: operations["list_schedules_projects__pid__sync_schedules_get"]; + put?: never; + /** Create Schedule */ + post: operations["create_schedule_projects__pid__sync_schedules_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sync/schedules/{sid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Update Schedule */ + put: operations["update_schedule_projects__pid__sync_schedules__sid__put"]; + post?: never; + /** Delete Schedule */ + delete: operations["delete_schedule_projects__pid__sync_schedules__sid__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/sync/schedules/{sid}/run-now": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run Schedule Now */ + post: operations["run_schedule_now_projects__pid__sync_schedules__sid__run_now_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/t12/normalize": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Post T12 Normalize + * @description CRE-T12 (R20) — map a trailing-twelve to the house operating chart of accounts and reconcile. + * + * Body: `{t12: {lines: [{description, amount | months[12]}], totals?: {income, expense, noi}}, + * units?: n}`. **The tie-out is a gate, not a report:** if source and mapped totals disagree the + * response carries `stopped: true`, the reconciling items, and `adjusted_noi: null` — an adjusted + * NOI on top of a lossy mapping is how a reclass disappears. Past the gate you get one-time items + * separated from run-rate, capital below the line, a run-rate-vs-trailing view, and the standard + * owner-operated add-back **questions** (never applied silently). + */ + post: operations["post_t12_normalize_projects__pid__t12_normalize_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/takeoff/2d": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Takeoff 2D + * @description TAKEOFF-2D: quantify + price regions traced on a 2D drawing (PDF page / scan) — the drawings-only + * case the model takeoff misses. Body: `{scale_units_per_px, unit?, regions:[{category, points, label?}], + * overrides?}` (or supply `calibration:{p1,p2,real_distance}` to derive the scale). Returns per-region + + * per-assembly quantities and cost, feeding the same 5D estimate. **Preliminary — trace/scale dependent.** + * + * With `layout` (a `sheet_regions` result) and `px_per_point`, two more answers ride along, both + * about *which drawing on the sheet* something sits on: `scope` for the traced regions, and + * `annotation_scope` for an optional `annotations:[{id?, kind?, x, y}]` or `{points:[[x,y],…]}` + * (a revision cloud). Both are omitted entirely when their input is absent. + */ + post: operations["takeoff_2d_projects__pid__takeoff_2d_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/takeoff/dxf": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Takeoff Dxf + * @description Quantity takeoff from an uploaded 2D CAD drawing (.dxf) — linear metres, enclosed area and + * block counts per layer, so estimating isn't IFC-only. DWG must be converted to DXF first. The + * upload is parsed in a temp file (never persisted to the source tree) and discarded. 400 on a file + * that isn't readable DXF. + */ + post: operations["takeoff_dxf_projects__pid__takeoff_dxf_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/tm-by-change-event": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Tm By Change Event + * @description T&M (eTicket) cost rolled up by the change event each ticket is linked to. + */ + get: operations["tm_by_change_event_projects__pid__tm_by_change_event_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/tm-summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Tm Summary + * @description Time & Material (eTicket) cost rollup — labor/material/equipment, billed vs unbilled. + */ + get: operations["tm_summary_projects__pid__tm_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Topics */ + get: operations["list_topics_projects__pid__topics_get"]; + put?: never; + /** Create Topic */ + post: operations["create_topic_projects__pid__topics_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics/board": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Topics Board + * @description TOPIC-BOARD — a kanban board over the BCF topics: columns by `status` / `priority` / `assignee` / + * `type` in stable workflow order, newest-modified first within a column; `filter` reuses the QUERY-DSL + * grammar over topic fields (`status=open & priority=High`, `title~duct`). Bad group/filter → 422. + * (Declared before /topics/{tid} so 'board' isn't captured as an id.) + */ + get: operations["topics_board_projects__pid__topics_board_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics/{tid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Topic */ + get: operations["get_topic_projects__pid__topics__tid__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Patch Topic */ + patch: operations["patch_topic_projects__pid__topics__tid__patch"]; + trace?: never; + }; + "/projects/{pid}/topics/{tid}/attachments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Attachments */ + get: operations["list_attachments_projects__pid__topics__tid__attachments_get"]; + put?: never; + /** Add Attachment */ + post: operations["add_attachment_projects__pid__topics__tid__attachments_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics/{tid}/comments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Comments */ + get: operations["list_comments_projects__pid__topics__tid__comments_get"]; + put?: never; + /** Add Comment */ + post: operations["add_comment_projects__pid__topics__tid__comments_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics/{tid}/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Topic Timeline + * @description TOPIC-LIFE — the topic's merged history (creation, status moves, edits, threaded comments, + * viewpoints, attachments) assembled from the audit trail + comment rows, oldest→newest, plus the + * canonical status list and this topic's allowed next transitions. + */ + get: operations["topic_timeline_projects__pid__topics__tid__timeline_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/topics/{tid}/viewpoints": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Viewpoints */ + get: operations["list_viewpoints_projects__pid__topics__tid__viewpoints_get"]; + put?: never; + /** Add Viewpoint */ + post: operations["add_viewpoint_projects__pid__topics__tid__viewpoints_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/turnover/certify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Certify + * @description Architect certifies substantial completion on a completion_certificate record: gate on a prepared + * punch list, record the Architect (certifying) + Owner/Contractor signatures, stamp the record model + * version, and issue the certificate. Render the G704 via .../contracts/completion_certificate/{rid}/ + * document.pdf?doc=g704. + */ + post: operations["certify_projects__pid__turnover_certify_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/turnover/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Readiness + * @description Punch-list rollup + latest model version — is the project ready for a G704 certification? + */ + get: operations["readiness_projects__pid__turnover_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/turnover/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Status + * @description Turnover package status: substantial-completion cert (signed?), record model, punch readiness. + */ + get: operations["status_projects__pid__turnover_status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/twin/readiness": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Twin Readiness + * @description Digital-twin + Digital Product Passport readiness: asset↔system linkage, sensor mapping, + * product-passport completeness, and the building-system graph. + */ + get: operations["twin_readiness_projects__pid__twin_readiness_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/types": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Types + * @description Catalog of placeable types ("families") in the project's source IFC, for the place-family + * picker. Deduped by (class, name). + */ + get: operations["list_types_projects__pid__types_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/types/{type_guid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Type Detail + * @description W10-1 type inspector: class, PredefinedType, box dims, type Psets, material layers, and the + * placed occurrences of one family type. Create/edit/material go through POST /edit with the + * create_type | edit_type_params | assign_material_set recipes (versioned + GUID-stable). + */ + get: operations["type_detail_projects__pid__types__type_guid__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/uploads/handshake": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Handshake + * @description Begin — or resume — an upload. Same request either way. + * + * Takes `{size, chunk_hashes: [sha256, ...], filename?}` and answers with the upload id, the chunk + * size the client must use, and the indices still needed. `need: []` means the server already holds + * every chunk: the client can go straight to `complete`, which is the deduplication case. + */ + post: operations["handshake_projects__pid__uploads_handshake_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/uploads/{uid}/chunk/{index}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Put Chunk + * @description Store one chunk, verified against the hash the manifest promised for it. + * + * The expected hash travels in `X-Chunk-Sha256` rather than being looked up from a stored manifest. + * That keeps the server stateless between the handshake and the chunks — there is no session to + * expire mid-upload — and it is safe because the upload id is *derived from* the manifest: a client + * that lies about a chunk hash computes a different id and writes into a different upload, which is + * its own, and cannot corrupt anyone else's. + */ + put: operations["put_chunk_projects__pid__uploads__uid__chunk__index__put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/uploads/{uid}/complete": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Complete + * @description Assemble the chunks into one object and drop the parts. + * + * Streams them: the assembled object is written through `storage.put_stream`, so a 200 MB IFC never + * exists as a single `bytes` in this process. That is the other half of R39-UPLOAD-CAP-APP, and it + * is why this route was worth writing rather than bolting resumption onto the existing multipart + * POST, which materialises the whole body by construction. + */ + post: operations["complete_projects__pid__uploads__uid__complete_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/validate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Run Validate + * @description Validate the source IFC against an IDS. Precedence: an **uploaded** `.ids` wins; otherwise + * `ids=auto` (default) uses the project's **pinned** IDS when one exists, else the built-in QA + * specs. `ids=stored` forces the pinned IDS (404 if none); `ids=default` forces the built-in specs. + * + * `format=json` (default) returns the per-specification pass/fail summary. `format=bcf` returns a + * **.bcfzip punch list of the non-conformances** — one topic per failing specification, its failing + * elements selected as components — so an IDS audit round-trips into Solibri / ACC / BIMcollab like + * any other coordination issue. + */ + post: operations["run_validate_projects__pid__validate_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verification": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Verifications */ + get: operations["list_verifications_projects__pid__verification_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verification/coverage": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Coverage + * @description Install-coverage summary: of the model's elements, how many are installed/verified, plus the + * deviation count. `total` comes from the uploaded property index (0 if none yet). + * + * **Also reports EVIDENCE coverage, which is the half this endpoint was missing.** This module + * exists for the verified handover to operations, and its own docstring calls deviations + * "photo-anchored" — but until now `coverage` counted status flags only. "42 elements verified" with + * no idea how many carry a photo is a claim nobody downstream can audit, and the status flag is the + * cheapest thing in the system to set. + * + * `deviations_without_photo` is the number worth surfacing first: a deviation is an assertion that + * something does not match design, and one with no photo is that assertion with nothing behind it. + * That is what becomes contentious at handover, months later, when the person who logged it has + * left the project. + * + * Deliberately NOT reported: anything derived from what the photos *contain*. Object detection + * answers "what is in this frame", which is not the same question as "is this element documented", + * and mixing the two would put an inferred number where an auditable one belongs. + */ + get: operations["coverage_projects__pid__verification_coverage_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verification/deviations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Deviations + * @description The deviation log — elements flagged as not matching design (for the punch / ops handover). + */ + get: operations["deviations_projects__pid__verification_deviations_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verification/{guid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Set Status + * @description Set an element's field-verification status (installed / verified / deviation / pending). + * Upserts by (project, guid); stamps ifc_class/storey from the property index when available. + */ + put: operations["set_status_projects__pid__verification__guid__put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verification/{guid}/photo": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Upload Photo + * @description Attach a field photo to an element's verification (deviation evidence / install proof). + * + * R22-PHOTO-CV: the upload is also the only moment both photos exist, so it is where the analysis + * has to happen. `photo_key` is a single column — one photo per element — so replacing it is + * destructive, and comparing the incoming shot against the stored one *before* the overwrite is the + * only progress signal available without a schema change and a migration. + * + * Two results ride back on the response: + * + * * `quality` — a blurred or blown-out photo is stored anyway but reported as unusable. Refusing + * it outright would be wrong: a field engineer in a dark riser may have no better shot, and + * losing the evidence is worse than keeping a poor frame. Flagging it lets the app offer a + * retake while the person is still standing there, which is the only time a retake is cheap. + * * `change` — present only when this element already had a photo. Read `change.confidence` + * before believing `change_score`; see `photo_cv.compare_photos`. + * * `duplicate` — **R37-TESTED-UNWIRED.** The same shot already filed against a DIFFERENT element + * in this project. `photo_cv.duplicate_of` was built and tested for exactly the abuse its + * docstring names — *one photo uploaded against thirty elements to clear a checklist* — and had + * no caller: this route ran `photo_quality` and `compare_photos` and never it. So the abuse was + * unguarded on the only path where it can happen. + * + * **The duplicate is reported, not refused, and that is the same judgement as `quality` above.** + * Two elements can legitimately share a frame — a wall and the conduit crossing it, an assembly + * photographed once. The system cannot tell that from checklist-clearing, and a person reviewing the + * verification set can. Refusing would also make the check adversarial: whoever wanted to defeat it + * would take one step to the left, and the record of what happened would be gone. Flagged, the shot + * is still on file and the pattern is visible across the set. + * + * **`duplicate.compared_against` rides with it, and is the load-bearing field.** The comparison can + * only see photos that were hashed, and nothing before v0.3.1115 was — the column is not backfilled. + * Without a count, "no duplicate" on a project with three hundred unhashed photos is indistinguishable + * from "checked all three hundred", and the second is the reading anybody makes. It is the same + * reasoning as `aec_jobs_stats_ok` in `metrics.render_queue`: the number that says whether the other + * number means anything. + * + * **Analysis never fails the upload — including when the bytes cannot be decoded at all.** The + * first version refused an undecodable file with a 400, on the reasoning that a non-image under a + * verification record destroys the record's evidence value. That was wrong twice over. + * + * It contradicted the paragraph above it: if a blurred frame is worth keeping because the engineer + * may have no better shot, an unparseable one is worth keeping for exactly the same reason. And it + * would have rejected the most likely real field photo on the platform — **iPhones shoot HEIC by + * default, and Pillow cannot decode HEIC without `pillow-heif`**, which is not a dependency here. + * A gate meant to protect evidence would have thrown away the evidence. + * + * So an undecodable upload is stored with `quality.analysed = False` and the decoder's complaint. + * The API cannot tell "corrupt" from "a format we lack a codec for", and between silently + * discarding a real photo and keeping one it could not read, keeping is the recoverable error. + */ + post: operations["upload_photo_projects__pid__verification__guid__photo_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verified-progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Verified Progress Index + * @description Roll element-level field verification up to each schedule activity: verified-in-place % vs the + * claimed % complete, with the trust gap (claimed − verified) worst-first. No model parse — reads the + * published element index + the field_verification / schedule_activity records. + */ + get: operations["verified_progress_index_projects__pid__verified_progress_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/verified-progress/from-layout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Verified Progress From Layout + * @description Run the as-installed layout check (`{measured, tolerance_m?, classes?}`) and create a + * field_verification record per checked point — in-tolerance → verified, out-of-tolerance → deviated, + * each anchored to its element GlobalId. Then returns the refreshed verified-progress rollup. + */ + post: operations["verified_progress_from_layout_projects__pid__verified_progress_from_layout_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/versions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Versions + * @description Model version history — one snapshot per publish (version, element count, +N/-N note). + */ + get: operations["list_versions_projects__pid__versions_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/versions/cost-delta": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Version Cost Delta + * @description REVISION-DELTA: the conceptual **cost impact** of the revision a→b. ADDED elements (present in the + * current model) are priced from the live takeoff; REMOVED elements are counted by IFC class (prior + * quantities aren't stored, so not priced); quantity-modified elements are flagged for re-estimate. + * A change-management aid, not a change order. 409 if the project has no source IFC. + */ + get: operations["version_cost_delta_projects__pid__versions_cost_delta_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/versions/diff": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Diff Versions + * @description Changed elements between two model versions — added / removed GUIDs + unchanged count. + */ + get: operations["diff_versions_projects__pid__versions_diff_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/versions/{version}/review": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Review Version + * @description MODEL-PUBLISH (R18) — the review gate over the model's publish history: `submit` a draft + * snapshot for review, `approve` or `reject` it (reject returns it to draft), with who/when and an + * optional note. The model file pointer is never touched — this is the QA record teams gate issuance + * on ("issue drawings only from approved versions"). Illegal transition → 409; unknown version → 404. + * Body: `{action: submit|approve|reject, note?}`. + * + * **`approve` is refused for the `api-key` identity.** Not a permission tidy-up — `approved` is a + * TERMINAL state (`_REVIEW_ACTIONS` has no transition out of it), so `reviewed_by` is a permanent + * answer to "who approved the model this drawing set was issued from", and "api-key" is not a who. + * The api-key is a machine credential with no person behind it, which is the same argument + * `/auth/step-up` makes and the reason it refuses that identity outright. + * + * Deliberately NOT a full step-up. A seal is a per-document legal attestation carrying personal + * liability, so it is worth a password per document; this is an internal QA record, and demanding a + * re-authentication on every model approval would be a contract change for every human caller in + * exchange for a much smaller claim. What the step-up thinking actually yields here is narrower: + * the *record* must name a person, so only the identity that cannot be one is refused. + * + * `submit` and `reject` stay open to the api-key — neither is terminal, both are reversible by the + * next action, and blocking automation from moving a draft into review buys nothing. + */ + post: operations["review_version_projects__pid__versions__version__review_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/view-templates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * View Templates Get + * @description The project's saved view templates (empty until saved). + */ + get: operations["view_templates_get_projects__pid__view_templates_get"]; + /** + * View Templates Put + * @description Replace the project's view templates. Every template is validated (selectors parse, colors are + * #rrggbb, caps hold) before anything is written — a bad template rejects the whole save with 422. + */ + put: operations["view_templates_put_projects__pid__view_templates_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/view-templates/{tid}/graphics": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * View Template Graphics + * @description R21-VG-OVERRIDES — the full graphic state per visible element: weight, colour, pattern and + * halftone, resolved SEPARATELY for the cut side and the projection side. + * + * Cut lines are heavier than projection lines; that weight difference — not colour — is how a reader + * reads depth off a flat sheet, and it is what makes output look like a drawing rather than a dump. + * Overrides layer as partials, so a rule that names only a colour never flattens the weights beneath + * it. `cut` is an optional comma-separated GUID list naming what the view plane actually cuts. + */ + get: operations["view_template_graphics_projects__pid__view_templates__tid__graphics_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/view-templates/{tid}/resolve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * View Template Resolve + * @description Resolve one template against the loaded model → the deterministic visible GUID list + color map + * (later rules win) — the one answer the viewer AND the drawing generators consume. 404 unknown id. + */ + get: operations["view_template_resolve_projects__pid__view_templates__tid__resolve_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/views/alerts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * View Alerts + * @description Saved-search alert feed: each of my saved views with its total + new-since-last-seen counts. + */ + get: operations["view_alerts_projects__pid__views_alerts_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/vitals": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Vitals + * @description R26-VITALS — the six numbers the bottom strip shows on every room. + * + * **LOD · area · $/ft² · float · IRR · health.** + * + * One endpoint rather than six client fetches, deliberately. The redesign audit's finding 03 is + * that the app contradicts itself on screen — the same project scored 24 for model health in one + * workspace and 77 in another. Assembling these in the browser would rebuild that: five requests, + * five chances to render a number that disagrees with the panel beside it. + * + * Each engine is called through the entry point that already owns its number — nothing is + * recomputed here — and each is wrapped so a project that has no schedule, no proforma or no + * spaces yields `None` with a reason rather than failing the strip. A vitals bar must not be able + * to take the page down. + */ + get: operations["get_vitals_projects__pid__vitals_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/warranties/expiring": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Warranties Expiring + * @description Warranties expiring within `within_days` (and any already expired) — turnover tracking so + * expiries don't lapse silently. Reads the `expires` date on each warranty record. + */ + get: operations["warranties_expiring_projects__pid__warranties_expiring_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/waterfall": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Waterfall Scenario + * @description Run a distribution / equity-waterfall scenario over the cap table. Body: {distributable:[..], + * dates:[..]} or {exit_amount, contribution_date, exit_date}; optional pref_rate/tiers/style/clawback. + * Returns LP/GP totals, IRR/EM, period splits, and the per-investor allocation. + */ + post: operations["waterfall_scenario_projects__pid__waterfall_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/wip": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Wip Schedule + * @description Work-in-Progress schedule: percentage-of-completion → earned revenue vs billed → + * over-/under-billing (contract liability / asset), retainage, gross profit and backlog. The + * accounting twin to the earned-value module. + * + * `method=cost-to-cost` (default) drives POC by cost-to-date ÷ estimated cost; `method=units-installed` + * drives it by physical model progress (installed elements ÷ total, by IFC GlobalId). When a model is + * loaded the response carries a `model` block cross-checking physical vs cost progress either way. + */ + get: operations["wip_schedule_projects__pid__wip_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/wip/model-progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Wip Model Progress + * @description Physical percent-complete straight from the model: installed elements ÷ total, keyed by IFC + * GlobalId, optionally weighted by an IFC base quantity (e.g. `quantity=NetVolume`). The independent + * 'units-installed' progress signal that cross-checks cost-to-cost POC. + */ + get: operations["wip_model_progress_projects__pid__wip_model_progress_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/work-queue": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Work Queue + * @description R26-WORK-QUEUE — `my-work`, dated, bucketed by urgency, with the actions this caller can run. + * + * Built ON `my_work` rather than beside it: a second definition of "in my court" is how two screens + * come to disagree. `undated` is a bucket of its own, above `later`, because an item nobody dated is + * a gap somebody should close, not a low priority. + */ + get: operations["work_queue_projects__pid__work_queue_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{pid}/workflow/{key}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Workflow + * @description WFE-3 — this project's effective workflow for a module: the shipped transitions, or its own + * override. `overridden` says which you're looking at, and `record_counts` shows where records + * actually sit, because that is what constrains any change you make. + */ + get: operations["get_workflow_projects__pid__workflow__key__get"]; + /** + * Put Workflow + * @description Replace this project's workflow for a module. **Refused** (422) when the override would strand + * records that already exist — a state holding live records with no transition out. Rewiring which + * of the module's *declared* states connect is allowed; inventing new states is not. + */ + put: operations["put_workflow_projects__pid__workflow__key__put"]; + post?: never; + /** + * Delete Workflow + * @description Drop the override — back to the shipped workflow. + */ + delete: operations["delete_workflow_projects__pid__workflow__key__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/re-syndication/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Re Syndication Status + * @description Whether the WPRealWise / MLS syndication bridge is configured (off unless REALWISE_URL+key set). + */ + get: operations["re_syndication_status_re_syndication_status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Ready + * @description Readiness — the process can serve real traffic (DB reachable). Pings the DB with a + * trivial `SELECT 1`; returns 503 if it's unreachable so a load balancer / orchestrator stops + * routing to (or restarts) this instance instead of serving 500s. Kept separate from /health + * so a DB blip doesn't kill a still-live process. The ping runs under a hard wall-clock timeout + * so a black-holed DB (paused host / network partition) yields a prompt 503 instead of hanging + * the probe itself. + */ + get: operations["ready_ready_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/reference/authoring-matrix": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Reference Authoring Matrix + * @description AUTHOR-MATRIX: the live authoring-coverage matrix — every GUID-stable edit recipe categorized by + * concern (create-structure / -enclosure / -mep / annotate / edit / type / group / data / lifecycle / + * analysis) with its IFC output. Derived from `edit.RECIPES`, so it never drifts from what's built. + */ + get: operations["reference_authoring_matrix_reference_authoring_matrix_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/reference/disciplines": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Reference Disciplines + * @description The Discipline Spine vocabularies: NCS disciplines (with their default MasterFormat divisions + + * Uniformat groups), the MasterFormat division master, and the Uniformat↔MasterFormat crosswalk. + * Drives the discipline/division selects and the model→sheets→specs→bid→budget joins. + */ + get: operations["reference_disciplines_reference_disciplines_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Report Catalog + * @description The available reports (id, name, group) for the Reports panel. + */ + get: operations["report_catalog_reports_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/rooms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Rooms + * @description R26 — the five-room spine, and the allocation of every module to exactly one room. + * + * Constant for every role: a workspace *weights* the spine, it never replaces it. `unplaced` must + * always be empty — a module with no room is one a user can no longer reach, which is the failure an + * IA restructure is most likely to cause and least likely to notice (`test_module_rooms` gates it). + */ + get: operations["list_rooms_rooms_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/routines/due": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Routines Due + * @description R22-ROUTINES — which recurring runs should be enqueued now, and why the rest were skipped. + * + * Body: `{routines:[{id, kind, cadence, project_id?, enabled?}], last_runs:{id: date}, + * in_flight:[id], now?: iso-datetime}`. `now` is accepted so a caller can evaluate a boundary + * deterministically; omitted, the server clock is used. + * + * **Catch-up is reported, never replayed.** A routine three windows behind fires ONCE with + * `missed_windows: 3`. Replaying closed windows duplicates reports into the same inbox and bills + * repeat calls to a paid API — the outage is information, not a backlog to work through. + * + * Two further refusals: a routine whose previous run is still in flight is not re-enqueued + * (recurrence plus a slow job is a pile-up that reads as a load problem and is a scheduling bug), + * and **cadence is a closed set** — an unknown cadence is refused and the known ones listed rather + * than being treated as daily, because a routine running on a schedule nobody chose is worse than + * one that does not run. + * + * Skips are returned WITH their reasons: "nothing ran" and "nothing was due" are different facts, + * and a scheduler that reports only what it fired cannot tell you which. + */ + post: operations["routines_due_routines_due_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/samples": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Samples + * @description The showcase library — packaged `.mass` containers, each described from its own manifest. + * + * Not project-scoped, and deliberately unauthenticated: this is the content a first-time visitor + * sees before they have an account, and it is the same content we ship in the repo. Nothing here + * reads the database. + */ + get: operations["list_samples_samples_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/samples/{sample_id}/open": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Open Sample + * @description Open a library sample as a new project. + * + * Runs through `import_bundle` — the identical path a user's own `.mass` takes — so a sample can + * never demonstrate behaviour the product does not actually have. A separate "load the demo" code + * path is how a demo drifts away from the thing it is demonstrating. + */ + post: operations["open_sample_samples__sample_id__open_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/schedule/takt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Takt + * @description Takt / line-of-balance plan — trades flow floor-to-floor at a steady production rate, with a + * just-in-time delivery plan (R2, the Empire State 'vertical assembly line'). + */ + post: operations["schedule_takt_schedule_takt_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/schedule/takt.svg": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Schedule Takt Svg + * @description Line-of-balance (takt) chart as SVG — floors vs days, one line per trade (R2). + */ + get: operations["schedule_takt_svg_schedule_takt_svg_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/schedule/takt/progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Schedule Takt Progress + * @description Actual-vs-takt tracking (R2/R4): compare each trade's actual floors complete against the + * line-of-balance plan → floor variance (+ahead/−behind), achieved production rate (floors/week), + * and an on/ahead/behind read. `actuals` = [{trade, floors_done, as_of_day?}, …]. + */ + post: operations["schedule_takt_progress_schedule_takt_progress_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/scim/v2/ResourceTypes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Scim Resource Types */ + get: operations["scim_resource_types_scim_v2_ResourceTypes_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/scim/v2/ServiceProviderConfig": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scim Config + * @description Advertise the supported feature set (RFC 7643 §5). We support filter + PATCH, not bulk/sort/etag. + */ + get: operations["scim_config_scim_v2_ServiceProviderConfig_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/scim/v2/Users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scim List Users + * @description List (or, with `filter=userName eq "x"`, look up) provisioned users. 1-based paging (RFC 7644). + */ + get: operations["scim_list_users_scim_v2_Users_get"]; + put?: never; + /** + * Scim Create User + * @description Provision a new SSO-only account. Idempotent-ish: re-activating an existing username updates it + * (some IdPs POST on re-provision) rather than 409-ing, so a rehire cleanly reactivates. + */ + post: operations["scim_create_user_scim_v2_Users_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/scim/v2/Users/{user_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Scim Get User */ + get: operations["scim_get_user_scim_v2_Users__user_id__get"]; + /** + * Scim Replace User + * @description Full replace of the mutable attributes (userName is immutable — the id). + */ + put: operations["scim_replace_user_scim_v2_Users__user_id__put"]; + post?: never; + /** + * Scim Delete User + * @description De-provision. Soft-delete: deactivate + revoke tokens (keeps the audit trail / record authorship + * intact) rather than hard-deleting the row. A subsequent POST reactivates (rehire). + */ + delete: operations["scim_delete_user_scim_v2_Users__user_id__delete"]; + options?: never; + head?: never; + /** + * Scim Patch User + * @description Partial update (RFC 7644 §3.5.2). The common case is deactivation on offboarding. + */ + patch: operations["scim_patch_user_scim_v2_Users__user_id__patch"]; + trace?: never; + }; + "/scope-library": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scope Library List + * @description The scope-of-work clause library used to compose Exhibit A (ids + titles, no bodies). + * + * `division` (a CSI MasterFormat key) or `trade` (which resolves to one) narrows the catalog to that + * division plus the universal clauses. The filter is not a nicety: the library spans every + * MasterFormat division, and an unnarrowed composer asks somebody to pick a subcontract exhibit out + * of a list where most entries belong to other trades. The response carries `divisions` so a caller + * can offer the filter without knowing the catalog. + * + * No clause count is quoted here on purpose. This docstring is published into `/openapi.json`, so a + * number in it is a live claim to every API consumer that nothing re-checks — the same reason the + * web standards stopped quoting a test count. `GET /scope-library` with no filter returns the + * current catalog; that is the number, and it cannot drift from itself. + * + * `trade` is resolved server-side rather than by the caller so the mapping lives in one place; an + * unrecognised trade returns the whole catalog rather than an empty one, which is the same + * fall-back `default_ids` makes. + * + * `exhibit_categories` says which of the returned clauses can actually go INTO Exhibit A, so a + * composer can offer only those without restating the rule. It is not decoration: `library()` + * returns clauses whose division matches **or is None**, and every `gc-*`/`sc-*` conditions clause + * has division None — so a narrowed catalog still contains clauses the exhibit renderer will drop. + * Without this the picker offers a tick that silently does nothing, which teaches a user the control + * is broken. + * + * Served rather than hardcoded client-side because a second copy of this exact rule is what caused + * the preview/document divergence: the route filtered and the PDF did not. One authority, read by + * everyone who needs it. + */ + get: operations["scope_library_list_scope_library_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/scope-library/exhibit": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Scope Library Exhibit + * @description Assemble a scope of work: the clauses that would go into Exhibit A, numbered, with bodies. + * + * This is the read-only half of the exhibit that `document.pdf?doc=exhibit` renders, and it exists + * so the composer can show what it is about to produce **before** a PDF is generated and attached to + * a record. It takes the same `clauses` override and the same trade default, so what is previewed + * here and what is signed there come from one code path rather than two that drift. + * + * Only exhibit categories are returned — inclusions, exclusions, clarifications. The conditions + * clauses belong to the agreement body, not to Exhibit A, and returning them here would invite a + * caller to render them twice in one contract package. + */ + get: operations["scope_library_exhibit_scope_library_exhibit_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/securities-syndication/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Securities Syndication Status + * @description Whether the capital-markets syndication bridge is configured (off unless a platform URL+key set). + * This connector syncs the investor ledger only and never moves money. + */ + get: operations["securities_syndication_status_securities_syndication_status_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings/integrations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Integrations + * @description Integration config for the Settings panel. Secret values are never returned — only + * whether each is configured. + */ + get: operations["get_integrations_settings_integrations_get"]; + /** + * Put Integrations + * @description Set/clear integration settings. A value here overrides the matching env var; an empty + * string clears it. Keys are validated against the catalog; secret values are not echoed back. + */ + put: operations["put_integrations_settings_integrations_put"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings/integrations/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Test Integration + * @description Live 'Test connection' for one integration group (by its catalog name) → {ok, message}. + * Gives a non-technical admin instant confirmation a key actually works. + */ + post: operations["test_integration_settings_integrations_test_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/shared/{token}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Shared Page + * @description PUBLIC (no auth) — a self-contained read-only HTML page rendering the share digest (the same + * curated readiness data as the .json digest). All values are HTML-escaped. Unknown/revoked → 404. + */ + get: operations["shared_page_shared__token__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/shared/{token}/comment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Shared Comment + * @description PUBLIC (no auth) — PORTAL-TXN phase 3: post a client comment through a live share token. The + * comment lands on the token's dedicated **feedback topic** (a real BCF topic), so the project team + * reads + answers it in the Issue Board and it round-trips through BCF export. Length-capped, hard + * per-thread cap. Body: `{text, client_name?}`. Unknown/revoked token → 404. + */ + post: operations["shared_comment_shared__token__comment_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/shared/{token}/decision": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Shared Decision + * @description PUBLIC (no auth) — PORTAL-TXN: record a client decision through a live share token — a timestamped, + * token-stamped **approve / acknowledge / decline** on a shared item. NOT a payment and NOT an e-signature + * of record. Inputs are whitelisted + length-capped and each token carries a hard decision cap. Body: + * `{item_type, item_ref, action, client_name?, note?}`. Unknown/revoked token → 404. + */ + post: operations["shared_decision_shared__token__decision_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/shared/{token}/digest": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Shared Digest + * @description PUBLIC (no auth) — the curated read-only project digest for a valid share token. High-level + * readiness only; no record-level data. An unknown or revoked token returns 404 (no enumeration). + */ + get: operations["shared_digest_shared__token__digest_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/shared/{token}/model.frag": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Shared Model + * @description R22-PUBLIC-VIEWER — the project's geometry fragment, for a token minted with `show_model`. + * + * Serves `{pid}/model.frag`: shapes and placements. It does NOT serve the source IFC, which carries + * every property set, classification and GlobalId — a different and much larger disclosure that + * "share the model" is easily read as including. + * + * Unknown token, revoked token, token without the opt-in, and project with no fragment ALL return + * the same 404. Distinguishing them would reveal whether a token exists, whether it was revoked, and + * whether a project has a model — three enumeration signals for no benefit to a legitimate viewer. + * + * No auth dependency by design: the token IS the credential, exactly as for the digest. Revoking + * stops this immediately, because the check is on the row and not on anything cached. + */ + get: operations["shared_model_shared__token__model_frag_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/stamps/library": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Stamps Library + * @description The A/E/C stamp template library — review (EJCDC + CSI), inspection, status, and seal templates. + * The client renders the picker and preview from this; the server is the source of truth. + */ + get: operations["stamps_library_stamps_library_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/structure/recommend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Structure Recommend + * @description Recommend a structural system + rough member sizes + load path for a building's scale (R3, + * Salvadori). Stateless; the same advisor drives the generated frame's member sizing. + */ + post: operations["structure_recommend_structure_recommend_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/templates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Templates */ + get: operations["list_templates_templates_get"]; + put?: never; + /** Create Template */ + post: operations["create_template_templates_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/templates/{tid}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Template */ + delete: operations["delete_template_templates__tid__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/test-fit/compare": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Test Fit Compare + * @description Compare unit-mix schemes on a floor plate — yield metrics (units, efficiency, NSF/GSF, mix) + * + parking — ranked so you can find the scheme that pencils. Stateless; the rects also feed the + * IFC massing generator (unit_layout='corridor'). + */ + post: operations["test_fit_compare_test_fit_compare_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/test-fit/optimize": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Test Fit Optimize + * @description Generative design — sweep unit-mix × parking presets, filter by targets, rank by yield-on-cost + * (or another objective). With `pid`, seed the econ from the project's real land price + cost budget + * so the ranking reflects the actual deal, not a generic proxy (U6). + * + * **`pid` arrives in the BODY**, so it is authorised by `_authorize_optimize_pid` above rather than + * by `require_role` (whose dependency resolves `pid` from the path or query and structurally cannot + * reach a body field). Without `pid` this is a pure calculator over caller-supplied numbers and + * stays open, like its four siblings in this file. With `pid` it reads another project's + * `purchase_price` and hard $/sf, + * which is confidential deal data and needs the same gate a `/projects/{pid}` route would carry. + * + * It had none. `require_role`'s inner signature is `dep(pid: str, ...)`, so FastAPI resolves that + * `pid` from the path or query and it cannot cover a body field; `test_route_authz` only walks + * `/projects/{pid}` routes; `/test-fit` is not in `main._PROTECTED_PREFIXES`; and the route sat in + * `test_global_authz`'s frozen baseline labelled "stateless compute" — **the only one of the five + * routes under that label that takes a `Session`**. Four independent mechanisms, none of which + * could see it, and an exemption list that had absorbed it. + * + * Seeding is also not the whole exposure: the seeded values are not echoed back, but the caller + * controls `plate_w`, `plate_d`, `floors` and the rest of `econ`, so the ranking is invertible — + * vary the inputs, observe the yield, solve for the seed. "Not returned verbatim" would have been + * the comfortable reading rather than the correct one. + */ + post: operations["test_fit_optimize_test_fit_optimize_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webhooks/deliveries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Webhook Deliveries + * @description Recent outbound-webhook delivery attempts (newest first) — url, event, ok, status, attempts, + * error — plus whether HMAC signing is configured. Process-local ring; for 'did my hook fire?'. + */ + get: operations["webhook_deliveries_webhooks_deliveries_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/wip/portfolio": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Wip Portfolio + * @description WIP across your projects — one row each, worst cash position (largest under-billing) first. + */ + get: operations["wip_portfolio_wip_portfolio_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** Actual */ + Actual: { + /** + * Actual To Date + * @default 0 + */ + actual_to_date: number; + /** + * Committed + * @default 0 + */ + committed: number; + /** Cost To Complete */ + cost_to_complete?: number | null; + }; + /** Assumptions */ + Assumptions: { + timing: components["schemas"]["Timing"]; + /** Cost Lines */ + cost_lines: components["schemas"]["CostLine"][]; + debt: components["schemas"]["Debt"]; + equity: components["schemas"]["Equity"]; + operations: components["schemas"]["Ops"]; + exit: components["schemas"]["Exit"]; + waterfall: components["schemas"]["Waterfall"]; + /** + * Discount Rate + * @default 0.1 + */ + discount_rate: number; + tax?: components["schemas"]["Tax"] | null; + /** Sources */ + sources?: { + [key: string]: { + [key: string]: unknown; + }[]; + } | null; + }; + /** AttachmentOut */ + AttachmentOut: { + /** Id */ + id: string; + /** Topic Id */ + topic_id: string; + /** Filename */ + filename: string; + /** Content Type */ + content_type: string | null; + /** Size */ + size: number; + /** Kind */ + kind: string; + /** Storage Key */ + storage_key: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** Axis */ + Axis: { + /** Path */ + path: string; + /** Values */ + values: number[]; + }; + /** + * BlankModelIn + * @description Create a blank authoring model — the from-scratch start for the in-browser modeler. + */ + BlankModelIn: { + /** + * Name + * @default New Model + */ + name: string; + /** + * Storeys + * @default 3 + */ + storeys: number; + /** + * Storey Height + * @default 3.5 + */ + storey_height: number; + }; + /** Body_add_attachment_projects__pid__topics__tid__attachments_post */ + Body_add_attachment_projects__pid__topics__tid__attachments_post: { + /** + * Kind + * @default file + */ + kind: string; + /** File */ + file: string; + }; + /** Body_add_comment_projects__pid__modules__key___rid__comments_post */ + Body_add_comment_projects__pid__modules__key___rid__comments_post: { + /** Text */ + text: string; + }; + /** Body_add_enum_option_projects__pid__modules__key__enum__field__post */ + Body_add_enum_option_projects__pid__modules__key__enum__field__post: { + /** Value */ + value: string; + }; + /** Body_add_project_model_projects__pid__models_post */ + Body_add_project_model_projects__pid__models_post: { + /** File */ + file: string; + /** + * Discipline + * @default Model + */ + discipline: string; + }; + /** Body_ai_author_projects__pid__ai_author_post */ + Body_ai_author_projects__pid__ai_author_post: { + /** Text */ + text: string; + /** + * Context + * @default {} + */ + context: { + [key: string]: unknown; + }; + }; + /** Body_apply_template_projects__pid__responsibility_apply_template_post */ + Body_apply_template_projects__pid__responsibility_apply_template_post: { + /** Key */ + key: string; + /** + * Mode + * @default RACI + */ + mode: string; + }; + /** Body_asset_rights_verify_asset_rights_verify_post */ + Body_asset_rights_verify_asset_rights_verify_post: { + /** Manifest */ + manifest: { + [key: string]: unknown; + }; + /** Public Key */ + public_key?: string | null; + }; + /** Body_assign_record_projects__pid__modules__key___rid__assign_post */ + Body_assign_record_projects__pid__modules__key___rid__assign_post: { + /** Assignee */ + assignee?: string | null; + }; + /** Body_bake_layers_projects__pid__layers_bake_post */ + Body_bake_layers_projects__pid__layers_bake_post: { + /** + * Publish + * @default true + */ + publish: boolean; + }; + /** Body_bcf3_add_document_bcf_3_0_projects__pid__topics__guid__document_references_post */ + Body_bcf3_add_document_bcf_3_0_projects__pid__topics__guid__document_references_post: { + /** File */ + file: string; + }; + /** Body_bcf_import_projects__pid__bcf_import_post */ + Body_bcf_import_projects__pid__bcf_import_post: { + /** File */ + file: string; + }; + /** Body_bulk_action_projects__pid__modules__key__bulk_post */ + Body_bulk_action_projects__pid__modules__key__bulk_post: { + /** Ids */ + ids: string[]; + /** Action */ + action: string; + /** Value */ + value?: string | null; + }; + /** Body_capital_call_projects__pid__capital_call_post */ + Body_capital_call_projects__pid__capital_call_post: { + /** Amount */ + amount: number; + /** + * Persist + * @default false + */ + persist: boolean; + }; + /** Body_capture_baseline_projects__pid__schedule_baselines_post */ + Body_capture_baseline_projects__pid__schedule_baselines_post: { + /** + * Name + * @default + */ + name: string; + }; + /** Body_certify_projects__pid__turnover_certify_post */ + Body_certify_projects__pid__turnover_certify_post: { + /** Cert Rid */ + cert_rid: string; + /** Architect */ + architect: string; + /** Owner */ + owner?: string | null; + /** Contractor */ + contractor?: string | null; + /** Occupancy Date */ + occupancy_date?: string | null; + }; + /** Body_change_password_auth_password_post */ + Body_change_password_auth_password_post: { + /** Current */ + current: string; + /** New */ + new: string; + }; + /** Body_classify_plan_projects__pid__classify_plan_post */ + Body_classify_plan_projects__pid__classify_plan_post: { + /** Accept */ + accept: string[]; + /** + * System + * @default masterformat + */ + system: string; + /** Edition */ + edition?: string | null; + }; + /** Body_classify_projects__pid__ifc_classify_post */ + Body_classify_projects__pid__ifc_classify_post: { + /** Elements */ + elements?: { + [key: string]: unknown; + }[] | null; + }; + /** Body_clone_scenario_proforma_scenarios__sid__clone_post */ + Body_clone_scenario_proforma_scenarios__sid__clone_post: { + /** Name */ + name: string; + }; + /** Body_code_check_projects__pid__codecheck_post */ + Body_code_check_projects__pid__codecheck_post: { + /** + * Description + * @default + */ + description: string; + /** Context */ + context?: string | null; + }; + /** Body_connection_query_connections__cid__query_post */ + Body_connection_query_connections__cid__query_post: { + /** Sql */ + sql: string; + /** + * Limit + * @default 200 + */ + limit: number; + }; + /** Body_content_import_projects__pid__content_import_post */ + Body_content_import_projects__pid__content_import_post: { + /** File */ + file: string; + }; + /** Body_convert_citygml_convert_citygml_post */ + Body_convert_citygml_convert_citygml_post: { + /** File */ + file: string; + }; + /** Body_convert_convert_post */ + Body_convert_convert_post: { + /** File */ + file: string; + }; + /** Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post */ + Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post: { + /** File */ + file: string; + }; + /** Body_coordination_import_xml_projects__pid__coordination_import_xml_post */ + Body_coordination_import_xml_projects__pid__coordination_import_xml_post: { + /** File */ + file: string; + }; + /** Body_coordination_stale_recheck_projects__pid__coordination_stale_recheck_post */ + Body_coordination_stale_recheck_projects__pid__coordination_stale_recheck_post: { + /** A */ + a: number; + /** B */ + b: number; + }; + /** Body_create_user_auth_users_post */ + Body_create_user_auth_users_post: { + /** Username */ + username: string; + /** Password */ + password: string; + /** + * Role + * @default user + */ + role: string; + /** Email */ + email?: string | null; + }; + /** Body_deliver_artifact_projects__pid__jobs__job_id__deliver_post */ + Body_deliver_artifact_projects__pid__jobs__job_id__deliver_post: { + /** To */ + to: string[]; + /** + * Note + * @default + */ + note: string; + }; + /** Body_design_options_board_projects__pid__design_options_board_pdf_post */ + Body_design_options_board_projects__pid__design_options_board_pdf_post: { + /** Options */ + options: { + [key: string]: unknown; + }[]; + /** Weights */ + weights?: { + [key: string]: unknown; + } | null; + }; + /** Body_design_options_generate_projects__pid__design_options_generate_post */ + Body_design_options_generate_projects__pid__design_options_generate_post: { + /** Base */ + base: { + [key: string]: unknown; + }; + /** Far Steps */ + far_steps?: number[] | null; + /** Types */ + types?: string[] | null; + }; + /** Body_design_options_score_projects__pid__design_options_score_post */ + Body_design_options_score_projects__pid__design_options_score_post: { + /** Options */ + options: { + [key: string]: unknown; + }[]; + /** Weights */ + weights?: { + [key: string]: unknown; + } | null; + }; + /** Body_distribution_projects__pid__distribution_post */ + Body_distribution_projects__pid__distribution_post: { + /** Amount */ + amount: number; + /** + * Persist + * @default false + */ + persist: boolean; + }; + /** Body_documents_file_model_projects__pid__documents_file_model_post */ + Body_documents_file_model_projects__pid__documents_file_model_post: { + /** + * Title + * @default Federated Model + */ + title: string; + /** Discipline */ + discipline?: string | null; + /** + * Cde State + * @default published + */ + cde_state: string; + }; + /** Body_documents_move_projects__pid__documents__fid__move_post */ + Body_documents_move_projects__pid__documents__fid__move_post: { + /** Path */ + path: string; + }; + /** Body_documents_upload_projects__pid__documents_upload_post */ + Body_documents_upload_projects__pid__documents_upload_post: { + /** Path */ + path: string; + /** File */ + file: string; + /** Title */ + title?: string | null; + /** Discipline */ + discipline?: string | null; + /** Doc Type */ + doc_type?: string | null; + /** Cde State */ + cde_state?: string | null; + /** Revision */ + revision?: string | null; + }; + /** Body_draft_rfi_ep_projects__pid__draft_rfi_post */ + Body_draft_rfi_ep_projects__pid__draft_rfi_post: { + /** + * Note + * @default + */ + note: string; + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_draft_rfi_projects__pid__ai_draft_rfi_post */ + Body_draft_rfi_projects__pid__ai_draft_rfi_post: { + /** + * Element + * @default {} + */ + element: { + [key: string]: unknown; + }; + /** Note */ + note?: string | null; + }; + /** Body_draft_scope_ep_projects__pid__draft_scope_post */ + Body_draft_scope_ep_projects__pid__draft_scope_post: { + /** + * Trade + * @default General + */ + trade: string; + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post */ + Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post: { + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_edit_batch_projects__pid__edit_batch_post */ + Body_edit_batch_projects__pid__edit_batch_post: { + /** Steps */ + steps: { + [key: string]: unknown; + }[]; + /** + * Publish + * @default false + */ + publish: boolean; + /** Base Source */ + base_source?: string | null; + }; + /** Body_edit_graph_projects__pid__edit_graph_post */ + Body_edit_graph_projects__pid__edit_graph_post: { + /** Graph */ + graph: { + [key: string]: unknown; + }; + /** + * Publish + * @default false + */ + publish: boolean; + /** Base Source */ + base_source?: string | null; + }; + /** Body_edit_precheck_projects__pid__edit_precheck_post */ + Body_edit_precheck_projects__pid__edit_precheck_post: { + /** Recipe */ + recipe: string; + /** + * Params + * @default {} + */ + params: { + [key: string]: unknown; + }; + }; + /** Body_edit_preview_projects__pid__edit_preview_post */ + Body_edit_preview_projects__pid__edit_preview_post: { + /** Recipe */ + recipe: string; + /** + * Params + * @default {} + */ + params: { + [key: string]: unknown; + }; + }; + /** Body_edit_projects__pid__edit_post */ + Body_edit_projects__pid__edit_post: { + /** Recipe */ + recipe: string; + /** + * Params + * @default {} + */ + params: { + [key: string]: unknown; + }; + /** + * Publish + * @default false + */ + publish: boolean; + /** Base Source */ + base_source?: string | null; + /** Want Guid */ + want_guid?: string | null; + }; + /** Body_edit_redo_projects__pid__edit_redo_post */ + Body_edit_redo_projects__pid__edit_redo_post: { + /** + * Publish + * @default true + */ + publish: boolean; + }; + /** Body_edit_undo_projects__pid__edit_undo_post */ + Body_edit_undo_projects__pid__edit_undo_post: { + /** + * Publish + * @default true + */ + publish: boolean; + }; + /** Body_enqueue_job_projects__pid__jobs_post */ + Body_enqueue_job_projects__pid__jobs_post: { + /** Kind */ + kind: string; + /** Params */ + params?: { + [key: string]: unknown; + } | null; + }; + /** Body_evm_capture_projects__pid__evm_snapshot_post */ + Body_evm_capture_projects__pid__evm_snapshot_post: { + /** Data Date */ + data_date?: string | null; + /** Period Label */ + period_label?: string | null; + /** Notes */ + notes?: string | null; + }; + /** Body_extract_sheets_ep_projects__pid__extract_sheets_post */ + Body_extract_sheets_ep_projects__pid__extract_sheets_post: { + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + /** + * Create + * @default false + */ + create: boolean; + }; + /** Body_firm_rules_put_firm_rules_put */ + Body_firm_rules_put_firm_rules_put: { + /** Rules */ + rules: { + [key: string]: unknown; + }[]; + }; + /** Body_forecast_stateless_proforma_forecast_post */ + Body_forecast_stateless_proforma_forecast_post: { + assumptions: components["schemas"]["Assumptions"]; + /** Actuals */ + actuals: components["schemas"]["Actual"][]; + /** + * As Of Month + * @default 0 + */ + as_of_month: number; + }; + /** Body_heartbeat_projects__pid__presence_post */ + Body_heartbeat_projects__pid__presence_post: { + /** Viewpoint */ + viewpoint?: { + [key: string]: unknown; + } | null; + }; + /** Body_import_bundle_projects_import_bundle_post */ + Body_import_bundle_projects_import_bundle_post: { + /** File */ + file: string; + /** Name */ + name?: string | null; + }; + /** Body_import_families_projects__pid__families_import_post */ + Body_import_families_projects__pid__families_import_post: { + /** File */ + file: string; + }; + /** Body_import_family_pack_projects__pid__families_import_pack_post */ + Body_import_family_pack_projects__pid__families_import_pack_post: { + /** Pack */ + pack: string; + /** + * Publish + * @default false + */ + publish: boolean; + }; + /** Body_import_module_bcf_projects__pid__modules__key__bcf_import_post */ + Body_import_module_bcf_projects__pid__modules__key__bcf_import_post: { + /** File */ + file: string; + }; + /** Body_import_preview_projects__pid__modules__key__import_preview_post */ + Body_import_preview_projects__pid__modules__key__import_preview_post: { + /** File */ + file: string; + }; + /** Body_import_records_projects__pid__modules__key__import_post */ + Body_import_records_projects__pid__modules__key__import_post: { + /** File */ + file: string; + /** + * Mapping + * @default {} + */ + mapping: string; + }; + /** Body_import_rvt_projects__pid__import_rvt_post */ + Body_import_rvt_projects__pid__import_rvt_post: { + /** File */ + file: string; + }; + /** Body_import_xer_projects__pid__schedule_import_xer_post */ + Body_import_xer_projects__pid__schedule_import_xer_post: { + /** File */ + file: string; + }; + /** Body_inspect_vim_convert_vim_inspect_post */ + Body_inspect_vim_convert_vim_inspect_post: { + /** File */ + file: string; + }; + /** Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post */ + Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post: { + /** Companies */ + companies: string[]; + }; + /** Body_layout_pdf_projects__pid__drawings_layout_pdf_post */ + Body_layout_pdf_projects__pid__drawings_layout_pdf_post: { + /** Viewports */ + viewports: { + [key: string]: unknown; + }[]; + /** Meta */ + meta?: { + [key: string]: unknown; + } | null; + /** + * Page + * @default A1 + */ + page: string; + }; + /** Body_layout_svg_projects__pid__drawings_layout_svg_post */ + Body_layout_svg_projects__pid__drawings_layout_svg_post: { + /** Viewports */ + viewports: { + [key: string]: unknown; + }[]; + /** Meta */ + meta?: { + [key: string]: unknown; + } | null; + /** + * Page + * @default A1 + */ + page: string; + }; + /** Body_level_quotes_projects__pid__procurement_level_quotes_post */ + Body_level_quotes_projects__pid__procurement_level_quotes_post: { + /** Quotes */ + quotes: { + [key: string]: unknown; + }[]; + }; + /** Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post */ + Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post: { + /** + * App No + * @default 1 + */ + app_no: number; + /** + * Vendor + * @default + */ + vendor: string; + /** + * Waiver Type + * @default Conditional Progress + */ + waiver_type: string; + }; + /** Body_login_auth_login_post */ + Body_login_auth_login_post: { + /** Username */ + username: string; + /** Password */ + password: string; + }; + /** Body_macros_expand_projects__pid__macros__macro_id__expand_post */ + Body_macros_expand_projects__pid__macros__macro_id__expand_post: { + /** + * Args + * @default {} + */ + args: { + [key: string]: unknown; + }; + }; + /** Body_macros_put_projects__pid__macros_put */ + Body_macros_put_projects__pid__macros_put: { + /** Macros */ + macros: { + [key: string]: unknown; + }[]; + }; + /** Body_macros_run_projects__pid__macros__macro_id__run_post */ + Body_macros_run_projects__pid__macros__macro_id__run_post: { + /** + * Args + * @default {} + */ + args: { + [key: string]: unknown; + }; + /** + * Publish + * @default false + */ + publish: boolean; + /** Base Source */ + base_source?: string | null; + }; + /** Body_mfa_disable_auth_mfa_disable_post */ + Body_mfa_disable_auth_mfa_disable_post: { + /** Password */ + password: string; + /** + * Code + * @default + */ + code: string; + }; + /** Body_mfa_enable_auth_mfa_enable_post */ + Body_mfa_enable_auth_mfa_enable_post: { + /** Code */ + code: string; + }; + /** Body_mfa_verify_auth_mfa_verify_post */ + Body_mfa_verify_auth_mfa_verify_post: { + /** Mfa Token */ + mfa_token: string; + /** Code */ + code: string; + }; + /** Body_model_equipment_spec_check_projects__pid__model_equipment_spec_check_post */ + Body_model_equipment_spec_check_projects__pid__model_equipment_spec_check_post: { + /** + * Requirements + * @default {} + */ + requirements: { + [key: string]: unknown; + }; + }; + /** Body_model_lod_proxy_projects__pid__model_lod_proxy_post */ + Body_model_lod_proxy_projects__pid__model_lod_proxy_post: { + /** Classes */ + classes?: string[] | null; + }; + /** Body_model_space_demand_projects__pid__model_space_demand_post */ + Body_model_space_demand_projects__pid__model_space_demand_post: { + /** + * Program + * @default {} + */ + program: { + [key: string]: unknown; + }; + /** + * Area Per Person + * @default 10 + */ + area_per_person: number; + }; + /** Body_open_sample_samples__sample_id__open_post */ + Body_open_sample_samples__sample_id__open_post: { + /** Name */ + name?: string | null; + }; + /** Body_option_activate_projects__pid__model_options__slug__activate_post */ + Body_option_activate_projects__pid__model_options__slug__activate_post: { + /** + * Publish + * @default true + */ + publish: boolean; + }; + /** Body_option_snapshot_projects__pid__model_options_post */ + Body_option_snapshot_projects__pid__model_options_post: { + /** Name */ + name: string; + /** Note */ + note?: string | null; + }; + /** Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post */ + Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post: { + /** App No */ + app_no?: number | null; + /** Period */ + period?: string | null; + /** Period From */ + period_from?: string | null; + /** Period To */ + period_to?: string | null; + /** + * Release Retainage + * @default false + */ + release_retainage: boolean; + }; + /** Body_pdf_extract_pdf_extract_post */ + Body_pdf_extract_pdf_extract_post: { + /** File */ + file: string; + /** Pages */ + pages: string; + }; + /** Body_pdf_info_pdf_info_post */ + Body_pdf_info_pdf_info_post: { + /** File */ + file: string; + }; + /** Body_pdf_merge_pdf_merge_post */ + Body_pdf_merge_pdf_merge_post: { + /** Files */ + files: string[]; + }; + /** Body_pdf_rotate_pdf_rotate_post */ + Body_pdf_rotate_pdf_rotate_post: { + /** File */ + file: string; + /** + * Angle + * @default 90 + */ + angle: number; + /** + * Pages + * @default + */ + pages: string; + }; + /** Body_pdf_seal_pdf_seal_post */ + Body_pdf_seal_pdf_seal_post: { + /** File */ + file: string; + /** Template Id */ + template_id: string; + /** + * Step Up + * @default + */ + step_up: string; + /** + * Page + * @default 1 + */ + page: number; + /** + * X + * @default 36 + */ + x: number; + /** + * Y + * @default 36 + */ + y: number; + /** + * Sign + * @default true + */ + sign: boolean; + /** License Id */ + license_id?: string | null; + /** Profile */ + profile?: string | null; + }; + /** Body_pdf_split_pdf_split_post */ + Body_pdf_split_pdf_split_post: { + /** File */ + file: string; + }; + /** Body_pdf_stamp_pdf_stamp_post */ + Body_pdf_stamp_pdf_stamp_post: { + /** File */ + file: string; + /** Template Id */ + template_id: string; + /** + * Page + * @default 1 + */ + page: number; + /** + * X + * @default 36 + */ + x: number; + /** + * Y + * @default 36 + */ + y: number; + /** + * Disposition + * @default + */ + disposition: string; + /** + * Values + * @default + */ + values: string; + }; + /** Body_place_family_projects__pid__families_place_post */ + Body_place_family_projects__pid__families_place_post: { + /** Family */ + family: string; + /** Position */ + position?: number[] | null; + /** Storey */ + storey?: string | null; + /** Type Name */ + type_name?: string | null; + /** + * Publish + * @default false + */ + publish: boolean; + }; + /** Body_post_clause_review_projects__pid__contracts_review_post */ + Body_post_clause_review_projects__pid__contracts_review_post: { + /** Contract Type */ + contract_type: string; + /** Findings */ + findings: unknown[]; + /** + * Document + * @default + */ + document: string; + }; + /** Body_post_competitive_supply_projects__pid__supply_competitive_post */ + Body_post_competitive_supply_projects__pid__supply_competitive_post: { + /** Projects */ + projects: unknown[]; + /** Window Start */ + window_start?: string | null; + /** Window End */ + window_end?: string | null; + /** Product Type */ + product_type?: string | null; + /** Monthly Absorption */ + monthly_absorption?: number | null; + }; + /** Body_post_decision_gate_projects__pid__decision_gate_post */ + Body_post_decision_gate_projects__pid__decision_gate_post: { + /** Evidence */ + evidence?: { + [key: string]: unknown; + } | null; + /** Required Exhibits */ + required_exhibits?: unknown[] | null; + /** + * Min Coverage + * @default 0.9 + */ + min_coverage: number; + }; + /** Body_post_hold_sell_projects__pid__hold_sell_post */ + Body_post_hold_sell_projects__pid__hold_sell_post: { + /** Inputs */ + inputs: { + [key: string]: unknown; + }; + /** + * Hurdle Rate + * @default 0.12 + */ + hurdle_rate: number; + /** + * Max Years + * @default 10 + */ + max_years: number; + }; + /** Body_post_loan_covenants_projects__pid__loan_covenants_post */ + Body_post_loan_covenants_projects__pid__loan_covenants_post: { + /** Loan */ + loan: { + [key: string]: unknown; + }; + /** Actuals */ + actuals?: { + [key: string]: unknown; + } | null; + }; + /** Body_post_rent_scrub_projects__pid__rent_roll_scrub_post */ + Body_post_rent_scrub_projects__pid__rent_roll_scrub_post: { + /** Income */ + income?: { + [key: string]: unknown; + } | null; + /** Units */ + units?: unknown[] | null; + }; + /** Body_post_t12_normalize_projects__pid__t12_normalize_post */ + Body_post_t12_normalize_projects__pid__t12_normalize_post: { + /** T12 */ + t12: { + [key: string]: unknown; + }; + /** Units */ + units?: number | null; + }; + /** Body_preview_bundle_projects_preview_bundle_post */ + Body_preview_bundle_projects_preview_bundle_post: { + /** File */ + file: string; + }; + /** Body_price_tm_projects__pid__cost_tm_post */ + Body_price_tm_projects__pid__cost_tm_post: { + /** Eticket Id */ + eticket_id: string; + }; + /** Body_promote_comment_projects__pid__modules__key___rid__comments__cid__promote_post */ + Body_promote_comment_projects__pid__modules__key___rid__comments__cid__promote_post: { + /** + * Kind + * @default rfi + */ + kind: string; + }; + /** Body_propmap_plan_projects__pid__propmap_plan_post */ + Body_propmap_plan_projects__pid__propmap_plan_post: { + /** Rules */ + rules: { + [key: string]: unknown; + }[]; + }; + /** Body_push_procore_projects__pid__sync_procore_push_post */ + Body_push_procore_projects__pid__sync_procore_push_post: { + /** Connection Id */ + connection_id: string; + /** Procore Project Id */ + procore_project_id: string; + /** + * Kinds + * @default [ + * "rfi" + * ] + */ + kinds: string[]; + }; + /** Body_put_clause_playbook_projects__pid__contracts_playbook_put */ + Body_put_clause_playbook_projects__pid__contracts_playbook_put: { + /** Playbook */ + playbook: { + [key: string]: unknown; + }; + }; + /** Body_put_config_projects__pid__responsibility_config_put */ + Body_put_config_projects__pid__responsibility_config_put: { + /** Roles */ + roles: string[]; + /** + * Mode + * @default RACI + */ + mode: string; + /** Rename */ + rename?: { + [key: string]: string; + } | null; + /** Drop */ + drop?: string[] | null; + }; + /** Body_put_deal_authority_projects__pid__deal_room_authority_put */ + Body_put_deal_authority_projects__pid__deal_room_authority_put: { + /** Entries */ + entries: unknown[]; + }; + /** Body_put_finance_lock_projects__pid__finance_lock_put */ + Body_put_finance_lock_projects__pid__finance_lock_put: { + /** Lock Date */ + lock_date?: string | null; + /** + * Note + * @default + */ + note: string; + }; + /** Body_put_hero_projects__pid__hero_put */ + Body_put_hero_projects__pid__hero_put: { + /** File */ + file: string; + }; + /** Body_put_integrations_settings_integrations_put */ + Body_put_integrations_settings_integrations_put: { + /** Values */ + values: { + [key: string]: unknown; + }; + }; + /** Body_put_mappings_connections__cid__mappings_put */ + Body_put_mappings_connections__cid__mappings_put: { + /** Mappings */ + mappings: { + [key: string]: unknown; + }; + }; + /** Body_put_project_ids_projects__pid__ids_put */ + Body_put_project_ids_projects__pid__ids_put: { + /** File */ + file: string; + }; + /** Body_put_shared_params_projects__pid__shared_params_put */ + Body_put_shared_params_projects__pid__shared_params_put: { + /** Params */ + params: unknown[]; + }; + /** Body_put_workflow_projects__pid__workflow__key__put */ + Body_put_workflow_projects__pid__workflow__key__put: { + /** Transitions */ + transitions: unknown[]; + }; + /** Body_raise_plan_to_bim_projects__pid__raise_plan_post */ + Body_raise_plan_to_bim_projects__pid__raise_plan_post: { + /** File */ + file: string; + /** + * Wall Height + * @default 3 + */ + wall_height: number; + /** + * Wall Thickness + * @default 0.2 + */ + wall_thickness: number; + /** + * Preview + * @default false + */ + preview: boolean; + }; + /** Body_received_sheet_regions_projects__pid__drawings_received_regions_post */ + Body_received_sheet_regions_projects__pid__drawings_received_regions_post: { + /** File */ + file: string; + }; + /** Body_recipe_replay_plan_projects__pid__recipes_replay_plan_post */ + Body_recipe_replay_plan_projects__pid__recipes_replay_plan_post: { + /** Indices */ + indices?: number[] | null; + }; + /** Body_register_auth_register_post */ + Body_register_auth_register_post: { + /** Username */ + username: string; + /** Password */ + password: string; + /** + * Role + * @default user + */ + role: string; + }; + /** Body_reset_password_auth_users__username__password_post */ + Body_reset_password_auth_users__username__password_post: { + /** Password */ + password: string; + }; + /** Body_reset_with_token_auth_reset_post */ + Body_reset_with_token_auth_reset_post: { + /** Token */ + token: string; + /** New */ + new: string; + }; + /** Body_residual_land_proforma_residual_land_post */ + Body_residual_land_proforma_residual_land_post: { + assumptions: components["schemas"]["Assumptions"]; + /** + * Target + * @default equity_irr + */ + target: string; + /** + * Target Value + * @default 0.15 + */ + target_value: number; + /** Max Land */ + max_land?: number | null; + }; + /** Body_resource_leveling_apply_projects__pid__schedule_resource_leveling_apply_post */ + Body_resource_leveling_apply_projects__pid__schedule_resource_leveling_apply_post: { + /** Cap */ + cap: number; + }; + /** Body_review_ask_ep_projects__pid__review_ask_post */ + Body_review_ask_ep_projects__pid__review_ask_post: { + /** Question */ + question: string; + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_review_contract_ep_projects__pid__review_contract_post */ + Body_review_contract_ep_projects__pid__review_contract_post: { + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_review_scenario_proforma_scenarios__sid__review_post */ + Body_review_scenario_proforma_scenarios__sid__review_post: { + /** Action */ + action: string; + /** + * Note + * @default + */ + note: string; + }; + /** Body_review_scope_ep_projects__pid__review_scope_post */ + Body_review_scope_ep_projects__pid__review_scope_post: { + /** File */ + file?: string | null; + /** Text */ + text?: string | null; + }; + /** Body_roundtrip_diff_projects__pid__model_roundtrip_diff_post */ + Body_roundtrip_diff_projects__pid__model_roundtrip_diff_post: { + /** File */ + file: string; + }; + /** Body_rules_put_projects__pid__rules_put */ + Body_rules_put_projects__pid__rules_put: { + /** Rules */ + rules: { + [key: string]: unknown; + }[]; + }; + /** Body_run_clash_federated_projects__pid__clash_federated_post */ + Body_run_clash_federated_projects__pid__clash_federated_post: { + /** + * Disciplines + * @default {} + */ + disciplines: { + [key: string]: unknown; + }; + }; + /** Body_run_validate_projects__pid__validate_post */ + Body_run_validate_projects__pid__validate_post: { + /** File */ + file?: string | null; + }; + /** Body_saml_acs_auth_saml_acs_post */ + Body_saml_acs_auth_saml_acs_post: { + /** Samlresponse */ + SAMLResponse: string; + /** + * Relaystate + * @default + */ + RelayState: string; + }; + /** Body_save_template_projects__pid__modules__key__save_template_post */ + Body_save_template_projects__pid__modules__key__save_template_post: { + /** Name */ + name: string; + }; + /** Body_save_view_projects__pid__modules__key__views_post */ + Body_save_view_projects__pid__modules__key__views_post: { + /** Name */ + name: string; + /** + * Config + * @default {} + */ + config: { + [key: string]: unknown; + }; + /** + * Scope + * @default private + */ + scope: string; + }; + /** Body_scan_deviation_projects__pid__scan_deviation_post */ + Body_scan_deviation_projects__pid__scan_deviation_post: { + /** File */ + file: string; + }; + /** Body_scan_verify_lod500_projects__pid__scan_verify_lod500_post */ + Body_scan_verify_lod500_projects__pid__scan_verify_lod500_post: { + /** File */ + file: string; + }; + /** Body_schedule_status_endpoint_projects__pid__schedule_status_post */ + Body_schedule_status_endpoint_projects__pid__schedule_status_post: { + /** Data Date */ + data_date?: string | null; + }; + /** Body_screen_parcels_screen_post */ + Body_screen_parcels_screen_post: { + /** Parcels */ + parcels: { + [key: string]: unknown; + }[]; + /** Criteria */ + criteria?: { + [key: string]: unknown; + } | null; + }; + /** Body_share_scenario_proforma_scenarios__sid__share_post */ + Body_share_scenario_proforma_scenarios__sid__share_post: { + /** User */ + user: string; + }; + /** Body_smart_views_put_projects__pid__smart_views_put */ + Body_smart_views_put_projects__pid__smart_views_put: { + /** Views */ + views: { + [key: string]: unknown; + }[]; + }; + /** Body_space_pack_put_projects__pid__rules_space_pack_put */ + Body_space_pack_put_projects__pid__rules_space_pack_put: { + /** Pack */ + pack: { + [key: string]: unknown; + }; + }; + /** Body_step_up_auth_step_up_post */ + Body_step_up_auth_step_up_post: { + /** Password */ + password: string; + /** + * Act + * @default pdf.seal + */ + act: string; + }; + /** Body_sync_procore_projects__pid__sync_procore_post */ + Body_sync_procore_projects__pid__sync_procore_post: { + /** Connection Id */ + connection_id: string; + /** Procore Project Id */ + procore_project_id: string; + /** + * Kinds + * @default [ + * "rfi", + * "submittal", + * "change_event" + * ] + */ + kinds: string[]; + }; + /** Body_tag_elements_projects__pid__modules__key___rid__elements_post */ + Body_tag_elements_projects__pid__modules__key___rid__elements_post: { + /** Guids */ + guids: string[]; + /** + * Mode + * @default add + */ + mode: string; + }; + /** Body_takeoff_dxf_projects__pid__takeoff_dxf_post */ + Body_takeoff_dxf_projects__pid__takeoff_dxf_post: { + /** File */ + file: string; + }; + /** Body_transition_projects__pid__modules__key___rid__transition_post */ + Body_transition_projects__pid__modules__key___rid__transition_post: { + /** Action */ + action: string; + /** Note */ + note?: string | null; + }; + /** Body_triage_rfi_projects__pid__ai_triage_rfi_post */ + Body_triage_rfi_projects__pid__ai_triage_rfi_post: { + /** Rid */ + rid?: string | null; + /** + * Rfi + * @default {} + */ + rfi: { + [key: string]: unknown; + }; + }; + /** Body_update_schedule_projects__pid__sync_schedules__sid__put */ + Body_update_schedule_projects__pid__sync_schedules__sid__put: { + /** Enabled */ + enabled?: boolean | null; + /** Interval Minutes */ + interval_minutes?: number | null; + /** Kinds */ + kinds?: string[] | null; + /** Push */ + push?: boolean | null; + }; + /** Body_upload_attachment_projects__pid__modules__key___rid__attachments_post */ + Body_upload_attachment_projects__pid__modules__key___rid__attachments_post: { + /** File */ + file: string; + }; + /** Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post */ + Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post: { + /** Files */ + files: string[]; + }; + /** Body_upload_index_projects__pid__properties_index_post */ + Body_upload_index_projects__pid__properties_index_post: { + /** File */ + file: string; + }; + /** Body_upload_photo_projects__pid__verification__guid__photo_post */ + Body_upload_photo_projects__pid__verification__guid__photo_post: { + /** File */ + file: string; + }; + /** Body_upload_source_ifc_projects__pid__source_ifc_post */ + Body_upload_source_ifc_projects__pid__source_ifc_post: { + /** File */ + file: string; + }; + /** Body_view_templates_put_projects__pid__view_templates_put */ + Body_view_templates_put_projects__pid__view_templates_put: { + /** Templates */ + templates: { + [key: string]: unknown; + }[]; + }; + /** CommentIn */ + CommentIn: { + /** Author */ + author?: string | null; + /** Text */ + text: string; + /** Viewpoint Id */ + viewpoint_id?: string | null; + /** Reply To */ + reply_to?: string | null; + }; + /** CommentOut */ + CommentOut: { + /** Id */ + id: string; + /** Topic Id */ + topic_id: string; + /** Author */ + author: string | null; + /** Text */ + text: string; + /** Viewpoint Id */ + viewpoint_id: string | null; + /** Reply To */ + reply_to?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** ConnectionIn */ + ConnectionIn: { + /** + * Name + * @default + */ + name: string; + /** Type */ + type: string; + /** + * Config + * @default {} + */ + config: { + [key: string]: unknown; + }; + }; + /** CostLine */ + CostLine: { + /** + * Category + * @enum {string} + */ + category: "land" | "hard" | "soft" | "contingency" | "fee"; + /** Name */ + name: string; + /** + * Amount + * @default 0 + */ + amount: number; + /** + * Curve + * @default scurve + * @enum {string} + */ + curve: "scurve" | "linear" | "upfront"; + /** + * Start Month + * @default 0 + */ + start_month: number; + /** End Month */ + end_month?: number | null; + /** Csi Code */ + csi_code?: string | null; + }; + /** Debt */ + Debt: { + /** Ltc */ + ltc: number; + /** Rate */ + rate: number; + /** + * Points + * @default 0 + */ + points: number; + /** + * Funding + * @default equity_first + * @enum {string} + */ + funding: "equity_first" | "pari_passu" | "loan_first"; + /** Max Ltv */ + max_ltv?: number | null; + /** Min Dscr */ + min_dscr?: number | null; + /** Min Debt Yield */ + min_debt_yield?: number | null; + }; + /** DevBudgetIn */ + DevBudgetIn: { + /** Lines */ + lines?: { + [key: string]: unknown; + }[]; + /** Contingency */ + contingency?: { + [key: string]: number; + }; + /** Rev */ + rev?: string | null; + }; + /** Distribution */ + Distribution: { + /** + * Kind + * @enum {string} + */ + kind: "normal" | "uniform" | "triangular"; + /** Mean */ + mean?: number | null; + /** Std */ + std?: number | null; + /** Low */ + low?: number | null; + /** High */ + high?: number | null; + /** Mode */ + mode?: number | null; + /** Min */ + min?: number | null; + /** Max */ + max?: number | null; + }; + /** DrawPackageIn */ + DrawPackageIn: { + /** Project Id */ + project_id: string; + /** Actuals */ + actuals: components["schemas"]["Actual"][]; + /** + * As Of Month + * @default 0 + */ + as_of_month: number; + /** + * Retainage Pct + * @default 5 + */ + retainage_pct: number; + /** + * App No + * @default 1 + */ + app_no: number; + }; + /** + * EntitlementIn + * @description Approval odds and what a refusal costs. `duration_months` reuses the Monte Carlo's own + * Distribution, so an entitlement period is specified exactly like any other sampled driver. + */ + EntitlementIn: { + /** Approval Probability */ + approval_probability: number; + duration_months?: components["schemas"]["Distribution"] | null; + /** Carry Cost Monthly */ + carry_cost_monthly?: number | null; + /** + * Pursuit Cost + * @default 0 + */ + pursuit_cost: number; + /** + * Land Basis + * @default 0 + */ + land_basis: number; + /** Land Recovery Pct */ + land_recovery_pct?: number | null; + }; + /** EntitlementRiskIn */ + EntitlementRiskIn: { + assumptions: components["schemas"]["Assumptions"]; + entitlement: components["schemas"]["EntitlementIn"]; + /** + * Variables + * @default [] + */ + variables: components["schemas"]["MonteCarloVar"][]; + /** + * Iterations + * @default 1000 + */ + iterations: number; + /** + * Seed + * @default 42 + */ + seed: number; + /** Metrics */ + metrics?: string[] | null; + /** Targets */ + targets?: { + [key: string]: number; + } | null; + }; + /** Equity */ + Equity: { + /** Lp Pct */ + lp_pct: number; + /** Gp Pct */ + gp_pct: number; + }; + /** Exit */ + Exit: { + /** Exit Cap */ + exit_cap: number; + /** + * Selling Cost Pct + * @default 0 + */ + selling_cost_pct: number; + }; + /** ForecastIn */ + ForecastIn: { + /** Actuals */ + actuals: components["schemas"]["Actual"][]; + /** + * As Of Month + * @default 0 + */ + as_of_month: number; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * MarkupBulkIn + * @description Persist a whole sheet's markup scene from the 2D editor. `replace` clears the caller's own prior + * markups for that sheet first (kept: any already promoted to an RFI). + */ + MarkupBulkIn: { + /** Sheet Id */ + sheet_id: string; + /** + * Replace + * @default true + */ + replace: boolean; + /** Markups */ + markups: components["schemas"]["MarkupItemIn"][]; + }; + /** MarkupIn */ + MarkupIn: { + /** Sheet Id */ + sheet_id: string; + /** X */ + x: number; + /** Y */ + y: number; + /** Note */ + note?: string | null; + /** + * Kind + * @default pin + */ + kind: string; + /** Data */ + data?: { + [key: string]: unknown; + } | null; + }; + /** + * MarkupItemIn + * @description One markup within a bulk save — the sheet_id comes from the parent, not each item. + */ + MarkupItemIn: { + /** X */ + x: number; + /** Y */ + y: number; + /** Note */ + note?: string | null; + /** + * Kind + * @default pin + */ + kind: string; + /** Data */ + data?: { + [key: string]: unknown; + } | null; + }; + /** + * MassingIn + * @description Zoning envelope (metres) + acquisition assumptions for the starter proforma. + */ + MassingIn: { + /** + * Name + * @default Massing Study */ name: string; /** @@ -11495,645 +23712,19012 @@ export interface components { * Rear Setback * @default 6 */ - rear_setback: number; + rear_setback: number; + /** + * Side Setback + * @default 3 + */ + side_setback: number; + /** Height Limit */ + height_limit?: number | null; + /** + * Floor To Floor + * @default 3.5 + */ + floor_to_floor: number; + /** + * Efficiency + * @default 0.82 + */ + efficiency: number; + /** + * Avg Unit M2 + * @default 75 + */ + avg_unit_m2: number; + /** + * Frame + * @default false + */ + frame: boolean; + /** + * Bay M + * @default 7.5 + */ + bay_m: number; + /** + * Units + * @default false + */ + units: boolean; + /** + * Envelope + * @default false + */ + envelope: boolean; + /** + * Wwr + * @default 0.4 + */ + wwr: number; + /** + * Core + * @default false + */ + core: boolean; + /** + * Unit Layout + * @default grid + */ + unit_layout: string; + /** + * Parking + * @default 0 + */ + parking: number; + /** + * Land Cost + * @default 2500000 + */ + land_cost: number; + /** + * Hard Cost Psf + * @default 225 + */ + hard_cost_psf: number; + /** + * Soft Cost Pct + * @default 0.15 + */ + soft_cost_pct: number; + /** + * Contingency Pct + * @default 0.05 + */ + contingency_pct: number; + /** + * Rent Per Unit Month + * @default 3000 + */ + rent_per_unit_month: number; + /** + * Rent Psf Year + * @default 38 + */ + rent_psf_year: number; + /** + * Opex Ratio + * @default 0.35 + */ + opex_ratio: number; + /** + * Exit Cap + * @default 0.05 + */ + exit_cap: number; + /** + * Ltc + * @default 0.6 + */ + ltc: number; + /** + * Rate + * @default 0.075 + */ + rate: number; + }; + /** + * MassingOptioneerIn + * @description MASSING-OPT — the zoning envelope + econ to optimise, plus the sweep controls. + */ + MassingOptioneerIn: { + envelope: components["schemas"]["MassingIn"]; + /** Levers */ + levers?: { + [key: string]: number[]; + } | null; + /** + * Objective + * @default yield_on_cost + */ + objective: string; + /** + * Limit + * @default 24 + */ + limit: number; + }; + /** MassingRecipesIn */ + MassingRecipesIn: { + envelope: components["schemas"]["MassingIn"]; + /** Levers */ + levers?: { + [key: string]: number[]; + } | null; + /** + * Objective + * @default yield_on_cost + */ + objective: string; + /** + * Limit + * @default 24 + */ + limit: number; + /** + * Option + * @description the scenario id to author (empty = the best option) + * @default + */ + option: string; + }; + /** MemberIn */ + MemberIn: { + /** User */ + user: string; + /** Role */ + role: string; + /** Party Role */ + party_role?: string | null; + /** Company */ + company?: string | null; + }; + /** MonteCarloIn */ + MonteCarloIn: { + assumptions: components["schemas"]["Assumptions"]; + /** Variables */ + variables: components["schemas"]["MonteCarloVar"][]; + /** + * Iterations + * @default 1000 + */ + iterations: number; + /** + * Seed + * @default 42 + */ + seed: number; + /** Metrics */ + metrics?: string[] | null; + /** Targets */ + targets?: { + [key: string]: number; + } | null; + }; + /** MonteCarloVar */ + MonteCarloVar: { + /** Path */ + path: string; + dist: components["schemas"]["Distribution"]; + }; + /** Ops */ + Ops: { + /** Potential Rent Annual */ + potential_rent_annual: number; + /** + * Other Income Annual + * @default 0 + */ + other_income_annual: number; + /** Opex Annual */ + opex_annual: number; + /** + * Reserves Annual + * @default 0 + */ + reserves_annual: number; + /** Stabilized Occ */ + stabilized_occ: number; + /** + * Credit Loss Pct + * @default 0 + */ + credit_loss_pct: number; + }; + /** + * OptimizeIn + * @description Generative design: sweep schemes and rank by an objective, filtered by targets. + */ + OptimizeIn: { + /** Plate W */ + plate_w: number; + /** Plate D */ + plate_d: number; + /** + * Floors + * @default 1 + */ + floors: number; + /** Targets */ + targets?: { + [key: string]: unknown; + }; + /** Econ */ + econ?: { + [key: string]: unknown; + }; + /** Pid */ + pid?: string | null; + /** Depths */ + depths?: number[] | null; + }; + /** ProdActualsIn */ + ProdActualsIn: { + /** Actuals */ + actuals?: { + [key: string]: unknown; + }[]; + /** Planned */ + planned?: { + [key: string]: unknown; + } | null; + }; + /** ProjectIn */ + ProjectIn: { + /** Name */ + name: string; + /** Origin */ + origin?: { + [key: string]: unknown; + } | null; + /** Source Ifc */ + source_ifc?: string | null; + }; + /** ProjectOut */ + ProjectOut: { + /** Name */ + name: string; + /** Origin */ + origin?: { + [key: string]: unknown; + } | null; + /** Source Ifc */ + source_ifc?: string | null; + /** Id */ + id: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Model Kind */ + model_kind?: string | null; + /** + * Has Source Ifc + * @default false + */ + has_source_ifc: boolean; + /** Jurisdiction */ + jurisdiction?: string | null; + }; + /** ProjectPatch */ + ProjectPatch: { + /** Name */ + name?: string | null; + /** Origin */ + origin?: { + [key: string]: unknown; + } | null; + /** Source Ifc */ + source_ifc?: string | null; + /** Jurisdiction */ + jurisdiction?: string | null; + }; + /** ScenarioIn */ + ScenarioIn: { + /** Name */ + name: string; + /** Project Id */ + project_id?: string | null; + assumptions: components["schemas"]["Assumptions"]; + }; + /** ScheduleIn */ + ScheduleIn: { + /** Connection Id */ + connection_id: string; + /** Procore Project Id */ + procore_project_id: string; + /** + * Kinds + * @default [ + * "rfi", + * "submittal", + * "change_event" + * ] + */ + kinds: string[]; + /** + * Interval Minutes + * @default 60 + */ + interval_minutes: number; + /** + * Enabled + * @default true + */ + enabled: boolean; + /** + * Push + * @default false + */ + push: boolean; + }; + /** SensitivityIn */ + SensitivityIn: { + assumptions: components["schemas"]["Assumptions"]; + x: components["schemas"]["Axis"]; + y: components["schemas"]["Axis"]; + /** + * Metric + * @default returns.equity_irr + */ + metric: string; + }; + /** SpecialtyMonteCarloIn */ + SpecialtyMonteCarloIn: { + assumptions: components["schemas"]["Assumptions"]; + /** Variables */ + variables: components["schemas"]["MonteCarloVar"][]; + /** + * Iterations + * @default 500 + */ + iterations: number; + /** + * Seed + * @default 42 + */ + seed: number; + /** Targets */ + targets?: { + [key: string]: number; + } | null; + /** + * Years + * @default 10 + */ + years: number; + /** + * Ramp Years + * @default 3 + */ + ramp_years: number; + /** + * Ramp Start + * @default 0.4 + */ + ramp_start: number; + /** + * Terminal Cap + * @default 0.1 + */ + terminal_cap: number; + }; + /** + * StructureIn + * @description Structural-system advice for a given scale. + */ + StructureIn: { + /** Height M */ + height_m: number; + /** + * Floors + * @default 1 + */ + floors: number; + /** + * Span M + * @default 7.5 + */ + span_m: number; /** - * Side Setback - * @default 3 + * Use Type + * @default residential */ - side_setback: number; - /** Height Limit */ - height_limit?: number | null; + use_type: string; + }; + /** TaktIn */ + TaktIn: { + /** Floors */ + floors: number; + /** Trades */ + trades?: { + [key: string]: unknown; + }[] | null; /** - * Floor To Floor - * @default 3.5 + * Jit Lead Days + * @default 1 */ - floor_to_floor: number; + jit_lead_days: number; + }; + /** TaktProgressIn */ + TaktProgressIn: { + /** Floors */ + floors: number; + /** Trades */ + trades?: { + [key: string]: unknown; + }[] | null; /** - * Efficiency - * @default 0.82 + * Jit Lead Days + * @default 1 */ - efficiency: number; + jit_lead_days: number; + /** Actuals */ + actuals?: { + [key: string]: unknown; + }[]; + /** As Of Day */ + as_of_day?: number | null; + }; + /** Tax */ + Tax: { /** - * Avg Unit M2 - * @default 75 + * Income Tax Rate + * @default 0.25 */ - avg_unit_m2: number; + income_tax_rate: number; /** - * Frame - * @default false + * Depreciation Years + * @default 27.5 */ - frame: boolean; + depreciation_years: number; /** - * Bay M - * @default 7.5 + * Capital Gains Rate + * @default 0.2 */ - bay_m: number; + capital_gains_rate: number; /** - * Units - * @default false + * Niit Rate + * @default 0.038 */ - units: boolean; + niit_rate: number; /** - * Envelope - * @default false + * Recapture Rate + * @default 0.25 */ - envelope: boolean; + recapture_rate: number; + }; + /** TemplateIn */ + TemplateIn: { + /** Module */ + module: string; + /** Name */ + name: string; /** - * Wwr - * @default 0.4 + * Items + * @default [] */ - wwr: number; + items: { + [key: string]: unknown; + }[]; + }; + /** + * TestFitIn + * @description Fit a unit mix to a floor plate and compare schemes (TestFit-style). + */ + TestFitIn: { + /** Plate W */ + plate_w: number; + /** Plate D */ + plate_d: number; /** - * Core - * @default false + * Floors + * @default 1 */ - core: boolean; + floors: number; + /** Schemes */ + schemes?: { + [key: string]: unknown; + }[]; /** - * Unit Layout - * @default grid + * With Defaults + * @default false */ - unit_layout: string; + with_defaults: boolean; + }; + /** Tier */ + Tier: { + /** Hurdle */ + hurdle?: number | null; + /** Lp */ + lp: number; + /** Gp */ + gp: number; + }; + /** Timing */ + Timing: { + /** Construction Months */ + construction_months: number; /** - * Parking + * Leaseup Months * @default 0 */ - parking: number; - /** - * Land Cost - * @default 2500000 - */ - land_cost: number; + leaseup_months: number; + /** Hold Years */ + hold_years: number; + /** Start Date */ + start_date?: string | null; + }; + /** TopicIn */ + TopicIn: { /** - * Hard Cost Psf - * @default 225 + * Type + * @default info + * @enum {string} */ - hard_cost_psf: number; + type: "rfi" | "punch" | "clash" | "info"; + /** Title */ + title: string; + /** Description */ + description?: string | null; /** - * Soft Cost Pct - * @default 0.15 + * Status + * @default open */ - soft_cost_pct: number; + status: string; + /** Priority */ + priority?: string | null; + /** Assignee */ + assignee?: string | null; + /** Author */ + author?: string | null; + /** Due Date */ + due_date?: string | null; + /** Labels */ + labels?: string[] | null; + /** Anchor */ + anchor?: { + [key: string]: number; + } | null; + /** Element Guids */ + element_guids?: string[] | null; + }; + /** TopicOut */ + TopicOut: { + /** Id */ + id: string; + /** Guid */ + guid: string; + /** Project Id */ + project_id: string; + /** Type */ + type: string; + /** Title */ + title: string; + /** Description */ + description: string | null; + /** Status */ + status: string; + /** Priority */ + priority: string | null; + /** Assignee */ + assignee: string | null; + /** Author */ + author: string | null; + /** Due Date */ + due_date: string | null; + /** Labels */ + labels: string[] | null; + /** Anchor */ + anchor: { + [key: string]: number; + } | null; + /** Element Guids */ + element_guids: string[] | null; /** - * Contingency Pct - * @default 0.05 + * Created At + * Format: date-time */ - contingency_pct: number; + created_at: string; /** - * Rent Per Unit Month - * @default 3000 + * Modified At + * Format: date-time */ - rent_per_unit_month: number; + modified_at: string; + }; + /** TopicPatch */ + TopicPatch: { + /** Type */ + type?: ("rfi" | "punch" | "clash" | "info") | null; + /** Title */ + title?: string | null; + /** Description */ + description?: string | null; + /** Status */ + status?: string | null; + /** Priority */ + priority?: string | null; + /** Assignee */ + assignee?: string | null; + /** Due Date */ + due_date?: string | null; + /** Labels */ + labels?: string[] | null; + /** Anchor */ + anchor?: { + [key: string]: number; + } | null; + /** Element Guids */ + element_guids?: string[] | null; + }; + /** UserPatch */ + UserPatch: { + /** Role */ + role?: string | null; + /** Active */ + active?: boolean | null; + /** Email */ + email?: string | null; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + /** Input */ + input?: unknown; + /** Context */ + ctx?: Record; + }; + /** ViewpointIn */ + ViewpointIn: { + /** Camera */ + camera?: { + [key: string]: unknown; + } | null; + /** Clipping Planes */ + clipping_planes?: { + [key: string]: unknown; + }[] | null; + /** Visibility */ + visibility?: { + [key: string]: unknown; + } | null; + /** Components */ + components?: string[] | null; + /** Snapshot */ + snapshot?: string | null; + }; + /** ViewpointOut */ + ViewpointOut: { + /** Id */ + id: string; + /** Guid */ + guid: string; + /** Topic Id */ + topic_id: string; + /** Camera */ + camera: { + [key: string]: unknown; + } | null; + /** Clipping Planes */ + clipping_planes: { + [key: string]: unknown; + }[] | null; + /** Visibility */ + visibility: { + [key: string]: unknown; + } | null; + /** Components */ + components: string[] | null; + /** Snapshot */ + snapshot: string | null; /** - * Rent Psf Year - * @default 38 + * Created At + * Format: date-time */ - rent_psf_year: number; + created_at: string; + }; + /** Waterfall */ + Waterfall: { /** - * Opex Ratio - * @default 0.35 + * Pref Rate + * @default 0.08 */ - opex_ratio: number; + pref_rate: number; /** - * Exit Cap - * @default 0.05 + * Style + * @default american + * @enum {string} */ - exit_cap: number; + style: "american" | "european"; /** - * Ltc - * @default 0.6 + * Clawback + * @default false */ - ltc: number; + clawback: boolean; /** - * Rate - * @default 0.075 + * Pref Accrual + * @default compounding + * @enum {string} */ - rate: number; + pref_accrual: "compounding" | "simple"; + /** Tiers */ + tiers: components["schemas"]["Tier"][]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + adoption_baseline_admin_baseline_get: { + parameters: { + query?: { + days?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_errors_admin_errors_get: { + parameters: { + query?: { + limit?: number; + source?: string | null; + level?: string | null; + since_hours?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clear_errors_admin_errors_delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_licenses_admin_licenses_get: { + parameters: { + query?: { + user?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_license_admin_licenses_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_license_admin_licenses__lid__delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + lid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + agent_pack_runs_agent_packs_runs_get: { + parameters: { + query?: { + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + asset_rights_status_asset_rights_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + asset_rights_verify_asset_rights_verify_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_asset_rights_verify_asset_rights_verify_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + download_attachment_attachments__aid__download_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + aid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sign_attachment_attachments__aid__signed_url_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + aid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + audit_log_audit_get: { + parameters: { + query?: { + action?: string | null; + actor?: string | null; + since?: string | null; + limit?: number; + offset?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cloud_callback_auth_cloud_callback_get: { + parameters: { + query?: { + code?: string | null; + state?: string | null; + error?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cloud_disconnect_auth_cloud_disconnect_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cloud_login_auth_cloud_login_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + cloud_refresh_profile_auth_cloud_refresh_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cloud_status_auth_cloud_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + login_auth_login_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_login_auth_login_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + logout_auth_logout_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + logout_all_auth_logout_all_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + me_auth_me_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mfa_disable_auth_mfa_disable_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_mfa_disable_auth_mfa_disable_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mfa_enable_auth_mfa_enable_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_mfa_enable_auth_mfa_enable_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mfa_setup_auth_mfa_setup_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mfa_status_auth_mfa_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mfa_verify_auth_mfa_verify_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_mfa_verify_auth_mfa_verify_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + oauth_callback_auth_oauth__provider__callback_get: { + parameters: { + query?: { + code?: string | null; + state?: string | null; + }; + header?: never; + path: { + provider: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + oauth_login_auth_oauth__provider__login_get: { + parameters: { + query?: never; + header?: never; + path: { + provider: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + change_password_auth_password_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_change_password_auth_password_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + auth_providers_auth_providers_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + register_auth_register_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_register_auth_register_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + reset_with_token_auth_reset_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_reset_with_token_auth_reset_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + saml_acs_auth_saml_acs_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_saml_acs_auth_saml_acs_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + saml_login_auth_saml_login_get: { + parameters: { + query?: { + relay_state?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + saml_metadata_auth_saml_metadata_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + step_up_auth_step_up_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_step_up_auth_step_up_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_users_auth_users_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_user_auth_users_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_create_user_auth_users_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_user_auth_users__username__patch: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + username: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserPatch"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + reset_password_auth_users__username__password_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + username: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_reset_password_auth_users__username__password_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + issue_reset_token_auth_users__username__reset_token_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + username: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + revoke_sessions_auth_users__username__revoke_sessions_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + username: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_auth_bcf_2_1_auth_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + bcf_projects_bcf_2_1_projects_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_topics_bcf_2_1_projects__pid__topics_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_create_topic_bcf_2_1_projects__pid__topics_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_topic_bcf_2_1_projects__pid__topics__guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_comments_bcf_2_1_projects__pid__topics__guid__comments_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_create_comment_bcf_2_1_projects__pid__topics__guid__comments_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_viewpoints_bcf_2_1_projects__pid__topics__guid__viewpoints_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_create_viewpoint_bcf_2_1_projects__pid__topics__guid__viewpoints_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_viewpoint_snapshot_bcf_2_1_projects__pid__topics__guid__viewpoints__vguid__snapshot_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + vguid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_auth_bcf_3_0_auth_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + bcf3_projects_bcf_3_0_projects_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_documents_bcf_3_0_projects__pid__documents_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_document_download_bcf_3_0_projects__pid__documents__doc_guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + doc_guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_topics_bcf_3_0_projects__pid__topics_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_create_topic_bcf_3_0_projects__pid__topics_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_topic_bcf_3_0_projects__pid__topics__guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_comments_bcf_3_0_projects__pid__topics__guid__comments_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_create_comment_bcf_3_0_projects__pid__topics__guid__comments_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_document_refs_bcf_3_0_projects__pid__topics__guid__document_references_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_add_document_bcf_3_0_projects__pid__topics__guid__document_references_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_bcf3_add_document_bcf_3_0_projects__pid__topics__guid__document_references_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf3_viewpoints_bcf_3_0_projects__pid__topics__guid__viewpoints_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_versions_bcf_versions_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + get_benchmarks_benchmarks_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + cost_benchmarks_benchmarks_costs_get: { + parameters: { + query?: { + min_samples?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pull_planning_benchmarks_pull_planning_get: { + parameters: { + query?: { + min_committed?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + response_rates_benchmarks_response_rates_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + space_utilization_benchmarks_space_utilization_get: { + parameters: { + query?: { + area_per_person?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + unit_rates_benchmarks_unit_rates_get: { + parameters: { + query?: { + min_projects?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + vendor_scorecards_benchmarks_vendors_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + rvt_bridge_status_bridge_rvt_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bsdd_class_bsdd_class_get: { + parameters: { + query: { + uri: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bsdd_search_bsdd_search_get: { + parameters: { + query: { + q: string; + dictionary?: string | null; + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + capabilities_capabilities_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + list_classifications_classifications_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + report_client_error_client_errors_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + library_model_cloud_library_models__model_id__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + model_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + library_projects_cloud_library_projects_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + library_project_cloud_library_projects__project_id__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + project_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_adoptions_codes_adoptions_get: { + parameters: { + query?: { + jurisdiction?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ebc_pathways_codes_ebc_pathways_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_families_codes_families_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + codes_ids_codes_ids_get: { + parameters: { + query?: { + description?: string; + edition?: string; + title?: string; + download?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_seeded_codes_seeded_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + compute_run_compute_graph_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + compute_nodes_compute_nodes_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + list_connections_connections_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_connection_connections_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConnectionIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + test_config_connections_test_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConnectionIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_connection_connections__cid__put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ConnectionIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_connection_connections__cid__delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + acc_issues_connections__cid__acc_projects__project_id__issues_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + project_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + erp_read_connections__cid__erp__entity__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + entity: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_mappings_connections__cid__mappings_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + put_mappings_connections__cid__mappings_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_mappings_connections__cid__mappings_put"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + connection_query_connections__cid__query_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_connection_query_connections__cid__query_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + quickbooks_read_connections__cid__quickbooks__entity__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + entity: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + connection_tables_connections__cid__tables_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + test_connection_connections__cid__test_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + cid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + content_catalog_content_catalog_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + contractor_statements_portfolio_contractor_statements_portfolio_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + convert_convert_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_convert_convert_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + convert_citygml_convert_citygml_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_convert_citygml_convert_citygml_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + e57_status_convert_e57_status_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + inspect_vim_convert_vim_inspect_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_inspect_vim_convert_vim_inspect_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_datasets_cost_datasets_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_import_cost_datasets_import_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_import_custom_cost_datasets_import_custom_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + benchmark_status_energy_benchmark_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + esign_status_esign_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + esign_webhook_esign_webhook_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + estimate_assemblies_estimate_assemblies_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_assembly_price_estimate_assembly_price_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + conceptual_catalog_estimate_conceptual_catalog_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + labor_rates_estimate_labor_rates_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + resource_catalog_estimate_resources_catalog_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + family_catalog_families_catalog_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + family_coverage_families_coverage_get: { + parameters: { + query?: { + typology?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + family_library_families_library_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + family_types_families__key__types_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + key: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + fca_portfolio_fca_portfolio_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + firm_rules_get_firm_rules_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + firm_rules_put_firm_rules_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_firm_rules_put_firm_rules_put"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + preview_massing_generate_massing_preview_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MassingIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + health_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: string; + }; + }; + }; + }; + }; + build_ids_ids_build_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + build_eir_ids_eir_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ids_templates_ids_templates_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + speckle_status_interop_speckle_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_packs_jurisdiction_packs_get: { + parameters: { + query?: { + jurisdiction?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + import_pack_jurisdiction_packs_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_pack_jurisdiction_packs__pack_id__delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pack_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + license_state_license_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + license_cloud_check_license_cloud_check_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + my_licenses_licenses_mine_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + reference_lifecycle_reference_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + market_snapshot_market_snapshot_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + massing_optioneer_massing_optioneer_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MassingOptioneerIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + massing_option_recipes_massing_optioneer_recipes_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MassingRecipesIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + mcp_tool_catalog_mcp_tools_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + prometheus_metrics_metrics_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + perf_budget_report_metrics_budget_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + }; + }; + record_client_interval_metrics_client_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + download_attachment_module_attachments__att_id__download_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + att_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_modules_modules_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + modules_graph_modules_graph_get: { + parameters: { + query?: { + workspace?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + openbim_capabilities_openbim_capabilities_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + permit_cities_opendata_permit_cities_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + analyze_parcels_analyze_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + data_status_parcels_data_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + screen_parcels_screen_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_screen_parcels_screen_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + payments_status_payments_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_extract_pdf_extract_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_extract_pdf_extract_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_info_pdf_info_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_info_pdf_info_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_merge_pdf_merge_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_merge_pdf_merge_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_rotate_pdf_rotate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_rotate_pdf_rotate_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_seal_pdf_seal_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_seal_pdf_seal_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_split_pdf_split_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_split_pdf_split_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pdf_stamp_pdf_stamp_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_pdf_stamp_pdf_stamp_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pipeline_allocate_route_pipeline_allocate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pipeline_funnel_pipeline_funnel_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + plugins_status_plugins_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + plugins_reload_plugins_reload_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + construction_portfolio_portfolio_construction_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_deal_memory_portfolio_deal_memory_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + executive_portfolio_portfolio_executive_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_prioritization_portfolio_prioritization_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_resourcing_portfolio_resourcing_get: { + parameters: { + query?: { + cap?: number | null; + limit?: number; + weeks?: number; + group?: string[] | null; + group_cap?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_risk_portfolio_risk_get: { + parameters: { + query?: { + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + pricing_status_pricing_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + rfq_status_procurement_rfq_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + compare_proforma_compare_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": string[]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_approval_risk_proforma_entitlement_risk_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EntitlementRiskIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + financials_stateless_proforma_financials_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Assumptions"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + forecast_stateless_proforma_forecast_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_forecast_stateless_proforma_forecast_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_monte_carlo_proforma_monte_carlo_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MonteCarloIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_proforma_portfolio_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + portfolio_compare_proforma_portfolio_compare_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + proforma_provenance_proforma_provenance_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Assumptions"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + proforma_admissibility_proforma_provenance_admissibility_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + residual_land_proforma_residual_land_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_residual_land_proforma_residual_land_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_scenarios_proforma_scenarios_get: { + parameters: { + query?: { + project_id?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_scenario_proforma_scenarios_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ScenarioIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_scenario_proforma_scenarios__sid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_scenario_proforma_scenarios__sid__put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ScenarioIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clone_scenario_proforma_scenarios__sid__clone_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_clone_scenario_proforma_scenarios__sid__clone_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + draw_package_proforma_scenarios__sid__draw_package_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DrawPackageIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + forecast_scenario_proforma_scenarios__sid__forecast_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ForecastIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + scenario_provenance_proforma_scenarios__sid__provenance_get: { + parameters: { + query?: { + revision?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + review_scenario_proforma_scenarios__sid__review_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_review_scenario_proforma_scenarios__sid__review_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + share_scenario_proforma_scenarios__sid__share_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_share_scenario_proforma_scenarios__sid__share_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_sensitivity_proforma_sensitivity_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SensitivityIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + solve_stateless_proforma_solve_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Assumptions"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_projects_projects_get: { + parameters: { + query?: { + limit?: number; + offset?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectOut"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_project_projects_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectOut"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + import_bundle_projects_import_bundle_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_bundle_projects_import_bundle_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectOut"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + preview_bundle_projects_preview_bundle_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_preview_bundle_projects_preview_bundle_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_project_projects__pid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectOut"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_project_projects__pid__delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + patch_project_projects__pid__patch: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProjectPatch"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectOut"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_costs_5d_projects__pid__5d_element_costs_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elements_5d_map_projects__pid__5d_heatmap_get: { + parameters: { + query?: { + by?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bills_iif_projects__pid__accounting_bills_iif_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + chart_of_accounts_projects__pid__accounting_chart_of_accounts_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + gl_csv_projects__pid__accounting_gl_csv_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + journal_projects__pid__accounting_journal_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_journal_batch_projects__pid__accounting_journal_batch_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_journal_batch_projects__pid__accounting_journal_batch__bid__export_get: { + parameters: { + query?: { + fmt?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + bid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + journal_entries_projects__pid__accounting_journal_entries_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + trial_balance_projects__pid__accounting_trial_balance_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + action_tracker_projects__pid__action_items_tracker_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_agent_packs_projects__pid__agent_packs_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_readiness_scorecard_projects__pid__ai_readiness_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_ask_projects__pid__ai_ask_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_audit_projects__pid__ai_audit_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_author_projects__pid__ai_author_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_ai_author_projects__pid__ai_author_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + draft_rfi_projects__pid__ai_draft_rfi_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_draft_rfi_projects__pid__ai_draft_rfi_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ai_estimate_projects__pid__ai_estimate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + risk_summary_projects__pid__ai_risk_summary_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + triage_rfi_projects__pid__ai_triage_rfi_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_triage_rfi_projects__pid__ai_triage_rfi_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + analytical_summary_projects__pid__analytical_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + answer_cited_query_projects__pid__answer_cited_query_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_appraisal_projects__pid__appraisal_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + save_appraisal_projects__pid__appraisal_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ask_model_projects__pid__ask_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_assistant_projects__pid__assistant_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + assistant_snapshot_projects__pid__assistant_snapshot_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + authoring_capabilities_projects__pid__authoring_capabilities_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_export_projects__pid__bcf_export_get: { + parameters: { + query?: { + version?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bcf_import_projects__pid__bcf_import_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_bcf_import_projects__pid__bcf_import_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bep_generate_projects__pid__bep_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + itb_summary_projects__pid__bidding_itb_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + invite_bidders_projects__pid__bidding_packages__rid__invite_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + scope_gap_analysis_projects__pid__bidding_scope_gap_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + leveling_projects__pid__bids_leveling_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + leveling_detail_projects__pid__bids_leveling__package_rid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + package_rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bim_kpi_scorecard_projects__pid__bim_kpi_scorecard_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + set_budget_baseline_projects__pid__budget_baseline_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clear_budget_baseline_projects__pid__budget_baseline_delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + budget_cashflow_projects__pid__budget_cashflow_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + gmp_budget_projects__pid__budget_gmp_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + two_sided_budget_projects__pid__budget_two_sided_get: { + parameters: { + query?: { + ltc?: number; + rate?: number; + construction_months?: number; + lp_pct?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + budget_variance_projects__pid__budget_variance_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_bundle_projects__pid__bundle_get: { + parameters: { + query?: { + asset_rights?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cam_reconciliation_projects__pid__cam_reconciliation_get: { + parameters: { + query?: { + year?: number | null; + gross_up_to_pct?: number; + building_sf?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cam_statement_projects__pid__cam_statement__rid__pdf_post: { + parameters: { + query?: { + year?: number | null; + gross_up_to_pct?: number; + building_sf?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cap_table_projects__pid__cap_table_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + capital_call_projects__pid__capital_call_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_capital_call_projects__pid__capital_call_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_carbon_projects__pid__carbon_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + carbon_compliance_report_projects__pid__carbon_compliance_get: { + parameters: { + query?: { + gfa_m2?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + carbon_elements_projects__pid__carbon_elements_get: { + parameters: { + query?: { + gfa_m2?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cde_exchange_acceptance_projects__pid__cde_exchange_acceptance_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cde_status_projects__pid__cde_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + co_log_projects__pid__change_orders_log_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ci_latest_projects__pid__ci_latest_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ci_run_projects__pid__ci_run_post: { + parameters: { + query?: { + create_topics?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_clash_projects__pid__clash_post: { + parameters: { + query?: { + a?: string | null; + b?: string | null; + a_q?: string | null; + b_q?: string | null; + min_volume?: number; + tolerance?: number; + narrow?: boolean; + max_narrow?: number; + create_topics?: boolean; + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + analyze_clashes_projects__pid__clash_analyze_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clash_clearance_rules_projects__pid__clash_clearance_rules_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + coordinate_clashes_projects__pid__clash_coordinate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_clash_federated_projects__pid__clash_federated_post: { + parameters: { + query?: { + min_volume?: number; + create_topics?: boolean; + coordinate?: boolean; + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_run_clash_federated_projects__pid__clash_federated_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clash_matrix_projects__pid__clash_matrix_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clash_metrics_projects__pid__clash_metrics_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + clash_sequence_projects__pid__clash_sequence_get: { + parameters: { + query?: { + min_overlap_days?: number; + crew_threshold?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + classify_coverage_projects__pid__classify_coverage_get: { + parameters: { + query?: { + system?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + classify_plan_projects__pid__classify_plan_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_classify_plan_projects__pid__classify_plan_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + classify_proposals_projects__pid__classify_proposals_get: { + parameters: { + query?: { + system?: string; + /** @description declared|keyword|class_map|unmapped */ + basis?: string | null; + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_client_decisions_projects__pid__client_decisions_get: { + parameters: { + query?: { + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + closeout_package_projects__pid__closeout_package_zip_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + closeout_summary_projects__pid__closeout_summary_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_pm_projects__pid__cmms_generate_pm_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cmms_kpis_projects__pid__cmms_kpis_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_code_amendments_projects__pid__code_amendments_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + put_code_amendments_projects__pid__code_amendments_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }[]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_check_projects__pid__codecheck_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_code_check_projects__pid__codecheck_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_analysis_projects__pid__codecheck_analysis_get: { + parameters: { + query?: { + occupancy_group?: string; + construction_type?: string; + sprinklered?: boolean; + jurisdiction?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + approvability_projects__pid__codecheck_approvability_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + approvability_to_bcf_projects__pid__codecheck_approvability_bcf_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + codecheck_ebc_projects__pid__codecheck_ebc_get: { + parameters: { + query?: { + jurisdiction?: string; + infer?: boolean; + adds_area?: boolean | null; + changes_occupancy?: boolean | null; + reconfigures_space?: boolean | null; + alters_openings?: boolean | null; + alters_systems?: boolean | null; + adds_equipment?: boolean | null; + replaces_same_purpose?: boolean | null; + repair_only?: boolean | null; + work_area_pct?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + codecheck_egress_projects__pid__codecheck_egress_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + egress_to_bcf_projects__pid__codecheck_egress_bcf_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + collab_snapshot_projects__pid__collab_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + commercial_drift_walk_projects__pid__commercial_drift_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + import_comparables_projects__pid__comparables_import_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + compliance_expiring_projects__pid__compliance_expiring_get: { + parameters: { + query?: { + within_days?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_tiered_comps_projects__pid__comps_tiered_get: { + parameters: { + query?: { + field?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + concept_render_ingest_projects__pid__concept_render_ingest_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + concept_render_request_projects__pid__concept_render_request_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + concept_render_status_projects__pid__concept_render_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + constraints_solve_projects__pid__constraints_solve_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + construction_draws_projects__pid__construction_draws_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + content_import_projects__pid__content_import_post: { + parameters: { + query?: { + category?: string; + e?: number; + n?: number; + scale?: number; + name?: string; + storey?: string; + publish?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_content_import_projects__pid__content_import_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + contractor_statements_projects__pid__contractor_statements_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_clause_playbook_projects__pid__contracts_playbook_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + put_clause_playbook_projects__pid__contracts_playbook_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_clause_playbook_projects__pid__contracts_playbook_put"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + post_clause_review_projects__pid__contracts_review_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_post_clause_review_projects__pid__contracts_review_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + digital_sign_projects__pid__contracts__key___rid__digital_sign_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + key: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + contract_document_projects__pid__contracts__key___rid__document_pdf_get: { + parameters: { + query?: { + doc?: string; + clauses?: string | null; + attach?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + key: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + contract_exhibit_docx_projects__pid__contracts__key___rid__exhibit_docx_get: { + parameters: { + query?: { + clauses?: string | null; + attach?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + key: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + send_for_signature_projects__pid__contracts__key___rid__send_for_signature_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + key: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sign_contract_projects__pid__contracts__key___rid__sign_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + key: string; + rid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + coordination_import_xlsx_projects__pid__coordination_import_xlsx_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + coordination_import_xml_projects__pid__coordination_import_xml_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_coordination_import_xml_projects__pid__coordination_import_xml_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + coordination_stale_projects__pid__coordination_stale_get: { + parameters: { + query: { + a: number; + b: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + coordination_stale_recheck_projects__pid__coordination_stale_recheck_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_coordination_stale_recheck_projects__pid__coordination_stale_recheck_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_spine_trace_projects__pid__cost_spine_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_cost_vintage_projects__pid__cost_vintage_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + set_cost_vintage_projects__pid__cost_vintage_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + advance_period_projects__pid__cost_advance_period_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_calibration_projects__pid__cost_calibration_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_estimate_projects__pid__cost_estimate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + g702_projects__pid__cost_g702_get: { + parameters: { + query?: { + app_no?: number; + period?: string | null; + release_retainage?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + g702_pdf_projects__pid__cost_g702_pdf_get: { + parameters: { + query?: { + app_no?: number; + period?: string | null; + release_retainage?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + g703_projects__pid__cost_g703_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + lien_waiver_projects__pid__cost_lien_waiver_get: { + parameters: { + query?: { + kind?: string; + app_no?: number; + claimant?: string; + customer?: string; + through_date?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + lien_waiver_pdf_projects__pid__cost_lien_waiver_pdf_get: { + parameters: { + query?: { + kind?: string; + app_no?: number; + claimant?: string; + customer?: string; + through_date?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + payapp_invoice_projects__pid__cost_pay_app_invoice_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_sov_projects__pid__cost_sov_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sov_from_budget_projects__pid__cost_sov_from_budget_post: { + parameters: { + query?: { + replace?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + summary_projects__pid__cost_summary_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + price_tm_projects__pid__cost_tm_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_price_tm_projects__pid__cost_tm_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cost_traceability_projects__pid__cost_traceability_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cv_progress_ingest_projects__pid__cv_progress_ingest_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cv_progress_ingest_batch_projects__pid__cv_progress_ingest_batch_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cv_progress_status_projects__pid__cv_progress_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cx_dossier_projects__pid__cx_dossier_get: { + parameters: { + query: { + system: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cx_matrix_projects__pid__cx_matrix_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cx_seed_projects__pid__cx_seed_post: { + parameters: { + query?: { + checklists?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + field_log_summary_projects__pid__daily_reports_summary_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_dashboard_projects__pid__dashboard_get: { + parameters: { + query?: { + party?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_deal_memory_beside_projects__pid__deal_memory_beside_get: { + parameters: { + query: { + hard_cost: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_deal_authority_projects__pid__deal_room_authority_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + put_deal_authority_projects__pid__deal_room_authority_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_deal_authority_projects__pid__deal_room_authority_put"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + post_decision_gate_projects__pid__decision_gate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_post_decision_gate_projects__pid__decision_gate_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_board_projects__pid__design_options_board_pdf_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_design_options_board_projects__pid__design_options_board_pdf_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_carbon_projects__pid__design_options_carbon_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_compare_projects__pid__design_options_compare_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_economics_projects__pid__design_options_economics_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_generate_projects__pid__design_options_generate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_design_options_generate_projects__pid__design_options_generate_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_record_projects__pid__design_options_record_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_options_score_projects__pid__design_options_score_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_design_options_score_projects__pid__design_options_score_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_standards_ruleset_projects__pid__design_standards_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + design_standards_check_projects__pid__design_standards_check_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + validate_detailing_projects__pid__detailing_rules_validate_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_detailing_projects__pid__detailing__guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_dev_budget_projects__pid__dev_budget_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + put_dev_budget_projects__pid__dev_budget_put: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DevBudgetIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + dev_budget_cost_lines_projects__pid__dev_budget_cost_lines_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + gmp_reconciliation_projects__pid__dev_budget_gmp_reconciliation_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sync_model_to_hard_projects__pid__dev_budget_sync_from_model_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sync_gmp_to_hard_projects__pid__dev_budget_sync_gmp_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_digest_projects__pid__digest_get: { + parameters: { + query?: { + scale?: string | null; + geometry?: boolean; + summary?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + project_digest_diff_projects__pid__digest_diff_post: { + parameters: { + query?: { + geometry?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + diligence_readiness_projects__pid__diligence_readiness_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + distribution_projects__pid__distribution_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_distribution_projects__pid__distribution_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doc_graph_projects__pid__doc_graph_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doctext_catalog_projects__pid__doctext_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doctext_ingest_projects__pid__doctext_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doctext_ask_projects__pid__doctext_ask_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doctext_search_projects__pid__doctext_search_get: { + parameters: { + query: { + q: string; + k?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + doctext_source_projects__pid__doctext__doc_id__source_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + doc_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_by_role_projects__pid__documents_by_role_get: { + parameters: { + query: { + role: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_file_model_projects__pid__documents_file_model_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_documents_file_model_projects__pid__documents_file_model_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_folder_projects__pid__documents_folder_get: { + parameters: { + query: { + path: string; + superseded?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_health_projects__pid__documents_health_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_model_history_projects__pid__documents_model_history_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_phase_gaps_projects__pid__documents_phase_gaps_get: { + parameters: { + query: { + phase: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_template_projects__pid__documents_template_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_tree_projects__pid__documents_tree_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_upload_projects__pid__documents_upload_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_documents_upload_projects__pid__documents_upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_delete_projects__pid__documents__fid__delete: { + parameters: { + query?: { + hard?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + fid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_download_projects__pid__documents__fid__download_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + fid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + documents_move_projects__pid__documents__fid__move_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + fid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_documents_move_projects__pid__documents__fid__move_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + draft_rfi_ep_projects__pid__draft_rfi_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_draft_rfi_ep_projects__pid__draft_rfi_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + draft_scope_ep_projects__pid__draft_scope_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_draft_scope_ep_projects__pid__draft_scope_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + draft_submittal_ep_projects__pid__draft_submittal_summary_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_drawing_set_projects__pid__drawing_set_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_set_compiled_projects__pid__drawing_set_compiled_pdf_get: { + parameters: { + query?: { + scale?: number; + max_sheets?: number; + schedules?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + file_drawing_set_route_projects__pid__drawing_set_file_drawing_set_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + generate_drawing_set_projects__pid__drawing_set_generate_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_issuance_matrix_projects__pid__drawing_set_issuance_matrix_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + issuance_purposes_projects__pid__drawing_set_issuance_purposes_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_issuances_projects__pid__drawing_set_issuances_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + issuance_sealed_projects__pid__drawing_set_issuances__iid__sealed_pdf_get: { + parameters: { + query?: { + name?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + iid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + issuance_transmittal_projects__pid__drawing_set_issuances__iid__transmittal_pdf_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + iid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + issue_drawing_set_projects__pid__drawing_set_issue_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_set_plan_projects__pid__drawing_set_plan_get: { + parameters: { + query?: { + disciplines?: string; + all?: boolean; + max_levels?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_set_qa_projects__pid__drawing_set_qa_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_detail_references_projects__pid__drawing_set_references_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_revisions_projects__pid__drawing_set_revisions_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_set_transmittal_projects__pid__drawing_set_transmittal_pdf_get: { + parameters: { + query?: { + to?: string; + note?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elevation_dxf_projects__pid__drawings_elevation_dxf_get: { + parameters: { + query?: { + direction?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elevation_projects__pid__drawings_elevation_svg_get: { + parameters: { + query?: { + direction?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + layout_pdf_projects__pid__drawings_layout_pdf_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_layout_pdf_projects__pid__drawings_layout_pdf_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + layout_svg_projects__pid__drawings_layout_svg_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_layout_svg_projects__pid__drawings_layout_svg_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + layout_presets_projects__pid__drawings_layout_presets_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_markup_projects__pid__drawings_markup_get: { + parameters: { + query?: { + sheet?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + add_markup_projects__pid__drawings_markup_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MarkupIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + save_markup_bulk_projects__pid__drawings_markup_bulk_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MarkupBulkIn"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + markup_stream_projects__pid__drawings_markup_stream_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_markup_projects__pid__drawings_markup__mid__delete: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + mid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + promote_markup_projects__pid__drawings_markup__mid__promote_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + mid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + rekey_storey_markups_projects__pid__drawings_markups_rekey_storeys_post: { + parameters: { + query?: { + dry_run?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + plan_dxf_projects__pid__drawings_plan_dxf_get: { + parameters: { + query?: { + elevation?: number; + cut_height?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + plan_projects__pid__drawings_plan_svg_get: { + parameters: { + query?: { + elevation?: number; + cut_height?: number; + title?: string; + storey?: string | null; + rooms?: boolean; + callouts?: boolean; + view_depth?: number | null; + by_discipline?: boolean; + pins?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + received_sheet_regions_projects__pid__drawings_received_regions_post: { + parameters: { + query?: { + page_index?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_received_sheet_regions_projects__pid__drawings_received_regions_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + schedule_csv_projects__pid__drawings_schedule_csv_get: { + parameters: { + query?: { + kind?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + schedule_pdf_projects__pid__drawings_schedule_pdf_get: { + parameters: { + query?: { + kinds?: string; + number?: string; + title?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + schedule_svg_projects__pid__drawings_schedule_svg_get: { + parameters: { + query?: { + kind?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_schedules_projects__pid__drawings_schedules_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawing_schedules_calc_projects__pid__drawings_schedules_calc_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + section_dxf_projects__pid__drawings_section_dxf_get: { + parameters: { + query?: { + axis?: string; + offset?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + section_projects__pid__drawings_section_svg_get: { + parameters: { + query?: { + axis?: string; + offset?: number | null; + title?: string; + lod?: string; + annotate?: boolean; + hatch?: boolean; + keynotes?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sheet_regions_endpoint_projects__pid__drawings_sheet_regions_get: { + parameters: { + query?: { + preset?: string; + page?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sheet_dxf_projects__pid__drawings_sheet_dxf_get: { + parameters: { + query?: { + sheet?: string; + page?: string; + purpose?: string; + rev?: string; + storey?: string | null; + views?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sheet_pdf_projects__pid__drawings_sheet_pdf_get: { + parameters: { + query?: { + sheet?: string; + page?: string; + purpose?: string; + rev?: string; + storey?: string | null; + views?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sheet_svg_projects__pid__drawings_sheet_svg_get: { + parameters: { + query?: { + sheet?: string; + page?: string; + purpose?: string; + rev?: string; + storey?: string | null; + views?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_storeys_projects__pid__drawings_storeys_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawings_stream_projects__pid__drawings_stream_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + drawings_sync_status_projects__pid__drawings_sync_status_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + revise_drawing_projects__pid__drawings__drawing_id__revise_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + drawing_id: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + due_feed_projects__pid__due_feed_get: { + parameters: { + query?: { + days?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_projects__pid__edit_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_edit_projects__pid__edit_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_preview_projects__pid__edit_preview_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_edit_preview_projects__pid__edit_preview_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_batch_projects__pid__edit_batch_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_edit_batch_projects__pid__edit_batch_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_graph_projects__pid__edit_graph_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_edit_graph_projects__pid__edit_graph_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_history_state_projects__pid__edit_history_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_precheck_projects__pid__edit_precheck_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_edit_precheck_projects__pid__edit_precheck_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_redo_projects__pid__edit_redo_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_edit_redo_projects__pid__edit_redo_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + edit_undo_projects__pid__edit_undo_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_edit_undo_projects__pid__edit_undo_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + egress_plan_svg_projects__pid__egress_plan_svg_get: { + parameters: { + query?: { + elevation?: number; + max_travel_m?: number; + cell?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + egress_routes_projects__pid__egress_routes_get: { + parameters: { + query?: { + elevation?: number; + max_travel_m?: number; + cell?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_connections_projects__pid__element_connections_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_elements_projects__pid__elements_get: { + parameters: { + query?: { + ifc_class?: string | null; + storey?: string | null; + discipline?: string | null; + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elements_by_discipline_projects__pid__elements_by_discipline_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + code_check_projects__pid__elements_code_check_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + color_by_projects__pid__elements_color_by_get: { + parameters: { + query: { + prop: string; + bins?: number; + ids?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + color_facets_projects__pid__elements_facets_list_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elements_freshness_projects__pid__elements_freshness_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + elements_properties_projects__pid__elements_properties_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + data_qa_projects__pid__elements_qa_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_projects__pid__elements__guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_5d_projects__pid__elements__guid__5d_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_costs_projects__pid__elements__guid__costs_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_lifecycle_projects__pid__elements__guid__lifecycle_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_records_projects__pid__elements__guid__records_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + element_sources_projects__pid__elements__guid__sources_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + energy_projects__pid__energy_get: { + parameters: { + query?: { + u_wall?: number | null; + u_window?: number | null; + ach?: number | null; + hdd?: number | null; + cdd?: number | null; + delta_t?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + energy_summary_projects__pid__energy_actual_get: { + parameters: { + query?: { + gfa_sf?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + energy_gbxml_projects__pid__energy_export_gbxml_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + energy_idf_projects__pid__energy_export_idf_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + energy_model_projects__pid__energy_model_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + entitlement_condition_checks_projects__pid__entitlements_condition_checks_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + entitlement_conditions_projects__pid__entitlements_conditions_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + entitlement_review_cycles_projects__pid__entitlements_review_cycles_get: { + parameters: { + query?: { + application?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_enum_options_projects__pid__enum_options_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + env_wind_screen_projects__pid__env_wind_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + envelope_audit_projects__pid__envelope_audit_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + envelope_check_projects__pid__envelope_check_get: { + parameters: { + query?: { + element_type?: string; + climate_zone?: string; + r_value?: number | null; + u_factor?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + escalations_scan_projects__pid__escalations_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + escalations_run_projects__pid__escalations_run_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + esg_summary_projects__pid__esg_get: { + parameters: { + query?: { + gfa_sf?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_bands_projects__pid__estimate_bands_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_boe_projects__pid__estimate_boe_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_cbs_projects__pid__estimate_cbs_get: { + parameters: { + query?: { + indirect_pct?: number | null; + contingency_pct?: number | null; + management_reserve_pct?: number | null; + fee_pct?: number | null; + tax_pct?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_concept_budget_projects__pid__estimate_concept_budget_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + conceptual_projects__pid__estimate_conceptual_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_confidence_projects__pid__estimate_confidence_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** MemberIn */ - MemberIn: { - /** User */ - user: string; - /** Role */ - role: string; - /** Party Role */ - party_role?: string | null; - /** Company */ - company?: string | null; + }; + estimate_diff_route_projects__pid__estimate_diff_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** MonteCarloIn */ - MonteCarloIn: { - assumptions: components["schemas"]["Assumptions"]; - /** Variables */ - variables: components["schemas"]["MonteCarloVar"][]; - /** - * Iterations - * @default 1000 - */ - iterations: number; - /** - * Seed - * @default 42 - */ - seed: number; - /** Metrics */ - metrics?: string[] | null; - /** Targets */ - targets?: { - [key: string]: number; - } | null; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; }; - /** MonteCarloVar */ - MonteCarloVar: { - /** Path */ - path: string; - dist: components["schemas"]["Distribution"]; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** Ops */ - Ops: { - /** Potential Rent Annual */ - potential_rent_annual: number; - /** - * Other Income Annual - * @default 0 - */ - other_income_annual: number; - /** Opex Annual */ - opex_annual: number; - /** - * Reserves Annual - * @default 0 - */ - reserves_annual: number; - /** Stabilized Occ */ - stabilized_occ: number; - /** - * Credit Loss Pct - * @default 0 - */ - credit_loss_pct: number; + }; + estimate_from_model_projects__pid__estimate_from_model_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_gaeb_projects__pid__estimate_gaeb_x83_get: { + parameters: { + query?: { + system?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + labor_estimate_projects__pid__estimate_labor_get: { + parameters: { + query?: { + loading?: string; + rate?: number; + full?: boolean; + crews?: number; + qto?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + estimate_resource_based_projects__pid__estimate_resource_based_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_snapshot_projects__pid__evm_get: { + parameters: { + query?: { + data_date?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_earned_schedule_projects__pid__evm_earned_schedule_get: { + parameters: { + query?: { + period?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_model_ev_projects__pid__evm_model_ev_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_scurve_projects__pid__evm_scurve_get: { + parameters: { + query?: { + period?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_capture_projects__pid__evm_snapshot_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_evm_capture_projects__pid__evm_snapshot_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + evm_trend_projects__pid__evm_trend_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_subset_ifc_projects__pid__export_subset_ifc_get: { + parameters: { + query: { + query: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** - * OptimizeIn - * @description Generative design: sweep schemes and rank by an objective, filtered by targets. - */ - OptimizeIn: { - /** Plate W */ - plate_w: number; - /** Plate D */ - plate_d: number; - /** - * Floors - * @default 1 - */ - floors: number; - /** Targets */ - targets?: { - [key: string]: unknown; + }; + export_cobie_projects__pid__exports_cobie_xlsx_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - /** Econ */ - econ?: { - [key: string]: unknown; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - /** Pid */ - pid?: string | null; }; - /** ProjectIn */ - ProjectIn: { - /** Name */ - name: string; - /** Origin */ - origin?: { - [key: string]: unknown; - } | null; - /** Source Ifc */ - source_ifc?: string | null; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** ProjectOut */ - ProjectOut: { - /** Name */ - name: string; - /** Origin */ - origin?: { - [key: string]: unknown; - } | null; - /** Source Ifc */ - source_ifc?: string | null; - /** Id */ - id: string; - /** - * Created At - * Format: date-time - */ - created_at: string; - /** Model Kind */ - model_kind?: string | null; - /** - * Has Source Ifc - * @default false - */ - has_source_ifc: boolean; + }; + export_gbxml_projects__pid__exports_model_gbxml_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** ProjectPatch */ - ProjectPatch: { - /** Name */ - name?: string | null; - /** Origin */ - origin?: { - [key: string]: unknown; - } | null; - /** Source Ifc */ - source_ifc?: string | null; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** ScenarioIn */ - ScenarioIn: { - /** Name */ - name: string; - /** Project Id */ - project_id?: string | null; - assumptions: components["schemas"]["Assumptions"]; + }; + export_qto_projects__pid__exports_qto_xlsx_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** ScheduleIn */ - ScheduleIn: { - /** Connection Id */ - connection_id: string; - /** Procore Project Id */ - procore_project_id: string; - /** - * Kinds - * @default [ - * "rfi", - * "submittal", - * "change_event" - * ] - */ - kinds: string[]; - /** - * Interval Minutes - * @default 60 - */ - interval_minutes: number; - /** - * Enabled - * @default true - */ - enabled: boolean; - /** - * Push - * @default false - */ - push: boolean; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** SensitivityIn */ - SensitivityIn: { - assumptions: components["schemas"]["Assumptions"]; - x: components["schemas"]["Axis"]; - y: components["schemas"]["Axis"]; - /** - * Metric - * @default returns.equity_irr - */ - metric: string; + }; + export_schedule_projects__pid__exports_schedule_xlsx_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** - * StructureIn - * @description Structural-system advice for a given scale. - */ - StructureIn: { - /** Height M */ - height_m: number; - /** - * Floors - * @default 1 - */ - floors: number; - /** - * Span M - * @default 7.5 - */ - span_m: number; - /** - * Use Type - * @default residential - */ - use_type: string; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** TaktIn */ - TaktIn: { - /** Floors */ - floors: number; - /** Trades */ - trades?: { - [key: string]: unknown; - }[] | null; - /** - * Jit Lead Days - * @default 1 - */ - jit_lead_days: number; + }; + export_spaces_projects__pid__exports_spaces_xlsx_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** Tax */ - Tax: { - /** - * Income Tax Rate - * @default 0.25 - */ - income_tax_rate: number; - /** - * Depreciation Years - * @default 27.5 - */ - depreciation_years: number; - /** - * Capital Gains Rate - * @default 0.2 - */ - capital_gains_rate: number; - /** - * Niit Rate - * @default 0.038 - */ - niit_rate: number; - /** - * Recapture Rate - * @default 0.25 - */ - recapture_rate: number; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** TemplateIn */ - TemplateIn: { - /** Module */ - module: string; - /** Name */ - name: string; - /** - * Items - * @default [] - */ - items: { - [key: string]: unknown; - }[]; + }; + extract_sheets_ep_projects__pid__extract_sheets_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** - * TestFitIn - * @description Fit a unit mix to a floor plate and compare schemes (TestFit-style). - */ - TestFitIn: { - /** Plate W */ - plate_w: number; - /** Plate D */ - plate_d: number; - /** - * Floors - * @default 1 - */ - floors: number; - /** Schemes */ - schemes?: { - [key: string]: unknown; - }[]; - /** - * With Defaults - * @default false - */ - with_defaults: boolean; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_extract_sheets_ep_projects__pid__extract_sheets_post"]; + }; }; - /** Tier */ - Tier: { - /** Hurdle */ - hurdle?: number | null; - /** Lp */ - lp: number; - /** Gp */ - gp: number; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** Timing */ - Timing: { - /** Construction Months */ - construction_months: number; - /** - * Leaseup Months - * @default 0 - */ - leaseup_months: number; - /** Hold Years */ - hold_years: number; - /** Start Date */ - start_date?: string | null; + }; + import_families_projects__pid__families_import_post: { + parameters: { + query?: { + publish?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** TopicIn */ - TopicIn: { - /** - * Type - * @default info - * @enum {string} - */ - type: "rfi" | "punch" | "clash" | "info"; - /** Title */ - title: string; - /** Description */ - description?: string | null; - /** - * Status - * @default open - */ - status: string; - /** Priority */ - priority?: string | null; - /** Assignee */ - assignee?: string | null; - /** Author */ - author?: string | null; - /** Due Date */ - due_date?: string | null; - /** Labels */ - labels?: string[] | null; - /** Anchor */ - anchor?: { - [key: string]: number; - } | null; - /** Element Guids */ - element_guids?: string[] | null; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_families_projects__pid__families_import_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** TopicOut */ - TopicOut: { - /** Id */ - id: string; - /** Guid */ - guid: string; - /** Project Id */ - project_id: string; - /** Type */ - type: string; - /** Title */ - title: string; - /** Description */ - description: string | null; - /** Status */ - status: string; - /** Priority */ - priority: string | null; - /** Assignee */ - assignee: string | null; - /** Author */ - author: string | null; - /** Due Date */ - due_date: string | null; - /** Labels */ - labels: string[] | null; - /** Anchor */ - anchor: { - [key: string]: number; - } | null; - /** Element Guids */ - element_guids: string[] | null; - /** - * Created At - * Format: date-time - */ - created_at: string; - /** - * Modified At - * Format: date-time - */ - modified_at: string; + }; + import_family_pack_projects__pid__families_import_pack_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** TopicPatch */ - TopicPatch: { - /** Type */ - type?: ("rfi" | "punch" | "clash" | "info") | null; - /** Title */ - title?: string | null; - /** Description */ - description?: string | null; - /** Status */ - status?: string | null; - /** Priority */ - priority?: string | null; - /** Assignee */ - assignee?: string | null; - /** Due Date */ - due_date?: string | null; - /** Labels */ - labels?: string[] | null; - /** Anchor */ - anchor?: { - [key: string]: number; - } | null; - /** Element Guids */ - element_guids?: string[] | null; + requestBody: { + content: { + "application/json": components["schemas"]["Body_import_family_pack_projects__pid__families_import_pack_post"]; + }; }; - /** UserPatch */ - UserPatch: { - /** Role */ - role?: string | null; - /** Active */ - active?: boolean | null; - /** Email */ - email?: string | null; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** ValidationError */ - ValidationError: { - /** Location */ - loc: (string | number)[]; - /** Message */ - msg: string; - /** Error Type */ - type: string; - /** Input */ - input?: unknown; - /** Context */ - ctx?: Record; + }; + place_family_projects__pid__families_place_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - /** ViewpointIn */ - ViewpointIn: { - /** Camera */ - camera?: { - [key: string]: unknown; - } | null; - /** Clipping Planes */ - clipping_planes?: { - [key: string]: unknown; - }[] | null; - /** Visibility */ - visibility?: { - [key: string]: unknown; - } | null; - /** Components */ - components?: string[] | null; - /** Snapshot */ - snapshot?: string | null; + requestBody: { + content: { + "application/json": components["schemas"]["Body_place_family_projects__pid__families_place_post"]; + }; }; - /** ViewpointOut */ - ViewpointOut: { - /** Id */ - id: string; - /** Guid */ - guid: string; - /** Topic Id */ - topic_id: string; - /** Camera */ - camera: { - [key: string]: unknown; - } | null; - /** Clipping Planes */ - clipping_planes: { - [key: string]: unknown; - }[] | null; - /** Visibility */ - visibility: { - [key: string]: unknown; - } | null; - /** Components */ - components: string[] | null; - /** Snapshot */ - snapshot: string | null; - /** - * Created At - * Format: date-time - */ - created_at: string; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; - /** Waterfall */ - Waterfall: { - /** - * Pref Rate - * @default 0.08 - */ - pref_rate: number; - /** - * Style - * @default american - * @enum {string} - */ - style: "american" | "european"; - /** - * Clawback - * @default false - */ - clawback: boolean; - /** Tiers */ - tiers: components["schemas"]["Tier"][]; + }; + fca_index_projects__pid__fca_index_get: { + parameters: { + query?: { + crv?: number | null; + gfa_sf?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - list_projects_projects_get: { + site_feasibility_projects__pid__feasibility_get: { parameters: { query?: { - limit?: number; - offset?: number; + gfa?: number | null; + zoning_id?: string | null; }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -12146,7 +42730,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ProjectOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12160,31 +42744,71 @@ export interface operations { }; }; }; - create_project_projects_post: { + feasibility_compare_projects__pid__feasibility_compare_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + feasibility_lot_supply_projects__pid__feasibility_lot_supply_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; requestBody: { content: { - "application/json": components["schemas"]["ProjectIn"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ProjectOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12198,7 +42822,49 @@ export interface operations { }; }; }; - my_membership_projects__pid__me_get: { + feasibility_sellout_projects__pid__feasibility_sellout_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + ffe_bom_projects__pid__ffe_bom_get: { parameters: { query?: never; header?: { @@ -12234,7 +42900,45 @@ export interface operations { }; }; }; - presence_roster_projects__pid__presence_get: { + finance_imports_projects__pid__finance_imports_get: { + parameters: { + query?: { + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_finance_lock_projects__pid__finance_lock_get: { parameters: { query?: never; header?: { @@ -12270,7 +42974,7 @@ export interface operations { }; }; }; - heartbeat_projects__pid__presence_post: { + put_finance_lock_projects__pid__finance_lock_put: { parameters: { query?: never; header?: { @@ -12286,7 +42990,7 @@ export interface operations { }; requestBody?: { content: { - "application/json": components["schemas"]["Body_heartbeat_projects__pid__presence_post"]; + "application/json": components["schemas"]["Body_put_finance_lock_projects__pid__finance_lock_put"]; }; }; responses: { @@ -12310,11 +43014,45 @@ export interface operations { }; }; }; - list_markup_projects__pid__drawings_markup_get: { + finance_reconcile_projects__pid__finance_reconcile_get: { parameters: { - query?: { - sheet?: string | null; + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; }; + }; + }; + project_financials_projects__pid__financials_get: { + parameters: { + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -12348,7 +43086,7 @@ export interface operations { }; }; }; - add_markup_projects__pid__drawings_markup_post: { + generate_massing_projects__pid__generate_massing_post: { parameters: { query?: never; header?: { @@ -12364,12 +43102,12 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["MarkupIn"]; + "application/json": components["schemas"]["MassingIn"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -12388,7 +43126,7 @@ export interface operations { }; }; }; - save_markup_bulk_projects__pid__drawings_markup_bulk_post: { + project_georeference_projects__pid__georeference_get: { parameters: { query?: never; header?: { @@ -12402,14 +43140,46 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["MarkupBulkIn"]; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + golden_thread_summary_projects__pid__golden_thread_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -12428,7 +43198,7 @@ export interface operations { }; }; }; - delete_markup_projects__pid__drawings_markup__mid__delete: { + golden_thread_seed_projects__pid__golden_thread_seed_post: { parameters: { query?: never; header?: { @@ -12437,7 +43207,6 @@ export interface operations { }; path: { pid: string; - mid: string; }; cookie?: { aec_token?: string | null; @@ -12465,7 +43234,7 @@ export interface operations { }; }; }; - promote_markup_projects__pid__drawings_markup__mid__promote_post: { + model_graph_projects__pid__graph_get: { parameters: { query?: never; header?: { @@ -12474,7 +43243,6 @@ export interface operations { }; path: { pid: string; - mid: string; }; cookie?: { aec_token?: string | null; @@ -12483,7 +43251,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -12502,9 +43270,12 @@ export interface operations { }; }; }; - list_members_projects__pid__members_get: { + model_graph_neighbors_projects__pid__graph_neighbors_get: { parameters: { - query?: never; + query: { + guid: string; + depth?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -12538,7 +43309,7 @@ export interface operations { }; }; }; - add_member_projects__pid__members_post: { + list_groups_projects__pid__groups_get: { parameters: { query?: never; header?: { @@ -12552,14 +43323,47 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["MemberIn"]; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; }; }; + }; + group_detail_projects__pid__groups__guid__get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + guid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -12578,7 +43382,7 @@ export interface operations { }; }; }; - remove_member_projects__pid__members__member__delete: { + handover_acceptance_projects__pid__handover_acceptance_get: { parameters: { query?: never; header?: { @@ -12587,7 +43391,6 @@ export interface operations { }; path: { pid: string; - member: string; }; cookie?: { aec_token?: string | null; @@ -12615,7 +43418,7 @@ export interface operations { }; }; }; - get_project_projects__pid__get: { + project_health_projects__pid__health_get: { parameters: { query?: never; header?: { @@ -12637,7 +43440,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ProjectOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12651,7 +43454,7 @@ export interface operations { }; }; }; - delete_project_projects__pid__delete: { + get_hero_projects__pid__hero_get: { parameters: { query?: never; header?: { @@ -12687,7 +43490,7 @@ export interface operations { }; }; }; - patch_project_projects__pid__patch: { + put_hero_projects__pid__hero_put: { parameters: { query?: never; header?: { @@ -12703,7 +43506,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["ProjectPatch"]; + "multipart/form-data": components["schemas"]["Body_put_hero_projects__pid__hero_put"]; }; }; responses: { @@ -12713,7 +43516,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ProjectOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12727,7 +43530,7 @@ export interface operations { }; }; }; - list_versions_projects__pid__versions_get: { + delete_hero_projects__pid__hero_delete: { parameters: { query?: never; header?: { @@ -12763,12 +43566,9 @@ export interface operations { }; }; }; - diff_versions_projects__pid__versions_diff_get: { + post_hold_sell_projects__pid__hold_sell_post: { parameters: { - query: { - a: number; - b: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -12780,7 +43580,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_post_hold_sell_projects__pid__hold_sell_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -12802,9 +43606,11 @@ export interface operations { }; }; }; - export_bundle_projects__pid__bundle_get: { + get_project_ids_projects__pid__ids_get: { parameters: { - query?: never; + query?: { + download?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -12838,26 +43644,33 @@ export interface operations { }; }; }; - import_bundle_projects_import_bundle_post: { + put_project_ids_projects__pid__ids_put: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_import_bundle_projects_import_bundle_post"]; + "multipart/form-data": components["schemas"]["Body_put_project_ids_projects__pid__ids_put"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ProjectOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12871,12 +43684,9 @@ export interface operations { }; }; }; - list_topics_projects__pid__topics_get: { + delete_project_ids_projects__pid__ids_delete: { parameters: { - query?: { - type?: string | null; - status?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -12896,7 +43706,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TopicOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12910,7 +43720,7 @@ export interface operations { }; }; }; - create_topic_projects__pid__topics_post: { + classify_projects__pid__ifc_classify_post: { parameters: { query?: never; header?: { @@ -12924,19 +43734,19 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["TopicIn"]; + "application/json": components["schemas"]["Body_classify_projects__pid__ifc_classify_post"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TopicOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12950,30 +43760,36 @@ export interface operations { }; }; }; - get_topic_projects__pid__topics__tid__get: { + import_rvt_projects__pid__import_rvt_post: { parameters: { - query?: never; + query?: { + confirm_cost?: boolean; + publish?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_rvt_projects__pid__import_rvt_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 202: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TopicOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -12987,7 +43803,7 @@ export interface operations { }; }; }; - patch_topic_projects__pid__topics__tid__patch: { + requirements_cascade_projects__pid__info_requirements_cascade_get: { parameters: { query?: never; header?: { @@ -12996,17 +43812,12 @@ export interface operations { }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["TopicPatch"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13014,7 +43825,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TopicOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13028,7 +43839,7 @@ export interface operations { }; }; }; - list_pins_projects__pid__pins_get: { + requirements_delivery_plan_projects__pid__info_requirements_delivery_plan_get: { parameters: { query?: never; header?: { @@ -13050,7 +43861,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TopicOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13064,7 +43875,7 @@ export interface operations { }; }; }; - list_comments_projects__pid__topics__tid__comments_get: { + requirements_register_projects__pid__info_requirements_register_get: { parameters: { query?: never; header?: { @@ -13073,7 +43884,6 @@ export interface operations { }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; @@ -13087,7 +43897,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CommentOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13101,7 +43911,7 @@ export interface operations { }; }; }; - add_comment_projects__pid__topics__tid__comments_post: { + speckle_send_projects__pid__interop_speckle_send_post: { parameters: { query?: never; header?: { @@ -13110,25 +43920,20 @@ export interface operations { }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["CommentIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 202: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CommentOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13142,7 +43947,7 @@ export interface operations { }; }; }; - list_viewpoints_projects__pid__topics__tid__viewpoints_get: { + investment_deck_projects__pid__investment_deck_pdf_get: { parameters: { query?: never; header?: { @@ -13151,7 +43956,6 @@ export interface operations { }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; @@ -13165,7 +43969,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ViewpointOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13179,7 +43983,7 @@ export interface operations { }; }; }; - add_viewpoint_projects__pid__topics__tid__viewpoints_post: { + investment_memo_projects__pid__investment_memo_pdf_get: { parameters: { query?: never; header?: { @@ -13188,25 +43992,20 @@ export interface operations { }; path: { pid: string; - tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["ViewpointIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ViewpointOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13220,16 +44019,18 @@ export interface operations { }; }; }; - list_attachments_projects__pid__topics__tid__attachments_get: { + investor_statement_share_projects__pid__investors__iid__share_post: { parameters: { - query?: never; + query?: { + ttl?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - tid: string; + iid: string; }; cookie?: { aec_token?: string | null; @@ -13243,7 +44044,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AttachmentOut"][]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13257,7 +44058,7 @@ export interface operations { }; }; }; - add_attachment_projects__pid__topics__tid__attachments_post: { + investor_statement_projects__pid__investors__iid__statement_pdf_get: { parameters: { query?: never; header?: { @@ -13266,25 +44067,53 @@ export interface operations { }; path: { pid: string; - tid: string; + iid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_add_attachment_projects__pid__topics__tid__attachments_post"]; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + investor_statement_public_projects__pid__investors__iid__statement_public_pdf_get: { + parameters: { + query?: never; + header?: never; + path: { + pid: string; + iid: string; }; + cookie?: never; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["AttachmentOut"]; + "application/json": unknown; }; }; /** @description Validation Error */ @@ -13298,15 +44127,17 @@ export interface operations { }; }; }; - download_attachment_attachments__aid__download_get: { + list_jobs_projects__pid__jobs_get: { parameters: { - query?: never; + query?: { + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - aid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -13334,7 +44165,7 @@ export interface operations { }; }; }; - sign_attachment_attachments__aid__signed_url_get: { + enqueue_job_projects__pid__jobs_post: { parameters: { query?: never; header?: { @@ -13342,16 +44173,20 @@ export interface operations { authorization?: string | null; }; path: { - aid: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_enqueue_job_projects__pid__jobs_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -13370,7 +44205,7 @@ export interface operations { }; }; }; - model_frag_projects__pid__model_frag_get: { + job_status_projects__pid__jobs__job_id__get: { parameters: { query?: never; header?: { @@ -13379,6 +44214,7 @@ export interface operations { }; path: { pid: string; + job_id: string; }; cookie?: { aec_token?: string | null; @@ -13406,7 +44242,7 @@ export interface operations { }; }; }; - sign_model_frag_projects__pid__model_frag_signed_url_get: { + job_artifact_projects__pid__jobs__job_id__artifact_get: { parameters: { query?: never; header?: { @@ -13415,6 +44251,7 @@ export interface operations { }; path: { pid: string; + job_id: string; }; cookie?: { aec_token?: string | null; @@ -13442,7 +44279,7 @@ export interface operations { }; }; }; - source_ifc_download_projects__pid__source_ifc_get: { + deliver_artifact_projects__pid__jobs__job_id__deliver_post: { parameters: { query?: never; header?: { @@ -13451,12 +44288,17 @@ export interface operations { }; path: { pid: string; + job_id: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_deliver_artifact_projects__pid__jobs__job_id__deliver_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -13478,10 +44320,10 @@ export interface operations { }; }; }; - bcf_export_projects__pid__bcf_export_get: { + project_check_projects__pid__jurisdiction_check_get: { parameters: { query?: { - version?: string; + pack?: string | null; }; header?: { "x-user"?: string | null; @@ -13516,9 +44358,11 @@ export interface operations { }; }; }; - bcf_import_projects__pid__bcf_import_post: { + project_requirements_projects__pid__jurisdiction_requirements_get: { parameters: { - query?: never; + query?: { + pack?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -13530,11 +44374,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_bcf_import_projects__pid__bcf_import_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13556,9 +44396,11 @@ export interface operations { }; }; }; - coordination_import_xlsx_projects__pid__coordination_import_xlsx_post: { + k1_pack_projects__pid__k1_pack_get: { parameters: { - query?: never; + query?: { + period?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -13570,11 +44412,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_coordination_import_xlsx_projects__pid__coordination_import_xlsx_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13596,7 +44434,7 @@ export interface operations { }; }; }; - upload_index_projects__pid__properties_index_post: { + get_layers_projects__pid__layers_get: { parameters: { query?: never; header?: { @@ -13610,11 +44448,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_upload_index_projects__pid__properties_index_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13636,7 +44470,7 @@ export interface operations { }; }; }; - meta_projects__pid__properties_meta_get: { + put_layers_projects__pid__layers_put: { parameters: { query?: never; header?: { @@ -13650,48 +44484,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - list_elements_projects__pid__elements_get: { - parameters: { - query?: { - ifc_class?: string | null; - storey?: string | null; - discipline?: string | null; - limit?: number; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13713,7 +44512,7 @@ export interface operations { }; }; }; - color_facets_projects__pid__elements_facets_list_get: { + bake_layers_projects__pid__layers_bake_post: { parameters: { query?: never; header?: { @@ -13727,47 +44526,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - color_by_projects__pid__elements_color_by_get: { - parameters: { - query: { - prop: string; - bins?: number; - ids?: boolean; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_bake_layers_projects__pid__layers_bake_post"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -13789,7 +44552,7 @@ export interface operations { }; }; }; - elements_by_discipline_projects__pid__elements_by_discipline_get: { + resolve_layers_projects__pid__layers_resolve_get: { parameters: { query?: never; header?: { @@ -13825,9 +44588,11 @@ export interface operations { }; }; }; - data_qa_projects__pid__elements_qa_get: { + layout_dxf_projects__pid__layout_dxf_get: { parameters: { - query?: never; + query?: { + classes?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -13861,9 +44626,12 @@ export interface operations { }; }; }; - code_check_projects__pid__elements_code_check_get: { + layout_points_projects__pid__layout_points_get: { parameters: { - query?: never; + query?: { + classes?: string | null; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -13897,16 +44665,19 @@ export interface operations { }; }; }; - element_projects__pid__elements__guid__get: { + layout_csv_projects__pid__layout_points_csv_get: { parameters: { - query?: never; + query?: { + classes?: string | null; + order?: string; + delimiter?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - guid: string; }; cookie?: { aec_token?: string | null; @@ -13934,7 +44705,7 @@ export interface operations { }; }; }; - ask_model_projects__pid__ask_post: { + layout_verify_projects__pid__layout_verify_post: { parameters: { query?: never; header?: { @@ -13948,7 +44719,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { "application/json": { [key: string]: unknown; @@ -13976,7 +44747,7 @@ export interface operations { }; }; }; - export_qto_projects__pid__exports_qto_xlsx_get: { + lean_ppc_projects__pid__lean_ppc_get: { parameters: { query?: never; header?: { @@ -14012,9 +44783,12 @@ export interface operations { }; }; }; - export_cobie_projects__pid__exports_cobie_xlsx_get: { + lease_management_projects__pid__leases_management_get: { parameters: { - query?: never; + query?: { + years?: number; + recoverable_opex?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14048,7 +44822,7 @@ export interface operations { }; }; }; - export_spaces_projects__pid__exports_spaces_xlsx_get: { + lifecycle_projects__pid__lifecycle_get: { parameters: { query?: never; header?: { @@ -14084,7 +44858,7 @@ export interface operations { }; }; }; - export_gbxml_projects__pid__exports_model_gbxml_get: { + seed_projects__pid__lifecycle_seed_post: { parameters: { query?: never; header?: { @@ -14120,7 +44894,7 @@ export interface operations { }; }; }; - export_schedule_projects__pid__exports_schedule_xlsx_get: { + listing_autofill_projects__pid__listings_autofill_get: { parameters: { query?: never; header?: { @@ -14156,19 +44930,15 @@ export interface operations { }; }; }; - closeout_package_projects__pid__closeout_package_zip_get: { + listing_public_projects__pid__listings__lid__public_get: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path: { pid: string; + lid: string; }; - cookie?: { - aec_token?: string | null; - }; + cookie?: never; }; requestBody?: never; responses: { @@ -14192,24 +44962,16 @@ export interface operations { }; }; }; - run_clash_projects__pid__clash_post: { + listing_reso_projects__pid__listings__lid__reso_get: { parameters: { - query?: { - a?: string | null; - b?: string | null; - min_volume?: number; - tolerance?: number; - narrow?: boolean; - max_narrow?: number; - create_topics?: boolean; - limit?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + lid: string; }; cookie?: { aec_token?: string | null; @@ -14237,12 +44999,10 @@ export interface operations { }; }; }; - run_clash_federated_projects__pid__clash_federated_post: { + listing_share_projects__pid__listings__lid__share_post: { parameters: { query?: { - min_volume?: number; - create_topics?: boolean; - limit?: number; + ttl?: number; }; header?: { "x-user"?: string | null; @@ -14250,16 +45010,13 @@ export interface operations { }; path: { pid: string; + lid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_run_clash_federated_projects__pid__clash_federated_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -14281,7 +45038,7 @@ export interface operations { }; }; }; - model_georeferencing_projects__pid__models_georeferencing_get: { + listing_syndicate_projects__pid__listings__lid__syndicate_post: { parameters: { query?: never; header?: { @@ -14290,6 +45047,7 @@ export interface operations { }; path: { pid: string; + lid: string; }; cookie?: { aec_token?: string | null; @@ -14317,11 +45075,9 @@ export interface operations { }; }; }; - scan_deviation_projects__pid__scan_deviation_post: { + loads_defaults_projects__pid__loads_defaults_get: { parameters: { - query?: { - tolerance?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14333,11 +45089,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_scan_deviation_projects__pid__scan_deviation_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -14359,7 +45111,7 @@ export interface operations { }; }; }; - ai_readiness_scorecard_projects__pid__ai_readiness_get: { + loads_takedown_projects__pid__loads_takedown_post: { parameters: { query?: never; header?: { @@ -14373,43 +45125,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - model_qa_report_projects__pid__models_qa_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -14431,9 +45153,13 @@ export interface operations { }; }; }; - model_alignment_projects__pid__models_alignment_get: { + loan_draws_projects__pid__loan_draws_get: { parameters: { - query?: never; + query?: { + ltc?: number; + rate?: number; + construction_months?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14467,9 +45193,13 @@ export interface operations { }; }; }; - discipline_quantities_projects__pid__quantities_disciplines_get: { + loan_draw_request_pdf_projects__pid__loan_draws_request_pdf_get: { parameters: { - query?: never; + query?: { + app_no?: number; + ltc?: number; + rate?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14503,16 +45233,49 @@ export interface operations { }; }; }; - energy_projects__pid__energy_get: { + post_loan_covenants_projects__pid__loan_covenants_post: { parameters: { - query?: { - u_wall?: number | null; - u_window?: number | null; - ach?: number | null; - hdd?: number | null; - cdd?: number | null; - delta_t?: number | null; + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Body_post_loan_covenants_projects__pid__loan_covenants_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + lod_summary_projects__pid__lod_get: { + parameters: { + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14546,7 +45309,7 @@ export interface operations { }; }; }; - mep_projects__pid__mep_get: { + lod_assessment_projects__pid__lod_assessment_get: { parameters: { query?: never; header?: { @@ -14582,10 +45345,10 @@ export interface operations { }; }; }; - get_project_ids_projects__pid__ids_get: { + lod_handover_readiness_projects__pid__lod_handover_readiness_get: { parameters: { query?: { - download?: boolean; + limit?: number; }; header?: { "x-user"?: string | null; @@ -14620,7 +45383,7 @@ export interface operations { }; }; }; - put_project_ids_projects__pid__ids_put: { + lod_matrix_projects__pid__lod_matrix_get: { parameters: { query?: never; header?: { @@ -14634,11 +45397,43 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_put_project_ids_projects__pid__ids_put"]; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; }; }; + }; + asbuilt_summary_projects__pid__lod500_get: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -14660,7 +45455,7 @@ export interface operations { }; }; }; - delete_project_ids_projects__pid__ids_delete: { + get_logistics_projects__pid__logistics_get: { parameters: { query?: never; header?: { @@ -14696,12 +45491,9 @@ export interface operations { }; }; }; - run_validate_projects__pid__validate_post: { + put_logistics_projects__pid__logistics_put: { parameters: { - query?: { - format?: string; - ids?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14713,9 +45505,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { + requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_run_validate_projects__pid__validate_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -14739,9 +45533,11 @@ export interface operations { }; }; }; - get_drawing_set_projects__pid__drawing_set_get: { + logistics_clash_projects__pid__logistics_clash_get: { parameters: { - query?: never; + query?: { + samples?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -14775,11 +45571,10 @@ export interface operations { }; }; }; - drawing_set_transmittal_projects__pid__drawing_set_transmittal_pdf_get: { + logistics_state_projects__pid__logistics_state_get: { parameters: { query?: { - to?: string; - note?: string; + date?: string | null; }; header?: { "x-user"?: string | null; @@ -14814,7 +45609,7 @@ export interface operations { }; }; }; - issuance_purposes_projects__pid__drawing_set_issuance_purposes_get: { + macros_list_projects__pid__macros_get: { parameters: { query?: never; header?: { @@ -14850,7 +45645,7 @@ export interface operations { }; }; }; - drawing_issuances_projects__pid__drawing_set_issuances_get: { + macros_put_projects__pid__macros_put: { parameters: { query?: never; header?: { @@ -14864,7 +45659,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_macros_put_projects__pid__macros_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -14886,7 +45685,7 @@ export interface operations { }; }; }; - issue_drawing_set_projects__pid__drawing_set_issue_post: { + macros_expand_projects__pid__macros__macro_id__expand_post: { parameters: { query?: never; header?: { @@ -14895,6 +45694,7 @@ export interface operations { }; path: { pid: string; + macro_id: string; }; cookie?: { aec_token?: string | null; @@ -14902,14 +45702,12 @@ export interface operations { }; requestBody?: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["Body_macros_expand_projects__pid__macros__macro_id__expand_post"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -14928,7 +45726,7 @@ export interface operations { }; }; }; - drawing_issuance_matrix_projects__pid__drawing_set_issuance_matrix_get: { + macros_run_projects__pid__macros__macro_id__run_post: { parameters: { query?: never; header?: { @@ -14937,12 +45735,17 @@ export interface operations { }; path: { pid: string; + macro_id: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_macros_run_projects__pid__macros__macro_id__run_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -14964,7 +45767,7 @@ export interface operations { }; }; }; - issuance_transmittal_projects__pid__drawing_set_issuances__iid__transmittal_pdf_get: { + margin_by_costcode_projects__pid__margin_by_costcode_get: { parameters: { query?: never; header?: { @@ -14973,7 +45776,6 @@ export interface operations { }; path: { pid: string; - iid: string; }; cookie?: { aec_token?: string | null; @@ -15001,10 +45803,13 @@ export interface operations { }; }; }; - issuance_sealed_projects__pid__drawing_set_issuances__iid__sealed_pdf_get: { + market_context_projects__pid__market_context_get: { parameters: { query?: { - name?: string; + region?: string | null; + sector?: string | null; + start_year?: number | null; + duration_months?: number | null; }; header?: { "x-user"?: string | null; @@ -15012,7 +45817,6 @@ export interface operations { }; path: { pid: string; - iid: string; }; cookie?: { aec_token?: string | null; @@ -15040,31 +45844,31 @@ export interface operations { }; }; }; - revise_drawing_projects__pid__drawings__drawing_id__revise_post: { + market_escalate_projects__pid__market_escalate_get: { parameters: { - query?: never; + query: { + amount: number; + region?: string | null; + start_year?: number | null; + duration_months?: number | null; + to_year?: number | null; + rate_pct?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - drawing_id: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -15083,7 +45887,7 @@ export interface operations { }; }; }; - drawing_revisions_projects__pid__drawing_set_revisions_get: { + market_exists_projects__pid__market_exists_get: { parameters: { query?: never; header?: { @@ -15119,12 +45923,11 @@ export interface operations { }; }; }; - drawing_set_plan_projects__pid__drawing_set_plan_get: { + master_builder_brief_projects__pid__master_builder_brief_get: { parameters: { query?: { - disciplines?: string; - all?: boolean; - max_levels?: number; + workspace?: string | null; + persona?: string | null; }; header?: { "x-user"?: string | null; @@ -15159,7 +45962,7 @@ export interface operations { }; }; }; - generate_drawing_set_projects__pid__drawing_set_generate_post: { + master_builder_brief_md_projects__pid__master_builder_brief_md_get: { parameters: { query?: never; header?: { @@ -15173,16 +45976,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -15201,23 +45998,21 @@ export interface operations { }; }; }; - pdf_info_pdf_info_post: { + apply_material_palette_projects__pid__materials_apply_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_info_pdf_info_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15239,23 +46034,21 @@ export interface operations { }; }; }; - pdf_merge_pdf_merge_post: { + get_material_palette_projects__pid__materials_palette_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_merge_pdf_merge_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15277,21 +46070,25 @@ export interface operations { }; }; }; - pdf_split_pdf_split_post: { + put_material_palette_projects__pid__materials_palette_put: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_pdf_split_pdf_split_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -15315,23 +46112,21 @@ export interface operations { }; }; }; - pdf_extract_pdf_extract_post: { + my_membership_projects__pid__me_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_extract_pdf_extract_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15353,23 +46148,21 @@ export interface operations { }; }; }; - pdf_rotate_pdf_rotate_post: { + list_members_projects__pid__members_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_rotate_pdf_rotate_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15391,22 +46184,28 @@ export interface operations { }; }; }; - stamps_library_stamps_library_get: { + add_member_projects__pid__members_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["MemberIn"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -15425,23 +46224,22 @@ export interface operations { }; }; }; - pdf_stamp_pdf_stamp_post: { + remove_member_projects__pid__members__member__delete: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + member: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_stamp_pdf_stamp_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15463,23 +46261,21 @@ export interface operations { }; }; }; - pdf_seal_pdf_seal_post: { + mep_summary_projects__pid__mep_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_pdf_seal_pdf_seal_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -15501,7 +46297,7 @@ export interface operations { }; }; }; - list_storeys_projects__pid__drawings_storeys_get: { + mep_connectivity_projects__pid__mep_connectivity_get: { parameters: { query?: never; header?: { @@ -15537,7 +46333,7 @@ export interface operations { }; }; }; - model_grid_projects__pid__model_grid_get: { + mep_fittings_projects__pid__mep_fittings_get: { parameters: { query?: never; header?: { @@ -15573,15 +46369,9 @@ export interface operations { }; }; }; - plan_projects__pid__drawings_plan_svg_get: { + mep_graph_projects__pid__mep_graph_get: { parameters: { - query?: { - elevation?: number; - cut_height?: number; - title?: string; - rooms?: boolean; - callouts?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -15615,13 +46405,9 @@ export interface operations { }; }; }; - section_projects__pid__drawings_section_svg_get: { + mep_model_extract_projects__pid__mep_model_extract_get: { parameters: { - query?: { - axis?: string; - offset?: number; - title?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -15655,10 +46441,12 @@ export interface operations { }; }; }; - elevation_projects__pid__drawings_elevation_svg_get: { + mep_pressure_loss_projects__pid__mep_pressure_loss_get: { parameters: { query?: { - direction?: string; + duct_friction_max?: number; + pipe_friction_max?: number; + hazen_c?: number; }; header?: { "x-user"?: string | null; @@ -15693,7 +46481,7 @@ export interface operations { }; }; }; - export_gltf_projects__pid__model_export_gltf_get: { + mep_schedule_projects__pid__mep_schedule_get: { parameters: { query?: never; header?: { @@ -15729,9 +46517,18 @@ export interface operations { }; }; }; - model_step_summary_projects__pid__model_step_summary_get: { + mep_size_projects__pid__mep_size_get: { parameters: { - query?: never; + query?: { + kind?: string; + flow?: number; + velocity?: number; + load?: number; + size?: number; + hanger_kind?: string; + gfa_sf?: number | null; + sf_per_ton?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -15765,13 +46562,11 @@ export interface operations { }; }; }; - sheet_svg_projects__pid__drawings_sheet_svg_get: { + mep_sizing_projects__pid__mep_sizing_get: { parameters: { query?: { - sheet?: string; - page?: string; - purpose?: string; - rev?: string; + duct_max_fpm?: number; + pipe_max_fps?: number; }; header?: { "x-user"?: string | null; @@ -15806,13 +46601,10 @@ export interface operations { }; }; }; - sheet_pdf_projects__pid__drawings_sheet_pdf_get: { + sprinkler_coverage_projects__pid__mep_sprinkler_coverage_get: { parameters: { query?: { - sheet?: string; - page?: string; - purpose?: string; - rev?: string; + hazard?: string; }; header?: { "x-user"?: string | null; @@ -15847,9 +46639,12 @@ export interface operations { }; }; }; - list_types_projects__pid__types_get: { + mep_thermal_loads_projects__pid__mep_thermal_loads_get: { parameters: { - query?: never; + query?: { + envelope_btuh_sf?: number; + block_sf_per_ton?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -15883,49 +46678,13 @@ export interface operations { }; }; }; - family_catalog_families_catalog_get: { + mep_tray_fill_projects__pid__mep_tray_fill_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - import_families_projects__pid__families_import_post: { - parameters: { - query?: { - publish?: boolean; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; path: { pid: string; }; @@ -15933,44 +46692,6 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_families_projects__pid__families_import_post"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - family_library_families_library_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path?: never; - cookie?: { - aec_token?: string | null; - }; - }; requestBody?: never; responses: { /** @description Successful Response */ @@ -15993,7 +46714,7 @@ export interface operations { }; }; }; - place_family_projects__pid__families_place_post: { + model_frag_projects__pid__model_frag_get: { parameters: { query?: never; header?: { @@ -16007,11 +46728,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_place_family_projects__pid__families_place_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16033,7 +46750,7 @@ export interface operations { }; }; }; - edit_projects__pid__edit_post: { + sign_model_frag_projects__pid__model_frag_signed_url_get: { parameters: { query?: never; header?: { @@ -16047,11 +46764,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_edit_projects__pid__edit_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16073,7 +46786,7 @@ export interface operations { }; }; }; - edit_preview_projects__pid__edit_preview_post: { + model_adjacency_projects__pid__model_adjacency_post: { parameters: { query?: never; header?: { @@ -16087,9 +46800,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["Body_edit_preview_projects__pid__edit_preview_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -16113,7 +46828,7 @@ export interface operations { }; }; }; - publish_projects__pid__publish_post: { + model_assembly_thermal_projects__pid__model_assembly_thermal_get: { parameters: { query?: never; header?: { @@ -16127,14 +46842,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": boolean; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 202: { + 200: { headers: { [name: string]: unknown; }; @@ -16153,11 +46864,9 @@ export interface operations { }; }; }; - upload_source_ifc_projects__pid__source_ifc_post: { + model_assets_projects__pid__model_assets_get: { parameters: { - query?: { - publish?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -16169,11 +46878,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_upload_source_ifc_projects__pid__source_ifc_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16195,7 +46900,7 @@ export interface operations { }; }; }; - list_project_models_projects__pid__models_get: { + model_assets_seed_projects__pid__model_assets_seed_post: { parameters: { query?: never; header?: { @@ -16231,7 +46936,7 @@ export interface operations { }; }; }; - add_project_model_projects__pid__models_post: { + create_blank_model_projects__pid__model_blank_post: { parameters: { query?: never; header?: { @@ -16247,12 +46952,12 @@ export interface operations { }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_add_project_model_projects__pid__models_post"]; + "application/json": components["schemas"]["BlankModelIn"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -16271,7 +46976,7 @@ export interface operations { }; }; }; - raise_plan_to_bim_projects__pid__raise_plan_post: { + model_capabilities_projects__pid__model_capabilities_get: { parameters: { query?: never; header?: { @@ -16285,11 +46990,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_raise_plan_to_bim_projects__pid__raise_plan_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16311,16 +47012,17 @@ export interface operations { }; }; }; - delete_project_model_projects__pid__models__mid__delete: { + model_columnar_aggregate_projects__pid__model_columnar_aggregate_get: { parameters: { - query?: never; + query?: { + group_by?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - mid: string; }; cookie?: { aec_token?: string | null; @@ -16348,14 +47050,16 @@ export interface operations { }; }; }; - rvt_bridge_status_bridge_rvt_status_get: { + model_columnar_stats_projects__pid__model_columnar_stats_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -16382,14 +47086,18 @@ export interface operations { }; }; }; - speckle_status_interop_speckle_status_get: { + model_connections_projects__pid__model_connections_get: { parameters: { - query?: never; + query?: { + include_assemblies?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -16416,7 +47124,7 @@ export interface operations { }; }; }; - speckle_send_projects__pid__interop_speckle_send_post: { + model_constraints_projects__pid__model_constraints_get: { parameters: { query?: never; header?: { @@ -16433,7 +47141,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 202: { + 200: { headers: { [name: string]: unknown; }; @@ -16452,12 +47160,9 @@ export interface operations { }; }; }; - import_rvt_projects__pid__import_rvt_post: { + model_design_metrics_projects__pid__model_design_metrics_get: { parameters: { - query?: { - confirm_cost?: boolean; - publish?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -16469,14 +47174,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_rvt_projects__pid__import_rvt_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 202: { + 200: { headers: { [name: string]: unknown; }; @@ -16495,7 +47196,7 @@ export interface operations { }; }; }; - publish_status_projects__pid__publish_status_get: { + element_effective_props_projects__pid__model_element__guid__effective_props_get: { parameters: { query?: never; header?: { @@ -16504,6 +47205,7 @@ export interface operations { }; path: { pid: string; + guid: string; }; cookie?: { aec_token?: string | null; @@ -16531,29 +47233,12 @@ export interface operations { }; }; }; - list_modules_modules_get: { + ensure_model_projects__pid__model_ensure_post: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + query?: { + storeys?: number; + storey_height?: number; }; - }; - }; - sync_procore_projects__pid__sync_procore_post: { - parameters: { - query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -16565,11 +47250,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_sync_procore_projects__pid__sync_procore_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16591,7 +47272,7 @@ export interface operations { }; }; }; - push_procore_projects__pid__sync_procore_push_post: { + model_equipment_projects__pid__model_equipment_get: { parameters: { query?: never; header?: { @@ -16605,11 +47286,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_push_procore_projects__pid__sync_procore_push_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16631,7 +47308,7 @@ export interface operations { }; }; }; - list_schedules_projects__pid__sync_schedules_get: { + model_equipment_budget_lines_projects__pid__model_equipment_budget_lines_get: { parameters: { query?: never; header?: { @@ -16667,7 +47344,7 @@ export interface operations { }; }; }; - create_schedule_projects__pid__sync_schedules_post: { + model_equipment_spec_check_projects__pid__model_equipment_spec_check_post: { parameters: { query?: never; header?: { @@ -16681,14 +47358,14 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["ScheduleIn"]; + "application/json": components["schemas"]["Body_model_equipment_spec_check_projects__pid__model_equipment_spec_check_post"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -16707,7 +47384,7 @@ export interface operations { }; }; }; - update_schedule_projects__pid__sync_schedules__sid__put: { + model_equipment_starter_projects__pid__model_equipment_starter_requirements_get: { parameters: { query?: never; header?: { @@ -16716,17 +47393,12 @@ export interface operations { }; path: { pid: string; - sid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_update_schedule_projects__pid__sync_schedules__sid__put"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16748,7 +47420,7 @@ export interface operations { }; }; }; - delete_schedule_projects__pid__sync_schedules__sid__delete: { + model_equipment_to_submittals_projects__pid__model_equipment_to_submittals_post: { parameters: { query?: never; header?: { @@ -16757,7 +47429,6 @@ export interface operations { }; path: { pid: string; - sid: string; }; cookie?: { aec_token?: string | null; @@ -16785,7 +47456,7 @@ export interface operations { }; }; }; - run_schedule_now_projects__pid__sync_schedules__sid__run_now_post: { + model_export_csv_projects__pid__model_export_csv_get: { parameters: { query?: never; header?: { @@ -16794,7 +47465,6 @@ export interface operations { }; path: { pid: string; - sid: string; }; cookie?: { aec_token?: string | null; @@ -16822,7 +47492,7 @@ export interface operations { }; }; }; - draft_rfi_projects__pid__ai_draft_rfi_post: { + export_glb_projects__pid__model_export_glb_get: { parameters: { query?: never; header?: { @@ -16836,11 +47506,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_draft_rfi_projects__pid__ai_draft_rfi_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16862,7 +47528,7 @@ export interface operations { }; }; }; - triage_rfi_projects__pid__ai_triage_rfi_post: { + export_gltf_projects__pid__model_export_gltf_get: { parameters: { query?: never; header?: { @@ -16876,11 +47542,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_triage_rfi_projects__pid__ai_triage_rfi_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -16902,7 +47564,7 @@ export interface operations { }; }; }; - my_work_projects__pid__my_work_get: { + export_ifc_projects__pid__model_export_ifc_get: { parameters: { query?: never; header?: { @@ -16938,9 +47600,11 @@ export interface operations { }; }; }; - notifications_projects__pid__notifications_get: { + model_export_ifcx_projects__pid__model_export_ifcx_get: { parameters: { - query?: never; + query?: { + flavor?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -16974,11 +47638,9 @@ export interface operations { }; }; }; - due_feed_projects__pid__due_feed_get: { + model_export_jsonld_projects__pid__model_export_jsonld_get: { parameters: { - query?: { - days?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -17012,7 +47674,7 @@ export interface operations { }; }; }; - digest_preview_projects__pid__notifications_digest_preview_get: { + model_export_parquet_projects__pid__model_export_parquet_get: { parameters: { query?: never; header?: { @@ -17048,7 +47710,7 @@ export interface operations { }; }; }; - send_digest_projects__pid__notifications_digest_post: { + model_export_params_parquet_projects__pid__model_export_params_parquet_get: { parameters: { query?: never; header?: { @@ -17084,9 +47746,11 @@ export interface operations { }; }; }; - notifications_stream_projects__pid__notifications_stream_get: { + model_fill_matrix_projects__pid__model_fill_matrix_get: { parameters: { - query?: never; + query?: { + min_count?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -17120,7 +47784,7 @@ export interface operations { }; }; }; - list_views_projects__pid__modules__key__views_get: { + model_grid_projects__pid__model_grid_get: { parameters: { query?: never; header?: { @@ -17129,7 +47793,6 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -17157,7 +47820,7 @@ export interface operations { }; }; }; - save_view_projects__pid__modules__key__views_post: { + record_load_timing_projects__pid__model_load_timing_post: { parameters: { query?: never; header?: { @@ -17166,7 +47829,6 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -17174,12 +47836,14 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_save_view_projects__pid__modules__key__views_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -17198,9 +47862,11 @@ export interface operations { }; }; }; - view_alerts_projects__pid__views_alerts_get: { + load_timings_projects__pid__model_load_timings_get: { parameters: { - query?: never; + query?: { + days?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -17234,17 +47900,17 @@ export interface operations { }; }; }; - mark_view_seen_projects__pid__modules__key__views__vid__seen_post: { + model_lod_census_projects__pid__model_lod_census_get: { parameters: { - query?: never; + query?: { + max_elements?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - vid: string; }; cookie?: { aec_token?: string | null; @@ -17272,7 +47938,7 @@ export interface operations { }; }; }; - delete_view_projects__pid__modules__key__views__vid__delete: { + model_lod_proxy_projects__pid__model_lod_proxy_post: { parameters: { query?: never; header?: { @@ -17281,14 +47947,16 @@ export interface operations { }; path: { pid: string; - key: string; - vid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_model_lod_proxy_projects__pid__model_lod_proxy_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -17310,7 +47978,7 @@ export interface operations { }; }; }; - list_enum_options_projects__pid__enum_options_get: { + model_maintenance_scan_projects__pid__model_maintenance_get: { parameters: { query?: never; header?: { @@ -17346,58 +48014,13 @@ export interface operations { }; }; }; - add_enum_option_projects__pid__modules__key__enum__field__post: { + option_list_projects__pid__model_options_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - key: string; - field: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_add_enum_option_projects__pid__modules__key__enum__field__post"]; - }; - }; - responses: { - /** @description Successful Response */ - 201: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - search_projects__pid__search_get: { - parameters: { - query: { - q: string; - limit?: number; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; path: { pid: string; }; @@ -17427,7 +48050,7 @@ export interface operations { }; }; }; - bulk_action_projects__pid__modules__key__bulk_post: { + option_snapshot_projects__pid__model_options_post: { parameters: { query?: never; header?: { @@ -17436,7 +48059,6 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -17444,7 +48066,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_bulk_action_projects__pid__modules__key__bulk_post"]; + "application/json": components["schemas"]["Body_option_snapshot_projects__pid__model_options_post"]; }; }; responses: { @@ -17468,21 +48090,16 @@ export interface operations { }; }; }; - list_records_projects__pid__modules__key__get: { + option_delete_projects__pid__model_options__slug__delete: { parameters: { - query?: { - state?: string | null; - q?: string | null; - limit?: number; - offset?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; + slug: string; }; cookie?: { aec_token?: string | null; @@ -17510,7 +48127,7 @@ export interface operations { }; }; }; - create_record_projects__pid__modules__key__post: { + option_activate_projects__pid__model_options__slug__activate_post: { parameters: { query?: never; header?: { @@ -17519,22 +48136,20 @@ export interface operations { }; path: { pid: string; - key: string; + slug: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["Body_option_activate_projects__pid__model_options__slug__activate_post"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -17553,7 +48168,7 @@ export interface operations { }; }; }; - export_csv_projects__pid__modules__key__export_csv_get: { + option_diff_projects__pid__model_options__slug__diff_get: { parameters: { query?: never; header?: { @@ -17562,7 +48177,7 @@ export interface operations { }; path: { pid: string; - key: string; + slug: string; }; cookie?: { aec_token?: string | null; @@ -17590,16 +48205,20 @@ export interface operations { }; }; }; - import_template_projects__pid__modules__key__import_template_csv_get: { + model_query_run_projects__pid__model_query_get: { parameters: { - query?: never; + query?: { + view?: string | null; + group_by?: string; + agg?: string; + quantity?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -17627,7 +48246,7 @@ export interface operations { }; }; }; - import_preview_projects__pid__modules__key__import_preview_post: { + model_query_views_projects__pid__model_query_views_get: { parameters: { query?: never; header?: { @@ -17636,17 +48255,12 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_preview_projects__pid__modules__key__import_preview_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -17668,7 +48282,7 @@ export interface operations { }; }; }; - import_records_projects__pid__modules__key__import_post: { + model_roundtrip_projects__pid__model_roundtrip_get: { parameters: { query?: never; header?: { @@ -17677,17 +48291,12 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_records_projects__pid__modules__key__import_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -17709,16 +48318,17 @@ export interface operations { }; }; }; - module_log_projects__pid__modules__key__log_pdf_get: { + roundtrip_export_projects__pid__model_roundtrip_csv_get: { parameters: { - query?: never; + query: { + props: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -17746,7 +48356,7 @@ export interface operations { }; }; }; - module_board_projects__pid__modules__key__board_get: { + roundtrip_diff_projects__pid__model_roundtrip_diff_post: { parameters: { query?: never; header?: { @@ -17755,51 +48365,16 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - get_record_projects__pid__modules__key___rid__get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - key: string; - rid: string; - }; - cookie?: { - aec_token?: string | null; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_roundtrip_diff_projects__pid__model_roundtrip_diff_post"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -17821,17 +48396,18 @@ export interface operations { }; }; }; - delete_record_projects__pid__modules__key___rid__delete: { + model_select_projects__pid__model_select_get: { parameters: { - query?: never; + query: { + q: string; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; @@ -17859,31 +48435,21 @@ export interface operations { }; }; }; - update_record_projects__pid__modules__key___rid__patch: { + model_setup_projects__pid__model_setup_get: { parameters: { - query?: { - expected_modified_at?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -17905,7 +48471,7 @@ export interface operations { }; }; }; - related_records_projects__pid__modules__key___rid__related_get: { + model_space_demand_projects__pid__model_space_demand_post: { parameters: { query?: never; header?: { @@ -17914,14 +48480,16 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_model_space_demand_projects__pid__model_space_demand_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -17943,17 +48511,17 @@ export interface operations { }; }; }; - revise_record_projects__pid__modules__key___rid__revise_post: { + model_space_utilization_projects__pid__model_space_utilization_get: { parameters: { - query?: never; + query?: { + area_per_person?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; @@ -17962,7 +48530,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -17981,7 +48549,7 @@ export interface operations { }; }; }; - transition_projects__pid__modules__key___rid__transition_post: { + model_split_plan_projects__pid__model_split_plan_get: { parameters: { query?: never; header?: { @@ -17990,18 +48558,12 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_transition_projects__pid__modules__key___rid__transition_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -18023,7 +48585,7 @@ export interface operations { }; }; }; - link_record_projects__pid__modules__key___rid__link_post: { + model_step_summary_projects__pid__model_step_summary_get: { parameters: { query?: never; header?: { @@ -18032,20 +48594,12 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -18067,7 +48621,7 @@ export interface operations { }; }; }; - add_comment_projects__pid__modules__key___rid__comments_post: { + model_stream_projects__pid__model_stream_get: { parameters: { query?: never; header?: { @@ -18076,21 +48630,15 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_add_comment_projects__pid__modules__key___rid__comments_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -18109,27 +48657,23 @@ export interface operations { }; }; }; - assign_record_projects__pid__modules__key___rid__assign_post: { + model_wall_joins_projects__pid__model_wall_joins_get: { parameters: { - query?: never; + query?: { + tol?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_assign_record_projects__pid__modules__key___rid__assign_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -18151,7 +48695,7 @@ export interface operations { }; }; }; - tag_elements_projects__pid__modules__key___rid__elements_post: { + list_project_models_projects__pid__models_get: { parameters: { query?: never; header?: { @@ -18160,18 +48704,12 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_tag_elements_projects__pid__modules__key___rid__elements_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -18193,7 +48731,7 @@ export interface operations { }; }; }; - upload_attachment_projects__pid__modules__key___rid__attachments_post: { + add_project_model_projects__pid__models_post: { parameters: { query?: never; header?: { @@ -18202,8 +48740,6 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; @@ -18211,7 +48747,7 @@ export interface operations { }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_upload_attachment_projects__pid__modules__key___rid__attachments_post"]; + "multipart/form-data": components["schemas"]["Body_add_project_model_projects__pid__models_post"]; }; }; responses: { @@ -18235,7 +48771,7 @@ export interface operations { }; }; }; - upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post: { + model_alignment_projects__pid__models_alignment_get: { parameters: { query?: never; header?: { @@ -18244,21 +48780,15 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -18277,18 +48807,15 @@ export interface operations { }; }; }; - export_module_bcf_projects__pid__modules__key__bcf_export_get: { + model_export_qa_projects__pid__models_export_qa_get: { parameters: { - query?: { - version?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; @@ -18316,7 +48843,7 @@ export interface operations { }; }; }; - import_module_bcf_projects__pid__modules__key__bcf_import_post: { + model_footprint_geojson_projects__pid__models_footprint_geojson_get: { parameters: { query?: never; header?: { @@ -18325,20 +48852,15 @@ export interface operations { }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_module_bcf_projects__pid__modules__key__bcf_import_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -18357,7 +48879,7 @@ export interface operations { }; }; }; - download_attachment_module_attachments__att_id__download_get: { + add_project_model_from_upload_projects__pid__models_from_upload_post: { parameters: { query?: never; header?: { @@ -18365,16 +48887,22 @@ export interface operations { authorization?: string | null; }; path: { - att_id: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -18393,7 +48921,7 @@ export interface operations { }; }; }; - record_pdf_projects__pid__modules__key___rid__pdf_get: { + model_georeferencing_projects__pid__models_georeferencing_get: { parameters: { query?: never; header?: { @@ -18402,8 +48930,6 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; @@ -18431,7 +48957,7 @@ export interface operations { }; }; }; - module_pins_projects__pid__module_pins_get: { + model_health_scorecard_projects__pid__models_health_get: { parameters: { query?: never; header?: { @@ -18467,7 +48993,7 @@ export interface operations { }; }; }; - g703_projects__pid__cost_g703_get: { + model_norm_valid_projects__pid__models_norm_valid_get: { parameters: { query?: never; header?: { @@ -18503,13 +49029,9 @@ export interface operations { }; }; }; - g702_projects__pid__cost_g702_get: { + model_qa_report_projects__pid__models_qa_get: { parameters: { - query?: { - app_no?: number; - period?: string | null; - release_retainage?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -18543,7 +49065,7 @@ export interface operations { }; }; }; - px_summary_projects__pid__px_summary_get: { + model_schema_diag_projects__pid__models_schema_diag_get: { parameters: { query?: never; header?: { @@ -18579,13 +49101,9 @@ export interface operations { }; }; }; - g702_pdf_projects__pid__cost_g702_pdf_get: { + model_warnings_feed_projects__pid__models_warnings_get: { parameters: { - query?: { - app_no?: number; - period?: string | null; - release_retainage?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -18619,7 +49137,7 @@ export interface operations { }; }; }; - payapp_invoice_projects__pid__cost_pay_app_invoice_post: { + delete_project_model_projects__pid__models__mid__delete: { parameters: { query?: never; header?: { @@ -18628,19 +49146,16 @@ export interface operations { }; path: { pid: string; + mid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_payapp_invoice_projects__pid__cost_pay_app_invoice_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -18659,7 +49174,7 @@ export interface operations { }; }; }; - advance_period_projects__pid__cost_advance_period_post: { + model_alignment_fit_projects__pid__models__mid__alignment_fit_get: { parameters: { query?: never; header?: { @@ -18668,6 +49183,7 @@ export interface operations { }; path: { pid: string; + mid: string; }; cookie?: { aec_token?: string | null; @@ -18695,7 +49211,7 @@ export interface operations { }; }; }; - summary_projects__pid__cost_summary_get: { + module_pins_projects__pid__module_pins_get: { parameters: { query?: never; header?: { @@ -18731,9 +49247,12 @@ export interface operations { }; }; }; - wip_schedule_projects__pid__wip_get: { + backfill_references_projects__pid__modules_backfill_references_post: { parameters: { - query?: never; + query?: { + module?: string | null; + apply?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -18767,14 +49286,24 @@ export interface operations { }; }; }; - wip_portfolio_wip_portfolio_get: { + list_records_projects__pid__modules__key__get: { parameters: { - query?: never; + query?: { + state?: string | null; + q?: string | null; + limit?: number; + offset?: number; + sort?: string | null; + sort_dir?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + }; cookie?: { aec_token?: string | null; }; @@ -18801,7 +49330,7 @@ export interface operations { }; }; }; - contractor_statements_projects__pid__contractor_statements_get: { + create_record_projects__pid__modules__key__post: { parameters: { query?: never; header?: { @@ -18810,15 +49339,22 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -18837,14 +49373,23 @@ export interface operations { }; }; }; - contractor_statements_portfolio_contractor_statements_portfolio_get: { + aggregate_records_projects__pid__modules__key__aggregate_get: { parameters: { - query?: never; + query: { + group_by: string; + agg?: string; + agg_field?: string | null; + state?: string | null; + join?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + }; cookie?: { aec_token?: string | null; }; @@ -18871,7 +49416,7 @@ export interface operations { }; }; }; - cost_traceability_projects__pid__cost_traceability_get: { + apply_template_projects__pid__modules__key__apply_template__tid__post: { parameters: { query?: never; header?: { @@ -18880,6 +49425,8 @@ export interface operations { }; path: { pid: string; + key: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -18888,7 +49435,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -18907,16 +49454,18 @@ export interface operations { }; }; }; - element_costs_projects__pid__elements__guid__costs_get: { + export_module_bcf_projects__pid__modules__key__bcf_export_get: { parameters: { - query?: never; + query?: { + version?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - guid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -18944,7 +49493,7 @@ export interface operations { }; }; }; - subcontractor_billing_projects__pid__subcontractor_billing_get: { + import_module_bcf_projects__pid__modules__key__bcf_import_post: { parameters: { query?: never; header?: { @@ -18953,15 +49502,20 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_module_bcf_projects__pid__modules__key__bcf_import_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -18980,7 +49534,7 @@ export interface operations { }; }; }; - element_5d_projects__pid__elements__guid__5d_get: { + module_board_projects__pid__modules__key__board_get: { parameters: { query?: never; header?: { @@ -18989,7 +49543,7 @@ export interface operations { }; path: { pid: string; - guid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19017,23 +49571,26 @@ export interface operations { }; }; }; - elements_5d_map_projects__pid__5d_heatmap_get: { + bulk_action_projects__pid__modules__key__bulk_post: { parameters: { - query?: { - by?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_bulk_action_projects__pid__modules__key__bulk_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -19055,7 +49612,7 @@ export interface operations { }; }; }; - gmp_budget_projects__pid__budget_gmp_get: { + calc_records_projects__pid__modules__key__calc_post: { parameters: { query?: never; header?: { @@ -19064,12 +49621,19 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -19091,7 +49655,7 @@ export interface operations { }; }; }; - set_budget_baseline_projects__pid__budget_baseline_post: { + add_enum_option_projects__pid__modules__key__enum__field__post: { parameters: { query?: never; header?: { @@ -19100,12 +49664,18 @@ export interface operations { }; path: { pid: string; + key: string; + field: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_add_enum_option_projects__pid__modules__key__enum__field__post"]; + }; + }; responses: { /** @description Successful Response */ 201: { @@ -19127,7 +49697,7 @@ export interface operations { }; }; }; - clear_budget_baseline_projects__pid__budget_baseline_delete: { + export_csv_projects__pid__modules__key__export_csv_get: { parameters: { query?: never; header?: { @@ -19136,6 +49706,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19163,7 +49734,7 @@ export interface operations { }; }; }; - budget_variance_projects__pid__budget_variance_get: { + import_records_projects__pid__modules__key__import_post: { parameters: { query?: never; header?: { @@ -19172,12 +49743,17 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_records_projects__pid__modules__key__import_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -19199,7 +49775,7 @@ export interface operations { }; }; }; - budget_cashflow_projects__pid__budget_cashflow_get: { + import_template_projects__pid__modules__key__import_template_csv_get: { parameters: { query?: never; header?: { @@ -19208,6 +49784,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19235,26 +49812,29 @@ export interface operations { }; }; }; - sov_from_budget_projects__pid__cost_sov_from_budget_post: { + import_preview_projects__pid__modules__key__import_preview_post: { parameters: { - query?: { - replace?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_import_preview_projects__pid__modules__key__import_preview_post"]; + }; + }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -19273,7 +49853,7 @@ export interface operations { }; }; }; - estimate_from_model_projects__pid__estimate_from_model_get: { + module_log_projects__pid__modules__key__log_pdf_get: { parameters: { query?: never; header?: { @@ -19282,6 +49862,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19309,22 +49890,29 @@ export interface operations { }; }; }; - resource_catalog_estimate_resources_catalog_get: { + save_template_projects__pid__modules__key__save_template_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_save_template_projects__pid__modules__key__save_template_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -19343,7 +49931,7 @@ export interface operations { }; }; }; - estimate_resource_based_projects__pid__estimate_resource_based_get: { + list_views_projects__pid__modules__key__views_get: { parameters: { query?: never; header?: { @@ -19352,6 +49940,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19379,7 +49968,7 @@ export interface operations { }; }; }; - takeoff_dxf_projects__pid__takeoff_dxf_post: { + save_view_projects__pid__modules__key__views_post: { parameters: { query?: never; header?: { @@ -19388,6 +49977,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -19395,12 +49985,12 @@ export interface operations { }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_takeoff_dxf_projects__pid__takeoff_dxf_post"]; + "application/json": components["schemas"]["Body_save_view_projects__pid__modules__key__views_post"]; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -19419,14 +50009,18 @@ export interface operations { }; }; }; - list_classifications_classifications_get: { + delete_view_projects__pid__modules__key__views__vid__delete: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + vid: string; + }; cookie?: { aec_token?: string | null; }; @@ -19453,14 +50047,18 @@ export interface operations { }; }; }; - reference_disciplines_reference_disciplines_get: { + mark_view_seen_projects__pid__modules__key__views__vid__seen_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + vid: string; + }; cookie?: { aec_token?: string | null; }; @@ -19487,17 +50085,17 @@ export interface operations { }; }; }; - estimate_gaeb_projects__pid__estimate_gaeb_x83_get: { + get_record_projects__pid__modules__key___rid__get: { parameters: { - query?: { - system?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; @@ -19525,7 +50123,7 @@ export interface operations { }; }; }; - qto_by_floor_projects__pid__qto_by_floor_get: { + delete_record_projects__pid__modules__key___rid__delete: { parameters: { query?: never; header?: { @@ -19534,6 +50132,8 @@ export interface operations { }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; @@ -19561,15 +50161,19 @@ export interface operations { }; }; }; - price_tm_projects__pid__cost_tm_post: { + update_record_projects__pid__modules__key___rid__patch: { parameters: { - query?: never; + query?: { + expected_modified_at?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; @@ -19577,7 +50181,9 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_price_tm_projects__pid__cost_tm_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -19601,27 +50207,27 @@ export interface operations { }; }; }; - lien_waiver_projects__pid__cost_lien_waiver_get: { + assign_record_projects__pid__modules__key___rid__assign_post: { parameters: { - query?: { - kind?: string; - app_no?: number; - claimant?: string; - customer?: string; - through_date?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_assign_record_projects__pid__modules__key___rid__assign_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -19643,7 +50249,7 @@ export interface operations { }; }; }; - lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post: { + upload_attachment_projects__pid__modules__key___rid__attachments_post: { parameters: { query?: never; header?: { @@ -19652,14 +50258,16 @@ export interface operations { }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { + requestBody: { content: { - "application/json": components["schemas"]["Body_lien_waiver_from_payapp_projects__pid__cost_lien_waiver_post"]; + "multipart/form-data": components["schemas"]["Body_upload_attachment_projects__pid__modules__key___rid__attachments_post"]; }; }; responses: { @@ -19683,64 +50291,30 @@ export interface operations { }; }; }; - lien_waiver_pdf_projects__pid__cost_lien_waiver_pdf_get: { + upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post: { parameters: { - query?: { - kind?: string; - app_no?: number; - claimant?: string; - customer?: string; - through_date?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - esign_status_esign_status_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path?: never; - cookie?: { - aec_token?: string | null; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_attachments_bulk_projects__pid__modules__key___rid__attachments_bulk_post"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -19759,22 +50333,30 @@ export interface operations { }; }; }; - scope_library_list_scope_library_get: { + add_comment_projects__pid__modules__key___rid__comments_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + rid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_add_comment_projects__pid__modules__key___rid__comments_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -19793,13 +50375,9 @@ export interface operations { }; }; }; - contract_document_projects__pid__contracts__key___rid__document_pdf_get: { + promote_comment_projects__pid__modules__key___rid__comments__cid__promote_post: { parameters: { - query?: { - doc?: string; - clauses?: string | null; - attach?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -19808,15 +50386,20 @@ export interface operations { pid: string; key: string; rid: string; + cid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_promote_comment_projects__pid__modules__key___rid__comments__cid__promote_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -19835,7 +50418,7 @@ export interface operations { }; }; }; - sign_contract_projects__pid__contracts__key___rid__sign_post: { + record_distribution_projects__pid__modules__key___rid__distribution_get: { parameters: { query?: never; header?: { @@ -19851,13 +50434,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -19879,7 +50456,7 @@ export interface operations { }; }; }; - send_for_signature_projects__pid__contracts__key___rid__send_for_signature_post: { + tag_elements_projects__pid__modules__key___rid__elements_post: { parameters: { query?: never; header?: { @@ -19895,46 +50472,9 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - esign_webhook_esign_webhook_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: { + requestBody: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["Body_tag_elements_projects__pid__modules__key___rid__elements_post"]; }; }; responses: { @@ -19958,7 +50498,7 @@ export interface operations { }; }; }; - digital_sign_projects__pid__contracts__key___rid__digital_sign_post: { + link_record_projects__pid__modules__key___rid__link_post: { parameters: { query?: never; header?: { @@ -19974,7 +50514,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { + requestBody: { content: { "application/json": { [key: string]: unknown; @@ -20002,14 +50542,18 @@ export interface operations { }; }; }; - report_catalog_reports_get: { + record_pdf_projects__pid__modules__key___rid__pdf_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + rid: string; + }; cookie?: { aec_token?: string | null; }; @@ -20036,7 +50580,7 @@ export interface operations { }; }; }; - report_pdf_projects__pid__reports__report__pdf_get: { + related_records_projects__pid__modules__key___rid__related_get: { parameters: { query?: never; header?: { @@ -20045,7 +50589,8 @@ export interface operations { }; path: { pid: string; - report: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; @@ -20073,7 +50618,7 @@ export interface operations { }; }; }; - report_xlsx_projects__pid__reports__report__xlsx_get: { + revise_record_projects__pid__modules__key___rid__revise_post: { parameters: { query?: never; header?: { @@ -20082,7 +50627,8 @@ export interface operations { }; path: { pid: string; - report: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; @@ -20091,7 +50637,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -20110,7 +50656,7 @@ export interface operations { }; }; }; - cpm_projects__pid__schedule_cpm_get: { + transition_projects__pid__modules__key___rid__transition_post: { parameters: { query?: never; header?: { @@ -20119,12 +50665,18 @@ export interface operations { }; path: { pid: string; + key: string; + rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_transition_projects__pid__modules__key___rid__transition_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -20146,11 +50698,9 @@ export interface operations { }; }; }; - resource_loading_endpoint_projects__pid__schedule_resource_loading_get: { + my_work_projects__pid__my_work_get: { parameters: { - query?: { - cap?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20184,11 +50734,9 @@ export interface operations { }; }; }; - resource_leveling_endpoint_projects__pid__schedule_resource_leveling_get: { + naming_audit_projects__pid__naming_audit_get: { parameters: { - query: { - cap: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20222,7 +50770,7 @@ export interface operations { }; }; }; - productivity_summary_projects__pid__productivity_summary_get: { + naming_conventions_projects__pid__naming_conventions_get: { parameters: { query?: never; header?: { @@ -20258,9 +50806,12 @@ export interface operations { }; }; }; - cv_progress_status_projects__pid__cv_progress_status_get: { + naming_validate_projects__pid__naming_validate_get: { parameters: { - query?: never; + query: { + name: string; + kind?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20294,9 +50845,18 @@ export interface operations { }; }; }; - cv_progress_ingest_projects__pid__cv_progress_ingest_post: { + notice_register_projects__pid__notices_get: { parameters: { - query?: never; + query?: { + family?: string | null; + as_of?: string | null; + due_soon_days?: number; + /** @description e.g. 'Fri,Sat' — defaults to Sat,Sun */ + weekend?: string | null; + /** @description comma-separated YYYY-MM-DD public holidays */ + holidays?: string | null; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20308,13 +50868,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -20336,9 +50890,12 @@ export interface operations { }; }; }; - cv_progress_ingest_batch_projects__pid__cv_progress_ingest_batch_post: { + notice_clauses_projects__pid__notices_clauses_get: { parameters: { - query?: never; + query?: { + family?: string | null; + event_type?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20350,13 +50907,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -20378,9 +50929,12 @@ export interface operations { }; }; }; - schedule_alerts_projects__pid__schedule_alerts_get: { + notice_draft_projects__pid__notices_draft_post: { parameters: { - query?: never; + query?: { + from_party?: string; + to_party?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20392,7 +50946,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -20414,7 +50974,7 @@ export interface operations { }; }; }; - schedule_optimize_projects__pid__schedule_optimize_get: { + notifications_projects__pid__notifications_get: { parameters: { query?: never; header?: { @@ -20450,7 +51010,7 @@ export interface operations { }; }; }; - gantt_projects__pid__schedule_gantt_svg_get: { + send_digest_projects__pid__notifications_digest_post: { parameters: { query?: never; header?: { @@ -20486,7 +51046,7 @@ export interface operations { }; }; }; - lob_projects__pid__schedule_lob_svg_get: { + digest_preview_projects__pid__notifications_digest_preview_get: { parameters: { query?: never; header?: { @@ -20522,12 +51082,9 @@ export interface operations { }; }; }; - lookahead_projects__pid__schedule_lookahead_get: { + notifications_stream_projects__pid__notifications_stream_get: { parameters: { - query?: { - weeks?: number; - start?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20561,9 +51118,11 @@ export interface operations { }; }; }; - set_baseline_projects__pid__schedule_baseline_post: { + openbim_quality_scan_projects__pid__openbim_quality_get: { parameters: { - query?: never; + query?: { + use_case?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20578,7 +51137,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -20597,9 +51156,17 @@ export interface operations { }; }; }; - clear_baseline_projects__pid__schedule_baseline_delete: { + query_permits_projects__pid__opendata_permits_get: { parameters: { - query?: never; + query: { + city: string; + lat?: number | null; + lon?: number | null; + radius?: number | null; + address?: string | null; + q?: string | null; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20633,9 +51200,17 @@ export interface operations { }; }; }; - variance_projects__pid__schedule_variance_get: { + permits_geojson_projects__pid__opendata_permits_geojson_get: { parameters: { - query?: never; + query: { + city: string; + lat?: number | null; + lon?: number | null; + radius?: number | null; + address?: string | null; + q?: string | null; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20669,7 +51244,7 @@ export interface operations { }; }; }; - earned_value_projects__pid__schedule_earned_value_get: { + import_permits_projects__pid__opendata_permits_import_post: { parameters: { query?: never; header?: { @@ -20683,45 +51258,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - evm_snapshot_projects__pid__evm_get: { - parameters: { - query?: { - data_date?: string | null; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -20743,11 +51286,9 @@ export interface operations { }; }; }; - evm_earned_schedule_projects__pid__evm_earned_schedule_get: { + lien_exposure_projects__pid__payapp_lien_exposure_get: { parameters: { - query?: { - period?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20781,10 +51322,10 @@ export interface operations { }; }; }; - evm_scurve_projects__pid__evm_scurve_get: { + weekly_payroll_projects__pid__payroll_get: { parameters: { query?: { - period?: string; + week_ending?: string | null; }; header?: { "x-user"?: string | null; @@ -20819,9 +51360,11 @@ export interface operations { }; }; }; - evm_model_ev_projects__pid__evm_model_ev_get: { + wh347_pdf_projects__pid__payroll_wh347_pdf_get: { parameters: { - query?: never; + query?: { + week_ending?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20855,9 +51398,14 @@ export interface operations { }; }; }; - evm_trend_projects__pid__evm_trend_get: { + permit_readiness_projects__pid__permit_readiness_get: { parameters: { - query?: never; + query?: { + occupancy_group?: string; + construction_type?: string; + sprinklered?: boolean; + jurisdiction?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20891,7 +51439,7 @@ export interface operations { }; }; }; - evm_capture_projects__pid__evm_snapshot_post: { + permits_timeline_projects__pid__permits_timeline_post: { parameters: { query?: never; header?: { @@ -20907,7 +51455,9 @@ export interface operations { }; requestBody?: { content: { - "application/json": components["schemas"]["Body_evm_capture_projects__pid__evm_snapshot_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -20931,7 +51481,7 @@ export interface operations { }; }; }; - milestones_projects__pid__schedule_milestones_get: { + phasing_summary_projects__pid__phasing_get: { parameters: { query?: never; header?: { @@ -20967,9 +51517,11 @@ export interface operations { }; }; }; - itb_summary_projects__pid__bidding_itb_get: { + list_pins_projects__pid__pins_get: { parameters: { - query?: never; + query?: { + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -20989,7 +51541,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["TopicOut"][]; }; }; /** @description Validation Error */ @@ -21003,7 +51555,7 @@ export interface operations { }; }; }; - invite_bidders_projects__pid__bidding_packages__rid__invite_post: { + list_all_pins_projects__pid__pins_all_get: { parameters: { query?: never; header?: { @@ -21012,17 +51564,12 @@ export interface operations { }; path: { pid: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_invite_bidders_projects__pid__bidding_packages__rid__invite_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -21044,7 +51591,7 @@ export interface operations { }; }; }; - leveling_projects__pid__bids_leveling_get: { + precon_alignment_projects__pid__precon_alignment_get: { parameters: { query?: never; header?: { @@ -21080,7 +51627,7 @@ export interface operations { }; }; }; - leveling_detail_projects__pid__bids_leveling__package_rid__get: { + precon_assumptions_projects__pid__precon_assumptions_get: { parameters: { query?: never; header?: { @@ -21089,44 +51636,7 @@ export interface operations { }; path: { pid: string; - package_rid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - list_templates_templates_get: { - parameters: { - query?: { - module?: string | null; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; }; - path?: never; cookie?: { aec_token?: string | null; }; @@ -21153,26 +51663,24 @@ export interface operations { }; }; }; - create_template_templates_post: { + precon_decisions_projects__pid__precon_decisions_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["TemplateIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -21191,15 +51699,17 @@ export interface operations { }; }; }; - delete_template_templates__tid__delete: { + precon_estimate_continuity_projects__pid__precon_estimate_continuity_get: { parameters: { - query?: never; + query?: { + budget?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - tid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -21227,26 +51737,23 @@ export interface operations { }; }; }; - save_template_projects__pid__modules__key__save_template_post: { + precon_snapshot_projects__pid__precon_snapshot_post: { parameters: { - query?: never; + query?: { + milestone?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_save_template_projects__pid__modules__key__save_template_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 201: { @@ -21268,17 +51775,17 @@ export interface operations { }; }; }; - apply_template_projects__pid__modules__key__apply_template__tid__post: { + precon_ve_projects__pid__precon_ve_get: { parameters: { - query?: never; + query?: { + target?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - key: string; - tid: string; }; cookie?: { aec_token?: string | null; @@ -21287,7 +51794,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -21306,10 +51813,11 @@ export interface operations { }; }; }; - get_dashboard_projects__pid__dashboard_get: { + prefab_register_projects__pid__prefab_kits_get: { parameters: { query?: { - party?: string | null; + as_of?: string | null; + limit?: number; }; header?: { "x-user"?: string | null; @@ -21344,14 +51852,19 @@ export interface operations { }; }; }; - construction_portfolio_portfolio_construction_get: { + prefab_kit_detail_projects__pid__prefab_kits__rid__get: { parameters: { - query?: never; + query?: { + as_of?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + rid: string; + }; cookie?: { aec_token?: string | null; }; @@ -21378,14 +51891,17 @@ export interface operations { }; }; }; - executive_portfolio_portfolio_executive_get: { + prefab_freeze_projects__pid__prefab_kits__rid__freeze_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + rid: string; + }; cookie?: { aec_token?: string | null; }; @@ -21412,14 +51928,16 @@ export interface operations { }; }; }; - portfolio_prioritization_portfolio_prioritization_get: { + preflight_gate_projects__pid__preflight_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -21446,10 +51964,10 @@ export interface operations { }; }; }; - safety_metrics_projects__pid__safety_metrics_get: { + coi_expiry_projects__pid__prequal_coi_expiry_get: { parameters: { query?: { - hours?: number | null; + soon_days?: number; }; header?: { "x-user"?: string | null; @@ -21484,12 +52002,21 @@ export interface operations { }; }; }; - capabilities_capabilities_get: { + prequal_scores_projects__pid__prequal_scores_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + query?: { + project_size?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -21502,14 +52029,30 @@ export interface operations { "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; - license_state_license_get: { + presence_roster_projects__pid__presence_get: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -21522,9 +52065,18 @@ export interface operations { "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; - risk_summary_projects__pid__ai_risk_summary_get: { + heartbeat_projects__pid__presence_post: { parameters: { query?: never; header?: { @@ -21538,7 +52090,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_heartbeat_projects__pid__presence_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -21560,7 +52116,7 @@ export interface operations { }; }; }; - risk_digest_projects__pid__risk_digest_get: { + reconcile_projects__pid__pricing_reconcile_get: { parameters: { query?: never; header?: { @@ -21596,7 +52152,7 @@ export interface operations { }; }; }; - ai_ask_projects__pid__ai_ask_post: { + buyout_packages_projects__pid__procurement_buyout_packages_post: { parameters: { query?: never; header?: { @@ -21610,7 +52166,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { "application/json": { [key: string]: unknown; @@ -21638,7 +52194,7 @@ export interface operations { }; }; }; - ai_estimate_projects__pid__ai_estimate_post: { + buyout_schedule_projects__pid__procurement_buyout_schedule_post: { parameters: { query?: never; header?: { @@ -21652,7 +52208,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { "application/json": { [key: string]: unknown; @@ -21680,9 +52236,11 @@ export interface operations { }; }; }; - status_report_projects__pid__report_pdf_get: { + procurement_compliance_feed_projects__pid__procurement_compliance_feed_get: { parameters: { - query?: never; + query?: { + within_days?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -21716,16 +52274,63 @@ export interface operations { }; }; }; - solve_stateless_proforma_solve_post: { + procurement_gate_check_projects__pid__procurement_gate_get: { + parameters: { + query: { + vendor: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + level_projects__pid__procurement_level_post: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody: { content: { - "application/json": components["schemas"]["Assumptions"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -21749,16 +52354,25 @@ export interface operations { }; }; }; - financials_stateless_proforma_financials_post: { + level_quotes_projects__pid__procurement_level_quotes_post: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + query?: { + record?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody: { content: { - "application/json": components["schemas"]["Assumptions"]; + "application/json": components["schemas"]["Body_level_quotes_projects__pid__procurement_level_quotes_post"]; }; }; responses: { @@ -21782,7 +52396,7 @@ export interface operations { }; }; }; - project_financials_projects__pid__financials_get: { + material_request_suggest_projects__pid__procurement_material_request_suggest_post: { parameters: { query?: never; header?: { @@ -21796,7 +52410,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -21818,14 +52438,9 @@ export interface operations { }; }; }; - two_sided_budget_projects__pid__budget_two_sided_get: { + save_buyout_packages_projects__pid__procurement_packages_save_post: { parameters: { - query?: { - ltc?: number; - rate?: number; - construction_months?: number; - lp_pct?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -21837,7 +52452,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -21859,7 +52480,7 @@ export interface operations { }; }; }; - get_dev_budget_projects__pid__dev_budget_get: { + send_rfq_projects__pid__procurement_packages__rid__send_rfq_post: { parameters: { query?: never; header?: { @@ -21868,12 +52489,19 @@ export interface operations { }; path: { pid: string; + rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -21895,9 +52523,11 @@ export interface operations { }; }; }; - put_dev_budget_projects__pid__dev_budget_put: { + price_history_projects__pid__procurement_price_history_get: { parameters: { - query?: never; + query?: { + material?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -21909,11 +52539,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["DevBudgetIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -21935,7 +52561,7 @@ export interface operations { }; }; }; - get_specialty_projects__pid__specialty_get: { + three_way_match_projects__pid__procurement_three_way_match_get: { parameters: { query?: never; header?: { @@ -21971,7 +52597,7 @@ export interface operations { }; }; }; - put_specialty_projects__pid__specialty_put: { + productivity_summary_projects__pid__productivity_summary_get: { parameters: { query?: never; header?: { @@ -21985,13 +52611,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -22013,9 +52633,11 @@ export interface operations { }; }; }; - get_property_projects__pid__property_get: { + project_income_basis_projects__pid__proforma_income_basis_get: { parameters: { - query?: never; + query?: { + declared_annual?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22049,7 +52671,7 @@ export interface operations { }; }; }; - put_property_projects__pid__property_put: { + proforma_live_projects__pid__proforma_live_get: { parameters: { query?: never; header?: { @@ -22063,13 +52685,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -22091,14 +52707,9 @@ export interface operations { }; }; }; - get_sources_uses_projects__pid__sources_uses_get: { + proforma_model_metrics_projects__pid__proforma_model_metrics_get: { parameters: { - query?: { - ltc?: number; - rate?: number; - construction_months?: number; - lp_pct?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22132,7 +52743,7 @@ export interface operations { }; }; }; - investment_memo_projects__pid__investment_memo_pdf_get: { + project_renovation_projects__pid__proforma_renovation_post: { parameters: { query?: never; header?: { @@ -22146,43 +52757,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - investment_deck_projects__pid__investment_deck_pdf_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -22204,9 +52785,19 @@ export interface operations { }; }; }; - gmp_reconciliation_projects__pid__dev_budget_gmp_reconciliation_get: { + project_rollover_projects__pid__proforma_rollover_get: { parameters: { - query?: never; + query?: { + renewal_probability?: number | null; + downtime_months?: number | null; + ti_new_psf?: number; + ti_renewal_psf?: number; + lc_new_pct?: number; + lc_renewal_pct?: number; + market_rent_psf?: number; + new_term_years?: number; + horizon_years?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22240,7 +52831,7 @@ export interface operations { }; }; }; - sync_gmp_to_hard_projects__pid__dev_budget_sync_gmp_post: { + solve_for_project_projects__pid__proforma_solve_post: { parameters: { query?: never; header?: { @@ -22254,7 +52845,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Assumptions"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -22276,13 +52871,9 @@ export interface operations { }; }; }; - loan_draws_projects__pid__loan_draws_get: { + program_summary_projects__pid__program_summary_get: { parameters: { - query?: { - ltc?: number; - rate?: number; - construction_months?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22316,13 +52907,9 @@ export interface operations { }; }; }; - loan_draw_request_pdf_projects__pid__loan_draws_request_pdf_get: { + project_prod_actuals_projects__pid__progress_actuals_post: { parameters: { - query?: { - app_no?: number; - ltc?: number; - rate?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22334,7 +52921,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ProdActualsIn"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -22356,7 +52947,7 @@ export interface operations { }; }; }; - construction_draws_projects__pid__construction_draws_get: { + progress_capture_diff_projects__pid__progress_capture_diff_post: { parameters: { query?: never; header?: { @@ -22370,7 +52961,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -22392,9 +52989,11 @@ export interface operations { }; }; }; - dev_budget_cost_lines_projects__pid__dev_budget_cost_lines_get: { + project_progress_reconciliation_projects__pid__progress_reconciliation_get: { parameters: { - query?: never; + query?: { + force_geometry?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -22428,7 +53027,7 @@ export interface operations { }; }; }; - proforma_model_metrics_projects__pid__proforma_model_metrics_get: { + progress_rollup_projects__pid__progress_rollup_post: { parameters: { query?: never; header?: { @@ -22442,73 +53041,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - run_sensitivity_proforma_sensitivity_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; requestBody: { content: { - "application/json": components["schemas"]["SensitivityIn"]; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + "application/json": { + [key: string]: unknown; }; }; }; - }; - run_monte_carlo_proforma_monte_carlo_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["MonteCarloIn"]; - }; - }; responses: { /** @description Successful Response */ 200: { @@ -22530,52 +53069,26 @@ export interface operations { }; }; }; - list_scenarios_proforma_scenarios_get: { + project_package_projects__pid__project_package_pdf_get: { parameters: { query?: { - project_id?: string | null; + max_sheets?: number; }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; + path: { + pid: string; }; - }; - }; - create_scenario_proforma_scenarios_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ScenarioIn"]; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -22594,7 +53107,7 @@ export interface operations { }; }; }; - get_scenario_proforma_scenarios__sid__get: { + project_package_contents_projects__pid__project_package_contents_get: { parameters: { query?: never; header?: { @@ -22602,7 +53115,7 @@ export interface operations { authorization?: string | null; }; path: { - sid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -22630,18 +53143,23 @@ export interface operations { }; }; }; - update_scenario_proforma_scenarios__sid__put: { + upload_index_projects__pid__properties_index_post: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { - sid: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["ScenarioIn"]; + "multipart/form-data": components["schemas"]["Body_upload_index_projects__pid__properties_index_post"]; }; }; responses: { @@ -22665,23 +53183,24 @@ export interface operations { }; }; }; - share_scenario_proforma_scenarios__sid__share_post: { + meta_projects__pid__properties_meta_get: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { - sid: string; + pid: string; }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_share_scenario_proforma_scenarios__sid__share_post"]; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -22700,23 +53219,24 @@ export interface operations { }; }; }; - clone_scenario_proforma_scenarios__sid__clone_post: { + get_property_projects__pid__property_get: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { - sid: string; + pid: string; }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_clone_scenario_proforma_scenarios__sid__clone_post"]; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -22735,18 +53255,25 @@ export interface operations { }; }; }; - forecast_scenario_proforma_scenarios__sid__forecast_post: { + put_property_projects__pid__property_put: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { - sid: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["ForecastIn"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -22770,18 +53297,21 @@ export interface operations { }; }; }; - forecast_stateless_proforma_forecast_post: { + propmap_detect_projects__pid__propmap_detect_get: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_forecast_stateless_proforma_forecast_post"]; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -22803,18 +53333,23 @@ export interface operations { }; }; }; - draw_package_proforma_scenarios__sid__draw_package_post: { + propmap_plan_projects__pid__propmap_plan_post: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { - sid: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody: { content: { - "application/json": components["schemas"]["DrawPackageIn"]; + "application/json": components["schemas"]["Body_propmap_plan_projects__pid__propmap_plan_post"]; }; }; responses: { @@ -22838,14 +53373,18 @@ export interface operations { }; }; }; - portfolio_proforma_portfolio_get: { + project_admissibility_projects__pid__provenance_admissibility_get: { parameters: { - query?: never; + query?: { + scenario_id?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -22872,21 +53411,28 @@ export interface operations { }; }; }; - compare_proforma_compare_post: { + publish_projects__pid__publish_post: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; - requestBody: { + requestBody?: { content: { - "application/json": string[]; + "application/json": boolean; }; }; responses: { /** @description Successful Response */ - 200: { + 202: { headers: { [name: string]: unknown; }; @@ -22905,7 +53451,7 @@ export interface operations { }; }; }; - generate_massing_projects__pid__generate_massing_post: { + publish_status_projects__pid__publish_status_get: { parameters: { query?: never; header?: { @@ -22919,11 +53465,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["MassingIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -22945,51 +53487,23 @@ export interface operations { }; }; }; - structure_recommend_structure_recommend_post: { + pull_plan_board_projects__pid__pull_plan_board_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["StructureIn"]; + query?: { + milestone?: string | null; }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; + path: { + pid: string; }; - }; - }; - test_fit_compare_test_fit_compare_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TestFitIn"]; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23011,18 +53525,23 @@ export interface operations { }; }; }; - test_fit_optimize_test_fit_optimize_post: { + pull_plan_pdf_projects__pid__pull_plan_board_pdf_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["OptimizeIn"]; + query?: { + milestone?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23044,18 +53563,23 @@ export interface operations { }; }; }; - preview_massing_generate_massing_preview_post: { + pull_plan_metrics_projects__pid__pull_plan_metrics_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["MassingIn"]; + query?: { + milestone?: string | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23077,7 +53601,7 @@ export interface operations { }; }; }; - concept_render_status_projects__pid__concept_render_status_get: { + pull_plan_stream_projects__pid__pull_plan_stream_get: { parameters: { query?: never; header?: { @@ -23113,7 +53637,7 @@ export interface operations { }; }; }; - concept_render_request_projects__pid__concept_render_request_post: { + project_pulse_projects__pid__pulse_get: { parameters: { query?: never; header?: { @@ -23127,13 +53651,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23155,7 +53673,7 @@ export interface operations { }; }; }; - concept_render_ingest_projects__pid__concept_render_ingest_post: { + px_summary_projects__pid__px_summary_get: { parameters: { query?: never; header?: { @@ -23169,16 +53687,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -23197,7 +53709,7 @@ export interface operations { }; }; }; - program_summary_projects__pid__program_summary_get: { + qto_by_floor_projects__pid__qto_by_floor_get: { parameters: { query?: never; header?: { @@ -23233,9 +53745,11 @@ export interface operations { }; }; }; - lifecycle_projects__pid__lifecycle_get: { + quality_chain_route_projects__pid__quality_chain_get: { parameters: { - query?: never; + query?: { + guids?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23269,7 +53783,7 @@ export interface operations { }; }; }; - seed_projects__pid__lifecycle_seed_post: { + quality_summary_projects__pid__quality_summary_get: { parameters: { query?: never; header?: { @@ -23305,14 +53819,18 @@ export interface operations { }; }; }; - reference_lifecycle_reference_get: { + quality_turnover_readiness_projects__pid__quality_turnover_readiness_get: { parameters: { - query?: never; + query?: { + guids?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -23339,7 +53857,7 @@ export interface operations { }; }; }; - resilience_flood_projects__pid__resilience_flood_get: { + discipline_quantities_projects__pid__quantities_disciplines_get: { parameters: { query?: never; header?: { @@ -23375,9 +53893,12 @@ export interface operations { }; }; }; - resilience_stormwater_projects__pid__resilience_stormwater_get: { + query_elements_projects__pid__query_get: { parameters: { - query?: never; + query: { + q: string; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23411,7 +53932,7 @@ export interface operations { }; }; }; - resilience_weather_projects__pid__resilience_weather_get: { + raise_plan_to_bim_projects__pid__raise_plan_post: { parameters: { query?: never; header?: { @@ -23425,43 +53946,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - resilience_climate_risk_projects__pid__resilience_climate_risk_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_raise_plan_to_bim_projects__pid__raise_plan_post"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23483,7 +53972,7 @@ export interface operations { }; }; }; - spine_traceability_projects__pid__spine_traceability_get: { + rebar_bbs_projects__pid__rebar_bbs_get: { parameters: { query?: never; header?: { @@ -23519,7 +54008,7 @@ export interface operations { }; }; }; - design_options_compare_projects__pid__design_options_compare_get: { + rebar_bbs_csv_projects__pid__rebar_bbs_csv_get: { parameters: { query?: never; header?: { @@ -23555,9 +54044,11 @@ export interface operations { }; }; }; - design_standards_ruleset_projects__pid__design_standards_get: { + rebar_check_projects__pid__rebar_check_get: { parameters: { - query?: never; + query: { + column: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23591,9 +54082,12 @@ export interface operations { }; }; }; - design_standards_check_projects__pid__design_standards_check_get: { + recipe_log_diff_projects__pid__recipes_diff_get: { parameters: { - query?: never; + query: { + a: number; + b: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23627,7 +54121,7 @@ export interface operations { }; }; }; - mep_schedule_projects__pid__mep_schedule_get: { + recipe_log_export_projects__pid__recipes_export_get: { parameters: { query?: never; header?: { @@ -23663,9 +54157,13 @@ export interface operations { }; }; }; - mep_model_extract_projects__pid__mep_model_extract_get: { + recipe_log_read_projects__pid__recipes_log_get: { parameters: { - query?: never; + query?: { + limit?: number; + offset?: number; + recipe?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23699,7 +54197,7 @@ export interface operations { }; }; }; - model_capabilities_projects__pid__model_capabilities_get: { + recipe_replay_plan_projects__pid__recipes_replay_plan_post: { parameters: { query?: never; header?: { @@ -23713,43 +54211,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; - }; - }; - }; - drawings_sync_status_projects__pid__drawings_sync_status_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_recipe_replay_plan_projects__pid__recipes_replay_plan_post"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -23771,7 +54237,7 @@ export interface operations { }; }; }; - drawings_stream_projects__pid__drawings_stream_get: { + get_rent_roll_projects__pid__rent_roll_get: { parameters: { query?: never; header?: { @@ -23807,15 +54273,11 @@ export interface operations { }; }; }; - mep_size_projects__pid__mep_size_get: { + get_net_effective_projects__pid__rent_roll_net_effective_get: { parameters: { query?: { - kind?: string; - flow?: number; - velocity?: number; - load?: number; - size?: number; - hanger_kind?: string; + discount_rate?: number; + lc_pct?: number | null; }; header?: { "x-user"?: string | null; @@ -23850,7 +54312,7 @@ export interface operations { }; }; }; - envelope_audit_projects__pid__envelope_audit_get: { + post_rent_scrub_projects__pid__rent_roll_scrub_post: { parameters: { query?: never; header?: { @@ -23864,7 +54326,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_post_rent_scrub_projects__pid__rent_roll_scrub_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -23886,14 +54352,9 @@ export interface operations { }; }; }; - envelope_check_projects__pid__envelope_check_get: { + status_report_projects__pid__report_pdf_get: { parameters: { - query?: { - element_type?: string; - climate_zone?: string; - r_value?: number | null; - u_factor?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -23927,7 +54388,7 @@ export interface operations { }; }; }; - diligence_readiness_projects__pid__diligence_readiness_get: { + project_report_catalog_projects__pid__reports_catalog_get: { parameters: { query?: never; header?: { @@ -23963,7 +54424,7 @@ export interface operations { }; }; }; - documents_template_projects__pid__documents_template_get: { + report_pdf_projects__pid__reports__report__pdf_get: { parameters: { query?: never; header?: { @@ -23972,6 +54433,7 @@ export interface operations { }; path: { pid: string; + report: string; }; cookie?: { aec_token?: string | null; @@ -23999,7 +54461,7 @@ export interface operations { }; }; }; - documents_tree_projects__pid__documents_tree_get: { + report_xlsx_projects__pid__reports__report__xlsx_get: { parameters: { query?: never; header?: { @@ -24008,6 +54470,7 @@ export interface operations { }; path: { pid: string; + report: string; }; cookie?: { aec_token?: string | null; @@ -24035,11 +54498,13 @@ export interface operations { }; }; }; - documents_folder_projects__pid__documents_folder_get: { + reserve_study_projects__pid__reserves_study_get: { parameters: { - query: { - path: string; - superseded?: boolean; + query?: { + horizon_years?: number; + opening_balance?: number; + annual_contribution?: number; + inflation_pct?: number; }; header?: { "x-user"?: string | null; @@ -24074,11 +54539,9 @@ export interface operations { }; }; }; - documents_by_role_projects__pid__documents_by_role_get: { + resilience_climate_risk_projects__pid__resilience_climate_risk_get: { parameters: { - query: { - role: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24112,7 +54575,7 @@ export interface operations { }; }; }; - documents_upload_projects__pid__documents_upload_post: { + resilience_flood_projects__pid__resilience_flood_get: { parameters: { query?: never; header?: { @@ -24126,14 +54589,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_documents_upload_projects__pid__documents_upload_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -24152,7 +54611,7 @@ export interface operations { }; }; }; - documents_move_projects__pid__documents__fid__move_post: { + resilience_stormwater_projects__pid__resilience_stormwater_get: { parameters: { query?: never; header?: { @@ -24161,17 +54620,12 @@ export interface operations { }; path: { pid: string; - fid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/x-www-form-urlencoded": components["schemas"]["Body_documents_move_projects__pid__documents__fid__move_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -24193,18 +54647,15 @@ export interface operations { }; }; }; - documents_delete_projects__pid__documents__fid__delete: { + resilience_weather_projects__pid__resilience_weather_get: { parameters: { - query?: { - hard?: boolean; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - fid: string; }; cookie?: { aec_token?: string | null; @@ -24232,7 +54683,7 @@ export interface operations { }; }; }; - documents_download_projects__pid__documents__fid__download_get: { + get_matrix_projects__pid__responsibility_get: { parameters: { query?: never; header?: { @@ -24241,7 +54692,6 @@ export interface operations { }; path: { pid: string; - fid: string; }; cookie?: { aec_token?: string | null; @@ -24269,7 +54719,7 @@ export interface operations { }; }; }; - documents_health_projects__pid__documents_health_get: { + apply_template_projects__pid__responsibility_apply_template_post: { parameters: { query?: never; header?: { @@ -24283,7 +54733,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_apply_template_projects__pid__responsibility_apply_template_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24305,11 +54759,9 @@ export interface operations { }; }; }; - documents_phase_gaps_projects__pid__documents_phase_gaps_get: { + put_config_projects__pid__responsibility_config_put: { parameters: { - query: { - phase: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24321,7 +54773,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_config_projects__pid__responsibility_config_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24343,14 +54799,16 @@ export interface operations { }; }; }; - market_snapshot_market_snapshot_get: { + get_templates_projects__pid__responsibility_templates_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -24377,14 +54835,9 @@ export interface operations { }; }; }; - market_context_projects__pid__market_context_get: { + review_ask_ep_projects__pid__review_ask_post: { parameters: { - query?: { - region?: string | null; - sector?: string | null; - start_year?: number | null; - duration_months?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24396,7 +54849,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_review_ask_ep_projects__pid__review_ask_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24418,16 +54875,9 @@ export interface operations { }; }; }; - market_escalate_projects__pid__market_escalate_get: { + review_contract_ep_projects__pid__review_contract_post: { parameters: { - query: { - amount: number; - region?: string | null; - start_year?: number | null; - duration_months?: number | null; - to_year?: number | null; - rate_pct?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24439,7 +54889,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_review_contract_ep_projects__pid__review_contract_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24461,7 +54915,7 @@ export interface operations { }; }; }; - market_exists_projects__pid__market_exists_get: { + review_scope_ep_projects__pid__review_scope_post: { parameters: { query?: never; header?: { @@ -24475,7 +54929,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_review_scope_ep_projects__pid__review_scope_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24497,7 +54955,7 @@ export interface operations { }; }; }; - readiness_projects__pid__turnover_readiness_get: { + rfi_qa_ask_projects__pid__rfi_qa_post: { parameters: { query?: never; header?: { @@ -24511,7 +54969,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24533,7 +54997,7 @@ export interface operations { }; }; }; - status_projects__pid__turnover_status_get: { + rfi_readiness_projects__pid__rfi_readiness_get: { parameters: { query?: never; header?: { @@ -24569,7 +55033,7 @@ export interface operations { }; }; }; - certify_projects__pid__turnover_certify_post: { + readiness_to_bcf_projects__pid__rfi_readiness_bcf_post: { parameters: { query?: never; header?: { @@ -24583,11 +55047,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_certify_projects__pid__turnover_certify_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -24609,18 +55069,21 @@ export interface operations { }; }; }; - schedule_takt_schedule_takt_post: { + rfi_register_projects__pid__rfi_register_get: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["TaktIn"]; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -24642,14 +55105,19 @@ export interface operations { }; }; }; - schedule_takt_svg_schedule_takt_svg_get: { + risk_board_endpoint_projects__pid__risk_board_get: { parameters: { - query?: { - floors?: number; + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - header?: never; - path?: never; - cookie?: never; }; requestBody?: never; responses: { @@ -24673,12 +55141,19 @@ export interface operations { }; }; }; - get_benchmarks_benchmarks_get: { + risk_digest_projects__pid__risk_digest_get: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -24691,42 +55166,32 @@ export interface operations { "application/json": unknown; }; }; - }; - }; - compute_nodes_compute_nodes_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { + /** @description Validation Error */ + 422: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - compute_run_compute_graph_post: { + project_routines_due_projects__pid__routines_due_get: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -24748,7 +55213,7 @@ export interface operations { }; }; }; - import_xer_projects__pid__schedule_import_xer_post: { + project_routines_run_due_projects__pid__routines_run_due_post: { parameters: { query?: never; header?: { @@ -24762,14 +55227,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_import_xer_projects__pid__schedule_import_xer_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -24788,7 +55249,7 @@ export interface operations { }; }; }; - clear_xer_projects__pid__schedule_import_xer_delete: { + rules_get_projects__pid__rules_get: { parameters: { query?: never; header?: { @@ -24824,11 +55285,9 @@ export interface operations { }; }; }; - schedule_4d_projects__pid__schedule_4d_get: { + rules_put_projects__pid__rules_put: { parameters: { - query?: { - source?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24840,7 +55299,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_rules_put_projects__pid__rules_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24862,7 +55325,7 @@ export interface operations { }; }; }; - lean_ppc_projects__pid__lean_ppc_get: { + rules_effective_projects__pid__rules_effective_get: { parameters: { query?: never; header?: { @@ -24898,11 +55361,9 @@ export interface operations { }; }; }; - pull_plan_board_projects__pid__pull_plan_board_get: { + rules_geometry_run_projects__pid__rules_geometry_run_post: { parameters: { - query?: { - milestone?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24914,7 +55375,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -24936,11 +55403,9 @@ export interface operations { }; }; }; - pull_plan_metrics_projects__pid__pull_plan_metrics_get: { + rules_run_projects__pid__rules_run_get: { parameters: { - query?: { - milestone?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -24974,7 +55439,7 @@ export interface operations { }; }; }; - pull_plan_stream_projects__pid__pull_plan_stream_get: { + space_pack_get_projects__pid__rules_space_pack_get: { parameters: { query?: never; header?: { @@ -25010,11 +55475,9 @@ export interface operations { }; }; }; - pull_plan_pdf_projects__pid__pull_plan_board_pdf_get: { + space_pack_put_projects__pid__rules_space_pack_put: { parameters: { - query?: { - milestone?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25026,7 +55489,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_space_pack_put_projects__pid__rules_space_pack_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -25048,9 +55515,11 @@ export interface operations { }; }; }; - get_matrix_projects__pid__responsibility_get: { + safety_metrics_projects__pid__safety_metrics_get: { parameters: { - query?: never; + query?: { + hours?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25084,9 +55553,11 @@ export interface operations { }; }; }; - get_templates_projects__pid__responsibility_templates_get: { + safety_summary_projects__pid__safety_summary_get: { parameters: { - query?: never; + query?: { + hours?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25120,9 +55591,11 @@ export interface operations { }; }; }; - put_config_projects__pid__responsibility_config_put: { + scan_deviation_projects__pid__scan_deviation_post: { parameters: { - query?: never; + query?: { + tolerance?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25136,7 +55609,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_put_config_projects__pid__responsibility_config_put"]; + "multipart/form-data": components["schemas"]["Body_scan_deviation_projects__pid__scan_deviation_post"]; }; }; responses: { @@ -25160,9 +55633,13 @@ export interface operations { }; }; }; - apply_template_projects__pid__responsibility_apply_template_post: { + scan_verify_lod500_projects__pid__scan_verify_lod500_post: { parameters: { - query?: never; + query?: { + tolerance?: number; + apply?: boolean; + verified_by?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25176,7 +55653,7 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_apply_template_projects__pid__responsibility_apply_template_post"]; + "multipart/form-data": components["schemas"]["Body_scan_verify_lod500_projects__pid__scan_verify_lod500_post"]; }; }; responses: { @@ -25200,7 +55677,7 @@ export interface operations { }; }; }; - review_contract_ep_projects__pid__review_contract_post: { + scene_digest_projects__pid__scene_digest_get: { parameters: { query?: never; header?: { @@ -25214,11 +55691,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_review_contract_ep_projects__pid__review_contract_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25240,7 +55713,7 @@ export interface operations { }; }; }; - review_scope_ep_projects__pid__review_scope_post: { + scene_manifest_projects__pid__scene_manifest_get: { parameters: { query?: never; header?: { @@ -25254,11 +55727,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_review_scope_ep_projects__pid__review_scope_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25280,7 +55749,7 @@ export interface operations { }; }; }; - review_ask_ep_projects__pid__review_ask_post: { + scene_package_zip_projects__pid__scene_package_get: { parameters: { query?: never; header?: { @@ -25294,11 +55763,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_review_ask_ep_projects__pid__review_ask_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25320,9 +55785,11 @@ export interface operations { }; }; }; - draft_rfi_ep_projects__pid__draft_rfi_post: { + schedule_4d_projects__pid__schedule_4d_get: { parameters: { - query?: never; + query?: { + source?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25334,11 +55801,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_draft_rfi_ep_projects__pid__draft_rfi_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25360,7 +55823,7 @@ export interface operations { }; }; }; - draft_submittal_ep_projects__pid__draft_submittal_summary_post: { + schedule_alerts_projects__pid__schedule_alerts_get: { parameters: { query?: never; header?: { @@ -25374,11 +55837,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_draft_submittal_ep_projects__pid__draft_submittal_summary_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25400,7 +55859,7 @@ export interface operations { }; }; }; - draft_scope_ep_projects__pid__draft_scope_post: { + set_baseline_projects__pid__schedule_baseline_post: { parameters: { query?: never; header?: { @@ -25414,14 +55873,10 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_draft_scope_ep_projects__pid__draft_scope_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -25440,7 +55895,7 @@ export interface operations { }; }; }; - extract_sheets_ep_projects__pid__extract_sheets_post: { + clear_baseline_projects__pid__schedule_baseline_delete: { parameters: { query?: never; header?: { @@ -25454,11 +55909,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "multipart/form-data": components["schemas"]["Body_extract_sheets_ep_projects__pid__extract_sheets_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25480,16 +55931,16 @@ export interface operations { }; }; }; - cost_benchmarks_benchmarks_costs_get: { + list_baselines_projects__pid__schedule_baselines_get: { parameters: { - query?: { - min_samples?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -25516,22 +55967,28 @@ export interface operations { }; }; }; - response_rates_benchmarks_response_rates_get: { + capture_baseline_projects__pid__schedule_baselines_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": components["schemas"]["Body_capture_baseline_projects__pid__schedule_baselines_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -25550,16 +56007,17 @@ export interface operations { }; }; }; - pull_planning_benchmarks_pull_planning_get: { + delete_baseline_projects__pid__schedule_baselines__bid__delete: { parameters: { - query?: { - min_committed?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + bid: string; + }; cookie?: { aec_token?: string | null; }; @@ -25586,17 +56044,16 @@ export interface operations { }; }; }; - prequal_scores_projects__pid__prequal_scores_get: { + named_baseline_variance_projects__pid__schedule_baselines__bid__variance_get: { parameters: { - query?: { - project_size?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + bid: string; }; cookie?: { aec_token?: string | null; @@ -25624,11 +56081,9 @@ export interface operations { }; }; }; - coi_expiry_projects__pid__prequal_coi_expiry_get: { + schedule_collapsed_endpoint_projects__pid__schedule_collapsed_post: { parameters: { - query?: { - soon_days?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25640,45 +56095,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - procurement_gate_check_projects__pid__procurement_gate_get: { - parameters: { - query: { - vendor: string; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25700,10 +56123,11 @@ export interface operations { }; }; }; - procurement_compliance_feed_projects__pid__procurement_compliance_feed_get: { + schedule_compare_endpoint_projects__pid__schedule_compare_get: { parameters: { query?: { - within_days?: number; + baseline_id?: string | null; + match?: string; }; header?: { "x-user"?: string | null; @@ -25738,7 +56162,7 @@ export interface operations { }; }; }; - lien_exposure_projects__pid__payapp_lien_exposure_get: { + schedule_compress_endpoint_projects__pid__schedule_compress_post: { parameters: { query?: never; header?: { @@ -25752,41 +56176,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - payments_status_payments_status_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path?: never; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -25808,7 +56204,7 @@ export interface operations { }; }; }; - journal_projects__pid__accounting_journal_get: { + cpm_projects__pid__schedule_cpm_get: { parameters: { query?: never; header?: { @@ -25844,9 +56240,11 @@ export interface operations { }; }; }; - chart_of_accounts_projects__pid__accounting_chart_of_accounts_get: { + schedule_earned_endpoint_projects__pid__schedule_earned_get: { parameters: { - query?: never; + query?: { + baseline_id?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -25880,7 +56278,7 @@ export interface operations { }; }; }; - journal_entries_projects__pid__accounting_journal_entries_get: { + earned_value_projects__pid__schedule_earned_value_get: { parameters: { query?: never; header?: { @@ -25916,7 +56314,7 @@ export interface operations { }; }; }; - trial_balance_projects__pid__accounting_trial_balance_get: { + schedule_eot_projects__pid__schedule_eot_post: { parameters: { query?: never; header?: { @@ -25930,7 +56328,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -25952,7 +56356,7 @@ export interface operations { }; }; }; - gl_csv_projects__pid__accounting_gl_csv_get: { + schedule_eot_sourced_projects__pid__schedule_eot_sourced_post: { parameters: { query?: never; header?: { @@ -25966,7 +56370,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -25988,9 +56398,11 @@ export interface operations { }; }; }; - bills_iif_projects__pid__accounting_bills_iif_get: { + export_schedule_projects__pid__schedule_export_get: { parameters: { - query?: never; + query?: { + fmt?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26024,7 +56436,7 @@ export interface operations { }; }; }; - project_carbon_projects__pid__carbon_get: { + schedule_flowline_endpoint_projects__pid__schedule_flowline_get: { parameters: { query?: never; header?: { @@ -26060,7 +56472,7 @@ export interface operations { }; }; }; - code_check_projects__pid__codecheck_post: { + schedule_from_estimate_projects__pid__schedule_from_estimate_post: { parameters: { query?: never; header?: { @@ -26076,7 +56488,9 @@ export interface operations { }; requestBody?: { content: { - "application/json": components["schemas"]["Body_code_check_projects__pid__codecheck_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -26100,14 +56514,16 @@ export interface operations { }; }; }; - ids_templates_ids_templates_get: { + gantt_projects__pid__schedule_gantt_svg_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -26134,25 +56550,21 @@ export interface operations { }; }; }; - build_ids_ids_build_post: { + schedule_health_endpoint_projects__pid__schedule_health_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26174,19 +56586,21 @@ export interface operations { }; }; }; - build_eir_ids_eir_post: { + schedule_impacted_endpoint_projects__pid__schedule_impacted_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { "application/json": { [key: string]: unknown; @@ -26214,9 +56628,11 @@ export interface operations { }; }; }; - level_quotes_projects__pid__procurement_level_quotes_post: { + import_xer_projects__pid__schedule_import_xer_post: { parameters: { - query?: never; + query?: { + project_id?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26230,12 +56646,12 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_level_quotes_projects__pid__procurement_level_quotes_post"]; + "multipart/form-data": components["schemas"]["Body_import_xer_projects__pid__schedule_import_xer_post"]; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -26254,7 +56670,7 @@ export interface operations { }; }; }; - three_way_match_projects__pid__procurement_three_way_match_get: { + clear_xer_projects__pid__schedule_import_xer_delete: { parameters: { query?: never; header?: { @@ -26290,53 +56706,27 @@ export interface operations { }; }; }; - rfq_status_procurement_rfq_status_get: { + schedule_level_endpoint_projects__pid__schedule_level_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; }; }; }; - }; - conceptual_catalog_estimate_conceptual_catalog_get: { - parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path?: never; - cookie?: { - aec_token?: string | null; - }; - }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26358,7 +56748,7 @@ export interface operations { }; }; }; - conceptual_projects__pid__estimate_conceptual_post: { + lob_projects__pid__schedule_lob_svg_get: { parameters: { query?: never; header?: { @@ -26372,13 +56762,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26400,9 +56784,12 @@ export interface operations { }; }; }; - classify_projects__pid__ifc_classify_post: { + lookahead_projects__pid__schedule_lookahead_get: { parameters: { - query?: never; + query?: { + weeks?: number; + start?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26414,11 +56801,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": components["schemas"]["Body_classify_projects__pid__ifc_classify_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26440,23 +56823,23 @@ export interface operations { }; }; }; - screen_parcels_screen_post: { + schedule_make_ready_projects__pid__schedule_make_ready_get: { parameters: { - query?: never; + query?: { + days?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_screen_parcels_screen_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26478,14 +56861,16 @@ export interface operations { }; }; }; - data_status_parcels_data_status_get: { + milestones_projects__pid__schedule_milestones_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -26512,9 +56897,14 @@ export interface operations { }; }; }; - reconcile_projects__pid__pricing_reconcile_get: { + schedule_montecarlo_endpoint_projects__pid__schedule_montecarlo_get: { parameters: { - query?: never; + query?: { + iterations?: number; + ppc_pct?: number | null; + distribution?: string; + seed?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26548,14 +56938,16 @@ export interface operations { }; }; }; - pricing_status_pricing_status_get: { + schedule_optimize_projects__pid__schedule_optimize_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -26582,11 +56974,9 @@ export interface operations { }; }; }; - warranties_expiring_projects__pid__warranties_expiring_get: { + schedule_optioneer_projects__pid__schedule_optioneer_post: { parameters: { - query?: { - within_days?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26598,7 +56988,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -26620,11 +57016,9 @@ export interface operations { }; }; }; - compliance_expiring_projects__pid__compliance_expiring_get: { + schedule_portfolio_endpoint_projects__pid__schedule_portfolio_post: { parameters: { - query?: { - within_days?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26636,7 +57030,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -26658,9 +57058,11 @@ export interface operations { }; }; }; - payapp_advance_projects__pid__cost_pay_app_advance_post: { + schedule_progress_endpoint_projects__pid__schedule_progress_report_get: { parameters: { - query?: never; + query?: { + baseline_id?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -26694,23 +57096,21 @@ export interface operations { }; }; }; - convert_citygml_convert_citygml_post: { + schedule_reliability_endpoint_projects__pid__schedule_reliability_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_convert_citygml_convert_citygml_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26732,23 +57132,23 @@ export interface operations { }; }; }; - convert_convert_post: { + resource_leveling_endpoint_projects__pid__schedule_resource_leveling_get: { parameters: { - query?: never; + query: { + cap: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_convert_convert_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26770,21 +57170,23 @@ export interface operations { }; }; }; - inspect_vim_convert_vim_inspect_post: { + resource_leveling_apply_projects__pid__schedule_resource_leveling_apply_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; requestBody: { content: { - "multipart/form-data": components["schemas"]["Body_inspect_vim_convert_vim_inspect_post"]; + "application/json": components["schemas"]["Body_resource_leveling_apply_projects__pid__schedule_resource_leveling_apply_post"]; }; }; responses: { @@ -26808,43 +57210,26 @@ export interface operations { }; }; }; - e57_status_convert_e57_status_get: { + resource_loading_endpoint_projects__pid__schedule_resource_loading_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + query?: { + cap?: number | null; }; - }; - }; - register_auth_register_post: { - parameters: { - query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_register_auth_register_post"]; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -26863,18 +57248,25 @@ export interface operations { }; }; }; - login_auth_login_post: { + schedule_risk_alias_projects__pid__schedule_risk_get: { parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_login_auth_login_post"]; + query?: { + iterations?: number; + seed?: number | null; + ppc?: number | null; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -26896,36 +57288,23 @@ export interface operations { }; }; }; - logout_auth_logout_post: { + schedule_status_endpoint_projects__pid__schedule_status_post: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; - }; - mfa_verify_auth_mfa_verify_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["Body_mfa_verify_auth_mfa_verify_post"]; + "application/json": components["schemas"]["Body_schedule_status_endpoint_projects__pid__schedule_status_post"]; }; }; responses: { @@ -26949,14 +57328,18 @@ export interface operations { }; }; }; - mfa_status_auth_mfa_status_get: { + schedule_takt_train_endpoint_projects__pid__schedule_takt_train_get: { parameters: { - query?: never; + query?: { + takt_days?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -26983,14 +57366,16 @@ export interface operations { }; }; }; - mfa_setup_auth_mfa_setup_post: { + project_takt_svg_projects__pid__schedule_takt_svg_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27017,23 +57402,21 @@ export interface operations { }; }; }; - mfa_enable_auth_mfa_enable_post: { + project_takt_progress_projects__pid__schedule_takt_progress_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_mfa_enable_auth_mfa_enable_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27055,23 +57438,21 @@ export interface operations { }; }; }; - mfa_disable_auth_mfa_disable_post: { + variance_projects__pid__schedule_variance_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_mfa_disable_auth_mfa_disable_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27093,36 +57474,27 @@ export interface operations { }; }; }; - auth_providers_auth_providers_get: { + schedule_weather_endpoint_projects__pid__schedule_weather_post: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - }; - }; - oauth_login_auth_oauth__provider__login_get: { - parameters: { - query?: never; - header?: never; path: { - provider: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; }; - cookie?: never; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27144,17 +57516,21 @@ export interface operations { }; }; }; - oauth_callback_auth_oauth__provider__callback_get: { + schedule_windows_endpoint_projects__pid__schedule_windows_get: { parameters: { query?: { - code?: string | null; - state?: string | null; + match?: string; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - header?: never; path: { - provider: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody?: never; responses: { @@ -27178,19 +57554,27 @@ export interface operations { }; }; }; - me_auth_me_get: { + scope_register_projects__pid__scope_register_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -27212,23 +57596,24 @@ export interface operations { }; }; }; - change_password_auth_password_post: { + search_projects__pid__search_get: { parameters: { - query?: never; + query: { + q: string; + limit?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_change_password_auth_password_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27250,14 +57635,16 @@ export interface operations { }; }; }; - logout_all_auth_logout_all_post: { + securities_package_projects__pid__securities_package_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27284,14 +57671,16 @@ export interface operations { }; }; }; - list_users_auth_users_get: { + securities_syndicate_projects__pid__securities_syndicate_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27318,26 +57707,24 @@ export interface operations { }; }; }; - create_user_auth_users_post: { + selections_push_change_events_projects__pid__selections_push_change_events_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_create_user_auth_users_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -27356,7 +57743,7 @@ export interface operations { }; }; }; - update_user_auth_users__username__patch: { + selections_summary_projects__pid__selections_summary_get: { parameters: { query?: never; header?: { @@ -27364,17 +57751,13 @@ export interface operations { authorization?: string | null; }; path: { - username: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["UserPatch"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27396,7 +57779,7 @@ export interface operations { }; }; }; - reset_password_auth_users__username__password_post: { + list_share_tokens_projects__pid__share_tokens_get: { parameters: { query?: never; header?: { @@ -27404,17 +57787,13 @@ export interface operations { authorization?: string | null; }; path: { - username: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_reset_password_auth_users__username__password_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27436,7 +57815,7 @@ export interface operations { }; }; }; - revoke_sessions_auth_users__username__revoke_sessions_post: { + create_share_token_projects__pid__share_tokens_post: { parameters: { query?: never; header?: { @@ -27444,13 +57823,19 @@ export interface operations { authorization?: string | null; }; path: { - username: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -27472,7 +57857,7 @@ export interface operations { }; }; }; - issue_reset_token_auth_users__username__reset_token_post: { + revoke_share_token_projects__pid__share_tokens__token__delete: { parameters: { query?: never; header?: { @@ -27480,7 +57865,8 @@ export interface operations { authorization?: string | null; }; path: { - username: string; + pid: string; + token: string; }; cookie?: { aec_token?: string | null; @@ -27489,7 +57875,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -27508,14 +57894,16 @@ export interface operations { }; }; }; - get_integrations_settings_integrations_get: { + get_shared_params_projects__pid__shared_params_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27542,21 +57930,23 @@ export interface operations { }; }; }; - put_integrations_settings_integrations_put: { + put_shared_params_projects__pid__shared_params_put: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; requestBody: { content: { - "application/json": components["schemas"]["Body_put_integrations_settings_integrations_put"]; + "application/json": components["schemas"]["Body_put_shared_params_projects__pid__shared_params_put"]; }; }; responses: { @@ -27580,25 +57970,26 @@ export interface operations { }; }; }; - test_integration_settings_integrations_test_post: { + site_context_projects__pid__site_context_get: { parameters: { - query?: never; + query?: { + lat?: number | null; + lon?: number | null; + radius?: number; + refresh?: boolean; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27620,20 +58011,16 @@ export interface operations { }; }; }; - audit_log_audit_get: { + clear_site_context_projects__pid__site_context_delete: { parameters: { - query?: { - action?: string | null; - actor?: string | null; - since?: string | null; - limit?: number; - offset?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27660,16 +58047,16 @@ export interface operations { }; }; }; - webhook_deliveries_webhooks_deliveries_get: { + smart_views_get_projects__pid__smart_views_get: { parameters: { - query?: { - limit?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -27696,16 +58083,23 @@ export interface operations { }; }; }; - reset_with_token_auth_reset_post: { + smart_views_put_projects__pid__smart_views_put: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody: { content: { - "application/json": components["schemas"]["Body_reset_with_token_auth_reset_post"]; + "application/json": components["schemas"]["Body_smart_views_put_projects__pid__smart_views_put"]; }; }; responses: { @@ -27729,14 +58123,20 @@ export interface operations { }; }; }; - scim_config_scim_v2_ServiceProviderConfig_get: { + smart_views_run_projects__pid__smart_views__vid__run_get: { parameters: { query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: never; + path: { + pid: string; + vid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -27760,14 +58160,61 @@ export interface operations { }; }; }; - scim_resource_types_scim_v2_ResourceTypes_get: { + upload_source_ifc_projects__pid__source_ifc_post: { + parameters: { + query?: { + publish?: boolean; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_source_ifc_projects__pid__source_ifc_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + source_ifc_download_projects__pid__source_ifc_get: { parameters: { query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: never; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -27791,18 +58238,24 @@ export interface operations { }; }; }; - scim_list_users_scim_v2_Users_get: { + get_sources_uses_projects__pid__sources_uses_get: { parameters: { query?: { - filter?: string | null; - startIndex?: number; - count?: number; + ltc?: number; + rate?: number; + construction_months?: number; + lp_pct?: number; }; header?: { + "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: never; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -27826,25 +58279,24 @@ export interface operations { }; }; }; - scim_create_user_scim_v2_Users_post: { + spatial_tree_projects__pid__spatial_tree_get: { parameters: { query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; + path: { + pid: string; + }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -27863,16 +58315,19 @@ export interface operations { }; }; }; - scim_get_user_scim_v2_Users__user_id__get: { + spec_links_projects__pid__spec_links_get: { parameters: { query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; path: { - user_id: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody?: never; responses: { @@ -27896,24 +58351,23 @@ export interface operations { }; }; }; - scim_replace_user_scim_v2_Users__user_id__put: { + spec_manual_projects__pid__spec_manual_get: { parameters: { - query?: never; + query?: { + system?: string | null; + }; header?: { + "x-user"?: string | null; authorization?: string | null; }; path: { - user_id: string; + pid: string; }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -27935,25 +58389,32 @@ export interface operations { }; }; }; - scim_delete_user_scim_v2_Users__user_id__delete: { + spec_manual_text_projects__pid__spec_manual_txt_get: { parameters: { - query?: never; + query?: { + system?: string | null; + }; header?: { + "x-user"?: string | null; authorization?: string | null; }; path: { - user_id: string; + pid: string; + }; + cookie?: { + aec_token?: string | null; }; - cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 204: { + 200: { headers: { [name: string]: unknown; }; - content?: never; + content: { + "application/json": unknown; + }; }; /** @description Validation Error */ 422: { @@ -27966,24 +58427,21 @@ export interface operations { }; }; }; - scim_patch_user_scim_v2_Users__user_id__patch: { + get_specialty_projects__pid__specialty_get: { parameters: { query?: never; header?: { + "x-user"?: string | null; authorization?: string | null; }; path: { - user_id: string; + pid: string; }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; + cookie?: { + aec_token?: string | null; }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28005,67 +58463,25 @@ export interface operations { }; }; }; - saml_metadata_auth_saml_metadata_get: { + put_specialty_projects__pid__specialty_put: { parameters: { query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; - }; - }; - }; - saml_login_auth_saml_login_get: { - parameters: { - query?: { - relay_state?: string; + header?: { + "x-user"?: string | null; + authorization?: string | null; }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": unknown; - }; + path: { + pid: string; }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["HTTPValidationError"]; - }; + cookie?: { + aec_token?: string | null; }; }; - }; - saml_acs_auth_saml_acs_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; requestBody: { content: { - "application/x-www-form-urlencoded": components["schemas"]["Body_saml_acs_auth_saml_acs_post"]; + "application/json": { + [key: string]: unknown; + }; }; }; responses: { @@ -28089,19 +58505,30 @@ export interface operations { }; }; }; - list_connections_connections_get: { + post_specialty_blended_projects__pid__specialty_blended_post: { parameters: { - query?: never; + query?: { + years?: number; + ramp_years?: number; + ramp_start?: number; + terminal_cap?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Assumptions"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28123,26 +58550,28 @@ export interface operations { }; }; }; - create_connection_connections_post: { + run_specialty_monte_carlo_projects__pid__specialty_monte_carlo_post: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; requestBody: { content: { - "application/json": components["schemas"]["ConnectionIn"]; + "application/json": components["schemas"]["SpecialtyMonteCarloIn"]; }; }; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -28161,25 +58590,26 @@ export interface operations { }; }; }; - update_connection_connections__cid__put: { + get_specialty_proforma_projects__pid__specialty_proforma_get: { parameters: { - query?: never; + query?: { + years?: number; + ramp_years?: number; + ramp_start?: number; + terminal_cap?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["ConnectionIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28201,7 +58631,7 @@ export interface operations { }; }; }; - delete_connection_connections__cid__delete: { + specs_extract_submittals_projects__pid__specs_extract_submittals_post: { parameters: { query?: never; header?: { @@ -28209,13 +58639,19 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28237,23 +58673,21 @@ export interface operations { }; }; }; - test_config_connections_test_post: { + specs_submittal_log_projects__pid__specs_submittal_log_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["ConnectionIn"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28275,7 +58709,7 @@ export interface operations { }; }; }; - test_connection_connections__cid__test_post: { + spine_traceability_projects__pid__spine_traceability_get: { parameters: { query?: never; header?: { @@ -28283,7 +58717,7 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28311,7 +58745,7 @@ export interface operations { }; }; }; - connection_tables_connections__cid__tables_get: { + stakeholder_analysis_projects__pid__stakeholders_analysis_get: { parameters: { query?: never; header?: { @@ -28319,7 +58753,7 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28347,25 +58781,23 @@ export interface operations { }; }; }; - connection_query_connections__cid__query_post: { + standards_check_projects__pid__standards_check_get: { parameters: { - query?: never; + query?: { + standard?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_connection_query_connections__cid__query_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28387,7 +58819,7 @@ export interface operations { }; }; }; - acc_issues_connections__cid__acc_projects__project_id__issues_get: { + structure_code_aster_projects__pid__structure_code_aster_mail_get: { parameters: { query?: never; header?: { @@ -28395,8 +58827,7 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; - project_id: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28424,16 +58855,28 @@ export interface operations { }; }; }; - quickbooks_read_connections__cid__quickbooks__entity__get: { + structure_lateral_projects__pid__structure_lateral_get: { parameters: { - query?: never; + query?: { + sds?: number; + sd1?: number; + r?: number; + ie?: number; + system?: string; + wind_speed_mph?: number; + exposure?: string; + dead_psf?: number; + area_sf?: number | null; + risk_category?: string; + cd?: number | null; + elastic_drift_ratio?: number | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - cid: string; - entity: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28461,7 +58904,7 @@ export interface operations { }; }; }; - erp_read_connections__cid__erp__entity__get: { + structure_opensees_projects__pid__structure_opensees_tcl_get: { parameters: { query?: never; header?: { @@ -28469,8 +58912,7 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; - entity: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28498,15 +58940,23 @@ export interface operations { }; }; }; - get_mappings_connections__cid__mappings_get: { + structure_solve_projects__pid__structure_solve_get: { parameters: { - query?: never; + query?: { + live_occupancy?: string; + sdl_psf?: number; + slab_thickness_in?: number; + tributary_ft?: number | null; + gross_area_sf?: number | null; + e_ksi?: number; + i_in4?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; @@ -28534,7 +58984,7 @@ export interface operations { }; }; }; - put_mappings_connections__cid__mappings_put: { + subcontractor_billing_projects__pid__subcontractor_billing_get: { parameters: { query?: never; header?: { @@ -28542,17 +58992,13 @@ export interface operations { authorization?: string | null; }; path: { - cid: string; + pid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": components["schemas"]["Body_put_mappings_connections__cid__mappings_put"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28574,14 +59020,16 @@ export interface operations { }; }; }; - permit_cities_opendata_permit_cities_get: { + submittal_register_projects__pid__submittals_register_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + }; cookie?: { aec_token?: string | null; }; @@ -28608,17 +59056,9 @@ export interface operations { }; }; }; - query_permits_projects__pid__opendata_permits_get: { + post_competitive_supply_projects__pid__supply_competitive_post: { parameters: { - query: { - city: string; - lat?: number | null; - lon?: number | null; - radius?: number | null; - address?: string | null; - q?: string | null; - limit?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -28630,7 +59070,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_post_competitive_supply_projects__pid__supply_competitive_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28652,17 +59096,9 @@ export interface operations { }; }; }; - permits_geojson_projects__pid__opendata_permits_geojson_get: { + sync_procore_projects__pid__sync_procore_post: { parameters: { - query: { - city: string; - lat?: number | null; - lon?: number | null; - radius?: number | null; - address?: string | null; - q?: string | null; - limit?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -28674,7 +59110,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_sync_procore_projects__pid__sync_procore_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28696,7 +59136,7 @@ export interface operations { }; }; }; - import_permits_projects__pid__opendata_permits_import_post: { + push_procore_projects__pid__sync_procore_push_post: { parameters: { query?: never; header?: { @@ -28712,9 +59152,7 @@ export interface operations { }; requestBody: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["Body_push_procore_projects__pid__sync_procore_push_post"]; }; }; responses: { @@ -28738,7 +59176,7 @@ export interface operations { }; }; }; - cap_table_projects__pid__cap_table_get: { + list_schedules_projects__pid__sync_schedules_get: { parameters: { query?: never; header?: { @@ -28774,7 +59212,7 @@ export interface operations { }; }; }; - capital_call_projects__pid__capital_call_post: { + create_schedule_projects__pid__sync_schedules_post: { parameters: { query?: never; header?: { @@ -28790,12 +59228,12 @@ export interface operations { }; requestBody: { content: { - "application/json": components["schemas"]["Body_capital_call_projects__pid__capital_call_post"]; + "application/json": components["schemas"]["ScheduleIn"]; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -28814,7 +59252,7 @@ export interface operations { }; }; }; - distribution_projects__pid__distribution_post: { + update_schedule_projects__pid__sync_schedules__sid__put: { parameters: { query?: never; header?: { @@ -28823,14 +59261,15 @@ export interface operations { }; path: { pid: string; + sid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { + requestBody?: { content: { - "application/json": components["schemas"]["Body_distribution_projects__pid__distribution_post"]; + "application/json": components["schemas"]["Body_update_schedule_projects__pid__sync_schedules__sid__put"]; }; }; responses: { @@ -28854,7 +59293,7 @@ export interface operations { }; }; }; - waterfall_scenario_projects__pid__waterfall_post: { + delete_schedule_projects__pid__sync_schedules__sid__delete: { parameters: { query?: never; header?: { @@ -28863,18 +59302,50 @@ export interface operations { }; path: { pid: string; + sid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: { - content: { - "application/json": { - [key: string]: unknown; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + run_schedule_now_projects__pid__sync_schedules__sid__run_now_post: { + parameters: { + query?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; + path: { + pid: string; + sid: string; + }; + cookie?: { + aec_token?: string | null; + }; }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -28896,7 +59367,7 @@ export interface operations { }; }; }; - investor_statement_projects__pid__investors__iid__statement_pdf_get: { + post_t12_normalize_projects__pid__t12_normalize_post: { parameters: { query?: never; header?: { @@ -28905,13 +59376,16 @@ export interface operations { }; path: { pid: string; - iid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_post_t12_normalize_projects__pid__t12_normalize_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28933,24 +59407,27 @@ export interface operations { }; }; }; - investor_statement_share_projects__pid__investors__iid__share_post: { + takeoff_2d_projects__pid__takeoff_2d_post: { parameters: { - query?: { - ttl?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - iid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -28972,17 +59449,25 @@ export interface operations { }; }; }; - investor_statement_public_projects__pid__investors__iid__statement_public_pdf_get: { + takeoff_dxf_projects__pid__takeoff_dxf_post: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { pid: string; - iid: string; }; - cookie?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_takeoff_dxf_projects__pid__takeoff_dxf_post"]; + }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -29004,7 +59489,7 @@ export interface operations { }; }; }; - get_rent_roll_projects__pid__rent_roll_get: { + tm_by_change_event_projects__pid__tm_by_change_event_get: { parameters: { query?: never; header?: { @@ -29040,12 +59525,9 @@ export interface operations { }; }; }; - lease_management_projects__pid__leases_management_get: { + tm_summary_projects__pid__tm_summary_get: { parameters: { - query?: { - years?: number; - recoverable_opex?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29079,9 +59561,14 @@ export interface operations { }; }; }; - listing_autofill_projects__pid__listings_autofill_get: { + list_topics_projects__pid__topics_get: { parameters: { - query?: never; + query?: { + type?: string | null; + status?: string | null; + limit?: number; + offset?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29101,7 +59588,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["TopicOut"][]; }; }; /** @description Validation Error */ @@ -29115,7 +59602,7 @@ export interface operations { }; }; }; - import_comparables_projects__pid__comparables_import_post: { + create_topic_projects__pid__topics_post: { parameters: { query?: never; header?: { @@ -29131,19 +59618,17 @@ export interface operations { }; requestBody: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["TopicIn"]; }; }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["TopicOut"]; }; }; /** @description Validation Error */ @@ -29157,9 +59642,12 @@ export interface operations { }; }; }; - get_appraisal_projects__pid__appraisal_get: { + topics_board_projects__pid__topics_board_get: { parameters: { - query?: never; + query?: { + group_by?: string; + filter?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29193,7 +59681,7 @@ export interface operations { }; }; }; - save_appraisal_projects__pid__appraisal_post: { + get_topic_projects__pid__topics__tid__get: { parameters: { query?: never; header?: { @@ -29202,18 +59690,13 @@ export interface operations { }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -29221,7 +59704,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["TopicOut"]; }; }; /** @description Validation Error */ @@ -29235,7 +59718,7 @@ export interface operations { }; }; }; - listing_reso_projects__pid__listings__lid__reso_get: { + patch_topic_projects__pid__topics__tid__patch: { parameters: { query?: never; header?: { @@ -29244,13 +59727,17 @@ export interface operations { }; path: { pid: string; - lid: string; + tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["TopicPatch"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -29258,7 +59745,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["TopicOut"]; }; }; /** @description Validation Error */ @@ -29272,14 +59759,17 @@ export interface operations { }; }; }; - re_syndication_status_re_syndication_status_get: { + list_attachments_projects__pid__topics__tid__attachments_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + tid: string; + }; cookie?: { aec_token?: string | null; }; @@ -29292,7 +59782,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["AttachmentOut"][]; }; }; /** @description Validation Error */ @@ -29306,7 +59796,7 @@ export interface operations { }; }; }; - listing_syndicate_projects__pid__listings__lid__syndicate_post: { + add_attachment_projects__pid__topics__tid__attachments_post: { parameters: { query?: never; header?: { @@ -29315,21 +59805,25 @@ export interface operations { }; path: { pid: string; - lid: string; + tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_add_attachment_projects__pid__topics__tid__attachments_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["AttachmentOut"]; }; }; /** @description Validation Error */ @@ -29343,18 +59837,16 @@ export interface operations { }; }; }; - listing_share_projects__pid__listings__lid__share_post: { + list_comments_projects__pid__topics__tid__comments_get: { parameters: { - query?: { - ttl?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; - lid: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -29368,7 +59860,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["CommentOut"][]; }; }; /** @description Validation Error */ @@ -29382,25 +59874,34 @@ export interface operations { }; }; }; - listing_public_projects__pid__listings__lid__public_get: { + add_comment_projects__pid__topics__tid__comments_post: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path: { pid: string; - lid: string; + tid: string; + }; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CommentIn"]; }; - cookie?: never; }; - requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["CommentOut"]; }; }; /** @description Validation Error */ @@ -29414,17 +59915,16 @@ export interface operations { }; }; }; - list_verifications_projects__pid__verification_get: { + topic_timeline_projects__pid__topics__tid__timeline_get: { parameters: { - query?: { - status?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -29452,7 +59952,7 @@ export interface operations { }; }; }; - coverage_projects__pid__verification_coverage_get: { + list_viewpoints_projects__pid__topics__tid__viewpoints_get: { parameters: { query?: never; header?: { @@ -29461,6 +59961,7 @@ export interface operations { }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -29474,7 +59975,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["ViewpointOut"][]; }; }; /** @description Validation Error */ @@ -29488,7 +59989,7 @@ export interface operations { }; }; }; - deviations_projects__pid__verification_deviations_get: { + add_viewpoint_projects__pid__topics__tid__viewpoints_post: { parameters: { query?: never; header?: { @@ -29497,20 +59998,25 @@ export interface operations { }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["ViewpointIn"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["ViewpointOut"]; }; }; /** @description Validation Error */ @@ -29524,7 +60030,7 @@ export interface operations { }; }; }; - set_status_projects__pid__verification__guid__put: { + certify_projects__pid__turnover_certify_post: { parameters: { query?: never; header?: { @@ -29533,7 +60039,6 @@ export interface operations { }; path: { pid: string; - guid: string; }; cookie?: { aec_token?: string | null; @@ -29541,9 +60046,7 @@ export interface operations { }; requestBody: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["Body_certify_projects__pid__turnover_certify_post"]; }; }; responses: { @@ -29567,7 +60070,7 @@ export interface operations { }; }; }; - upload_photo_projects__pid__verification__guid__photo_post: { + readiness_projects__pid__turnover_readiness_get: { parameters: { query?: never; header?: { @@ -29576,17 +60079,12 @@ export interface operations { }; path: { pid: string; - guid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody: { - content: { - "multipart/form-data": components["schemas"]["Body_upload_photo_projects__pid__verification__guid__photo_post"]; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -29608,11 +60106,9 @@ export interface operations { }; }; }; - weekly_payroll_projects__pid__payroll_get: { + status_projects__pid__turnover_status_get: { parameters: { - query?: { - week_ending?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29646,11 +60142,9 @@ export interface operations { }; }; }; - wh347_pdf_projects__pid__payroll_wh347_pdf_get: { + twin_readiness_projects__pid__twin_readiness_get: { parameters: { - query?: { - week_ending?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29684,7 +60178,7 @@ export interface operations { }; }; }; - project_assistant_projects__pid__assistant_post: { + list_types_projects__pid__types_get: { parameters: { query?: never; header?: { @@ -29698,13 +60192,7 @@ export interface operations { aec_token?: string | null; }; }; - requestBody: { - content: { - "application/json": { - [key: string]: unknown; - }; - }; - }; + requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -29726,7 +60214,7 @@ export interface operations { }; }; }; - assistant_snapshot_projects__pid__assistant_snapshot_get: { + type_detail_projects__pid__types__type_guid__get: { parameters: { query?: never; header?: { @@ -29735,6 +60223,7 @@ export interface operations { }; path: { pid: string; + type_guid: string; }; cookie?: { aec_token?: string | null; @@ -29762,7 +60251,7 @@ export interface operations { }; }; }; - record_distribution_projects__pid__modules__key___rid__distribution_get: { + handshake_projects__pid__uploads_handshake_post: { parameters: { query?: never; header?: { @@ -29771,14 +60260,18 @@ export interface operations { }; path: { pid: string; - key: string; - rid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -29786,7 +60279,9 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": { + [key: string]: unknown; + }; }; }; /** @description Validation Error */ @@ -29800,7 +60295,7 @@ export interface operations { }; }; }; - stakeholder_analysis_projects__pid__stakeholders_analysis_get: { + put_chunk_projects__pid__uploads__uid__chunk__index__put: { parameters: { query?: never; header?: { @@ -29809,6 +60304,8 @@ export interface operations { }; path: { pid: string; + uid: string; + index: number; }; cookie?: { aec_token?: string | null; @@ -29822,7 +60319,9 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": { + [key: string]: unknown; + }; }; }; /** @description Validation Error */ @@ -29836,7 +60335,7 @@ export interface operations { }; }; }; - tm_summary_projects__pid__tm_summary_get: { + complete_projects__pid__uploads__uid__complete_post: { parameters: { query?: never; header?: { @@ -29845,12 +60344,19 @@ export interface operations { }; path: { pid: string; + uid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -29858,7 +60364,9 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": { + [key: string]: unknown; + }; }; }; /** @description Validation Error */ @@ -29872,9 +60380,12 @@ export interface operations { }; }; }; - tm_by_change_event_projects__pid__tm_by_change_event_get: { + run_validate_projects__pid__validate_post: { parameters: { - query?: never; + query?: { + format?: string; + ids?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29886,7 +60397,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "multipart/form-data": components["schemas"]["Body_run_validate_projects__pid__validate_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -29908,9 +60423,11 @@ export interface operations { }; }; }; - co_log_projects__pid__change_orders_log_get: { + list_verifications_projects__pid__verification_get: { parameters: { - query?: never; + query?: { + status?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -29944,7 +60461,7 @@ export interface operations { }; }; }; - action_tracker_projects__pid__action_items_tracker_get: { + coverage_projects__pid__verification_coverage_get: { parameters: { query?: never; header?: { @@ -29980,7 +60497,7 @@ export interface operations { }; }; }; - specs_submittal_log_projects__pid__specs_submittal_log_get: { + deviations_projects__pid__verification_deviations_get: { parameters: { query?: never; header?: { @@ -30016,7 +60533,7 @@ export interface operations { }; }; }; - specs_extract_submittals_projects__pid__specs_extract_submittals_post: { + set_status_projects__pid__verification__guid__put: { parameters: { query?: never; header?: { @@ -30025,6 +60542,7 @@ export interface operations { }; path: { pid: string; + guid: string; }; cookie?: { aec_token?: string | null; @@ -30058,24 +60576,26 @@ export interface operations { }; }; }; - site_feasibility_projects__pid__feasibility_get: { + upload_photo_projects__pid__verification__guid__photo_post: { parameters: { - query?: { - gfa?: number | null; - zoning_id?: string | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + guid: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_photo_projects__pid__verification__guid__photo_post"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30097,7 +60617,7 @@ export interface operations { }; }; }; - feasibility_compare_projects__pid__feasibility_compare_get: { + verified_progress_index_projects__pid__verified_progress_get: { parameters: { query?: never; header?: { @@ -30133,11 +60653,9 @@ export interface operations { }; }; }; - precon_estimate_continuity_projects__pid__precon_estimate_continuity_get: { + verified_progress_from_layout_projects__pid__verified_progress_from_layout_post: { parameters: { - query?: { - budget?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30149,7 +60667,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30171,7 +60695,7 @@ export interface operations { }; }; }; - precon_decisions_projects__pid__precon_decisions_get: { + list_versions_projects__pid__versions_get: { parameters: { query?: never; header?: { @@ -30207,9 +60731,12 @@ export interface operations { }; }; }; - precon_assumptions_projects__pid__precon_assumptions_get: { + version_cost_delta_projects__pid__versions_cost_delta_get: { parameters: { - query?: never; + query: { + a: number; + b: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30243,10 +60770,11 @@ export interface operations { }; }; }; - precon_ve_projects__pid__precon_ve_get: { + diff_versions_projects__pid__versions_diff_get: { parameters: { - query?: { - target?: number | null; + query: { + a: number; + b: number; }; header?: { "x-user"?: string | null; @@ -30281,7 +60809,7 @@ export interface operations { }; }; }; - precon_alignment_projects__pid__precon_alignment_get: { + review_version_projects__pid__versions__version__review_post: { parameters: { query?: never; header?: { @@ -30290,12 +60818,19 @@ export interface operations { }; path: { pid: string; + version: number; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30317,11 +60852,9 @@ export interface operations { }; }; }; - precon_snapshot_projects__pid__precon_snapshot_post: { + view_templates_get_projects__pid__view_templates_get: { parameters: { - query?: { - milestone?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30336,7 +60869,7 @@ export interface operations { requestBody?: never; responses: { /** @description Successful Response */ - 201: { + 200: { headers: { [name: string]: unknown; }; @@ -30355,7 +60888,7 @@ export interface operations { }; }; }; - project_health_projects__pid__health_get: { + view_templates_put_projects__pid__view_templates_put: { parameters: { query?: never; header?: { @@ -30369,7 +60902,11 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_view_templates_put_projects__pid__view_templates_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30391,15 +60928,18 @@ export interface operations { }; }; }; - closeout_summary_projects__pid__closeout_summary_get: { + view_template_graphics_projects__pid__view_templates__tid__graphics_get: { parameters: { - query?: never; + query?: { + cut?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -30427,17 +60967,16 @@ export interface operations { }; }; }; - safety_summary_projects__pid__safety_summary_get: { + view_template_resolve_projects__pid__view_templates__tid__resolve_get: { parameters: { - query?: { - hours?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + tid: string; }; cookie?: { aec_token?: string | null; @@ -30465,7 +61004,7 @@ export interface operations { }; }; }; - field_log_summary_projects__pid__daily_reports_summary_get: { + view_alerts_projects__pid__views_alerts_get: { parameters: { query?: never; header?: { @@ -30501,7 +61040,7 @@ export interface operations { }; }; }; - rfi_register_projects__pid__rfi_register_get: { + get_vitals_projects__pid__vitals_get: { parameters: { query?: never; header?: { @@ -30537,9 +61076,11 @@ export interface operations { }; }; }; - quality_summary_projects__pid__quality_summary_get: { + warranties_expiring_projects__pid__warranties_expiring_get: { parameters: { - query?: never; + query?: { + within_days?: number; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30573,7 +61114,7 @@ export interface operations { }; }; }; - submittal_register_projects__pid__submittals_register_get: { + waterfall_scenario_projects__pid__waterfall_post: { parameters: { query?: never; header?: { @@ -30587,7 +61128,13 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30609,9 +61156,11 @@ export interface operations { }; }; }; - generate_pm_projects__pid__cmms_generate_pm_post: { + wip_schedule_projects__pid__wip_get: { parameters: { - query?: never; + query?: { + method?: string; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30645,9 +61194,11 @@ export interface operations { }; }; }; - cmms_kpis_projects__pid__cmms_kpis_get: { + wip_model_progress_projects__pid__wip_model_progress_get: { parameters: { - query?: never; + query?: { + quantity?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30681,11 +61232,9 @@ export interface operations { }; }; }; - energy_summary_projects__pid__energy_actual_get: { + work_queue_projects__pid__work_queue_get: { parameters: { - query?: { - gfa_sf?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -30719,7 +61268,7 @@ export interface operations { }; }; }; - twin_readiness_projects__pid__twin_readiness_get: { + get_workflow_projects__pid__workflow__key__get: { parameters: { query?: never; header?: { @@ -30728,6 +61277,7 @@ export interface operations { }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -30755,19 +61305,26 @@ export interface operations { }; }; }; - benchmark_status_energy_benchmark_status_get: { + put_workflow_projects__pid__workflow__key__put: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + pid: string; + key: string; + }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_workflow_projects__pid__workflow__key__put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -30789,17 +61346,16 @@ export interface operations { }; }; }; - esg_summary_projects__pid__esg_get: { + delete_workflow_projects__pid__workflow__key__delete: { parameters: { - query?: { - gfa_sf?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; path: { pid: string; + key: string; }; cookie?: { aec_token?: string | null; @@ -30827,21 +61383,14 @@ export interface operations { }; }; }; - reserve_study_projects__pid__reserves_study_get: { + re_syndication_status_re_syndication_status_get: { parameters: { - query?: { - horizon_years?: number; - opening_balance?: number; - annual_contribution?: number; - inflation_pct?: number; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -30868,19 +61417,34 @@ export interface operations { }; }; }; - fca_index_projects__pid__fca_index_get: { + ready_ready_get: { parameters: { - query?: { - crv?: number | null; - gfa_sf?: number | null; + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; }; + }; + }; + reference_authoring_matrix_reference_authoring_matrix_get: { + parameters: { + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -30907,7 +61471,7 @@ export interface operations { }; }; }; - fca_portfolio_fca_portfolio_get: { + reference_disciplines_reference_disciplines_get: { parameters: { query?: never; header?: { @@ -30941,20 +61505,14 @@ export interface operations { }; }; }; - cam_reconciliation_projects__pid__cam_reconciliation_get: { + report_catalog_reports_get: { parameters: { - query?: { - year?: number | null; - gross_up_to_pct?: number; - building_sf?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -30981,26 +61539,45 @@ export interface operations { }; }; }; - cam_statement_projects__pid__cam_statement__rid__pdf_get: { + list_rooms_rooms_get: { parameters: { - query?: { - year?: number | null; - gross_up_to_pct?: number; - building_sf?: number | null; + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; }; + }; + }; + routines_due_routines_due_post: { + parameters: { + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - rid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -31022,7 +61599,27 @@ export interface operations { }; }; }; - cde_status_projects__pid__cde_status_get: { + list_samples_samples_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + open_sample_samples__sample_id__open_post: { parameters: { query?: never; header?: { @@ -31030,21 +61627,25 @@ export interface operations { authorization?: string | null; }; path: { - pid: string; + sample_id: string; }; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody?: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_open_sample_samples__sample_id__open_post"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["ProjectOut"]; }; }; /** @description Validation Error */ @@ -31058,21 +61659,18 @@ export interface operations { }; }; }; - requirements_register_projects__pid__info_requirements_register_get: { + schedule_takt_schedule_takt_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TaktIn"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31094,19 +61692,14 @@ export interface operations { }; }; }; - requirements_cascade_projects__pid__info_requirements_cascade_get: { + schedule_takt_svg_schedule_takt_svg_get: { parameters: { - query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + query?: { + floors?: number; }; + header?: never; + path?: never; + cookie?: never; }; requestBody?: never; responses: { @@ -31130,21 +61723,18 @@ export interface operations { }; }; }; - requirements_delivery_plan_projects__pid__info_requirements_delivery_plan_get: { + schedule_takt_progress_schedule_takt_progress_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TaktProgressIn"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31166,19 +61756,14 @@ export interface operations { }; }; }; - cde_exchange_acceptance_projects__pid__cde_exchange_acceptance_get: { + scim_resource_types_scim_v2_ResourceTypes_get: { parameters: { query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; + path?: never; + cookie?: never; }; requestBody?: never; responses: { @@ -31202,21 +61787,14 @@ export interface operations { }; }; }; - openbim_quality_scan_projects__pid__openbim_quality_get: { + scim_config_scim_v2_ServiceProviderConfig_get: { parameters: { - query?: { - use_case?: string | null; - }; + query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; + path?: never; + cookie?: never; }; requestBody?: never; responses: { @@ -31240,19 +61818,18 @@ export interface operations { }; }; }; - lod_matrix_projects__pid__lod_matrix_get: { + scim_list_users_scim_v2_Users_get: { parameters: { - query?: never; + query?: { + filter?: string | null; + startIndex?: number; + count?: number; + }; header?: { - "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; - }; + path?: never; + cookie?: never; }; requestBody?: never; responses: { @@ -31276,24 +61853,25 @@ export interface operations { }; }; }; - lod_assessment_projects__pid__lod_assessment_get: { + scim_create_user_scim_v2_Users_post: { parameters: { query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; }; }; - requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -31312,19 +61890,16 @@ export interface operations { }; }; }; - naming_conventions_projects__pid__naming_conventions_get: { + scim_get_user_scim_v2_Users__user_id__get: { parameters: { query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + user_id: string; }; + cookie?: never; }; requestBody?: never; responses: { @@ -31348,24 +61923,24 @@ export interface operations { }; }; }; - naming_validate_projects__pid__naming_validate_get: { + scim_replace_user_scim_v2_Users__user_id__put: { parameters: { - query: { - name: string; - kind?: string; - }; + query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; path: { - pid: string; + user_id: string; }; - cookie?: { - aec_token?: string | null; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31387,30 +61962,25 @@ export interface operations { }; }; }; - naming_audit_projects__pid__naming_audit_get: { + scim_delete_user_scim_v2_Users__user_id__delete: { parameters: { query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + user_id: string; }; + cookie?: never; }; requestBody?: never; responses: { /** @description Successful Response */ - 200: { + 204: { headers: { [name: string]: unknown; }; - content: { - "application/json": unknown; - }; + content?: never; }; /** @description Validation Error */ 422: { @@ -31423,21 +61993,24 @@ export interface operations { }; }; }; - model_query_views_projects__pid__model_query_views_get: { + scim_patch_user_scim_v2_Users__user_id__patch: { parameters: { query?: never; header?: { - "x-user"?: string | null; authorization?: string | null; }; path: { - pid: string; + user_id: string; }; - cookie?: { - aec_token?: string | null; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31459,21 +62032,17 @@ export interface operations { }; }; }; - model_query_run_projects__pid__model_query_get: { + scope_library_list_scope_library_get: { parameters: { query?: { - view?: string | null; - group_by?: string; - agg?: string; - quantity?: string | null; + division?: string | null; + trade?: string | null; }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -31500,16 +62069,17 @@ export interface operations { }; }; }; - model_export_csv_projects__pid__model_export_csv_get: { + scope_library_exhibit_scope_library_exhibit_get: { parameters: { - query?: never; + query?: { + trade?: string | null; + clauses?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -31536,16 +62106,14 @@ export interface operations { }; }; }; - model_export_jsonld_projects__pid__model_export_jsonld_get: { + securities_syndication_status_securities_syndication_status_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -31572,16 +62140,14 @@ export interface operations { }; }; }; - model_export_parquet_projects__pid__model_export_parquet_get: { + get_integrations_settings_integrations_get: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; @@ -31608,21 +62174,23 @@ export interface operations { }; }; }; - model_columnar_stats_projects__pid__model_columnar_stats_get: { + put_integrations_settings_integrations_put: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["Body_put_integrations_settings_integrations_put"]; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -31644,23 +62212,25 @@ export interface operations { }; }; }; - model_columnar_aggregate_projects__pid__model_columnar_aggregate_get: { + test_integration_settings_integrations_test_post: { parameters: { - query?: { - group_by?: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path: { - pid: string; - }; + path?: never; cookie?: { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -31682,19 +62252,14 @@ export interface operations { }; }; }; - model_export_params_parquet_projects__pid__model_export_params_parquet_get: { + shared_page_shared__token__get: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + token: string; }; + cookie?: never; }; requestBody?: never; responses: { @@ -31718,21 +62283,22 @@ export interface operations { }; }; }; - bim_kpi_scorecard_projects__pid__bim_kpi_scorecard_get: { + shared_comment_shared__token__comment_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path: { - pid: string; + token: string; }; - cookie?: { - aec_token?: string | null; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31754,21 +62320,22 @@ export interface operations { }; }; }; - handover_acceptance_projects__pid__handover_acceptance_get: { + shared_decision_shared__token__decision_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path: { - pid: string; + token: string; }; - cookie?: { - aec_token?: string | null; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + [key: string]: unknown; + }; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31790,21 +62357,14 @@ export interface operations { }; }; }; - standards_check_projects__pid__standards_check_get: { + shared_digest_shared__token__digest_get: { parameters: { - query?: { - standard?: string; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + query?: never; + header?: never; path: { - pid: string; - }; - cookie?: { - aec_token?: string | null; + token: string; }; + cookie?: never; }; requestBody?: never; responses: { @@ -31828,21 +62388,14 @@ export interface operations { }; }; }; - bsdd_search_bsdd_search_get: { + shared_model_shared__token__model_frag_get: { parameters: { - query: { - q: string; - dictionary?: string | null; - limit?: number; - }; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; - path?: never; - cookie?: { - aec_token?: string | null; + query?: never; + header?: never; + path: { + token: string; }; + cookie?: never; }; requestBody?: never; responses: { @@ -31866,11 +62419,9 @@ export interface operations { }; }; }; - bsdd_class_bsdd_class_get: { + stamps_library_stamps_library_get: { parameters: { - query: { - uri: string; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -31902,19 +62453,18 @@ export interface operations { }; }; }; - openbim_capabilities_openbim_capabilities_get: { + structure_recommend_structure_recommend_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path?: never; - cookie?: { - aec_token?: string | null; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["StructureIn"]; }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -31936,9 +62486,11 @@ export interface operations { }; }; }; - mcp_tool_catalog_mcp_tools_get: { + list_templates_templates_get: { parameters: { - query?: never; + query?: { + module?: string | null; + }; header?: { "x-user"?: string | null; authorization?: string | null; @@ -31970,14 +62522,9 @@ export interface operations { }; }; }; - list_errors_admin_errors_get: { + create_template_templates_post: { parameters: { - query?: { - limit?: number; - source?: string | null; - level?: string | null; - since_hours?: number | null; - }; + query?: never; header?: { "x-user"?: string | null; authorization?: string | null; @@ -31987,10 +62534,14 @@ export interface operations { aec_token?: string | null; }; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["TemplateIn"]; + }; + }; responses: { /** @description Successful Response */ - 200: { + 201: { headers: { [name: string]: unknown; }; @@ -32009,14 +62560,16 @@ export interface operations { }; }; }; - clear_errors_admin_errors_delete: { + delete_template_templates__tid__delete: { parameters: { query?: never; header?: { "x-user"?: string | null; authorization?: string | null; }; - path?: never; + path: { + tid: string; + }; cookie?: { aec_token?: string | null; }; @@ -32043,23 +62596,16 @@ export interface operations { }; }; }; - report_client_error_client_errors_post: { + test_fit_compare_test_fit_compare_post: { parameters: { query?: never; - header?: { - "x-user"?: string | null; - authorization?: string | null; - }; + header?: never; path?: never; - cookie?: { - aec_token?: string | null; - }; + cookie?: never; }; requestBody: { content: { - "application/json": { - [key: string]: unknown; - }; + "application/json": components["schemas"]["TestFitIn"]; }; }; responses: { @@ -32083,14 +62629,23 @@ export interface operations { }; }; }; - health_health_get: { + test_fit_optimize_test_fit_optimize_post: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path?: never; - cookie?: never; + cookie?: { + aec_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["OptimizeIn"]; + }; }; - requestBody?: never; responses: { /** @description Successful Response */ 200: { @@ -32098,19 +62653,33 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": { - [key: string]: string; - }; + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; }; }; }; }; - ready_ready_get: { + webhook_deliveries_webhooks_deliveries_get: { parameters: { - query?: never; - header?: never; + query?: { + limit?: number; + }; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path?: never; - cookie?: never; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -32123,14 +62692,28 @@ export interface operations { "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; - prometheus_metrics_metrics_get: { + wip_portfolio_wip_portfolio_get: { parameters: { query?: never; - header?: never; + header?: { + "x-user"?: string | null; + authorization?: string | null; + }; path?: never; - cookie?: never; + cookie?: { + aec_token?: string | null; + }; }; requestBody?: never; responses: { @@ -32143,6 +62726,15 @@ export interface operations { "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; } diff --git a/docs/roadmap.md b/docs/roadmap.md index b2cd5a8d..84628774 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1114,21 +1114,57 @@ instances: silent, because somebody acts on it.* The two are now separate assertions, each mutation-checked in its own direction. -- ◧ **SCHEMA-STALE — the generated client types are half the API, and nothing regenerates them** - *(S — Lane I; found while measuring ROUTE-SHADOW's blast radius, NOT fixed here)* - - `apps/web/src/api/schema.d.ts` is produced by `npm run gen:api-types`, which reads - `src/api/openapi.json` — **a file that is not in the repository**. So the checked-in types are a - hand-run dump from an unrecorded moment, and no CI step notices them ageing. Measured 2026-09-24: - **500 paths declared against 947 the server serves**, and the two operations ROUTE-SHADOW's `/mep` - and `plan.svg` resolve to today are absent from it entirely. - - Recorded rather than fixed, and the reason is worth stating: the repair is not running the - generator. It is deciding whether `openapi.json` is checked in (a large generated artifact in every - diff) or dumped in CI (a step that needs the API importable in the web job), and then whether a - stale `schema.d.ts` should FAIL a build or be regenerated for you. That is a decision about the - build, not a gap to close on the way past. *A generated file with no producer in CI is prose with a - file extension.* +- ✅ **SCHEMA-UNGENERATED — the generated client types were never complete, because the generator's + input was a file nobody tracked** *(S — Lane I; opened while measuring ROUTE-SHADOW's blast radius, + **CLOSED 2026-09-25**; gated by `services/api/test_schema_types_agree.py`)* + + **Filed as "SCHEMA-STALE", and that name was the first thing wrong with it.** Stale implies the + file was once right. `apps/web/src/api/schema.d.ts` and the `apps/web/.gitignore` line hiding its + input were last written in the **same commit** (`8432a88`, 2026-09-11), and the app has *fewer* + route decorators today — 1016 against 1022 at that commit — so drift cannot account for a gap that + runs in the other direction. **The types were already half the API on the day they were generated.** + A stale-sounding name sends the next reader to re-run the generator, which is the one action that + could not fix it. *(The name also collided with `services/api/test_schema_stale.py`, an unrelated + gate about module-record schema versioning — two items one word apart in a 139-module tree.)* + + **Why re-running it could not fix it.** `package.json` ran + `openapi-typescript src/api/openapi.json -o src/api/schema.d.ts`, and `apps/web/.gitignore` ignores + `src/api/openapi.json`. On a fresh clone the command fails for want of an input; on a machine that + has one it regenerates from whatever dump is sitting there, **prints a green tick, and writes the + same file**. *"Regenerate the types" did not mean "read the server", and nothing said so.* + + | | | + |---|---| + | paths the app serves | **947** (1,021 operations) | + | paths `schema.d.ts` declared | **500** (541 operations) | + | operations served but undeclared | **482**, across 165 of 203 path groups | + | operations declared but no longer served | **2** | + + Fixed in three parts, because any one of them alone leaves the defect reachable. + `apps/web/scripts/gen-api-types.mjs` dumps the spec from `aec_api.main:app` into a temp file it then + deletes, so **there is no persistent input left to be stale** — and it sorts `paths` first, because + FastAPI emits them in route-registration order and including a router earlier shuffles thousands of + lines, which is much of why "regenerating churned 38,231 lines" had become a reason not to. + `schema.d.ts` is regenerated, by that script rather than by hand, and asserted byte-identical across + two runs. And `services/api/test_schema_types_agree.py` asserts every live `(path, method)` is + declared — because a generator can only be *run*, and nothing makes anyone run it. + + **The reason it went unseen for a fortnight is worth more than the fix.** Seven audits in this tree + exempt `schema.d.ts` by name — `deadFieldScope`, `docComments`, `unfiledMap`, `deadFieldTyped`, + `noRespelledShapes`, `test_route_reachability`, `test_file_sizes` — each for a good reason of its + own. *Seven exemptions and no owner is how an artifact stops being checked by anybody.* And + `test_route_reachability`'s is the sharpest: it once counted this file as client code, so **29 + routes were "called" by a generated file restating the server's own route table.** That is the + opposite error — treating the artifact as evidence about the *client*. It is only ever a claim about + the *server*, and nothing was checking the claim. + + *(The entry this replaces said the repair was "a decision about the build, not a gap to close on the + way past" — whether `openapi.json` is committed or dumped in CI. That framing was wrong in a way + that mattered: it presented a real question as a **blocking** one, and the fix needed neither + answer. The input does not have to be committed **or** produced in CI if it does not persist, and + the agreement check needs no node and no artifact passing between jobs — it reads the committed + `schema.d.ts` and the live app in the one job that already imports it. *An item parked on a decision is + parked until somebody re-derives whether the decision was load-bearing.*)* - ✅ **RESPELLED-SHAPE — a wire type declared twice is invisible to every audit over the declarations** *(XS — Lane I; **CLOSED 2026-09-24**; gated by @@ -1286,6 +1322,49 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **RESIDUAL-DARK — the developer's actual question was built, routed, tested and unreachable** + *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/residualLandCard.test.ts` and the `UNCALLED` deletion in + `apps/web/src/api/clientCallers.test.ts`)* + + Every other figure on the pro forma runs **forward** from a land price somebody typed. Site + acquisition is the one number a developer negotiates, and the question at the table is *"what is the + most I can pay for the dirt and still clear my hurdle"* — a different solve, not a re-reading of the + same one. FIN-CALC shipped that solve: `services/api/src/aec_api/proforma/residual.py` bisects the + land line over the same forward `solve()` every other number comes from, the route serves it, + `services/api/test_fin_calc.py` covers it, and `ApiClient.residualLand()` was written for it. + **No screen called it.** Same shape as DISC-poché and the MEP systems browser. + + **The substance of the fix is the three caveats, not the number.** `residual_land_value` is careful + in exactly the way a panel throws away, which is this document's SCREEN-VS-REPORT axis: + `land_value: null` means the target is unreachable **even at $0 land** (printing a figure would be + inventing one; the engine's own note says *"the deal, not the dirt"*); `converged: false` **with** a + figure means the bisection hit its cap, so that figure is a bracket endpoint rather than a price; and + `bounds` — the honest answer in that second case — **was declared nowhere in this client**, so no + unread-field audit here could see it, every one of them starting from the declared interfaces. All + three are rendered, one `it` each, because one test over three caveats passes when two of them work. + + **A second finding came out of the write-back.** `residual.py::_with_land` scales the **first** + `category: "land"` line and zeroes any others, so the residual is the *total* land basis; the driver + form's "Land $" field is bound to `cost_lines.0.amount`, which is true of the default assumption set + and not of one adopted from the massing tab. Writing the answer under the looser definition would + leave a second land line standing, and the forward re-solve would then carry more land than the + answer allowed for — *an inverse answer applied under a different definition than the one it was + solved under stops reconciling with the deal it came from*, silently, because every visible number + still balances. + + **It also found a live defect in an existing gate, in the direction that costs the most.** The XSS + source pin in `apps/web/src/proforma/proforma.render.test.ts` required the literal spelling + `escapeHtml((e as Error).message)`, and this directory imports the escaper under two names — + so a line escaping **correctly** under the conventional alias was reported as an unescaped sink. + *A check keyed on the local NAME cannot see a call that is about the BINDING*, and this was not a + miss but a confident accusation about a safe line, telling its author they had an XSS. Renaming the + import to suit the regex was the tempting repair and the wrong one: the convention it would bend to + is the regex's, not the directory's. The escaper resolves through the file's own import now — and the + pin additionally could not tell *"no offenders"* from *"nothing to offend"*, asserting an empty list + with no floor on either side, so it now proves it found files and is still looking at sinks before it + may report none unescaped. + - ✅ ⭐ **PIN-POPULATION — the pin engine read 5 registers of 37, and six of its names were not modules** *(M — Lane C; **CLOSED**, fix in this change; gated by `services/api/test_pin_population.py`, with the coverage half in `services/api/test_pin_empty.py`)* @@ -4001,7 +4080,7 @@ two rows share a path, so two agents in different rows cannot collide. | **F · Docs & demo** | `README.md`, `docs/`, `apps/web/src/demo/` | keep the shipped surface honest (below) — no coded items. **`demoData.test.ts` now gates the shell's startup endpoints**; re-run `build_demo_data.py` and that test after adding one | | **G · API surface** | `services/api/src/aec_api/routers/`, `main.py` | *(empty again — RMW-LOCKGAP closed 2026-09-14, RMW-TOKEN 2026-09-23; RFQ-IDEMPOTENT shipped 2026-09-13.)* **Both concurrency items needed a column in `models.py`, which is Lane C's, and neither turned out to need one** — they closed with a lock this lane's own files already imported. *A lane assignment that reserves a crossing for work the entry has not yet costed is a prediction, and this cell carried two of them.* It also once carried RFQ-IDEMPOTENT because the fix looked like it sat entirely inside a router's transaction boundary. **It did not**: the duplicate was a router ordering bug, but the race under it was in `modules.transition`, which is Lane C. *A lane assignment made from where a defect SHOWS is wrong whenever the cause is one layer down.* **The sentence below was true until 2026-09-11 and is kept because it still explains the lane's shape**: every lane normally routes its own work, which is why this is a lane rather than a shared file. | | **H · Registers** | `services/api/modules/*/module.json` | — | -| **I · API client** | `apps/web/src/api/` | SCHEMA-STALE *(open — `schema.d.ts` declares 500 of the 947 paths the server serves, and its generator reads an `openapi.json` that is not in the repository. Filed here because the artifact lives in this directory; **the repair is a build decision, not a file edit** — whether `openapi.json` is committed or dumped in CI, and whether a stale `schema.d.ts` fails a build or is regenerated for you)* · RESPONSE-UNDECLARED *(**CLOSED 2026-09-11** — all 15 gaps declared and rendered, `KNOWN_GAPS` is empty and asserted empty. The finding came from `services/api/src/aec_api/routers/`, which is Lane G's, but the WORK is declaring the field on the client interface so something can read it — lanes go by the directory the work touches, the correction Lane E's cell already had to make. Rendering the newly-declared field afterwards is Lane B's)* · SCALE-SEAM *(the only open slice; ②–⓾ plus (81)–(91) have shipped. **Deliberately carries NO mark**: ⓾ is the last glyph in `MARKS` and the double-circled range it closes has no successor, so the next slice cannot be numbered at all — writing a mark the parser does not know would drop the item out of this table's own population.)* *(this cell said (81)–(87) until 2026-09-04, four slices after (88) landed — the same drift its own history below records, in the same cell, for the third time. It named ⓽ until 2026-09-03; ②–⓼ had shipped. This cell named ⑬–⑳ until 2026-08-24 — eight slices whose extractions had already landed — because the item regex could not see `㉒` at all, so nothing required this row to be right)* | +| **I · API client** | `apps/web/src/api/` | SCHEMA-UNGENERATED *(**CLOSED 2026-09-25** — `schema.d.ts` declared 500 of the 947 paths the server serves and had done since the day it was generated, because `gen:api-types` read a gitignored `openapi.json`, so "regenerate" did not mean "read the server". The generator now dumps from the app into a temp file it deletes, and `services/api/test_schema_types_agree.py` asserts every live (path, method) is declared. **Filed as SCHEMA-STALE, and that name was the first thing wrong with it** — stale implies it was once right, and it sent the reader at the one action that could not fix it)* · RESPONSE-UNDECLARED *(**CLOSED 2026-09-11** — all 15 gaps declared and rendered, `KNOWN_GAPS` is empty and asserted empty. The finding came from `services/api/src/aec_api/routers/`, which is Lane G's, but the WORK is declaring the field on the client interface so something can read it — lanes go by the directory the work touches, the correction Lane E's cell already had to make. Rendering the newly-declared field afterwards is Lane B's)* · SCALE-SEAM *(the only open slice; ②–⓾ plus (81)–(91) have shipped. **Deliberately carries NO mark**: ⓾ is the last glyph in `MARKS` and the double-circled range it closes has no successor, so the next slice cannot be numbered at all — writing a mark the parser does not know would drop the item out of this table's own population.)* *(this cell said (81)–(87) until 2026-09-04, four slices after (88) landed — the same drift its own history below records, in the same cell, for the third time. It named ⓽ until 2026-09-03; ②–⓼ had shipped. This cell named ⑬–⑳ until 2026-08-24 — eight slices whose extractions had already landed — because the item regex could not see `㉒` at all, so nothing required this row to be right)* | | **J · Build & tooling** | `apps/web/scripts/`, `apps/web/vite.config.ts`, `apps/web/src/style.css`, `apps/web/src/tooling/`, `services/api/test_file_sizes.py`, `services/api/run_tests.py` | R39-TSC-CACHE *(local typecheck once diverged from CI; cause unknown, prior explanation retracted — an OBSERVATION, not a defect with a known fix. Read the entry before "fixing" it: the proposed fix is named there and rejected)* · DESKTOP-CONVERT-TIMEOUT *(**CLOSED 2026-09-24** — a COOPERATIVE deadline bounds the Python path at its phase boundaries and inside both loops, and the conversion now runs in a CHILD PROCESS the parent kills, which covers the two costs no checkpoint follows: `ifcopenshell.open`, and a single `create_shape` that never returns. **This row twice recorded a wrong REASON for the item being blocked** — first "process/packaging shape", which cost 13 days, then `multiprocessing`-under-PyInstaller, which cost another fortnight. Neither was true of process isolation as such: `services/api/desktop_entry.py` is the `Analysis` script for both specs, so an argv sentinel re-entering `sys.executable` serves frozen and unfrozen alike. *A blocker recorded as a property of the platform turned out to be a property of one approach to it.* Gated by `services/api/test_fragconvert_timeout.py` and `services/api/test_fragconvert_kill.py`)* | **Parked — not available to pick up.** These are decisions or multi-release commitments, listed so diff --git a/services/api/run_tests.py b/services/api/run_tests.py index 39e0970c..eb8011bb 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -92,7 +92,7 @@ # R23-DIGEST — the deterministic model digest and its two routes: "test_model_digest", "test_digest_route", # observability (error alerting + distributed tracing) — env-gated, no-op when unconfigured: - "test_sentry", "test_otel", "test_doctext_source", "test_docs_module_schema", "test_schema_stale", "test_schema_strictness", + "test_sentry", "test_otel", "test_doctext_source", "test_docs_module_schema", "test_schema_stale", "test_schema_strictness", "test_schema_types_agree", # public docs are a shipped surface: competitor names for interop only, never comparison: "test_no_comparative_names", # this list is itself hand-maintained, so it gets a test of its own: diff --git a/services/api/test_roadmap_status.py b/services/api/test_roadmap_status.py index f4640e41..bd48c0f6 100644 --- a/services/api/test_roadmap_status.py +++ b/services/api/test_roadmap_status.py @@ -79,6 +79,14 @@ def _gate_says(script: str, needle: str) -> bool: "test_vendor_reachable reports 0 unreached vendored modules"), "R37-TRIAGE": (lambda: _gate_says("test_dead_code_population.py", "0 unreferenced"), "test_dead_code_population reports 0 unreferenced public functions"), + #: Registered by the pull request that CLOSED it, which is the thing `test_gap_records` exists + #: because nothing forces: *a registry reports on what it contains, and its silence is + #: indistinguishable from a clean bill.* The needle deliberately avoids the path and operation + #: counts in the same verdict line — those move on every route added, and a needle that decays + #: with normal work is one somebody loosens rather than reads. + "SCHEMA-UNGENERATED": ( + lambda: _gate_says("test_schema_types_agree.py", "all declared, 0 declared-but-gone"), + "test_schema_types_agree reports every served (path, method) declared in schema.d.ts"), } #: Genuinely open, with a measurement that should say so. The negative control: without it, a bug @@ -168,8 +176,12 @@ def _marked_open_on(text: str, code: str = "SYNTH-CODE") -> bool: check(f"{code} is open and its measurement still says so", still_open(), f"{what} (negative control: proves a False predicate is not how this passes)") -print(("ROADMAP-STATUS OK — every item with a measurement agrees with its marker. " - "The four found by hand on 2026-08-29 (R46, R37-TRIAGE, R37-TESTED-UNWIRED, QTO-TRADE) are " - "corrected; the two that HAVE a gate are now held by it.") +# The counts are DERIVED, not written. This line said "the two that HAVE a gate" and was wrong the +# moment a third registered itself — the narrative-copy-of-a-number drift CLAUDE.md names for +# `viewer/app.ts`, reproduced inside the gate whose whole subject is markers that stopped being true. +print((f"ROADMAP-STATUS OK — every item with a measurement agrees with its marker. " + f"The four found by hand on 2026-08-29 (R46, R37-TRIAGE, R37-TESTED-UNWIRED, QTO-TRADE) are " + f"corrected; {len(DONE_WHEN)} closed item(s) and {len(OPEN_WHEN)} open one(s) are held by a " + f"gate here — {', '.join(sorted(DONE_WHEN))} closed, {', '.join(sorted(OPEN_WHEN))} open.") if not FAILED else f"roadmap_status: {len(FAILED)} FAILED — {FAILED}") sys.exit(1 if FAILED else 0) diff --git a/services/api/test_schema_types_agree.py b/services/api/test_schema_types_agree.py new file mode 100644 index 00000000..fedf628a --- /dev/null +++ b/services/api/test_schema_types_agree.py @@ -0,0 +1,420 @@ +"""Does the committed `apps/web/src/api/schema.d.ts` describe the API this server actually serves? + +`apps/web/src/api/schema.d.ts` is generated from the FastAPI spec by `openapi-typescript` and is +**committed**, so it is a claim about the server checked in beside the client. Nothing was checking +the claim, and it was false by a wide margin on the day it was written. + +MEASURED 2026-09-25, before this gate existed: + + paths the app serves 947 + paths `schema.d.ts` declared 500 + missing 448, scattered across 165 of 203 path groups + declared but no longer served 1 + +**It is NOT staleness, and the name it was filed under ("SCHEMA-STALE") pointed the next reader at +the one action that could not fix it.** Stale implies it was once right. `schema.d.ts` and the +`apps/web/.gitignore` line hiding its input were last written in the SAME commit (`8432a88`, +2026-09-11), and the app has *fewer* route decorators today — 1016 against 1022 at that commit — so +drift cannot account for a 448-path gap in the direction it runs. The types were already half the +API when they were generated. Re-running the generator as documented would have reproduced the same +file, because: + +**The generator's input was a file nobody tracked.** `package.json` ran +`openapi-typescript src/api/openapi.json -o src/api/schema.d.ts`, and `apps/web/.gitignore` ignores +`src/api/openapi.json`. On a fresh clone the command fails for want of an input; on a machine that +has one it regenerates from whatever dump is sitting there, prints a green tick and writes the same +stale file. *"Regenerate the types" did not mean "read the server", and nothing said so.* Fixed in +the same change: `apps/web/scripts/gen-api-types.mjs` dumps the spec from `aec_api.main:app` into a +temp file it deletes, so there is no persistent input left to be stale. + +WHY A GATE AND NOT JUST THE GENERATOR FIX + A generator can only be *run*. Nothing makes anyone run it, and the failure this file exists to + catch is precisely the one that produces no symptom: a route added on the Python side, no type + error anywhere, `npm run build` green, and a committed file that quietly describes a different + server. **The gap was invisible for a fortnight because every audit in the tree exempts this file + by name** — `deadFieldScope`, `docComments`, `unfiledMap`, `deadFieldTyped`, `noRespelledShapes`, + `test_route_reachability` and `test_file_sizes` all skip it, each for a good reason of its own. + Seven exemptions and no owner is how an artifact stops being checked by anybody. + + (`test_route_reachability`'s exemption is the sharpest of the seven and worth reading beside this + one: it used to count `schema.d.ts` as client code, so **29 routes were "called" by a generated + file restating the server's own route table**. That is the opposite error — treating the artifact + as evidence *about* the client. This gate treats it as a claim about the SERVER, which is the one + thing it genuinely is.) + +WHY THIS GATE LIVES IN PYTHON, IN `services/api` + It needs the live app, which means the backend venv. It does NOT need node: the declared set is + read out of the committed `schema.d.ts` by parsing it, so nothing has to be generated, no artifact + passes between CI jobs, and `api-tests` — which already imports the app — is the only job + involved. + +HOW IT FAILS CLOSED + Every path group in the file must parse into a set of method verdicts. A group whose body the + parser cannot classify raises rather than being skipped, because the two blind spots this + repository has paid for twice were both *a predicate deciding what to LOOK at* — and everything + such a predicate excludes is invisible to its own output, so the count looks complete. + + Five preconditions run BEFORE any verdict is printed, one per way of being silently wrong: + + 1. The parse reaches. A floor on paths and on declared operations, on both sides, so a parser + that matched nothing cannot report a clean tree. + 2. Deleting a whole path group from a copy must be FOUND as missing. + 3. Flipping one `get: operations[...]` to `get?: never` must be FOUND as missing — the + method-level arm. A path-level check alone passes a file that declares the URL and none of + its verbs, which is the shape a partially-regenerated file actually has. + 4. Removing a verb LINE must be REFUSED rather than parsed into a smaller set, because a + silently-narrowed group is reported as an absent declaration, which reads as "regeneration + due" rather than "the parser no longer understands this file". + 5. No route may be registered under an `if`. The whole comparison assumes the app serves the + same routes here, in CI and on the machine that last ran the generator — and that + assumption is a precondition, so it is asserted rather than believed. The scanner is shown + finding a synthetic gated registration first, because a check whose expected answer is zero + is the easiest kind to break silently. + + Mutation 3 is not a hypothetical: `openapi-typescript` emits all eight HTTP verbs for every path + and marks the unserved ones `?: never`, so "declared" and "present in the file" are different + questions and only one of them is the right one. + +Run: PYTHONPATH="src;../data/src" ./.venv/Scripts/python.exe test_schema_types_agree.py +""" +import ast +import os +import re +import sys +from pathlib import Path + +sys.path.insert(0, "src") + +os.environ["DATABASE_URL"] = "sqlite:///./_schema_types.db" +os.environ.setdefault("STORAGE_DIR", "./_schema_types_store") + +from aec_api.main import app # noqa: E402 + +FAILED: list[str] = [] + + +def check(label, ok, detail=""): + print(f"{'PASS' if ok else 'FAIL'} {label}{(' — ' + str(detail)) if detail and not ok else ''}") + if not ok: + FAILED.append(label) + + +HERE = Path(__file__).resolve().parent +SCHEMA = HERE.parents[1] / "apps" / "web" / "src" / "api" / "schema.d.ts" + +#: The verbs OpenAPI (and so the generated file) can key a path group on. +_VERBS = ("get", "put", "post", "delete", "options", "head", "patch", "trace") + +#: There is deliberately NO carve-out for head/options/trace. An earlier draft excluded them from +#: both sides as "verbs nobody writes" — measured: the live spec emits 0 of all three. So the +#: exclusion bought nothing and silently shrank the population, which is how *a list of known cases +#: becomes a list somebody stopped widening*: an explicit `@router.head` would be skipped on both +#: sides, and a missing declaration for it would read as a clean tree. All eight are compared. + + +class Unparsable(Exception): + """Raised when a path group cannot be ruled on. Never caught into a skip.""" + + +def declared(src: str) -> dict[str, set[str]]: + """`{path: {VERB, ...}}` — every (path, method) the committed `schema.d.ts` says the server serves. + + A verb counts as DECLARED only when it maps to an `operations[...]` entry. `get?: never` is the + generator saying *this path does not answer GET*, which is a claim to be checked, not an absence + to be ignored — so parsing "does the word `get` appear in this group" would pass a file that + declares all 947 URLs and not one of their verbs. + """ + m = re.search(r"^export interface paths \{\n(.*?)^\}$", src, re.S | re.M) + if not m: + raise Unparsable("no `export interface paths` block — the generator's output shape changed") + body = m.group(1) + + out: dict[str, set[str]] = {} + # Groups are emitted at exactly four spaces of indent, opening on the quoted path and closing on + # a ` };` line. Anchoring on the indent is what makes the group boundary unambiguous without a + # brace counter that would have to understand string literals inside the doc comments. + for gm in re.finditer(r'^ "([^"]+)": \{\n(.*?)^ \};$', body, re.S | re.M): + path, group = gm.group(1), gm.group(2) + if path in out: + raise Unparsable(f"path {path!r} appears twice in the generated file") + verbs: set[str] = set() + for verb in _VERBS: + # One line per verb, at eight spaces, either `verb: operations["id"];` or `verb?: never;`. + hit = re.search(rf'^ {verb}(\?)?: (operations\["[^"]+"\]|never);$', group, re.M) + if hit is None: + raise Unparsable( + f"path {path!r} declares no line for {verb!r} — the generator emits all eight " + f"verbs for every group, so this file was hand-edited or the shape changed" + ) + if hit.group(2) != "never": + verbs.add(verb.upper()) + out[path] = verbs + + # DERIVE THE POPULATION *AND* PROVE THE DERIVATION REACHES IT. `finditer` yields nothing for a + # group whose shape it cannot match, silently — and a path absent from `out` is reported below as + # "served but undeclared", which reads as *regenerate* rather than *the parser broke*. Counting + # the path keys independently of the group bodies is what makes those two outcomes distinguishable. + # + # The key count is derived by a DELIBERATELY CRUDER shape than the group match — any line at four + # spaces opening a quoted key. A first draft counted `^ "…": \{$`, which fails in exactly the + # same way the group regex does (both anchor the brace at end of line), so a mutation that broke + # one broke the other and the counts stayed equal: *a parity check between two derivations that + # share a failure mode is not a check.* Verified by mutation — trailing whitespace after the brace + # defeats the strict pair and is caught by this one. + keys = re.findall(r'^ "([^"]+)":', body, re.M) + if len(keys) != len(out): + missed = sorted(set(keys) - set(out)) + raise Unparsable( + f"{len(keys)} path keys but {len(out)} groups parsed — the group regex did not match " + f"{missed[:5]}. Every unmatched group would be reported as an undeclared path, so this " + f"is a parser failure wearing the costume of a stale file") + return out + + +def live() -> dict[str, set[str]]: + """`{path: {VERB, ...}}` from the app's own OpenAPI document — the same document the generator reads.""" + out: dict[str, set[str]] = {} + for path, group in app.openapi()["paths"].items(): + out[path] = {v.upper() for v in group if v in _VERBS} + return out + + +class _CondScan(ast.NodeVisitor): + """Collects route registrations that sit under an `if`, in one module's AST.""" + + _VERBS = ("get", "post", "put", "patch", "delete", "api_route") + _CALLS = ("include_router", "add_api_route") + + def __init__(self, label: str): + self.label = label + self.depth = 0 + self.hits: list[str] = [] + + def visit_If(self, node): + self.depth += 1 + self.generic_visit(node) + self.depth -= 1 + + def visit_FunctionDef(self, node): + if self.depth: + for dec in node.decorator_list: + src = ast.unparse(dec) + if any(f".{v}(" in src for v in self._VERBS): + self.hits.append(f"{self.label}:{node.lineno} {src[:60]}") + self.generic_visit(node) + + visit_AsyncFunctionDef = visit_FunctionDef + + def visit_Call(self, node): + if (self.depth and isinstance(node.func, ast.Attribute) + and node.func.attr in self._CALLS): + self.hits.append(f"{self.label}:{node.lineno} {ast.unparse(node)[:60]}") + self.generic_visit(node) + + +def conditional_sites(src: str, label: str) -> list[str]: + """Route registrations under an `if` in `src` — i.e. a route set that depends on the environment. + + **This is a precondition, not a style rule, and it is written down because the alternative is + believing it.** The comparison below assumes the app serves the same routes here, in CI and on the + machine that last ran the generator. One `if settings.FEATURE:` around a decorator breaks that, and + it breaks the expensive way: this gate would red with *"N served but undeclared — regenerate"*, a + reader would regenerate, and the file would then disagree in the other direction on the next + machine. *A check whose failure message can misdiagnose is worse than one that stays silent, + because somebody acts on it.* + + Takes SOURCE rather than a path so the self-test below can hand it a synthetic module: a scanner + that can only read the real tree, whose expected answer is zero, is the easiest kind of check to + break silently. + """ + scan = _CondScan(label) + scan.visit(ast.parse(src)) + return scan.hits + + +def conditionally_registered() -> tuple[list[str], int]: + """`(sites, modules_scanned)` over every module in `src/aec_api`. + + Measured 0 sites on 2026-09-25, so this costs no exemptions today — and if it ever stops being 0, + the repair is a named env axis in this file, not a looser comparison. + + **The module count is returned, not discarded, and the tree is anchored on `__file__`.** A bare + `Path("src")` resolves against the working directory: `run_tests.py` sets `cwd=services/api` so it + would have worked under the runner, and `ci.yml` invokes `python services/api/run_tests.py` from + the repo ROOT, where the same literal names nothing. `rglob` over a missing directory yields + nothing and raises nothing, so the precondition would have reported a clean tree from the wrong + question — the failure `test_scratch_ignored` paid for twice, asked from `services/` instead of the + repo root. *A wrong question returns a confident number.* So the count is asserted against a floor + beside the verdict, and the path cannot depend on where anybody stood. + """ + out: list[str] = [] + n = 0 + for path in sorted((HERE / "src" / "aec_api").rglob("*.py")): + try: + out += conditional_sites(path.read_text(encoding="utf-8"), str(path.relative_to(HERE))) + except SyntaxError: # pragma: no cover — a syntax error reds the suite elsewhere + continue + n += 1 + return out, n + + +def disagreement(decl: dict[str, set[str]], srv: dict[str, set[str]]) -> tuple[list[str], list[str]]: + """`(missing, extra)` as `"VERB path"` strings — served-but-undeclared, declared-but-not-served.""" + missing = [f"{v} {p}" for p, vs in sorted(srv.items()) for v in sorted(vs - decl.get(p, set()))] + extra = [f"{v} {p}" for p, vs in sorted(decl.items()) for v in sorted(vs - srv.get(p, set()))] + return missing, extra + + +def _sample(xs, n=12): + """Cap a failure detail. A 39 KB message is one nobody reads to the end — measured: the first run + of this gate against the file as shipped printed every one of 481 undeclared operations twice.""" + xs = sorted(xs) + return ", ".join(xs[:n]) + (f" … and {len(xs) - n} more" if len(xs) > n else "") + + +# --------------------------------------------------------------------------------------------- +# PRECONDITIONS — each is one way this gate could report a clean tree while seeing nothing. + +if not SCHEMA.is_file(): + print(f"FAIL the generated types are at {SCHEMA} — file not found") + sys.exit(1) + +_SRC = SCHEMA.read_text(encoding="utf-8") +_DECL = declared(_SRC) +_LIVE = live() +_DECL_OPS = sum(len(v) for v in _DECL.values()) + +check(f"the parse reaches the generated file ({len(_DECL)} paths, {_DECL_OPS} declared operations)", + len(_DECL) >= 900 and _DECL_OPS >= 950, + f"{len(_DECL)} paths / {_DECL_OPS} operations parsed out of {SCHEMA.name} — below the floor, so " + "a parser that matched nothing would report this tree clean") + +check(f"the app's own spec reaches ({len(_LIVE)} paths, {sum(len(v) for v in _LIVE.values())} operations)", + len(_LIVE) >= 900 and sum(len(v) for v in _LIVE.values()) >= 950, + "the live spec came back implausibly small — the comparison below would be vacuous") + +# Mutation 1 — a whole path group deleted must be FOUND. Picks the subject from the file rather than +# naming one, so a route legitimately renamed cannot break this precondition with a message that +# blames the wrong cause. +_VICTIM = next(p for p, vs in sorted(_LIVE.items()) if vs and p in _DECL) +_CUT = {p: vs for p, vs in _DECL.items() if p != _VICTIM} +_CUT_MISSING, _ = disagreement(_CUT, _LIVE) +check(f"a DELETED path group is found ({_VICTIM})", + sorted(_CUT_MISSING) == sorted(f"{v} {_VICTIM}" for v in _LIVE[_VICTIM]), + f"deleting {_VICTIM} from the declared set reported {_sample(_CUT_MISSING) or 'nothing'} — " + "the comparison is not looking at paths") + +# Mutation 2 — the method-level arm. A path declared with its verbs turned off must be FOUND, or a +# half-regenerated file passes: `openapi-typescript` writes every URL and marks unserved verbs +# `?: never`, so URL presence is not the question. +_VERB = sorted(_LIVE[_VICTIM])[0] +_FLIPPED = dict(_DECL) | {_VICTIM: _DECL[_VICTIM] - {_VERB}} +_FLIP_MISSING, _ = disagreement(_FLIPPED, _LIVE) +check(f"a verb flipped to `never` is found ({_VERB} {_VICTIM})", + _FLIP_MISSING == [f"{_VERB} {_VICTIM}"], + f"turning off {_VERB} {_VICTIM} reported {_sample(_FLIP_MISSING) or 'nothing'} — the " + "comparison is path-level, so a file declaring every URL and no verb would pass") + +# Mutation 3a — the group-count parity arm. A path group whose SHAPE the group regex cannot match is +# absent from `declared()` silently, and would be reported as "served but undeclared" — a parser +# failure wearing the costume of a stale file. Two shapes, because the first draft of the parity check +# counted keys with `^ "…": \{$` and so failed in exactly the same way the group regex does: the +# trailing-whitespace mutation broke both and the counts stayed equal. *A parity check between two +# derivations that share a failure mode is not a check.* +_SHAPE_MUTANTS = { + "trailing whitespace after a group's opening brace": + (f' "{_VICTIM}": {{\n', f' "{_VICTIM}": {{ \n'), + "a group's closing brace indented one space too far": + (" trace?: never;\n };\n", " trace?: never;\n };\n"), +} +for _name, (_a, _b) in _SHAPE_MUTANTS.items(): + _m = _SRC.replace(_a, _b, 1) + _applied = _m != _SRC + try: + declared(_m) + _refused = False + except Unparsable: + _refused = True + check(f"a group the regex cannot match is REFUSED — {_name}", + _applied and _refused, + "the mutation did not apply, so this proves nothing" if not _applied else + "the parser returned a SMALLER set instead of refusing — every group it silently drops is " + "reported as an undeclared path, which reads as 'regenerate' rather than 'the parser broke'") + +# Mutation 3 — the parser must REFUSE a group it cannot rule on, rather than returning a smaller set. +# `declared()` raising is the whole of failing closed; a parser that skipped the group would simply +# report that path as missing everything, which reads like a regeneration being due. +_MANGLED = re.sub(rf'^ {_VERB.lower()}(\?)?: [^\n]+$', "", _SRC, count=1, flags=re.M) +try: + declared(_MANGLED) + _REFUSED = False +except Unparsable: + _REFUSED = True +check("a group missing a verb line is REFUSED, not silently narrowed", + _REFUSED, + "the parser accepted a path group with a verb line removed — an unparsable group would be " + "reported as an absent declaration, which reads as 'regeneration due' rather than 'the parser " + "no longer understands this file'") + +# Precondition 4 — the route set must not depend on the environment, or the comparison is between two +# different apps and its failure message would send a reader to regenerate for the wrong reason. +_SYNTH_COND = ''' +if flag: + @router.get("/x") + async def x(): ... + app.include_router(other) +''' +_SYNTH_PLAIN = ''' +@router.get("/x") +async def x(): ... +app.include_router(other) +''' +check("SELF-TEST: the scanner finds a route registered under an `if`…", + len(conditional_sites(_SYNTH_COND, "synth")) == 2, + f"a synthetic module with a gated decorator AND a gated include_router reported " + f"{conditional_sites(_SYNTH_COND, 'synth')} — a scanner whose expected answer is zero is the " + "easiest kind to break silently, so it must be shown finding one") + +check("SELF-TEST: …and passes the same two registered unconditionally", + conditional_sites(_SYNTH_PLAIN, "synth") == [], + f"flagged {conditional_sites(_SYNTH_PLAIN, 'synth')} — the scanner is matching the " + "registration, not the `if`") + +_COND, _MODULES = conditionally_registered() +check(f"the conditional-registration scan reached the package ({_MODULES} modules)", + _MODULES >= 100, + f"only {_MODULES} module(s) under {HERE / 'src' / 'aec_api'} — `rglob` over a missing directory " + "yields nothing and raises nothing, so the check below would report a clean tree from the wrong " + "question") + +check("no route is registered under an `if`, so the served set is the same here and in CI", + not _COND, + f"{len(_COND)} conditional registration(s): {'; '.join(_COND[:6])} — the committed types can " + "only agree with ONE route set, so an env-gated route makes this gate's verdict a fact about " + "this machine. Name the env axis here rather than loosening the comparison") + +# --------------------------------------------------------------------------------------------- +# THE VERDICT + +_MISSING, _EXTRA = disagreement(_DECL, _LIVE) + + +check(f"every served (path, method) is declared in schema.d.ts ({sum(len(v) for v in _LIVE.values())} operations)", + not _MISSING, + f"{len(_MISSING)} served but undeclared: {_sample(_MISSING)} — the committed types describe a " + "different server than the one this branch runs. Regenerate with `npm run gen:api-types` from " + "apps/web (it reads the app, not a file) and commit the result") + +check("no declared (path, method) has stopped being served", + not _EXTRA, + f"{len(_EXTRA)} declared but gone: {_sample(_EXTRA)} — a client typed off these would compile " + "against routes that 404. Regenerate with `npm run gen:api-types` from apps/web") + +print() +if FAILED: + print(f"schema_types_agree: {len(FAILED)} FAILED — {FAILED}") + sys.exit(1) +print( + f"schema_types_agree: all checks passed — {len(_LIVE)} paths / " + f"{sum(len(v) for v in _LIVE.values())} operations served, all declared, 0 declared-but-gone" +) From 43cd6966621d869f682d2b29582f4dcd502d79ad Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 03:32:18 +0000 Subject: [PATCH 03/21] RENTROLL-DARK: two engines that say whether you can believe the rent roll, and no screen called either MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pro forma's Operations tab shows FACE numbers: base rent, in-place income, occupancy, WALT. Face rent is what a broker quotes. Two R20 engines shipped to qualify it and neither was ever called — ApiClient.netEffectiveRent() and ApiClient.rentRollScrub() both sat in clientCallers.test.ts's UNCALLED list, reachable only from a script. Same shape as RESIDUAL-DARK and the MEP browser. BOTH ENGINES ARE BUILT AROUND A REFUSAL, AND THE REFUSAL IS WHAT A PANEL DESTROYS. That, not the numbers, is what this change is about and what its fourteen tests pin. * `clean` is never rendered as a clean rent roll. rent_scrub.py computes it as `bool(ran) and not failed`, so ONE check running and passing with six unable to run is clean: true. Its own docstring names what a green tick would be: "a scrub that reports 'no findings' because half its inputs were missing is worse than no scrub — it launders absent data into apparent confidence." Coverage is the headline; the flag qualifies it. * The NER totals cover a smaller population than the card above them. net_effective.roll_up filters to the computable leases BEFORE summing, and lease_count is that subset's size — so with any skipped_count > 0 the Face GPR here is a different set from the rent roll's "Base rent / yr" three lines above, on one screen, inviting a subtraction. Said out loud, with the skipped leases and their reasons. * The skipped list can be a page. `skipped` is capped at 50 server-side while skipped_count is not — the CLASH-TRUNC shape, a screen reporting over a partial view and calling it complete. The card says "Showing 50 of 120". * No leasing commission means the optimistic case. lc_included is false unless a rate is supplied and the engine never invents one, so landlord costs are understated and both NERs are high. An absent input must not read as a complete answer. * Zero computable leases renders as a refusal, not as $0. A total of zero and an absence of a total are different claims. The scrub card's not-run table names what each blocked check NEEDS, which is the actionable half: it says which document to go and get, rather than leaving the gap as an absence of green ticks. Six mutations, each redding exactly one test: rendering clean as clean, dropping the population warning, presenting the skipped page as the whole set, dropping the commission caveat, rendering zeros as a valuation, and dropping what a blocked check needs. Verified: tsc --noEmit clean, eslint clean, npm run build clean, 266 files / 2,875 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 48 ++++ apps/web/src/api/clientCallers.test.ts | 6 +- apps/web/src/proforma/proforma.ts | 5 + apps/web/src/proforma/rentRollQuality.test.ts | 187 +++++++++++++++ apps/web/src/proforma/rentRollQuality.ts | 213 ++++++++++++++++++ docs/roadmap.md | 28 +++ 6 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/proforma/rentRollQuality.test.ts create mode 100644 apps/web/src/proforma/rentRollQuality.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de4cf0f..10f5d678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,54 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### RENTROLL-DARK — two engines that say whether you can believe the rent roll, and no screen called either + +The pro forma's Operations tab shows **face** numbers: base rent, in-place income, occupancy, WALT. +Face rent is what a broker quotes. Two R20 engines shipped to qualify it and neither was ever called — +`ApiClient.netEffectiveRent()` and `ApiClient.rentRollScrub()` both sat in +`apps/web/src/api/clientCallers.test.ts`'s `UNCALLED` list, reachable only from a script. Both are now +rendered under the rent roll by `apps/web/src/proforma/rentRollQuality.ts`. + +**Both engines are built around a REFUSAL, and the refusal is what a panel destroys.** That, rather +than the numbers, is what this change is about and what its fourteen tests pin. + +### Added + +- **Net effective rent** — face GPR against the discounted and straight-line NER, the concession load, + and the leases with the widest face→NER gap (the ones an underwriter re-cuts first). The card names + which form agency underwriting uses, so two numbers are a choice rather than a puzzle. +- **Rent-roll scrub** — the seven diligence checks, with **coverage as the headline**, the findings by + severity, and every check that could not run beside **what it would need**. That last table is the + actionable half: it tells you which document to go and get, rather than leaving the gap as an absence + of green ticks. + +### The caveats, each of which a naive panel would delete + +- **`clean` is never rendered as a clean rent roll.** `rent_scrub.py` computes it as + `bool(ran) and not failed`, so **one** check running and passing with six unable to run is + `clean: true`. A green tick off that flag is precisely what the engine's own docstring exists to + refuse — *"a scrub that reports 'no findings' because half its inputs were missing is worse than no + scrub — it launders absent data into apparent confidence."* The card leads with `N of M checks could + run` and qualifies the flag with it. +- **The NER totals cover a smaller population than the card above them.** `roll_up` filters to the + computable leases *before* summing, and `lease_count` is that subset's size — so with any + `skipped_count > 0` the Face GPR here is a different set from the rent roll's "Base rent / yr" three + lines above, and the two sit on one screen inviting a subtraction. Said out loud, with the skipped + leases and their reasons listed. +- **The skipped list can be a page.** `skipped` is capped at 50 server-side while `skipped_count` is + not. A page presented as the whole set is the CLASH-TRUNC shape — a screen reporting over a partial + view and calling it complete — so the card says `Showing 50 of 120` when it is showing a page. +- **No leasing commission means the optimistic case.** `lc_included` is false unless a rate is + supplied, and the engine never invents one; without it the landlord's costs are understated, so both + NERs are high. The absence would otherwise read as a complete answer. +- **Zero computable leases renders as a refusal, not as `$0`.** Every total would be zero, and "$0 of + net effective rent" is a different claim from "this cannot be stated for this rent roll". + +`apps/web/src/proforma/rentRollQuality.test.ts` — fourteen tests, and **six mutations each red exactly +one of them**: rendering `clean` as clean, dropping the population warning, presenting the skipped page +as the whole set, dropping the leasing-commission caveat, rendering zeros as a valuation, and dropping +what a blocked check needs. + ### RESIDUAL-DARK — the developer's actual question was built, routed, tested and unreachable Every other figure on the pro forma runs **forward** from a land price somebody typed. Site diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index a7f8692d..5d5cd457 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -306,13 +306,15 @@ const UNCALLED: readonly string[] = [ "importFamilyPack", "layoutVerify", "listMacros", "listingReso", "liveStream", "loanCovenants", "massingOptionRecipes", "mcpTools", "modelAdjacency", "moduleCalc", "myWork", - "netEffectiveRent", "normalizeT12", "parcelsDataStatus", + // `netEffectiveRent` and `rentRollScrub` left this list 2026-09-25 — `proforma/rentRollQuality.ts` + // renders both under the Operations rent roll. Shipped with R20, called by nothing. + "normalizeT12", "parcelsDataStatus", "pdfInfo", "permitsTimeline", "preconSnapshot", "proformaRenovation", "proformaRollover", "progressActuals", "progressCaptureDiff", "progressRollup", "raisePlan", "recordDistribution", // `residualLand` left this list 2026-09-25 — `proforma/residualLandCard.ts` is the Feasibility // tab's inverse solve. The engine and the client method shipped with FIN-CALC; nothing called it. - "rentRollScrub", "reviewPost", + "reviewPost", "reviewScenario", "reviseDrawing", "runClash", "runMacro", "saveClausePlaybook", "saveDealAuthority", "saveMacros", "saveViewTemplates", "scanDeviation", "scopeRegister", "securitiesPackage", "sendDigest", diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index 2024b205..5611859d 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -8,6 +8,7 @@ import { drawPackageLines } from "./drawPackage"; import { money, pct } from "./format"; import { renderMassingTab } from "./massingTab"; import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; +import { renderRentRollQuality } from "./rentRollQuality"; import { renderTestFitTab } from "./testfitTab"; import { downloadPostedPdf, camStatementPath } from "../api/downloadPdf"; import { toast } from "../ui/feedback"; @@ -533,6 +534,10 @@ export class ProformaUI { catch (e) { this.setStatus("Couldn't value from rent roll: " + (e as Error).message); } }; rb.append(rl, rx, rrv); rc.appendChild(rb); host.appendChild(rc); + // The card above is FACE rent. These two qualify it: what the roll is worth after concessions, + // and which diligence checks could actually run against it. Both engines shipped with R20 and + // had no caller — see rentRollQuality.ts. + await renderRentRollQuality(host, pid, { api: this.api, setStatus: this.setStatus }); await this.renderLeaseManagement(host, pid); } catch (e) { host.innerHTML = `
${escapeHtml((e as Error).message)}
`; } } diff --git a/apps/web/src/proforma/rentRollQuality.test.ts b/apps/web/src/proforma/rentRollQuality.test.ts new file mode 100644 index 00000000..caca0a31 --- /dev/null +++ b/apps/web/src/proforma/rentRollQuality.test.ts @@ -0,0 +1,187 @@ +import { describe, expect, it } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { renderNetEffective, renderRentScrub } from "./rentRollQuality"; + +/** + * Two R20 engines — CRE-NER and CRE-RRSCRUB — shipped complete, tested, and with no screen. Both are + * built around a REFUSAL, and what these tests pin is that the cards keep it. + * + * `rent_scrub.py`: *"a check that cannot run says so … A scrub that reports 'no findings' because half + * its inputs were missing is worse than no scrub — it launders absent data into apparent confidence."* + * And `clean` is `bool(ran) and not failed`, so ONE check running and passing with six unable to run + * is `clean: true`. Rendering a green tick off that flag would be the exact defect the engine exists + * to prevent, committed by its own consumer. + * + * `net_effective.py`: every total is summed over the COMPUTABLE leases only, so with any + * `skipped_count > 0` the Face GPR in this card is a different population from the "Base rent / yr" in + * the rent-roll card directly above it — and the two sit on one screen inviting a subtraction. The + * `skipped` list is additionally capped at 50 while `skipped_count` is not. + */ + +type Ner = Awaited>; +type Scrub = Awaited>; + +const NER = (over: Partial = {}): Ner => ({ + lease_count: 12, skipped_count: 0, excluded_not_active: 0, + face_gpr_annual: 3_600_000, ner_gpr_annual_discounted: 3_150_000, + ner_gpr_annual_straight_line: 3_240_000, concession_total_term: 1_800_000, + concession_load_pct: 9.4, face_to_ner_delta_annual: 450_000, face_to_ner_delta_pct: 12.5, + lc_included: true, discount_rate: 0.08, skipped: [], + leases: [{ tenant: "Northline Health", suite: "400", face_rent_annual: 480_000, + ner_annual_discounted: 402_000, ner_psf_discounted: 28.4, concession_load_pct: 16.2 }], + note: "Concessions are deducted before effective income.", + ...over, +} as Ner); + +const CHECK = (over: Partial): Scrub["checks"][number] => ({ + check: "scheduled_vs_gpr", applicable: true, passed: true, severity: "info", + finding: "scheduled rent is within tolerance of gross potential rent", ...over, +}); + +const SCRUB = (over: Partial = {}): Scrub => ({ + lease_count: 12, excluded_not_active: 0, clean: true, + counts: { total: 7, ran: 7, not_applicable: 0, passed: 7, failed: 0 }, + checks: [CHECK({})], findings: [], + coverage_note: "7 of 7 checks could run; 0 lacked inputs and are reported as not-run, never as passing.", + ...over, +} as Scrub); + +function mount() { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +} + +describe("net effective rent card", () => { + it("renders face, both NER forms and the concession load", () => { + const t = renderNetEffective(mount(), NER()).textContent ?? ""; + expect(t).toContain("$3,600,000"); + expect(t).toContain("$3,150,000"); + expect(t).toContain("$3,240,000"); + expect(t).toContain("9.4%"); + expect(t).toContain("12.5%"); + }); + + it("names which NER form underwriting uses, so two numbers are a choice and not a puzzle", () => { + expect(renderNetEffective(mount(), NER()).textContent).toContain("discounted"); + }); + + // The engine never invents a leasing-commission rate. Without it landlord costs are understated, + // so BOTH NERs are the optimistic case — the card has to say so or the absence reads as complete. + it("says both NERs are the optimistic case when leasing commission was not supplied", () => { + const t = renderNetEffective(mount(), NER({ lc_included: false })).textContent ?? ""; + expect(t).toContain("Leasing commission is not included"); + expect(t).toContain("optimistic"); + }); + + it("…and says nothing of the sort when it WAS supplied", () => { + expect(renderNetEffective(mount(), NER({ lc_included: true })).textContent) + .not.toContain("optimistic"); + }); + + // THE POPULATION CAVEAT. Every total is over the computable leases; the rent-roll card above shows + // all of them. Two figures for "the rent roll", one screen, different sets. + it("warns that the totals cover a smaller population than the rent roll above", () => { + const t = renderNetEffective(mount(), NER({ + lease_count: 9, skipped_count: 3, + skipped: [{ tenant: "Vale Dental", suite: "210", reason: "no end date" }, + { tenant: "Kerr & Co", suite: "115", reason: "no base rent" }, + { tenant: "Unit 6", suite: "6", reason: "end date before start" }], + })).textContent ?? ""; + expect(t).toContain("9 lease(s) valued"); + expect(t).toContain("3 skipped"); + expect(t).toContain("not meant to reconcile"); + expect(t).toContain("no end date"); // the reasons, so the gap is actionable + }); + + it("…and stays quiet about reconciling when nothing was skipped", () => { + expect(renderNetEffective(mount(), NER()).textContent).not.toContain("not meant to reconcile"); + }); + + // `skipped` is capped at 50 server-side while `skipped_count` is not — a page presented as the + // whole set is how a screen reports over a partial view and calls it complete (CLASH-TRUNC). + it("says when the skipped list is a page of a larger set", () => { + const t = renderNetEffective(mount(), NER({ + lease_count: 40, skipped_count: 120, + skipped: Array.from({ length: 50 }, (_, i) => ({ tenant: `T${i}`, suite: "", reason: "no end date" })), + })).textContent ?? ""; + expect(t).toContain("Showing 50 of 120 skipped leases"); + }); + + it("…and does not claim truncation when the list is complete", () => { + const t = renderNetEffective(mount(), NER({ + lease_count: 10, skipped_count: 2, + skipped: [{ tenant: "A", suite: "", reason: "no base rent" }, + { tenant: "B", suite: "", reason: "no end date" }], + })).textContent ?? ""; + expect(t).not.toContain("Showing"); + }); + + // Zero computable leases: every total would be 0, and rendering those renders an absence as a + // measurement — "$0 of net effective rent" is a different claim from "this cannot be stated". + it("refuses to render zeros as a valuation when nothing could be computed", () => { + const t = renderNetEffective(mount(), NER({ + lease_count: 0, skipped_count: 4, face_gpr_annual: 0, ner_gpr_annual_discounted: 0, + concession_load_pct: 0, face_to_ner_delta_annual: 0, face_to_ner_delta_pct: 0, leases: [], + })).textContent ?? ""; + expect(t).toContain("cannot be stated"); + expect(t).not.toMatch(/\$0/); + }); +}); + +describe("rent-roll scrub card", () => { + // THE ONE THIS CARD EXISTS FOR. `clean` is true whenever at least one check ran and none failed. + it("never renders `clean` as a clean rent roll when checks could not run", () => { + const t = renderRentScrub(mount(), SCRUB({ + clean: true, + counts: { total: 7, ran: 1, not_applicable: 6, passed: 1, failed: 0 }, + checks: [CHECK({}), + CHECK({ check: "occupied_no_lease", applicable: false, passed: undefined, + finding: "not run", needs: "a unit inventory with {unit, occupied}" })], + coverage_note: "1 of 7 checks could run; 6 lacked inputs and are reported as not-run, never as passing.", + })).textContent ?? ""; + expect(t).toContain("1 of 7 checks could run"); + expect(t).toContain("not the same as a clean"); + expect(t).toContain("6 check(s) had no inputs"); + }); + + it("…and does not hedge when every check ran and passed", () => { + const t = renderRentScrub(mount(), SCRUB()).textContent ?? ""; + expect(t).toContain("7 of 7 checks could run"); + expect(t).not.toContain("not the same as a clean"); + }); + + // The actionable half: what to go and get. Without it the gap is only an absence of green ticks. + it("names what each check that could not run would need", () => { + const t = renderRentScrub(mount(), SCRUB({ + counts: { total: 7, ran: 5, not_applicable: 2, passed: 5, failed: 0 }, + checks: [CHECK({ check: "occupied_no_lease", applicable: false, passed: undefined, + finding: "not run", needs: "a unit inventory with {unit, occupied}" }), + CHECK({ check: "bad_debt_vs_occupancy", applicable: false, passed: undefined, + finding: "not run", needs: "income.bad_debt + prior_bad_debt" })], + })).textContent ?? ""; + expect(t).toContain("a unit inventory with {unit, occupied}"); + expect(t).toContain("income.bad_debt + prior_bad_debt"); + }); + + it("renders findings with their severity", () => { + const t = renderRentScrub(mount(), SCRUB({ + clean: false, + counts: { total: 7, ran: 7, not_applicable: 0, passed: 5, failed: 2 }, + findings: [{ check: "scheduled_vs_gpr", severity: "high", + finding: "scheduled rent is 11.4% above gross potential rent" }, + { check: "expired_active", severity: "medium", + finding: "3 active leases are past their end date" }], + })).textContent ?? ""; + expect(t).toContain("11.4% above gross potential rent"); + expect(t).toContain("high"); + expect(t).toContain("2 finding(s)"); + }); + + it("reports the population it scrubbed, and what it left out", () => { + const t = renderRentScrub(mount(), SCRUB({ lease_count: 12, excluded_not_active: 4 })).textContent ?? ""; + expect(t).toContain("12 active lease(s) scrubbed"); + expect(t).toContain("4 excluded as not active"); + }); +}); diff --git a/apps/web/src/proforma/rentRollQuality.ts b/apps/web/src/proforma/rentRollQuality.ts new file mode 100644 index 00000000..5ca82186 --- /dev/null +++ b/apps/web/src/proforma/rentRollQuality.ts @@ -0,0 +1,213 @@ +/** Can you believe this rent roll? — the two CRE-R20 engines that answer that, and had no screen. + * + * The Operations tab shows **face** numbers: base rent, in-place income, occupancy, WALT. Face rent + * is what a broker quotes. Two engines shipped to qualify it and neither was ever called — + * `ApiClient.netEffectiveRent()` and `ApiClient.rentRollScrub()` both sat in + * `apps/web/src/api/clientCallers.test.ts`'s `UNCALLED` list, reachable only from a script. + * + * BOTH ENGINES ARE BUILT AROUND A REFUSAL, AND THE REFUSAL IS WHAT A PANEL DESTROYS. + * + * `rent_scrub.py`'s module docstring states the rule outright: *"a check that cannot run says so … + * A scrub that reports 'no findings' because half its inputs were missing is worse than no scrub — + * it launders absent data into apparent confidence."* And `clean` is computed as + * `bool(ran) and not failed` — so **one** check running and passing, with six unable to run, is + * `clean: true`. A card that renders a green tick off that flag is the exact defect the engine was + * written to prevent, committed by its own consumer. So `clean` is never rendered alone here: the + * coverage is the headline and the flag is a qualifier on it. + * + * `net_effective.py` is the same shape one level quieter. Its totals — `face_gpr_annual`, + * `concession_load_pct`, `face_to_ner_delta_*` — are summed over the **computable** leases only + * (`roll_up` filters to `ok` before summing), and `lease_count` is that subset's size, not the rent + * roll's. So with any `skipped_count > 0` the face GPR in this card is a DIFFERENT population from + * the "Base rent / yr" in the rent-roll card directly above it, and the two sit on one screen + * inviting the reader to subtract them. Said out loud rather than left to be noticed. + * + * And `skipped` is capped at 50 while `skipped_count` is not, so the list can be a page of a larger + * set — the CLASH-TRUNC shape, where a screen reported over a partial matrix and declared it whole. + * The card says when it is showing a page. + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; +import { money, pct } from "./format"; + +type Ner = Awaited>; +type Scrub = Awaited>; + +const card = (title: string) => { + const el = document.createElement("div"); + el.className = "fin-card"; + el.style.marginTop = "10px"; + el.innerHTML = `
${esc(title)}
`; + return el; +}; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +/** Net effective rent: what the rent roll is worth after concessions, and over which leases. */ +export function renderNetEffective(host: HTMLElement, n: Ner): HTMLElement { + const el = card("Net effective rent (after concessions)"); + + if (n.lease_count === 0) { + // Nothing computable. Saying "0" for every total would render an absence as a measurement. + el.insertAdjacentHTML("beforeend", meta( + n.skipped_count > 0 + ? `No lease carries the fields this needs — ${n.skipped_count} active lease(s) were skipped. ` + + `Net effective rent cannot be stated for this rent roll yet.` + : `No active leases to value.`)); + host.appendChild(el); + return el; + } + + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `
Face GPR / yr${esc(money(n.face_gpr_annual))}
NER / yr (discounted @ ${esc(pct(n.discount_rate))})${esc(money(n.ner_gpr_annual_discounted))}
NER / yr (straight-line)${esc(money(n.ner_gpr_annual_straight_line))}
Concession load${n.concession_load_pct}%
Face → NER−${esc(money(n.face_to_ner_delta_annual))} (${n.face_to_ner_delta_pct}%)
`); + + // The discounted form is the commercial underwriting figure; the straight-line one is the + // quick-look average. Saying which is which is the difference between two numbers and a choice. + el.insertAdjacentHTML("beforeend", meta( + `The discounted figure prices when the free rent and the TI cheque land; ` + + `straight-line averages them over the term. Agency underwriting uses the discounted one.`)); + + // `lc_included` is false unless the caller supplies a rate — and without it the landlord's costs + // are understated, so both NERs are the OPTIMISTIC case. The engine refuses to invent the rate; + // the panel must not let its absence read as a complete answer. + if (!n.lc_included) { + el.insertAdjacentHTML("beforeend", meta( + `Leasing commission is not included — no rate was supplied, and the engine ` + + `never invents one. Both NERs above are therefore the optimistic case.`, + "var(--status-warn)")); + } + + // POPULATION. Every total above is summed over the computable leases only. + const parts: string[] = [`${n.lease_count} lease(s) valued`]; + if (n.skipped_count > 0) parts.push(`${n.skipped_count} skipped`); + if (n.excluded_not_active > 0) parts.push(`${n.excluded_not_active} excluded as not active`); + el.insertAdjacentHTML("beforeend", meta(parts.join(" · "))); + + if (n.skipped_count > 0) { + el.insertAdjacentHTML("beforeend", meta( + `Every figure above covers the ${n.lease_count} lease(s) that could be valued — ` + + `so the Face GPR here is a smaller population than the rent roll's base rent above it, and the ` + + `two are not meant to reconcile.`, "var(--status-warn)")); + const shown = n.skipped.length; + el.insertAdjacentHTML("beforeend", + `` + + n.skipped.map((s) => + `` + + ``).join("") + + `
Skipped leasewhy
${esc(s.tenant || "—")}${s.suite ? ` ${esc(s.suite)}` : ""}${esc(s.reason || "—")}
`); + // `skipped` is capped at 50 server-side while `skipped_count` is not. A list presented as the + // whole set when it is a page is how a screen reports over a partial view and calls it complete. + if (shown < n.skipped_count) { + el.insertAdjacentHTML("beforeend", meta( + `Showing ${shown} of ${n.skipped_count} skipped leases.`, "var(--status-warn)")); + } + } + + if (n.leases.length) { + // Ordered by face→NER delta server-side: the leases an underwriter re-cuts first. + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + n.leases.slice(0, 8).map((l) => + `` + + `` + + `` + + ``).join("") + + `
Widest face → NERfaceNERload
${esc(l.tenant || "—")}${l.suite ? ` ${esc(l.suite)}` : ""}${esc(money(l.face_rent_annual))}${esc(money(l.ner_annual_discounted))}${l.concession_load_pct}%
`); + } + + host.appendChild(el); + return el; +} + +/** Rent-roll scrub: which diligence checks ran, which could not, and what they would need. */ +export function renderRentScrub(host: HTMLElement, s: Scrub): HTMLElement { + const el = card("Rent-roll scrub (diligence checks)"); + const { total, ran, not_applicable: notRun, passed, failed } = s.counts; + + // COVERAGE IS THE HEADLINE, not `clean`. `clean` is `bool(ran) && !failed`, so one check running + // and passing while six cannot run is `true` — and a green tick off that flag is precisely the + // laundering of absent data into confidence that rent_scrub.py exists to refuse. + const colour = failed > 0 ? "var(--status-crit)" + : notRun > 0 ? "var(--status-warn)" : "var(--status-good)"; + el.insertAdjacentHTML("beforeend", + `
` + + `${ran} of ${total} checks could run` + + (failed > 0 ? ` · ${failed} finding(s)` : ran > 0 ? ` · ${passed} passed` : "") + + `
` + + meta(esc(s.coverage_note))); + + if (s.clean && notRun > 0) { + // The one sentence this card exists for. + el.insertAdjacentHTML("beforeend", meta( + `No findings among the ${ran} check(s) that ran — which is not the same as a clean ` + + `rent roll. ${notRun} check(s) had no inputs to run on.`, "var(--status-warn)")); + } + + if (s.findings.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + s.findings.map((f) => + ``).join("") + + `
Findingseverity
${esc(f.finding)}${esc(f.severity || "—")}
`); + } + + // The not-run checks with what each one NEEDS: this is the actionable half — it tells the user + // which document to go and get, rather than leaving the gap as an absence of green ticks. + const blocked = s.checks.filter((c) => !c.applicable); + if (blocked.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + blocked.map((c) => + ``).join("") + + `
Could not runneeds
${esc(c.check)}${esc(c.needs || c.finding || "—")}
`); + } + + const pop: string[] = [`${s.lease_count} active lease(s) scrubbed`]; + if (s.excluded_not_active > 0) pop.push(`${s.excluded_not_active} excluded as not active`); + el.insertAdjacentHTML("beforeend", meta(pop.join(" · "))); + + host.appendChild(el); + return el; +} + +export interface RentRollQualityCtx { + api: ApiClient; + setStatus: (m: string) => void; +} + +/** Fetch both and render them under the rent roll. Each failure is reported on its own card, so one + * engine being unavailable does not hide the other's answer. */ +export async function renderRentRollQuality(host: HTMLElement, pid: string, + ctx: RentRollQualityCtx): Promise { + const [ner, scrub] = await Promise.allSettled([ + ctx.api.netEffectiveRent(pid), + ctx.api.rentRollScrub(pid), + ]); + if (ner.status === "fulfilled") renderNetEffective(host, ner.value); + else { + const el = card("Net effective rent (after concessions)"); + el.insertAdjacentHTML("beforeend", meta(esc((ner.reason as Error).message), "var(--status-crit)")); + host.appendChild(el); + } + if (scrub.status === "fulfilled") renderRentScrub(host, scrub.value); + else { + const el = card("Rent-roll scrub (diligence checks)"); + el.insertAdjacentHTML("beforeend", meta(esc((scrub.reason as Error).message), "var(--status-crit)")); + host.appendChild(el); + } + if (ner.status === "rejected" && scrub.status === "rejected") ctx.setStatus("rent-roll quality unavailable"); +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 84628774..911f08b7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,34 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **RENTROLL-DARK — two engines that say whether you can believe the rent roll, and no screen + called either** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/rentRollQuality.test.ts` and two `UNCALLED` deletions in + `apps/web/src/api/clientCallers.test.ts`)* + + The Operations tab shows FACE numbers — base rent, in-place income, occupancy, WALT. CRE-NER and + CRE-RRSCRUB shipped to qualify them; `ApiClient.netEffectiveRent()` and `ApiClient.rentRollScrub()` + were both callerless. **Both engines are built around a refusal, and the refusal is what a panel + destroys** — which is why the fourteen tests pin the caveats rather than the numbers: + + * `rent_scrub.py` computes `clean` as `bool(ran) and not failed`, so **one** check running and + passing with six unable to run is `clean: true`. Its own docstring names the defect a green tick + would be: *"a scrub that reports 'no findings' because half its inputs were missing … launders + absent data into apparent confidence."* Coverage is the headline here and the flag qualifies it. + * `net_effective.roll_up` sums over the COMPUTABLE leases only, so with any `skipped_count > 0` the + Face GPR is a different population from the "Base rent / yr" three lines above it on the same + screen. Stated, with the skipped leases and reasons. + * `skipped` is capped at 50 while `skipped_count` is not — *the CLASH-TRUNC shape*, a page presented + as the whole set — so the card says when it is showing a page. + * `lc_included` false means the landlord's costs are understated and both NERs are the optimistic + case; an absent input must not read as a complete answer. + * Zero computable leases renders as a refusal rather than `$0`, because a total of zero and an + absence of a total are different claims. + + Six mutations, each redding exactly one test: rendering `clean` as clean, dropping the population + warning, presenting the skipped page as whole, dropping the commission caveat, rendering zeros as a + valuation, and dropping what a blocked check needs. + - ✅ ⭐ **RESIDUAL-DARK — the developer's actual question was built, routed, tested and unreachable** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/residualLandCard.test.ts` and the `UNCALLED` deletion in From 5c6b8e605a47ce6af0f54e7e7b52ffd7f99656a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 03:57:55 +0000 Subject: [PATCH 04/21] T12-SELFTIE: a gate that could not fail, because nobody was handing it evidence services/api/src/aec_api/t12.py exists for one refusal, in its own module docstring: "income, expense and NOI must reconcile before and after mapping, or the engine stops ... It does not publish an adjusted NOI on top of a mapping that lost money." ApiClient.normalizeT12() was written for it and no screen ever called it. Measuring what a caller would actually get found something sharper than the missing UI. normalize() says what it does and the consequence is easy to miss: "When the caller supplies source totals they are the reference the mapping must reproduce; WITHOUT THEM THE SUM OF THE SOURCE LINES IS." Both sides of the comparison are then computed from the same mapped rows, so the deltas are zero by construction and the gate passes vacuously. Measured through the real engine on one T-12 carrying a single unmapped $90,000 line: no stated totals reconciles: true deltas all 0.0 adjusted_noi: 3,180,000 with stated totals reconciles: false expense -90,000 stopped: true, null The $90k reclass -- precisely what the gate exists to catch -- is invisible in the first case, and an adjusted NOI is published on top of it. THAT IS NOT AN ENGINE DEFECT: it is a caller obligation that never had a caller. A guard is only as sound as the evidence it is handed -- the CLASH-TRUNC lesson one layer over, where a panel fed the guard a page of a matrix and declared it whole. Here a caller can feed it a reference derived from the answer. AND THE TWO RESPONSES ARE INDISTINGUISHABLE. `reconciles` is true either way, and so is every other field the card could read. Only the caller knows which it handed over, which is why "were totals stated" is a PARAMETER of the renderer rather than something derived from the response: a check that tried to infer it would be inferring it from the very field that cannot tell. So stated totals are the primary input here, not an optional extra, and absent them the card prints "Tie-out tied to itself -- not a check" and puts unmapped_count where the verdict would be, that being the only signal left. Past the gate: adjusted NOI, one-time items separated from run-rate, capital below the line, the run-rate-vs-trailing movers, and the owner-operated QUESTIONS -- rendered as questions with the number behind each and labelled "never applied for you", because treating them as priced in is the 15-25% NOI miss they exist to prevent. A stopped tie-out says its derived views were NEVER CALCULATED rather than hidden, which is true (they are computed only past the gate) and stops somebody hunting for a toggle. THE PASTE PARSER SPLITS ON THE LAST COMMA. Account names carry commas ("Repairs, maintenance & turnover") and amounts do not carry tabs. Splitting on the first truncates the description AND reads the remainder as the amount, which parses -- so the failure would be a wrong number rather than an error. An unreadable amount returns null, never 0: a zero enters the mapping as a real line worth nothing, shifts no total, and is invisible in both the tie-out and the unmapped list. Rows this client cannot read are reported, because they never reach the engine and are therefore absent from both totals however the tie-out is run. IT ALSO CLOSES THE LOOP ON RENTROLL-DARK. Five of the scrub's seven checks report applicable: false for want of an income statement, and a normalised T-12 is one. "Use in the rent-roll scrub" re-runs it in place with gross_potential_rent and bad_debt, and says which statement it ran against -- a coverage figure that moved is otherwise indistinguishable from a rent roll that changed. Only those two: prior_bad_debt, occupancy_pct and prior_occupancy_pct describe a prior period and a unit inventory, and filling them from this period to make a check run would be the defect the scrub is written to refuse, committed from the outside. Sixteen tests, seven mutations each redding exactly one: trusting `reconciles` without knowing whether totals were stated, splitting on the first comma, returning 0 for an unreadable amount, presenting the unmapped page as the whole set, inventing the prior-period fields, dropping "never applied for you", and calling the derived views hidden rather than never calculated. TWO MORE UNDECLARED FIELDS, both on this response. run_rate_vs_trailing.label was found by THE COMPILER rather than a reader -- the card rendered x.label and tsc refused it, which is the declared type doing its job; an inline re-spelled shape would have accepted it silently, which is why noRespelledShapes.test.ts exists. And add_back_questions omitted amount and pct_of_income, where the engine's docstring says each finding is "a QUESTION with the number behind it". AND residualLandCard.ts's buttons carried className = "btn", which matches NO rule in style.css -- so they rendered as browser defaults beside styled siblings. Every button in proforma/ uses file-btn or tool-btn; this card had copied a class from portal/panels/, where six files use it and are equally unstyled. A class name is not a style, and nothing typechecks the gap. Fixed for proforma/; the six in portal/panels/ are pre-existing and left for Lane B. Verified: tsc --noEmit clean, eslint clean, npm run build clean, 267 files / 2,891 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 79 +++++ apps/web/src/api/clientCallers.test.ts | 4 +- apps/web/src/api/creDeal.ts | 17 +- apps/web/src/proforma/proforma.ts | 20 +- apps/web/src/proforma/rentRollQuality.ts | 24 +- apps/web/src/proforma/residualLandCard.ts | 8 +- apps/web/src/proforma/t12Card.test.ts | 182 ++++++++++++ apps/web/src/proforma/t12Card.ts | 347 ++++++++++++++++++++++ docs/roadmap.md | 37 +++ 9 files changed, 703 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/proforma/t12Card.test.ts create mode 100644 apps/web/src/proforma/t12Card.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f5d678..178453c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,85 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### T12-SELFTIE — a gate that could not fail, because nobody was handing it evidence + +`services/api/src/aec_api/t12.py` exists for one refusal, stated in its own module docstring: + +> income, expense and NOI must reconcile *before* and *after* mapping, or the engine stops and lists +> the reconciling items. It does not publish an adjusted NOI on top of a mapping that lost money. + +`ApiClient.normalizeT12()` was written for it and **no screen ever called it**. Measuring what a +caller would actually get found something sharper than the missing UI. + +**`normalize()` says what it does and the consequence is easy to miss** — *"When the caller supplies +source totals they are the reference the mapping must reproduce; **without them the sum of the source +lines is**."* Both sides of the comparison are then computed from the same mapped rows, so the deltas +are zero by construction and **the gate passes vacuously.** Measured through the real engine on one +T-12 carrying a single unmapped $90,000 line: + +| input | tie-out | adjusted NOI | +|---|---|---| +| no stated totals | `reconciles: true`, all deltas `0.0` | **published: $3,180,000** | +| with stated totals | `reconciles: false`, expense −90,000 | `stopped: true`, `null` | + +The $90k reclass — precisely what the gate exists to catch — is invisible in the first case, and an +adjusted NOI is published on top of it. **That is not an engine defect: it is a caller obligation that +never had a caller.** *A guard is only as sound as the evidence it is handed* — the CLASH-TRUNC lesson +one layer over, where a panel fed the guard a page of a matrix and declared it whole. Here a caller +can feed it a reference derived from the answer. + +**And the two responses are indistinguishable.** `reconciles` is `true` either way, and so is every +other field the card could read. Only the caller knows which it handed over, which is why "were totals +stated" is a parameter of the renderer rather than something derived from the response — a check that +tried to infer it would be inferring it from the very thing that cannot tell. + +### Added + +- **`apps/web/src/proforma/t12Card.ts`** — paste the seller's statement, give its own stated totals, + and get the mapping tied out. Stated totals are the **primary** input, not an optional extra; with + them absent the card prints *"Tie-out tied to itself — not a check"* and puts `unmapped_count` where + the verdict would be, because that is the only signal left. +- Past the gate: adjusted NOI, one-time income and expense separated from run-rate, capital below the + line, the run-rate-vs-trailing movers, and the owner-operated **questions** — rendered as questions + with the number behind each, and labelled *never applied for you*, because treating them as priced + in is the 15–25% NOI miss they exist to prevent. +- A stopped tie-out says its derived views were **never calculated**, not hidden. `add_back_questions` + and `run_rate_vs_trailing` are computed only past the gate, so "hidden" would misdescribe what + happened and send someone looking for a toggle. +- **The paste parser splits on the LAST comma**, because account names carry commas + (*"Repairs, maintenance & turnover"*) and amounts do not carry tabs. Splitting on the first truncates + the description **and** reads the remainder as the amount — which parses, so the failure would be a + wrong number rather than an error. An unreadable amount returns `null`, never `0`: a zero would enter + the mapping as a real line worth nothing, shift no total, and be invisible in both the tie-out and the + unmapped list. Rows the client cannot read are reported, because they never reach the engine and are + therefore absent from both totals and invisible to the tie-out however it is run. +- **The handoff into the rent-roll scrub.** Five of the scrub's seven checks report + `applicable: false` for want of an income statement, and a normalised T-12 is exactly that input — + so **Use in the rent-roll scrub** re-runs it with `gross_potential_rent` and `bad_debt`, in place, + and says which statement it ran against. Only those two: `prior_bad_debt`, `occupancy_pct` and + `prior_occupancy_pct` describe a prior period and a unit inventory, and filling them from this period + to make a check run would be the defect the scrub is written to refuse, committed from the outside. + The check that needs them stays not-run and says so. +- `apps/web/src/proforma/t12Card.test.ts` — sixteen tests, **seven mutations each redding exactly + one**: trusting `reconciles` without knowing whether totals were stated, splitting the paste on the + first comma, returning `0` for an unreadable amount, presenting the unmapped page as the whole set, + inventing the prior-period fields for the scrub, dropping *"never applied for you"*, and calling the + derived views hidden rather than never calculated. + +### Fixed + +- **Two more fields the client declared nowhere**, both on this response. `run_rate_vs_trailing.label` + was found by **the compiler** rather than by a reader — the card rendered `x.label` and `tsc` refused + it, which is the declared type doing its job; an inline re-spelled shape would have accepted it + silently, which is why `noRespelledShapes.test.ts` exists. And `add_back_questions` omitted `amount` + and `pct_of_income`, where the engine's docstring says each finding is *"a QUESTION with the number + behind it"* — the number being the point of the finding rather than detail beside it. +- **`residualLandCard.ts`'s buttons carried `className = "btn"`, and `.btn` matches no rule in + `style.css`** — so they rendered as browser defaults beside styled siblings. Every button in + `proforma/` uses `file-btn` or `tool-btn`; this card copied a class from `portal/panels/`, where six + files use it and are equally unstyled. *A class name is not a style, and nothing typechecks the gap.* + Fixed here for `proforma/`; the six in `portal/panels/` are pre-existing and left for Lane B. + ### RENTROLL-DARK — two engines that say whether you can believe the rent roll, and no screen called either The pro forma's Operations tab shows **face** numbers: base rent, in-place income, occupancy, WALT. diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 5d5cd457..fecac962 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -308,7 +308,9 @@ const UNCALLED: readonly string[] = [ "modelAdjacency", "moduleCalc", "myWork", // `netEffectiveRent` and `rentRollScrub` left this list 2026-09-25 — `proforma/rentRollQuality.ts` // renders both under the Operations rent roll. Shipped with R20, called by nothing. - "normalizeT12", "parcelsDataStatus", + // `normalizeT12` left this list 2026-09-25 — `proforma/t12Card.ts`. Its tie-out is a gate that + // could not fail without a caller supplying stated totals; see T12-SELFTIE in that file's header. + "parcelsDataStatus", "pdfInfo", "permitsTimeline", "preconSnapshot", "proformaRenovation", "proformaRollover", "progressActuals", "progressCaptureDiff", "progressRollup", "raisePlan", "recordDistribution", diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index f36248da..cd99d00d 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -213,8 +213,21 @@ export function withCreDeal>(Base: TBase) { one_time_items?: { description: string; amount: number; kind: string }[]; capital_items?: { description: string; amount: number }[]; by_category?: { category: string; label: string; amount: number; run_rate: number }[]; - run_rate_vs_trailing?: { category: string; trailing: number; run_rate: number; delta: number }[]; - add_back_questions?: { check: string; severity: string; finding: string; question: string }[]; + // `label` here was undeclared too, and the COMPILER found this one rather than a reader: the + // first draft of `proforma/t12Card.ts` rendered `x.label` and `tsc` refused it. That is the + // argument for the declared type doing work — a re-spelled inline shape would have accepted it + // silently and `noRespelledShapes.test.ts` exists because two wire types already did exactly + // that. (`by_category`'s `kind` / `operating` / `line_count` stay undeclared on purpose: + // nothing here reads them, and declaring a field no reader wants adds noise to the very audit + // that counts unread fields.) + run_rate_vs_trailing?: { category: string; label?: string; trailing: number; + run_rate: number; delta: number }[]; + // `amount` and `pct_of_income` were returned by `t12.add_back_questions` and declared nowhere + // here until 2026-09-25 — the third instance of the axis SCREEN-VS-REPORT names, and the one + // that matters most for this field: the engine's docstring says each is *"a QUESTION with the + // number behind it"*, so the number is the point of the finding rather than detail beside it. + add_back_questions?: { check: string; severity: string; finding: string; question: string; + amount?: number; pct_of_income?: number }[]; note: string }>( `/projects/${pid}/t12/normalize`, { method: "POST", body: JSON.stringify({ t12, units }) }); } diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index 5611859d..7201b6d4 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -9,6 +9,7 @@ import { money, pct } from "./format"; import { renderMassingTab } from "./massingTab"; import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; import { renderRentRollQuality } from "./rentRollQuality"; +import { renderT12Card } from "./t12Card"; import { renderTestFitTab } from "./testfitTab"; import { downloadPostedPdf, camStatementPath } from "../api/downloadPdf"; import { toast } from "../ui/feedback"; @@ -534,10 +535,21 @@ export class ProformaUI { catch (e) { this.setStatus("Couldn't value from rent roll: " + (e as Error).message); } }; rb.append(rl, rx, rrv); rc.appendChild(rb); host.appendChild(rc); - // The card above is FACE rent. These two qualify it: what the roll is worth after concessions, - // and which diligence checks could actually run against it. Both engines shipped with R20 and - // had no caller — see rentRollQuality.ts. - await renderRentRollQuality(host, pid, { api: this.api, setStatus: this.setStatus }); + // The card above is FACE rent. These qualify it: what the roll is worth after concessions, + // which diligence checks could actually run against it, and the seller's own T-12 tied out. + // All three engines shipped with R20 and had no caller — see rentRollQuality.ts / t12Card.ts. + const quality = document.createElement("div"); host.appendChild(quality); + await renderRentRollQuality(quality, pid, { api: this.api, setStatus: this.setStatus }); + renderT12Card(host, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + // Re-render the two quality cards against the normalised T-12: the scrub's coverage is the + // thing that moves, and it has to be visibly the same cards rather than a second opinion + // appearing further down the page. + scrubWithIncome: (income) => { + quality.replaceChildren(); + void renderRentRollQuality(quality, pid, { api: this.api, setStatus: this.setStatus }, income); + }, + }); await this.renderLeaseManagement(host, pid); } catch (e) { host.innerHTML = `
${escapeHtml((e as Error).message)}
`; } } diff --git a/apps/web/src/proforma/rentRollQuality.ts b/apps/web/src/proforma/rentRollQuality.ts index 5ca82186..052c2f17 100644 --- a/apps/web/src/proforma/rentRollQuality.ts +++ b/apps/web/src/proforma/rentRollQuality.ts @@ -190,12 +190,17 @@ export interface RentRollQualityCtx { } /** Fetch both and render them under the rent roll. Each failure is reported on its own card, so one - * engine being unavailable does not hide the other's answer. */ -export async function renderRentRollQuality(host: HTMLElement, pid: string, - ctx: RentRollQualityCtx): Promise { + * engine being unavailable does not hide the other's answer. + * + * `income` re-runs the scrub with an income statement — the normalised T-12 from + * `proforma/t12Card.ts`. Five of the scrub's seven checks report `applicable: false` for want of + * one, so this is what moves its coverage; the scrub's own not-run table names what is still + * missing, which is why nothing here has to guess. */ +export async function renderRentRollQuality(host: HTMLElement, pid: string, ctx: RentRollQualityCtx, + income?: Record): Promise { const [ner, scrub] = await Promise.allSettled([ ctx.api.netEffectiveRent(pid), - ctx.api.rentRollScrub(pid), + ctx.api.rentRollScrub(pid, income), ]); if (ner.status === "fulfilled") renderNetEffective(host, ner.value); else { @@ -203,8 +208,15 @@ export async function renderRentRollQuality(host: HTMLElement, pid: string, el.insertAdjacentHTML("beforeend", meta(esc((ner.reason as Error).message), "var(--status-crit)")); host.appendChild(el); } - if (scrub.status === "fulfilled") renderRentScrub(host, scrub.value); - else { + if (scrub.status === "fulfilled") { + const el = renderRentScrub(host, scrub.value); + if (income) { + // Say WHICH income statement this ran against. A coverage figure that moved is otherwise + // indistinguishable from a rent roll that changed, and the reader has no way to tell which. + el.insertAdjacentHTML("beforeend", meta( + `Run against the normalised T-12 above (${Object.keys(income).sort().join(", ")}).`)); + } + } else { const el = card("Rent-roll scrub (diligence checks)"); el.insertAdjacentHTML("beforeend", meta(esc((scrub.reason as Error).message), "var(--status-crit)")); host.appendChild(el); diff --git a/apps/web/src/proforma/residualLandCard.ts b/apps/web/src/proforma/residualLandCard.ts index 1b1dc2be..51e158c2 100644 --- a/apps/web/src/proforma/residualLandCard.ts +++ b/apps/web/src/proforma/residualLandCard.ts @@ -132,8 +132,12 @@ export function renderResidualLandCard(root: HTMLElement, ctx: ResidualLandCtx): host.appendChild(grid); const out = document.createElement("div"); out.style.cssText = "margin-top:6px"; - const go = document.createElement("button"); go.className = "btn"; go.textContent = "Solve residual land"; - const applyBtn = document.createElement("button"); applyBtn.className = "btn"; + // `file-btn`, not `btn`: **`.btn` matches no rule in `style.css`**, so a button carrying it renders + // as a browser default beside styled siblings. Six files under `portal/panels/` use it and this card + // copied them; every button in `proforma/` uses `file-btn` or `tool-btn`, which is the convention + // that actually has CSS behind it. A class name is not a style, and nothing typechecks the gap. + const go = document.createElement("button"); go.className = "file-btn"; go.textContent = "Solve residual land"; + const applyBtn = document.createElement("button"); applyBtn.className = "file-btn"; applyBtn.textContent = "Apply to the deal"; applyBtn.style.display = "none"; applyBtn.style.marginLeft = "6px"; const actions = document.createElement("div"); actions.style.cssText = "margin-top:6px"; actions.appendChild(go); actions.appendChild(applyBtn); diff --git a/apps/web/src/proforma/t12Card.test.ts b/apps/web/src/proforma/t12Card.test.ts new file mode 100644 index 00000000..0c33b61c --- /dev/null +++ b/apps/web/src/proforma/t12Card.test.ts @@ -0,0 +1,182 @@ +import { describe, expect, it } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { hasStatedTotals, incomeFromT12, parseAmount, parseT12, renderT12Result } from "./t12Card"; + +/** + * T12-SELFTIE — `t12.py` exists for one refusal and the refusal is unreachable without a caller that + * hands it real evidence. + * + * `normalize()`'s docstring: *"When the caller supplies source totals they are the reference the + * mapping must reproduce; **without them the sum of the source lines is**."* Both sides then come from + * the same mapped rows, so the deltas are zero by construction. Measured through the real engine, on + * one T-12 with a single unmapped $90,000 line: + * + * no stated totals reconciles: true deltas all 0.0 adjusted_noi: 3,180,000 + * with stated totals reconciles: false expense −90,000 stopped: true · adjusted_noi: null + * + * So the response for a vacuous tie-out is **indistinguishable from a real pass** on every field the + * card could read — `reconciles` is true either way. Only the CALLER knows which it handed over, which + * is why `stated` is a parameter here and not something derived from the response. + */ + +type T12 = Awaited>; + +/** The engine's shape for a tie-out that "passed" — identical whether or not totals were supplied. */ +const PASSED = (over: Partial = {}): T12 => ({ + line_count: 24, + source_totals: { income: 3_420_000, expense: 1_330_000, noi: 2_090_000 }, + mapped_totals: { income: 3_420_000, expense: 1_330_000, noi: 2_090_000 }, + tie_out: { reconciles: true, deltas: { income: 0, expense: 0, noi: 0 }, tolerance: 1 }, + adjusted_noi: 2_140_000, unmapped_count: 0, unmapped: [], + one_time_items: [{ description: "Legal settlement", amount: 50_000, kind: "expense" }], + capital_items: [{ description: "Roof replacement", amount: 180_000 }], + by_category: [{ category: "gross_potential_rent", label: "Gross potential rent", + amount: 3_600_000, run_rate: 3_648_000 }, + { category: "bad_debt", label: "Bad debt", amount: 41_000, run_rate: 52_000 }], + run_rate_vs_trailing: [{ category: "bad_debt", label: "Bad debt", trailing: 41_000, + run_rate: 52_000, delta: 11_000 }], + add_back_questions: [{ check: "management_fee", severity: "high", + finding: "no management fee in the statement", + question: "Is this owner-managed? Add a market management fee before pricing.", + amount: 0, pct_of_income: 0 }], + note: "Tie-out passed", + ...over, +} as T12); + +const STOPPED = (over: Partial = {}): T12 => ({ + line_count: 24, + source_totals: { income: 3_420_000, expense: 1_330_000, noi: 2_090_000 }, + mapped_totals: { income: 3_420_000, expense: 1_240_000, noi: 2_180_000 }, + tie_out: { reconciles: false, deltas: { income: 0, expense: -90_000, noi: 90_000 }, tolerance: 1 }, + stopped: true, adjusted_noi: null, unmapped_count: 1, + unmapped: [{ description: "Owner draw — misc", amount: 90_000 }], + reconciling_items: [{ issue: "unmapped line", description: "Owner draw — misc", amount: 90_000 }], + note: "STOPPED: source and mapped totals do not reconcile", + ...over, +} as T12); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; + +describe("the paste parser", () => { + it("splits on the LAST comma, because account names contain commas and amounts do not", () => { + // Splitting on the first comma truncates the description AND reads the remainder as the amount, + // which parses — so the failure would be a wrong number rather than an error. + const { lines } = parseT12("Repairs, maintenance & turnover, 240000"); + expect(lines).toEqual([{ description: "Repairs, maintenance & turnover", amount: 240_000 }]); + }); + + it("prefers a tab when there is one", () => { + const { lines } = parseT12("Repairs, maintenance\t240000"); + expect(lines[0]!.description).toBe("Repairs, maintenance"); + }); + + it("reads what a spreadsheet paste actually contains", () => { + expect(parseAmount("$3,600,000.00")).toBe(3_600_000); + expect(parseAmount("(180,000)")).toBe(-180_000); // parenthesised negative + expect(parseAmount(" -1234.5 ")).toBe(-1234.5); + }); + + it("returns null rather than 0 for something it cannot read", () => { + // 0 would enter the mapping as a real line worth nothing and shift no total, so a misread row + // would be invisible in both the tie-out and the unmapped list. + expect(parseAmount("n/a")).toBeNull(); + expect(parseAmount("")).toBeNull(); + expect(parseAmount("12,34,x")).toBeNull(); + }); + + it("collects unreadable rows instead of dropping them", () => { + const { lines, skipped } = parseT12("Rent, 100\nsomething odd\nTaxes, n/a\n\nInsurance\t2000"); + expect(lines.map((l) => l.description)).toEqual(["Rent", "Insurance"]); + expect(skipped).toEqual(["something odd", "Taxes, n/a"]); + }); +}); + +describe("whether the caller handed the engine a real reference", () => { + it("needs BOTH totals — one alone leaves the other tied to itself", () => { + expect(hasStatedTotals({ income: 3_420_000, expense: 1_330_000 })).toBe(true); + expect(hasStatedTotals({ income: 3_420_000 })).toBe(false); + expect(hasStatedTotals({ expense: 1_330_000 })).toBe(false); + expect(hasStatedTotals({ income: NaN, expense: 1_330_000 })).toBe(false); + }); +}); + +describe("the tie-out verdict", () => { + // THE ONE THIS CARD EXISTS FOR. Same response, same `reconciles: true`; only the caller knows. + it("refuses to call a self-tie a pass", () => { + const t = renderT12Result(mount(), PASSED({ unmapped_count: 1 }), false).textContent ?? ""; + expect(t).toContain("tied to itself"); + expect(t).not.toContain("Tie-out passed"); + expect(t).toContain("the same mapping"); + expect(t).toContain("1"); // unmapped_count — the only signal left + expect(t).toContain("Enter the statement's own totals"); + }); + + it("…and DOES call it a pass on the identical response when totals were stated", () => { + const t = renderT12Result(mount(), PASSED({ unmapped_count: 1 }), true).textContent ?? ""; + expect(t).toContain("Tie-out passed against the stated totals"); + expect(t).not.toContain("tied to itself"); + }); + + it("renders a stopped tie-out with its deltas and reconciling items, and no adjusted NOI", () => { + const t = renderT12Result(mount(), STOPPED(), true).textContent ?? ""; + expect(t).toContain("Tie-out STOPPED"); + expect(t).toContain("Owner draw"); + expect(t).toContain("$90,000"); + expect(t).not.toContain("Adjusted NOI"); + }); + + it("says the derived views were never calculated, not merely hidden", () => { + // `add_back_questions` and `run_rate_vs_trailing` are computed only past the gate, so "hidden" + // would misdescribe what happened and invite someone to look for a toggle. + expect(renderT12Result(mount(), STOPPED(), true).textContent).toContain("never calculated"); + }); + + it("shows stated against mapped side by side, which is the whole comparison", () => { + const t = renderT12Result(mount(), STOPPED(), true).textContent ?? ""; + expect(t).toContain("$1,330,000"); // stated expense + expect(t).toContain("$1,240,000"); // mapped expense + }); +}); + +describe("past the gate", () => { + it("renders the adjusted NOI, the treatments and the run-rate movers", () => { + const t = renderT12Result(mount(), PASSED(), true).textContent ?? ""; + expect(t).toContain("$2,140,000"); + expect(t).toContain("Legal settlement"); + expect(t).toContain("Roof replacement"); + expect(t).toContain("Bad debt"); + }); + + it("renders add-backs as QUESTIONS with the number behind each, never as applied", () => { + const t = renderT12Result(mount(), PASSED(), true).textContent ?? ""; + expect(t).toContain("Questions to ask before you price it"); + expect(t).toContain("Is this owner-managed?"); + expect(t).toContain("never applied for you"); + }); + + it("says when the unmapped list is a page of a larger set", () => { + const t = renderT12Result(mount(), PASSED({ + unmapped_count: 120, + unmapped: Array.from({ length: 50 }, (_, i) => ({ description: `L${i}`, amount: 10 })), + }), true).textContent ?? ""; + expect(t).toContain("Showing 50 of 120 unmapped lines"); + }); +}); + +describe("the handoff into the rent-roll scrub", () => { + it("supplies only the two fields a trailing twelve can honestly supply", () => { + // `prior_bad_debt`, `occupancy_pct` and `prior_occupancy_pct` describe a PRIOR period and a unit + // inventory. Filling them from this period to make a check run would be the defect the scrub is + // written to refuse, committed from the outside — so the check that needs them stays not-run. + expect(incomeFromT12(PASSED())).toEqual({ gross_potential_rent: 3_600_000, bad_debt: 41_000 }); + }); + + it("supplies nothing when the mapping produced no categories", () => { + expect(incomeFromT12(PASSED({ by_category: undefined }))).toEqual({}); + }); +}); diff --git a/apps/web/src/proforma/t12Card.ts b/apps/web/src/proforma/t12Card.ts new file mode 100644 index 00000000..b0607850 --- /dev/null +++ b/apps/web/src/proforma/t12Card.ts @@ -0,0 +1,347 @@ +/** The seller's trailing twelve, normalised — and the gate that refuses to guess, made reachable. + * + * `services/api/src/aec_api/t12.py` exists for one refusal, stated in its own module docstring: + * + * > income, expense and NOI must reconcile *before* and *after* mapping, or the engine stops and + * > lists the reconciling items. It does not publish an adjusted NOI on top of a mapping that lost + * > money. + * + * `ApiClient.normalizeT12()` was written for it and **no screen ever called it** — frozen in + * `apps/web/src/api/clientCallers.test.ts`'s `UNCALLED` list. + * + * T12-SELFTIE — WHAT MEASURING IT FOUND, AND IT IS WORTH MORE THAN THE WIRING + * -------------------------------------------------------------------------- + * `normalize()`'s docstring says what it does, and the consequence is easy to miss: *"When the + * caller supplies source totals they are the reference the mapping must reproduce; **without them + * the sum of the source lines is**."* Both sides of the comparison are then computed from the same + * mapped rows, so the deltas are zero by construction and **the gate passes vacuously.** + * + * Measured on one T-12 carrying a single unmapped $90,000 line, through the real engine: + * + * no stated totals reconciles: true deltas all 0.0 adjusted_noi: 3,180,000 + * with stated totals reconciles: false expense −90,000 stopped: true · adjusted_noi: null + * + * The $90k reclass — precisely what the gate exists to catch — is invisible in the first case, and + * the engine publishes an adjusted NOI on top of it. That is not an engine defect: it is a **caller + * obligation that never had a caller.** *A guard is only as sound as the evidence it is handed* — + * the CLASH-TRUNC lesson one layer over, where a panel fed the guard a page of a matrix and declared + * it whole. Here a caller can feed it a reference derived from the answer. + * + * So stated totals are the PRIMARY input here, not an optional extra, and with them absent this card + * never prints a passing tie-out. It says the mapping tied to itself and puts `unmapped_count` — + * the only signal left — where the verdict would otherwise be. + * + * WHY IT LIVES BESIDE THE RENT-ROLL SCRUB + * A T-12 is acquisition diligence and the Underwriting tab would be the tidier home. It is here + * because `rentRollScrub` takes an `income` body and five of its seven checks report + * `applicable: false` for want of one — so the normalised output is the scrub's missing input, and + * the handoff has to be adjacent to be found at all. + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; +import { money } from "./format"; + +type T12 = Awaited>; + +/** One pasted T-12 line. */ +export interface T12Line { description: string; amount: number } + +/** + * Parse a pasted T-12: one line per row, `descriptionamount`, where the separator is a tab or + * the LAST comma on the line. + * + * The last comma rather than the first, deliberately: account names carry commas + * ("Repairs, maintenance & turnover") and amounts do not carry tabs. Splitting on the first comma + * silently truncates the description and, worse, reads the remainder as the amount — which parses, + * so the failure would be a wrong number rather than an error. Currency marks, thousands separators + * and parenthesised negatives are all accepted because that is what a spreadsheet paste contains. + */ +export function parseT12(text: string): { lines: T12Line[]; skipped: string[] } { + const lines: T12Line[] = []; + const skipped: string[] = []; + for (const raw of text.split(/\r?\n/)) { + const row = raw.trim(); + if (!row) continue; + const tab = row.lastIndexOf("\t"); + const cut = tab >= 0 ? tab : row.lastIndexOf(","); + if (cut <= 0) { skipped.push(row); continue; } + const desc = row.slice(0, cut).trim().replace(/^"|"$/g, ""); + const amount = parseAmount(row.slice(cut + 1)); + if (!desc || amount === null) { skipped.push(row); continue; } + lines.push({ description: desc, amount }); + } + return { lines, skipped }; +} + +/** `$1,234.50`, `(1,234.50)` and `-1234.5` all parse; anything else is `null`, never 0. */ +export function parseAmount(s: string): number | null { + const t = s.trim().replace(/^"|"$/g, ""); + if (!t) return null; + const negative = /^\(.*\)$/.test(t); + const digits = t.replace(/[()$£€\s,]/g, ""); + if (!/^-?\d*\.?\d+$/.test(digits)) return null; + const n = parseFloat(digits); + return negative ? -Math.abs(n) : n; +} + +const meta = (html: string, colour?: string) => + `
${html}
`; + +/** Did the caller give the engine an independent reference to tie against? */ +export function hasStatedTotals(t: { income?: number | null; expense?: number | null }): boolean { + return typeof t.income === "number" && isFinite(t.income) + && typeof t.expense === "number" && isFinite(t.expense); +} + +/** Render one normalisation result. `stated` says whether the caller supplied source totals. */ +export function renderT12Result(host: HTMLElement, r: T12, stated: boolean): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + + // ---- THE VERDICT, and the three cases are genuinely different claims ------------------------- + if (r.stopped) { + el.insertAdjacentHTML("beforeend", + `
Tie-out STOPPED — no adjusted NOI
` + + meta(`Source and mapped totals disagree, so nothing derived is published. ` + + `Δ income ${esc(money(r.tie_out.deltas.income ?? 0))} · ` + + `Δ expense ${esc(money(r.tie_out.deltas.expense ?? 0))} · ` + + `Δ NOI ${esc(money(r.tie_out.deltas.noi ?? 0))} ` + + `(tolerance ${esc(money(r.tie_out.tolerance))}).`)); + if (r.reconciling_items?.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + r.reconciling_items.map((i) => + `` + + ``).join("") + + `
Reconciling itemamount
${esc(i.description || i.issue)}${i.amount == null ? "—" : esc(money(i.amount))}
`); + } + el.insertAdjacentHTML("beforeend", meta( + `Resolve these and re-run — the add-back questions and the run-rate view are computed only past ` + + `the gate, so they are not merely hidden, they were never calculated.`)); + } else if (!stated) { + // THE CASE THIS CARD EXISTS FOR. `reconciles: true` here means the mapping was compared with a + // total derived from the same mapping, so it cannot fail. Printing "passed" would be a claim the + // response does not support, and the engine's own docstring says why that claim is dangerous. + el.insertAdjacentHTML("beforeend", + `
Tie-out tied to itself — not a check
` + + meta(`No stated income/expense totals were supplied, so the engine reconciled the mapping ` + + `against a figure derived from the same mapping. The deltas are zero ` + + `by construction and a lost line cannot show up here. ` + + `${r.unmapped_count} line(s) did not map — that is the only signal ` + + `left. Enter the statement's own totals to make this a real gate.`)); + } else { + el.insertAdjacentHTML("beforeend", + `
Tie-out passed against the stated totals
` + + meta(`The mapping reproduces income ${esc(money(r.source_totals.income ?? 0))} and expense ` + + `${esc(money(r.source_totals.expense ?? 0))} as stated, so the derived views below are ` + + `safe to read.`)); + } + + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `
statedmapped
Income${esc(money(r.source_totals.income ?? 0))}${esc(money(r.mapped_totals.income ?? 0))}
Expense${esc(money(r.source_totals.expense ?? 0))}${esc(money(r.mapped_totals.expense ?? 0))}
NOI${esc(money(r.source_totals.noi ?? 0))}${esc(money(r.mapped_totals.noi ?? 0))}
` + + meta(`${r.line_count} line(s) mapped` + + (r.unmapped_count ? ` · ${r.unmapped_count} unmapped` : ""))); + + if (r.unmapped?.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + r.unmapped.map((u) => `` + + ``).join("") + + `
Did not mapamount
${esc(u.description)}${esc(money(u.amount))}
`); + // `unmapped` is capped at 50 server-side while `unmapped_count` is not — the same page-as-whole + // shape the NER card guards. Here it is sharper: with no stated totals this list IS the verdict. + if (r.unmapped.length < r.unmapped_count) { + el.insertAdjacentHTML("beforeend", meta( + `Showing ${r.unmapped.length} of ${r.unmapped_count} unmapped lines.`, "var(--status-warn)")); + } + } + + // ---- everything below exists only past the gate --------------------------------------------- + if (r.adjusted_noi != null) { + el.insertAdjacentHTML("beforeend", + `
${esc(money(r.adjusted_noi))}
` + + meta(`Adjusted NOI — one-time income removed, one-time expense added back, capital below the ` + + `line.`)); + } + + if (r.one_time_items?.length || r.capital_items?.length) { + const rows = [ + ...(r.one_time_items ?? []).map((i) => [`one-time ${i.kind}`, i.description, i.amount] as const), + ...(r.capital_items ?? []).map((i) => ["capital", i.description, i.amount] as const), + ]; + el.insertAdjacentHTML("beforeend", + `` + + `` + + rows.map(([k, d, a]) => `` + + ``).join("") + + `
Treatmentlineamount
${esc(k)}${esc(d)}${esc(money(a))}
`); + } + + if (r.run_rate_vs_trailing?.length) { + const moved = r.run_rate_vs_trailing.filter((x) => Math.abs(x.delta) > 0.5); + if (moved.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + moved.map((x) => `` + + `` + + `` + + ``).join("") + + `
Run rate vs trailingtrailinglast 3 × 4Δ
${esc(x.label ?? x.category)}${esc(money(x.trailing))}${esc(money(x.run_rate))}${esc(money(x.delta))}
`); + } + } + + // Add-backs are QUESTIONS and the engine never applies them. Rendering them as findings would + // invite the reader to treat them as already priced in, which is the 15–25% NOI miss they exist + // to prevent. The number behind each one is rendered because the engine's docstring says the + // number is the point — and it had to be DECLARED on the client type first, which it was not. + if (r.add_back_questions?.length) { + el.insertAdjacentHTML("beforeend", + `
Questions to ask before you price it
` + + `` + + r.add_back_questions.map((q) => + `` + + ``).join("") + + `
${esc(q.finding)}` + + (q.amount != null ? ` ${esc(money(q.amount))}` + + (q.pct_of_income != null ? ` · ${q.pct_of_income}% of income` : "") + `` : "") + + `
${esc(q.question)}
${esc(q.severity)}
` + + meta(`These are never applied for you — an owner-managed property that a third-party manager ` + + `will run costs more than the T-12 shows.`)); + } else if (!r.stopped) { + el.insertAdjacentHTML("beforeend", meta(`No owner-operated tells in this statement.`)); + } + + host.appendChild(el); + return el; +} + +export interface T12Ctx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; + /** Re-run the rent-roll scrub with an income statement derived from this T-12. */ + scrubWithIncome?: (income: Record) => void; +} + +/** The income fields `rent_scrub.py` reads, taken from a normalised T-12's categories. + * + * Only the two it can supply. `prior_bad_debt`, `occupancy_pct` and `prior_occupancy_pct` describe a + * PRIOR period and a unit inventory, which a single trailing twelve does not carry — so the check + * that needs them stays `applicable: false` and says so. Filling them with this period's figures to + * make the check run would be the defect the scrub is written to refuse, committed from the outside. + */ +export function incomeFromT12(r: T12): Record { + const by = new Map((r.by_category ?? []).map((b) => [b.category, b.amount])); + const out: Record = {}; + const gpr = by.get("gross_potential_rent"); + const bad = by.get("bad_debt"); + if (typeof gpr === "number") out.gross_potential_rent = gpr; + if (typeof bad === "number") out.bad_debt = bad; + return out; +} + +export function renderT12Card(root: HTMLElement, ctx: T12Ctx): HTMLElement { + const host = document.createElement("div"); + host.id = "pf-t12"; + host.className = "fin-card"; + host.style.marginTop = "10px"; + host.innerHTML = `
Seller's T-12 — normalise and tie out
` + + `
Paste the statement (one line each, description, amount), then ` + + `give its own stated totals. The totals are what makes this a check — without ` + + `them the mapping is reconciled against itself and cannot fail.
`; + + const ta = document.createElement("textarea"); + ta.rows = 6; + ta.className = "portal-filter"; + ta.style.cssText = "width:100%;margin-top:6px;font-family:var(--mono, monospace);font-size:12px"; + ta.placeholder = "Gross potential rent, 3,600,000\nVacancy loss, (180,000)\nRepairs & maintenance, 240,000"; + host.appendChild(ta); + + const grid = document.createElement("div"); grid.className = "pf-form"; + const num = (label: string, title: string) => { + const w = document.createElement("label"); w.className = "pf-field"; + w.innerHTML = `${esc(label)}`; + const i = document.createElement("input"); + i.type = "number"; i.step = "any"; i.title = title; + w.appendChild(i); grid.appendChild(w); return i; + }; + const incIn = num("Stated income $", "The statement's own total income — the reference the mapping must reproduce."); + const expIn = num("Stated expense $", "The statement's own total operating expense."); + const unitsIn = num("Units (optional)", "Unit count, used only for the per-unit maintenance question."); + host.appendChild(grid); + + const actions = document.createElement("div"); actions.style.cssText = "margin-top:6px"; + const go = document.createElement("button"); go.className = "file-btn"; go.textContent = "Normalise + tie out"; + const toScrub = document.createElement("button"); toScrub.className = "file-btn"; + toScrub.textContent = "Use in the rent-roll scrub"; + toScrub.style.cssText = "margin-left:6px;display:none"; + actions.append(go, toScrub); host.appendChild(actions); + + const out = document.createElement("div"); host.appendChild(out); + root.appendChild(host); + + go.onclick = async () => { + const pid = ctx.projectId(); + if (!pid) { out.innerHTML = meta("Open a project first."); return; } + const { lines, skipped } = parseT12(ta.value); + if (!lines.length) { + out.innerHTML = meta("No parsable lines — each row needs a description and an amount.", + "var(--status-crit)"); + return; + } + const income = parseFloat(incIn.value), expense = parseFloat(expIn.value); + const stated = hasStatedTotals({ income, expense }); + const units = parseFloat(unitsIn.value); + const body: Record = { lines }; + if (stated) body.totals = { income, expense }; + + go.disabled = true; toScrub.style.display = "none"; + out.innerHTML = meta("mapping and reconciling…"); + ctx.setStatus("normalising the T-12…"); + try { + const r = await ctx.api.normalizeT12(pid, body, isFinite(units) ? units : undefined); + out.innerHTML = ""; + if (skipped.length) { + // Lines this client dropped never reach the engine, so they are absent from BOTH totals and + // invisible to the tie-out however it is run. Reported here because nothing downstream can. + out.insertAdjacentHTML("beforeend", meta( + `${skipped.length} pasted row(s) could not be read and were not sent: ` + + skipped.slice(0, 5).map((s) => esc(s)).join(" · ") + + (skipped.length > 5 ? ` … and ${skipped.length - 5} more` : ""), + "var(--status-warn)")); + } + renderT12Result(out, r, stated); + if (!r.stopped && ctx.scrubWithIncome) { + const inc = incomeFromT12(r); + if (Object.keys(inc).length) { + toScrub.style.display = ""; + toScrub.onclick = () => { + ctx.scrubWithIncome!(inc); + ctx.setStatus("re-running the rent-roll scrub with the normalised T-12"); + }; + } + } + ctx.setStatus(r.stopped ? "T-12 tie-out stopped" + : stated ? "T-12 tie-out passed" : "T-12 mapped (tie-out tied to itself)"); + } catch (e) { + out.innerHTML = meta(esc((e as Error).message), "var(--status-crit)"); + ctx.setStatus("T-12 normalisation failed"); + } finally { + go.disabled = false; + } + }; + + return host; +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 911f08b7..3fb71bff 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,43 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **T12-SELFTIE — a gate that could not fail, because nobody was handing it evidence** + *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/t12Card.test.ts` and the `UNCALLED` deletion in + `apps/web/src/api/clientCallers.test.ts`)* + + `services/api/src/aec_api/t12.py` exists for one refusal — *"it does not publish an adjusted NOI on + top of a mapping that lost money"* — and `ApiClient.normalizeT12()` was callerless. **Measuring what + a caller would get found something sharper than the missing screen.** `normalize()`'s docstring says + that without stated totals *"the sum of the source lines is"* the reference, so both sides of the + comparison come from the same mapped rows and the deltas are zero by construction. Measured through + the real engine on one T-12 with a single unmapped $90,000 line: + + | input | tie-out | adjusted NOI | + |---|---|---| + | no stated totals | `reconciles: true`, all deltas `0.0` | **published: $3,180,000** | + | with stated totals | `reconciles: false`, expense −90,000 | `stopped: true`, `null` | + + The reclass the gate exists to catch is invisible in the first case. **Not an engine defect: a + caller obligation that never had a caller** — *a guard is only as sound as the evidence it is + handed*, CLASH-TRUNC one layer over. **And the two responses are indistinguishable**: `reconciles` + is true either way, so "were totals stated" is a parameter of the renderer rather than something + inferred — inferring it would mean inferring it from the very field that cannot tell. + + Stated totals are the primary input now; absent them the card prints *"tied to itself — not a + check"* with `unmapped_count` where the verdict would be. It also closes the loop on RENTROLL-DARK: + five of the scrub's seven checks want an income statement, and a normalised T-12 is one, so the + handoff supplies `gross_potential_rent` and `bad_debt` — **and only those**, because the prior-period + fields would have to be invented and inventing them is the defect the scrub refuses, committed from + outside. + + Two more undeclared fields fell out: `run_rate_vs_trailing.label`, found by **the compiler** when the + card rendered it, and `add_back_questions.{amount, pct_of_income}`, where the engine calls each + finding *"a QUESTION with the number behind it"*. And `residualLandCard.ts`'s buttons carried + `className = "btn"`, which **matches no rule in `style.css`** — *a class name is not a style, and + nothing typechecks the gap*; six files under `portal/panels/` carry the same unstyled class and are + left for Lane B. + - ✅ ⭐ **RENTROLL-DARK — two engines that say whether you can believe the rent roll, and no screen called either** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/rentRollQuality.test.ts` and two `UNCALLED` deletions in From f8bae562834ddefe182e30bb8daad6d14d4a37e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 04:28:57 +0000 Subject: [PATCH 05/21] COVENANT-DARK: two verdicts that read clean when nothing was evaluated services/api/src/aec_api/covenants.py opens with the case for itself: "A borrower with clean financials who files on day nine of a 'ten business days' notice -- counted from the lender's notice date, not from the day it landed -- has breached exactly as surely as one who missed a DSCR test." ApiClient.loanCovenants() was written for it and no screen ever called it, so nothing had yet had the chance to get its verdicts wrong. Measured through the real engine, not reasoned about: register state at_risk clean 2 uncomputable obligations, 2 untested covenants false false 1 of 3 covenants tested and passing false TRUE at_risk is `overdue > 0 or due_soon > 0 or breach > 0 or cure_period_open > 0`, so a register that could evaluate NOTHING reports not at risk -- every count it reads is zero for want of inputs rather than for want of problems. And clean is `bool(tested) and all(passing)`, which fails closed only at zero tested; one tested and two not is true. THE FOURTH ENGINE THIS SESSION WITH THAT SHAPE, after soft_clash (CLASH-TRUNC), rent_scrub and t12. Each one returns a boolean verdict beside a count of what it could not evaluate, each hands over the antidote -- covenants.py's own note says "an untested covenant is not a passing one" -- and each was defeated only by the absence of a consumer. VERDICT-COVERAGE is filed to make that a derived gate rather than a fifth hand-found instance. So coverage is the headline here and the verdicts are qualifiers on it: "N of M obligations computable, K of L covenants tested", and a register that evaluated nothing says so in those words. THE DUE DATE SHOWS ITS WORK, which is the engine's stated purpose: "the anchor date, the basis, the count, and every non-working day it skipped. A due date a reviewer cannot re-derive by hand is not a due date." All four fields were DECLARED NOWHERE in api/creDeal.ts, so the one property the engine was built for was invisible to its client by construction. Rendered now, with alternate_reading beside them: when the lender's notice date and our receipt date differ the obligation carries BOTH due dates, the day difference, and "confirm the counting basis with counsel before the calendar goes live". A calendar showing one of two possible dates is worse than one that admits it has two. Three covenant states, not two: the engine keeps cure_period_open apart from breach and says why in a `note` that was also undeclared, so the sentence could not reach a screen. TWO MISTAKES IN ITS OWN TESTS, BOTH THE SAME SHAPE. A mutation that did not compile printed no results line, and the harness read that as silence rather than as a failed experiment -- so it reported nothing about a property it had not tested. It now says so explicitly. And the three-state test first asserted only TEXT, every word of which comes from the server, so painting a curable breach and an uncured one the same colour PASSED. `curable !== uncured` was the next draft, and a mutation giving a curable breach the PASSING colour satisfied it while being worse than folding the two together. Asserting one inequality is not asserting that three states are three states; all three are compared pairwise now. FIXED: day() formatted through toLocaleDateString, which is environment- dependent -- this Node's ICU renders en-GB short September as "Sept", another build renders "Sep", so the test pinning it would pass or fail on which runtime executed it, and a calendar read across machines would spell its dates differently on two of them. Formatted from a fixed month table now. FIXED: the localStorage key had no readable stem. clearCacheKeys.test.ts walks every localStorage call and refuses an argument it cannot resolve -- "an unread key is an unclassified key", because Clear cached data then makes a decision about it that nothing has checked. The first draft built the key through a helper, so the argument began with ${ and had no stem. The prefix is a literal at the call site now. It is KEPT by that button, which falls out of the denylist design rather than needing a declaration: a hand-entered register has no server copy, so clearing it loses work rather than evicting a cache. The register lives in localStorage per project, the way testfitTab.ts keeps a unit mix -- a working register, not a shared system of record, and the card says so where somebody might otherwise rely on it. A register module would make it shared and is a product decision; recorded as chosen-against rather than overlooked. Verified: tsc --noEmit clean, eslint clean, npm run build clean, 268 files / 2,905 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 72 +++++ apps/web/src/api/clientCallers.test.ts | 3 +- apps/web/src/api/creDeal.ts | 32 +- apps/web/src/proforma/covenantCard.test.ts | 215 +++++++++++++ apps/web/src/proforma/covenantCard.ts | 341 +++++++++++++++++++++ apps/web/src/proforma/proforma.ts | 9 +- docs/roadmap.md | 30 ++ 7 files changed, 692 insertions(+), 10 deletions(-) create mode 100644 apps/web/src/proforma/covenantCard.test.ts create mode 100644 apps/web/src/proforma/covenantCard.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 178453c2..9c1a01be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,78 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### COVENANT-DARK — two verdicts that read clean when nothing was evaluated + +`services/api/src/aec_api/covenants.py` opens with the case for itself: + +> A borrower with clean financials who files on day nine of a "ten business days" notice — counted +> from the lender's notice date, not from the day it landed — has breached exactly as surely as one +> who missed a DSCR test. + +`ApiClient.loanCovenants()` was written for it and **no screen ever called it**, so nothing had yet +had the chance to get its verdicts wrong. **Measured through the real engine, not reasoned about:** + +| register state | `at_risk` | `financial.clean` | what was evaluated | +|---|---|---|---| +| 2 uncomputable obligations, 2 untested covenants | `false` | `false` | **nothing** | +| 1 of 3 covenants tested and passing | `false` | **`true`** | one third | + +`at_risk` is `overdue > 0 or due_soon > 0 or breach > 0 or cure_period_open > 0`, so a register that +could evaluate nothing reports *not at risk* — every count it reads is zero **for want of inputs +rather than for want of problems**. And `clean` is `bool(tested) and all(passing)`, which fails closed +only at *zero* tested; one tested and two not is `true`. This is the **fourth** engine this session +with that shape, after `soft_clash`, `rent_scrub` and `t12`. + +The engine is not wrong and hands over the antidote to both — `summary.untested_covenants`, +`summary.uncomputable_obligations`, and its own note: *"an untested covenant is not a passing one."* + +### Added + +- **`apps/web/src/proforma/covenantCard.ts`**, on Budget & Capital beside the facility it is about. + Coverage is the headline — *"N of M obligations computable · K of L covenants tested"* — and the + verdicts are qualifiers on it. A register that evaluated nothing says so in those words, rather + than inheriting `at_risk: false`. +- **The due date shows its work**, which is the engine's stated purpose: *"the anchor date, the + basis, the count, and every non-working day it skipped. **A due date a reviewer cannot re-derive by + hand is not a due date.**"* All four fields were **declared nowhere in `api/creDeal.ts`** until this + change, so the one property the engine was built for was invisible to its client by construction. +- **Both readings when the clock starts disagree.** When the lender's notice date and our receipt + date differ, the obligation carries two due dates, the day difference and *"confirm the counting + basis with counsel before the calendar goes live."* A calendar showing one of two possible dates is + worse than one that admits it has two. +- **Three covenant states, not two.** The engine keeps `cure_period_open` apart from `breach` and + says why in a `note` that was also undeclared — *"A breach inside an open cure window is a different + conversation from one outside it"* — so the sentence could not reach a screen. +- An unreadable line in the actuals box is reported, because an absent actual is an **untested** + covenant: a typo would otherwise look exactly like a figure you never had. +- `apps/web/src/proforma/covenantCard.test.ts` — fourteen tests, seven mutations. + +### The two mistakes this found in its own tests, both the same shape + +- **A mutation that did not compile proved nothing, and printed nothing.** The runner emitted no + results line at all and the loop read that as silence rather than as a failed experiment. The + mutation harness now says so explicitly when no result line comes back. +- **Asserting one inequality is not asserting that three states are three states.** The three-state + test first checked only text — every word of which comes from the server — so painting a curable + breach and an uncured one the same colour **passed**. Asserting `curable !== uncured` was the next + draft, and a mutation giving a curable breach the *passing* colour satisfied it while being worse + than folding the two together. All three are now compared pairwise. + +### Fixed + +- **`day()` formatted through `toLocaleDateString`, which is environment-dependent.** This Node's ICU + renders `en-GB` short September as **"Sept"**; another build renders "Sep", so the test pinning it + would pass or fail on which runtime executed it — and a covenant calendar read across machines + would spell its dates differently on two of them. Formatted from a fixed month table now, the same + reasoning that made `test_route_reachability`'s verdict invariant under file order. +- **The localStorage key had no readable stem.** `apps/web/src/ui/clearCacheKeys.test.ts` walks every + `localStorage.*Item` call and refuses an argument it cannot resolve — *"an unread key is an + unclassified key"*, because **Clear cached data** then makes a decision about it that nothing has + checked. The first draft built the key through a helper, so the argument began with `${` and had no + stem; the prefix is a literal at the call site now. It is *kept* by that button, which falls out of + the denylist design rather than needing a declaration — a hand-entered register has no server copy, + so clearing it loses work rather than evicting a cache. + ### T12-SELFTIE — a gate that could not fail, because nobody was handing it evidence `services/api/src/aec_api/t12.py` exists for one refusal, stated in its own module docstring: diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index fecac962..31543d42 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -304,7 +304,8 @@ const UNCALLED: readonly string[] = [ "energyModel", "equipmentSpecCheck", "expandMacro", "feasibilityLotSupply", "feasibilitySellout", "holdSell", "importFamilyPack", "layoutVerify", "listMacros", "listingReso", - "liveStream", "loanCovenants", "massingOptionRecipes", "mcpTools", + // `loanCovenants` left this list 2026-09-25 — `proforma/covenantCard.ts` on Budget & Capital. + "liveStream", "massingOptionRecipes", "mcpTools", "modelAdjacency", "moduleCalc", "myWork", // `netEffectiveRent` and `rentRollScrub` left this list 2026-09-25 — `proforma/rentRollQuality.ts` // renders both under the Operations rent roll. Shipped with R20, called by nothing. diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index cd99d00d..392d91ce 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -138,18 +138,34 @@ export function withCreDeal>(Base: TBase) { `/projects/${pid}/contracts/review`, { method: "POST", body: JSON.stringify({ contract_type: contractType, findings, document }) }); } - /** CRE-COVENANT — the loan covenant + reporting register (day-count basis, clock start). */ + /** CRE-COVENANT — the loan covenant + reporting register (day-count basis, clock start). + * + * FIELDS DECLARED 2026-09-25, AND THE REASON IS THE ENGINE'S OWN PURPOSE. `covenants.py`'s module + * docstring says the calculated due date *"shows its work: the anchor date, the basis, the count, + * and every non-working day it skipped. **A due date a reviewer cannot re-derive by hand is not a + * due date.**"* — and `anchor_date`, `non_working_days_skipped`, `delivered_date` and `days_early` + * were declared nowhere here, so the one property the engine was built for was invisible to its + * client by construction. Likewise the covenant `note` (*"A breach inside an open cure window is a + * different conversation from one outside it"*) and `actual` / `cure_days`: the sentence that + * distinguishes the two states could not reach a screen. */ loanCovenants(pid: string, loan: unknown, actuals?: Record) { - return this.json<{ loan: { name: string; lender: string }; at_risk: boolean; + return this.json<{ loan: { name: string | null; lender: string | null }; at_risk: boolean; summary: Record; - reporting: { obligations: { name: string; computable: boolean; due_date?: string; - day_basis?: string; clock_start?: string; anchor_source?: string; status?: string; - risk?: string; days_remaining?: number; clock_start_matters?: boolean; - alternate_reading?: { due_date: string; days_difference: number; warning: string } }[]; + reporting: { as_of: string; horizon_days: number; note: string; + obligations: { name: string; computable: boolean; reason?: string; due_date?: string; + day_basis?: string; clock_start?: string; anchor_source?: string; anchor_date?: string; + days?: number; non_working_days_skipped?: { date: string; why: string }[]; + delivered_date?: string | null; days_early?: number; + status?: string; risk?: string; days_remaining?: number | null; clock_start_matters?: boolean; + alternate_reading?: { clock_start: string; due_date: string; days_difference: number; + warning: string } }[]; upcoming: unknown[]; overdue: unknown[]; not_computable: { name: string; reason: string }[]; counts: Record }; - financial: { covenants: { name: string; tested: boolean; passing?: boolean; status?: string; - headroom?: number; cure_ends?: string | null; reason?: string }[]; + financial: { as_of: string; note: string; + covenants: { name: string; tested: boolean; passing?: boolean; status?: string; + actual?: number; threshold?: number; direction?: string; frequency?: string; + headroom?: number; cure_days?: number | null; cure_ends?: string | null; + note?: string; reason?: string }[]; untested: { name: string; reason: string }[]; counts: Record; clean: boolean } }>( `/projects/${pid}/loan/covenants`, diff --git a/apps/web/src/proforma/covenantCard.test.ts b/apps/web/src/proforma/covenantCard.test.ts new file mode 100644 index 00000000..56af5562 --- /dev/null +++ b/apps/web/src/proforma/covenantCard.test.ts @@ -0,0 +1,215 @@ +import { describe, expect, it } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { day, parseActuals, renderRegister } from "./covenantCard"; + +/** + * CRE-COVENANT shipped complete and `ApiClient.loanCovenants()` was callerless, so nothing had yet + * had the chance to get its two verdicts wrong. Both are vacuous on an unevaluated register — + * **measured through the real engine, not reasoned about**: + * + * register state at_risk financial.clean + * 2 uncomputable obligations, 2 untested covenants false false + * 1 of 3 covenants tested and passing false TRUE + * + * `at_risk` is `overdue > 0 or due_soon > 0 or breach > 0 or cure_period_open > 0` — every count it + * reads is zero for want of inputs rather than for want of problems. `clean` is + * `bool(tested) and all(passing)`, which fails closed only when ZERO covenants are tested. + * + * What is pinned here is therefore coverage-before-verdict, the three covenant states (the engine + * keeps `cure_period_open` apart from `breach` and says why), and the four fields that make a due + * date re-derivable — which the engine's docstring calls its purpose and which were declared nowhere + * in `api/creDeal.ts` until this change. + */ + +type Register = Awaited>; + +const OBLIGATION = (over: Partial = {}) => ({ + name: "Quarterly financials", computable: true, days: 10, day_basis: "business", + clock_start: "our_receipt", anchor_date: "2026-09-03", anchor_source: "our receipt", + due_date: "2026-09-17", + non_working_days_skipped: [{ date: "2026-09-05", why: "weekend" }, + { date: "2026-09-06", why: "weekend" }], + delivered_date: null, status: "outstanding", days_remaining: 8, risk: "ok", ...over, +}); + +const REG = (over: Record = {}): Register => ({ + loan: { name: "Senior facility", lender: "Example Bank" }, + at_risk: false, + summary: { overdue_filings: 0, filings_due_soon: 0, covenant_breaches: 0, in_cure_period: 0, + untested_covenants: 0, uncomputable_obligations: 0 }, + reporting: { + as_of: "2026-09-09", horizon_days: 90, + note: "Due dates show their anchor, basis and skipped non-working days so a reviewer can re-derive them by hand.", + obligations: [OBLIGATION()], upcoming: [], overdue: [], not_computable: [], + counts: { total: 1, computable: 1, outstanding: 1, overdue: 0, due_soon: 0, filed_late: 0 }, + }, + financial: { + as_of: "2026-09-09", + note: "A covenant with no supplied actual is reported UNTESTED with what it needed — an untested covenant is not a passing one.", + covenants: [{ name: "DSCR", tested: true, passing: true, status: "pass", actual: 1.41, + threshold: 1.25, headroom: 0.16 }], + untested: [], counts: { total: 1, tested: 1, untested: 0, passing: 1, breach: 0, cure_period_open: 0 }, + clean: true, + }, + ...over, +} as Register); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; + +describe("reading the actuals", () => { + it("accepts `=` and `:`, and strips percent and thousands marks", () => { + expect(parseActuals("dscr = 1.41\nltv: 62%\ndebt_yield = 0.09").actuals) + .toEqual({ dscr: 1.41, ltv: 62, debt_yield: 0.09 }); + }); + + it("reports a line it cannot read instead of dropping it", () => { + // An unreadable actual is an ABSENT actual, which the engine reports as an untested covenant — + // so a typo is indistinguishable from a figure you never had unless the client says so. + const { actuals, skipped } = parseActuals("dscr = one point four\nltv = 0.62"); + expect(actuals).toEqual({ ltv: 0.62 }); + expect(skipped).toEqual(["dscr = one point four"]); + }); +}); + +describe("dates", () => { + it("formats an ISO date", () => expect(day("2026-09-17")).toBe("17 Sep 2026")); + it("returns the input unchanged rather than 'Invalid Date'", () => expect(day("soon")).toBe("soon")); + it("renders an absent date as a dash, not as today", () => expect(day(null)).toBe("—")); +}); + +describe("coverage before verdict", () => { + // THE SHARPEST CASE. Every count `at_risk` reads is zero because nothing could be evaluated. + it("refuses to let `at_risk: false` stand for a register that evaluated nothing", () => { + const t = renderRegister(mount(), REG({ + at_risk: false, + summary: { overdue_filings: 0, filings_due_soon: 0, covenant_breaches: 0, in_cure_period: 0, + untested_covenants: 2, uncomputable_obligations: 2 }, + reporting: { ...REG().reporting, obligations: [], counts: { total: 2, computable: 0 }, + not_computable: [{ name: "Quarterly financials", reason: "no anchor date" }, + { name: "Annual audit", reason: "no anchor date" }] }, + financial: { ...REG().financial, covenants: [], clean: false, + untested: [{ name: "DSCR", reason: "no actual supplied" }, + { name: "Debt yield", reason: "no actual supplied" }], + counts: { total: 2, tested: 0, untested: 2, passing: 0, breach: 0, cure_period_open: 0 } }, + })).textContent ?? ""; + expect(t).toContain("Nothing in this register could be evaluated"); + expect(t).toContain("an absence of inputs, not an absence of risk"); + expect(t).toContain("0 of 2 obligations computable"); + expect(t).toContain("0 of 2 covenants tested"); + expect(t).toContain("no anchor date"); + }); + + // `clean` fails closed only at ZERO tested. One tested and two not is `true`. + it("never renders a partially-tested register as clean", () => { + const t = renderRegister(mount(), REG({ + summary: { ...REG().summary, untested_covenants: 2 }, + financial: { ...REG().financial, clean: true, + untested: [{ name: "Debt yield", reason: "no actual supplied" }, + { name: "LTV", reason: "no actual supplied" }], + counts: { total: 3, tested: 1, untested: 2, passing: 1, breach: 0, cure_period_open: 0 } }, + })).textContent ?? ""; + expect(t).toContain("1 of 3 covenants tested"); + expect(t).toContain("could not be tested"); + expect(t).toContain("an untested covenant is not a passing one"); + }); + + it("…and says nothing of the sort when every covenant was tested", () => { + expect(renderRegister(mount(), REG()).textContent).not.toContain("could not be tested"); + }); +}); + +describe("the due date shows its work", () => { + // The engine's stated purpose: "A due date a reviewer cannot re-derive by hand is not a due date." + it("renders the anchor, the basis, the count and the skipped days", () => { + const t = renderRegister(mount(), REG()).textContent ?? ""; + expect(t).toContain("10 business day(s) from our receipt 3 Sep 2026"); + expect(t).toContain("skipping 2 non-working day(s)"); + expect(t).toContain("5 Sep 2026 (weekend)"); + expect(t).toContain("17 Sep 2026"); + }); + + // A calendar showing one of two possible dates is worse than one that admits it has two. + it("renders BOTH readings when the two clock starts disagree", () => { + const t = renderRegister(mount(), REG({ + reporting: { ...REG().reporting, obligations: [OBLIGATION({ + clock_start_matters: true, + alternate_reading: { clock_start: "lender_notice", due_date: "2026-09-15", + days_difference: 2, + warning: "The two clock-start readings give different due dates — confirm the counting basis with counsel before the calendar goes live." }, + })] }, + })).textContent ?? ""; + expect(t).toContain("lender notice"); + expect(t).toContain("15 Sep 2026"); + expect(t).toContain("2 day(s) different"); + expect(t).toContain("confirm the counting basis with counsel"); + }); + + it("says an obligation with no computable date is undated, not 'not due yet'", () => { + const t = renderRegister(mount(), REG({ + reporting: { ...REG().reporting, obligations: [], counts: { total: 1, computable: 0 }, + not_computable: [{ name: "Annual audit", reason: "no anchor date" }] }, + financial: { ...REG().financial, counts: { total: 1, tested: 1, untested: 0, passing: 1, breach: 0, cure_period_open: 0 } }, + })).textContent ?? ""; + expect(t).toContain("Annual audit"); + expect(t).toContain("no anchor date"); + expect(t).toContain("no date at all"); + }); +}); + +describe("three covenant states, not two", () => { + it("separates a breach inside an open cure window from one outside it, and carries the note", () => { + const t = renderRegister(mount(), REG({ + at_risk: true, + summary: { ...REG().summary, in_cure_period: 1 }, + financial: { ...REG().financial, clean: false, + covenants: [{ name: "DSCR", tested: true, passing: false, status: "cure_period_open", + actual: 1.11, threshold: 1.25, headroom: -0.14, cure_days: 30, + cure_ends: "2026-10-09", + note: "A breach inside an open cure window is a different conversation from one outside it — this one is still curable." }], + counts: { total: 1, tested: 1, untested: 0, passing: 0, breach: 0, cure_period_open: 1 } }, + })).textContent ?? ""; + expect(t).toContain("cure period open"); + expect(t).toContain("still curable"); + expect(t).toContain("cure ends 9 Oct 2026"); + expect(t).not.toContain("1 breach"); + }); + + it("…and a curable breach does not READ like an uncured one", () => { + // Asserting the text alone passes a card that paints both red: every word above comes from the + // server, so the only thing the card contributes to the distinction is how it renders. + const state = (status: string, note: string) => { + const el = renderRegister(mount(), REG({ + financial: { ...REG().financial, clean: false, + covenants: [{ name: "DSCR", tested: true, passing: false, status, actual: 1.11, + threshold: 1.25, headroom: -0.14, note }], + counts: { total: 1, tested: 1, untested: 0, passing: 0, + breach: status === "breach" ? 1 : 0, + cure_period_open: status === "cure_period_open" ? 1 : 0 } }, + })); + const cells = [...el.querySelectorAll("td")]; + return cells.find((c) => c.textContent?.includes(status.replace(/_/g, " ")))?.style.color ?? ""; + }; + const passing = state("pass", ""); + const curable = state("cure_period_open", "still curable."); + const uncured = state("breach", "past its cure period."); + for (const [label, c] of [["pass", passing], ["cure", curable], ["breach", uncured]] as const) { + expect(c, `the ${label} state has no colour of its own`).toBeTruthy(); + } + expect(curable, "a curable breach reads as an uncured one").not.toBe(uncured); + expect(curable, "a curable breach reads as a PASS — worse than folding it into breach") + .not.toBe(passing); + expect(uncured).not.toBe(passing); + }); + + it("shows the actual against the threshold, which is what makes headroom checkable", () => { + const t = renderRegister(mount(), REG()).textContent ?? ""; + expect(t).toContain("1.41"); + expect(t).toContain("1.25"); + expect(t).toContain("0.16"); + }); +}); diff --git a/apps/web/src/proforma/covenantCard.ts b/apps/web/src/proforma/covenantCard.ts new file mode 100644 index 00000000..93967ead --- /dev/null +++ b/apps/web/src/proforma/covenantCard.ts @@ -0,0 +1,341 @@ +/** The loan covenant and reporting register — and the two verdicts that read clean when nothing was + * evaluated. + * + * `services/api/src/aec_api/covenants.py` opens with the case for itself: + * + * > A borrower with clean financials who files on day nine of a "ten business days" notice — + * > counted from the lender's notice date, not from the day it landed — has breached exactly as + * > surely as one who missed a DSCR test. + * + * `ApiClient.loanCovenants()` was written for it and **no screen ever called it**, so every + * property below had no consumer to get them wrong until this file existed. + * + * TWO VERDICTS, BOTH VACUOUS ON AN UNEVALUATED REGISTER — MEASURED, NOT REASONED + * ----------------------------------------------------------------------------- + * register state at_risk financial.clean + * 2 uncomputable obligations, 2 untested covenants false false + * 1 of 3 covenants tested and passing false TRUE + * + * `at_risk` is `overdue > 0 or due_soon > 0 or breach > 0 or cure_period_open > 0`, so a register + * that could evaluate **nothing** reports *not at risk* — every count it reads is zero for want of + * inputs rather than for want of problems. And `clean` is `bool(tested) and all(passing)`, which + * fails closed only when ZERO covenants are tested; one tested and two not is `true`. + * + * The engine is not wrong and hands over the antidote to both — `summary.untested_covenants`, + * `summary.uncomputable_obligations`, and its own note: *"A covenant with no supplied actual is + * reported UNTESTED with what it needed — an untested covenant is not a passing one."* So coverage + * is the headline here and the verdicts are qualifiers on it, never the other way round. + * + * THE DUE DATE SHOWS ITS WORK, WHICH IS THE ENGINE'S STATED PURPOSE + * `covenants.py`: *"the calculated due date shows its work: the anchor date, the basis, the count, + * and every non-working day it skipped. **A due date a reviewer cannot re-derive by hand is not a + * due date.**"* Those four fields were declared nowhere in `api/creDeal.ts` until this change — the + * one property the engine was built for was invisible to its client by construction. They are + * rendered, and `alternate_reading` with them: when the lender's notice date and our receipt date + * differ, the obligation carries BOTH due dates, the day difference and a warning to confirm the + * basis with counsel. A calendar that shows one of two possible dates is worse than one that admits + * it has two. + * + * WHERE THE REGISTER LIVES, stated rather than left to be discovered + * There is no `loan` module: the route is stateless and takes the register in the body. This card + * keeps it in `localStorage`, per project, the way `testfitTab.ts` keeps a unit mix — **a per-browser + * convenience, not a shared system of record**, and the card says so where somebody might otherwise + * rely on it. A register module would make it shared and is a product decision rather than a + * wiring one; recorded here as chosen-against rather than overlooked. + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; + +type Register = Awaited>; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +/** A realistic skeleton, so the body shape is discoverable without reading the route's docstring. */ +export const EXAMPLE_REGISTER = { + name: "Senior construction facility", + lender: "Example Bank", + holidays: ["2026-12-25", "2026-12-26"], + obligations: [ + { name: "Quarterly financials", days: 10, day_basis: "business", clock_start: "our_receipt", + lender_notice_date: "2026-09-01", received_date: "2026-09-03" }, + { name: "Annual audited accounts", days: 120, day_basis: "calendar", clock_start: "lender_notice", + period_end: "2026-12-31" }, + ], + covenants: [ + { name: "DSCR", metric: "dscr", direction: "min", threshold: 1.25, frequency: "quarterly", + cure_days: 30 }, + { name: "Debt yield", metric: "debt_yield", direction: "min", threshold: 0.09 }, + { name: "LTV", metric: "ltv", direction: "max", threshold: 0.65 }, + ], +}; + +/** `dscr = 1.41` / `ltv: 0.62` — one metric per line. Returns `null` for a line it cannot read, so a + * typo becomes a reported problem rather than a silently absent actual (and hence an untested + * covenant, which is exactly the state this card exists to make visible). */ +export function parseActuals(text: string): { actuals: Record; skipped: string[] } { + const actuals: Record = {}; + const skipped: string[] = []; + for (const raw of text.split(/\r?\n/)) { + const row = raw.trim(); + if (!row) continue; + const m = /^([A-Za-z_][\w.]*)\s*[=:]\s*(.+)$/.exec(row); + const n = m ? Number(m[2]!.replace(/[%\s,]/g, "")) : NaN; + if (!m || !isFinite(n)) { skipped.push(row); continue; } + actuals[m[1]!] = n; + } + return { actuals, skipped }; +} + +const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +/** `2026-09-17` → `17 Sep 2026`; anything unparsable comes back unchanged, never as "Invalid Date". + * + * Formatted from a fixed table rather than through `toLocaleDateString`, which was the first draft + * and was **environment-dependent**: this Node's ICU renders `en-GB` short September as "Sept", an + * older or differently-built one renders "Sep", and the test that pinned it would then pass or fail + * on which runtime happened to execute it. A covenant calendar is read across machines and a date + * that is spelled differently on two of them is the wrong kind of surprise — the same reason + * `test_route_reachability` had to make its own verdict invariant under file order. */ +export function day(iso?: string | null): string { + if (!iso) return "—"; + const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso); + if (!m) return iso; + const month = MONTHS[Number(m[2]) - 1]; + if (!month) return iso; + return `${Number(m[3])} ${month} ${m[1]}`; +} + +/** Render one evaluated register. */ +export function renderRegister(host: HTMLElement, r: Register): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + const rep = r.reporting, fin = r.financial; + const untested = fin.counts.untested ?? 0; + const uncomputable = rep.not_computable.length; + + // ---- COVERAGE FIRST. `at_risk` alone is false on a register that evaluated nothing. ----------- + const evaluatedNothing = (rep.counts.computable ?? 0) === 0 && (fin.counts.tested ?? 0) === 0; + const trouble = (r.summary.overdue_filings ?? 0) + (r.summary.covenant_breaches ?? 0) + + (r.summary.in_cure_period ?? 0); + const colour = trouble > 0 ? "var(--status-crit)" + : (untested || uncomputable) ? "var(--status-warn)" : "var(--status-good)"; + el.insertAdjacentHTML("beforeend", + `
` + + `${rep.counts.computable ?? 0} of ${rep.counts.total ?? 0} obligations computable · ` + + `${fin.counts.tested ?? 0} of ${fin.counts.total ?? 0} covenants tested` + + `
`); + + if (evaluatedNothing) { + // The sharpest case: every count `at_risk` reads is zero for want of inputs, so it says false. + el.insertAdjacentHTML("beforeend", meta( + `Nothing in this register could be evaluated. The engine reports ` + + `at_risk: false because every count it reads is zero — that is an absence of ` + + `inputs, not an absence of risk. Supply the dates and the actuals below before treating this ` + + `as a calendar.`, "var(--status-crit)")); + } else { + el.insertAdjacentHTML("beforeend", meta( + r.at_risk + ? `At risk — ${r.summary.overdue_filings ?? 0} overdue · ` + + `${r.summary.filings_due_soon ?? 0} due soon · ${r.summary.covenant_breaches ?? 0} breach · ` + + `${r.summary.in_cure_period ?? 0} in cure` + : `Nothing overdue, due soon, breached or in cure among what could be evaluated.`, + r.at_risk ? "var(--status-crit)" : undefined)); + } + + // `clean` is `bool(tested) and all(passing)` — true with one tested and two untested. The engine's + // own note is the wording; rendering the flag without it is what this card exists to refuse. + if (fin.clean && untested > 0) { + el.insertAdjacentHTML("beforeend", meta( + `Every covenant that was tested passed — and ${untested} could not be tested. ` + + esc(fin.note), "var(--status-warn)")); + } + + // ---- the reporting calendar ----------------------------------------------------------------- + const computable = rep.obligations.filter((o) => o.computable); + if (computable.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + computable.map((o) => { + const risk = o.risk === "overdue" ? "var(--status-crit)" + : o.risk === "due_soon" || o.risk === "late_filed" ? "var(--status-warn)" : ""; + // The engine's whole point: anchor, basis, count and the skipped days, so a reviewer can + // re-derive the date by hand. Rendering only `due_date` throws that away. + const skipped = o.non_working_days_skipped ?? []; + const work = `${o.days ?? "?"} ${esc(o.day_basis ?? "")} day(s) from ` + + `${esc(o.anchor_source ?? "")} ${day(o.anchor_date)}` + + (skipped.length ? `, skipping ${skipped.length} non-working day(s): ` + + skipped.slice(0, 6).map((s) => `${day(s.date)} (${esc(s.why)})`).join(", ") + + (skipped.length > 6 ? ` …` : "") : ""); + return `${day(o.due_date)}` + + `` + + ``; + }).join("") + + `
Obligationdueanchordays
${esc(o.name)}
${work}
` + + (o.clock_start_matters && o.alternate_reading + ? `
` + + `Under the ${esc(o.alternate_reading.clock_start.replace(/_/g, " "))} ` + + `reading it is due ${day(o.alternate_reading.due_date)} — ` + + `${Math.abs(o.alternate_reading.days_difference)} day(s) different. ` + + `${esc(o.alternate_reading.warning)}
` : "") + + `
${esc(o.status?.replace(/_/g, " ") ?? "")}` + + (o.days_early != null && o.delivered_date + ? ` ${o.days_early >= 0 ? `${o.days_early}d early` : `${-o.days_early}d late`}` : "") + + `${o.days_remaining ?? "—"}
`); + } + + if (uncomputable) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + rep.not_computable.map((n) => `` + + ``).join("") + + `
No due date could be computedneeds
${esc(n.name ?? "—")}${esc(n.reason ?? "—")}
` + + meta(`These carry no date at all — they are not "not due yet".`, "var(--status-warn)")); + } + + // ---- financial covenants -------------------------------------------------------------------- + const tested = fin.covenants.filter((c) => c.tested); + if (tested.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + tested.map((c) => { + // THREE states, not two. The engine separates `cure_period_open` from `breach` and says + // why in its own note; folding them together loses the only distinction that matters at + // the moment somebody has to act. + const colourFor = c.status === "breach" ? "var(--status-crit)" + : c.status === "cure_period_open" ? "var(--status-warn)" : "var(--status-good)"; + return `` + + `` + + ``; + }).join("") + + `
Covenantactualthresholdheadroomstate
${esc(c.name)}` + + (c.note ? `
${esc(c.note)}
` : "") + + `
${c.actual ?? "—"}${c.threshold ?? "—"}${c.headroom ?? "—"}${esc((c.status ?? "").replace(/_/g, " "))}` + + (c.cure_ends ? `
cure ends ${day(c.cure_ends)}
` : "") + + `
`); + } + + if (untested) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + fin.untested.map((u) => ``).join("") + + `
Not testedneeds
${esc(u.name)}${esc(u.reason)}
` + + meta(esc(fin.note), "var(--status-warn)")); + } + + el.insertAdjacentHTML("beforeend", meta( + `As of ${day(rep.as_of)} · horizon ${rep.horizon_days} days. ${esc(rep.note)}`)); + + host.appendChild(el); + return el; +} + +export interface CovenantCtx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; +} + +export function renderCovenantCard(root: HTMLElement, ctx: CovenantCtx): HTMLElement { + const host = document.createElement("div"); + host.id = "pf-covenants"; + host.className = "fin-card"; + host.style.marginTop = "10px"; + host.innerHTML = `
Loan covenants + reporting calendar
` + + `
Timing alone can breach a loan: a filing counted in business days ` + + `from the lender's notice is a different date from calendar days from your receipt, and the ` + + `engine computes both when they disagree.
`; + + const pid = ctx.projectId(); + // The key is spelled with its LITERAL prefix first, not built through a helper. + // `apps/web/src/ui/clearCacheKeys.test.ts` walks every `localStorage.*Item` call and refuses an + // argument it cannot resolve to a stem — *"an unread key is an unclassified key"*, because + // **Clear cached data** then makes a decision about it that nothing has checked. A template + // beginning with `${` has no stem to read; this one does. + // + // It is KEPT by that button rather than cleared, and that falls out of the denylist design rather + // than needing a declaration: `CACHE_KEY_PREFIXES` is empty and `isKeeper` is true for everything + // not named there. Right for this key — a hand-entered register has no server copy, so clearing it + // loses work rather than evicting a cache, the same argument `clearCache.ts` makes for the offline + // upload queue. + const load = (k: string, fallback: string) => { + try { return pid ? (localStorage.getItem(`covenant-register:${pid}:${k}`) ?? fallback) : fallback; } + catch { return fallback; } + }; + const save = (k: string, v: string) => { + try { if (pid) localStorage.setItem(`covenant-register:${pid}:${k}`, v); } + catch { /* private window */ } + }; + + const area = (label: string, rows: number, value: string, ph: string) => { + host.insertAdjacentHTML("beforeend", `
${esc(label)}
`); + const t = document.createElement("textarea"); + t.rows = rows; t.className = "portal-filter"; t.value = value; t.placeholder = ph; + t.style.cssText = "width:100%;font-family:var(--mono, monospace);font-size:12px"; + host.appendChild(t); return t; + }; + const regIn = area("Register (JSON)", 8, load("register", ""), + '{"name": …, "obligations": [...], "covenants": [...]}'); + const actIn = area("Actuals — one metric per line", 3, load("actuals", ""), "dscr = 1.41\nltv = 0.62"); + + const actions = document.createElement("div"); actions.style.cssText = "margin-top:6px"; + const go = document.createElement("button"); go.className = "file-btn"; go.textContent = "Evaluate register"; + const ex = document.createElement("button"); ex.className = "file-btn"; ex.textContent = "Load example"; + ex.style.marginLeft = "6px"; + actions.append(go, ex); host.appendChild(actions); + host.insertAdjacentHTML("beforeend", meta( + `Kept in this browser only, per project — a working register, not a shared system of ` + + `record. Nobody else sees what you enter here.`)); + + const out = document.createElement("div"); host.appendChild(out); + root.appendChild(host); + + ex.onclick = () => { regIn.value = JSON.stringify(EXAMPLE_REGISTER, null, 2); save("register", regIn.value); }; + + go.onclick = async () => { + const project = ctx.projectId(); + if (!project) { out.innerHTML = meta("Open a project first."); return; } + let loan: unknown; + try { + loan = JSON.parse(regIn.value); + } catch (e) { + out.innerHTML = meta(`The register is not valid JSON — ${esc((e as Error).message)}`, + "var(--status-crit)"); + return; + } + const { actuals, skipped } = parseActuals(actIn.value); + save("register", regIn.value); save("actuals", actIn.value); + + go.disabled = true; + out.innerHTML = meta("evaluating…"); + ctx.setStatus("evaluating the covenant register…"); + try { + const r = await ctx.api.loanCovenants(project, loan, actuals); + out.innerHTML = ""; + if (skipped.length) { + // An unreadable actual is an ABSENT actual, and an absent actual is an untested covenant — + // which the card below reports as untested without knowing a typo caused it. Said here. + out.insertAdjacentHTML("beforeend", meta( + `${skipped.length} actual(s) could not be read and were not sent: ` + + skipped.slice(0, 4).map((s) => esc(s)).join(" · ") + + `. A covenant whose metric is missing is reported untested below, so a typo here ` + + `looks exactly like a figure you never had.`, "var(--status-warn)")); + } + renderRegister(out, r); + ctx.setStatus(r.at_risk ? "covenant register: at risk" : "covenant register evaluated"); + } catch (e) { + out.innerHTML = meta(esc((e as Error).message), "var(--status-crit)"); + ctx.setStatus("covenant evaluation failed"); + } finally { + go.disabled = false; + } + }; + + return host; +} diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index 7201b6d4..e26bcbab 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -8,6 +8,7 @@ import { drawPackageLines } from "./drawPackage"; import { money, pct } from "./format"; import { renderMassingTab } from "./massingTab"; import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; +import { renderCovenantCard } from "./covenantCard"; import { renderRentRollQuality } from "./rentRollQuality"; import { renderT12Card } from "./t12Card"; import { renderTestFitTab } from "./testfitTab"; @@ -152,7 +153,13 @@ export class ProformaUI { const into = (el: HTMLElement, fn: () => void) => { const r = self.root; self.root = el; try { fn(); } finally { self.root = r; } }; this.overviewEl = sections.over; this.renderOverview(); if (sections.feas) into(sections.feas, () => { this.renderMassing(); this.renderTestFit(); this.renderResidualLand(); this.renderProperty(); }); - if (sections.cap) into(sections.cap, () => { this.renderBudget(); this.renderSourcesUses(); this.renderSpecialty(); }); + if (sections.cap) into(sections.cap, () => { + this.renderBudget(); this.renderSourcesUses(); this.renderSpecialty(); + // Beside the facility it is about: renderSourcesUses prints the loan amount and its fees. + renderCovenantCard(this.root, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + }); + }); const uwSec = sections.uw; if (uwSec) into(uwSec, () => { uwSec.appendChild(form); diff --git a/docs/roadmap.md b/docs/roadmap.md index 3fb71bff..9ab4b98c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,36 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **COVENANT-DARK — two verdicts that read clean when nothing was evaluated** *(S — + `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/covenantCard.test.ts` and the `UNCALLED` deletion in + `apps/web/src/api/clientCallers.test.ts`)* + + `ApiClient.loanCovenants()` was callerless, so nothing had yet had the chance to get CRE-COVENANT's + verdicts wrong. **Measured through the real engine:** + + | register state | `at_risk` | `financial.clean` | evaluated | + |---|---|---|---| + | 2 uncomputable obligations, 2 untested covenants | `false` | `false` | **nothing** | + | 1 of 3 covenants tested and passing | `false` | **`true`** | one third | + + `at_risk` reads four counts that are zero **for want of inputs rather than want of problems**, and + `clean` fails closed only at *zero* tested. **The fourth engine this session with that shape** — + after `soft_clash`, `rent_scrub` and `t12` — which is why VERDICT-COVERAGE below exists. + + Also rendered, all previously undeclared on the client: the four fields that make a due date + re-derivable (*"A due date a reviewer cannot re-derive by hand is not a due date"* is the engine's + own sentence, and its client could not see them), the `alternate_reading` when the two clock starts + disagree, and the covenant `note` distinguishing a breach inside an open cure window from one + outside it. + + **Two mistakes in its own tests, both the same shape.** A mutation that did not compile printed no + results line and the loop read that as silence rather than as a failed experiment. And the + three-state test first asserted only text — all of it from the server — so painting a curable breach + and an uncured one identically **passed**; `curable !== uncured` was the next draft, and a mutation + giving a curable breach the *passing* colour satisfied it while being worse than folding the two + together. *Asserting one inequality is not asserting that three states are three states.* + - ✅ ⭐ **T12-SELFTIE — a gate that could not fail, because nobody was handing it evidence** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/t12Card.test.ts` and the `UNCALLED` deletion in From f661a33a809442e00d1ffe56b60d18b10bc8a8b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 04:56:04 +0000 Subject: [PATCH 06/21] VERDICT-COVERAGE: the class found five times by hand, now derived Five engines in this session returned a boolean verdict beside a count of what they could not evaluate, and the verdict was only meaningful in light of the count: soft_clash coordinated over a page of the matrix (CLASH-TRUNC) rent_scrub clean bool(ran) and not failed -- 1 of 7 ran t12 tie_out.reconciles reference derived from the answer covenants clean, at_risk counts zero for want of inputs sequence_clash clean over the activities that had a date EVERY ONE OF THOSE ENGINES IS CAREFUL. Each hands the caller the coverage beside the verdict and three attach a sentence saying why -- rent_scrub.py: "a scrub that reports 'no findings' because half its inputs were missing is worse than no scrub; it launders absent data into apparent confidence." The defect is always in the CONSUMER, and four of the five had none, so nothing was wrong until somebody wrote one. Finding it a sixth time by hand is what this gate exists to prevent. THE VERDICT IS DERIVED STRUCTURALLY, NOT BY NAME. A subset verdict is a dict key whose value is `bool(A) and (all(...)|not B)` -- the bool(A) guard being the author writing down that the subset can be empty, which is precisely when the verdict means nothing. Its coverage is a sibling key, nested ones included, whose name by itself says something was not evaluated. A consumer is a web file calling the client method whose declared response type carries the verdict plus >=3 of its siblings. Every consumer must read at least one coverage field. Population: 5 subset verdicts, 616 client methods, 0 bare consumers. THE DERIVATION FOUND THREE INSTANCES NOBODY HAD FOUND BY HAND -- sequence_clash, fived and perf_budget; the last two have no client method at all, so the gate reports them unexposed rather than passing them silently. THE DEFECT IT FOUND ON ITS FIRST RUN. viewer/tools/analyseSection.ts printed "clean" over whatever fraction of the schedule was analysable. sequence_clash skips any activity with no location, no dates, or finish before start, EACH WITH A STATED REASON, and the panel rendered analyzed, finding_count, support_unscheduled_count and the not_covered note -- but never skipped_count. So on a schedule where 180 of 200 activities lack a location it read "20 dated locatable activities, 0 space contention, 0 install-before-support" with a green note and the status "clean". It now says "clean of N, M not checked", groups the skipped activities by the engine's own reason, and stops claiming ok while anything went unexamined. `skipped` -- the list carrying those reasons -- was declared nowhere in api/schedule.ts, so the reasons could not reach a screen even once somebody went looking. And resultNote had no way to say the third thing: its kinds were ok (green, which over an unexamined remainder is the appearance of a result) and bad (red, which claims a finding that is not there). `warn` added, on the --status-warn token; the two rules above it are literal hexes equal to the same tokens, left alone rather than bundled in, since light mode redefines them. FIVE THINGS THE FIRST DRAFTS GOT WRONG, EACH MEASURED RATHER THAN REASONED ABOUT. 1. Matching coverage-ish WORDS against client response types returned 24 methods, mostly unrelated senses of the word -- a sprinkler's max_coverage_m2_per_head, a dry_run flag, ok/writes beside a truncated list. A rule that needs an exemption list is a rule whose population is wrong. 2. Linking consumers by shared VOCABULARY cross-talked: it reported a net-effective-rent card as a consumer of sequence_clash, on skipped, skipped_count and findings. The link is the CALL SITE of the owning client method; nothing else claims "this file reads THIS response". 3. Treating `not_` as a coverage prefix matched not_covered, a prose note about a DIFFERENT dimension -- it would have blessed the one consumer the rule exists to catch. 4. Detecting a read as `.name` missed `const { not_applicable: notRun } = ...` and called a carefully-written card bare. 5. Widening that to a bare word boundary then matched the English word "skipped" inside an unrelated prose string in the same file -- the opposite error, one line later. AND MUTATING IT FOUND THE LIMIT IT DOES NOT COVER: wrapping the coverage render in `if (false)` leaves the text in place and PASSES. It is a source-level check -- the same bargain test_route_reachability makes for URL literals -- and the complement is the consumer's own test, where rentRollQuality.test.ts and covenantCard.test.ts assert the coverage is RENDERED, with mutations. Written down rather than discovered later. It replays the pre-fix consumer it was written from, frozen in the file rather than fetched with `git show`, because CI's checkout is shallow: it must re-find that instance, and must find its repair clean. Verified: ruff over the whole tree clean, manifest 1:1 with disk, tsc --noEmit clean, eslint clean, npm run build clean, 268 files / 2,905 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 71 +++++ CLAUDE.md | 32 +++ apps/web/src/api/schedule.ts | 1 + apps/web/src/style.css | 1 + apps/web/src/ui/result.ts | 10 +- apps/web/src/viewer/tools/analyseSection.ts | 26 +- docs/roadmap.md | 31 ++ services/api/run_tests.py | 2 +- services/api/test_verdict_coverage.py | 300 ++++++++++++++++++++ 9 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 services/api/test_verdict_coverage.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c1a01be..27141139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,77 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### VERDICT-COVERAGE — the class found five times by hand, now derived + +Five engines in this session returned a boolean verdict beside a count of what they could not +evaluate, and the verdict was only meaningful in light of the count: + +| engine | verdict | vacuous because | +|---|---|---| +| `soft_clash` | `coordinated` | fed a page of the matrix (CLASH-TRUNC) | +| `rent_scrub` | `clean` | `bool(ran) and not failed` — 1 of 7 checks ran | +| `t12` | `tie_out.reconciles` | reference derived from the answer | +| `covenants` | `clean`, `at_risk` | counts zero for want of inputs | +| `sequence_clash` | `clean` | over the activities that had a date and a location | + +Every one of those engines is careful: each hands the caller the coverage beside the verdict, and +three attach a sentence saying why. **The defect is always in the consumer**, and for four of the five +there was no consumer — so nothing was wrong until somebody wrote one. Finding it a sixth time by hand +is what `services/api/test_verdict_coverage.py` exists to prevent. + +### Added + +- **`services/api/test_verdict_coverage.py`.** A **subset verdict** is a dict key whose value is + `bool(A) and (all(…)/any(…) | not B)` — the `bool(A)` guard being the author writing down that the + subset can be empty, which is precisely when the verdict means nothing. Its **coverage** is a + sibling key, including nested ones, whose name by itself says something was not evaluated. A + **consumer** is a web file calling the client method whose declared response type carries the + verdict plus ≥3 of its siblings. Every consumer must read at least one coverage field. +- Population today: **5 subset verdicts, 616 client methods, 0 bare consumers** — and the derivation + found **three instances nobody had found by hand** (`sequence_clash`, `fived`, `perf_budget`). Two of + those have no client method at all, so the gate reports them as unexposed rather than passing them + silently. +- It replays the pre-fix consumer it was written from — frozen in the file, not fetched with + `git show`, because CI's checkout is shallow — and must re-find it, and must find its repair clean. + +### Fixed — the defect the gate found on its first run + +- **`viewer/tools/analyseSection.ts` printed "clean" over whatever fraction of the schedule was + analysable.** `sequence_clash` skips any activity with no location, no dates, or finish before + start, *each with a stated reason*, and the panel rendered `analyzed`, `finding_count`, + `support_unscheduled_count` and the `not_covered` note but never `skipped_count`. So on a schedule + where 180 of 200 activities lack a location it read *"20 dated locatable activities · 0 space + contention · 0 install-before-support"* with a green note and the status **clean**. It now says + `clean of N · M not checked`, groups the skipped activities by the engine's own reason, and stops + claiming `ok` while anything went unexamined. +- `skipped` — the list carrying those reasons — was **declared nowhere** in `api/schedule.ts`, so the + reasons could not reach a screen even once somebody went looking. +- **`resultNote` had no way to say the third thing.** Its kinds were `ok` (green, which over an + unexamined remainder is the appearance of a result) and `bad` (red, which claims a finding that is + not there). `warn` added, using the `--status-warn` token — the two rules above it are literal hexes + equal to the same tokens, left alone rather than bundled in, since light mode redefines them. + +### Five things the gate's first drafts got wrong, each measured + +1. **Matching coverage-ish WORDS** against client response types returned **24** methods, mostly + unrelated senses: a sprinkler's `max_coverage_m2_per_head`, a `dry_run` flag, `ok`/`writes` beside + a `truncated` list. *A rule that needs an exemption list is a rule whose population is wrong.* +2. **Linking consumers by shared vocabulary** cross-talked: it reported a net-effective-rent card as a + consumer of `sequence_clash`, on `skipped`, `skipped_count` and `findings`. The link is the call + site of the owning client method; nothing else claims *this file reads THIS response*. +3. **Treating `not_` as a coverage prefix** matched `not_covered`, a prose note about a *different* + dimension — it would have blessed the one consumer the rule exists to catch. +4. **Detecting a read as `.name`** missed `const { not_applicable: notRun } = s.counts` and called a + carefully-written card bare. +5. **Widening that to a bare word boundary** then matched the English word *"skipped"* inside an + unrelated prose string in the same file — the opposite error, one line later. + +And what it does **not** prove, found by mutating it: this is a source-level check. Wrapping the +coverage render in `if (false)` leaves the text in place and passes — measured, not assumed. Same +bargain `test_route_reachability` makes for URL literals; the complement is the consumer's own test, +where `rentRollQuality.test.ts` and `covenantCard.test.ts` assert the coverage is *rendered*, with +mutations. This gate's job is to stop a new consumer being written without one. + ### COVENANT-DARK — two verdicts that read clean when nothing was evaluated `services/api/src/aec_api/covenants.py` opens with the case for itself: diff --git a/CLAUDE.md b/CLAUDE.md index 9f2c4314..4afd1f69 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -455,6 +455,38 @@ trailing-whitespace mutation broke both derivations and the counts stayed equal. between two derivations that share a failure mode is not a check* — the crude count catches what the strict one cannot, and both mutations are now folded in and must be refused before the gate reports. +**An eighteenth joined them on 2026-09-25: `services/api/test_verdict_coverage.py`** — does a screen +that reads a verdict about a SUBSET also read what the engine could not evaluate? Five engines in one +session returned a boolean beside a count of the unevaluated, and the boolean was meaningless without +the count: `soft_clash.coordinated` over a page of the matrix (CLASH-TRUNC), `rent_scrub.clean` = +`bool(ran) and not failed` with 1 of 7 checks run, `t12.tie_out.reconciles` against a reference derived +from the answer, `covenants.clean`/`at_risk` on counts that are zero for want of inputs, and +`sequence_clash.clean` over the activities that happened to have a date and a location. **Every one of +those engines is careful** — each hands the caller the coverage and three attach a sentence saying why. +*The defect is always in the consumer*, and four of the five had none, so nothing was wrong until +somebody wrote one. **A subset verdict is derived structurally, not by name:** a dict key whose value +is `bool(A) and (all(…) | not B)`, the guard being the author writing down that the subset can be +empty. Matching coverage-ish WORDS instead returned 24 candidates of mostly unrelated senses — a +sprinkler's coverage area, a `dry_run` flag — and *a rule that needs an exemption list is a rule whose +population is wrong.* The structural form returns 5 with no exemptions, and **three of the five were +instances nobody had found by hand.** +**Four more drafts were wrong in ways only measurement showed.** Linking consumers by shared +VOCABULARY cross-talked — a net-effective-rent card was reported as a `sequence_clash` consumer on +`skipped`, `findings` — so the link is the call site of the owning client method, the only thing that +claims *this file reads THIS response*. Treating `not_` as a coverage prefix matched `not_covered`, a +prose note about a **different dimension**, which *would have blessed the one consumer the rule exists +to catch*. Detecting a read as `.name` missed `const { not_applicable: notRun } = …` and called a +careful card bare; widening to a bare word boundary then matched the English word "skipped" inside an +unrelated prose string in the same file. *A detector keyed on one spelling cannot see the others, and +one keyed on none sees everything.* +**And mutating it found the limit it does not cover:** wrapping the coverage render in `if (false)` +leaves the text in place and PASSES. It is a source-level check — the same bargain +`test_route_reachability` makes for URL literals — and the complement is the consumer's own test, +where `apps/web/src/proforma/rentRollQuality.test.ts` and +`apps/web/src/proforma/covenantCard.test.ts` assert the coverage is *rendered*. Written down rather +than discovered later, because a gate whose reach is assumed is the thing these eighteen entries are +about. + "Cite a gate only after `git ls-files` confirms it" is itself a rule held as prose, so it is now `services/api/test_claude_md_gates.py`: every backticked code file named here, in `docs/roadmap-directions.md` **and in `docs/roadmap.md`** must resolve to a tracked path — including diff --git a/apps/web/src/api/schedule.ts b/apps/web/src/api/schedule.ts index 84926247..af6622f4 100644 --- a/apps/web/src/api/schedule.ts +++ b/apps/web/src/api/schedule.ts @@ -81,6 +81,7 @@ export function withSchedule>(Base: TBase) { crew_threshold: String(crewThreshold) }); return this.json<{ analyzed: number; skipped_count: number; locations: number; + skipped: { id?: string; name: string; reason: string }[]; findings: { location: string; overlap_days: number; combined_crew: number; a: { id?: string; name: string; trade: string }; b: { id?: string; name: string; trade: string }; diff --git a/apps/web/src/style.css b/apps/web/src/style.css index 5168114f..f21ccc51 100644 --- a/apps/web/src/style.css +++ b/apps/web/src/style.css @@ -441,6 +441,7 @@ html, body { overflow: hidden; } /* the app manages its own internal scrolling .result-note { margin-bottom: 10px; padding: 8px 10px; border-radius: 6px; background: var(--panel2); } .result-note.ok { border-left: 3px solid #33d17a; } .result-note.bad { border-left: 3px solid #e2554a; } +.result-note.warn { border-left: 3px solid var(--status-warn); } /* properties */ #props { width: 320px; overflow: auto; padding: 12px 14px; background: var(--panel); border-left: 1px solid var(--line); } diff --git a/apps/web/src/ui/result.ts b/apps/web/src/ui/result.ts index e9204987..a8da62f7 100644 --- a/apps/web/src/ui/result.ts +++ b/apps/web/src/ui/result.ts @@ -106,8 +106,14 @@ export function metricGrid(items: { label: string; value: string; sub?: string } return g; } -/** A status/section line in the result body. */ -export function resultNote(html: string, kind: "ok" | "bad" | "" = ""): HTMLElement { +/** A status/section line in the result body. + * + * `warn` was added 2026-09-25 for a third state these notes had no way to say: *the check ran and + * part of the input was not examined*. Before it the only choices were `ok` (green, which over an + * unexamined remainder is the appearance of a result rather than one) and `bad` (red, which claims + * a finding that is not there). `services/api/test_verdict_coverage.py` is the gate that made this + * necessary — a verdict true of a subset needs a note that is neither. */ +export function resultNote(html: string, kind: "ok" | "warn" | "bad" | "" = ""): HTMLElement { const d = document.createElement("div"); d.className = `result-note ${kind}`.trim(); d.innerHTML = html; diff --git a/apps/web/src/viewer/tools/analyseSection.ts b/apps/web/src/viewer/tools/analyseSection.ts index 3293733a..0d7f2341 100644 --- a/apps/web/src/viewer/tools/analyseSection.ts +++ b/apps/web/src/viewer/tools/analyseSection.ts @@ -5,7 +5,7 @@ import { enqueueAndWait, isJobStillRunning } from "../../api/waitForJob"; import { LayerManager } from "../../tools/layers"; import { LogisticsOverlay } from "../draft/logisticsOverlay"; import { kvTable, resultNote, showResult } from "../../ui/result"; -import { toast, withLoading } from "../../ui/feedback"; +import { escapeHtml, toast, withLoading } from "../../ui/feedback"; import { populate4dPanel } from "../fourD"; import type { LogisticsResource } from "../../api/client"; @@ -133,14 +133,34 @@ export function buildAnalyseSection(d: AnalyseDeps): void { try { r = await api.sequenceClash(pid); } catch { toast("Needs a project with a schedule", "error"); return; } const n = r.finding_count + r.support_finding_count; - out.textContent = n ? `${n} sequence clash${n === 1 ? "" : "es"}` : (r.clean ? "clean" : "checked"); + // `clean` is true of the activities the engine COULD analyse — it skips any with no + // location, no dates, or finish before start. Saying "clean" over a schedule where most + // activities were skipped states a result about a sample as though it were the whole. + // `services/api/test_verdict_coverage.py` is the gate; this line is what it found. + out.textContent = n ? `${n} sequence clash${n === 1 ? "" : "es"}` + : r.clean ? (r.skipped_count ? `clean of ${r.analyzed} · ${r.skipped_count} not checked` : "clean") + : "checked"; showResult("Sequence clash — space contention + install-before-support", (body) => { body.appendChild(resultNote( `${r!.analyzed} dated locatable activit${r!.analyzed === 1 ? "y" : "ies"} · ` + `${r!.finding_count} space contention · ${r!.support_finding_count} install-before-support` + (r!.support_unscheduled_count ? ` · ${r!.support_unscheduled_count} pair(s) missing a dated binding` : "") + `.`, - n ? "bad" : (r!.clean ? "ok" : ""))); + // Not "ok" while activities went unchecked: a green note over a tenth of the schedule + // is the appearance of a result rather than one. + n ? "bad" : (r!.clean && !r!.skipped_count ? "ok" : ""))); + if (r!.skipped_count) { + // The engine names WHY each one was skipped, which makes the gap fixable rather than + // merely reported — every reason is a missing field on a schedule activity. + const why = new Map(); + for (const s of r!.skipped) why.set(s.reason, (why.get(s.reason) ?? 0) + 1); + body.appendChild(resultNote( + `${r!.skipped_count} activit${r!.skipped_count === 1 ? "y was" : "ies were"} ` + + `not checked at all: ` + + [...why].map(([reason, count]) => `${count} ${escapeHtml(reason)}`).join(" · ") + + `. Sequence clash can only see activities with a location and both dates.`, + "warn")); + } if (r!.findings.length) { body.appendChild(kvTable(r!.findings.slice(0, 40).map((f) => ({ k: f.location, diff --git a/docs/roadmap.md b/docs/roadmap.md index 9ab4b98c..eb37405d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,37 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **VERDICT-COVERAGE — the class found five times by hand, now derived** *(S — Lane J; + **CLOSED 2026-09-25**; gated by `services/api/test_verdict_coverage.py`)* + + Five engines this session returned a boolean verdict beside a count of what they could not evaluate: + `soft_clash.coordinated`, `rent_scrub.clean`, `t12.tie_out.reconciles`, `covenants.clean`/`at_risk`, + `sequence_clash.clean`. **Every one of those engines is careful** — each hands the caller the coverage + and three attach a sentence saying why. *The defect is always in the consumer*, and four of the five + had none, so nothing was wrong until somebody wrote one. + + **The verdict is derived structurally, not by name:** `bool(A) and (all(…) | not B)`, where the guard + is the author recording that the subset can be empty. Coverage-*word* matching returned 24 candidates + of mostly unrelated senses and would have needed five exemptions; the structural form returns **5 with + none** — and **three were instances nobody had found by hand** (`sequence_clash`, `fived`, + `perf_budget`; the last two have no client method, so the gate reports them unexposed rather than + passing them). + + **It found a live defect on its first run.** `viewer/tools/analyseSection.ts` printed **"clean"** over + whatever fraction of the schedule was analysable — `sequence_clash` skips any activity with no + location, no dates, or finish before start, each with a stated reason, and the panel rendered + `analyzed` and `not_covered` but never `skipped_count`. Fixed, with `skipped` (the reasons) declared + on the client for the first time and a `warn` kind added to `resultNote`, which had no way to say + *the check ran and part of the input was not examined*. + + Four more drafts were wrong in ways only measurement showed — vocabulary linking cross-talked between + responses, a `not_` prefix matched a note about a different dimension and would have blessed the very + consumer the rule exists to catch, and read-detection failed first too narrowly (missing a + destructure) then too widely (matching the word "skipped" in prose). *And mutating it found the limit + it does not cover*: a coverage render wrapped in `if (false)` leaves the text and passes, so this is a + source-level check and the consumer's own tests are the complement — written down rather than + discovered later. + - ✅ ⭐ **COVENANT-DARK — two verdicts that read clean when nothing was evaluated** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/covenantCard.test.ts` and the `UNCALLED` deletion in diff --git a/services/api/run_tests.py b/services/api/run_tests.py index eb8011bb..bd50ece7 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -92,7 +92,7 @@ # R23-DIGEST — the deterministic model digest and its two routes: "test_model_digest", "test_digest_route", # observability (error alerting + distributed tracing) — env-gated, no-op when unconfigured: - "test_sentry", "test_otel", "test_doctext_source", "test_docs_module_schema", "test_schema_stale", "test_schema_strictness", "test_schema_types_agree", + "test_sentry", "test_otel", "test_doctext_source", "test_docs_module_schema", "test_schema_stale", "test_schema_strictness", "test_schema_types_agree", "test_verdict_coverage", # public docs are a shipped surface: competitor names for interop only, never comparison: "test_no_comparative_names", # this list is itself hand-maintained, so it gets a test of its own: diff --git a/services/api/test_verdict_coverage.py b/services/api/test_verdict_coverage.py new file mode 100644 index 00000000..ace50b19 --- /dev/null +++ b/services/api/test_verdict_coverage.py @@ -0,0 +1,300 @@ +"""Does a screen that reads a SUBSET VERDICT also read what the engine could not evaluate? + +THE CLASS, FOUND FIVE TIMES BY HAND BEFORE IT WAS WORTH DERIVING +--------------------------------------------------------------- +An engine evaluates what it can, reports a boolean about that, and reports separately what it could +not reach. The boolean is true of a SUBSET. A screen that renders the boolean and not the subset +size manufactures confidence out of an absence of inputs: + + soft_clash `coordinated` over a page of the matrix (CLASH-TRUNC, 2026-09-24) + rent_scrub `clean` `bool(ran) and not failed` — 1 of 7 checks ran + t12 `tie_out.reconciles` reference derived from the answer + covenants `clean`, `at_risk` counts zero for want of inputs + sequence_clash `clean` over the activities that had a date and a location + +Every one of those engines is careful. Each hands the caller the coverage beside the verdict and +three of them attach a sentence explaining why it matters — `rent_scrub.py`: *"a scrub that reports +'no findings' because half its inputs were missing is worse than no scrub — it launders absent data +into apparent confidence."* The defect is always in the consumer, and for four of the five there was +no consumer, so nothing was wrong until somebody wrote one. **Finding it a sixth time by hand is what +this file exists to prevent.** + +THE RULE +-------- +* A **subset verdict** is a dict key whose value is `bool(A) and (all(…)/any(…) | not B)`. The + `bool(A)` guard is the author writing down that the subset can be empty — which is exactly the + case where the verdict means nothing. +* Its **coverage** is a sibling key (including nested ones) whose name *by itself* says something was + not evaluated. +* A **consumer** is a web file that calls the client method whose declared response type carries the + verdict plus at least three of its siblings. +* Every consumer must READ at least one coverage field. + +FIVE THINGS THE FIRST DRAFTS GOT WRONG, EACH MEASURED RATHER THAN REASONED ABOUT +------------------------------------------------------------------------------- +1. **Matching coverage-ish WORDS** (`coverage`, `skipped`, `truncated`) against client response + types returned **24 methods**, most of them unrelated senses of the word: a sprinkler's + `max_coverage_m2_per_head`, a `dry_run` flag, `ok`/`writes` beside a `truncated` list. *A rule + that needs an exemption list is a rule whose population is wrong*, so the population moved to the + structural form above, which returns **5 with no exemptions** — and three of those five were + instances nobody had found by hand. +2. **Linking consumers by shared VOCABULARY** (verdict + ≥2 sibling names) cross-talks, because the + sibling names are ordinary words. It reported `proforma/rentRollQuality.ts` — a net-effective-rent + card — as a consumer of `sequence_clash`, on the strength of `skipped`, `skipped_count` and + `findings`. The link is the **call site of the owning client method**; nothing else claims *this + file reads THIS response*. +3. **Treating `not_` as a coverage prefix** matched `sequence_clash.not_covered`, which is a prose + note about a *different dimension* (whether directed support pairs were supplied). It would have + blessed the one consumer this rule exists to catch. The vocabulary is anchored whole-word now. +4. **Detecting a read as `.name`** missed `const { not_applicable: notRun } = s.counts` and reported + a card that reads its coverage carefully as BARE. *A detector keyed on one spelling cannot see the + same read written another way.* +5. **Widening that to a bare word boundary** then matched the English word *"skipped"* inside an + unrelated prose string in the same file — the opposite error, one line later. A read is `.name`, + `["name"]` or `name:`, and nothing else. + +WHAT THIS DOES NOT PROVE, stated because mutation-checking it is what surfaced the limit +---------------------------------------------------------------------------------------- +It is a SOURCE-level check: it proves the consumer reads the field somewhere in its source, not that +the read reaches a screen. Wrapping the coverage render in `if (false)` leaves the text in place and +passes — measured, not assumed. That is the same bargain `test_route_reachability` makes for URL +literals, and the complement is the consumer's own test: `proforma/rentRollQuality.test.ts` and +`proforma/covenantCard.test.ts` assert the coverage is RENDERED, with mutations. This file's job is +to make sure a new consumer cannot be written without one. + +Run: PYTHONPATH="src;../data/src" ./.venv/Scripts/python.exe test_verdict_coverage.py +""" +import ast +import re +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +API = HERE / "src" / "aec_api" +WEB = HERE.parents[1] / "apps" / "web" / "src" +WEBAPI = WEB / "api" + +FAILED: list[str] = [] + + +def check(label, ok, detail=""): + print(f"{'PASS' if ok else 'FAIL'} {label}{(' — ' + str(detail)) if detail and not ok else ''}") + if not ok: + FAILED.append(label) + + +#: Names that, on their own, say "this was not evaluated". Anchored whole-word: `not_` as a prefix +#: matched `not_covered`, a note about a different dimension — see lesson 3. +COVERAGE_NAME = re.compile( + r"^(untested|unmapped|unpriced|unmeasured|uncomputable|uncovered" + r"|skipped\w*|not_applicable|not_computable|not_run|not_tested)$") + + +def is_subset_verdict(value: ast.expr) -> bool: + """`bool(A) and (all(…)/any(…) | not B)` — a verdict true of a subset, guarded because it can be empty. + + The guard is the load-bearing half. `truncated`, `ok` and `dry_run` are booleans too and are not + verdicts about a subset; requiring `bool()` beside a quantifier is what separates them + without a list of names to maintain. + """ + if not (isinstance(value, ast.BoolOp) and isinstance(value.op, ast.And)): + return False + guarded = quantified = False + for part in value.values: + if isinstance(part, ast.Call) and isinstance(part.func, ast.Name): + if part.func.id == "bool" and part.args and isinstance(part.args[0], ast.Name): + guarded = True + elif part.func.id in ("all", "any") and part.args: + quantified = True + elif isinstance(part, ast.UnaryOp) and isinstance(part.op, ast.Not): + quantified = True + return guarded and quantified + + +def keys_deep(node: ast.Dict) -> set[str]: + """Every string key in a dict literal, including nested ones — `rent_scrub`'s coverage lives in + `counts.not_applicable`, so a top-level-only walk would report that response as having none.""" + out: set[str] = set() + for k, v in zip(node.keys, node.values): + if isinstance(k, ast.Constant) and isinstance(k.value, str): + out.add(k.value) + if isinstance(v, ast.Dict): + out |= keys_deep(v) + return out + + +def subset_verdicts(root: Path) -> list[tuple[str, str, set[str], set[str]]]: + """`(module, verdict, top-level siblings, all siblings)` for every subset verdict under `root`.""" + out = [] + for path in sorted(root.rglob("*.py")): + try: + tree = ast.parse(path.read_text(encoding="utf-8")) + except SyntaxError: # pragma: no cover — a syntax error reds the suite elsewhere + continue + for node in ast.walk(tree): + if not isinstance(node, ast.Dict): + continue + deep = keys_deep(node) + top = {k.value for k in node.keys + if isinstance(k, ast.Constant) and isinstance(k.value, str)} + for k, v in zip(node.keys, node.values): + if isinstance(k, ast.Constant) and isinstance(k.value, str) and is_subset_verdict(v): + out.append((str(path.relative_to(root)), k.value, + top - {k.value}, deep - {k.value})) + return out + + +def client_methods() -> dict[str, set[str]]: + """`{method: declared response field names}` for every `ApiClient` method that fetches JSON.""" + out: dict[str, set[str]] = {} + for path in sorted(WEBAPI.glob("*.ts")): + if path.name.endswith(".test.ts") or path.name == "schema.d.ts": + continue + src = path.read_text(encoding="utf-8") + for m in re.finditer(r"^ (\w+)\(", src, re.M): + start = m.end() + end = src.find("\n }", start) + if end < 0 or "this.json<" not in src[start:end]: + continue + out[m.group(1)] = set(re.findall(r"\b(\w+)\s*\??\s*:", src[start:end])) + return out + + +def reads(src: str, name: str) -> bool: + """`r.skipped_count`, `r["skipped"]` and `{ not_applicable: x }` are reads. The word `skipped` + inside a prose string is not — see lessons 4 and 5, which this one line is the repair for.""" + n = re.escape(name) + return re.search(rf'(?:\.{n}\b)|(?:\[["\']{n}["\']\])|(?:\b{n}\s*:)', src) is not None + + +def web_files() -> list[Path]: + """Application sources: not tests, not vendored, not the client layer itself.""" + return [p for p in sorted(WEB.rglob("*.ts")) + if not p.name.endswith(".test.ts") and "vendor" not in p.parts and p.parent != WEBAPI] + + +def bare_consumers(sources=None): + """`(file, module, verdict, coverage)` for every consumer reading a verdict without its coverage. + + Takes `(label, source)` pairs so the replay below can hand it a frozen fixture instead of the + tree — the same reason `test_gap_records` embeds its pre-fix subject rather than fetching it with + `git show`, which works locally and not in CI's shallow checkout. + """ + methods = client_methods() + files = sources if sources is not None else [ + (str(p.relative_to(WEB)), p.read_text(encoding="utf-8")) for p in web_files()] + out = [] + for mod, verdict, top, deep in subset_verdicts(API): + coverage = sorted(k for k in deep if COVERAGE_NAME.match(k)) + if not coverage: + continue + owners = [n for n, declared in methods.items() + if verdict in declared and len(top & declared) >= 3] + for owner in owners: + call = re.compile(rf"\.{re.escape(owner)}\(") + for label, src in files: + if not call.search(src): + continue + if not any(reads(src, c) for c in coverage): + out.append((label, mod, verdict, coverage)) + return out + + +# --------------------------------------------------------------------------------------------- +# PRECONDITIONS — each is a way this file could report a clean tree while seeing nothing. + +_VERDICTS = subset_verdicts(API) +_METHODS = client_methods() + +check(f"the AST walk found subset verdicts ({len(_VERDICTS)})", + len(_VERDICTS) >= 4, + f"only {len(_VERDICTS)} found under {API} — the rule below would be a statement about an " + "empty population, which reads exactly like a clean tree") + +check(f"the client surface parsed ({len(_METHODS)} methods)", + len(_METHODS) >= 200, + f"only {len(_METHODS)} client methods parsed out of {WEBAPI} — with no owners the consumer " + "check can never fire") + +_NAMES = {(m, v) for m, v, _, _ in _VERDICTS} +_MUST_FIND = {("covenants.py", "clean"), ("rent_scrub.py", "clean"), + ("sequence_clash.py", "clean")} +check("the walk re-finds the three known subset verdicts", + _MUST_FIND <= _NAMES, + f"missing {sorted(_MUST_FIND - _NAMES)} — these are the instances the rule was derived from, " + "so a walk that cannot see them is measuring something else") + +# A plain boolean is NOT a subset verdict. `truncated` is the counter-example that matters: it is a +# bool, it sits beside coverage-ish siblings, and it was what made the word-based population 24 wide. +_SYNTH_PLAIN = ast.parse("x = {'truncated': len(rows) > cap, 'skipped': 3}").body[0].value +_SYNTH_SUBSET = ast.parse("x = {'clean': bool(ran) and not failed, 'skipped': 3}").body[0].value +check("SELF-TEST: a plain boolean is not a subset verdict…", + not any(is_subset_verdict(v) for v in _SYNTH_PLAIN.values), + "`truncated: len(rows) > cap` was read as a subset verdict — the population widens to every " + "boolean and the rule needs an exemption list") +check("SELF-TEST: …and a guarded, quantified one is", + any(is_subset_verdict(v) for v in _SYNTH_SUBSET.values), + "`bool(ran) and not failed` was NOT read as a subset verdict, so the walk above is finding " + "its three by accident") + +_SRC_DOT = "const n = r.skipped_count;" +_SRC_DESTRUCT = "const { not_applicable: notRun } = s.counts;" +_SRC_INDEX = 'const n = r["skipped"];' +_SRC_PROSE = "resultNote(`3 space(s) have no area and were skipped — add areas.`)" +check("SELF-TEST: a read is found in all three spellings…", + reads(_SRC_DOT, "skipped_count") and reads(_SRC_DESTRUCT, "not_applicable") + and reads(_SRC_INDEX, "skipped"), + "a spelling of a read went undetected — which is how the first draft reported a card that " + "reads its coverage carefully as BARE") +check("SELF-TEST: …and the same word in PROSE is not a read", + not reads(_SRC_PROSE, "skipped"), + "the English word `skipped` in a string counted as reading the field — the opposite error, " + "and the one that would bless the consumer this rule exists to catch") + +check("SELF-TEST: `not_covered` is not a coverage name", + not COVERAGE_NAME.match("not_covered"), + "a `not_` prefix rule matched a prose note about a different dimension, which is how this " + "gate would have passed the very file it found") +check("SELF-TEST: …while the real ones are", + all(COVERAGE_NAME.match(n) for n in ("untested", "skipped", "skipped_count", "not_applicable")), + "a coverage name the rule exists for is unmatched") + +# REPLAY — the shape this gate was written from, and its repair. Frozen rather than read out of +# history: `viewer/tools/analyseSection.ts` at the commit before the fix called `.sequenceClash(`, +# rendered `clean`, `analyzed` and `finding_count`, and read neither `skipped` nor `skipped_count`. +_REPLAY_BARE = ("replay/analyseSection.pre-fix.ts", """ + let r; r = await api.sequenceClash(pid); + const n = r.finding_count + r.support_finding_count; + out.textContent = n ? `${n} sequence clashes` : (r.clean ? "clean" : "checked"); + body.appendChild(resultNote(`${r.analyzed} dated locatable activities.`, n ? "bad" : "ok")); +""") +_REPLAY_OK = (_REPLAY_BARE[0].replace("pre-fix", "fixed"), + _REPLAY_BARE[1] + ' if (r.skipped_count) body.appendChild(resultNote("not checked"));\n') + +_FOUND = bare_consumers([_REPLAY_BARE]) +check("the replay finds the pre-fix consumer it was written from", + [f for f, _m, v, _c in _FOUND if v == "clean"] == [_REPLAY_BARE[0]], + f"replaying the pre-fix source reported {_FOUND or 'nothing'} — the rule cannot re-find the " + "instance it was derived from, so a green verdict below is about something else") + +check(" …and its repair is clean", + not bare_consumers([_REPLAY_OK]), + "the fixed source still reads as bare, so the rule cannot be satisfied by fixing the defect") + +# --------------------------------------------------------------------------------------------- +# THE VERDICT + +_BARE = bare_consumers() +_DETAIL = "; ".join(f"{f} reads {m}::{v} and none of {c}" for f, m, v, c in _BARE) +check(f"every consumer of a subset verdict also reads its coverage ({len(_VERDICTS)} verdicts)", + not _BARE, + f"{len(_BARE)}: {_DETAIL} — the verdict is true of what the engine could evaluate, and a " + "screen that shows it without the size of what it could not turns an absence of inputs into " + "an appearance of confidence. Render the coverage beside the verdict") + +print() +if FAILED: + print(f"verdict_coverage: {len(FAILED)} FAILED — {FAILED}") + sys.exit(1) +print(f"verdict_coverage: all checks passed — {len(_VERDICTS)} subset verdict(s), " + f"{len(_METHODS)} client methods, 0 consumers reading a verdict without its coverage") From 6fc46a27e194f63ac0b88050ffc99ed15835ffdd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 05:45:52 +0000 Subject: [PATCH 07/21] AUTHORITY-DARK: a gate you could read and could not satisfy services/api/src/aec_api/deal_authority.py opens with the case for itself: "A data room accumulates three offering memoranda, two rent rolls and a tax bill from the year before the reassessment. Every one of them is a real file; only one of each is authoritative. Analysis that reads whichever copy it happened to open is how a superseded number reaches a committee." THE FINDING IS SHARPER THAN "NOBODY CALLED IT", AND MY FIRST READING WAS WRONG. dealAuthority() -- the READ -- has had a caller all along: portal/panels/design.ts renders the gate on its go/no-go card, and renders it well (missing, stale with days_over, and the superseded-still-active case "nobody looks for, because the document exists and reads fine"). What was callerless is saveDealAuthority() -- the WRITE. So the table could be SEEN and never DECLARED: the design panel could say "Source facts NOT current" and no screen in the product could make them current. A gate you can read and cannot satisfy is worse to ship than one nobody can see, because it is visibly unfinished and reads as a bug in the analysis rather than as a missing surface. The card is on Underwriting -- it governs every figure on that tab rather than one of them. A row per fact type: document, date, optional freshness override. Saving returns the REASSESSMENT, which is what the card renders; re-fetching would race the write and could show the previous verdict. The gate leads, because its purpose is "to stop the work, not to annotate it after the fact". Advisory is rendered APART from blocking: a stale offering package is not a stale tax bill, and one list would make the required ones look negotiable and the optional ones alarming. superseded_still_active gets its own table -- it is the one row type a reader cannot derive from the others, because nothing about it looks wrong in isolation. And the table shows age against each fact type's OWN limit rather than a bare "stale": a tax bill and an offering package go stale on different clocks, and "147 / 60d" is checkable where "stale" is asserted. An undated row is refused here, before the PUT, because the server's 422 names an entry by index and the person is looking at a table of labels. THE TRIPWIRE IS THE LOAD-BEARING TEST, NOT THE RENDERING. FACT_TYPES has to be mirrored because the GET returns declared rows plus the REQUIRED missing ones -- so an optional fact type nobody has declared yet (a title commitment, a survey) appears in neither, and a card built from the response alone could never offer to add one. authorityCard.test.ts reads deal_authority.py off disk and asserts every key, label, default freshness and required flag agrees, in order: the reportMoments.test.ts technique against reports.py, for the same reason. The `required` flag is the one that matters -- a drift there shows a fact type as optional while assess() blocks on it. WHAT I CHECKED AND DID NOT FIND. Four engines this session carried a verdict that went vacuous once nothing had been evaluated, so the empty table was measured first. It FAILS CLOSED: gate.passes is `not blocking`, and with no entries the three required fact types are each missing, so it blocks; one fresh rent roll still blocks on the other two. test_verdict_coverage.py leaves it alone correctly -- `not blocking` is not a verdict quantified over a subset. A clean negative is worth as much as a finding, and saying so is part of the report. FIXED: saveDealAuthority declared `assessment: { gate: { passes: boolean } }`, a narrowed re-spelling of the record the GET returns in full, which forced a consumer that wanted to render the saved verdict to cast -- the shape noRespelledShapes.test.ts exists to catch. AuthorityAssessment is declared once and used by both routes. And `stale` carried the full row while being declared {fact_type, days_over}, so a screen could say a document was stale and not which one; `advisory` was unknown[] when it is {fact_type, why}[]; `reviewer` and `supersedes` on a table row were undeclared. AND A TEST SATISFIED BY THE WRONG CODE PATH. The blocking-table test asserted the fact type's LABEL against the whole card's text -- and the same label also appears in the "Not declared" line, which is built straight from FACT_TYPES rather than through label(). So a mutation rendering the raw fact_type key in the blocking table PASSED. An assertion satisfied by a different code path than the one it is about is not an assertion about that path. Scoped to the blocking table's own cells, and re-mutated. AUTHORITY-DOWNSTREAM is filed open rather than decided: the residual-land, NER, rent-roll-scrub, T-12 and covenant cards all compute regardless of this gate, which exists to stop exactly that. How hard to block -- refuse, warn, or annotate -- is a product decision, and the wrong choice in either direction is expensive. Fifteen tests, eight mutations. Verified: tsc --noEmit clean, eslint clean, npm run build clean, 269 files / 2,920 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 71 +++++ apps/web/src/api/clientCallers.test.ts | 2 +- apps/web/src/api/creDeal.ts | 51 +++- apps/web/src/proforma/authorityCard.test.ts | 170 +++++++++++ apps/web/src/proforma/authorityCard.ts | 308 ++++++++++++++++++++ apps/web/src/proforma/proforma.ts | 6 + docs/roadmap.md | 47 +++ 7 files changed, 645 insertions(+), 10 deletions(-) create mode 100644 apps/web/src/proforma/authorityCard.test.ts create mode 100644 apps/web/src/proforma/authorityCard.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 27141139..d21c5687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,77 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### AUTHORITY-DARK — a gate you could read and could not satisfy + +`services/api/src/aec_api/deal_authority.py` opens with the case for itself: + +> A data room accumulates three offering memoranda, two rent rolls and a tax bill from the year before +> the reassessment. Every one of them is a real file; only one of each is *authoritative*. Analysis +> that reads whichever copy it happened to open is how a superseded number reaches a committee. + +**The finding is sharper than "nobody called it", and the first draft of this entry got it wrong.** +`dealAuthority()` — the READ — has had a caller all along: `portal/panels/design.ts` renders the gate +on its go/no-go card, and renders it well. What was callerless is **`saveDealAuthority()` — the +WRITE.** So the table could be *seen* and never *declared*: the design panel could tell you *"Source +facts NOT current"* and no screen in the product could make them current. **A gate you can read and +cannot satisfy is worse to ship than one nobody can see**, because it is visibly unfinished and reads +as a bug in the analysis rather than as a missing surface. + +### Added + +- **`apps/web/src/proforma/authorityCard.ts`** on the Underwriting tab — the gate on whether any + figure on that tab should be trusted, so it sits with the underwriting rather than beside one of the + things it governs. A row per fact type: document, date, optional freshness override. Saving returns + the **reassessment**, which is what the card renders — re-fetching would race the write and could + show the previous verdict. +- **The gate leads**, because its purpose is *"to stop the work, not to annotate it after the fact"*, + with the engine's own `why` per blocked fact type. Advisory is rendered **apart** from blocking: a + stale offering package is not a stale tax bill, and one list would make the required ones look + negotiable and the optional ones alarming. `superseded_still_active` gets its own table — it is the + one row type a reader cannot derive from the others, because nothing about it looks wrong in + isolation. +- **Age against the fact type's own limit**, not a bare "stale": a tax bill and an offering package go + stale on different clocks, and `147 / 60d` is checkable where "stale" is asserted. +- An undated row is refused **here**, before the PUT, because the server's 422 names an entry by index + and the person is looking at a table of labels. + +### The tripwire is the load-bearing test + +`FACT_TYPES` is mirrored from Python because the GET returns declared rows plus the *required* missing +ones — so an optional fact type nobody has declared yet (a title commitment, a survey) appears in +neither, and a card built from the response alone could never offer to add one. +`authorityCard.test.ts` reads `deal_authority.py` off disk and asserts every key, label, default +freshness and required flag agrees, in order — the `ui/reportMoments.test.ts` technique against +`reports.py`, for the same reason. **The `required` flag is the one that matters**: if it drifts, the +card shows a fact type as optional while the gate blocks on it. + +### What I checked and did not find + +Four engines this session carried a verdict that went vacuous once nothing had been evaluated, so the +empty table was measured first. **It fails closed**: `gate.passes` is `not blocking`, and with no +entries the three required fact types are each missing, so it blocks; one fresh rent roll still blocks +on the other two. `test_verdict_coverage.py` leaves it alone correctly — `not blocking` is not a +verdict quantified over a subset. *A clean negative is worth as much as a finding, and saying so is +part of the report.* + +### Fixed + +- **`saveDealAuthority` declared `assessment: { gate: { passes: boolean } }`** — a narrowed + re-spelling of the record the GET returns in full, which forced a consumer that wanted to render the + saved verdict to cast. That is the shape `apps/web/src/api/noRespelledShapes.test.ts` exists to + catch. `AuthorityAssessment` is declared once and used by both routes. +- **`stale` carried the full row and was declared `{fact_type, days_over}`**, so a screen could say a + document was stale and not which one; `advisory` was `unknown[]` when it is `{fact_type, why}[]`; + `reviewer` and `supersedes` on a table row were undeclared. + +### And a test that was satisfied by the wrong code path + +The blocking-table test asserted the fact type's **label** against the whole card's text — and the +same label also appears in the "Not declared" line, which is built straight from `FACT_TYPES` rather +than through `label()`. So a mutation rendering the raw `fact_type` key in the blocking table +**passed**. *An assertion satisfied by a different code path than the one it is about is not an +assertion about that path.* Scoped to the blocking table's own cells, and re-mutated. + ### VERDICT-COVERAGE — the class found five times by hand, now derived Five engines in this session returned a boolean verdict beside a count of what they could not diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 31543d42..81703a91 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -319,7 +319,7 @@ const UNCALLED: readonly string[] = [ // tab's inverse solve. The engine and the client method shipped with FIN-CALC; nothing called it. "reviewPost", "reviewScenario", "reviseDrawing", "runClash", "runMacro", "saveClausePlaybook", - "saveDealAuthority", "saveMacros", "saveViewTemplates", + "saveMacros", "saveViewTemplates", "scanDeviation", "scopeRegister", "securitiesPackage", "sendDigest", "setLod", "setPhase", "sharedComment", "sharedDecision", "sharedDigestUrl", "spaceUtilBenchmarks", "speckleStatus", "tieredComps", diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index 392d91ce..e9a687d0 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -98,6 +98,38 @@ import { HttpCore } from "./httpCore"; type Ctor = new (...args: any[]) => T; +/** One declared authority row. `table` and `stale` return the SAME record — `stale` adds + * `days_over` — so they share a type rather than being spelled twice. */ +export interface AuthorityRow { + fact_type: string; label: string; document: string; as_of: string; + age_days: number | null; freshness_days: number; fresh: boolean; + reviewer: string | null; required: boolean; supersedes: string[]; +} + +/** The assessment `deal_authority.assess()` returns. **Both routes return it** — the GET directly and + * the PUT under `assessment` — and until 2026-09-25 the PUT declared it as + * `{ gate: { passes: boolean } }`, a narrowed re-spelling of the same record. That is the shape + * `apps/web/src/api/noRespelledShapes.test.ts` exists to catch, and it forced a consumer that wanted + * to render the saved verdict to cast. One name, used twice. */ +export interface AuthorityAssessment { + table: AuthorityRow[]; + missing: { fact_type: string; label: string; required: boolean }[]; + stale: (AuthorityRow & { days_over: number })[]; + superseded_still_active: { fact_type: string; document: string; issue: string }[]; + gate: { passes: boolean; blocking: { fact_type: string; why: string }[]; + advisory: { fact_type: string; why: string }[] }; + as_of: string; + counts: Record; + note: string; +} + +/** An entry as the PUT stores it, after `validate()` has normalised it. */ +export interface AuthorityEntryWire { + fact_type: string; document: string; as_of: string; + freshness_days: number | null; authoritative: boolean; supersedes: string[]; + reviewer?: string | null; +} + export function withCreDeal>(Base: TBase) { return class CreDeal extends Base { /** CRE-HOLDSELL — hold vs sell: incremental hold-year IRRs against the proceeds declined today. */ @@ -171,18 +203,19 @@ export function withCreDeal>(Base: TBase) { `/projects/${pid}/loan/covenants`, { method: "POST", body: JSON.stringify({ loan, actuals }) }); } - /** CRE-AUTHORITY — the deal-room authority table; required gaps BLOCK downstream analysis. */ + /** CRE-AUTHORITY — the deal-room authority table; required gaps BLOCK downstream analysis. + * + * FIELDS CORRECTED 2026-09-25. `stale` carries the FULL row — label, document, `as_of`, + * `age_days`, `freshness_days`, `supersedes` and `days_over` — and was declared as + * `{fact_type, days_over}`, so a screen could say a document was stale and not which one. And + * `advisory` was typed `unknown[]` when it is `{fact_type, why}[]`: rendering it would have + * needed an inline cast, which is the re-spelled shape `apps/web/src/api/noRespelledShapes.test.ts` + * exists to stop. `reviewer` and `supersedes` on a table row were undeclared too. */ dealAuthority(pid: string) { - return this.json<{ table: { fact_type: string; label: string; document: string; as_of: string; - age_days: number | null; freshness_days: number; fresh: boolean; required: boolean }[]; - missing: { fact_type: string; label: string }[]; - stale: { fact_type: string; days_over: number }[]; - superseded_still_active: { fact_type: string; document: string; issue: string }[]; - gate: { passes: boolean; blocking: { fact_type: string; why: string }[]; advisory: unknown[] }; - counts: Record; note: string }>(`/projects/${pid}/deal-room/authority`); + return this.json(`/projects/${pid}/deal-room/authority`); } saveDealAuthority(pid: string, entries: unknown[]) { - return this.json<{ entries: unknown[]; assessment: { gate: { passes: boolean } } }>( + return this.json<{ entries: AuthorityEntryWire[]; assessment: AuthorityAssessment }>( `/projects/${pid}/deal-room/authority`, { method: "PUT", body: JSON.stringify({ entries }) }); } diff --git a/apps/web/src/proforma/authorityCard.test.ts b/apps/web/src/proforma/authorityCard.test.ts new file mode 100644 index 00000000..2df2f215 --- /dev/null +++ b/apps/web/src/proforma/authorityCard.test.ts @@ -0,0 +1,170 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { FACT_TYPES, day, entriesFromAssessment, renderAuthority } from "./authorityCard"; + +/** + * CRE-AUTHORITY could be **read and never declared**. + * + * `portal/panels/design.ts` has rendered this gate on its go/no-go card all along — so the product + * could tell you *"Source facts NOT current"* — and `saveDealAuthority()` had no caller anywhere, so + * nothing in the product could make them current. A gate you can read and cannot satisfy looks like a + * bug in the analysis rather than a missing surface. + * + * The load-bearing test here is the **tripwire**, not the rendering: `FACT_TYPES` is mirrored from + * Python because the GET returns declared rows plus the *required* missing ones, so an optional fact + * type nobody has declared yet appears in neither and a card built from the response alone could never + * offer to add one. Two tables encoding one fact drift — this repo has the scars — and the `required` + * flag is the one that matters: if it drifts, this card shows a fact type as optional while the gate + * blocks on it. Same technique as `ui/reportMoments.test.ts` against `reports.py`. + */ + +const DEAL_AUTHORITY_PY = resolve( + __dirname, "../../../../services/api/src/aec_api/deal_authority.py"); + +type Assessment = Awaited>; + +const ROW = (over: Partial = {}) => ({ + fact_type: "rent_roll", label: "Rent roll", document: "RR 2026-08.xlsx", as_of: "2026-09-01", + age_days: 24, freshness_days: 45, fresh: true, reviewer: null, required: true, + supersedes: [], ...over, +}); + +const ASSESS = (over: Partial = {}): Assessment => ({ + as_of: "2026-09-25", table: [ROW()], missing: [], stale: [], superseded_still_active: [], + gate: { passes: true, blocking: [], advisory: [] }, + counts: { declared: 1, missing: 0, stale: 0, blocking: 0, advisory: 0 }, + note: "Authority is declared per fact type, not per file.", + ...over, +} as Assessment); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; + +describe("the mirrored fact-type table", () => { + /** `FACT_TYPES: dict[str, tuple[label, days, required]]` as the Python declares it, in file order. */ + function pythonFactTypes(): [string, string, number, boolean][] { + const src = readFileSync(DEAL_AUTHORITY_PY, "utf-8"); + const block = /FACT_TYPES: dict\[str, tuple\[str, int, bool\]\] = \{([\s\S]*?)\n\}/.exec(src); + expect(block, "FACT_TYPES not found in deal_authority.py — the mirror below cannot be checked, " + + "which is exactly the state this test exists to prevent").toBeTruthy(); + const out: [string, string, number, boolean][] = []; + for (const m of block![1]!.matchAll(/"(\w+)":\s*\("([^"]+)",\s*(\d+),\s*(True|False)\)/g)) { + out.push([m[1]!, m[2]!, Number(m[3]), m[4] === "True"]); + } + return out; + } + + it("parsed the Python at all — a short list would make every assertion below vacuous", () => { + expect(pythonFactTypes().length).toBeGreaterThanOrEqual(8); + }); + + it("matches key, label, default freshness and required flag, in order", () => { + // `required` is the load-bearing column: a drift there shows a fact type as optional on this card + // while `assess()` blocks on it, which is worse than not showing the fact type at all. + expect(FACT_TYPES.map((f) => [...f])).toEqual(pythonFactTypes().map((f) => [...f])); + }); + + it("agrees with the engine about which types are required", () => { + expect(FACT_TYPES.filter(([, , , r]) => r).map(([k]) => k)) + .toEqual(["rent_roll", "operating_statement", "tax"]); + }); +}); + +describe("dates", () => { + it("formats from a fixed table, not the host's ICU", () => expect(day("2026-09-01")).toBe("1 Sep 2026")); + it("returns an unparsable value unchanged", () => expect(day("whenever")).toBe("whenever")); + it("renders an absent date as a dash", () => expect(day(null)).toBe("—")); +}); + +describe("the gate", () => { + it("leads with the block and the engine's own reason for each fact type", () => { + const el = renderAuthority(mount(), ASSESS({ + gate: { passes: false, advisory: [], blocking: [ + { fact_type: "operating_statement", + why: "authoritative document is 87 day(s) past its 60-day freshness limit" }, + { fact_type: "tax", why: "no authoritative document declared" }] }, + })); + expect(el.textContent).toContain("Blocked — 2 required fact type(s)"); + // Scoped to the blocking TABLE. Asserting the label against the whole card passes on the + // "Not declared" line, which is rendered straight from FACT_TYPES and not through `label()` — + // so the raw-key mutation satisfied it via a path the test is not about. + const blockRows = [...el.querySelectorAll("table")][0]!.querySelectorAll("tr"); + const cells = [...blockRows].slice(1).map((tr) => [...tr.querySelectorAll("td")].map((td) => td.textContent)); + expect(cells).toEqual([ + ["Operating statement (T-12)", "authoritative document is 87 day(s) past its 60-day freshness limit"], + ["Property tax bill", "no authoritative document declared"], + ]); + }); + + it("says plainly when it passes", () => { + expect(renderAuthority(mount(), ASSESS()).textContent) + .toContain("Authority is sufficient to underwrite"); + }); + + // A stale offering package is not a stale tax bill. One list would make the required ones look + // negotiable and the optional ones look alarming. + it("keeps advisory apart from blocking", () => { + const t = renderAuthority(mount(), ASSESS({ + gate: { passes: true, blocking: [], + advisory: [{ fact_type: "offering", why: "stale (not required)" }] }, + })).textContent ?? ""; + expect(t).toContain("Advisory — stale, not required"); + expect(t).toContain("Offering package"); + expect(t).not.toContain("Blocked"); + }); + + it("renders the case nobody looks for: superseded and still relied on", () => { + const t = renderAuthority(mount(), ASSESS({ + superseded_still_active: [{ fact_type: "tax", document: "Tax bill 2024.pdf", + issue: "superseded by Tax bill 2025.pdf and still authoritative" }], + })).textContent ?? ""; + expect(t).toContain("Superseded, still active"); + expect(t).toContain("Tax bill 2024.pdf"); + expect(t).toContain("how a stale number reaches a committee"); + }); +}); + +describe("the declared table", () => { + // A tax bill and an offering package go stale on different clocks, so "stale" alone is not + // checkable — the age against its own limit is. + it("shows age against the fact type's own freshness limit", () => { + const t = renderAuthority(mount(), ASSESS({ + table: [ROW({ age_days: 147, freshness_days: 60, fresh: false })], + })).textContent ?? ""; + expect(t).toContain("147 / 60d"); + }); + + it("names what has not been declared, required ones marked", () => { + const t = renderAuthority(mount(), ASSESS()).textContent ?? ""; + expect(t).toContain("Not declared:"); + expect(t).toContain("Operating statement (T-12) (required)"); + expect(t).toContain("Property tax bill (required)"); + expect(t).not.toContain("Rent roll (required)"); // it IS declared + }); + + it("marks a required row as required", () => { + expect(renderAuthority(mount(), ASSESS()).textContent).toContain("required"); + }); +}); + +describe("round-tripping into the editor", () => { + it("opens on what is already declared rather than a blank form", () => { + expect(entriesFromAssessment(ASSESS())).toEqual([{ + fact_type: "rent_roll", document: "RR 2026-08.xlsx", as_of: "2026-09-01", + freshness_days: 45, authoritative: true, supersedes: [], + }]); + }); + + it("carries a supersedes chain through", () => { + const [e] = entriesFromAssessment(ASSESS({ + table: [ROW({ supersedes: ["RR 2026-06.xlsx"] })] })); + expect(e!.supersedes).toEqual(["RR 2026-06.xlsx"]); + }); +}); diff --git a/apps/web/src/proforma/authorityCard.ts b/apps/web/src/proforma/authorityCard.ts new file mode 100644 index 00000000..f61d1e78 --- /dev/null +++ b/apps/web/src/proforma/authorityCard.ts @@ -0,0 +1,308 @@ +/** The deal-room authority table — which document is *the* document for each fact, and whether the + * analysis downstream of it should run at all. + * + * `services/api/src/aec_api/deal_authority.py` opens with the case for itself: + * + * > A data room accumulates three offering memoranda, two rent rolls and a tax bill from the year + * > before the reassessment. Every one of them is a real file; only one of each is *authoritative*. + * > Analysis that reads whichever copy it happened to open is how a superseded number reaches a + * > committee. + * + * THE FINDING IS SHARPER THAN "NOBODY CALLED IT", and the first draft of this comment got it wrong. + * `dealAuthority()` — the READ — has had a caller all along: `portal/panels/design.ts` renders the + * gate on its go/no-go card, and renders it well (missing, stale with `days_over`, and the + * `superseded_still_active` case *"nobody looks for, because the document exists and reads fine"*). + * What was callerless is `saveDealAuthority()` — the WRITE. **So the table could be seen and never + * declared**: the design panel could tell you "Source facts NOT current" and there was no screen + * anywhere in the product that could make them current. A gate you can read and cannot satisfy is a + * worse thing to ship than one nobody can see, because it is visibly unfinished and looks like a + * bug in the analysis rather than a missing surface. This card is the surface. + * + * WHAT I CHECKED AND DID NOT FIND, recorded because a clean negative is worth as much as a finding + * ---------------------------------------------------------------------------------------------- + * Four engines this session carried a verdict that went vacuous once nothing had been evaluated, so + * the first thing measured here was the empty table. It **fails closed**: `gate.passes` is + * `not blocking`, and with no entries the three required fact types — rent roll, operating + * statement, tax — are each missing, so the gate blocks. One fresh rent roll still blocks on the + * other two. `services/api/test_verdict_coverage.py` leaves it alone correctly, too: `not blocking` + * is not a verdict quantified over a subset, and the `bool(A)` guard that makes those dangerous is + * absent because there is no subset. *The engine is sound in the direction that matters, and saying + * so is part of the report.* + * + * WHY THE FACT TYPES ARE MIRRORED HERE, AND WHAT STOPS THE MIRROR DRIFTING + * The GET returns declared rows plus the REQUIRED ones that are missing — so an optional fact type + * nobody has declared yet (a title commitment, a survey) appears in neither, and a card built only + * from the response could never offer to add one. The table is therefore mirrored, the way + * `ui/reportMoments.ts` mirrors `reports.py`, and `authorityCard.test.ts` reads + * `deal_authority.py` off disk and asserts every key, label, default freshness and required flag + * agrees. **The required flag is the load-bearing one**: if it drifts, this card shows a fact type + * as optional while the gate blocks on it, which is worse than not showing it at all. + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; + +type Authority = Awaited>; + +/** `fact_type -> [label, default freshness days, required for underwriting]`, mirroring + * `deal_authority.FACT_TYPES`. Order is that file's order, which is the order of a diligence list + * rather than the alphabet. Pinned against the Python by `authorityCard.test.ts`. */ +export const FACT_TYPES: readonly [string, string, number, boolean][] = [ + ["offering", "Offering package", 180, false], + ["rent_roll", "Rent roll", 45, true], + ["operating_statement", "Operating statement (T-12)", 60, true], + ["tax", "Property tax bill", 365, true], + ["insurance", "Insurance loss run / policy", 365, false], + ["title", "Title commitment", 180, false], + ["survey", "Survey", 730, false], + ["environmental", "Environmental report", 730, false], + ["appraisal", "Appraisal", 365, false], + ["loan", "Loan documents", 3650, false], +]; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +/** `2026-09-17` → `17 Sep 2026`, from a fixed table rather than `toLocaleDateString`, whose `en-GB` + * short month is "Sept" on this Node's ICU and "Sep" on another — see `proforma/covenantCard.ts`. */ +const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +export function day(iso?: string | null): string { + if (!iso) return "—"; + const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso); + const month = m ? MONTHS[Number(m[2]) - 1] : undefined; + return m && month ? `${Number(m[3])} ${month} ${m[1]}` : iso; +} + +/** Render one assessment. */ +export function renderAuthority(host: HTMLElement, a: Authority): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + const gate = a.gate; + const blocking = gate.blocking ?? []; + const advisory = gate.advisory ?? []; + + // THE GATE LEADS. Its purpose is "to stop the work, not to annotate it after the fact", so it is + // the first thing on the card rather than a footnote under the table. + el.insertAdjacentHTML("beforeend", + gate.passes + ? `
Authority is sufficient to underwrite
` + + meta(`Every required fact type has a fresh, authoritative document.`) + : `
` + + `Blocked — ${blocking.length} required fact type(s)
` + + `` + + `` + + blocking.map((b) => `` + + ``).join("") + + `
Fact typewhy it blocks
${esc(label(b.fact_type))}${esc(b.why)}
` + + meta(`Required fact types that are missing, stale or superseded-but-active stop downstream ` + + `analysis rather than being annotated afterwards.`)); + + // ADVISORY IS RENDERED APART. A stale offering package is not a stale tax bill; one list would + // make the required ones look negotiable and the optional ones look alarming. + if (advisory.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + advisory.map((x) => ``).join("") + + `
Advisory — stale, not requiredwhy
${esc(label(x.fact_type))}${esc(x.why)}
`); + } + + // The engine's own named failure: an older file left marked active. It is the one row type a + // reader cannot derive from the others, because nothing about it looks wrong in isolation. + if (a.superseded_still_active?.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + a.superseded_still_active.map((s) => `` + + ``).join("") + + `
Superseded, still activedocumentissue
${esc(label(s.fact_type))}${esc(s.document)}${esc(s.issue)}
` + + meta(`A file that has been superseded and is still marked authoritative is how a stale number ` + + `reaches a committee — the case this table exists to prevent.`, "var(--status-warn)")); + } + + // The table shows AGE AGAINST THRESHOLD rather than a bare "stale", because a tax bill and an + // offering package go stale on different clocks and the verdict is only checkable with both. + if (a.table.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + a.table.map((r) => { + const over = r.age_days != null && r.age_days > r.freshness_days; + return `` + + `` + + ``; + }).join("") + + `
Declareddocumentas ofage / limit
${esc(r.label)}${r.required ? ` required` : ""}${esc(r.document)}${day(r.as_of)}` + + `${r.age_days ?? "—"} / ${r.freshness_days}d
`); + } + + const undeclared = FACT_TYPES.filter(([k]) => !a.table.some((r) => r.fact_type === k)); + if (undeclared.length) { + el.insertAdjacentHTML("beforeend", meta( + `Not declared: ${undeclared.map(([, l, , req]) => esc(l) + (req ? " (required)" : "")).join(" · ")}.`)); + } + + el.insertAdjacentHTML("beforeend", meta(esc(a.note))); + host.appendChild(el); + return el; +} + +function label(factType: string): string { + return FACT_TYPES.find(([k]) => k === factType)?.[1] ?? factType; +} + +/** One row of the editable table, as the PUT wants it. */ +export interface AuthorityEntry { + fact_type: string; + document: string; + as_of: string; + freshness_days?: number | null; + authoritative?: boolean; + supersedes?: string[]; +} + +/** The entries a saved assessment implies, so the editor opens on what is already declared rather + * than on a blank form. Superseded rows are not returned by the GET — it reports the authoritative + * view — so this round-trips what IS authoritative and leaves archival history to the server. */ +export function entriesFromAssessment(a: Authority): AuthorityEntry[] { + return a.table.map((r) => ({ + fact_type: r.fact_type, document: r.document, as_of: r.as_of, + freshness_days: r.freshness_days, authoritative: true, + supersedes: r.supersedes ?? [], + })); +} + +export interface AuthorityCtx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; +} + +export function renderAuthorityCard(root: HTMLElement, ctx: AuthorityCtx): HTMLElement { + const host = document.createElement("div"); + host.id = "pf-authority"; + host.className = "fin-card"; + host.style.marginTop = "10px"; + host.innerHTML = `
Deal-room authority
` + + `
One authoritative document per fact, with its date. A data room holds three ` + + `offering memoranda and a tax bill from before the reassessment; every one is a real file and ` + + `only one of each is the one to underwrite from.
`; + + const out = document.createElement("div"); host.appendChild(out); + const editor = document.createElement("div"); editor.style.marginTop = "8px"; host.appendChild(editor); + root.appendChild(host); + + const rows: AuthorityEntry[] = []; + + const paint = () => { + editor.replaceChildren(); + const table = document.createElement("table"); table.className = "fin-table"; + table.innerHTML = `Fact typedocumentas of` + + `freshness`; + for (const [key, lab, days, required] of FACT_TYPES) { + const row = rows.find((r) => r.fact_type === key); + const tr = document.createElement("tr"); + const name = document.createElement("td"); + name.innerHTML = `${esc(lab)}${required ? ` required` : ""}`; + const docCell = document.createElement("td"); + const doc = document.createElement("input"); + doc.className = "portal-filter"; doc.style.width = "100%"; + doc.placeholder = "document name"; doc.value = row?.document ?? ""; + docCell.appendChild(doc); + const dateCell = document.createElement("td"); + const date = document.createElement("input"); + date.type = "date"; date.className = "portal-filter"; date.value = row?.as_of ?? ""; + dateCell.appendChild(date); + const freshCell = document.createElement("td"); freshCell.className = "num"; + const fresh = document.createElement("input"); + fresh.type = "number"; fresh.className = "portal-filter"; fresh.style.width = "72px"; + fresh.placeholder = String(days); + fresh.title = `Default ${days} days. A tax bill goes stale on a different clock from an offering package.`; + fresh.value = row?.freshness_days != null && row.freshness_days !== days + ? String(row.freshness_days) : ""; + freshCell.appendChild(fresh); + const clearCell = document.createElement("td"); + const clear = document.createElement("button"); + clear.className = "tool-btn"; clear.textContent = "✕"; clear.title = "Remove this declaration"; + clear.onclick = () => { doc.value = ""; date.value = ""; fresh.value = ""; }; + clearCell.appendChild(clear); + + const sync = () => { + const i = rows.findIndex((r) => r.fact_type === key); + // A row with no document is not declared at all — sending an empty one would be refused by + // `validate()`, which requires a document name and a real date on every entry. + if (!doc.value.trim()) { if (i >= 0) rows.splice(i, 1); return; } + const entry: AuthorityEntry = { + fact_type: key, document: doc.value.trim(), as_of: date.value, + freshness_days: fresh.value.trim() === "" ? null : Number(fresh.value), + authoritative: true, + }; + if (i >= 0) rows[i] = entry; else rows.push(entry); + }; + for (const input of [doc, date, fresh]) input.onchange = sync; + clear.addEventListener("click", sync); + + tr.append(name, docCell, dateCell, freshCell, clearCell); + table.appendChild(tr); + } + editor.appendChild(table); + + const actions = document.createElement("div"); actions.style.marginTop = "6px"; + const save = document.createElement("button"); + save.className = "file-btn"; save.textContent = "Save authority table"; + actions.appendChild(save); + editor.appendChild(actions); + editor.insertAdjacentHTML("beforeend", meta( + `Exactly one authoritative document per fact type — the server refuses two, and refuses any ` + + `entry without a date, because an undated document cannot be judged fresh or stale.`)); + + save.onclick = async () => { + const pid = ctx.projectId(); + if (!pid) { out.innerHTML = meta("Open a project first."); return; } + const undated = rows.filter((r) => !r.as_of); + if (undated.length) { + // Caught here rather than sent, because the server's 422 names one entry by index and the + // person is looking at a table of labels. + out.innerHTML = meta( + `${undated.map((r) => esc(label(r.fact_type))).join(", ")} ` + + `${undated.length === 1 ? "has" : "have"} a document but no date. An undated document ` + + `cannot be judged fresh or stale, so the table will not save.`, "var(--status-crit)"); + return; + } + save.disabled = true; + ctx.setStatus("saving the authority table…"); + try { + const res = await ctx.api.saveDealAuthority(pid, rows); + out.replaceChildren(); + // The PUT returns the REASSESSMENT, so the gate on screen is the gate for what was just + // saved. Re-fetching would race the write and could show the previous verdict. + renderAuthority(out, res.assessment); + ctx.setStatus(res.assessment.gate.passes + ? "authority sufficient to underwrite" : "authority blocked"); + } catch (e) { + out.innerHTML = meta(esc((e as Error).message), "var(--status-crit)"); + ctx.setStatus("authority table not saved"); + } finally { + save.disabled = false; + } + }; + }; + + const pid = ctx.projectId(); + if (!pid) { + out.innerHTML = meta("Open a project to declare its authority table."); + return host; + } + out.innerHTML = meta("loading…"); + void ctx.api.dealAuthority(pid).then((a) => { + out.replaceChildren(); + renderAuthority(out, a); + rows.splice(0, rows.length, ...entriesFromAssessment(a)); + paint(); + }).catch((e: Error) => { + out.innerHTML = meta(esc(e.message), "var(--status-crit)"); + paint(); + }); + return host; +} diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index e26bcbab..f63f073d 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -8,6 +8,7 @@ import { drawPackageLines } from "./drawPackage"; import { money, pct } from "./format"; import { renderMassingTab } from "./massingTab"; import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; +import { renderAuthorityCard } from "./authorityCard"; import { renderCovenantCard } from "./covenantCard"; import { renderRentRollQuality } from "./rentRollQuality"; import { renderT12Card } from "./t12Card"; @@ -166,6 +167,11 @@ export class ProformaUI { const out = document.createElement("div"); out.id = "pf-out"; uwSec.appendChild(out); const sens = document.createElement("div"); sens.id = "pf-sens"; uwSec.appendChild(sens); const mc = document.createElement("div"); mc.id = "pf-mc"; uwSec.appendChild(mc); + // The authority table is the gate on whether any figure on this tab should be trusted, so it + // sits with the underwriting rather than beside one of the things it governs. + renderAuthorityCard(uwSec, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + }); this.renderDraws(); }); if (sections.deliver) into(sections.deliver, () => { this.renderDeliverables(); this.renderModelLink(); }); diff --git a/docs/roadmap.md b/docs/roadmap.md index eb37405d..fe10532f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,53 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **AUTHORITY-DARK — a gate you could read and could not satisfy** *(S — + `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/authorityCard.test.ts`, whose tripwire reads + `services/api/src/aec_api/deal_authority.py` off disk)* + + **The finding is sharper than "nobody called it", and my first reading of it was wrong.** + `dealAuthority()` — the READ — has had a caller all along: `apps/web/src/portal/panels/design.ts` + renders the gate on its go/no-go card, and renders it well. What was callerless is + `saveDealAuthority()` — the WRITE. So the table could be **seen and never declared**: the design + panel could say *"Source facts NOT current"* and nothing in the product could make them current. + *A gate you can read and cannot satisfy is worse to ship than one nobody can see*, because it is + visibly unfinished and reads as a bug in the analysis rather than as a missing surface. + + The card is on Underwriting — it governs every figure on that tab rather than one of them — and + renders the gate first, advisory apart from blocking, `superseded_still_active` in its own table + (the one row type a reader cannot derive from the others), and age against each fact type's own + freshness limit rather than a bare "stale". + + **The tripwire is the load-bearing test, not the rendering.** `FACT_TYPES` has to be mirrored + because the GET returns declared rows plus the *required* missing ones, so an optional type nobody + has declared appears in neither and a card built from the response could never offer to add one. + The test reads the Python off disk and asserts key, label, default freshness and required flag in + order — the `reportMoments.test.ts` technique. **The `required` flag is the one that matters**: a + drift there shows a fact type as optional while `assess()` blocks on it. + + **A clean negative, recorded as such:** the empty table was measured first, since four engines this + session carried a verdict that went vacuous when nothing had been evaluated. This one fails closed — + `gate.passes` is `not blocking`, and an empty table blocks on all three required fact types. + `services/api/test_verdict_coverage.py` leaves it alone correctly. + + **And a test satisfied by the wrong code path.** The blocking-table test asserted the fact type's + label against the whole card's text, and the same label also appears in the "Not declared" line, + which is built straight from `FACT_TYPES` rather than through `label()` — so a mutation rendering the + raw key in the blocking table passed. *An assertion satisfied by a different code path than the one + it is about is not an assertion about that path.* + +- 🟡 **AUTHORITY-DOWNSTREAM — the CRE cards do not consult the gate that exists to block them** + *(S — `apps/web/src/proforma/`; **OPEN — needs the user's call on how hard to block**)* + + `deal_authority.py`: *"Required fact types that are missing, stale, or superseded-but-still-active + BLOCK downstream analysis — the point is to stop the work, not to annotate it after the fact."* The + residual-land, net-effective-rent, rent-roll-scrub, T-12 and covenant cards all compute regardless. + If the authority table says the rent roll is 87 days past its freshness limit, the NER card values + it and says nothing. Wiring the gate through is coherent and small; **how hard to block — refuse, + warn, or annotate — is a product decision**, and the wrong choice in either direction is expensive: + refusing makes the tab unusable during diligence, annotating makes the gate decorative. + - ✅ ⭐ **VERDICT-COVERAGE — the class found five times by hand, now derived** *(S — Lane J; **CLOSED 2026-09-25**; gated by `services/api/test_verdict_coverage.py`)* From f779f0c0f81d395ed102fe2d8fb90a585b6e0fdc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 06:11:46 +0000 Subject: [PATCH 08/21] DECISION-GATE-DARK: the keystone over everything else on the tab, and nothing called it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit services/api/src/aec_api/decision_gate.py names the failure it exists to prevent: "a package that LOOKS finished reaching a committee, because nothing between the analyst and the room ever asked whether the numbers were sourced." Seven gates -- citation coverage, comp tiering, the T-12 tie-out, the rent-roll scrub, the deal-room authority gate, exhibits, and a named human sign-off -- and two rules that make it honest rather than decorative: "A gate whose evidence was not supplied is `unknown`, and unknown BLOCKS -- absent evidence must never read as a pass. The actions list says what to do, not just what failed." THAT IS THE PRINCIPLE THIS SESSION SPENT ITSELF ENFORCING IN CONSUMERS, implemented here at the composition level. ApiClient.decisionGate() was callerless, so none of it was reachable -- and the card's job is therefore not to add judgement but to avoid subtracting it. The actions lead: a list of failures is a status report, a list of actions is what somebody can act on at the moment they are stopped. `unknown` renders as blocking in WORD and COLOUR -- "no evidence — blocks", on the warn token, distinct from both pass and fail -- because a card that renders it as a shrug states something the engine did not. And a vacuous pass keeps its reason: with nothing supplied, `exhibits` passes "no exhibits were required for this package", a gate that tested nothing, which is exactly what the other six exist to refuse. THE EVIDENCE IT GATHERS IS NAMED, AND WHAT IT CANNOT GATHER IT LEAVES ABSENT. The card sends the two things this app can produce on demand -- the authority assessment and the rent-roll scrub -- and leaves the other five absent, which makes them unknown, which blocks. That is the correct answer rather than a gap in the card: a committee package without a citation contract, tiered comps, a tied-out T-12 or a named signer is not ready. Naming what WAS supplied makes an unknown legible as "not gathered here" rather than "gathered and found wanting", which are two different things to do about it. A source that fails is omitted rather than filled in, because an invented payload would make a gate pass or fail on something the card made up. Measured with no evidence at all: 6 of 7 unknown, verdict blocked, six actions. Ten tests, seven mutations, each redding its own: a neutral word for unknown, the pass colour for unknown, dropping the actions table, labelling actions by key, dropping the per-gate detail, folding unknown into the failed count, and inventing a payload for a source that failed. AND THE LANE TABLE CAUGHT THE ITEM I OPENED. roadmapLanes.test.ts refused the build: AUTHORITY-DOWNSTREAM was opened as a roadmap item and assigned to neither a lane nor Parked. Parked is the right home -- that list exists for items needing a decision, "so nobody starts one thinking it is a sprint item" -- and this one is exactly a decision: whether a stale authority table should REFUSE, WARN or ANNOTATE the analysis downstream of it. Wrong in either direction is expensive: refusing makes the tab unusable during diligence, annotating makes the gate decorative. Verified: tsc --noEmit clean, eslint clean, npm run build clean, 270 files / 2,930 web tests pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 52 ++++++ apps/web/src/api/clientCallers.test.ts | 2 +- .../web/src/proforma/decisionGateCard.test.ts | 146 ++++++++++++++++ apps/web/src/proforma/decisionGateCard.ts | 165 ++++++++++++++++++ apps/web/src/proforma/proforma.ts | 6 + docs/roadmap.md | 24 ++- 6 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/proforma/decisionGateCard.test.ts create mode 100644 apps/web/src/proforma/decisionGateCard.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d21c5687..635c2b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,58 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### DECISION-GATE-DARK — the keystone over everything else on the tab, and nothing called it + +`services/api/src/aec_api/decision_gate.py` names the failure it exists to prevent: + +> a package that *looks* finished reaching a committee, because nothing between the analyst and the +> room ever asked whether the numbers were sourced. + +Seven gates — citation coverage, comp tiering, the T-12 tie-out, the rent-roll scrub, the deal-room +authority gate, exhibits, and a named human sign-off — and two rules that make it honest rather than +decorative: + +> A gate whose evidence was not supplied is `unknown`, and unknown BLOCKS — absent evidence must never +> read as a pass. The actions list says what to do, not just what failed. + +**That is the principle this session spent itself enforcing in consumers, implemented here at the +composition level** — so the card's job is not to add judgement but to avoid subtracting it. +`ApiClient.decisionGate()` was callerless, so none of it was reachable. + +### Added + +- **`apps/web/src/proforma/decisionGateCard.ts`**, last on the Underwriting tab because it is the gate + over everything above it: it reads the evidence the other cards produce rather than the conclusions + they draw. +- **The actions lead.** A list of failures is a status report; a list of actions is what somebody can + act on at the moment they are stopped, which is the engine's stated contract. +- **`unknown` is rendered as blocking, in word and in colour** — *"no evidence — blocks"*, on the warn + token, distinct from both pass and fail. A card that renders it as a shrug states something the + engine did not. +- **A vacuous pass keeps its reason.** With nothing supplied, `exhibits` passes *"no exhibits were + required for this package"* — a gate that tested nothing, which is exactly what the other six exist + to refuse, so it must not render as a bare tick. +- **The evidence it gathers is named.** The card sends the two things this app can produce on demand — + the authority assessment and the rent-roll scrub — and leaves the other five absent, which makes them + `unknown`, which blocks. **That is the correct answer rather than a gap in the card**, and naming + what was supplied makes an `unknown` legible as *"not gathered here"* rather than *"gathered and + found wanting"* — two different things to do about it. A source that fails is omitted rather than + filled in: an invented payload would make a gate pass or fail on something the card made up. +- Measured with no evidence at all: **6 of 7 `unknown`, verdict blocked, six actions emitted.** +- `decisionGateCard.test.ts` — ten tests, **seven mutations**, each redding its own: a neutral word for + `unknown`, the pass colour for `unknown`, dropping the actions table, labelling actions by key, + dropping the per-gate detail, folding `unknown` into the failed count, and inventing a payload for a + source that failed. + +### And the lane table caught the item I opened + +`apps/web/src/shell/roadmapLanes.test.ts` refused the build: **AUTHORITY-DOWNSTREAM** was opened as a +roadmap item and assigned to neither a lane nor Parked. Parked is the right home — that list exists +for items needing a decision, *"so nobody starts one thinking it is a sprint item"* — and this one is +exactly a decision: whether a stale authority table should **refuse**, **warn** or **annotate** the +analysis downstream of it. Wrong in either direction is expensive: refusing makes the tab unusable +during diligence, annotating makes the gate decorative. + ### AUTHORITY-DARK — a gate you could read and could not satisfy `services/api/src/aec_api/deal_authority.py` opens with the case for itself: diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 81703a91..68e7c626 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -298,7 +298,7 @@ const UNCALLED: readonly string[] = [ "ciLatest", "citedQuery", "clashFederated", "clausePlaybook", "clientDecisions", "codeAdoptions", "codeCheck", "colorFacets", "competitiveSupply", "connectElements", "costSummary", "createAssembly", "createGroup", - "createType", "decisionGate", + "createType", "docGraph", "draftPost", "drawingSchedulesCalc", "drawingSetPlan", "drawingsSyncStatus", "ebcPathways", "editType", "elements5dMap", "energyModel", "equipmentSpecCheck", diff --git a/apps/web/src/proforma/decisionGateCard.test.ts b/apps/web/src/proforma/decisionGateCard.test.ts new file mode 100644 index 00000000..9a5de41f --- /dev/null +++ b/apps/web/src/proforma/decisionGateCard.test.ts @@ -0,0 +1,146 @@ +import { describe, expect, it, vi } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { gatherEvidence, renderDecisionGate } from "./decisionGateCard"; + +/** + * CRE-DECISION-GATE composes the evidence the rest of this tab produces, and its contract is the + * principle this session spent itself enforcing in consumers — implemented, for once, in the engine: + * + * > A gate whose evidence was not supplied is `unknown`, and unknown BLOCKS — absent evidence must + * > never read as a pass. The actions list says what to do, not just what failed. + * + * So the card's job is not to add judgement but to avoid subtracting it. What is pinned here is that + * `unknown` reads as blocking rather than as neutral, that the actions lead, and that a *vacuous* + * pass is rendered with its reason — a gate that passed because nothing was required is not the same + * claim as one that passed because it was tested. + */ + +type Gate = Awaited>; + +const G = (gate: string, label: string, status: "pass" | "fail" | "unknown", + detail: string, action = "") => ({ gate, label, status, detail, action }); + +const BLOCKED = (over: Partial = {}): Gate => ({ + verdict: "blocked", ready: false, + gates: [ + G("sourced", "Answers are sourced", "unknown", "no cited-answer contract was supplied", + "Produce the answer through the cited-answer contract."), + G("rent_roll_scrubbed", "Rent roll reconciles", "fail", "3 checks failed", + "Resolve the rent-roll findings."), + G("exhibits", "Required exhibits are present", "pass", + "no exhibits were required for this package"), + ], + blocking: [], actions: [ + { gate: "sourced", action: "Produce the answer through the cited-answer contract." }, + { gate: "rent_roll_scrubbed", action: "Resolve the rent-roll findings." }, + ], + counts: { total: 3, passed: 1, failed: 1, unknown: 1 }, + note: "A gate whose evidence was not supplied is `unknown`, and unknown BLOCKS.", + ...over, +} as Gate); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; + +describe("the verdict", () => { + it("counts the no-evidence gates separately from the failures", () => { + const t = renderDecisionGate(mount(), BLOCKED()).textContent ?? ""; + expect(t).toContain("Blocked — not ready for committee"); + expect(t).toContain("1 passed"); + expect(t).toContain("1 failed"); + expect(t).toContain("1 with no evidence"); + }); + + it("says ready plainly when it is", () => { + const t = renderDecisionGate(mount(), BLOCKED({ + verdict: "ready", ready: true, actions: [], + gates: [G("exhibits", "Required exhibits are present", "pass", "all 3 present")], + counts: { total: 1, passed: 1, failed: 0, unknown: 0 }, + })).textContent ?? ""; + expect(t).toContain("Ready for committee"); + expect(t).not.toContain("Blocked"); + }); +}); + +describe("unknown is not neutral", () => { + // The engine blocks on it, so a card that renders it as a shrug states something the engine did + // not. Asserted on the rendered word AND the colour, because either alone passes a card that says + // the right word in grey or the wrong word in amber. + it("reads as blocking, in word and in colour", () => { + const el = renderDecisionGate(mount(), BLOCKED()); + const cells = [...el.querySelectorAll("td")]; + const unknown = cells.find((c) => c.textContent?.includes("no evidence — blocks")); + expect(unknown, "an unknown gate does not say it blocks").toBeTruthy(); + expect(unknown!.style.color).toBe("var(--status-warn)"); + }); + + it("does not colour an unknown the same as a pass", () => { + const el = renderDecisionGate(mount(), BLOCKED()); + const colourOf = (word: string) => + [...el.querySelectorAll("td")].find((c) => c.textContent === word)?.style.color; + expect(colourOf("pass")).toBeTruthy(); + expect(colourOf("no evidence — blocks")).not.toBe(colourOf("pass")); + expect(colourOf("fail")).not.toBe(colourOf("no evidence — blocks")); + }); +}); + +describe("the actions lead", () => { + it("renders what to do, labelled by gate, above the gate table", () => { + const el = renderDecisionGate(mount(), BLOCKED()); + const tables = [...el.querySelectorAll("table")]; + const first = [...tables[0]!.querySelectorAll("tr")].slice(1) + .map((tr) => [...tr.querySelectorAll("td")].map((td) => td.textContent)); + expect(first).toEqual([ + ["Answers are sourced", "Produce the answer through the cited-answer contract."], + ["Rent roll reconciles", "Resolve the rent-roll findings."], + ]); + }); + + it("uses the gate's label, not its key", () => { + // `sourced` is the key; "Answers are sourced" is what a person reads. + expect(renderDecisionGate(mount(), BLOCKED()).textContent).not.toContain("rent_roll_scrubbed"); + }); +}); + +describe("a vacuous pass is rendered with its reason", () => { + it("keeps the engine's detail beside a pass that was never tested", () => { + // "no exhibits were required for this package" is a pass that tested nothing — exactly what the + // other six gates exist to refuse elsewhere, so it must not render as a bare tick. + expect(renderDecisionGate(mount(), BLOCKED()).textContent) + .toContain("no exhibits were required for this package"); + }); +}); + +describe("gathering evidence", () => { + it("sends what this app can produce and leaves the rest absent", async () => { + const api = { + dealAuthority: vi.fn().mockResolvedValue({ gate: { passes: true } }), + rentRollScrub: vi.fn().mockResolvedValue({ clean: true }), + } as unknown as ApiClient; + expect(await gatherEvidence(api, "p1")).toEqual({ + authority: { gate: { passes: true } }, rent_scrub: { clean: true }, + }); + }); + + it("omits a source that failed rather than sending a half-answer", async () => { + // An omitted key makes its gate `unknown`, which blocks. Sending a partial or invented payload + // would make it pass or fail on something this card made up. + const api = { + dealAuthority: vi.fn().mockRejectedValue(new Error("no authority table")), + rentRollScrub: vi.fn().mockResolvedValue({ clean: false }), + } as unknown as ApiClient; + expect(await gatherEvidence(api, "p1")).toEqual({ rent_scrub: { clean: false } }); + }); + + it("returns nothing when both sources fail, rather than throwing", async () => { + const api = { + dealAuthority: vi.fn().mockRejectedValue(new Error("x")), + rentRollScrub: vi.fn().mockRejectedValue(new Error("y")), + } as unknown as ApiClient; + expect(await gatherEvidence(api, "p1")).toEqual({}); + }); +}); diff --git a/apps/web/src/proforma/decisionGateCard.ts b/apps/web/src/proforma/decisionGateCard.ts new file mode 100644 index 00000000..07b2a68f --- /dev/null +++ b/apps/web/src/proforma/decisionGateCard.ts @@ -0,0 +1,165 @@ +/** The pre-committee readiness gate — the keystone over everything else on this tab. + * + * `services/api/src/aec_api/decision_gate.py` names the failure it exists to prevent: + * + * > a package that *looks* finished reaching a committee, because nothing between the analyst and + * > the room ever asked whether the numbers were sourced. + * + * Seven gates — citation coverage, comp tiering, the T-12 tie-out, the rent-roll scrub, the + * deal-room authority gate, exhibits, and a named human sign-off — and two rules that make it + * honest rather than decorative: + * + * > A gate whose evidence was not supplied is `unknown`, and unknown BLOCKS — absent evidence must + * > never read as a pass. The actions list says what to do, not just what failed. + * + * **That is the principle this whole session has been enforcing in consumers, implemented here at + * the composition level** — so the card's job is not to add judgement but to avoid subtracting it: + * render `unknown` as blocking rather than as neutral, and lead with the actions. + * + * `ApiClient.decisionGate()` was callerless, so none of that was reachable. + * + * MEASURED WITH NO EVIDENCE: 6 of 7 `unknown`, verdict `blocked`, six actions emitted. The seventh, + * `exhibits`, passes *"no exhibits were required for this package"* — a vacuous pass, rendered with + * its reason rather than as a bare tick, because a pass that was never tested is the thing the other + * six are protected against. + * + * WHAT IT SENDS, AND WHAT IT DELIBERATELY DOES NOT INVENT + * The card gathers the two pieces of evidence this app can already produce on demand — the + * deal-room authority assessment and the rent-roll scrub — and sends them. The other five it leaves + * absent, which makes them `unknown`, which blocks. **That is the correct answer, not a gap in the + * card**: a committee package without a citation contract, tiered comps, a tied-out T-12 or a named + * signer is not ready, and saying so with the engine's own actions is more useful than a card that + * quietly omits the gates it cannot feed. *An absent input is a finding here, which is the one place + * in this product where that is true by design.* + */ +import type { ApiClient } from "../api/client"; +import { escapeHtml as esc } from "../ui/feedback"; + +type Gate = Awaited>; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +/** `unknown` is not neutral here — the engine blocks on it, so it must not READ as neutral either. */ +const COLOUR: Record = { + pass: "var(--status-good)", + fail: "var(--status-crit)", + unknown: "var(--status-warn)", +}; + +/** The word for a status, in the engine's own terms rather than a tick and a cross. */ +const WORD: Record = { + pass: "pass", + fail: "fail", + unknown: "no evidence — blocks", +}; + +export function renderDecisionGate(host: HTMLElement, g: Gate): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + const c = g.counts; + + el.insertAdjacentHTML("beforeend", + `
` + + (g.ready ? "Ready for committee" : "Blocked — not ready for committee") + + `
` + + meta(`${c.passed ?? 0} passed · ${c.failed ?? 0} failed · ` + + `${c.unknown ?? 0} with no evidence — of ${c.total ?? 0} gates.`)); + + // THE ACTIONS LEAD, because the engine's contract is that the verdict says what to DO. A list of + // failures is a status report; a list of actions is the thing somebody can act on at the moment + // they are stopped. + if (g.actions.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + g.actions.map((a) => `` + + ``).join("") + + `
To unblockdo this
${esc(labelOf(g, a.gate))}${esc(a.action)}
`); + } + + el.insertAdjacentHTML("beforeend", + `` + + `` + + g.gates.map((x) => + `` + + `` + + ``).join("") + + `
Gatestatedetail
${esc(x.label)}${esc(WORD[x.status] ?? x.status)}${esc(x.detail)}
`); + + el.insertAdjacentHTML("beforeend", meta(esc(g.note))); + host.appendChild(el); + return el; +} + +function labelOf(g: Gate, gate: string): string { + return g.gates.find((x) => x.gate === gate)?.label ?? gate; +} + +export interface DecisionGateCtx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; +} + +/** Gather the evidence this app can produce on demand. The rest stays absent — and therefore + * `unknown`, and therefore blocking, which is the honest answer rather than a gap in the card. */ +export async function gatherEvidence(api: ApiClient, pid: string): Promise> { + const [authority, scrub] = await Promise.allSettled([ + api.dealAuthority(pid), + api.rentRollScrub(pid), + ]); + const ev: Record = {}; + if (authority.status === "fulfilled") ev.authority = authority.value; + if (scrub.status === "fulfilled") ev.rent_scrub = scrub.value; + return ev; +} + +export function renderDecisionGateCard(root: HTMLElement, ctx: DecisionGateCtx): HTMLElement { + const host = document.createElement("div"); + host.id = "pf-decision-gate"; + host.className = "fin-card"; + host.style.marginTop = "10px"; + host.innerHTML = `
Pre-committee readiness
` + + `
Seven gates over the evidence, not over the conclusions. A gate whose ` + + `evidence was not supplied is unknown, and unknown blocks — absent evidence ` + + `must never read as a pass.
`; + + const actions = document.createElement("div"); actions.style.marginTop = "6px"; + const go = document.createElement("button"); + go.className = "file-btn"; go.textContent = "Check readiness"; + actions.appendChild(go); host.appendChild(actions); + const out = document.createElement("div"); host.appendChild(out); + root.appendChild(host); + + go.onclick = async () => { + const pid = ctx.projectId(); + if (!pid) { out.innerHTML = meta("Open a project first."); return; } + go.disabled = true; + out.innerHTML = meta("gathering evidence…"); + ctx.setStatus("checking pre-committee readiness…"); + try { + const evidence = await gatherEvidence(ctx.api, pid); + const g = await ctx.api.decisionGate(pid, evidence); + out.replaceChildren(); + // Name which evidence this card supplied, so an `unknown` is legible as "not gathered here" + // rather than as "gathered and found wanting" — two different things to do about it. + const supplied = Object.keys(evidence).sort(); + out.insertAdjacentHTML("beforeend", meta( + supplied.length + ? `Supplied from this project: ${supplied.map((k) => esc(k.replace(/_/g, " "))).join(", ")}. ` + + `Anything else was not gathered here and shows as no evidence.` + : `No evidence could be gathered from this project — every gate below is reporting an ` + + `absence, not a failure.`)); + renderDecisionGate(out, g); + ctx.setStatus(g.ready ? "ready for committee" : `blocked — ${g.actions.length} action(s)`); + } catch (e) { + out.innerHTML = meta(esc((e as Error).message), "var(--status-crit)"); + ctx.setStatus("readiness check failed"); + } finally { + go.disabled = false; + } + }; + + return host; +} diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index f63f073d..a13d73f3 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -10,6 +10,7 @@ import { renderMassingTab } from "./massingTab"; import { applyLandBasis, landBasis, renderResidualLandCard, type LandLine } from "./residualLandCard"; import { renderAuthorityCard } from "./authorityCard"; import { renderCovenantCard } from "./covenantCard"; +import { renderDecisionGateCard } from "./decisionGateCard"; import { renderRentRollQuality } from "./rentRollQuality"; import { renderT12Card } from "./t12Card"; import { renderTestFitTab } from "./testfitTab"; @@ -172,6 +173,11 @@ export class ProformaUI { renderAuthorityCard(uwSec, { api: this.api, projectId: this.projectId, setStatus: this.setStatus, }); + // Last on the tab, because it is the gate over everything above it: the readiness check reads + // the evidence the other cards produce rather than the conclusions they draw. + renderDecisionGateCard(uwSec, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + }); this.renderDraws(); }); if (sections.deliver) into(sections.deliver, () => { this.renderDeliverables(); this.renderModelLink(); }); diff --git a/docs/roadmap.md b/docs/roadmap.md index fe10532f..e4369b40 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,28 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **DECISION-GATE-DARK — the keystone over everything else on the tab, and nothing called it** + *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/decisionGateCard.test.ts`)* + + `decision_gate.py` composes citation coverage, comp tiering, the T-12 tie-out, the rent-roll scrub, + the authority gate, exhibits and a named human sign-off, and its contract is **the principle this + session spent itself enforcing in consumers, implemented in the engine**: *"A gate whose evidence + was not supplied is `unknown`, and unknown BLOCKS — absent evidence must never read as a pass. The + actions list says what to do, not just what failed."* `ApiClient.decisionGate()` was callerless. + + So the card's job is not to add judgement but to avoid subtracting it: the actions lead, `unknown` + renders as blocking in **word and colour** rather than as a shrug, and a vacuous pass keeps its + reason — with nothing supplied, `exhibits` passes *"no exhibits were required for this package"*, + which is a gate that tested nothing and must not read as a tick. + + **The evidence it gathers is named, and what it cannot gather it leaves absent.** The card sends the + authority assessment and the rent-roll scrub; the other five stay `unknown`, and therefore blocking. + *That is the correct answer rather than a gap in the card* — and naming what was supplied makes an + `unknown` legible as "not gathered here" rather than "gathered and found wanting". A source that + fails is omitted rather than filled in, because an invented payload would make a gate pass or fail + on something the card made up. Measured with no evidence: 6 of 7 unknown, blocked, six actions. + - ✅ ⭐ **AUTHORITY-DARK — a gate you could read and could not satisfy** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/authorityCard.test.ts`, whose tripwire reads @@ -4258,7 +4280,7 @@ two rows share a path, so two agents in different rows cannot collide. **Parked — not available to pick up.** These are decisions or multi-release commitments, listed so nobody starts one thinking it is a sprint item: QUALITY-ROOM · R26-V-TIMING · R24-PERSONA-SHAPE · -R24-IDENTITY · R32-TAXONOMY-LIFECYCLE (all five need the user's call) · PHOTO-PIN · CMMS-OPS (BIG-TICKET: open **one**, slice +R24-IDENTITY · R32-TAXONOMY-LIFECYCLE · AUTHORITY-DOWNSTREAM (all six need the user's call) · PHOTO-PIN · CMMS-OPS (BIG-TICKET: open **one**, slice it) · REL-7 (gated on RT-KNIP) · R35-SANDBOX-ISOLATION (process/container isolation for snippet execution — a genuine design change, needs the user's call on deployment shape) · R35-PREFLIGHT-CI (run the prod-config validator against the **actual deploy overlay** in CI — still needs a decision on where the deploy env template lives. **Split 2026-08-02:** the half that needs NO decision — smoke the validator against a *synthetic* safe posture → exit 0 and an unsafe one → exit 1, catching a validator crash, a check regressed to a no-op, or a FAIL demoted — is unparked as a ~10-line CI step; the security session has claimed it). **A fourth was wrong until 2026-08-07, and it is wrong in the way the table could not see.** From 482084bb920e506834a7e53c456ebc4e24865209 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 06:37:00 +0000 Subject: [PATCH 09/21] VERDICT-COVERAGE: the gate caught the card written to demonstrate it services/api/test_verdict_coverage.py was added in f661a33a to derive, rather than re-find by hand, the class this branch keeps hitting: an engine returns a boolean verdict beside a count of what it could not evaluate, and a consumer renders the verdict without the coverage. One commit later it failed on apps/web/src/proforma/decisionGateCard.ts -- the card written to demonstrate that very principle, shipped in f779f0c0 and pushed red. THE FINDING IS REAL AND I NEARLY TALKED MYSELF OUT OF IT. The card does not render the rent-roll scrub's `clean`; it forwards the whole payload to decision_gate.evaluate and renders that engine's answer. So the first reading was "a forwarder, not a renderer -- false positive". Measuring instead of reasoning showed otherwise. _gate() splats **extra onto the row, so `rent_roll_scrubbed` arrives at the client carrying `ran`, `failed` and `not_applicable`, and the card declared five fields and rendered `detail`, which expresses none of them. Measured through the real engine, a scrub of {ran: 1, not_applicable: 6, failed: 0}: status: "pass" detail: "the scrub ran but found nothing" dropped: {ran: 1, failed: 0, not_applicable: 6} "Nothing" there means NO PROBLEMS FOUND. It reads as "nothing was wrong", over six checks that never ran -- the same defect one layer up from the four this branch already fixed, in the card that argues for fixing them. THE ENGINE IS SOUND AND STAYS UNTOUCHED. decision_gate already requires ran > 0 before it will pass this gate, and it already attaches the coverage to the row. It measured the thing and handed it over; the client dropped it. DECLARED, NOT RE-SPELLED. The response was an inline object literal at the call site, so DecisionGateRow and DecisionGateResult are now named interfaces in apps/web/src/api/creDeal.ts -- the shape noRespelledShapes.test.ts exists to stop, and the reason the three coverage fields could sit on the wire unread by any derivation over declarations. WRITTEN AS A RULE, NOT A SPECIAL CASE. coverageOf() reports whatever coverage a row carries; only rent_roll_scrubbed carries any today, and the next _gate(..., **extra) will arrive the same way. A special case is a thing somebody has to remember to widen. The engine's own status word is left alone: this card declines to drop what the engine measured, it does not overrule the engine -- a card that demoted this pass to a warning would be inventing a verdict the engine did not reach. Four tests, four mutations, each redding its own: never rendering the coverage, coverageOf always null, coverageOf reporting at full coverage, and a total counting only the checks that ran. The full-coverage case is the anti-vacuity twin -- without it, a coverageOf that always returned null would pass the positive assertion only by accident of the fixture. Verified: test_verdict_coverage green (5 subset verdicts, 616 client methods, 0 consumers reading a verdict without its coverage); tsc --noEmit clean; eslint clean; 20 tests across decisionGateCard and clientCallers. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 21 +++++++ apps/web/src/api/creDeal.ts | 30 ++++++++-- .../web/src/proforma/decisionGateCard.test.ts | 59 ++++++++++++++++++- apps/web/src/proforma/decisionGateCard.ts | 25 +++++++- 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 635c2b57..057aed3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### The pre-committee gate card dropped the coverage its own gate demands + +`services/api/test_verdict_coverage.py` — added one commit earlier, to derive exactly this class — +failed on `apps/web/src/proforma/decisionGateCard.ts`, the card written to demonstrate the principle +it enforces. + +`_gate()` in `decision_gate.py` splats `**extra` onto the row, so `rent_roll_scrubbed` arrives +carrying `ran`, `failed` and `not_applicable`. The card declared five fields and rendered `detail`, +which expresses none of them. Measured through the real engine with a scrub of `ran: 1, +not_applicable: 6, failed: 0`: + +> `status: "pass"` · `detail: "the scrub ran but found nothing"` + +— where "nothing" means *no problems found*, over six checks that never ran. + +`DecisionGateRow` and `DecisionGateResult` are now named interfaces in `apps/web/src/api/creDeal.ts` +rather than an inline re-spelling, and `coverageOf()` renders the coverage beside the verdict as a +rule over whatever a row carries, not as a special case for the one gate that carries it today. The +engine's own status word is left alone: this card declines to drop what the engine measured, it does +not overrule the engine. + ### DECISION-GATE-DARK — the keystone over everything else on the tab, and nothing called it `services/api/src/aec_api/decision_gate.py` names the failure it exists to prevent: diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index e9a687d0..f11ad699 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -130,6 +130,29 @@ export interface AuthorityEntryWire { reviewer?: string | null; } +/** One row of the pre-committee gate. + * + * **The last three are the COVERAGE, and they were undeclared until 2026-09-25.** `_gate()` in + * `decision_gate.py` splats `**extra` onto the row, so `rent_roll_scrubbed` arrives carrying + * `ran`, `failed` and `not_applicable` — and the card declared five fields and rendered `detail`, + * so a gate that PASSED with one check run and six not applicable read "pass · the scrub ran but + * found nothing", where "nothing" means "no problems". Found by + * `services/api/test_verdict_coverage.py`, on the card written to demonstrate the principle that + * gate enforces. */ +export interface DecisionGateRow { + gate: string; label: string; status: "pass" | "fail" | "unknown"; + detail: string; action: string; + ran?: number; failed?: number; not_applicable?: number; +} + +export interface DecisionGateResult { + verdict: "ready" | "blocked"; ready: boolean; + gates: DecisionGateRow[]; + blocking: { gate: string; status: string; detail: string }[]; + actions: { gate: string; action: string }[]; + counts: Record; note: string; +} + export function withCreDeal>(Base: TBase) { return class CreDeal extends Base { /** CRE-HOLDSELL — hold vs sell: incremental hold-year IRRs against the proceeds declined today. */ @@ -228,12 +251,7 @@ export function withCreDeal>(Base: TBase) { } /** CRE-DECISION-GATE — the pre-committee gate; a gate without evidence is unknown, and blocks. */ decisionGate(pid: string, evidence: unknown, requiredExhibits?: string[], minCoverage?: number) { - return this.json<{ verdict: "ready" | "blocked"; ready: boolean; - gates: { gate: string; label: string; status: "pass" | "fail" | "unknown"; detail: string; - action: string }[]; - blocking: { gate: string; status: string; detail: string }[]; - actions: { gate: string; action: string }[]; - counts: Record; note: string }>( + return this.json( `/projects/${pid}/decision-gate`, { method: "POST", body: JSON.stringify({ evidence, required_exhibits: requiredExhibits, min_coverage: minCoverage ?? 0.9 }) }); diff --git a/apps/web/src/proforma/decisionGateCard.test.ts b/apps/web/src/proforma/decisionGateCard.test.ts index 9a5de41f..6a9c78b5 100644 --- a/apps/web/src/proforma/decisionGateCard.test.ts +++ b/apps/web/src/proforma/decisionGateCard.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import type { ApiClient } from "../api/client"; -import { gatherEvidence, renderDecisionGate } from "./decisionGateCard"; +import { coverageOf, gatherEvidence, renderDecisionGate } from "./decisionGateCard"; /** * CRE-DECISION-GATE composes the evidence the rest of this tab produces, and its contract is the @@ -115,6 +115,63 @@ describe("a vacuous pass is rendered with its reason", () => { }); }); +describe("a gate's own coverage is not dropped", () => { + /** + * **This card shipped without this and its own sibling gate caught it.** + * `services/api/test_verdict_coverage.py` — written one commit earlier, to derive exactly this + * class — failed on `decisionGateCard.ts`. `_gate()` splats `**extra` onto the row, so + * `rent_roll_scrubbed` arrives carrying `ran`, `failed` and `not_applicable`; the card declared + * five fields and rendered `detail`, which expresses none of them. + * + * Measured through the real engine: a scrub with `ran: 1, not_applicable: 6, failed: 0` returns + * **`status: "pass"`** and **`detail: "the scrub ran but found nothing"`** — where "nothing" + * means "no problems found" and reads as "nothing was wrong", over six checks that never ran. + */ + const SCRUBBED = () => BLOCKED({ + gates: [G("rent_roll_scrubbed", "The rent roll reconciles to income", "pass", + "the scrub ran but found nothing")], + counts: { total: 1, passed: 1, failed: 0, unknown: 0 }, + }); + const withCoverage = (over: Record) => { + const g = SCRUBBED(); + Object.assign(g.gates[0]!, over); + return g; + }; + + it("renders what could not be checked beside the verdict", () => { + const t = renderDecisionGate(mount(), withCoverage({ ran: 1, not_applicable: 6, failed: 0 })) + .textContent ?? ""; + expect(t).toContain("1 of 7 check(s) ran"); + expect(t).toContain("6 could not"); + }); + + it("says nothing when every check ran — silence must mean full coverage", () => { + // The anti-vacuity twin. Without it, a `coverageOf` that always returned null would pass the + // assertion above only by accident of the fixture, and this file would still read green. + const t = renderDecisionGate(mount(), withCoverage({ ran: 7, not_applicable: 0, failed: 0 })) + .textContent ?? ""; + expect(t).not.toContain("could not"); + expect(t).toContain("the scrub ran but found nothing"); // the row is still rendered + }); + + it("leaves the engine's own status word alone", () => { + // The card declines to DROP what the engine measured; it does not overrule the engine. A card + // that demoted this pass to a warning would be inventing a verdict the engine did not reach. + const el = renderDecisionGate(mount(), withCoverage({ ran: 1, not_applicable: 6, failed: 0 })); + const cells = [...el.querySelectorAll("td")]; + expect(cells.some((c) => c.textContent === "pass")).toBe(true); + }); + + it("is a rule over whatever coverage a row carries, not a special case for one gate", () => { + expect(coverageOf({ ran: 2, not_applicable: 3 })).toBe("2 of 5 check(s) ran · 3 could not"); + expect(coverageOf({ ran: 5, not_applicable: 0 })).toBeNull(); + expect(coverageOf({})).toBeNull(); + // A row that reports only what could NOT run still has to report it — `ran` defaulting to 0 is + // the worst case, not an absent one. + expect(coverageOf({ not_applicable: 4 })).toBe("0 of 4 check(s) ran · 4 could not"); + }); +}); + describe("gathering evidence", () => { it("sends what this app can produce and leaves the rest absent", async () => { const api = { diff --git a/apps/web/src/proforma/decisionGateCard.ts b/apps/web/src/proforma/decisionGateCard.ts index 07b2a68f..2b0cc9fe 100644 --- a/apps/web/src/proforma/decisionGateCard.ts +++ b/apps/web/src/proforma/decisionGateCard.ts @@ -54,6 +54,26 @@ const WORD: Record = { unknown: "no evidence — blocks", }; +/** The coverage a row carries, or null when it carries none. + * + * **This card shipped without it and `services/api/test_verdict_coverage.py` caught it** — the gate + * written one commit earlier, on the card written to demonstrate the principle it enforces. + * `_gate()` splats `**extra` onto the row, so `rent_roll_scrubbed` arrives with `ran`, `failed` and + * `not_applicable`, and its `detail` expresses none of them: a scrub with ONE check run and SIX not + * applicable is a `pass` reading *"the scrub ran but found nothing"*, where "nothing" means "no + * problems found" and is read as "nothing was wrong". + * + * Written as a RULE over whatever coverage a row carries rather than as a special case for the one + * gate that carries it today, because the next `_gate(..., **extra)` will arrive the same way and a + * special case is a thing somebody has to remember to widen. The engine's own status word is left + * alone — this card does not overrule the engine, it declines to drop what the engine measured. */ +export function coverageOf(g: { ran?: number; not_applicable?: number }): string | null { + const na = g.not_applicable ?? 0; + if (!na) return null; + const ran = g.ran ?? 0; + return `${ran} of ${ran + na} check(s) ran · ${na} could not`; +} + export function renderDecisionGate(host: HTMLElement, g: Gate): HTMLElement { const el = document.createElement("div"); el.style.marginTop = "8px"; @@ -84,7 +104,10 @@ export function renderDecisionGate(host: HTMLElement, g: Gate): HTMLElement { + g.gates.map((x) => `${esc(x.label)}` + `${esc(WORD[x.status] ?? x.status)}` - + `${esc(x.detail)}`).join("") + + `${esc(x.detail)}` + + (coverageOf(x) ? ` — ` + + `${esc(coverageOf(x)!)}` : ``) + + ``).join("") + ``); el.insertAdjacentHTML("beforeend", meta(esc(g.note))); From b2bb594b54a6d6021529b5e979983b87e84bea47 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 06:39:35 +0000 Subject: [PATCH 10/21] SUPPLY-DARK: an empty competitive set reported the most favourable verdict there is services/api/src/aec_api/supply_pipeline.py weighs a competitive pipeline by what is RECORDED about each project rather than by the label on the deck, and states the distinction it exists for: "'Under construction' on a broker deck and 'under construction' with a recorded construction deed of trust are not the same fact, and a rendering with no permit is neither certain supply nor zero." Two filters (delivery window, product type) and an eight-tier evidence weight; a raw and a weighted months-of-supply index side by side, "because the gap between them IS the argument". ApiClient.competitiveSupply() was written for it and no screen called it. A SECOND FINDING IN THE SAME METHOD: the response was typed Record. Every field was therefore invisible to apps/web/src/api/deadFieldTyped.test.ts and to every other audit that starts from the client's declarations -- the same hole excluded_comparables fell through in SCREEN-VS-REPORT. Five interfaces are now declared in apps/web/src/api/creDeal.ts. MEASURED THROUGH THE ENGINE BEFORE A LINE OF THE CARD WAS WRITTEN. A pipeline of four projects, none matching the subject's product type: counts.competing 0 excluded.counts.wrong_product 4 weighted_index.band "undersupplied" lsi 0 discount_pct 0.0 A market in which every supplied project was filtered out reports THE MOST FAVOURABLE VERDICT THE ENGINE CAN PRODUCE, byte-identical to a market with genuinely no competition. "Undersupplied" is an argument to build -- which is worse than the four `clean: true` cases this branch already fixed, because those at least read as neutral. THE ENGINE IS NOT WRONG AND NOTHING IN IT CLAIMS COVERAGE. counts.competing and excluded.counts are in the response, and its own note says the excluded projects are listed "so a thin competitive set is visible, not implied". The defect would have been entirely the consumer's -- so the card withholds rather than annotates: on a vacuous set there is no band, no totals and no discount, and the exclusions with their reasons are the only thing left on it. It also distinguishes "the filters ate the pipeline" from "there was no pipeline", which are two different findings; collapsing them sends somebody to widen a window that is already right. Three narrower withholdings, each measured rather than assumed: * discount_pct is `round(...) if raw else 0.0`, so a zero raw yields the same 0.0 as a pipeline that needed no discount. Withheld when raw is 0. * certain and rumored units are never added. The engine keeps them apart on purpose and says so; one combined figure is the number it refuses to produce. * from_status_label is rendered, because it marks the row whose tier was INFERRED from a status string rather than read from a recorded flag -- the difference between a fact and a broker's assertion, which is the whole reason the table exists. test_verdict_coverage DOES NOT FLAG THIS, and the limit is recorded rather than papered over. That gate derives boolean subset verdicts -- bool(A) and (all/any | not B). A `band` string and an `lsi` integer match no part of that shape. A derived population is bounded by the form it derives, and the form there was chosen from five hand-found instances that all happened to be booleans. AND THE MUTATION THAT SURVIVED WAS THE ONE THAT MATTERED. Eleven of twelve redded immediately; the twelfth -- folding rumored units into the certain total, the single thing the engine is most careful to keep apart -- passed, because the test asserted the engine's WORDING on the stated grounds that "300 + 450 = 750 is the raw total and is legitimately shown, so the number is not checkable". It is checkable; it just has to be read out of the row it belongs to. A number asserted against the whole card is not asserted at all -- the AUTHORITY-DARK lesson one item later, arriving from the opposite direction. Re-mutated after the repair: 14 of 14 red, including two more blending mutations added while there. The mirrored EVIDENCE table is pinned against supply_pipeline.py off disk, weight included: a drift there makes the editor offer a discount the engine will not apply. RUMORED is read from the Python too, so a tier the engine treats as rumored that this card cannot offer reds the build. The pipeline persists per project, because a competitive set is typed in once and consulted for months, and re-entering six projects to re-run a reading is the reason a screen goes unused. Verified: 24 tests, 14 mutations; tsc --noEmit clean; eslint clean; production build clean; test_claude_md_gates, test_verdict_coverage, test_roadmap_status and test_gap_records all green; full web suite 271 files / 2,958 tests. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 26 ++ apps/web/src/api/clientCallers.test.ts | 2 +- apps/web/src/api/creDeal.ts | 64 +++- apps/web/src/proforma/proforma.ts | 10 +- apps/web/src/proforma/supplyCard.test.ts | 311 +++++++++++++++++ apps/web/src/proforma/supplyCard.ts | 422 +++++++++++++++++++++++ docs/roadmap.md | 52 +++ 7 files changed, 883 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/proforma/supplyCard.test.ts create mode 100644 apps/web/src/proforma/supplyCard.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 057aed3c..8452b7f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### SUPPLY-DARK — an empty competitive set reported the most favourable market verdict there is + +`services/api/src/aec_api/supply_pipeline.py` weighs a competitive pipeline by what is **recorded** +about each project rather than by the label on the deck: + +> "Under construction" on a broker deck and "under construction" with a recorded construction deed of +> trust are not the same fact, and a rendering with no permit is neither certain supply nor zero. + +`ApiClient.competitiveSupply()` was written for it and no screen called it. Its response was also +typed `Record`, so every field was invisible to `deadFieldTyped.test.ts` and to any +other audit that starts from the client's declarations — five interfaces are now declared in +`apps/web/src/api/creDeal.ts`. + +**Measured through the engine first.** A pipeline of four projects, none matching the subject's +product type, returns `counts.competing: 0` and `excluded.counts.wrong_product: 4` beside an index +whose band is **`"undersupplied"`** and whose `lsi` is `0` — the most favourable verdict the engine +can produce, byte-identical to a market with genuinely no competition. *"Undersupplied" is an +argument to build.* + +The engine is sound: the coverage is in the response, and its own note says the excluded projects are +listed "so a thin competitive set is visible, not implied". So the new **Competitive supply** card on +the Feasibility tab withholds rather than annotates — on a vacuous set there is no band, no totals and +no discount, and the exclusions with their reasons are the only thing on the card. Certain and rumored +units are never added; `from_status_label` is rendered, because it marks the row whose tier was +inferred from a status string rather than read from a recorded flag. + ### The pre-committee gate card dropped the coverage its own gate demands `services/api/test_verdict_coverage.py` — added one commit earlier, to derive exactly this class — diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 68e7c626..91750155 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -297,7 +297,7 @@ const UNCALLED: readonly string[] = [ "assignMaterialSet", "assumptionsRegister", "attachDocument", "ciLatest", "citedQuery", "clashFederated", "clausePlaybook", "clientDecisions", "codeAdoptions", "codeCheck", "colorFacets", - "competitiveSupply", "connectElements", "costSummary", "createAssembly", "createGroup", + "connectElements", "costSummary", "createAssembly", "createGroup", "createType", "docGraph", "draftPost", "drawingSchedulesCalc", "drawingSetPlan", "drawingsSyncStatus", "ebcPathways", "editType", "elements5dMap", diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index f11ad699..66d4cd88 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -153,6 +153,60 @@ export interface DecisionGateResult { counts: Record; note: string; } +/** One project in the competitive pipeline, as `supply_pipeline.assess()` reports it back. + * `weight` and `weighted_units` are the discount applied; `from_status_label` says the evidence was + * INFERRED from a status string rather than read from a recorded flag, which is the difference + * between "a loan is recorded" and "the deck says under construction". */ +export interface SupplyProjectRow { + name: string | null; units: number; delivery_date: string | null; + product_type: string | null; distance_mi: number | null; + evidence: string; evidence_label: string; weight: number; + from_status_label: boolean; weighted_units: number; rumored: boolean; + /** Present only on an EXCLUDED row, and it is the reason. */ + excluded?: string; +} + +/** The evidence-weighted competitive set. `certain_supply_units` and `rumored_supply_units` are + * deliberately separate totals and must not be added together on screen — the engine's note says + * rumored supply "is never blended into the certain count". */ +export interface SupplyAssessment { + window: { start: string | null; end: string | null }; + product_type: string | null; + competing: SupplyProjectRow[]; + certain_supply_units: number; + rumored_supply_units: number; + raw_units: number; + weighted_units: number; + discount_pct: number; + by_evidence: { evidence: string; label: string; projects: number; + units: number; weighted: number }[]; + /** THE COVERAGE. Every total above is computed over `competing` alone, so these counts are what + * say whether the totals are an answer or an absence — see `supplyCard.ts`. */ + excluded: { out_of_window: SupplyProjectRow[]; wrong_product: SupplyProjectRow[]; + counts: { out_of_window: number; wrong_product: number } }; + counts: { competing: number; certain: number; rumored: number }; + note: string; +} + +/** `absorption.lot_supply_index()`. `months_of_supply` and `lsi` are `null` and `band` is + * `"unknown"` when absorption is not positive; `equilibrium_months` is absent on that branch. */ +export interface LotSupplyIndex { + vdl: number; monthly_absorption: number; equilibrium_months?: number; + months_of_supply: number | null; lsi: number | null; + band: "oversupplied" | "balanced" | "undersupplied" | "unknown"; + note: string; +} + +/** What the route returns when `monthly_absorption` is supplied. Both readings are returned because + * "the gap between them IS the argument". */ +export interface SupplyIndexResult { + supply: SupplyAssessment; + weighted_index: LotSupplyIndex; + raw_index: LotSupplyIndex; + delta_months: number | null; + note: string; +} + export function withCreDeal>(Base: TBase) { return class CreDeal extends Base { /** CRE-HOLDSELL — hold vs sell: incremental hold-year IRRs against the proceeds declined today. */ @@ -242,11 +296,17 @@ export function withCreDeal>(Base: TBase) { `/projects/${pid}/deal-room/authority`, { method: "PUT", body: JSON.stringify({ entries }) }); } - /** CRE-SUPPLY — competitive supply weighted by recorded evidence, not by status label. */ + /** CRE-SUPPLY — competitive supply weighted by recorded evidence, not by status label. + * + * DECLARED 2026-09-25. This returned `Record`, so every field below was + * invisible to `deadFieldTyped.test.ts` and to any other audit that starts from the client's + * declarations — the same hole `excluded_comparables` fell through in SCREEN-VS-REPORT. The + * overload is real and is kept: `monthly_absorption` switches the response from a bare + * assessment to the index wrapper, which is why the return type is a union. */ competitiveSupply(pid: string, body: { projects: unknown[]; window_start?: string; window_end?: string; product_type?: string; monthly_absorption?: number }) { - return this.json>( + return this.json( `/projects/${pid}/supply/competitive`, { method: "POST", body: JSON.stringify(body) }); } /** CRE-DECISION-GATE — the pre-committee gate; a gate without evidence is unknown, and blocks. */ diff --git a/apps/web/src/proforma/proforma.ts b/apps/web/src/proforma/proforma.ts index a13d73f3..9a34a684 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -12,6 +12,7 @@ import { renderAuthorityCard } from "./authorityCard"; import { renderCovenantCard } from "./covenantCard"; import { renderDecisionGateCard } from "./decisionGateCard"; import { renderRentRollQuality } from "./rentRollQuality"; +import { renderSupplyCard } from "./supplyCard"; import { renderT12Card } from "./t12Card"; import { renderTestFitTab } from "./testfitTab"; import { downloadPostedPdf, camStatementPath } from "../api/downloadPdf"; @@ -154,7 +155,14 @@ export class ProformaUI { const self = this as unknown as { root: HTMLElement }; const into = (el: HTMLElement, fn: () => void) => { const r = self.root; self.root = el; try { fn(); } finally { self.root = r; } }; this.overviewEl = sections.over; this.renderOverview(); - if (sections.feas) into(sections.feas, () => { this.renderMassing(); this.renderTestFit(); this.renderResidualLand(); this.renderProperty(); }); + if (sections.feas) into(sections.feas, () => { + this.renderMassing(); this.renderTestFit(); this.renderResidualLand(); this.renderProperty(); + // Beside the residual: what you can pay for the dirt is an argument about absorption, and + // absorption is an argument about who else is delivering into the same window. + renderSupplyCard(this.root, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + }); + }); if (sections.cap) into(sections.cap, () => { this.renderBudget(); this.renderSourcesUses(); this.renderSpecialty(); // Beside the facility it is about: renderSourcesUses prints the loan amount and its fees. diff --git a/apps/web/src/proforma/supplyCard.test.ts b/apps/web/src/proforma/supplyCard.test.ts new file mode 100644 index 00000000..c5303091 --- /dev/null +++ b/apps/web/src/proforma/supplyCard.test.ts @@ -0,0 +1,311 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import type { SupplyAssessment, SupplyIndexResult } from "../api/creDeal"; +import { EVIDENCE, bodyFor, excludedCount, isIndexResult, isVacuous, + renderSupply } from "./supplyCard"; + +/** + * CRE-SUPPLY, and the one thing this card exists to refuse. + * + * Measured through the real engine: a pipeline of four projects, none matching the subject's + * product type, returns `counts.competing: 0`, `excluded.counts.wrong_product: 4`, and an index + * whose band is **`"undersupplied"`** — the most favourable verdict it can produce, on no evidence, + * and byte-identical to a market with genuinely no competition. + * + * So the load-bearing assertions here are the WITHHOLDINGS, and each is mutation-checked: printing + * the band on a vacuous set, summing certain with rumored, or showing a 0.0% discount over a zero + * raw are each a sentence the engine did not say. + */ + +const P = (over: Partial = {}) => ({ + name: "Alder Flats", units: 300, delivery_date: "2027-03-01", product_type: "multifamily", + distance_mi: 1.2, evidence: "loan_recorded", evidence_label: "Construction loan recorded", + weight: 1.0, from_status_label: false, weighted_units: 300, rumored: false, ...over, +}); + +const ASSESS = (over: Partial = {}): SupplyAssessment => ({ + window: { start: "2026-01-01", end: "2028-12-31" }, product_type: "multifamily", + competing: [P(), P({ name: "Rumor Tower", units: 450, evidence: "announced", + evidence_label: "Announced / rendering only", weight: 0.05, + weighted_units: 22.5, rumored: true })], + certain_supply_units: 300, rumored_supply_units: 450, + raw_units: 750, weighted_units: 322.5, discount_pct: 57.0, + by_evidence: [{ evidence: "loan_recorded", label: "Construction loan recorded", projects: 1, + units: 300, weighted: 300 }], + excluded: { out_of_window: [], wrong_product: [], counts: { out_of_window: 0, wrong_product: 0 } }, + counts: { competing: 2, certain: 1, rumored: 1 }, + note: "Units are discounted by RECORDED evidence, not by status label.", + ...over, +} as SupplyAssessment); + +/** The measured case: everything supplied was filtered out. */ +const VACUOUS = (): SupplyAssessment => ASSESS({ + competing: [], certain_supply_units: 0, rumored_supply_units: 0, + raw_units: 0, weighted_units: 0, discount_pct: 0.0, by_evidence: [], + counts: { competing: 0, certain: 0, rumored: 0 }, + excluded: { + out_of_window: [], counts: { out_of_window: 0, wrong_product: 4 }, + wrong_product: [P({ name: "Retail Row", product_type: "retail", + excluded: "different product type" })], + }, +}); + +const INDEX = (s: SupplyAssessment, over: Partial = {}): SupplyIndexResult => ({ + supply: s, + weighted_index: { vdl: s.weighted_units, monthly_absorption: 25, equilibrium_months: 6, + months_of_supply: 12.9, lsi: 215, band: "oversupplied", note: "LSI note." }, + raw_index: { vdl: s.raw_units, monthly_absorption: 25, equilibrium_months: 6, + months_of_supply: 30, lsi: 500, band: "oversupplied", note: "LSI note." }, + delta_months: 17.1, note: "The weighted index is the one to underwrite.", + ...over, +} as SupplyIndexResult); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; + +const text = (s: SupplyAssessment, i?: SupplyIndexResult) => + renderSupply(mount(), s, i).textContent ?? ""; + +describe("the mirrored evidence table", () => { + const PY = resolve(__dirname, "../../../../services/api/src/aec_api/supply_pipeline.py"); + + /** `EVIDENCE` as the Python declares it, in rank order. */ + function pythonEvidence(): [string, string, number][] { + const src = readFileSync(PY, "utf-8"); + const block = /^EVIDENCE: dict\[str, dict\[str, Any\]\] = \{([\s\S]*?)^\}/m.exec(src); + expect(block, "EVIDENCE not found in supply_pipeline.py — the mirror below cannot be checked, " + + "which is the state this test exists to prevent").toBeTruthy(); + const out: [string, string, number][] = []; + for (const m of block![1]!.matchAll( + /"(\w+)":\s*\{"label":\s*"([^"]+)",\s*"weight":\s*([\d.]+),\s*"rank":\s*(\d+)\}/g)) { + out.push([m[1]!, m[2]!, Number(m[3])]); + } + return out; + } + + it("parsed the Python at all — a short list makes every assertion below vacuous", () => { + expect(pythonEvidence().length).toBeGreaterThanOrEqual(8); + }); + + it("matches key, label and WEIGHT, in rank order", () => { + // The weight is load-bearing: a drift makes this card offer a discount the engine will not + // apply, so the select would promise 85% and the total would come back at 50%. + expect(EVIDENCE.map((e) => [...e])).toEqual(pythonEvidence().map((e) => [...e])); + }); + + it("agrees with the engine about which tiers are rumored", () => { + const src = readFileSync(PY, "utf-8"); + const m = /^RUMORED = \(([^)]*)\)/m.exec(src); + expect(m, "RUMORED not found").toBeTruthy(); + const rumored = [...m![1]!.matchAll(/"(\w+)"/g)].map((x) => x[1]); + // Every rumored tier must be one this card can offer, or a user cannot express it. + for (const r of rumored) expect(EVIDENCE.map(([k]) => k)).toContain(r); + }); +}); + +describe("a vacuous set is not a favourable one", () => { + it("withholds every supply total and says why", () => { + const t = text(VACUOUS()); + expect(t).toContain("No competitive set"); + expect(t).toContain("This is not a finding of no competition."); + // MUTATION: rendering the totals table anyway. Each of these would read as a measurement. + expect(t).not.toContain("Certain"); + expect(t).not.toContain("Evidence-weighted"); + }); + + it("withholds the BAND, which is the verdict that inverts", () => { + const s = VACUOUS(); + const el = renderSupply(mount(), s, INDEX(s, { + weighted_index: { vdl: 0, monthly_absorption: 25, equilibrium_months: 6, + months_of_supply: 0, lsi: 0, band: "undersupplied", note: "LSI note." }, + raw_index: { vdl: 0, monthly_absorption: 25, equilibrium_months: 6, + months_of_supply: 0, lsi: 0, band: "undersupplied", note: "LSI note." }, + delta_months: 0, + })); + // Scoped to the TABLES, not to the card's text. The explanation below names "undersupplied" as + // the word it is refusing to print — asserting over the whole card would have been broken by + // the very sentence that makes the withholding legible, which is the authority-card lesson + // ("an assertion satisfied by a different code path") arriving from the other direction. + const tables = [...el.querySelectorAll("table")].map((x) => x.textContent ?? "").join(" "); + expect(tables).not.toContain("undersupplied"); + expect(tables).not.toContain("Months of supply"); + expect(el.textContent).toContain("the most favourable band it can return, on no evidence"); + }); + + it("still lists what was excluded, with the reason", () => { + const t = text(VACUOUS()); + expect(t).toContain("Retail Row"); + expect(t).toContain("different product type"); + expect(t).toContain("4 project(s) supplied were filtered out"); + }); + + it("distinguishes 'nothing competed' from 'nothing was supplied'", () => { + // Two different findings. The first says the filters ate the pipeline; the second says there + // was no pipeline. Collapsing them sends somebody to widen a window that is already right. + const empty = ASSESS({ + competing: [], certain_supply_units: 0, rumored_supply_units: 0, raw_units: 0, + weighted_units: 0, by_evidence: [], counts: { competing: 0, certain: 0, rumored: 0 }, + excluded: { out_of_window: [], wrong_product: [], + counts: { out_of_window: 0, wrong_product: 0 } }, + }); + expect(text(empty)).toContain("No projects were supplied"); + expect(text(VACUOUS())).not.toContain("No projects were supplied"); + }); +}); + +describe("coverage leads on a real set too", () => { + it("says what the totals cover when anything was excluded", () => { + const s = ASSESS({ + excluded: { out_of_window: [P({ name: "Old Mill", excluded: "delivers before the window" })], + wrong_product: [P({ name: "Retail Row", excluded: "different product type" })], + counts: { out_of_window: 1, wrong_product: 1 } }, + }); + const t = text(s); + expect(t).toContain("2 competing project(s)"); + expect(t).toContain("2 excluded"); + expect(t).toContain("1 outside the delivery window"); + expect(t).toContain("1 a different product type"); + expect(t).toContain("not the 4 supplied"); + }); + + it("says so plainly when nothing was excluded", () => { + expect(text(ASSESS())).toContain("Nothing was excluded"); + }); +}); + +describe("certain and rumored are never added", () => { + it("reports two totals and labels the rumored one as excluded from the other", () => { + // ASSERTED ON THE CELL, not on the card. The first draft of this test checked the engine's + // WORDING, on the stated grounds that "300 + 450 = 750 is the raw total and is legitimately + // shown, so the number is not checkable". The mutation proved that reasoning wrong: adding the + // rumored units into the certain row printed 750 where 300 belongs — blending the two counts + // the engine keeps apart on purpose — and every assertion still passed. The number IS + // checkable; it just has to be read out of the row it belongs to. + const el = renderSupply(mount(), ASSESS()); + const cells = (want: string) => [...el.querySelectorAll("tr")] + .filter((tr) => tr.querySelector("td")?.textContent === want) + .map((tr) => [...tr.querySelectorAll("td")].map((td) => td.textContent)); + expect(cells("Certain")[0]?.[1]).toBe("300"); + expect(cells("Rumored")[0]?.[1]).toBe("450"); + expect(cells("Evidence-weighted")[0]?.[1]).toBe("323"); + expect(cells("Rumored")[0]?.[2]).toContain("added to certain"); + }); + + it("names the rumored rows as rumored in the project table", () => { + const el = renderSupply(mount(), ASSESS()); + const rows = [...el.querySelectorAll("tr")].map((tr) => tr.textContent ?? ""); + expect(rows.some((r) => r.includes("Rumor Tower") && r.includes("rumored"))).toBe(true); + expect(rows.some((r) => r.includes("Alder Flats") && r.includes("rumored"))).toBe(false); + }); +}); + +describe("the discount", () => { + it("is shown when there is something to discount", () => { + expect(text(ASSESS())).toContain("57.0% discount"); + }); + + it("is withheld when raw units are zero — 0.0% there means 'nothing weighed'", () => { + // `discount_pct` is `round(...) if raw else 0.0`, so a zero raw yields the same 0.0 as a + // pipeline that genuinely needed no discount. + const s = ASSESS({ competing: [P({ units: 0, weighted_units: 0 })], raw_units: 0, + weighted_units: 0, discount_pct: 0.0, certain_supply_units: 0, + rumored_supply_units: 0, counts: { competing: 1, certain: 1, rumored: 0 } }); + // `% discount`, not `discount` — the engine's own note ends "...are discounted by RECORDED + // evidence", so the bare word is in every response by construction. + expect(text(s)).not.toContain("% discount"); + expect(text(ASSESS())).toContain("% discount"); // the check is not vacuous + }); +}); + +describe("evidence inferred from a label is marked", () => { + it("names the projects whose tier came from a status string", () => { + const t = text(ASSESS({ + competing: [P({ name: "Deck Tower", from_status_label: true })], + counts: { competing: 1, certain: 1, rumored: 0 }, + })); + expect(t).toContain("tiered from a STATUS LABEL"); + expect(t).toContain("Deck Tower"); + }); + + it("says nothing when every tier came from a recorded flag", () => { + expect(text(ASSESS())).not.toContain("STATUS LABEL"); + }); +}); + +describe("the index", () => { + it("shows weighted and raw side by side with the gap between them", () => { + const s = ASSESS(); + const t = text(s, INDEX(s)); + expect(t).toContain("Evidence-weighted — underwrite this"); + expect(t).toContain("Raw, undiscounted"); + expect(t).toContain("12.9 mo · LSI 215"); + expect(t).toContain("30.0 mo · LSI 500"); + expect(t).toContain("17.1 month(s)"); + }); + + it("renders the engine's reason instead of a band when absorption was not positive", () => { + const s = ASSESS(); + const t = text(s, INDEX(s, { + weighted_index: { vdl: 322.5, monthly_absorption: 0, months_of_supply: null, lsi: null, + band: "unknown", note: "Need a positive absorption rate." }, + })); + expect(t).toContain("Need a positive absorption rate."); + expect(t).not.toContain("unknown"); + }); +}); + +describe("what the card sends", () => { + it("expands the evidence tier into the boolean flag the engine prefers", () => { + // `evidence_of` checks explicit flags BEFORE falling back to a status string, so sending the + // flag is what makes `from_status_label` false. + expect(bodyFor([{ name: "A", units: 10, delivery_date: "2027-01-01", + product_type: "multifamily", evidence: "permit_issued" }], {})) + .toEqual({ projects: [{ name: "A", units: 10, delivery_date: "2027-01-01", + product_type: "multifamily", permit_issued: true }] }); + }); + + it("sends no flag for the unknown tier, which has none", () => { + const b = bodyFor([{ name: "A", units: 10, delivery_date: "", product_type: "", + evidence: "unknown" }], {}); + expect(Object.keys(b.projects[0] as object)).toEqual( + ["name", "units", "delivery_date", "product_type"]); + }); + + it("omits an empty window, product type and a non-positive absorption", () => { + const b = bodyFor([], { windowStart: "", productType: " ".trim(), absorption: 0 }); + expect(b).toEqual({ projects: [] }); + }); + + it("sends absorption only when it is positive, because that is what adds the index", () => { + expect(bodyFor([], { absorption: 25 }).monthly_absorption).toBe(25); + }); +}); + +describe("telling the two response shapes apart", () => { + it("recognises the index wrapper by the field only it carries", () => { + expect(isIndexResult(INDEX(ASSESS()))).toBe(true); + expect(isIndexResult(ASSESS())).toBe(false); + }); +}); + +describe("the two predicates the card withholds on", () => { + it("isVacuous is about the COMPETING count, not about the totals", () => { + // A pipeline of real projects that all have zero units has zero totals and is not vacuous — + // it was measured and the answer was zero. Reading the totals would conflate the two. + expect(isVacuous(VACUOUS())).toBe(true); + expect(isVacuous(ASSESS({ raw_units: 0, weighted_units: 0 }))).toBe(false); + }); + + it("excludedCount adds both filters", () => { + expect(excludedCount(ASSESS({ + excluded: { out_of_window: [], wrong_product: [], + counts: { out_of_window: 2, wrong_product: 3 } }, + }))).toBe(5); + }); +}); diff --git a/apps/web/src/proforma/supplyCard.ts b/apps/web/src/proforma/supplyCard.ts new file mode 100644 index 00000000..eb709904 --- /dev/null +++ b/apps/web/src/proforma/supplyCard.ts @@ -0,0 +1,422 @@ +/** Competitive supply — who else is delivering into this project's lease-up window, weighted by + * what is actually *recorded* about them. + * + * `services/api/src/aec_api/supply_pipeline.py` states the distinction it exists for: + * + * > "Under construction" on a broker deck and "under construction" with a recorded construction + * > deed of trust are not the same fact, and a rendering with no permit is neither certain supply + * > nor zero. + * + * `ApiClient.competitiveSupply()` was written for it and **no screen called it**, so the whole + * engine — two filters, an eight-tier evidence weight, and the raw-versus-weighted supply index — + * was unreachable. Its response was additionally typed `Record`, so every field + * was invisible to `apps/web/src/api/deadFieldTyped.test.ts` and to any other audit that starts + * from the client's own declarations. Both are fixed here; the shapes now live in + * `apps/web/src/api/creDeal.ts`. + * + * MEASURED BEFORE WRITING A LINE OF THIS CARD, and it is the sharpest instance of the shape this + * session kept finding. A pipeline of four projects, none of them matching the subject's product + * type, returns: + * + * | | | + * |---|---| + * | `counts.competing` | **0** | + * | `excluded.counts.wrong_product` | **4** | + * | `weighted_index.band` | **`"undersupplied"`** | + * | `lsi` | **0** | + * | `discount_pct` | **0.0** | + * + * **A market in which every supplied project was filtered out reports the single most favourable + * verdict the engine can produce**, and reports it identically to a market that genuinely has no + * competition. "Undersupplied" is an argument to build. That is worse than the four `clean: true` + * cases this session fixed, because those at least read as neutral. + * + * **The engine is not wrong — nothing in it claims coverage.** `counts.competing` and + * `excluded.counts` are right there in the response, and its own note says the excluded projects + * are listed *"so a thin competitive set is visible, not implied"*. The defect would have been + * entirely this card's, which is why it is built the way it is: + * + * * **The band is WITHHELD when nothing competed.** No competing project means no reading, not a + * favourable one. The card says what was excluded and why, and offers no supply verdict at all. + * * **`discount_pct` is withheld when `raw_units` is 0**, where the engine's `if raw else 0.0` + * makes "nothing to discount" indistinguishable from "no discount applied". + * * **Certain and rumored totals are never added.** The engine keeps them apart on purpose; a + * single "total competing units" would undo the one thing it is most careful about. + * * **`from_status_label` is rendered**, because it marks the row where the evidence tier was + * inferred from a status string rather than read from a recorded flag — the exact difference + * between a fact and a broker's assertion, and the reason the whole table exists. + */ +import type { ApiClient } from "../api/client"; +import type { LotSupplyIndex, SupplyAssessment, SupplyIndexResult } from "../api/creDeal"; +import { escapeHtml as esc } from "../ui/feedback"; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +const n0 = (v: number) => Math.round(v).toLocaleString("en-US"); + +/** `supply_pipeline.EVIDENCE`, in the engine's rank order. Mirrored so the editor can OFFER a tier + * that no project in the pipeline currently carries — the response only ever names the tiers that + * are present, exactly as `authorityCard.ts` has to mirror `FACT_TYPES`. Pinned against the Python + * by `supplyCard.test.ts`; the WEIGHT is the load-bearing column, because a drift there makes this + * card offer a discount the engine will not apply. */ +export const EVIDENCE: readonly [string, string, number][] = [ + ["loan_recorded", "Construction loan recorded", 1.0], + ["under_construction", "GC mobilized / vertical construction", 1.0], + ["permit_issued", "Building permit issued", 0.85], + ["permit_applied", "Permit application filed", 0.5], + ["entitled", "Entitled / approved", 0.35], + ["planning_filed", "Planning application filed", 0.2], + ["announced", "Announced / rendering only", 0.05], + ["unknown", "No recorded evidence", 0.05], +]; + +/** Is the assessment an answer, or an absence? True when the engine evaluated nothing — which the + * totals cannot express, because every one of them is 0 either way. */ +export function isVacuous(s: SupplyAssessment): boolean { + return s.counts.competing === 0; +} + +/** How many projects the two filters removed. */ +export function excludedCount(s: SupplyAssessment): number { + return s.excluded.counts.out_of_window + s.excluded.counts.wrong_product; +} + +/** The coverage line — always rendered, and it LEADS, because every total below it is computed over + * the competing set alone. */ +function coverage(s: SupplyAssessment): string { + const ex = excludedCount(s); + const parts: string[] = []; + if (s.excluded.counts.out_of_window) { + parts.push(`${s.excluded.counts.out_of_window} outside the delivery window`); + } + if (s.excluded.counts.wrong_product) { + parts.push(`${s.excluded.counts.wrong_product} a different product type`); + } + if (isVacuous(s)) { + return `
No competitive set
` + + meta(ex + ? `All ${ex} project(s) supplied were filtered out — ${esc(parts.join(", "))}. ` + + `This is not a finding of no competition. Every total below would read ` + + `zero either way, so no supply reading is shown.` + : `No projects were supplied, so there is nothing to weigh.`, "var(--status-warn)"); + } + return `
` + + `${s.counts.competing} competing project(s)
` + + meta(ex + ? `${ex} excluded — ${esc(parts.join(", "))}. Every total below covers the ` + + `${s.counts.competing} that compete, not the ${s.counts.competing + ex} supplied.` + : `Nothing was excluded: every project supplied competes.`); +} + +/** One supply index, or the reason there is not one. */ +function indexRow(label: string, ix: LotSupplyIndex): string { + if (ix.months_of_supply == null || ix.lsi == null) { + return `${esc(label)}${esc(ix.note)}`; + } + const colour = ix.band === "oversupplied" ? "var(--status-crit)" + : ix.band === "undersupplied" ? "var(--status-good)" : ""; + return `${esc(label)}` + + `${ix.months_of_supply.toFixed(1)} mo · LSI ${ix.lsi}` + + `${esc(ix.band)}`; +} + +/** Render an assessment. `index` is present only when an absorption rate was supplied. */ +export function renderSupply(host: HTMLElement, s: SupplyAssessment, + index?: SupplyIndexResult): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + el.insertAdjacentHTML("beforeend", coverage(s)); + + if (!isVacuous(s)) { + // CERTAIN AND RUMORED ARE NEVER ADDED. The engine reports them as two totals on purpose and + // says so in its note; one combined figure is precisely the number it refuses to produce. + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `
Supplyunitsbasis
Certain${n0(s.certain_supply_units)}${s.counts.certain} project(s) with recorded evidence
Rumored${n0(s.rumored_supply_units)}${s.counts.rumored} announced or unevidenced — not added to certain
Evidence-weighted${n0(s.weighted_units)}of ${n0(s.raw_units)} raw` + + (s.raw_units > 0 ? ` — a ${s.discount_pct.toFixed(1)}% discount` : ``) + + `
`); + + if (s.by_evidence.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + s.by_evidence.map((b) => `` + + `` + + ``).join("") + + `
Evidenceprojectsunitsweighted
${esc(b.label)}${b.projects}${n0(b.units)}${n0(b.weighted)}
`); + } + + const inferred = s.competing.filter((r) => r.from_status_label); + if (inferred.length) { + el.insertAdjacentHTML("beforeend", meta( + `${inferred.length} project(s) were tiered from a STATUS LABEL rather than a recorded flag: ` + + `${esc(inferred.map((r) => r.name ?? "unnamed").join(", "))}. A label is an assertion; ` + + `the weights above are only as good as it is.`, "var(--status-warn)")); + } + + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + s.competing.map((r) => `` + + `` + + ``).join("") + + `
Projectunitsdeliversevidenceweighted
${esc(r.name ?? "—")}` + + (r.rumored ? ` rumored` : ``) + + (r.from_status_label ? ` from label` : ``) + + `${n0(r.units)}${esc(r.delivery_date ?? "—")}${esc(r.evidence_label)}${n0(r.weighted_units)}
`); + } + + // THE EXCLUSIONS ARE RENDERED WITH THEIR REASONS whether or not anything competed — when nothing + // did, they are the only thing on the card that carries information. + const excluded = [...s.excluded.out_of_window, ...s.excluded.wrong_product]; + if (excluded.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + excluded.map((r) => `` + + `` + + ``).join("") + + `
Excludedunitsdeliverswhy
${esc(r.name ?? "—")}${n0(r.units)}${esc(r.delivery_date ?? "—")}${esc(r.excluded ?? "")}
`); + } + + // THE INDEX IS WITHHELD ON A VACUOUS SET. With nothing competing the weighted VDL is 0, which the + // index reads as "undersupplied" — the most favourable verdict it can return, for want of input. + if (index && !isVacuous(s)) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + indexRow("Evidence-weighted — underwrite this", index.weighted_index) + + indexRow("Raw, undiscounted", index.raw_index) + + `
Months of supplyreadingband
` + + meta(index.delta_months != null + ? `The discount is worth ${index.delta_months.toFixed(1)} month(s) of ` + + `supply. ${esc(index.note)}` + : esc(index.note))); + } else if (index) { + el.insertAdjacentHTML("beforeend", meta( + `No months-of-supply reading: with nothing in the competitive set the weighted VDL is zero, ` + + `which the index would report as undersupplied — the most favourable band it can ` + + `return, on no evidence.`, "var(--status-warn)")); + } + + el.insertAdjacentHTML("beforeend", meta(esc(s.note))); + host.appendChild(el); + return el; +} + +/** One row of the pipeline the card sends. */ +export interface SupplyProject { + name: string; + units: number; + delivery_date: string; + product_type: string; + evidence: string; +} + +/** The request body, with the evidence tier expanded into the boolean flag the engine reads. + * `evidence_of` prefers an explicit flag over a status label, so sending the flag is what makes + * `from_status_label` false — a row typed here is a recorded fact as far as this card knows. */ +export function bodyFor(rows: SupplyProject[], opts: { + windowStart?: string; windowEnd?: string; productType?: string; absorption?: number; +}): { projects: unknown[]; window_start?: string; window_end?: string; + product_type?: string; monthly_absorption?: number } { + const projects = rows.map((r) => { + const p: Record = { + name: r.name, units: r.units, delivery_date: r.delivery_date, + product_type: r.product_type, + }; + if (r.evidence && r.evidence !== "unknown") p[r.evidence] = true; + return p; + }); + const body: ReturnType = { projects }; + if (opts.windowStart) body.window_start = opts.windowStart; + if (opts.windowEnd) body.window_end = opts.windowEnd; + if (opts.productType) body.product_type = opts.productType; + if (opts.absorption != null && opts.absorption > 0) body.monthly_absorption = opts.absorption; + return body; +} + +/** The two response shapes the route returns, told apart by the field only the index form carries. */ +export function isIndexResult(r: SupplyAssessment | SupplyIndexResult): r is SupplyIndexResult { + return "weighted_index" in r; +} + +export interface SupplyCtx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; +} + +export function renderSupplyCard(root: HTMLElement, ctx: SupplyCtx): HTMLElement { + const host = document.createElement("div"); + host.id = "pf-supply"; + host.className = "fin-card"; + host.style.marginTop = "10px"; + host.innerHTML = `
Competitive supply
` + + `
Units are discounted by what is recorded about a project, ` + + `not by the label on the deck. Only projects delivering inside this deal's own window and ` + + `matching its product type compete; everything else is listed with the reason it was left out.` + + `
`; + + const controls = document.createElement("div"); + controls.style.cssText = "margin-top:6px;display:flex;gap:6px;flex-wrap:wrap;align-items:center"; + const mk = (type: string, placeholder: string, width: string, title: string) => { + const i = document.createElement("input"); + i.type = type; i.className = "portal-filter"; i.placeholder = placeholder; + i.style.width = width; i.title = title; + return i; + }; + const wsIn = mk("date", "window start", "150px", + "Start of this deal's delivery-and-lease-up window."); + const weIn = mk("date", "window end", "150px", + "End of the window. A tower finishing after we stabilize is not our competition."); + const ptIn = mk("text", "product type", "130px", + "This deal's product type. A project of a different type is excluded, and counted as excluded."); + const absIn = mk("number", "absorption /mo", "120px", + "Units absorbed per month. Supplying it adds the months-of-supply index, raw beside weighted."); + controls.append(wsIn, weIn, ptIn, absIn); + host.appendChild(controls); + + const editor = document.createElement("div"); editor.style.marginTop = "6px"; + host.appendChild(editor); + const actions = document.createElement("div"); actions.style.marginTop = "6px"; + const add = document.createElement("button"); + add.className = "file-btn"; add.textContent = "Add project"; + const go = document.createElement("button"); + go.className = "file-btn"; go.style.marginLeft = "6px"; go.textContent = "Weigh the pipeline"; + actions.append(add, go); + host.appendChild(actions); + const out = document.createElement("div"); host.appendChild(out); + root.appendChild(host); + + const rows: SupplyProject[] = []; + + // Persisted per project: a competitive pipeline is typed in once and consulted for months, and + // re-entering six projects to re-run a reading is the reason a screen goes unused. + const load = () => { + const pid = ctx.projectId(); + if (!pid) return; + try { + const raw = localStorage.getItem(`supply-pipeline:${pid}`); + if (!raw) return; + const saved = JSON.parse(raw) as { rows?: SupplyProject[]; ws?: string; we?: string; + pt?: string; abs?: string }; + if (Array.isArray(saved.rows)) rows.push(...saved.rows); + wsIn.value = saved.ws ?? ""; weIn.value = saved.we ?? ""; + ptIn.value = saved.pt ?? ""; absIn.value = saved.abs ?? ""; + } catch { /* a corrupt or unavailable store is an empty form, never an error */ } + }; + const save = () => { + const pid = ctx.projectId(); + if (!pid) return; + try { + localStorage.setItem(`supply-pipeline:${pid}`, JSON.stringify({ + rows, ws: wsIn.value, we: weIn.value, pt: ptIn.value, abs: absIn.value, + })); + } catch { /* private mode, blocked storage — the card still works, it just forgets */ } + }; + for (const c of [wsIn, weIn, ptIn, absIn]) c.onchange = save; + + const paint = () => { + editor.replaceChildren(); + if (!rows.length) { + editor.insertAdjacentHTML("beforeend", meta( + `No projects yet. Add the ones you know about — including the ones you only half believe; ` + + `the evidence tier is what decides how much of each counts.`)); + return; + } + const table = document.createElement("table"); table.className = "fin-table"; + table.innerHTML = `Projectunits` + + `deliversproductrecorded evidence`; + rows.forEach((r, i) => { + const tr = document.createElement("tr"); + const cell = (child: HTMLElement, num = false) => { + const td = document.createElement("td"); + if (num) td.className = "num"; + td.appendChild(child); return td; + }; + const name = mk("text", "name", "100%", ""); + name.value = r.name; + const units = mk("number", "units", "80px", ""); + units.value = r.units ? String(r.units) : ""; + const when = mk("date", "", "150px", ""); + when.value = r.delivery_date; + const product = mk("text", "product", "110px", ""); + product.value = r.product_type; + const ev = document.createElement("select"); + ev.className = "portal-filter"; + ev.title = "The strongest thing RECORDED about this project. A recorded loan is a fact; " + + "a press release is not."; + for (const [key, lab, weight] of EVIDENCE) { + const o = document.createElement("option"); + o.value = key; o.textContent = `${lab} — ${Math.round(weight * 100)}%`; + ev.appendChild(o); + } + ev.value = r.evidence; + const del = document.createElement("button"); + del.className = "tool-btn"; del.textContent = "✕"; del.title = "Remove this project"; + del.onclick = () => { rows.splice(i, 1); save(); paint(); }; + + const sync = () => { + rows[i] = { + name: name.value.trim(), units: Number(units.value) || 0, + delivery_date: when.value, product_type: product.value.trim(), evidence: ev.value, + }; + save(); + }; + for (const input of [name, units, when, product]) input.onchange = sync; + ev.onchange = sync; + + tr.append(cell(name), cell(units, true), cell(when), cell(product), cell(ev), cell(del)); + table.appendChild(tr); + }); + editor.appendChild(table); + }; + + add.onclick = () => { + rows.push({ name: "", units: 0, delivery_date: "", product_type: ptIn.value.trim(), + evidence: "permit_issued" }); + save(); paint(); + }; + + go.onclick = async () => { + const pid = ctx.projectId(); + if (!pid) { out.innerHTML = meta("Open a project first."); return; } + go.disabled = true; + out.innerHTML = meta("weighing the pipeline…"); + ctx.setStatus("weighing competitive supply…"); + try { + const body = bodyFor(rows, { + windowStart: wsIn.value, windowEnd: weIn.value, + productType: ptIn.value.trim(), absorption: Number(absIn.value) || undefined, + }); + const r = await ctx.api.competitiveSupply(pid, body); + out.replaceChildren(); + const assessment = isIndexResult(r) ? r.supply : r; + renderSupply(out, assessment, isIndexResult(r) ? r : undefined); + ctx.setStatus(isVacuous(assessment) + ? `no competitive set — ${excludedCount(assessment)} excluded` + : `${assessment.counts.competing} competing · ${n0(assessment.weighted_units)} weighted units`); + } catch (e) { + out.innerHTML = meta(esc((e as Error).message), "var(--status-crit)"); + ctx.setStatus("competitive supply failed"); + } finally { + go.disabled = false; + } + }; + + load(); + paint(); + return host; +} diff --git a/docs/roadmap.md b/docs/roadmap.md index e4369b40..6ccead8d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1322,6 +1322,58 @@ instances: `apps/web/src/api/deadFieldTyped.test.ts`; both panel fixes were mutation-checked by deleting them and watching all ten checks red.* +- ✅ ⭐ **SUPPLY-DARK — an empty competitive set reported the most favourable market verdict the + engine can produce** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by + `apps/web/src/proforma/supplyCard.test.ts`, whose tripwire reads + `services/api/src/aec_api/supply_pipeline.py` off disk)* + + `supply_pipeline.py` weighs a competitive pipeline by what is **recorded** about each project + rather than by the label on the deck, and states the distinction it exists for: *"Under + construction" on a broker deck and "under construction" with a recorded construction deed of trust + are not the same fact, and a rendering with no permit is neither certain supply nor zero.* + `ApiClient.competitiveSupply()` was written for it and **no screen called it**. + + **The response was additionally typed `Record`, which is a second finding.** Every + field was therefore invisible to `apps/web/src/api/deadFieldTyped.test.ts` and to every other audit + that starts from the client's declarations — the same hole `excluded_comparables` fell through in + SCREEN-VS-REPORT. Five interfaces are now declared in `apps/web/src/api/creDeal.ts`. + + **MEASURED THROUGH THE ENGINE BEFORE A LINE OF THE CARD WAS WRITTEN, and this is the sharpest + instance of the shape this session kept finding.** A pipeline of four projects, none matching the + subject's product type: + + | | | + |---|---| + | `counts.competing` | **0** | + | `excluded.counts.wrong_product` | **4** | + | `weighted_index.band` | **`"undersupplied"`** | + | `lsi` | **0** | + | `discount_pct` | **0.0** | + + A market in which every supplied project was filtered out reports **the most favourable verdict the + engine can produce**, byte-identical to a market with genuinely no competition. *"Undersupplied" is + an argument to build* — which is worse than the four `clean: true` cases this session fixed, because + those at least read as neutral. **The engine is not wrong and nothing in it claims coverage**: + `counts.competing` and `excluded.counts` are in the response, and its own note says the excluded + projects are listed *"so a thin competitive set is visible, not implied"*. The defect would have + been entirely the consumer's, which is why the card withholds rather than annotates — the band, the + totals and the discount are all absent on a vacuous set, and the exclusions with their reasons are + the only thing left on the card. + + **`test_verdict_coverage.py` does not flag this, and the limit is worth recording rather than + papering over.** That gate derives *boolean subset verdicts* — `bool(A) and (all(…) | not B)`. A + `band` string and an `lsi` integer match no part of that shape, so the sweep it automates would not + have found this one. *A derived population is bounded by the form it derives, and the form here was + chosen from five hand-found instances that happened to all be booleans.* + + **And the mutation that survived was the one that mattered.** Eleven of twelve mutations redded + immediately; the twelfth — folding the rumored units into the certain total, which is the single + thing the engine is most careful to keep apart — passed, because the test asserted the engine's + *wording* on the stated grounds that "300 + 450 = 750 is the raw total and is legitimately shown, so + the number is not checkable". It is checkable; it just has to be read out of the row it belongs to. + *A number asserted against the whole card is not asserted at all* — the AUTHORITY-DARK lesson, one + item later, in the opposite direction. + - ✅ ⭐ **DECISION-GATE-DARK — the keystone over everything else on the tab, and nothing called it** *(S — `apps/web/src/proforma/`; **CLOSED 2026-09-25**; gated by `apps/web/src/proforma/decisionGateCard.test.ts`)* From b2cc2954580adfb556732f99e4e7a33ecb2e5e80 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 06:48:25 +0000 Subject: [PATCH 11/21] SCAN-TRUNC: a model cut short turned a correct building into 500 as-built findings scan_deviation.model_surface_points caps the reference at 200,000 surface vertices, and reaches that cap by BREAKING out of the element iterator. So hitting it does not thin the reference evenly -- it drops whole elements, in whatever order ifcopenshell happened to yield them. Every scan point over a dropped element is then measured against the nearest surface that remains, which can be metres away. Nothing in the response said the cap had been hit. MEASURED ON A TWO-WING MODEL BUILT EXACTLY TO DESIGN: full reference within_pct 100.0 - 0 out of tolerance - max 0.004 m reference truncated to wing A within_pct 50.0 - 500 out of tolerance - max 50.0 m That is not a loss of precision, it is a FABRICATED DEFECT -- and on a QA/QC as-built check it sends a crew to re-survey a wing that is fine. The cap exists FOR large models, so this is the normal case on a real project, not an edge. IT IS THE OPPOSITE SIGN FROM CLASH-TRUNC, where truncation made a partial matrix read CLEAN. The direction a silent bound pushes the answer is a property of the bound, not of truncation, so neither case predicts the other and both had to be measured. Having found one is not having found the class. THE TWO CAPS ARE TREATED DIFFERENTLY, AND THE ASYMMETRY IS THE DECISION. * A truncated SCAN (parse_point_cloud, 500,000 points) is a COVERAGE claim. Every point read was measured correctly, so the verdict is true of the part examined; it stands, with its coverage beside it, in the shape this repository uses everywhere else. The note says a prefix of a scan file is a REGION of the cloud rather than a sample of it, because scan files are written in sweep order. * A truncated REFERENCE is a CORRECTNESS claim. There is no population the figure is true of, so within_pct, out_of_tolerance and the histogram are WITHHELD rather than qualified. A caveat is for a number that means something. The refusal names the cap and points at /scan/verify-lod500, which queries per element and never truncates the model. model_surface_points_capped and parse_point_cloud_counted are new; the original two remain as array-only wrappers, so nothing that only wanted the array breaks. The counter keeps counting past the cap while it stops appending, so memory stays bounded and the response can say what share of the cloud it examined -- the old form returned an array whose length WAS the cap, making a 500,000-point file indistinguishable from a truncated one. AND THE GATE'S FIRST DRAFT VERIFIED THE REFUSAL WITHOUT VERIFYING ANYTHING COULD REACH IT. Two mutations -- the producer returning False unconditionally, and the route dropping the keyword -- both left test_scan_trunc reporting a clean tree, because it exercised analyze() in isolation and never the chain. The refusal could have been correct and dead code, and the gate would have agreed with itself. Verifying a refusal is not verifying that anything can reach it. Closed both: the producer is exercised against a fake geometry iterator (the cap arithmetic is what is under test, not ifcopenshell), and the route's analyze call is read by AST with both keywords required to be NAMES rather than constants -- a hardcoded False was the form the second mutation took. Ten mutations, all red, in BOTH directions: over-correcting reds as well as under-correcting (the scan cap also refusing; the producer claiming truncation always). One earlier mutation printed no verdict line, which the harness reports as a failed experiment rather than a pass, and it was rewritten until it ran. Verified: test_scan_trunc, test_scan_deviation, test_scan_to_lod500, test_scan_cache and test_lod500_readiness all green; ruff clean; registered in run_tests.py beside test_clash_trunc; the doc gates and the six roadmap-reading web tests green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 17 ++ docs/roadmap.md | 40 +++ services/api/run_tests.py | 2 +- services/api/src/aec_api/routers/analysis.py | 12 +- services/api/src/aec_api/scan_deviation.py | 98 ++++++- services/api/test_scan_trunc.py | 263 +++++++++++++++++++ 6 files changed, 415 insertions(+), 17 deletions(-) create mode 100644 services/api/test_scan_trunc.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8452b7f5..346e7881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### SCAN-TRUNC — a model cut short turned a correct building into 500 as-built findings + +`scan_deviation.model_surface_points` caps the reference at 200,000 surface vertices and reaches the +cap by **breaking out of the element iterator**, so it drops whole elements rather than thinning +evenly. Scan points over a dropped element were measured against whatever surface remained. + +Measured on a two-wing model built exactly to design: **100.0% within tolerance** against the full +reference, **50.0% with 500 out-of-tolerance points and a 50 m maximum** once the reference was cut +short — with nothing in the response saying so. That is a fabricated defect, not a loss of precision, +and on a QA/QC check it sends a crew to re-survey a wing that is fine. It is the *opposite sign* from +CLASH-TRUNC, where truncation made a partial matrix read clean. + +The two caps are now handled differently on purpose. A truncated **scan** is a coverage claim — the +points read were read correctly — so the verdict stands with its coverage beside it. A truncated +**reference** is a correctness claim, so `within_pct` and the histogram are withheld and the response +says why, pointing at `/scan/verify-lod500`, which queries per element and never truncates the model. + ### SUPPLY-DARK — an empty competitive set reported the most favourable market verdict there is `services/api/src/aec_api/supply_pipeline.py` weighs a competitive pipeline by what is **recorded** diff --git a/docs/roadmap.md b/docs/roadmap.md index 6ccead8d..2cee2fb4 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,46 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ ⭐ **SCAN-TRUNC — a model cut short turns a correct building into 500 as-built findings** + *(S — Lane C; **CLOSED 2026-09-25**; gated by `services/api/test_scan_trunc.py`)* + + `scan_deviation.model_surface_points` caps the reference at 200,000 surface vertices, and reaches + that cap by **breaking out of the element iterator**. So hitting it does not thin the reference + evenly — it drops whole elements, in whatever order ifcopenshell yielded them. Every scan point + over a dropped element is then measured against the nearest surface that remains, which can be + metres away. Nothing in the response said the cap had been hit. + + **Measured on a two-wing model built exactly to design:** + + | reference | `within_pct` | out of tolerance | max deviation | + |---|---|---|---| + | full | **100.0** | 0 | 0.004 m | + | truncated to the first wing | **50.0** | **500** | **50.0 m** | + + *That is not a loss of precision, it is a fabricated defect* — and on a QA/QC as-built check it + sends a crew to re-survey a wing that is fine. **It is the opposite sign from CLASH-TRUNC**, where + truncation made a partial matrix read *clean*. The direction a silent bound pushes the answer is a + property of the bound, not of truncation, so neither case predicts the other and both had to be + measured. The cap exists *for* large models, so this is the normal case on a real project. + + **The two caps are treated differently, and the asymmetry is the load-bearing decision.** A + truncated SCAN (`parse_point_cloud`, 500,000 points) is a coverage claim — every point read was + measured correctly, so the verdict is true of the part examined and is reported beside its + coverage, the shape this repository uses everywhere else. A truncated REFERENCE is a correctness + claim — there is no population the figure is true of — so `within_pct` and the histogram are + **withheld** rather than qualified. *A caveat is for a number that means something.* The refusal + also points at `/scan/verify-lod500`, which queries per element and never truncates the model. + + **And the gate's first draft verified the refusal without verifying anything could reach it.** Two + mutations — the producer returning `False` unconditionally, and the route dropping the keyword — + both left it reporting a clean tree, because it exercised `analyze()` in isolation. The refusal + could have been correct and dead. *Verifying a refusal is not verifying that anything can reach + it.* The producer is now exercised against a fake geometry iterator, and the route's call is read + by AST with both keywords required to be names rather than constants — a hardcoded `False` was the + form the second mutation took. Ten mutations, all red, in both directions: over-correcting (the + scan cap also refusing, the producer claiming truncation always) reds as well as under-correcting. + + Seven of eleven engines once shipped with no route. The R32 filing-spine entries that occupied this band are all closed and recorded in [`roadmap-completed.md`](roadmap-completed.md). The current diff --git a/services/api/run_tests.py b/services/api/run_tests.py index bd50ece7..2270ebce 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -86,7 +86,7 @@ "test_markup", "test_route_authz", "test_resource_id_authz", "test_route_reachability", "test_route_shadow", "test_body_param_reach", "test_resumable_upload", "test_model_align", "test_ifc_parse_gate", "test_plugin_isolation", "test_body_pid_authz", "test_global_authz", "test_protected_prefix_coverage", "test_baseline", "test_global_mutating_authz", "test_ref_counter", "test_audit_coverage", "test_bsdd", "test_openbim_registry", "test_waterfall", "test_waterfall_cents", "test_sessions", "test_mfa", "test_stored_ids", "test_cobie", "test_fts_index", "test_scim", "test_scim_provision_race", "test_natural_key_race", "test_seeding_sweep", "test_unique_read_guard", "test_member_role_race", "test_bcf_reimport_dedupe", "test_model_version_collision", "test_saml", "test_responsibility", "test_audit_commit", "test_array_live", "test_assemblies", "test_dxf_takeoff", "test_qto_class_match", "test_georef", "test_scene_package", "test_clash_bvh", "test_model_qa", "test_model_health", "test_roundtrip_qa", "test_stakeholder", "test_prioritization", "test_ai_readiness", - "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", + "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_scan_trunc", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", # R41-TEST-RESIDUE — the residue sweep must never propose a database it does not own: "test_sweep_guard", # R23-DIGEST — the deterministic model digest and its two routes: diff --git a/services/api/src/aec_api/routers/analysis.py b/services/api/src/aec_api/routers/analysis.py index b8448094..6f615e9c 100644 --- a/services/api/src/aec_api/routers/analysis.py +++ b/services/api/src/aec_api/routers/analysis.py @@ -811,16 +811,22 @@ async def scan_deviation(pid: str, file: UploadFile = File(...), tolerance: floa # The point-cloud parse and — far heavier — the IFC open + full tessellation are CPU-bound and # would block the event loop (stalling every other request on this worker) if run inline. Offload # to the threadpool, mirroring run_validate below. - pts = await run_in_threadpool(lambda: sd.parse_point_cloud(raw.decode("utf-8", "ignore"))) + pts, points_total = await run_in_threadpool( + lambda: sd.parse_point_cloud_counted(raw.decode("utf-8", "ignore"))) if len(pts) == 0: raise HTTPException(400, "no readable XYZ points in the upload") try: - ref = await run_in_threadpool(lambda: sd.model_surface_points(ifcopenshell.open(ifc_path))) + # BOTH caps are carried into `analyze`, because whether they were hit changes what the + # numbers mean — and for the reference cap it changes whether there are numbers at all. + ref, ref_truncated = await run_in_threadpool( + lambda: sd.model_surface_points_capped(ifcopenshell.open(ifc_path))) except Exception as e: # noqa: BLE001 — geometry failure is a 4xx, not a 500 raise HTTPException(400, f"could not build model geometry: {e}") from e if len(ref) == 0: raise HTTPException(409, "the model has no triangulated geometry to compare against") - return await run_in_threadpool(lambda: sd.analyze(pts, ref, tolerance)) + return await run_in_threadpool( + lambda: sd.analyze(pts, ref, tolerance, + points_total=points_total, reference_truncated=ref_truncated)) @router.post("/projects/{pid}/scan/verify-lod500") diff --git a/services/api/src/aec_api/scan_deviation.py b/services/api/src/aec_api/scan_deviation.py index 41f6345b..cd2edb43 100644 --- a/services/api/src/aec_api/scan_deviation.py +++ b/services/api/src/aec_api/scan_deviation.py @@ -13,9 +13,33 @@ from typing import Any -def analyze(points: Any, reference: Any, tolerance: float = 0.05) -> dict[str, Any]: +def analyze(points: Any, reference: Any, tolerance: float = 0.05, *, + points_total: int | None = None, reference_truncated: bool = False) -> dict[str, Any]: """points / reference: Nx3 arrays (scan points, model surface vertices). Returns the deviation - summary + histogram. `tolerance` is the in/out threshold in model units (metres).""" + summary + histogram. `tolerance` is the in/out threshold in model units (metres). + + **The two truncation arguments are not decoration, and they are not symmetric.** + + `model_surface_points` caps the reference at 200,000 vertices and BREAKS out of the element + iterator to do it, so whole elements are simply absent from the reference — and which ones is + whatever order ifcopenshell yielded. Every scan point near an omitted element is then measured + against the nearest surface that IS present, which can be metres away. Measured on a two-wing + model built exactly to design: + + full reference within_pct 100.0 · 0 out of tolerance · max 0.004 m + reference truncated to wing A within_pct 50.0 · 500 out of tolerance · max 50.0 m + + So a truncated reference does not make the answer less precise, it **fabricates as-built + defects** — and on a QA/QC check that sends a crew to re-survey a wing that is fine. A deviation + figure computed against a partial model is not a measurement of anything, so `within_pct` and + the histogram are WITHHELD rather than qualified: there is no reading to caveat. + + `points_total` is different in kind. `parse_point_cloud` caps the scan at 500,000 points, and + every point that WAS read is still measured correctly — the verdict is true of the part of the + cloud that was examined. A scan file is written in sweep order, so a prefix is a spatial subset + rather than a random sample, which is a coverage claim and not a correctness one. It is reported + beside the verdict, in the shape this repository uses everywhere else for that. + """ import numpy as np from scipy.spatial import cKDTree @@ -24,6 +48,19 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05) -> dict[str, A if len(pts) == 0 or len(ref) == 0: return {"point_count": int(len(pts)), "reference_count": int(len(ref)), "error": "empty point cloud or reference", "within_pct": None} + if reference_truncated: + return { + "point_count": int(len(pts)), "reference_count": int(len(ref)), + "tolerance": tolerance, + "reference_truncated": True, "within_pct": None, + "error": (f"the model reference was capped at {len(ref):,} surface vertices, so part of " + "the model is absent from the comparison. Scan points near an omitted element " + "would be measured against whatever surface remains, which reports a correct " + "structure as out of tolerance — no deviation figure is produced"), + "note": "Absence of reference geometry is not evidence of deviation. Compare a smaller " + "region, or use /scan/verify-lod500, which queries per element and never " + "truncates the model.", + } dist, _ = cKDTree(ref).query(pts, k=1) within = int((dist <= tolerance).sum()) n = int(len(pts)) @@ -31,8 +68,12 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05) -> dict[str, A edges = [0, tolerance, 2 * tolerance, 3 * tolerance, float("inf")] labels = ["≤1×tol", "1–2×tol", "2–3×tol", ">3×tol"] hist = [int(((dist >= edges[i]) & (dist < edges[i + 1])).sum()) for i in range(4)] + truncated_pts = points_total is not None and points_total > n return { "point_count": n, "reference_count": int(len(ref)), + "points_total": int(points_total) if points_total is not None else n, + "points_truncated": bool(truncated_pts), + "reference_truncated": False, "tolerance": tolerance, "within_tolerance": within, "within_pct": round(100 * within / n, 1), "out_of_tolerance": n - within, @@ -40,8 +81,11 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05) -> dict[str, A "max_deviation": round(float(dist.max()), 4), "p95_deviation": round(float(np.percentile(dist, 95)), 4), "histogram": [{"band": lbl, "count": c} for lbl, c in zip(labels, hist)], - "note": "Nearest-surface deviation of each scan point vs the model's triangulated vertices; " - "within-tolerance is the share ≤ the tolerance. Feeds a red/green deviation heatmap.", + "note": ("Nearest-surface deviation of each scan point vs the model's triangulated vertices; " + "within-tolerance is the share ≤ the tolerance. Feeds a red/green deviation heatmap." + + (f" ONLY {n:,} of {points_total:,} readable scan points were examined — a scan " + "file is written in sweep order, so this covers a REGION of the cloud rather " + "than a sample of it." if truncated_pts else "")), } @@ -207,8 +251,20 @@ def verify_from_scan(model, deviation: dict[str, Any], verified_by: str = "", def model_surface_points(model, max_points: int = 200000): - """Triangulated-surface vertices of the IFC model (reference for the deviation query). Iterates - ifcopenshell.geom; capped at `max_points` so a huge model can't blow memory. Returns an Nx3 list.""" + """Triangulated-surface vertices of the IFC model. Kept for callers that only want the array; + `model_surface_points_capped` is what the route uses, because whether this CAP was hit changes + the meaning of every number computed from the result.""" + verts, _ = model_surface_points_capped(model, max_points) + return verts + + +def model_surface_points_capped(model, max_points: int = 200000): + """`(vertices, truncated)` — the triangulated-surface vertices, and whether the cap was reached. + + **The cap breaks out of the element iterator**, so hitting it does not thin the reference + evenly; it drops whole elements, in whatever order ifcopenshell yielded them. A scan point over + a dropped element is then measured against the nearest surface still present. `analyze` refuses + to produce a deviation figure when this is True, and the docstring there has the measurement.""" import ifcopenshell.geom as geom import numpy as np @@ -227,14 +283,28 @@ def model_surface_points(model, max_points: int = 200000): if not it.next(): break if not verts: - return np.zeros((0, 3)) - return np.vstack(verts)[:max_points] + return np.zeros((0, 3)), False + stacked = np.vstack(verts) + return stacked[:max_points], bool(len(stacked) >= max_points) def parse_point_cloud(text: str, max_points: int = 500000): - """Parse an ASCII point cloud (XYZ / CSV — one point per line, first three numbers are x y z).""" + """Parse an ASCII point cloud (XYZ / CSV — one point per line, first three numbers are x y z). + Kept for callers that only want the array; `parse_point_cloud_counted` is what the route uses.""" + pts, _ = parse_point_cloud_counted(text, max_points) + return pts + + +def parse_point_cloud_counted(text: str, max_points: int = 500000): + """`(points, readable_total)` — the points KEPT, and how many the file actually contained. + + The old form stopped reading at the cap and returned an array whose length was the cap, so + `point_count: 500000` was indistinguishable from a file that happened to hold exactly that many. + Counting continues past the cap while appending does not, so memory stays bounded and the + response can say what share of the cloud it examined.""" import numpy as np pts = [] + total = 0 for line in text.splitlines(): line = line.strip() if not line or line[0] in "#/": @@ -242,9 +312,11 @@ def parse_point_cloud(text: str, max_points: int = 500000): parts = line.replace(",", " ").split() if len(parts) >= 3: try: - pts.append((float(parts[0]), float(parts[1]), float(parts[2]))) + xyz = (float(parts[0]), float(parts[1]), float(parts[2])) except ValueError: continue - if len(pts) >= max_points: - break - return np.asarray(pts, dtype=float) if pts else np.zeros((0, 3)) + total += 1 + if len(pts) < max_points: + pts.append(xyz) + arr = np.asarray(pts, dtype=float) if pts else np.zeros((0, 3)) + return arr, total diff --git a/services/api/test_scan_trunc.py b/services/api/test_scan_trunc.py new file mode 100644 index 00000000..060de098 --- /dev/null +++ b/services/api/test_scan_trunc.py @@ -0,0 +1,263 @@ +"""SCAN-TRUNC — is a deviation figure ever computed against a MODEL THAT WAS CUT SHORT? + +`scan_deviation.model_surface_points` caps the reference at 200,000 surface vertices, and it reaches +that cap by BREAKING out of the element iterator. So hitting it does not thin the reference evenly — +it drops whole elements, in whatever order ifcopenshell happened to yield them. Every scan point over +a dropped element is then measured against the nearest surface that remains, which can be metres away. + +**That is not a loss of precision, it is a fabricated defect.** Measured on a two-wing model built +exactly to design, and reproduced by `test_truncation_biases_toward_failure` below: + + full reference within_pct 100.0 · 0 out of tolerance · max 0.004 m + reference truncated to wing A within_pct 50.0 · 500 out of tolerance · max 50.0 m + +On a QA/QC as-built check that sends a crew to re-survey a wing that is fine. It is the opposite sign +from CLASH-TRUNC, where truncation made a partial matrix read CLEAN — *the direction a silent bound +pushes the answer is a property of the bound, not of truncation*, so neither case predicts the other +and both have to be measured. + +**The two caps are treated differently on purpose, and the asymmetry is the load-bearing decision.** +A truncated SCAN is a coverage claim: every point that was read is still measured correctly, so the +verdict is true of the part examined and is reported beside its coverage, the way this repository +handles `skipped_count` everywhere else. A truncated REFERENCE is a correctness claim: there is no +population the figure is true of, so `within_pct` and the histogram are withheld rather than +qualified. *A caveat is for a number that means something.* + +This gate runs the real engine rather than inspecting source, because the defect is entirely in what +the numbers come out as — a static check that the flags are threaded through would pass a version +that threaded them through and ignored them, which is how `test_pin_pgnull`'s first draft failed. +""" +from __future__ import annotations + +import os +import sys + +os.environ.setdefault("DATABASE_URL", "sqlite:///./test_scan_trunc.db") +os.environ.setdefault("STORAGE_DIR", "./test_storage_scan_trunc") + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) + +import numpy as np # noqa: E402 + +from aec_api import scan_deviation as sd # noqa: E402 + +FAILURES: list[str] = [] + + +def check(name: str, ok: bool, detail: str = "") -> None: + print(f"{'PASS' if ok else 'FAIL'} {name}{(' ' + detail) if detail else ''}") + if not ok: + FAILURES.append(name) + + +def two_wing_scan(): + """A scan of a building in two wings, 50 m apart, built exactly to design.""" + rng = np.random.default_rng(0) + return np.vstack([rng.normal(0, 0.001, (500, 3)), + rng.normal(0, 0.001, (500, 3)) + [50.0, 0.0, 0.0]]) + + +def two_wing_reference(): + return np.vstack([np.zeros((300, 3)), np.tile([50.0, 0.0, 0.0], (300, 1))]) + + +# -------------------------------------------------------------------------------------------- +# THE CLAIM, REPRODUCED. Without this the entry above is an assertion; with it, deleting the +# refusal below re-creates the shipped defect and this file says so in numbers. +# -------------------------------------------------------------------------------------------- +scan, full = two_wing_scan(), two_wing_reference() +wing_a = full[:300] + +sound = sd.analyze(scan, full, 0.05) +check("a correct building reads as correct against the FULL reference", + sound["within_pct"] == 100.0 and sound["out_of_tolerance"] == 0, + f"within_pct={sound['within_pct']} out={sound['out_of_tolerance']}") + +# The pre-fix behaviour: the same scan, the same model, the reference cut short and nothing said. +unflagged = sd.analyze(scan, wing_a, 0.05) +check("truncation BIASES TOWARD FAILURE — this is what shipped", + unflagged["within_pct"] == 50.0 and unflagged["out_of_tolerance"] == 500 + and unflagged["max_deviation"] > 49.0, + f"within_pct={unflagged['within_pct']} out={unflagged['out_of_tolerance']} " + f"max={unflagged['max_deviation']}") + +# -------------------------------------------------------------------------------------------- +# THE REFUSAL +# -------------------------------------------------------------------------------------------- +refused = sd.analyze(scan, wing_a, 0.05, reference_truncated=True) +check("a truncated reference produces NO deviation figure", + refused["within_pct"] is None, f"within_pct={refused['within_pct']!r}") +check("...and no out-of-tolerance count to quote out of context", + "out_of_tolerance" not in refused and "histogram" not in refused, + f"keys={sorted(k for k in refused if k in ('out_of_tolerance', 'histogram'))}") +check("...and says why, naming the cap rather than reporting a generic error", + "capped at" in refused.get("error", "") and refused.get("reference_truncated") is True) +check("...and points at the route that does NOT truncate the model", + "verify-lod500" in refused.get("note", "")) + +# -------------------------------------------------------------------------------------------- +# THE COVERAGE — the other cap, handled the other way ON PURPOSE +# -------------------------------------------------------------------------------------------- +partial = sd.analyze(scan[:300], np.zeros((300, 3)), 0.05, points_total=1000) +check("a truncated SCAN keeps its verdict — the points read were read correctly", + partial["within_pct"] is not None, f"within_pct={partial['within_pct']}") +check("...and reports the coverage beside it", + partial["points_truncated"] is True and partial["point_count"] == 300 + and partial["points_total"] == 1000, + f"{partial['point_count']} of {partial['points_total']}") +check("...and says a prefix of a scan file is a REGION, not a sample", + "REGION" in partial["note"]) + +untruncated = sd.analyze(scan, full, 0.05, points_total=len(scan)) +check("an untruncated scan claims no truncation", + untruncated["points_truncated"] is False and untruncated["reference_truncated"] is False) +check("...and defaults to the same when no total is supplied — silence is not a claim", + sd.analyze(scan, full, 0.05)["points_truncated"] is False) + +# -------------------------------------------------------------------------------------------- +# THE PRODUCERS. A flag the caller can never learn is a flag that is never set, so the two +# functions that DO the truncating have to be able to say so. +# -------------------------------------------------------------------------------------------- +text = "\n".join(f"{i * 0.001} 0 0" for i in range(1200)) +kept, total = sd.parse_point_cloud_counted(text, max_points=1000) +check("parse_point_cloud_counted reports the readable total, not the capped one", + len(kept) == 1000 and total == 1200, f"kept {len(kept)} of {total}") +check("...and counts an exactly-full file as untruncated", + sd.parse_point_cloud_counted("\n".join(f"{i} 0 0" for i in range(1000)), + max_points=1000)[1] == 1000) +check("the legacy array-only form still works, for callers that want no metadata", + sd.parse_point_cloud("0 0 0\n1 1 1\n").shape == (2, 3)) + +# -------------------------------------------------------------------------------------------- +# CAN THE FLAG EVER BE SET? — the hole the first draft of this file had, found by mutation. +# +# Everything above tests what `analyze` DOES with `reference_truncated=True`. Nothing tested that +# anything ever passes it True. Both mutations "the producer never reports its cap" (return False +# unconditionally) and "the route stops threading the flag" left this file reporting a clean tree — +# so the refusal could have been dead code, correct and unreachable, and the gate would have agreed +# with itself. *Verifying a refusal is not verifying that anything can reach it.* +# +# The producer is exercised against a FAKE geometry iterator rather than a real IFC, because what is +# under test is the cap arithmetic and not ifcopenshell. Both functions it reaches for are imported +# inside the function body, so they can be substituted here. +# -------------------------------------------------------------------------------------------- +import types # noqa: E402 + + +class _FakeShape: + def __init__(self, n): + self.geometry = types.SimpleNamespace(verts=[0.0] * (3 * n)) + + +class _FakeIterator: + """Yields `chunks` elements of `per` vertices each.""" + + def __init__(self, chunks, per): + self._left, self._per = chunks, per + + def initialize(self): + return self._left > 0 + + def get(self): + return _FakeShape(self._per) + + def next(self): + self._left -= 1 + return self._left > 0 + + +def _with_fake_geometry(chunks, per, max_points): + """Run `model_surface_points_capped` against a fake iterator of known size.""" + fake_geom = types.ModuleType("ifcopenshell.geom") + fake_geom.settings = lambda: None + fake_ifc = types.ModuleType("ifcopenshell") + fake_ifc.geom = fake_geom + fake_geomconf = types.ModuleType("aec_data.geomconf") + fake_geomconf.bounded_iterator = lambda *a, **k: _FakeIterator(chunks, per) + saved = {k: sys.modules.get(k) for k in + ("ifcopenshell", "ifcopenshell.geom", "aec_data.geomconf")} + sys.modules.update({"ifcopenshell": fake_ifc, "ifcopenshell.geom": fake_geom, + "aec_data.geomconf": fake_geomconf}) + try: + return sd.model_surface_points_capped(object(), max_points=max_points) + finally: + for k, v in saved.items(): + if v is None: + sys.modules.pop(k, None) + else: + sys.modules[k] = v + + +over_verts, over_flag = _with_fake_geometry(chunks=10, per=100, max_points=250) +check("the producer SETS the flag when the cap is reached — without this the refusal is dead code", + over_flag is True, f"truncated={over_flag} verts={len(over_verts)}") +check("...and caps the array it returns", + len(over_verts) == 250, f"{len(over_verts)} vertices") + +under_verts, under_flag = _with_fake_geometry(chunks=3, per=100, max_points=100000) +check("...and does NOT claim truncation on a model that fits", + under_flag is False, f"truncated={under_flag} verts={len(under_verts)}") + +# -------------------------------------------------------------------------------------------- +# IS THE FLAG THREADED THROUGH THE ROUTE? — the second half of the same hole. +# +# This one is read from the source rather than exercised, and the limit is stated rather than +# glossed: driving `/scan/deviation` needs a project with an uploaded source IFC and a real +# tessellation, which this gate deliberately does not build. So it asserts the CALL — both keywords +# present, and each fed from the capped/counted producer rather than from a literal, which is the +# form the mutation took. `test_pin_pgnull`'s lesson (asserting the statement is not asserting the +# behaviour) applies to the arithmetic, and the arithmetic is exercised above; what is left here is +# wiring, which is exactly what source can answer. +# -------------------------------------------------------------------------------------------- +import ast # noqa: E402 +from pathlib import Path # noqa: E402 + +ROUTE = Path(__file__).resolve().parent / "src" / "aec_api" / "routers" / "analysis.py" +_tree = ast.parse(ROUTE.read_text(encoding="utf-8")) +_fn = next((n for n in ast.walk(_tree) + if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) + and n.name == "scan_deviation"), None) +check("PRECONDITION: the scan_deviation route function was found", + _fn is not None, str(ROUTE)) + +_calls = [c for c in ast.walk(_fn) + if isinstance(c, ast.Call) and isinstance(c.func, ast.Attribute) + and c.func.attr == "analyze"] if _fn else [] +check("PRECONDITION: the route still calls analyze exactly once", + len(_calls) == 1, f"{len(_calls)} call(s)") + +if _calls: + _kw = {k.arg: k.value for k in _calls[0].keywords} + check("the route passes reference_truncated", "reference_truncated" in _kw) + check("the route passes points_total", "points_total" in _kw) + # A literal would satisfy "passes it" while meaning the producer is ignored — which is what the + # mutation did by dropping the keyword entirely, and what a lazier fix would do by hardcoding. + check("...and neither is a hardcoded constant", + all(not isinstance(v, ast.Constant) for v in + (_kw.get("reference_truncated"), _kw.get("points_total")) if v is not None), + f"{ {k: type(v).__name__ for k, v in _kw.items()} }") + +_src = ROUTE.read_text(encoding="utf-8") +check("the route uses the producers that CAN report a cap", + "model_surface_points_capped" in _src and "parse_point_cloud_counted" in _src) + +# -------------------------------------------------------------------------------------------- +# PRECONDITIONS — a suite of `check`s over a module that failed to import would print nothing +# and exit 0, and the caps must be what this file claims they are. +# -------------------------------------------------------------------------------------------- +import inspect # noqa: E402 + +check("PRECONDITION: the reference cap is still the value this file reasons about", + inspect.signature(sd.model_surface_points_capped).parameters["max_points"].default == 200000) +check("PRECONDITION: the scan cap is still the value this file reasons about", + inspect.signature(sd.parse_point_cloud_counted).parameters["max_points"].default == 500000) +check("PRECONDITION: the reference cap still BREAKS the element loop, which is why it drops " + "whole elements rather than thinning evenly", + "break" in inspect.getsource(sd.model_surface_points_capped)) + +print() +if FAILURES: + print(f"scan_trunc: {len(FAILURES)} FAILED — {FAILURES}") + sys.exit(1) +print("scan_trunc: all checks passed — a deviation figure is never computed against a model that " + "was cut short; a truncated scan keeps its verdict and reports its coverage.") From e22c28280c276c74bb63cf6260559a64404b571d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 06:58:48 +0000 Subject: [PATCH 12/21] SCAN-TRUNC: delete the compatibility wrapper nothing was compatible with test_dead_code_population caught b2cc2954 within the hour: `model_surface_points` is unreachable. It is right, and the entry it flagged was mine from one commit earlier. Splitting `model_surface_points_capped` out, I kept the original as an array-only wrapper and wrote "kept for callers that only want the array" beside it. There are no such callers. The route moved to the capped form in the same commit, and that was its only production caller -- so the shim was compatibility for a caller that does not exist, justified by a sentence that sounded like a reason and named nobody. `parse_point_cloud` keeps its wrapper and is NOT dead, because the LOD 500 route at routers/analysis.py:851 still calls it. That asymmetry is the evidence: the same refactor produced one wrapper with a caller and one without, and only the second is dead. Had I checked callers instead of applying a symmetric habit to both, the gate would have had nothing to find. The repo's rule for this state is wire it, delete it, or FROZEN with a reason. Deleting is the honest one: there is now a single function, and it tells its caller whether it truncated, which is the whole point of the change. Verified: test_dead_code_population, test_scan_trunc, test_scan_deviation, test_scan_to_lod500, test_lod500_readiness and test_reachable all green; ruff clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- services/api/src/aec_api/scan_deviation.py | 15 ++++----------- services/api/test_scan_trunc.py | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/services/api/src/aec_api/scan_deviation.py b/services/api/src/aec_api/scan_deviation.py index cd2edb43..af1a6fa2 100644 --- a/services/api/src/aec_api/scan_deviation.py +++ b/services/api/src/aec_api/scan_deviation.py @@ -5,8 +5,9 @@ tolerance, mean/max/p95 deviation, a deviation histogram, and the out-of-tolerance count — the data behind a red/green heatmap. -Pure over numpy arrays; scipy cKDTree for the nearest-neighbour query. `model_surface_points` pulls the -reference vertices from an opened IFC via ifcopenshell.geom (guarded).""" +Pure over numpy arrays; scipy cKDTree for the nearest-neighbour query. `model_surface_points_capped` +pulls the reference vertices from an opened IFC via ifcopenshell.geom (guarded), and reports whether +its cap was reached — see `analyze`.""" from __future__ import annotations import contextlib @@ -20,7 +21,7 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05, *, **The two truncation arguments are not decoration, and they are not symmetric.** - `model_surface_points` caps the reference at 200,000 vertices and BREAKS out of the element + `model_surface_points_capped` caps the reference at 200,000 vertices and BREAKS out of the element iterator to do it, so whole elements are simply absent from the reference — and which ones is whatever order ifcopenshell yielded. Every scan point near an omitted element is then measured against the nearest surface that IS present, which can be metres away. Measured on a two-wing @@ -250,14 +251,6 @@ def verify_from_scan(model, deviation: dict[str, Any], verified_by: str = "", } -def model_surface_points(model, max_points: int = 200000): - """Triangulated-surface vertices of the IFC model. Kept for callers that only want the array; - `model_surface_points_capped` is what the route uses, because whether this CAP was hit changes - the meaning of every number computed from the result.""" - verts, _ = model_surface_points_capped(model, max_points) - return verts - - def model_surface_points_capped(model, max_points: int = 200000): """`(vertices, truncated)` — the triangulated-surface vertices, and whether the cap was reached. diff --git a/services/api/test_scan_trunc.py b/services/api/test_scan_trunc.py index 060de098..09661d3f 100644 --- a/services/api/test_scan_trunc.py +++ b/services/api/test_scan_trunc.py @@ -1,6 +1,6 @@ """SCAN-TRUNC — is a deviation figure ever computed against a MODEL THAT WAS CUT SHORT? -`scan_deviation.model_surface_points` caps the reference at 200,000 surface vertices, and it reaches +`scan_deviation.model_surface_points_capped` caps the reference at 200,000 surface vertices, and it reaches that cap by BREAKING out of the element iterator. So hitting it does not thin the reference evenly — it drops whole elements, in whatever order ifcopenshell happened to yield them. Every scan point over a dropped element is then measured against the nearest surface that remains, which can be metres away. From 0601e21c5e2acf39fef904886f1f95892ac5fbce Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:22:03 +0000 Subject: [PATCH 13/21] TRUNC-COUNTED: three screens printed the size of a page, and one of them acted on it THE ONE THAT ACTS IS roundtrip_diff. It carries three bounds -- rows[1:5001] on the sheet, changes[:1000], unknown_guids[:100] -- and disclosed exactly one, via `truncated`. apps/web/src/api/model.ts never DECLARED even that one, so qaSection.ts could not read it, while the same file reads the identical flag correctly forty lines earlier on a response whose type does declare it. A field absent from the declaration is invisible to every audit over declarations: the CLASH-TRUNC hole in a second place, and the reason this was carelessness by nobody. The Apply button posts d.changes -- the PAGE. So a sheet with more changes than the cap was APPLIED IN PART AND REPORTED AS WHOLE, and the model ends up differing from the spreadsheet the operator believes they applied, with no error anywhere. Reachable by construction rather than in principle: _diff_row emits one change per changed CELL against a 5,000-row bound, so a single property column overflows a 1,000 cap fivefold. The other two only print. operations.ts summed three 100-row pages into "Broken links (N)" beside percentages computed over the full population, so the card contradicted the numbers next to it; portal.ts showed 20 of a 100-row page with no total at all. The "every sheet, spec and package is linked" verdict was never wrong -- truncation cannot make a non-empty list empty -- only the counts were, and saying which is which is part of the report. WHY THERE IS NO SWEEP GATE, which is the part worth reading. The class was derived: 43 list truncations inside returned dicts, and 26 ALREADY CARRY a sibling len(). The good pattern is this codebase's own convention, which is what makes the rest anomalous rather than normal. Of the 17 without, 11 carry the count under a different NAME (total - compliant; unapproved; count) or are top-N by design, leaving 6 fields in 3 files. Joining those names to .length reads in the web tree reports 35 sites AND IT IS NOT A FINDING: `guids` alone accounts for 12, matched against engines their callers never call. The two read in full were correct code -- qaSection.ts:205 reads an assembly-thermal result's guids and slices to 200 itself, and repairPanel.ts:184 reads sample.length only to decide whether to print an ellipsis, with the authoritative `removable` count rendered beside it. A leaf name is not a response, and a number with a list attached reads as evidence. Even a checker-resolved join would still have to separate ".length shown to a user as a count" from ".length used for an ellipsis" -- a judgement call, and a rule that needs judgement calls needs an exemption list, which is where the next instance hides. So the three fixed sites are gated behaviourally and the sweep stops. AND THE GATE'S OWN CHECK WAS WRONG TWICE, both times by matching a name too widely. `"truncated:" in model_ts` passed with the field deleted, because `rows_truncated:` contains it -- a suffix match, the mirror of the prefix match test_gap_records paid for. Word-boundary matching over the WHOLE FILE also passed, because `truncated: boolean` is declared on four other unrelated endpoints in that 66 KB file. The sentence "a leaf name is not a response" is in this test's own docstring, about the 35-site join, and was committed as a defect one screen later. The subject is now roundtripDiff's own declaration, extracted by locator; renaming the method reds eight checks rather than narrowing the search to nothing. Two more, both mine. The web-tree anchor was parents[1], which is services/ -- it failed closed with a traceback, the right DIRECTION but the wrong shape, because a check that tracebacks blames Python instead of naming the path it wanted; a bad anchor is now a named failure. And innerHtmlGuard.test.ts caught gapList interpolating the bare parameter `total` into innerHTML: a server-side number today, but that guard keys on the BINDING NAME, which does not move when a caller starts passing something else. Escaped, not grandfathered. spineExcluded.test.ts's fixture predated gaps.counts and now blanks the card rather than showing a wrong number. That is the intended direction and no fallback was added: server and client ship from one build, so a response missing the field is a bug, not a skew to paper over -- and a fallback summing the page lengths would reinstate the defect silently. Verified: 10 mutations red in both directions, plus 4 more on the scoped declaration check; the gate runs identically from services/api and from the repo root; web 271 files / 2,958 tests green; typecheck, eslint and production build clean; ruff clean; test_claude_md_gates, test_roadmap_status, test_gap_records, test_reachable, test_dead_code_population and test_ruff_scope green; registered in run_tests.py beside test_scan_trunc. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 19 ++ apps/web/src/api/model.ts | 10 +- apps/web/src/api/types.ts | 10 +- apps/web/src/portal/panels/operations.ts | 26 ++- .../src/portal/panels/spineExcluded.test.ts | 9 +- apps/web/src/portal/portal.ts | 5 + apps/web/src/viewer/tools/qaSection.ts | 27 ++- docs/roadmap.md | 52 +++++ services/api/run_tests.py | 2 +- services/api/src/aec_api/dashboard.py | 6 +- services/api/src/aec_api/routers/standards.py | 10 +- services/api/src/aec_api/spine.py | 9 +- services/api/test_trunc_counted.py | 212 ++++++++++++++++++ 13 files changed, 379 insertions(+), 18 deletions(-) create mode 100644 services/api/test_trunc_counted.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 346e7881..56473885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### TRUNC-COUNTED — three screens printed the size of a page as the total, and one acted on it + +`roundtrip_diff` carries three bounds and disclosed one. `apps/web/src/api/model.ts` never declared +even that one, so the QA panel could not read it — while the same file reads the identical flag +correctly on another response whose type does declare it. + +The Apply button posts the truncated `changes` page, so a sheet past the cap was **applied in part +and reported as whole**: the model ends up differing from the spreadsheet the operator believes they +applied, with no error. Reachable by construction — one change per changed *cell* against a 5,000-row +bound overflows a 1,000 cap with a single property column. + +All three bounds now report themselves, `spine.traceability` returns `gaps.counts`, the dashboard +returns `action_item_count`, and the three screens read the totals instead of the page lengths. The +Apply button names what it will actually write. + +No sweep gate, and that is deliberate: 43 list truncations, **26 already carrying a sibling count**, +and joining the rest to `.length` reads by NAME reports 35 sites of which the ones read in full were +correct code. A leaf name is not a response. + ### SCAN-TRUNC — a model cut short turned a correct building into 500 as-built findings `scan_deviation.model_surface_points` caps the reference at 200,000 surface vertices and reaches the diff --git a/apps/web/src/api/model.ts b/apps/web/src/api/model.ts index 6039297e..1e8e4320 100644 --- a/apps/web/src/api/model.ts +++ b/apps/web/src/api/model.ts @@ -538,8 +538,16 @@ export function withModel>(Base: TBase) { const fd = new FormData(); fd.append("file", file); const res = await fetch(this.url(`/projects/${pid}/model/roundtrip/diff`), { method: "POST", body: fd, headers: this.authHeaders() }); if (!res.ok) { const e = await res.json().catch(() => ({ detail: res.statusText })); throw new HttpError(e.detail || `diff -> ${res.status}`, res.status); } + // THREE BOUNDS, all now declared. `changes` and `unknown_guids` are PAGES; `change_count` and + // `unknown_count` are the totals a screen should print. `truncated` shipped on the wire and was + // declared nowhere here, so `qaSection.ts` could not read it even though the same file reads the + // identical flag on another response — a field absent from the declaration is invisible to every + // audit over declarations, which is what CLASH-TRUNC paid to learn. return res.json() as Promise<{ checked: number; changes: { guid: string; pset: string; prop: string; old: string | null; new: string }[]; - unknown_guids: string[]; unchanged: number }>; + truncated: boolean; change_count: number; + unknown_guids: string[]; unknown_count: number; + rows_read: number; rows_cap: number; rows_truncated: boolean; + unchanged: number }>; } /** QUERY-DSL — select elements by a selector string (`IfcWall & Pset_WallCommon.FireRating=2HR & * storey=L3`) → matching GUIDs + parsed predicates. One grammar for filter / isolate / scope. */ diff --git a/apps/web/src/api/types.ts b/apps/web/src/api/types.ts index 389b3a0e..3ddd3503 100644 --- a/apps/web/src/api/types.ts +++ b/apps/web/src/api/types.ts @@ -614,7 +614,9 @@ export interface Dashboard { party: string; kpis: Record; cost: { budget: number; committed: number; actual: number; projected_over_under: number } | null; + /** A page of at most 100; `action_item_count` is how many are actually in this party's court. */ action_items: { module: string; module_name: string; id: string; ref: string; title: string | null; state: string; actions: string[] }[]; + action_item_count: number; by_module: { key: string; name: string; section: string; count: number; by_state: Record }[]; } @@ -1161,9 +1163,15 @@ export interface SpineTraceability { sheets_specced_pct: number | null; spec_to_budget_pct: number | null }; spec_count: number; withdrawn_excluded: { ref: string; section: string; title: string }[]; + /** The three lists are PAGES (100 each); `counts` is the population each was taken from. The + * `*_pct` values above were always computed over the full lists, so a screen summing the array + * lengths contradicted the percentages printed beside them. */ gaps: { specs_without_bid_package: { ref: string; section: string; title: string }[]; bid_packages_without_cost_code: { ref: string; name: string }[]; - sheets_without_spec: { ref: string; sheet: string }[] }; + sheets_without_spec: { ref: string; sheet: string }[]; + counts: { specs_without_bid_package: number; bid_packages_without_cost_code: number; + sheets_without_spec: number; total: number } }; + chain_count: number; chain: { spec: string; section: string; title: string; discipline: string | null; bid_package: string | null; bid_package_name: string | null; cost_code: string | null; cost_code_value: string | null; linked: boolean }[]; diff --git a/apps/web/src/portal/panels/operations.ts b/apps/web/src/portal/panels/operations.ts index 63f3d475..d98f55f3 100644 --- a/apps/web/src/portal/panels/operations.ts +++ b/apps/web/src/portal/panels/operations.ts @@ -301,18 +301,30 @@ export async function renderSpine(ctx: PanelContext) { // coverage gaps const g = t.gaps; - const gapCount = g.specs_without_bid_package.length + g.bid_packages_without_cost_code.length + g.sheets_without_spec.length; + // The server's own totals, not the lengths of the three 100-row pages. The percentages + // rendered above this card were always taken over the full population, so summing the page + // lengths made the card disagree with the numbers directly beside it. + const gapCount = g.counts.total; const gc = el("div", "dash-card"); gc.style.marginBottom = "10px"; gc.innerHTML = `
${gapCount ? "⚠ " : "✓ "}Broken links (${gapCount})
`; - const gapList = (title: string, items: string[]) => { - if (!items.length) return; + const gapList = (title: string, items: string[], total: number) => { + if (!total) return; const d = el("div", "meta"); d.style.margin = "3px 0"; - d.innerHTML = `${esc(title)} (${items.length}): ${items.slice(0, 12).map(esc).join(", ")}${items.length > 12 ? " …" : ""}`; + // `total` is escaped even though it is a server-side number: `innerHtmlGuard.test.ts` keys + // on the BINDING NAME, and a name does not move when a caller starts passing something else. + const n = esc(String(total)); + d.innerHTML = `${esc(title)} (${n}): ${items.slice(0, 12).map(esc).join(", ")}` + + (total > items.length + ? ` … showing ${items.length} of ${n}` + : items.length > 12 ? " …" : ""); gc.appendChild(d); }; - gapList("Specs with no bid package", g.specs_without_bid_package.map((x) => x.section || x.ref)); - gapList("Bid packages with no cost code", g.bid_packages_without_cost_code.map((x) => x.name || x.ref)); - gapList("Sheets with no governing spec", g.sheets_without_spec.map((x) => x.sheet || x.ref)); + gapList("Specs with no bid package", g.specs_without_bid_package.map((x) => x.section || x.ref), + g.counts.specs_without_bid_package); + gapList("Bid packages with no cost code", g.bid_packages_without_cost_code.map((x) => x.name || x.ref), + g.counts.bid_packages_without_cost_code); + gapList("Sheets with no governing spec", g.sheets_without_spec.map((x) => x.sheet || x.ref), + g.counts.sheets_without_spec); if (!gapCount) gc.insertAdjacentHTML("beforeend", `
Every sheet, spec and package is linked through to the budget.
`); body.appendChild(gc); diff --git a/apps/web/src/portal/panels/spineExcluded.test.ts b/apps/web/src/portal/panels/spineExcluded.test.ts index c89c69ee..a3c74a38 100644 --- a/apps/web/src/portal/panels/spineExcluded.test.ts +++ b/apps/web/src/portal/panels/spineExcluded.test.ts @@ -34,7 +34,14 @@ const SPINE = (over: Partial = {}): SpineTraceability => ({ { ref: "SPEC-011", section: "07 21 00", title: "Thermal insulation" }, { ref: "SPEC-019", section: "09 91 00", title: "Painting" }, ], - gaps: { specs_without_bid_package: [], bid_packages_without_cost_code: [], sheets_without_spec: [] }, + // `counts` joined the response in TRUNC-COUNTED: the three lists are 100-row PAGES and the card + // used to print the sum of their lengths as the broken-link total, beside percentages computed + // over the full population. A fixture without it now blanks the card rather than showing a wrong + // number, which is the intended direction — server and client ship from one build, so a response + // missing this field is a bug, not a deployment skew to paper over. + gaps: { specs_without_bid_package: [], bid_packages_without_cost_code: [], sheets_without_spec: [], + counts: { specs_without_bid_package: 0, bid_packages_without_cost_code: 0, + sheets_without_spec: 0, total: 0 } }, chain: [], note: "", ...over, diff --git a/apps/web/src/portal/portal.ts b/apps/web/src/portal/portal.ts index fe16b700..23aea8c5 100644 --- a/apps/web/src/portal/portal.ts +++ b/apps/web/src/portal/portal.ts @@ -1010,6 +1010,11 @@ export class PortalUI { // MAIN — Ball in your court (the most actionable list) main.appendChild(Object.assign(el("div", "section-title"), { textContent: "Ball in your court" })); if (d.action_items.length) { + if (d.action_item_count > 20) { + main.appendChild(Object.assign(el("div", "meta"), { + textContent: `showing 20 of ${d.action_item_count}`, + })); + } for (const a of d.action_items.slice(0, 20)) { const row = el("button", "portal-mod") as HTMLButtonElement; row.innerHTML = `→ ${esc(a.ref)} ${esc(a.title ?? "")} ${statusChip(a.state)}`; diff --git a/apps/web/src/viewer/tools/qaSection.ts b/apps/web/src/viewer/tools/qaSection.ts index a4b28e66..4043aafc 100644 --- a/apps/web/src/viewer/tools/qaSection.ts +++ b/apps/web/src/viewer/tools/qaSection.ts @@ -695,9 +695,15 @@ export function buildQaSection(d: QaDeps): void { status.textContent = "computing dry-run diff…"; diffBox.replaceChildren(); try { const d = await api.roundtripDiff(pid, f); - status.innerHTML = `${d.changes.length} change(s) across ${d.checked} rows` - + (d.unknown_guids.length ? ` · ${d.unknown_guids.length} unknown GUID(s) skipped` : "") - + ` · ${d.unchanged} unchanged`; + // COUNTS, NOT PAGE LENGTHS. `changes` caps at 1000 and `unknown_guids` at 100, so + // every figure here used to stop climbing at the cap while the sheet grew. + status.innerHTML = `${d.change_count} change(s) across ${d.checked} rows` + + (d.unknown_count ? ` · ${d.unknown_count} unknown GUID(s) skipped` : "") + + ` · ${d.unchanged} unchanged` + + (d.rows_truncated + ? ` · only the first ${d.rows_cap} rows of ` + + `${d.rows_read} were read` + : ""); if (!d.changes.length) return; const tbl = document.createElement("table"); tbl.className = "result-table"; for (const c of d.changes.slice(0, 300)) { @@ -708,7 +714,20 @@ export function buildQaSection(d: QaDeps): void { } diffBox.appendChild(tbl); const apply = document.createElement("button"); apply.className = "mini-btn on"; apply.style.marginTop = "6px"; - apply.textContent = `✓ Apply ${d.changes.length} change(s) + republish`; + // THE BUTTON NAMES WHAT IT WILL ACTUALLY DO. It posts `d.changes`, which is the + // PAGE — so on a sheet past the cap it applies 1000 of N and the model then differs + // from the spreadsheet the user believes they applied, with no error anywhere. It + // said "Apply ${d.change_count}" by accident of reading the wrong length. + apply.textContent = d.truncated + ? `✓ Apply the first ${d.changes.length} of ${d.change_count} change(s) + republish` + : `✓ Apply ${d.changes.length} change(s) + republish`; + if (d.truncated) { + diffBox.insertAdjacentHTML("beforeend", + `
` + + `This sheet has ${d.change_count} changes and the diff returns at most ` + + `${d.changes.length}. Applying now writes those and leaves the rest — split the ` + + `sheet and re-upload to apply all of them.
`); + } apply.onclick = async () => { apply.disabled = true; status.textContent = "applying via set_props_by_guid…"; try { diff --git a/docs/roadmap.md b/docs/roadmap.md index 2cee2fb4..ffab9560 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,58 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ ⭐ **TRUNC-COUNTED — three screens printed the length of a page as if it were the total, and + one of them ACTED on it** *(S — Lane C/G; **CLOSED 2026-09-25**; gated by + `services/api/test_trunc_counted.py`)* + + **The one that acts is `roundtrip_diff`.** It carries three bounds — `rows[1:5001]` on the sheet, + `changes[:1000]`, `unknown_guids[:100]` — of which only the middle one was disclosed, via + `truncated`. And `apps/web/src/api/model.ts` **did not declare `truncated`**, so + `apps/web/src/viewer/tools/qaSection.ts` could not read it — while the same file reads the + identical flag correctly forty lines earlier, on a response whose type does declare it. *A field + absent from the declaration is invisible to every audit over declarations* — the CLASH-TRUNC hole + in a second place, and the reason this was carelessness by nobody. + + The Apply button posts `d.changes`, the PAGE. **A sheet with more changes than the cap was applied + in part and reported as whole**, so the model ends up differing from the spreadsheet the operator + believes they applied, with no error anywhere. The cap is reachable by construction rather than in + principle: `_diff_row` emits one change per changed CELL against a 5,000-row bound, so a single + property column overflows a 1,000 cap fivefold. + + The other two only print: `portal/panels/operations.ts` summed three 100-row pages into + *"Broken links (N)"* beside percentages computed over the full population — so the card + contradicted the numbers next to it — and `portal/portal.ts` showed 20 of a 100-row page with no + total at all. *The `✓ Every sheet, spec and package is linked` verdict was never wrong*, because + truncation cannot make a non-empty list empty; only the counts were. + + **WHY THERE IS NO SWEEP GATE, which is the part worth reading.** The class was derived: **43 list + truncations inside returned dicts, and 26 already carry a sibling `len()`** — the good pattern is + this codebase's own convention, which is what makes the rest anomalous rather than normal. Of the + 17 without, **11 carry the count under a different NAME** (`total - compliant` in `naming.py`, + `unapproved` in `design_standards.py`, `count` in `rfi_prevention.py`) or are top-N by design, + leaving 6 fields in 3 files. + + Joining those names to `.length` reads in the web tree reports **35 sites, and it is not a + finding**: `guids` alone accounts for 12, matched against engines their callers never call. Two + were read in full — `viewer/tools/qaSection.ts:205` reads an assembly-thermal result's `guids` + (and slices to 200 itself), and `viewer/tools/repairPanel.ts:184` reads `sample.length` only to + decide whether to print an ellipsis, with the authoritative `removable` count rendered beside it. + *A leaf name is not a response, and a number with a list attached reads as evidence.* Even a + checker-resolved join would still have to separate "`.length` shown to a user as a count" from + "`.length` used for an ellipsis" — a judgement call, and **a rule that needs judgement calls needs + an exemption list, which is where the next instance hides.** So the three fixed sites are gated + behaviourally and the sweep stops there, deliberately. + + **The gate's own check was wrong twice, both times by matching a name too widely.** + `"truncated:" in model_ts` passed with the field deleted, because `rows_truncated:` contains it — + a suffix match, the mirror of the prefix match `test_gap_records` paid for. Word-boundary matching + over the whole file **also** passed, because `truncated: boolean` is declared on four other + unrelated endpoints in that 66 KB file. *The sentence in this entry's own paragraph above —* a + leaf name is not a response *— was committed as a defect one screen later.* The subject is now + `roundtripDiff`'s own declaration, extracted by locator, and renaming the method reds eight checks + rather than silently narrowing the search to nothing. + + - ✅ ⭐ **SCAN-TRUNC — a model cut short turns a correct building into 500 as-built findings** *(S — Lane C; **CLOSED 2026-09-25**; gated by `services/api/test_scan_trunc.py`)* diff --git a/services/api/run_tests.py b/services/api/run_tests.py index 2270ebce..c1674edf 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -86,7 +86,7 @@ "test_markup", "test_route_authz", "test_resource_id_authz", "test_route_reachability", "test_route_shadow", "test_body_param_reach", "test_resumable_upload", "test_model_align", "test_ifc_parse_gate", "test_plugin_isolation", "test_body_pid_authz", "test_global_authz", "test_protected_prefix_coverage", "test_baseline", "test_global_mutating_authz", "test_ref_counter", "test_audit_coverage", "test_bsdd", "test_openbim_registry", "test_waterfall", "test_waterfall_cents", "test_sessions", "test_mfa", "test_stored_ids", "test_cobie", "test_fts_index", "test_scim", "test_scim_provision_race", "test_natural_key_race", "test_seeding_sweep", "test_unique_read_guard", "test_member_role_race", "test_bcf_reimport_dedupe", "test_model_version_collision", "test_saml", "test_responsibility", "test_audit_commit", "test_array_live", "test_assemblies", "test_dxf_takeoff", "test_qto_class_match", "test_georef", "test_scene_package", "test_clash_bvh", "test_model_qa", "test_model_health", "test_roundtrip_qa", "test_stakeholder", "test_prioritization", "test_ai_readiness", - "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_scan_trunc", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", + "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_scan_trunc", "test_trunc_counted", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", # R41-TEST-RESIDUE — the residue sweep must never propose a database it does not own: "test_sweep_guard", # R23-DIGEST — the deterministic model digest and its two routes: diff --git a/services/api/src/aec_api/dashboard.py b/services/api/src/aec_api/dashboard.py index 7434c024..147afdb3 100644 --- a/services/api/src/aec_api/dashboard.py +++ b/services/api/src/aec_api/dashboard.py @@ -93,4 +93,8 @@ def open_count(key): by_module.sort(key=lambda m: (-m["count"], m["name"])) action_items.sort(key=lambda a: a["module"]) return {"party": party or "GC", "kpis": kpis, "cost": cost_snapshot, - "action_items": action_items[:100], "by_module": by_module} + # `action_items` is a page; `action_item_count` is how many are actually in this + # party's court. The dashboard renders the first 20 of the page, so without the total + # a user with 340 open items and one with 100 saw the same screen. + "action_items": action_items[:100], "action_item_count": len(action_items), + "by_module": by_module} diff --git a/services/api/src/aec_api/routers/standards.py b/services/api/src/aec_api/routers/standards.py index 5157f8fe..bc11a8a1 100644 --- a/services/api/src/aec_api/routers/standards.py +++ b/services/api/src/aec_api/routers/standards.py @@ -773,7 +773,15 @@ async def roundtrip_diff(pid: str, file: UploadFile = File(...), db: Session = D changes += row_changes unchanged += row_unchanged return {"checked": checked, "changes": changes[:1000], "truncated": len(changes) > 1000, - "unknown_guids": unknown[:100], "unchanged": unchanged} + # EVERY bound in this function is now reported, because a caller applies `changes` + # verbatim through `set_props_by_guid`: a sheet with more changes than the cap was + # applied in part and reported as whole. `change_count` is the number a screen should + # print; `len(changes)` is the size of the page it was handed. + "change_count": len(changes), + "unknown_guids": unknown[:100], "unknown_count": len(unknown), + "rows_read": max(0, len(rows) - 1), "rows_cap": 5000, + "rows_truncated": len(rows) - 1 > 5000, + "unchanged": unchanged} @router.post("/projects/{pid}/ci/run") diff --git a/services/api/src/aec_api/spine.py b/services/api/src/aec_api/spine.py index a5791855..b453eaa3 100644 --- a/services/api/src/aec_api/spine.py +++ b/services/api/src/aec_api/spine.py @@ -133,8 +133,15 @@ def bucket(code: str | None): "specs_without_bid_package": specs_no_pkg[:100], "bid_packages_without_cost_code": pkgs_no_code[:100], "sheets_without_spec": dwgs_no_spec[:100], + # The same rule the `spec_count` comment above states, applied to its neighbours: the + # lists are pages and these are the totals. The percentages beside them were always + # taken over the full population, so a screen summing the LENGTHS contradicted them. + "counts": {"specs_without_bid_package": len(specs_no_pkg), + "bid_packages_without_cost_code": len(pkgs_no_code), + "sheets_without_spec": len(dwgs_no_spec), + "total": len(specs_no_pkg) + len(pkgs_no_code) + len(dwgs_no_spec)}, }, - "chain": chain[:300], + "chain": chain[:300], "chain_count": len(chain), "note": "Traces discipline → sheets → specs → bid packages → cost codes → budget. A spec is " "fully traceable when it reaches a bid package and a cost code; the gaps list the broken " "links so scope can't fall between the model, the documents and the money. VOID spec " diff --git a/services/api/test_trunc_counted.py b/services/api/test_trunc_counted.py new file mode 100644 index 00000000..22531fda --- /dev/null +++ b/services/api/test_trunc_counted.py @@ -0,0 +1,212 @@ +"""TRUNC-COUNTED — does a response that returns a PAGE also say how big the population was? + +Three engines returned a truncated list and no total, and three screens printed the page's length as +if it were the total. The sharpest is `roundtrip_diff`, because a screen does not merely misreport +it — it ACTS on it: + + * `rows[1:5001]` — the sheet itself is capped. Undisclosed. + * `changes[:1000]` — disclosed, via `truncated`, which `apps/web/src/api/model.ts` did not DECLARE, + so `qaSection.ts` could not read it even though the same file reads the identical flag on another + response whose type does declare it. *A field absent from the declaration is invisible to every + audit over declarations* — the CLASH-TRUNC hole, in a second place. + * `unknown_guids[:100]` — undisclosed. + +The Apply button posts `d.changes`, the PAGE. A sheet with more changes than the cap was therefore +applied in part and reported as whole: the model ends up differing from the spreadsheet the operator +believes they applied, with no error anywhere. The cap is reachable by construction and not only in +principle — `_diff_row` emits one change per changed CELL against a 5,000-row bound, so a single +property column overflows a 1,000 cap fivefold. + +WHY THERE IS NO SWEEP GATE HERE, recorded so nobody rebuilds it and believes the number. +The class was derived: 43 list truncations inside returned dicts, of which **26 already carry a +sibling `len()`** — the good pattern is this codebase's own convention, which is what makes the rest +anomalous. Of the 17 without, 11 carry the count under another NAME (`total - compliant`, +`unapproved`, `count`) or are top-N by design, leaving 6 fields in 3 files. + +Joining those names to `.length` reads in the web tree reports **35 sites**, and it is not a finding: +`guids` alone accounts for 12, matched against engines their callers never call. Two were read in +full — `qaSection.ts:205` reads an assembly-thermal result's `guids` (it slices to 200 itself), and +`repairPanel.ts:184` reads `sample.length` only to decide whether to print an ellipsis, with the +authoritative `removable` count rendered beside it. *A leaf name is not a response*, and a number with +a list attached reads as evidence. Even a checker-resolved join would still have to separate +"`.length` shown to a user as a count" from "`.length` used for an ellipsis" — a judgement call, and a +rule needing judgement calls needs an exemption list, which is where the next instance hides. + +So this file gates the three fixed sites behaviourally and stops there. +""" +from __future__ import annotations + +import os +import sys + +os.environ.setdefault("DATABASE_URL", "sqlite:///./test_trunc_counted.db") +os.environ.setdefault("STORAGE_DIR", "./test_storage_trunc_counted") +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) + +import ast # noqa: E402 +import inspect # noqa: E402 +from pathlib import Path # noqa: E402 + +FAILURES: list[str] = [] + + +def check(name: str, ok: bool, detail: str = "") -> None: + print(f"{'PASS' if ok else 'FAIL'} {name}{(' ' + detail) if detail else ''}") + if not ok: + FAILURES.append(name) + + +def returned_keys(fn) -> set[str]: + """The literal string keys of the dict this function RETURNS, by AST rather than by regex — a + regex over the source also matches keys of dicts built along the way.""" + tree = ast.parse(inspect.getsource(fn).lstrip()) + out: set[str] = set() + for node in ast.walk(tree): + if isinstance(node, ast.Return) and isinstance(node.value, ast.Dict): + for k in node.value.keys: + if isinstance(k, ast.Constant) and isinstance(k.value, str): + out.add(k.value) + return out + + +# ------------------------------------------------------------------------------------------------ +# 1) roundtrip_diff — every bound reports itself +# ------------------------------------------------------------------------------------------------ +from aec_api.routers import standards # noqa: E402 + +rt = returned_keys(standards.roundtrip_diff) +check("PRECONDITION: roundtrip_diff's return dict was parsed", len(rt) >= 5, f"{len(rt)} keys") +for pair, why in ((("changes", "change_count"), "the changes page"), + (("unknown_guids", "unknown_count"), "the unknown-GUID page")): + page, total = pair + check(f"{why} is returned with its total", page in rt and total in rt, + f"{page}={page in rt} {total}={total in rt}") +check("the SHEET bound reports itself too — it was the one nothing mentioned", + {"rows_read", "rows_cap", "rows_truncated"} <= rt, + f"missing {sorted({'rows_read', 'rows_cap', 'rows_truncated'} - rt)}") +check("the pre-existing `truncated` flag is still returned", + "truncated" in rt) + +# The sheet bound must be the one the loop actually applies, not a number written twice. +src = inspect.getsource(standards.roundtrip_diff) +check("`rows_cap` agrees with the slice the loop really takes", + "rows[1:5001]" in src and "\"rows_cap\": 5000" in src, + "a cap reported from a different literal than the one applied is worse than none") + +# ------------------------------------------------------------------------------------------------ +# 2) spine traceability — the counts beside the pages +# ------------------------------------------------------------------------------------------------ +from aec_api import spine # noqa: E402 + +sp = inspect.getsource(spine.traceability) +check("spine gaps carry a `counts` block", '"counts": {"specs_without_bid_package"' in sp) +check("...with a `total`, which is what the screen prints", '"total": len(specs_no_pkg)' in sp) +check("...and `chain` carries its count", '"chain_count": len(chain)' in sp) +# The percentages were always right; that is the whole reason the screen contradicted itself. +check("the percentages are still taken over the FULL lists, not the pages", + "len(specs) - len(specs_no_pkg)" in sp and "specs_no_pkg[:100]" in sp, + "if these ever read from the page, the pct and the count would agree by both being wrong") + +# ------------------------------------------------------------------------------------------------ +# 3) dashboard — a worklist page with no total +# ------------------------------------------------------------------------------------------------ +from aec_api import dashboard # noqa: E402 + +db = returned_keys(dashboard.build) +check("dashboard returns action_item_count beside the page", + {"action_items", "action_item_count"} <= db, + f"missing {sorted({'action_items', 'action_item_count'} - db)}") + +# ------------------------------------------------------------------------------------------------ +# 4) THE CLIENT MUST DECLARE THEM — an undeclared field is unreadable, which is the whole cause here. +# `truncated` shipped on the wire for months and `model.ts` never declared it, so no type error, +# lint, or audit over declarations could reach `qaSection.ts:699`. +# ------------------------------------------------------------------------------------------------ +#: `parents[2]`, not `[1]` — `[1]` is `services/`. The first draft used it and died with a +#: FileNotFoundError, which is the right DIRECTION to fail but the wrong shape: a check that +#: tracebacks blames Python rather than naming the path it wanted. `read()` below turns a bad +#: anchor into a named failure, and the byte floors turn an empty read into one too. +WEB = Path(__file__).resolve().parents[2] / "apps" / "web" / "src" + + +def read(*parts: str) -> str: + p = WEB.joinpath(*parts) + if not p.is_file(): + check(f"PRECONDITION: {'/'.join(parts)} exists", False, f"looked in {p}") + return "" + return p.read_text(encoding="utf-8") + + +model_ts = read("api", "model.ts") +check("PRECONDITION: model.ts was read", len(model_ts) > 2000, f"{len(model_ts)} bytes") +#: SCOPED TO `roundtripDiff`'s OWN DECLARATION, and word-boundary matched. Two drafts were wrong +#: here, in the two ways this repo keeps re-learning: +#: +#: 1. `f"{f}:" in model_ts` — `rows_truncated:` CONTAINS `truncated:`, so deleting the field +#: under test still passed. A suffix match, the mirror of the prefix match `test_gap_records` +#: paid for. +#: 2. Word-boundary matching over the WHOLE FILE — still passed, because `truncated: boolean` +#: is declared on four other, unrelated endpoints in this 66 KB file (lines 271, 330, 555, +#: 711). *A leaf name is not a response* — the sentence in this file's own docstring, about +#: the 35-site name join, committed one screen later in the check itself. +#: +#: So the subject is the text of roundtripDiff's return type, nothing else. +import re as _re # noqa: E402 + + +def declaration_of(src: str, method: str) -> str: + """The source of one client method, from its name to the start of the next member.""" + m = _re.search(rf"^ (?:async )?{_re.escape(method)}\s*\(", src, _re.M) + if not m: + return "" + rest = src[m.end():] + nxt = _re.search(r"^ (?:async )?[a-zA-Z_]\w*\s*\(|^ /\*\*", rest, _re.M) + return rest[:nxt.start()] if nxt else rest + + +rt_decl = declaration_of(model_ts, "roundtripDiff") +check("PRECONDITION: roundtripDiff's own declaration was located and is not the whole file", + 500 < len(rt_decl) < 3000, f"{len(rt_decl)} bytes of {len(model_ts)}") +check("PRECONDITION: …and it is the right method", "roundtrip/diff" in rt_decl) + +for f in ("truncated", "change_count", "unknown_count", "rows_read", "rows_cap", "rows_truncated"): + check(f"roundtripDiff declares `{f}`", + _re.search(rf"(? 10000, f"{len(types_ts)} bytes") +check("types.ts declares the spine gap counts", "counts: { specs_without_bid_package: number" in types_ts) +check("types.ts declares action_item_count", "action_item_count: number" in types_ts) +check("types.ts declares chain_count", "chain_count: number" in types_ts) + +# ------------------------------------------------------------------------------------------------ +# 5) AND THE CONSUMERS MUST READ THEM RATHER THAN THE PAGE LENGTH. Declaring a total that nobody +# reads is the same screen with more JSON behind it. +# ------------------------------------------------------------------------------------------------ +ops = read("portal", "panels", "operations.ts") +check("operations.ts counts broken links from the server's total", + "const gapCount = g.counts.total;" in ops) +check("...and no longer sums the three page lengths", + "g.specs_without_bid_package.length + g.bid_packages_without_cost_code.length" not in ops) + +qa = read("viewer", "tools", "qaSection.ts") +check("qaSection headline uses change_count, not changes.length", + "${d.change_count} change(s)" in qa and "${d.changes.length} change(s)" not in qa) +check("...and unknown_count, not unknown_guids.length", + "d.unknown_count" in qa and "d.unknown_guids.length" not in qa) +check("the Apply button says it applies only the PAGE when the sheet overflows", + "Apply the first ${d.changes.length} of ${d.change_count}" in qa, + "this is the one that ACTS on the number rather than just printing it") +check("...and warns that the rest are left unwritten", + "leaves the rest" in qa) + +portal = read("portal", "portal.ts") +check("the dashboard worklist says how many it is showing of how many", + "showing 20 of ${d.action_item_count}" in portal) + +print() +if FAILURES: + print(f"trunc_counted: {len(FAILURES)} FAILED — {FAILURES}") + sys.exit(1) +print("trunc_counted: all checks passed — the three truncated responses report their populations, " + "the client declares them, and the three screens read them instead of the page length.") From d1085e8c2dda7c26a97474251fda80c2d04563c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:46:23 +0000 Subject: [PATCH 14/21] TRUNC-COUNTED: extract the diff view, because the ratchet said the panel may not grow test_file_sizes caught 0601e21c AFTER I pushed it, and the reason I missed it is worth more than the fix. Before pushing I checked that every suite covering the CHANGED FILES' BEHAVIOUR had already passed in that run -- test_xlsx_roundtrip, test_dashboard, test_traceability, test_golden_thread, test_roundtrip_qa, test_trunc_counted -- and concluded the remaining suites could not touch the diff. They could: test_file_sizes covers changed files too, STRUCTURALLY rather than behaviourally, and enumerating one kind of coverage felt like enumerating coverage. A gate that reads the size of a file I edited is a gate about my edit. Two files under down-only extraction ratchets had grown: portal.ts 1233 > 1228 and qaSection.ts 1293 > 1274. Raising a cap is not the repair -- those files exist to shrink -- so the additions went where the repo's own guidance puts them: a self-contained module plus one small mount point. apps/web/src/viewer/tools/roundtripDiffView.ts now owns the headline, the Apply label, the truncation note and the change table. qaSection.ts is 1261 (13 under its cap, having been 19 over), and portal.ts is exactly at 1228 with the worklist total folded into the heading it already renders rather than added beside it. AND THE EXTRACTION FIXED A REAL WEAKNESS IN MY OWN GATE. test_trunc_counted.py asserted the three rendering decisions by string-matching qaSection.ts, which is the weakest check available: it cannot tell a rendered string from a comment, and the declaration check next to it had already passed TWICE while its subject was deleted. Those three decisions are now unit tests -- roundtripDiffView.test.ts, 14 of them, calling the functions -- including the load-bearing one: on an overflowing sheet the button must not name the 4,000 it is not going to write, and Apply must receive the 1,000 the server actually sent. It also pins that spreadsheet cells are escaped into innerHTML, which the inline version had and nothing asserted. test_trunc_counted.py keeps what a unit test cannot see: the server shape, the client DECLARATIONS, and that the panel delegates rather than keeping a second copy. Plus a check that the vitest file exists at all -- without it the rendering assertions could be deleted and nothing here would notice. Verified: test_file_sizes green (portal.ts 1228/1228, qaSection.ts 1261/1274, client.ts 545/554, app.ts 2442/2442, register.ts 2447/2452); web 272 files / 2,972 tests green; typecheck, eslint and production build clean; ruff clean; test_trunc_counted, test_claude_md_gates, test_dead_code_population, test_reachable and test_roadmap_status green. The full backend suite on the previous commit finished 721/722, the one failure being this size gate, now fixed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- apps/web/src/portal/portal.ts | 7 +- apps/web/src/viewer/tools/qaSection.ts | 46 +------ .../viewer/tools/roundtripDiffView.test.ts | 128 ++++++++++++++++++ .../web/src/viewer/tools/roundtripDiffView.ts | 95 +++++++++++++ services/api/test_trunc_counted.py | 24 ++-- 5 files changed, 246 insertions(+), 54 deletions(-) create mode 100644 apps/web/src/viewer/tools/roundtripDiffView.test.ts create mode 100644 apps/web/src/viewer/tools/roundtripDiffView.ts diff --git a/apps/web/src/portal/portal.ts b/apps/web/src/portal/portal.ts index 23aea8c5..e6126227 100644 --- a/apps/web/src/portal/portal.ts +++ b/apps/web/src/portal/portal.ts @@ -1008,13 +1008,8 @@ export class PortalUI { cols.append(main, side); root.appendChild(cols); // MAIN — Ball in your court (the most actionable list) - main.appendChild(Object.assign(el("div", "section-title"), { textContent: "Ball in your court" })); + main.appendChild(Object.assign(el("div", "section-title"), { textContent: d.action_item_count > 20 ? `Ball in your court — showing 20 of ${d.action_item_count}` : "Ball in your court" })); if (d.action_items.length) { - if (d.action_item_count > 20) { - main.appendChild(Object.assign(el("div", "meta"), { - textContent: `showing 20 of ${d.action_item_count}`, - })); - } for (const a of d.action_items.slice(0, 20)) { const row = el("button", "portal-mod") as HTMLButtonElement; row.innerHTML = `→ ${esc(a.ref)} ${esc(a.title ?? "")} ${statusChip(a.state)}`; diff --git a/apps/web/src/viewer/tools/qaSection.ts b/apps/web/src/viewer/tools/qaSection.ts index 4043aafc..d5afe6fb 100644 --- a/apps/web/src/viewer/tools/qaSection.ts +++ b/apps/web/src/viewer/tools/qaSection.ts @@ -16,6 +16,7 @@ import { escapeHtml, toast, withLoading } from "../../ui/feedback"; import { LayerManager } from "../../tools/layers"; import { ModelLoader } from "../loader"; import { SelectionSets } from "../selectionSets"; +import { renderRoundtripDiff } from "./roundtripDiffView"; /** * R39-DECOMP-VIEWER ② — the clash / QA tool section, out of `app.ts`. @@ -695,48 +696,15 @@ export function buildQaSection(d: QaDeps): void { status.textContent = "computing dry-run diff…"; diffBox.replaceChildren(); try { const d = await api.roundtripDiff(pid, f); - // COUNTS, NOT PAGE LENGTHS. `changes` caps at 1000 and `unknown_guids` at 100, so - // every figure here used to stop climbing at the cap while the sheet grew. - status.innerHTML = `${d.change_count} change(s) across ${d.checked} rows` - + (d.unknown_count ? ` · ${d.unknown_count} unknown GUID(s) skipped` : "") - + ` · ${d.unchanged} unchanged` - + (d.rows_truncated - ? ` · only the first ${d.rows_cap} rows of ` - + `${d.rows_read} were read` - : ""); - if (!d.changes.length) return; - const tbl = document.createElement("table"); tbl.className = "result-table"; - for (const c of d.changes.slice(0, 300)) { - const tr = document.createElement("tr"); - tr.innerHTML = `${escapeHtml(c.guid.slice(0, 8))}… ${escapeHtml(c.pset)}.${escapeHtml(c.prop)}` - + `${escapeHtml(c.old ?? "—")} → ${escapeHtml(c.new)}`; - tbl.appendChild(tr); - } - diffBox.appendChild(tbl); - const apply = document.createElement("button"); apply.className = "mini-btn on"; apply.style.marginTop = "6px"; - // THE BUTTON NAMES WHAT IT WILL ACTUALLY DO. It posts `d.changes`, which is the - // PAGE — so on a sheet past the cap it applies 1000 of N and the model then differs - // from the spreadsheet the user believes they applied, with no error anywhere. It - // said "Apply ${d.change_count}" by accident of reading the wrong length. - apply.textContent = d.truncated - ? `✓ Apply the first ${d.changes.length} of ${d.change_count} change(s) + republish` - : `✓ Apply ${d.changes.length} change(s) + republish`; - if (d.truncated) { - diffBox.insertAdjacentHTML("beforeend", - `
` - + `This sheet has ${d.change_count} changes and the diff returns at most ` - + `${d.changes.length}. Applying now writes those and leaves the rest — split the ` - + `sheet and re-upload to apply all of them.
`); - } - apply.onclick = async () => { - apply.disabled = true; status.textContent = "applying via set_props_by_guid…"; + // Rendering, the three bounds and the Apply label live in `roundtripDiffView.ts`. + renderRoundtripDiff(d, { status, diffBox, apply: async (changes, btn) => { + btn.disabled = true; status.textContent = "applying via set_props_by_guid…"; try { - const r = await api.editIfc(pid, "set_props_by_guid", { changes: d.changes }); + const r = await api.editIfc(pid, "set_props_by_guid", { changes }); status.textContent = `applied ${r.changed} change(s) — model republishing`; notify("properties applied — reload the model to see them", "success"); - } catch (e) { status.textContent = `apply failed: ${(e as Error).message}`; apply.disabled = false; } - }; - diffBox.appendChild(apply); + } catch (e) { status.textContent = `apply failed: ${(e as Error).message}`; btn.disabled = false; } + } }); } catch (e) { status.textContent = `diff failed: ${(e as Error).message}`; } finally { upInput.value = ""; } }; diff --git a/apps/web/src/viewer/tools/roundtripDiffView.test.ts b/apps/web/src/viewer/tools/roundtripDiffView.test.ts new file mode 100644 index 00000000..1ab5542a --- /dev/null +++ b/apps/web/src/viewer/tools/roundtripDiffView.test.ts @@ -0,0 +1,128 @@ +import { describe, expect, it, vi } from "vitest"; + +import type { RoundtripDiff } from "./roundtripDiffView"; +import { applyLabel, renderRoundtripDiff, statusHtml, truncationNote } from "./roundtripDiffView"; + +/** + * TRUNC-COUNTED, asserted on BEHAVIOUR rather than on the panel's source text. + * + * `services/api/test_trunc_counted.py` originally string-matched `qaSection.ts` for these three + * decisions, which is the weakest form of check available — it cannot tell a rendered string from a + * comment, and it passed twice while the thing under test was deleted. Extracting the view to make + * `qaSection.ts` fit its down-only size ratchet made them testable for real, so this file owns the + * behaviour and the Python gate keeps only the server shape and the client DECLARATIONS. + * + * The load-bearing case is `truncated`: Apply posts the PAGE, so on an overflowing sheet the button + * must not name the total it is not going to write. + */ + +const D = (over: Partial = {}): RoundtripDiff => ({ + checked: 40, unchanged: 10, + changes: [{ guid: "3Rb$mtGnf8kQm0Xy1_ZzAB", pset: "Pset_WallCommon", prop: "FireRating", + old: "1HR", new: "2HR" }], + truncated: false, change_count: 1, + unknown_guids: [], unknown_count: 0, + rows_read: 40, rows_cap: 5000, rows_truncated: false, + ...over, +} as RoundtripDiff); + +/** A page of 1,000 changes out of 4,000 — the measured overflow shape. */ +const OVERFLOW = () => D({ + changes: Array.from({ length: 1000 }, (_, i) => ({ + guid: `G${i}`, pset: "P", prop: "x", old: "a", new: "b" })), + truncated: true, change_count: 4000, checked: 5000, +}); + +describe("the headline counts, never the page", () => { + it("reports change_count, not changes.length", () => { + expect(statusHtml(OVERFLOW())).toContain("4000 change(s)"); + expect(statusHtml(OVERFLOW())).not.toContain("1000 change(s)"); + }); + + it("reports unknown_count, not unknown_guids.length", () => { + const d = D({ unknown_guids: new Array(100).fill("G"), unknown_count: 5000 }); + expect(statusHtml(d)).toContain("5000 unknown GUID(s)"); + expect(statusHtml(d)).not.toContain("100 unknown GUID(s)"); + }); + + it("says nothing about unknown GUIDs when there are none — a caveat always on is decoration", () => { + expect(statusHtml(D())).not.toContain("unknown GUID"); + }); + + it("names the SHEET bound, which nothing disclosed at all", () => { + const d = D({ rows_read: 20000, rows_cap: 5000, rows_truncated: true }); + expect(statusHtml(d)).toContain("only the first 5000 rows of 20000 were read"); + }); + + it("stays quiet about the sheet bound when the whole sheet was read", () => { + expect(statusHtml(D())).not.toContain("rows of"); + }); +}); + +describe("the button names what it will actually write", () => { + // It posts `d.changes` — the page. Claiming the total here is the defect that made a partial + // write read as a complete one. + it("on an overflowing sheet, says the page AND the total", () => { + expect(applyLabel(OVERFLOW())).toBe("✓ Apply the first 1000 of 4000 change(s) + republish"); + }); + + it("on a whole sheet, says the plain count", () => { + expect(applyLabel(D())).toBe("✓ Apply 1 change(s) + republish"); + }); + + it("never claims the total it is not going to write", () => { + expect(applyLabel(OVERFLOW())).not.toBe("✓ Apply 4000 change(s) + republish"); + }); +}); + +describe("the warning beside it", () => { + it("says the rest are left unwritten, and what to do", () => { + const n = truncationNote(OVERFLOW()); + expect(n).toContain("4000 changes"); + expect(n).toContain("leaves the rest"); + expect(n).toContain("split the sheet"); + }); + + it("is absent when nothing was truncated", () => { + expect(truncationNote(D())).toBe(""); + }); +}); + +describe("rendering", () => { + const mount = () => { + const status = document.createElement("div"); + const diffBox = document.createElement("div"); + document.body.replaceChildren(status, diffBox); + return { status, diffBox }; + }; + + it("returns no Apply button when there is nothing to apply", () => { + const ctx = { ...mount(), apply: vi.fn() }; + expect(renderRoundtripDiff(D({ changes: [], change_count: 0 }), ctx)).toBeNull(); + expect(ctx.status.textContent).toContain("0 change(s)"); + }); + + it("hands Apply the PAGE, which is all the server gave it", () => { + const ctx = { ...mount(), apply: vi.fn() }; + const d = OVERFLOW(); + const btn = renderRoundtripDiff(d, ctx)!; + btn.click(); + expect(ctx.apply).toHaveBeenCalledOnce(); + expect(ctx.apply.mock.calls[0]![0]).toHaveLength(1000); + }); + + it("escapes values that came out of a spreadsheet", () => { + // The CSV is user-supplied and every cell reaches innerHTML. + const ctx = { ...mount(), apply: vi.fn() }; + renderRoundtripDiff(D({ changes: [{ guid: "G", pset: "", prop: "p", + old: null, new: "x" }] }), ctx); + expect(ctx.diffBox.innerHTML).not.toContain(" { + const ctx = { ...mount(), apply: vi.fn() }; + renderRoundtripDiff(OVERFLOW(), ctx); + expect(ctx.diffBox.textContent).toContain("leaves the rest"); + }); +}); diff --git a/apps/web/src/viewer/tools/roundtripDiffView.ts b/apps/web/src/viewer/tools/roundtripDiffView.ts new file mode 100644 index 00000000..684fa5a0 --- /dev/null +++ b/apps/web/src/viewer/tools/roundtripDiffView.ts @@ -0,0 +1,95 @@ +/** The XLSX round-trip dry-run diff — and the three bounds it has to be honest about. + * + * `roundtrip_diff` returns a PAGE of changes and a page of unknown GUIDs, from a sheet that is + * itself capped: + * + * | bound | what it caps | + * |---|---| + * | `rows[1:5001]` | how much of the uploaded sheet is read at all | + * | `changes[:1000]` | how many changes come back | + * | `unknown_guids[:100]` | how many unmatched GUIDs are listed | + * + * **Only the middle one was ever disclosed, and its flag was not DECLARED on the client**, so the + * panel could not read it — while `qaSection.ts` reads the identical flag correctly on a different + * response whose type does declare it. *A field absent from the declaration is invisible to every + * audit over declarations.* + * + * The consequence is not a wrong label, it is a wrong WRITE. Apply posts `changes` verbatim through + * `set_props_by_guid`, so a sheet past the cap was applied in part and reported as whole: the model + * ends up differing from the spreadsheet the operator believes they applied, with no error + * anywhere. Reachable by construction — `_diff_row` emits one change per changed CELL against a + * 5,000-row bound, so a single property column overflows a 1,000 cap fivefold. + * + * Extracted from `qaSection.ts` rather than added to it: that file is under a down-only extraction + * ratchet in `services/api/test_file_sizes.py`, and the repo's guidance is a self-contained module + * plus one small mount point. It also makes these three decisions unit-testable, which string + * matching over the panel's source was never going to be. + */ +import type { ApiClient } from "../../api/client"; +import { escapeHtml } from "../../ui/feedback"; + +export type RoundtripDiff = Awaited>; + +/** The headline. Every figure is a COUNT, never the length of the page it arrived in. */ +export function statusHtml(d: RoundtripDiff): string { + return `${d.change_count} change(s) across ${d.checked} rows` + + (d.unknown_count ? ` · ${d.unknown_count} unknown GUID(s) skipped` : "") + + ` · ${d.unchanged} unchanged` + + (d.rows_truncated + ? ` · only the first ${d.rows_cap} rows of ` + + `${d.rows_read} were read` + : ""); +} + +/** What the button will ACTUALLY do — it posts the page, so on an overflowing sheet it must not + * claim the total. */ +export function applyLabel(d: RoundtripDiff): string { + return d.truncated + ? `✓ Apply the first ${d.changes.length} of ${d.change_count} change(s) + republish` + : `✓ Apply ${d.changes.length} change(s) + republish`; +} + +/** The warning beside it, or "" when the whole sheet came back. */ +export function truncationNote(d: RoundtripDiff): string { + if (!d.truncated) return ""; + return `
` + + `This sheet has ${d.change_count} changes and the diff returns at most ${d.changes.length}. ` + + `Applying now writes those and leaves the rest — split the sheet and re-upload to apply all ` + + `of them.
`; +} + +/** The change table, capped at 300 rows for the DOM's sake — a display bound, not a data one, and + * the count above it is already the real total. */ +export function changeTable(d: RoundtripDiff): HTMLTableElement { + const tbl = document.createElement("table"); + tbl.className = "result-table"; + for (const c of d.changes.slice(0, 300)) { + const tr = document.createElement("tr"); + tr.innerHTML = `${escapeHtml(c.guid.slice(0, 8))}… ${escapeHtml(c.pset)}.${escapeHtml(c.prop)}` + + `${escapeHtml(c.old ?? "—")} → ${escapeHtml(c.new)}`; + tbl.appendChild(tr); + } + return tbl; +} + +export interface DiffViewCtx { + status: HTMLElement; + diffBox: HTMLElement; + apply: (changes: RoundtripDiff["changes"], btn: HTMLButtonElement) => void | Promise; +} + +/** Render a completed dry run. Returns the Apply button, or null when there is nothing to apply. */ +export function renderRoundtripDiff(d: RoundtripDiff, ctx: DiffViewCtx): HTMLButtonElement | null { + ctx.status.innerHTML = statusHtml(d); + if (!d.changes.length) return null; + ctx.diffBox.appendChild(changeTable(d)); + const btn = document.createElement("button"); + btn.className = "mini-btn on"; + btn.style.marginTop = "6px"; + btn.textContent = applyLabel(d); + const note = truncationNote(d); + if (note) ctx.diffBox.insertAdjacentHTML("beforeend", note); + btn.onclick = () => ctx.apply(d.changes, btn); + ctx.diffBox.appendChild(btn); + return btn; +} diff --git a/services/api/test_trunc_counted.py b/services/api/test_trunc_counted.py index 22531fda..fa8cbbfd 100644 --- a/services/api/test_trunc_counted.py +++ b/services/api/test_trunc_counted.py @@ -189,16 +189,22 @@ def declaration_of(src: str, method: str) -> str: check("...and no longer sums the three page lengths", "g.specs_without_bid_package.length + g.bid_packages_without_cost_code.length" not in ops) +# The three RENDERING decisions moved to `apps/web/src/viewer/tools/roundtripDiffView.test.ts`, +# which asserts them by calling the functions. String-matching a panel's source was the weakest +# check available — it cannot tell a rendered string from a comment, and the declaration check +# above passed twice while its subject was deleted. What stays here is what a unit test cannot see: +# that the panel delegates at all, rather than keeping a second copy of the logic. qa = read("viewer", "tools", "qaSection.ts") -check("qaSection headline uses change_count, not changes.length", - "${d.change_count} change(s)" in qa and "${d.changes.length} change(s)" not in qa) -check("...and unknown_count, not unknown_guids.length", - "d.unknown_count" in qa and "d.unknown_guids.length" not in qa) -check("the Apply button says it applies only the PAGE when the sheet overflows", - "Apply the first ${d.changes.length} of ${d.change_count}" in qa, - "this is the one that ACTS on the number rather than just printing it") -check("...and warns that the rest are left unwritten", - "leaves the rest" in qa) +check("qaSection delegates the diff view instead of rendering it inline", + "renderRoundtripDiff(d, {" in qa) +check("...and no longer counts the page itself", + "d.changes.length" not in qa and "d.unknown_guids.length" not in qa) + +view = read("viewer", "tools", "roundtripDiffView.ts") +check("PRECONDITION: the extracted view was read", len(view) > 1500, f"{len(view)} bytes") +check("the view is unit-tested rather than grepped", + (WEB / "viewer" / "tools" / "roundtripDiffView.test.ts").is_file(), + "if this file goes, the rendering assertions go with it and nothing here would notice") portal = read("portal", "portal.ts") check("the dashboard worklist says how many it is showing of how many", From e3905a45eb8d2e0ac86b8045c59c64af8da60fd8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 07:56:11 +0000 Subject: [PATCH 15/21] SCAN-DARK: the refusal SCAN-TRUNC added pointed at a route frozen as unreachable THIS ITEM EXISTS BECAUSE OF THE PREVIOUS ONE. SCAN-TRUNC made /scan/deviation refuse when the model reference was cut short, and wrote a message telling the reader to "use /scan/verify-lod500, which queries per element and never truncates the model". That route had NO client method at all and was listed in services/api/test_route_reachability.py as deliberately clientless. A refusal that redirects to an unreachable feature is a gate you can read and cannot satisfy -- the AUTHORITY-DARK shape, created here by my own wording one commit earlier, and found only by going to check whether the sentence was true. And ApiClient.scanDeviation's declared return type OMITTED EVERY FIELD SCAN-TRUNC ADDED -- reference_truncated, points_truncated, points_total, error -- so the refusal was invisible to the client. Third instance of that hole this session after excluded_comparables and roundtripDiff.truncated. The engine also emitted the two flags from only two of its three branches; all three emit them now, so the client type requires them instead of guessing. A CLEAN NEGATIVE, MEASURED BEFORE A LINE OF UI. Five engines this session carried a verdict that went vacuous once nothing had been evaluated, so verify_from_scan was measured on a scan covering nothing first: verified 0, stamped 0, uncovered 50, nothing stamped, and within_tolerance is null rather than false on an uncovered element. IT FAILS CLOSED AND NEEDED NO REPAIR. Recording that is part of the report -- the card's only job is not to subtract the care already there. So: the aggregate's refusal renders as a refusal and prints no percentage, no band table and no statistic, because a zero there reinstates exactly what SCAN-TRUNC closed; `uncovered` carries the same weight as `verified`, since a high verified count over a thin scan is the number somebody quotes; and a finding renders as "verified as WRONG -- not stamped", a punch item rather than a handover. The per-element pass runs after the aggregate whatever the aggregate said, which is what makes the refusal's own advice actionable. THE SURVIVING MUTATION NAMED A BRANCH MY FIXTURES DID NOT HAVE. Keying refused() on `reference_truncated` instead of on the missing figure passed all 14 tests, because every fixture set the two together -- and the engine has a THIRD refusal branch, an empty cloud or reference, where within_pct is null and that flag is false. Asserting the one case you thought of is not asserting the property. The predicate reads the absent figure, both branches are fixtures now, and refused()-always-true and always-false both red. The route leaves test_route_reachability's clientless list and scanDeviation leaves the callerless ratchet in clientCallers.test.ts. Mounted through a delegating three-line call in qaSection.ts, which is under a down-only size ratchet: 1266 of 1274. Verified: 9 mutations red plus 3 on the refusal predicate; web 273 files / 2,987 tests green; typecheck, eslint and production build clean; ruff clean; test_route_reachability, test_file_sizes, test_scan_trunc, test_scan_deviation, test_scan_to_lod500, test_reachable, test_claude_md_gates, test_roadmap_status, test_gap_records and test_dead_code_population all green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 13 ++ apps/web/src/api/client.ts | 11 +- apps/web/src/api/clientCallers.test.ts | 2 +- apps/web/src/api/model.ts | 39 ++++ apps/web/src/viewer/tools/qaSection.ts | 5 + .../src/viewer/tools/scanVerifyView.test.ts | 166 ++++++++++++++++ apps/web/src/viewer/tools/scanVerifyView.ts | 186 ++++++++++++++++++ docs/roadmap.md | 32 +++ services/api/src/aec_api/scan_deviation.py | 4 + services/api/test_route_reachability.py | 1 - 10 files changed, 453 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/viewer/tools/scanVerifyView.test.ts create mode 100644 apps/web/src/viewer/tools/scanVerifyView.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 56473885..b4925267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### SCAN-DARK — scan-to-BIM deviation and LOD 500 verification reach a screen + +SCAN-TRUNC's refusal tells the reader to use `/scan/verify-lod500` instead. That route had no client +method and was frozen as deliberately clientless, so the refusal redirected to something unreachable. +`scanDeviation`'s declared type also omitted every field SCAN-TRUNC added, making the refusal +invisible to the client. + +Both are wired now, behind **📡 Scan → as-built** in the model-QA tools. The engines were measured +first and are sound: a scan covering nothing reports `verified: 0, stamped: 0, uncovered: 50` and +stamps nothing. The card renders the aggregate's refusal as a refusal rather than a zero, gives +`uncovered` the same weight as `verified`, and shows a finding as *verified as wrong, not stamped* — +a punch item rather than a handover. + ### TRUNC-COUNTED — three screens printed the size of a page as the total, and one acted on it `roundtrip_diff` carries three bounds and disclosed one. `apps/web/src/api/model.ts` never declared diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index b745edba..5b5103c8 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -290,10 +290,13 @@ export class ApiClient extends _ApiStageB { const res = await fetch(this.url(`/projects/${pid}/scan/deviation?tolerance=${tolerance}`), { method: "POST", body: fd, headers: this.authHeaders() }); if (!res.ok) throw new HttpError((await res.json().catch(() => ({ detail: res.status }))).detail || `scan -> ${res.status}`, res.status); - return res.json() as Promise<{ point_count: number; reference_count: number; tolerance: number; - within_tolerance: number; within_pct: number | null; out_of_tolerance: number; - mean_deviation: number; max_deviation: number; p95_deviation: number; - histogram: { band: string; count: number }[]; note: string }>; + // A truncated REFERENCE produces no deviation figure at all — `within_pct` is null and every + // per-band field is absent — so those are optional and a reader has to handle the refusal. + return res.json() as Promise<{ point_count: number; reference_count: number; tolerance?: number; + points_total: number; points_truncated: boolean; reference_truncated: boolean; + within_pct: number | null; error?: string; within_tolerance?: number; out_of_tolerance?: number; + mean_deviation?: number; max_deviation?: number; p95_deviation?: number; + histogram?: { band: string; count: number }[]; note?: string }>; } // W9-1 property mapping / normalization — the transform verb between IDS-validate and COBie-export diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 91750155..5daa6a21 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -320,7 +320,7 @@ const UNCALLED: readonly string[] = [ "reviewPost", "reviewScenario", "reviseDrawing", "runClash", "runMacro", "saveClausePlaybook", "saveMacros", "saveViewTemplates", - "scanDeviation", "scopeRegister", "securitiesPackage", "sendDigest", + "scopeRegister", "securitiesPackage", "sendDigest", "setLod", "setPhase", "sharedComment", "sharedDecision", "sharedDigestUrl", "spaceUtilBenchmarks", "speckleStatus", "tieredComps", "topicComments", "updateConnection", "veLog", "verificationDeviations", diff --git a/apps/web/src/api/model.ts b/apps/web/src/api/model.ts index 1e8e4320..23845c7a 100644 --- a/apps/web/src/api/model.ts +++ b/apps/web/src/api/model.ts @@ -549,6 +549,45 @@ export function withModel>(Base: TBase) { rows_read: number; rows_cap: number; rows_truncated: boolean; unchanged: number }>; } + /** SCAN → LOD 500 — attribute an uploaded point cloud to individual ELEMENTS and stamp the ones + * that verify. `/scan/deviation` compares the cloud to the whole model and returns one aggregate, + * which is useful for QA and useless for verification because it cannot say WHICH element is + * right. This runs the query per element, and unlike the aggregate it never truncates the model. + * + * Three outcomes, and the distinction is the whole value: covered and inside tolerance → stamped + * with its measured deviation, so the LOD 500 assertion states an accuracy; covered and outside → + * returned as a finding and deliberately NOT stamped, because verified-as-wrong is a punch item; + * never scanned → reported uncovered, because absence of points is not evidence. + * + * `apply=false` returns the same plan without writing, so a team can see what a scan would assert + * before it touches the model. Added 2026-09-25: the route shipped complete and was frozen in + * `services/api/test_route_reachability.py` as having no client — and `scan_deviation`'s own + * refusal message points at it, so the refusal was directing people to something unreachable. */ + async scanVerifyLod500(pid: string, file: File, opts: { tolerance?: number; apply?: boolean; + verifiedBy?: string } = {}) { + const fd = new FormData(); fd.append("file", file); + const q = new URLSearchParams({ tolerance: String(opts.tolerance ?? 0.05), + apply: String(opts.apply ?? false), verified_by: opts.verifiedBy ?? "" }); + const res = await fetch(this.url(`/projects/${pid}/scan/verify-lod500?${q}`), + { method: "POST", body: fd, headers: this.authHeaders() }); + if (!res.ok) { + const e = await res.json().catch(() => ({ detail: res.statusText })); + throw new HttpError(e.detail || `verify-lod500 -> ${res.status}`, res.status); + } + return res.json() as Promise<{ + applied: boolean; verified: number; stamped: number; accuracy_recorded: number; + findings: { guid: string; ifc_class: string | null; p95_deviation: number | null; + max_deviation: number | null }[]; + findings_count: number; uncovered: number; uncovered_guids: string[]; + tolerance: number | null; note: string; + deviation: { elements?: never; tolerance: number; coverage_band: number; scan_points: number; + covered: number; uncovered: number; within_tolerance: number; out_of_tolerance: number; + note: string }; + elements: { guid: string; ifc_class: string | null; name: string | null; covered: boolean; + within_tolerance: boolean | null; sampled?: number; scanned_points?: number; + mean_deviation?: number; max_deviation?: number; p95_deviation?: number; note?: string }[]; + }>; + } /** QUERY-DSL — select elements by a selector string (`IfcWall & Pset_WallCommon.FireRating=2HR & * storey=L3`) → matching GUIDs + parsed predicates. One grammar for filter / isolate / scope. */ modelSelect(pid: string, q: string, limit = 5000) { diff --git a/apps/web/src/viewer/tools/qaSection.ts b/apps/web/src/viewer/tools/qaSection.ts index d5afe6fb..657c3aba 100644 --- a/apps/web/src/viewer/tools/qaSection.ts +++ b/apps/web/src/viewer/tools/qaSection.ts @@ -17,6 +17,7 @@ import { LayerManager } from "../../tools/layers"; import { ModelLoader } from "../loader"; import { SelectionSets } from "../selectionSets"; import { renderRoundtripDiff } from "./roundtripDiffView"; +import { renderScanCard } from "./scanVerifyView"; /** * R39-DECOMP-VIEWER ② — the clash / QA tool section, out of `app.ts`. @@ -340,6 +341,10 @@ export function buildQaSection(d: QaDeps): void { // on it; this endpoint checks it and had no client caller, so the promise was unverifiable // from the product. Distinct from `roundtripDiff`, which compares a file YOU bring back — // this one asks whether OUR OWN export is lossless. + b.appendChild(toolBtn2("📡 Scan → as-built (deviation + LOD 500 verify)", () => { + showResult("Scan to as-built", (body) => renderScanCard(body, + { api, projectId: () => pid, setStatus: (m) => { out.textContent = m; } })); + })); b.appendChild(toolBtn2("🔁 Round-trip fidelity (is our export lossless?)", () => withLoading(container, "Serialising and re-parsing", async () => { let r; try { r = await api.modelRoundtrip(pid); } diff --git a/apps/web/src/viewer/tools/scanVerifyView.test.ts b/apps/web/src/viewer/tools/scanVerifyView.test.ts new file mode 100644 index 00000000..8d1507c3 --- /dev/null +++ b/apps/web/src/viewer/tools/scanVerifyView.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it } from "vitest"; + +import type { Deviation, Lod500 } from "./scanVerifyView"; +import { refused, renderDeviation, renderLod500 } from "./scanVerifyView"; + +/** + * SCAN-DARK. Both engines are sound and were measured before this card existed: + * + * * `analyze` REFUSES to produce a deviation figure when the model reference was cut short, and + * that refusal is the whole of SCAN-TRUNC — rendering a zero, or an empty histogram, in its + * place would reinstate the defect from the consumer side. + * * `verify_from_scan` fails closed on a scan that covered nothing: `verified: 0, stamped: 0, + * uncovered: 50`. `within_tolerance` is `null` on an uncovered element, never `false`. + * + * So what is pinned here is that the card does not SUBTRACT that care. The two load-bearing ones + * are the refusal rendering as a refusal, and `uncovered` carrying the same weight as `verified` — + * a high verified count over a thin scan is precisely the number somebody would quote. + */ + +const DEV = (over: Partial = {}): Deviation => ({ + point_count: 1000, reference_count: 200000, tolerance: 0.05, + points_total: 1000, points_truncated: false, reference_truncated: false, + within_pct: 97.5, within_tolerance: 975, out_of_tolerance: 25, + mean_deviation: 0.012, max_deviation: 0.31, p95_deviation: 0.04, + histogram: [{ band: "≤1×tol", count: 975 }, { band: ">3×tol", count: 25 }], + note: "Nearest-surface deviation of each scan point.", + ...over, +} as Deviation); + +/** The SCAN-TRUNC refusal, as the engine really returns it. */ +const REFUSAL = (): Deviation => ({ + point_count: 1000, reference_count: 200000, + points_total: 1000, points_truncated: false, reference_truncated: true, + within_pct: null, tolerance: 0.05, + error: "the model reference was capped at 200,000 surface vertices, so part of the model is " + + "absent from the comparison", + note: "Absence of reference geometry is not evidence of deviation.", +} as Deviation); + +/** The OTHER refusal branch: no readable points, or no model geometry. `within_pct` is null and + * `reference_truncated` is FALSE, so a predicate keyed on that flag misses it entirely — which a + * mutation proved, because every fixture above happened to set the two together. *Asserting the + * one case you thought of is not asserting the property.* */ +const EMPTY_REFUSAL = (): Deviation => ({ + point_count: 0, reference_count: 200000, + points_total: 0, points_truncated: false, reference_truncated: false, + within_pct: null, error: "empty point cloud or reference", +} as Deviation); + +const L = (over: Partial = {}): Lod500 => ({ + applied: false, verified: 2, stamped: 0, accuracy_recorded: 0, + findings: [], findings_count: 0, uncovered: 0, uncovered_guids: [], + tolerance: 0.05, note: "Verified elements are stamped WITH their measured deviation.", + deviation: {} as Lod500["deviation"], elements: [], + ...over, +} as Lod500); + +const mount = () => { + const host = document.createElement("div"); + document.body.replaceChildren(host); + return host; +}; +const devText = (d: Deviation) => renderDeviation(mount(), d).textContent ?? ""; +const lodText = (r: Lod500) => renderLod500(mount(), r).textContent ?? ""; + +describe("the refusal renders as a refusal", () => { + it("is recognised from within_pct being null, not from a flag a future branch might omit", () => { + expect(refused(REFUSAL())).toBe(true); + expect(refused(DEV())).toBe(false); + // THE SECOND BRANCH, and the reason the predicate reads the missing FIGURE rather than the + // truncation flag: an empty cloud refuses too, with `reference_truncated` false. + expect(refused(EMPTY_REFUSAL())).toBe(true); + expect(EMPTY_REFUSAL().reference_truncated).toBe(false); + }); + + it("renders the empty-input refusal as a refusal too", () => { + const el = renderDeviation(mount(), EMPTY_REFUSAL()); + expect(el.textContent).toContain("No deviation figure"); + expect(el.textContent).toContain("empty point cloud or reference"); + expect(el.querySelector("table")).toBeNull(); + }); + + it("says there is no figure, and gives the engine's reason", () => { + const t = devText(REFUSAL()); + expect(t).toContain("No deviation figure"); + expect(t).toContain("capped at 200,000 surface vertices"); + }); + + it("prints NO percentage, band table or statistic — a zero here reinstates SCAN-TRUNC", () => { + const el = renderDeviation(mount(), REFUSAL()); + expect(el.querySelector("table")).toBeNull(); + const t = el.textContent ?? ""; + expect(t).not.toContain("%"); + expect(t).not.toContain("mean"); + }); +}); + +describe("the aggregate, when there IS a figure", () => { + it("leads with the percentage and its tolerance", () => { + expect(devText(DEV())).toContain("97.5% of scan points within 0.05 m"); + }); + + it("renders the deviation bands", () => { + const rows = [...renderDeviation(mount(), DEV()).querySelectorAll("tr")] + .map((tr) => tr.textContent ?? ""); + expect(rows.some((r) => r.includes("≤1×tol") && r.includes("975"))).toBe(true); + }); + + it("says when only a region of the cloud was examined", () => { + const t = devText(DEV({ point_count: 500000, points_total: 2000000, points_truncated: true })); + expect(t).toContain("500,000 of 2,000,000"); + expect(t).toContain("REGION"); + }); + + it("stays quiet about that when the whole cloud was read", () => { + expect(devText(DEV())).not.toContain("REGION"); + }); +}); + +describe("uncovered carries the same weight as verified", () => { + it("names what was never scanned, and that it is not evidence", () => { + const t = lodText(L({ verified: 2, uncovered: 48 })); + expect(t).toContain("2 verified of 50"); + expect(t).toContain("48 never scanned"); + expect(t).toContain("absence of points is not evidence"); + }); + + it("says so plainly when coverage was complete", () => { + const t = lodText(L({ verified: 50, uncovered: 0 })); + expect(t).toContain("Every element was covered"); + expect(t).not.toContain("never scanned"); + }); + + it("does not let a thin scan read as a strong result — the count is of the WHOLE population", () => { + // 2 of 50, not "2 verified" with the other 48 invisible. + expect(lodText(L({ verified: 2, uncovered: 48 }))).not.toContain("2 verified of 2"); + }); +}); + +describe("a finding is shown as NOT stamped", () => { + it("lists it with its deviations and says why it was left alone", () => { + const t = lodText(L({ verified: 1, findings_count: 1, uncovered: 0, + findings: [{ guid: "3Rb$mtGnf8kQm0Xy1", ifc_class: "IfcSlab", + p95_deviation: 0.4, max_deviation: 0.6 }] })); + expect(t).toContain("Verified as WRONG — not stamped"); + expect(t).toContain("IfcSlab"); + expect(t).toContain("0.4"); + expect(t).toContain("punch item, not a handover"); + }); + + it("shows no findings table when there are none", () => { + expect(renderLod500(mount(), L()).querySelector("table")).toBeNull(); + }); +}); + +describe("a dry run says so", () => { + it("distinguishes a plan from a write", () => { + expect(lodText(L())).toContain("Dry run"); + expect(lodText(L({ applied: true, stamped: 2 }))).toContain("Stamped 2"); + }); + + it("says when stamps carry their measured accuracy", () => { + expect(lodText(L({ applied: true, stamped: 2, accuracy_recorded: 2 }))) + .toContain("states an accuracy rather than a bare claim"); + }); +}); diff --git a/apps/web/src/viewer/tools/scanVerifyView.ts b/apps/web/src/viewer/tools/scanVerifyView.ts new file mode 100644 index 00000000..86dd6334 --- /dev/null +++ b/apps/web/src/viewer/tools/scanVerifyView.ts @@ -0,0 +1,186 @@ +/** Scan-to-BIM: the aggregate deviation, and the per-element LOD 500 verification behind it. + * + * Both routes shipped complete and neither had a screen. `scanDeviation` sat in the callerless + * freeze list; `/scan/verify-lod500` had **no client method at all** and was frozen in + * `services/api/test_route_reachability.py` as unreachable — while `scan_deviation.analyze`'s own + * refusal message tells the reader to *"use /scan/verify-lod500, which queries per element and + * never truncates the model."* **A refusal that redirects to an unreachable feature is a gate you + * can read and cannot satisfy**, which is the AUTHORITY-DARK shape, created here by SCAN-TRUNC's + * own wording. Wiring both is what makes that sentence true. + * + * WHAT WAS MEASURED BEFORE ANY OF THIS WAS WRITTEN, and it is a clean negative worth stating. + * Five engines this session carried a verdict that went vacuous once nothing had been evaluated, so + * the degenerate case was measured first: a scan covering NOTHING returns + * `verified: 0, stamped: 0, uncovered: 50`, stamps nothing, and says so — *"absence of points is + * not a pass"*. `within_tolerance` is `null` rather than `false` on an uncovered element, which is + * the distinction the whole engine exists for. **It fails closed and needed no repair.** + * + * So the card's only job is not to subtract that care: + * + * * the aggregate's REFUSAL is rendered as a refusal, not as a zero — a truncated reference yields + * no `within_pct`, and printing "0%" or an empty histogram would reinstate exactly the defect + * SCAN-TRUNC closed; + * * `uncovered` is given equal weight to `verified`, because a high verified count over a thin + * scan is the number somebody would quote; + * * a finding is shown as NOT stamped, since verified-as-wrong is a punch item rather than a + * handover. + */ +import type { ApiClient } from "../../api/client"; +import { escapeHtml as esc } from "../../ui/feedback"; + +export type Deviation = Awaited>; +export type Lod500 = Awaited>; + +const meta = (html: string, colour?: string) => + `
${html}
`; + +const n0 = (v: number) => Math.round(v).toLocaleString("en-US"); + +/** Did the engine decline to produce a deviation figure? */ +export function refused(d: Deviation): boolean { + return d.within_pct == null; +} + +/** The aggregate. On a refusal this renders the reason and NOTHING numeric. */ +export function renderDeviation(host: HTMLElement, d: Deviation): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + + if (refused(d)) { + el.insertAdjacentHTML("beforeend", + `
No deviation figure
` + + meta(esc(d.error ?? "the engine returned no reading"), "var(--status-warn)") + + (d.note ? meta(esc(d.note)) : "")); + host.appendChild(el); + return el; + } + + el.insertAdjacentHTML("beforeend", + `
${d.within_pct!.toFixed(1)}% of scan points within ` + + `${d.tolerance ?? "—"} m
` + + meta(`${n0(d.point_count)} scan point(s) against ${n0(d.reference_count)} model surface ` + + `vertices · ${n0(d.out_of_tolerance ?? 0)} out of tolerance`) + + (d.points_truncated + ? meta(`Only ${n0(d.point_count)} of ${n0(d.points_total)} readable scan points were ` + + `examined — a scan file is written in sweep order, so this covers a REGION of the ` + + `cloud rather than a sample of it.`, "var(--status-warn)") + : "")); + + if (d.histogram?.length) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + d.histogram.map((h) => `` + + ``).join("") + + `
Deviation bandpoints
${esc(h.band)}${n0(h.count)}
`); + } + el.insertAdjacentHTML("beforeend", meta( + `mean ${d.mean_deviation ?? "—"} m · p95 ${d.p95_deviation ?? "—"} m · max ${d.max_deviation ?? "—"} m`)); + if (d.note) el.insertAdjacentHTML("beforeend", meta(esc(d.note))); + host.appendChild(el); + return el; +} + +/** The per-element verification. `uncovered` leads beside `verified`, deliberately. */ +export function renderLod500(host: HTMLElement, r: Lod500): HTMLElement { + const el = document.createElement("div"); + el.style.marginTop = "8px"; + const total = r.verified + r.findings_count + r.uncovered; + + el.insertAdjacentHTML("beforeend", + `
${r.applied ? `Stamped ${n0(r.stamped)}` : "Dry run"} — ` + + `${n0(r.verified)} verified of ${n0(total)} element(s)
` + // THE UNCOVERED COUNT IS NOT A FOOTNOTE. A verified count over a thin scan is the number + // somebody quotes, and the engine refuses to treat absence as evidence — so must this. + + meta(r.uncovered + ? `${n0(r.uncovered)} never scanned — not stamped and not counted against ` + + `the model. They need another scan position; absence of points is not evidence.` + : `Every element was covered by the scan.`, + r.uncovered ? "var(--status-warn)" : undefined)); + + if (r.findings_count) { + el.insertAdjacentHTML("beforeend", + `` + + `` + + `` + + r.findings.map((f) => `` + + `` + + `` + + ``).join("") + + `
Verified as WRONG — not stampedclassp95max
${esc((f.guid ?? "").slice(0, 10))}…${esc(f.ifc_class ?? "—")}${f.p95_deviation ?? "—"}${f.max_deviation ?? "—"}
` + + meta(`An element measured outside tolerance is a punch item, not a handover — it is ` + + `deliberately left unstamped.`, "var(--status-crit)")); + } + + if (r.accuracy_recorded) { + el.insertAdjacentHTML("beforeend", meta( + `${n0(r.accuracy_recorded)} stamp(s) carry the measured deviation, so the LOD 500 assertion ` + + `states an accuracy rather than a bare claim.`)); + } + el.insertAdjacentHTML("beforeend", meta(esc(r.note))); + host.appendChild(el); + return el; +} + +export interface ScanCtx { + api: ApiClient; + projectId: () => string | null | undefined; + setStatus: (m: string) => void; +} + +/** The card: pick a cloud, run the aggregate, then the per-element verification. */ +export function renderScanCard(root: HTMLElement, ctx: ScanCtx): HTMLElement { + const host = document.createElement("div"); + host.id = "scan-verify"; + host.style.marginTop = "8px"; + host.innerHTML = meta( + `Upload an as-built point cloud (XYZ/CSV). The aggregate answers how close is the build to ` + + `the model; the per-element pass answers which elements can be asserted as built ` + + `— and never truncates the model to do it.`); + + const row = document.createElement("div"); + row.style.cssText = "display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:6px 0"; + const tol = document.createElement("input"); + tol.type = "number"; tol.step = "0.01"; tol.className = "portal-filter"; tol.style.width = "90px"; + tol.value = "0.05"; tol.title = "Tolerance in metres — the in/out threshold."; + const label = document.createElement("label"); + label.className = "mini-btn"; label.textContent = "⇪ Point cloud"; label.style.cursor = "pointer"; + const input = document.createElement("input"); + input.type = "file"; input.accept = ".xyz,.csv,.txt"; input.style.display = "none"; + label.appendChild(input); + row.append(tol, label); + host.appendChild(row); + const out = document.createElement("div"); host.appendChild(out); + root.appendChild(host); + + input.onchange = async () => { + const f = input.files?.[0]; const pid = ctx.projectId(); + if (!f) return; + if (!pid) { out.innerHTML = meta("Open a project first."); return; } + const t = Number(tol.value) || 0.05; + out.replaceChildren(); + out.innerHTML = meta("comparing the cloud to the model…"); + ctx.setStatus("scan deviation…"); + try { + const d = await ctx.api.scanDeviation(pid, f, t); + out.replaceChildren(); + renderDeviation(out, d); + ctx.setStatus(refused(d) ? "no deviation figure — see the reason" + : `${d.within_pct!.toFixed(1)}% within ${t} m`); + // The per-element pass runs whatever the aggregate said: when the aggregate REFUSED because + // the model reference was cut short, this is the route its own message points at. + out.insertAdjacentHTML("beforeend", meta("verifying per element…")); + const r = await ctx.api.scanVerifyLod500(pid, f, { tolerance: t, apply: false }); + out.lastElementChild?.remove(); + renderLod500(out, r); + ctx.setStatus(`${r.verified} verified · ${r.findings_count} finding(s) · ${r.uncovered} uncovered`); + } catch (e) { + out.insertAdjacentHTML("beforeend", meta(esc((e as Error).message), "var(--status-crit)")); + ctx.setStatus("scan verification failed"); + } finally { + input.value = ""; + } + }; + + return host; +} diff --git a/docs/roadmap.md b/docs/roadmap.md index ffab9560..11912bcc 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,38 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ ⭐ **SCAN-DARK — the refusal SCAN-TRUNC added pointed at a route frozen as unreachable** + *(S — Lane E/G; **CLOSED 2026-09-25**; gated by `apps/web/src/viewer/tools/scanVerifyView.test.ts`)* + + **This entry exists because of the previous one.** SCAN-TRUNC made `/scan/deviation` refuse when + the model reference was cut short, and its message says *"use `/scan/verify-lod500`, which queries + per element and never truncates the model."* That route had **no client method at all** and was + listed in `services/api/test_route_reachability.py` as deliberately clientless. *A refusal that + redirects to an unreachable feature is a gate you can read and cannot satisfy* — the AUTHORITY-DARK + shape, created here by my own wording one item earlier. + + And `ApiClient.scanDeviation`'s declared return type **omitted every field SCAN-TRUNC added** — + `reference_truncated`, `points_truncated`, `points_total`, `error` — so the refusal was invisible + to the client. The third instance of that hole this session, after `excluded_comparables` and + `roundtripDiff.truncated`. The engine also returned the two flags from only two of its three + branches, so they are emitted from all three now and the client type requires them. + + **A CLEAN NEGATIVE, measured before a line of UI was written.** Five engines this session carried + a verdict that went vacuous once nothing had been evaluated, so `verify_from_scan` was measured on + a scan covering nothing: `verified: 0, stamped: 0, uncovered: 50`, nothing stamped, and + `within_tolerance` is `null` rather than `false` on an uncovered element. **It fails closed and + needed no repair** — the card's only job is not to subtract that care. So `uncovered` is given the + same weight as `verified` (a high verified count over a thin scan is the number somebody quotes), + a finding renders as *verified as wrong, not stamped*, and the aggregate's refusal renders as a + refusal rather than as a zero. + + **The surviving mutation named a branch the fixtures did not have.** Keying `refused()` on + `reference_truncated` instead of on the missing figure passed every test, because every fixture + set the two together — and the engine has a THIRD refusal branch, an empty cloud or reference, + where `within_pct` is null and that flag is `false`. *Asserting the one case you thought of is not + asserting the property.* The predicate reads the absent figure, and both branches are fixtures now. + + - ✅ ⭐ **TRUNC-COUNTED — three screens printed the length of a page as if it were the total, and one of them ACTED on it** *(S — Lane C/G; **CLOSED 2026-09-25**; gated by `services/api/test_trunc_counted.py`)* diff --git a/services/api/src/aec_api/scan_deviation.py b/services/api/src/aec_api/scan_deviation.py index af1a6fa2..26411932 100644 --- a/services/api/src/aec_api/scan_deviation.py +++ b/services/api/src/aec_api/scan_deviation.py @@ -48,11 +48,15 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05, *, ref = np.asarray(reference, dtype=float).reshape(-1, 3) if len(pts) == 0 or len(ref) == 0: return {"point_count": int(len(pts)), "reference_count": int(len(ref)), + "points_total": int(points_total) if points_total is not None else int(len(pts)), + "points_truncated": False, "reference_truncated": bool(reference_truncated), "error": "empty point cloud or reference", "within_pct": None} if reference_truncated: return { "point_count": int(len(pts)), "reference_count": int(len(ref)), "tolerance": tolerance, + "points_total": int(points_total) if points_total is not None else int(len(pts)), + "points_truncated": points_total is not None and points_total > len(pts), "reference_truncated": True, "within_pct": None, "error": (f"the model reference was capped at {len(ref):,} surface vertices, so part of " "the model is absent from the comparison. Scan points near an omitted element " diff --git a/services/api/test_route_reachability.py b/services/api/test_route_reachability.py index c554622c..5bac9972 100644 --- a/services/api/test_route_reachability.py +++ b/services/api/test_route_reachability.py @@ -284,7 +284,6 @@ def check(label, ok, detail=""): "/projects/{pid}/provenance/admissibility", "/projects/{pid}/recipes/replay-plan", # `/rules/space-pack` left here in v0.3.1137 — Model Analysis reads the stored pack. - "/projects/{pid}/scan/verify-lod500", "/projects/{pid}/verified-progress/from-layout", # `/view-templates/{tid}/graphics` left here in v0.3.1138 — Model Analysis reads cut vs projection. From 850aa321b6b33bcb79229df0b03322518747b0cf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:04:11 +0000 Subject: [PATCH 16/21] PROGRESS-UNMATCHED: an engine that promised "never silently dropped" and dropped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit progress_rollup.capture_diff builds both of its sets by filtering through the model's element list: added = sorted(g for g in (s2 - s1) if g in known) removed = sorted(g for g in (s1 - s2) if g in known) while its note said: "Elements present at t1 but absent at t2 are surfaced as 'disappeared' -- a re-scan or rework flag, NEVER SILENTLY DROPPED." The promise held only among elements the CURRENT model still contains, which excludes exactly the case a rework flag exists for: something taken out of the model AND off the site between two captures. Measured, 500 capture GUIDs at t2 against a 300-element model: installed_t2 300 · newly_installed 300 · pct_complete_t2 1.0 200 dropped · no key naming them A note that claims more than the code does is worse than no note, because it is read as a guarantee and stops the next person looking. THE FILTER IS KEPT AND ONLY THE SILENCE IS FIXED. A diff is scoped to the model it is about, and an unmatched GUID carries no class or storey, so added_by_class and added_by_level have nowhere to put it -- removing the filter crashes the engine, which the gate proves by doing it. What changed is that unmatched_t1 and unmatched_t2 are reported, so a capture aimed at a different model version shows as an unmatched count instead of arriving as a quietly smaller diff that reads like slower progress. The note states the scope it has, and says nothing at all when everything matched -- a sweep that always speaks is one nobody reads. THE WIRING HALF IS NOT SMALL, AND ASSUMING IT WAS IS THE CORRECTION WORTH RECORDING. This was opened as another callerless-method item. It is not one: progressCaptureDiff AND progressRollup are both callerless, and nothing in the product produces an "installed GUIDs at time T" set -- so giving either a screen means building capture capture, not adding a card. Same shape as CITE-RECORD, where "give the record builder a producer" turned out to mean building record-aware QA. An item's size is a claim about its dependencies, and this one's was wrong until somebody went looking for the producer. Both methods stay in the callerless freeze list, deliberately. Eight mutations, all red. Two of them redded by CRASHING rather than by failing a named check, and the harness reported those as "NO VERDICT LINE" -- inconclusive, not passes -- so they were re-run with exit codes to confirm. One of the two was my own test indexing a deleted key: a bare KeyError IS a red, but it names Python instead of the property that broke, so that access is a .get() now and the same mutation reports two named failures. A check whose failure message can misdiagnose is worse than one that stays silent. Two clean negatives from the same sweep, recorded so nobody re-runs them: adjacency.summary returns total, satisfiable and the complete unmet list with no cap and no vacuous verdict; verify_from_scan fails closed on a scan that covered nothing. Half the engines measured in this sweep needed no repair, and saying so is what makes the other half credible. Verified: test_progress_unmatched, test_progress_rollup, test_verified_progress, test_schedule_progress, test_claude_md_gates, test_roadmap_status and test_gap_records green; 466 web shell/tooling tests green; ruff clean; registered in run_tests.py beside test_progress_rollup. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 11 ++ docs/roadmap.md | 39 ++++++ services/api/run_tests.py | 2 +- services/api/src/aec_api/progress_rollup.py | 22 +++- services/api/test_progress_unmatched.py | 129 ++++++++++++++++++++ 5 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 services/api/test_progress_unmatched.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b4925267..43e6c0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### PROGRESS-UNMATCHED — a capture diff now says how much of the capture it could not use + +`progress_rollup.capture_diff` filtered both its added and disappeared sets through the model's +element list while its note promised disappearances were "never silently dropped". The promise held +only among elements the current model still contains — which excludes the case the flag is for. + +Measured: 500 capture GUIDs at t2 against a 300-element model reported 300 installed, 100% complete, +and 200 dropped with no key naming them. `unmatched_t1`/`unmatched_t2` are now reported and the note +states the scope it actually has, so a capture aimed at a different model version is visible instead +of arriving as a quietly smaller diff. + ### SCAN-DARK — scan-to-BIM deviation and LOD 500 verification reach a screen SCAN-TRUNC's refusal tells the reader to use `/scan/verify-lod500` instead. That route had no client diff --git a/docs/roadmap.md b/docs/roadmap.md index 11912bcc..628ac698 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,45 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ **PROGRESS-UNMATCHED — an engine whose note promised "never silently dropped" and silently + dropped** *(XS — Lane C; **CLOSED 2026-09-25**; gated by + `services/api/test_progress_unmatched.py`)* + + `progress_rollup.capture_diff` builds both of its sets by filtering through the model's element + list — `added = [g for g in (s2 - s1) if g in known]`, and the same for `removed` — while its note + said: + + > Elements present at t1 but absent at t2 are surfaced as `disappeared` — a re-scan or rework + > flag, **never silently dropped**. + + **The promise held only among elements the CURRENT model still contains**, which excludes exactly + the case a rework flag exists for: something taken out of the model *and* off the site between two + captures. Measured — 500 capture GUIDs at t2 against a 300-element model: `installed_t2` 300, + `newly_installed` 300, `pct_complete_t2` **1.0**, 200 dropped, and no key anywhere naming them. + *A note that claims more than the code does is worse than no note, because it is read as a + guarantee and stops the next person looking.* + + **The filter is kept and only the silence is fixed.** A diff is scoped to the model it is about, + and an unmatched GUID carries no class or storey, so `added_by_class` and `added_by_level` have + nowhere to put it — removing the filter crashes the engine, which the gate proves by doing it. + What changed is that `unmatched_t1`/`unmatched_t2` are reported, so a capture aimed at a different + model version shows as an unmatched count instead of arriving as a quietly smaller diff that reads + like slower progress. The note now states the scope it has. + + **THE WIRING HALF IS NOT SMALL, and assuming it was is the correction worth recording.** This was + opened as another callerless-method item. It is not one: `progressCaptureDiff` *and* + `progressRollup` are both callerless, and **nothing in the product produces an "installed GUIDs at + time T" set** — so giving either a screen means building capture capture, not adding a card. Same + shape as CITE-RECORD, where "give the record builder a producer" turned out to mean building + record-aware QA. *An item's size is a claim about its dependencies, and this one's was wrong until + somebody looked for the producer.* Both methods stay in the callerless freeze list, deliberately. + + Two clean negatives from the same sweep, recorded so nobody re-runs them: `adjacency.summary` + returns `total`, `satisfiable` and the complete `unmet` list with no cap and no vacuous verdict; + `scan_deviation.verify_from_scan` fails closed on a scan that covered nothing. **Half the engines + measured in this sweep needed no repair**, and saying so is what makes the other half credible. + + - ✅ ⭐ **SCAN-DARK — the refusal SCAN-TRUNC added pointed at a route frozen as unreachable** *(S — Lane E/G; **CLOSED 2026-09-25**; gated by `apps/web/src/viewer/tools/scanVerifyView.test.ts`)* diff --git a/services/api/run_tests.py b/services/api/run_tests.py index c1674edf..53257d99 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -86,7 +86,7 @@ "test_markup", "test_route_authz", "test_resource_id_authz", "test_route_reachability", "test_route_shadow", "test_body_param_reach", "test_resumable_upload", "test_model_align", "test_ifc_parse_gate", "test_plugin_isolation", "test_body_pid_authz", "test_global_authz", "test_protected_prefix_coverage", "test_baseline", "test_global_mutating_authz", "test_ref_counter", "test_audit_coverage", "test_bsdd", "test_openbim_registry", "test_waterfall", "test_waterfall_cents", "test_sessions", "test_mfa", "test_stored_ids", "test_cobie", "test_fts_index", "test_scim", "test_scim_provision_race", "test_natural_key_race", "test_seeding_sweep", "test_unique_read_guard", "test_member_role_race", "test_bcf_reimport_dedupe", "test_model_version_collision", "test_saml", "test_responsibility", "test_audit_commit", "test_array_live", "test_assemblies", "test_dxf_takeoff", "test_qto_class_match", "test_georef", "test_scene_package", "test_clash_bvh", "test_model_qa", "test_model_health", "test_roundtrip_qa", "test_stakeholder", "test_prioritization", "test_ai_readiness", - "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_scan_trunc", "test_trunc_counted", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", + "test_scan_deviation", "test_plan_to_bim", "test_errorlog", "test_import_cycles", "test_tenant_scoping", "test_schedule_risk_single", "test_carbon_compliance", "test_permit_check", "test_drawing_qa", "test_element_5d", "test_authoring_matrix", "test_recipe_reach", "test_option_missing", "test_option_score", "test_plugin_registry", "test_jobs", "test_clash_federated_job", "test_inbox_jobs", "test_job_kind_labels", "test_worker_split", "test_job_orphan_scope", "test_job_stall", "test_pid_lock_xproc", "test_pid_lock_pgxproc", "test_pid_lock_bound", "test_pid_lock_surface", "test_sheet_layout", "test_dim_component", "test_sheet_recover", "test_firm_standards", "test_site_context", "test_risk_board", "test_risk_portfolio", "test_env_wind", "test_model_options", "test_doc_text", "test_escalation", "test_query_dsl", "test_rule_library", "test_schedule_baselines", "test_model_ci", "test_xlsx_roundtrip", "test_geometric_rules", "test_rebar_rules", "test_cx", "test_distwaterfall", "test_cap_table_state", "test_license_cloud", "test_smart_views", "test_view_delete", "test_version_approve_identity", "test_upload_streaming", "test_lod_aspects", "test_lod_element_table", "test_publish_reconvert", "test_model_cache_seed", "test_model_cache_mutation", "test_mutating_readers", "test_adopt_guid", "test_ifcpatch", "test_bcf_api", "test_coordination_fresh", "test_assemblies_cost", "test_fem_export", "test_subset_export", "test_norm_valid", "test_schema_diag", "test_revision_delta", "test_bep", "test_pm_close", "test_itp", "test_quality_chain", "test_quality_chain_route", "test_meeting_links", "test_est_bands", "test_scope_gap", "test_golden_thread", "test_clash_xml_import", "test_gis_out", "test_cbs", "test_mep_graph", "test_model_warnings", "test_schedule_options", "test_master_builder", "test_master_builder_scope", "test_get_commits", "test_project_pulse", "test_client_portal", "test_selections", "test_margin", "test_model_assets", "test_macros", "test_layout_options", "test_equipment", "test_space_util", "test_design_metrics", "test_mep_fittings", "test_prod_actuals", "test_pipeline_allocate", "test_resource_portfolio", "test_production", "test_procure_level", "test_adjacency", "test_supply_chain", "test_invisible_unicode", "test_cited_answer", "test_est_confidence", "test_buyout_schedule", "test_scope_register", "test_permit_timeline", "test_absorption", "test_progress_rollup", "test_progress_unmatched", "test_fill_matrix", "test_parcel_geometry", "test_assembly_thermal", "test_portal_txn", "test_persona_answer", "test_boe_ledger", "test_assumption_provenance", "test_assumption_provenance_route", "test_concept_budget", "test_topic_board", "test_roof_window", "test_topic_lifecycle", "test_comment_promote", "test_artifact_deliver", "test_calc_fields", "test_constraints", "test_element_lookup", "test_cli", "test_view_templates", "test_type_catalogs", "test_password_policy", "test_stepup_single_verifier", "test_fin_gov", "test_fin_calc", "test_fin_ingest", "test_fin_portfolio", "test_level_move", "test_instance_props", "test_roundtrip", "test_wall_joins", "test_composite_family", "test_shared_params", "test_version_values", "test_ifcpatch_transforms", "test_bcf3", "test_energy_export", "test_net_effective", "test_cre_deal_desk", "test_cre_governance", "test_cre_tier3", "test_family_geometry", "test_demo_seed", "test_cost_spine", "test_commercial_drift", "test_family_shapes", "test_workflow_config", "test_option_takeoff", "test_option_carbon", "test_option_carbon_route", "test_option_economics", "test_option_economics_route", "test_option_object", "test_option_object_route", "test_family_coverage", "test_section_annotation", "test_lod500_readiness", "test_scan_to_lod500", "test_egress_routes", "test_status_workflow_parity", "test_section_hatch", "test_section_keynotes", "test_detail_refs", "test_vg_overrides", "test_revit_export_cfg", "test_soft_clash", "test_clash_trunc", "test_sequence_clash", "test_scan_trunc", "test_trunc_counted", "test_element_tags", "test_cost_ifc", "test_fived", "test_health_consistency", "test_module_rooms", "test_modules_response_complete", "test_lifecycle_strip", "test_family_merge", "test_element_facts", "test_consistency", "test_work_queue", "test_task_bind", "test_qto_wire", "test_estimate_diff", "test_dim_constraints", "test_sov_build", "test_takeoff_scope", "test_r37_wire_routes", "test_r37_consolidate", "test_r37_contract", "test_export_promises", "test_pdf_ingest_gate", "test_roadmap_status", "test_claim_type", "test_risk_calibrate", "test_schedule_status", "test_engine_routes", "test_reachable", "test_money_wire", "test_license_gate", "test_license_lock_gate", "test_lock_advisories", "test_npm_advisories", "test_perf_budget", "test_perf_rate", "test_cache_key", "test_oauth_providers", "test_qto_measured_area", "test_lod_census", "test_lod_proxy", "test_model_ensure", "test_support_graph", "test_export_colour_stable", "test_stair_ramp", "test_profile_dims", "test_eot", "test_eot_methods", "test_eot_sourced", "test_shared_model", "test_plan_identity", "test_axon_view", "test_view_kind_dispatch", "test_photo_cv", "test_photo_detect", "test_photo_duplicate", "test_pipeline_scales", "test_plan_pins", "test_plan_cut_quality", "test_pins_unified", "test_index_freshness", "test_bake_budget", "test_geom_slots", "test_bake_shared", "test_geo_ref", "test_asset_verify", "test_folder_owner", "test_file_sizes", "test_declared_imports", "test_ruff_scope", "test_delete_ratchet", "test_doc_substance", "test_claude_md_gates", "test_db_url_isolation", "test_desktop_paths", "test_frozen_paths", "test_cors_expose_headers", "test_open_redirect", "test_mp_engine", "test_upload_cap", "test_vitals", "test_samples", "test_bundle_index", # R41-TEST-RESIDUE — the residue sweep must never propose a database it does not own: "test_sweep_guard", # R23-DIGEST — the deterministic model digest and its two routes: diff --git a/services/api/src/aec_api/progress_rollup.py b/services/api/src/aec_api/progress_rollup.py index 1019ae56..a9da8c9f 100644 --- a/services/api/src/aec_api/progress_rollup.py +++ b/services/api/src/aec_api/progress_rollup.py @@ -66,6 +66,15 @@ def capture_diff(elements: list[dict], installed_t1: list | set, installed_t2: l known = {str(el.get("guid") or el.get("GlobalId") or ""): el for el in elements or [] if isinstance(el, dict)} added = sorted(g for g in (s2 - s1) if g in known) removed = sorted(g for g in (s1 - s2) if g in known) + # EVERY capture GUID THE MODEL DOES NOT KNOW is dropped from both sets above, and the note used + # to promise the opposite: "never silently dropped". It was true only among elements the CURRENT + # model still contains — which excludes exactly the case a rework flag is for, an element taken + # out of the model and off the site between captures. The filter stays (a diff is scoped to the + # model it is about, and a GUID with no element has no class or storey to group by), but the + # count no longer does: unmatched is reported, so a capture aimed at the wrong model version is + # visible instead of arriving as a quietly smaller diff. + unmatched_t1 = sorted(g for g in s1 if g not in known) + unmatched_t2 = sorted(g for g in s2 if g not in known) def _grp(guids: list[str], key: str, fallback: str) -> list[dict]: counts: dict[str, int] = {} @@ -87,14 +96,21 @@ def _grp(guids: list[str], key: str, fallback: str) -> list[dict]: "installed_t1": len(s1 & set(known)), "installed_t2": len(s2 & set(known)), "newly_installed": len(added), "disappeared": len(removed), "added_guids": added[:200], "disappeared_guids": removed[:200], + "unmatched_t1": len(unmatched_t1), "unmatched_t2": len(unmatched_t2), + "unmatched_guids": sorted(set(unmatched_t1) | set(unmatched_t2))[:200], "added_by_class": _grp(added, "ifc_class", "Unclassified"), "added_by_level": _grp(added, "storey", "—"), "pct_complete_t1": r1["pct_complete"], "pct_complete_t2": r2["pct_complete"], "pct_delta": round(r2["pct_complete"] - r1["pct_complete"], 3), "elements_per_day": round(len(added) / days, 2) if days and days > 0 else None, - "note": "Capture-to-capture change log: newly installed per class/level + the progress delta (+ a " - "daily rate when dates are given). Elements present at t1 but absent at t2 are surfaced as " - "'disappeared' — a re-scan or rework flag, never silently dropped.", + "note": ("Capture-to-capture change log: newly installed per class/level + the progress delta " + "(+ a daily rate when dates are given). An element present at t1 and absent at t2 is " + "surfaced as 'disappeared' — a re-scan or rework flag. BOTH sets cover only GUIDs the " + "model still contains; anything else is counted in `unmatched_t1`/`unmatched_t2` " + "rather than dropped, because a capture aimed at a different model version otherwise " + "arrives as a quietly smaller diff." + + (f" {len(unmatched_t1) + len(unmatched_t2)} capture GUID(s) matched no element." + if (unmatched_t1 or unmatched_t2) else "")), } diff --git a/services/api/test_progress_unmatched.py b/services/api/test_progress_unmatched.py new file mode 100644 index 00000000..ab61bbae --- /dev/null +++ b/services/api/test_progress_unmatched.py @@ -0,0 +1,129 @@ +"""PROGRESS-DARK — does a capture diff say how much of the capture it could not use? + +`progress_rollup.capture_diff` builds both of its sets by filtering through the model's element +list: + + added = sorted(g for g in (s2 - s1) if g in known) + removed = sorted(g for g in (s1 - s2) if g in known) + +and its note promised the opposite of what that does: + + > Elements present at t1 but absent at t2 are surfaced as 'disappeared' — a re-scan or rework + > flag, **never silently dropped**. + +**The promise held only among elements the CURRENT model still contains**, which excludes exactly +the case a rework flag is for: something taken out of the model *and* off the site between captures. +Measured before the fix — 500 capture GUIDs at t2 against a 300-element model: + + installed_t2 300 · newly_installed 300 · pct_complete_t2 1.0 · 200 dropped · no key naming them + +*A note that claims more than the code does is worse than no note*, because it is read as a +guarantee and stops the next person looking. + +**The filter is kept and only the silence is fixed.** A diff is scoped to the model it is about, and +a GUID with no element carries no class or storey to group by — so `added_by_class` and +`added_by_level` have nothing to put it in. What changed is that the count is reported, so a capture +aimed at a different model version shows up as an unmatched count instead of arriving as a quietly +smaller diff that reads like slower progress. + +The caps beside it were ALREADY the good pattern and are left alone: `added_guids[:200]` sits next to +`newly_installed`, which is the true total. `unmatched_guids` follows the same shape. +""" +from __future__ import annotations + +import os +import sys + +os.environ.setdefault("DATABASE_URL", "sqlite:///./test_progress_unmatched.db") +os.environ.setdefault("STORAGE_DIR", "./test_storage_progress_unmatched") +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) + +import inspect # noqa: E402 + +from aec_api import progress_rollup as pr # noqa: E402 + +FAILURES: list[str] = [] + + +def check(name: str, ok: bool, detail: str = "") -> None: + print(f"{'PASS' if ok else 'FAIL'} {name}{(' ' + detail) if detail else ''}") + if not ok: + FAILURES.append(name) + + +MODEL = [{"guid": f"E{i}", "ifc_class": "IfcWall", "storey": "L1"} for i in range(300)] + +# ------------------------------------------------------------------------------------------------ +# THE CLAIM, REPRODUCED. A capture naming 500 installed elements against a 300-element model. +# ------------------------------------------------------------------------------------------------ +t2 = [f"E{i}" for i in range(300)] + [f"UNKNOWN{i}" for i in range(200)] +r = pr.capture_diff(MODEL, [], t2, "2026-01-01", "2026-02-01") + +check("PRECONDITION: the diff ran and counted the known elements", + r["installed_t2"] == 300 and r["newly_installed"] == 300, + f"installed_t2={r['installed_t2']} newly_installed={r['newly_installed']}") +check("the 200 capture GUIDs with no element are COUNTED, not dropped", + r["unmatched_t2"] == 200, f"unmatched_t2={r['unmatched_t2']}") +#: `.get()`, not `[...]`. Deleting the key made this file die with a bare KeyError — which IS a +#: red, but one that names Python instead of the property that broke. A check whose failure message +#: can misdiagnose is worse than one that stays silent, because somebody acts on it. +_listed = r.get("unmatched_guids") +check("...and are listed, capped like every other page here", + isinstance(_listed, list) and 0 < len(_listed) <= 200, + f"{len(_listed) if isinstance(_listed, list) else _listed!r} listed of {r['unmatched_t2']}") +check("...and the note says so in numbers rather than in general", + "200 capture GUID(s) matched no element" in r["note"]) + +# The t1 side is the half the old note actually named, and it had the same hole. +r_t1 = pr.capture_diff(MODEL, ["E1", "GHOST1", "GHOST2"], ["E1"]) +check("an UNMATCHED GUID vanishing between captures is counted on the t1 side too", + r_t1["unmatched_t1"] == 2, f"unmatched_t1={r_t1['unmatched_t1']}") +check("...while a KNOWN element vanishing is still a 'disappeared', as it always was", + pr.capture_diff(MODEL, ["E1", "E2"], ["E1"])["disappeared"] == 1) + +# ------------------------------------------------------------------------------------------------ +# SILENCE MUST MEAN SOMETHING. A sweep that always speaks is a sweep nobody reads. +# ------------------------------------------------------------------------------------------------ +clean = pr.capture_diff(MODEL, [], [f"E{i}" for i in range(300)]) +check("a capture that matches entirely reports zero unmatched", + clean["unmatched_t1"] == 0 and clean["unmatched_t2"] == 0) +check("...and the note adds no count sentence at all", + "matched no element" not in clean["note"]) +check("...and lists no unmatched GUIDs", + clean.get("unmatched_guids") == []) + +# ------------------------------------------------------------------------------------------------ +# THE NOTE MUST NOT RE-OVERCLAIM. This is the defect that was in the prose rather than the code, so +# the prose is what is pinned: the old sentence promised a property the filter cannot deliver. +# ------------------------------------------------------------------------------------------------ +check("the note no longer claims disappearances are NEVER silently dropped", + "never silently dropped" not in clean["note"], + "the filter cannot deliver that, and the sentence stopped the next reader looking") +check("...and states the scope it actually has", + "only GUIDs the model still contains" in clean["note"]) + +# ------------------------------------------------------------------------------------------------ +# THE GOOD PATTERN BESIDE IT IS UNTOUCHED — a page with its true total. +# ------------------------------------------------------------------------------------------------ +big = pr.capture_diff(MODEL, [], [f"E{i}" for i in range(300)]) +check("added_guids is a page and newly_installed is the total", + len(big["added_guids"]) == 200 and big["newly_installed"] == 300, + f"{len(big['added_guids'])} listed of {big['newly_installed']}") + +# ------------------------------------------------------------------------------------------------ +# PRECONDITION: the filter this file reasons about is still the one in the code. If someone removes +# it, unmatched GUIDs would flow into added_by_class with no class — a different bug, and these +# assertions would be measuring nothing. +# ------------------------------------------------------------------------------------------------ +src = inspect.getsource(pr.capture_diff) +check("PRECONDITION: both sets are still filtered through the model's elements", + src.count("if g in known") == 2, f"{src.count('if g in known')} filter(s)") +check("PRECONDITION: unmatched is derived from the SAME membership test", + "g not in known" in src) + +print() +if FAILURES: + print(f"progress_unmatched: {len(FAILURES)} FAILED — {FAILURES}") + sys.exit(1) +print("progress_unmatched: all checks passed — a capture diff reports the GUIDs it could not use, " + "and its note claims only the scope the filter gives it.") From 5abcc1c3a550c9fb1c93e9894432fc92b2748f19 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:09:42 +0000 Subject: [PATCH 17/21] SCOPE-EMPTY: an empty scope register and one with nothing done were the same numbers scope_register.register returned pct_quantified / pct_allocated / pct_scheduled as 0.0 when there were no items. Measured, and the two responses are indistinguishable on every number a card would show: an empty register item_count 0 0.0 / 0.0 / 0.0 one item, nothing done item_count 1 0.0 / 0.0 / 0.0 Those are opposite findings -- go write the register, versus go do the work -- and only item_count separated them, which a percentage-led card need not render. Same shape as T12-SELFTIE, where the two responses were likewise byte-identical and the discriminator had to become a parameter of the renderer. THE REPAIR IS NOT A JUDGEMENT CALL, BECAUSE THE ANSWER WAS ALREADY IN THE TREE. spine.traceability returns None for an empty population, and apps/web/src/api/coverageMaps.ts names the two engines side by side as THE two completeness mappers, each answering "what proportion of these records carry the link they need". Two engines documented as answering the same question disagreed about how to say "no population", and one of the two spellings cannot be told from a real measurement. AN EXISTING TEST ASSERTED THE DEFECT, and that is the part worth recording. test_scope_register had `assert e["pct_quantified"] == 0.0` on the empty case: a VALUE standing in for a property it never stated. Changing a test to match new behaviour is only legitimate when the old assertion encoded the bug, and saying which it was is the whole of the justification. It now asserts the property -- the three are None, a real 0% is still 0.0, and the two cannot be equal. Five mutations, all red, in BOTH directions: each percentage reverting to 0.0 on empty, and the over-correction that turns a genuine 0% into None. The over-correction matters because the narrow fix and the sloppy one are indistinguishable on the empty case alone. WIRING scopeRegister IS A FEATURE, NOT A CARD. The route takes scope_items, qto_lines and activities entirely from the request body and reads nothing from the database, so a caller has to assemble three datasets first. Unlike PROGRESS-UNMATCHED the producers do exist, so this is buildable rather than blocked -- it is simply not extra-small, and the method stays in the callerless freeze list until somebody sizes it honestly rather than assuming. Verified: test_scope_register, test_scope_gap, test_cbs, test_takeoff_scope, test_claude_md_gates, test_roadmap_status, test_gap_records, test_file_sizes, test_ruff_scope and test_dead_code_population all green; ruff clean; 466 web shell/tooling tests green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 11 +++++++ docs/roadmap.md | 37 ++++++++++++++++++++++ services/api/src/aec_api/scope_register.py | 12 +++++-- services/api/test_scope_register.py | 16 ++++++++-- 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43e6c0a2..ebc64c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### SCOPE-EMPTY — an empty scope register no longer reads as one where nothing was done + +`scope_register.register` returned `0.0` for all three coverage percentages on an empty register — +byte-identical to a register holding one item with nothing quantified, allocated or scheduled. Those +are opposite findings, and only `item_count` separated them. + +`spine.traceability`, named beside this engine in `apps/web/src/api/coverageMaps.ts` as the other +completeness mapper, has always returned `None` for an empty population. The two now agree. The +existing test asserted the old value, so it now asserts the property that value stood in for: the +three are `None`, a real 0% is still `0.0`, and the two cannot be confused. + ### PROGRESS-UNMATCHED — a capture diff now says how much of the capture it could not use `progress_rollup.capture_diff` filtered both its added and disappeared sets through the model's diff --git a/docs/roadmap.md b/docs/roadmap.md index 628ac698..84edeb6e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,43 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ **SCOPE-EMPTY — an empty scope register and one with nothing done were the same three numbers** + *(XS — Lane C; **CLOSED 2026-09-25**; held by `services/api/test_scope_register.py`)* + + `scope_register.register` returned `pct_quantified` / `pct_allocated` / `pct_scheduled` as `0.0` + when there were no items. Measured, and the two responses are **indistinguishable on every number + a card would show**: + + | | `item_count` | the three percentages | + |---|---|---| + | an empty register | 0 | **0.0 / 0.0 / 0.0** | + | one item, nothing done | 1 | **0.0 / 0.0 / 0.0** | + + Those are opposite findings — *go write the register* versus *go do the work* — and only + `item_count` separated them, which a percentage-led card need not render. Same shape as + T12-SELFTIE, where the two responses were likewise byte-identical. + + **The repair is not a judgement call, because the answer was already in the tree.** + `spine.traceability` returns `None` for an empty population, and + `apps/web/src/api/coverageMaps.ts` names the two engines side by side as *the* two completeness + mappers, each answering "what proportion of these records carry the link they need". **Two engines + documented as answering the same question disagreed about how to say 'no population'**, and one of + the two spellings cannot be told from a real measurement. + + **An existing test asserted the defect**, and that is the part worth recording. `test_scope_register` + had `assert e["pct_quantified"] == 0.0` on the empty case — a VALUE standing in for a property it + did not state. It is now the property: the three are `None`, a real 0% is still `0.0`, and the two + cannot be equal. *Changing a test to match new behaviour is only legitimate when the old assertion + encoded the bug, and saying which it was is the whole of the justification.* Five mutations, all + red, including the over-correction that turns a genuine 0% into `None`. + + **Wiring `scopeRegister` is a feature, not a card** — the route takes `scope_items`, `qto_lines` + and `activities` entirely from the request body and reads nothing from the database, so a caller + has to assemble three datasets first. Unlike PROGRESS-UNMATCHED the producers do exist, so this is + buildable rather than blocked; it is simply not extra-small, and the method stays in the callerless + freeze list until somebody sizes it honestly. + + - ✅ **PROGRESS-UNMATCHED — an engine whose note promised "never silently dropped" and silently dropped** *(XS — Lane C; **CLOSED 2026-09-25**; gated by `services/api/test_progress_unmatched.py`)* diff --git a/services/api/src/aec_api/scope_register.py b/services/api/src/aec_api/scope_register.py index 363cc5da..04b93557 100644 --- a/services/api/src/aec_api/scope_register.py +++ b/services/api/src/aec_api/scope_register.py @@ -115,9 +115,15 @@ def register(scope_items: list[dict], qto_lines: list[dict] | None = None, rows.sort(key=lambda r: (r["status"] == "complete", -(r["value"] or 0))) # gaps first, highest-value first return { "item_count": n, "complete": complete, "with_gaps": n - complete, - "pct_quantified": round(n_quant / n, 3) if n else 0.0, - "pct_allocated": round(n_alloc / n, 3) if n else 0.0, - "pct_scheduled": round(n_sched / n, 3) if n else 0.0, + # `None`, not 0.0, on an empty register. `0.0` reads as "nothing is quantified" and is + # BYTE-IDENTICAL to a register holding one item with nothing done — opposite findings: one + # says go write the register, the other says go do the work. `spine.traceability`, named + # beside this engine in `apps/web/src/api/coverageMaps.ts` as the other completeness mapper, + # has always returned None here; two engines documented as answering the same question + # disagreed about how to say "no population". + "pct_quantified": round(n_quant / n, 3) if n else None, + "pct_allocated": round(n_alloc / n, 3) if n else None, + "pct_scheduled": round(n_sched / n, 3) if n else None, "total_value": round(total_value, 2), "by_owner": sorted(({"owner": k, "value": round(v, 2)} for k, v in by_owner.items()), key=lambda r: -r["value"]), diff --git a/services/api/test_scope_register.py b/services/api/test_scope_register.py index a91b0335..cd3d993b 100644 --- a/services/api/test_scope_register.py +++ b/services/api/test_scope_register.py @@ -30,9 +30,21 @@ assert r["items"][0]["id"] == "s2", r["items"] # highest-value gap first assert r["by_owner"][0]["owner"] == "ABC Concrete" and r["by_owner"][0]["value"] == 50000, r["by_owner"] -# empty input is well-formed +# empty input is well-formed — and DISTINGUISHABLE from a register with nothing done. +# +# This line used to assert `pct_quantified == 0.0` on an empty register, which is the defect it was +# written to protect against: 0.0 is byte-identical to a register holding one item that is +# unquantified, and those are opposite findings — "go write the register" versus "go do the work". +# `spine.traceability`, named beside this engine in apps/web/src/api/coverageMaps.ts as the other +# completeness mapper, has always returned None for an empty population; the two disagreed. The +# assertion now pins the PROPERTY the old value was standing in for. e = sr.register([]) -assert e["item_count"] == 0 and e["pct_quantified"] == 0.0 and e["total_value"] == 0.0, e +assert e["item_count"] == 0 and e["total_value"] == 0.0, e +assert e["pct_quantified"] is None and e["pct_allocated"] is None and e["pct_scheduled"] is None, e + +_one = sr.register([{"ref": "S-1", "description": "Slab on grade", "cost_code": "03-30-00"}]) +assert _one["pct_quantified"] == 0.0, _one # a real 0% is still a real number +assert e["pct_quantified"] != _one["pct_quantified"], (e, _one) # ...and the two cannot be confused # --- route: 404 missing project; 200 otherwise ----------------------------------------------------- if os.path.exists("./test_scope_register.db"): From 7e1c0d0593821458eb3a89c06510af78a0dbda34 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:11:35 +0000 Subject: [PATCH 18/21] DEGENERATE-SWEEP: the ledger, so the eight clean results are not re-derived Every item from RENTROLL-DARK onward began the same way: call the engine with nothing to evaluate, BEFORE writing a line of UI. Not reading the code -- calling it. Seventeen engines were measured that way across this session. Nine carried a defect. Eight were clean. The clean ones are recorded because an unrecorded negative gets re-derived by the next person, and because a sweep that only ever finds things is one nobody should trust. decision_gate attaches coverage to the row it belongs to; deal_authority blocks on all three required fact types; verify_from_scan stamps nothing and says absence is not a pass; adjacency.summary returns total, satisfiable and the full unmet list uncapped; and the four self-supplying endpoints probed last -- ci/latest, doc-graph, drawings/sync-status, client-decisions -- were all clean against an actually-empty project. ci/latest is the nicest of them: overall "none", badge "NONE", "No CI run yet." It refuses to let no-run read as passed, which is the whole thesis of this sweep, already implemented. That is how the axis ENDED rather than being abandoned: the last four probes came back clean. THIS IS A SNAPSHOT AND DELIBERATELY NOT A GATE, which this roadmap's own rule makes a claim needing justification -- "a sweep held as prose is a check that can only report good news". The mechanically derivable part of the population ALREADY is a gate: test_verdict_coverage derives boolean subset verdicts structurally, with no exemption list. What is left over is not derivable. `band: "undersupplied"` is a string, `0.0` is a float, and "would a reader mistake this for a measurement" is a judgement -- and a rule that needs judgement calls needs an exemption list, which is where the next instance hides. TRUNC-COUNTED paid for that lesson twice in one afternoon. So the ledger says what was measured and when, and the next sweep starts by re-measuring rather than by trusting the table. Verified: test_claude_md_gates, test_roadmap_status and test_gap_records green; 466 web shell/tooling tests green. Probe databases removed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- docs/roadmap.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/roadmap.md b/docs/roadmap.md index 84edeb6e..e8a2ab64 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,49 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ◧ **DEGENERATE-SWEEP — the ledger of what was measured, so the negative half is not re-run** + *(the sweep behind nine of this session's items; a SNAPSHOT, not a gate — see the last paragraph)* + + Every item from RENTROLL-DARK onward began the same way: **call the engine with nothing to + evaluate, before writing a line of UI.** Not reading the code — calling it. Seventeen engines were + measured that way. **Nine carried a defect, eight were clean**, and the clean ones are listed here + because an unrecorded negative gets re-derived by the next person. + + | engine | what it returned when nothing could be evaluated | + |---|---| + | `rent_scrub` | `clean: true` with 1 of 7 checks run — **defect** | + | `covenants` | `at_risk: false`, every count zero for want of inputs — **defect** | + | `t12.tie_out` | `reconciles: true` against a reference derived from the answer — **defect** | + | `sequence_clash` | `clean` over whatever fraction had dates and a location — **defect** | + | `soft_clash` (federated caller) | `clean` over a truncated page — **defect**, CLASH-TRUNC | + | `supply_pipeline` | `band: "undersupplied"`, the most favourable verdict there is — **defect** | + | `scan_deviation.analyze` | a correct building at 50% out of tolerance, max 50 m — **defect** | + | `progress_rollup.capture_diff` | 200 capture GUIDs dropped, note promising otherwise — **defect** | + | `scope_register.register` | `0.0` percentages, indistinguishable from nothing-done — **defect** | + | `decision_gate.evaluate` | `unknown` blocks; coverage attached to the row — **clean** | + | `deal_authority.assess` | blocks on all three required fact types — **clean** | + | `scan_deviation.verify_from_scan` | `verified: 0`, stamps nothing, *absence is not a pass* — **clean** | + | `adjacency.summary` | `total`, `satisfiable` and the full `unmet` list, uncapped — **clean** | + | `ci/latest` | `overall: "none"`, `badge: "NONE"`, *"No CI run yet."* — **clean** | + | `doc-graph` | 409 with the reason — **clean** | + | `drawings/sync-status` | `model_loaded: false`, `elements: 0`, with a note — **clean** | + | `client-decisions` | `{decisions: []}` — no verdict to go vacuous — **clean** | + + **The clean ones are not a rounding error, they are the argument.** *A sweep that only ever finds + things is one nobody should trust* — and the four self-supplying endpoints probed last were clean + on an actually-empty project, which is how this axis ended rather than being abandoned. + + **This is a SNAPSHOT and deliberately not a gate**, which the roadmap's own rule makes a claim + needing justification: *a sweep held as prose is a check that can only report good news.* The + mechanically derivable part of this population **already is** a gate — + `services/api/test_verdict_coverage.py` derives boolean subset verdicts structurally, with no + exemption list. What is left over is not derivable: `band: "undersupplied"` is a string, `0.0` is a + float, and "would a reader mistake this for a measurement" is a judgement. **A rule that needs + judgement calls needs an exemption list, which is where the next instance hides** — the lesson + TRUNC-COUNTED paid for twice. So the ledger says what was measured and when, and the next sweep + starts by re-measuring rather than by trusting this table. + + - ✅ **SCOPE-EMPTY — an empty scope register and one with nothing done were the same three numbers** *(XS — Lane C; **CLOSED 2026-09-25**; held by `services/api/test_scope_register.py`)* From 9db199b371c19e9417905ba459653e3d94c88f85 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:21:57 +0000 Subject: [PATCH 19/21] CI-LATEST-DARK: the badge is stored so it need not be recomputed, and recomputing it was the only way to see it ciRun had a caller; ciLatest did not. The MODEL-CI pack -- rule library, data completeness, clash, pinned IDS, quantity drift -- PERSISTS its report, and the tool's own footnote says why: "the badge is stored so every model version carries a quality gate." Opening the tool ran the whole pack again. A result persisted specifically so it need not be recomputed, reachable only by recomputing it. The tool now opens on the stored report with a Run-again action beside it; an unrun project gets "Run the check pack" instead. WIRING IT HANDED THE RENDERER A STATE ciRun CAN NEVER PRODUCE, and it could not describe it. model_ci.latest returns {overall: "none", badge: "NONE", checks: [], note: "No CI run yet."}, and the inline line ${r.passed ?? 0}/${r.total_checks ?? r.checks.length} passed renders that as "0/0 passed" -- a score, for something never scored. A renderer is only as sound as the states it has been handed, and while ciRun was its only source it had never been handed this one. isUnrun is a branch rather than a caveat: no score, the engine's own sentence, a warn colour rather than a pass. The ENGINE was clean before any of this and is in the DEGENERATE-SWEEP ledger as such -- it already refuses to let a no-run read as a pass. The defect was entirely in what could reach it. THE SURVIVING MUTATION NAMED A THIRD STATE. Reducing isUnrun to !r.checks.length passed all eleven tests, because no fixture had an empty `checks` on a report that had actually RUN -- a run with nothing applicable to check. "Run it" and "it ran and found nothing to check" are different findings, and conflating them is this session's own theme one level down. Both are fixtures now, and isUnrun keyed on either field alone reds. Extracted to modelCiView.ts rather than grown in place: qaSection.ts is under a down-only size ratchet and had eight lines of headroom. 1270 of 1274. ALSO FIXES A COMMIT I PUSHED RED. 7e1c0d05 added DEGENERATE-SWEEP to the roadmap marked open, which roadmapLanes.test.ts refuses unless an open item is in a lane or explicitly Parked. I ran that suite for the ledger commit and tailed three lines of output, which showed the DURATION and not the verdict -- I read a timing and called it a pass. The sweep is closed rather than parked, because the axis ended (the last four probes came back clean) and a future sweep is new work, not the remainder of this one. Verified: web 274 files / 2,999 tests green -- the whole suite this time, with the verdict line read, not the tail; typecheck, eslint and production build clean; test_claude_md_gates, test_roadmap_status, test_gap_records, test_file_sizes and test_model_ci green; 12 tests and 11 mutations on the new module, the survivor among them re-killed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 12 ++ apps/web/src/api/clientCallers.test.ts | 2 +- apps/web/src/viewer/tools/modelCiView.test.ts | 124 ++++++++++++++++++ apps/web/src/viewer/tools/modelCiView.ts | 66 ++++++++++ apps/web/src/viewer/tools/qaSection.ts | 28 ++-- docs/roadmap.md | 34 ++++- 6 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 apps/web/src/viewer/tools/modelCiView.test.ts create mode 100644 apps/web/src/viewer/tools/modelCiView.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc64c3d..3e662032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### CI-LATEST-DARK — the Model CI tool opens on the stored badge instead of re-running the pack + +`ciRun` was wired and `ciLatest` was not, so the persisted model-CI report — stored precisely so the +badge survives without recomputation — could only be seen by running the whole pack again (rule +library, completeness, clash, IDS, quantity drift). + +The tool now opens on the stored report with **⟳ Run again** beside it. Wiring it exposed a state +`ciRun` can never return: a project that has never run CI, which the old renderer would have shown as +**"0/0 passed"** — a score, for something never scored. It now shows the engine's own "No CI run yet." +with no score and a warn colour, and a run that had nothing applicable to check is kept distinct from +a project that never ran. + ### SCOPE-EMPTY — an empty scope register no longer reads as one where nothing was done `scope_register.register` returned `0.0` for all three coverage percentages on an empty register — diff --git a/apps/web/src/api/clientCallers.test.ts b/apps/web/src/api/clientCallers.test.ts index 5daa6a21..5beb1394 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -295,7 +295,7 @@ const UNCALLED: readonly string[] = [ "addBasePlate", "addCurtainWall", "addMepFitting", "addRebarCage", "addShearTab", "addTopicComment", "applyDetailingRules", "arrayElement", "assignMaterialSet", "assumptionsRegister", "attachDocument", - "ciLatest", "citedQuery", "clashFederated", "clausePlaybook", + "citedQuery", "clashFederated", "clausePlaybook", "clientDecisions", "codeAdoptions", "codeCheck", "colorFacets", "connectElements", "costSummary", "createAssembly", "createGroup", "createType", diff --git a/apps/web/src/viewer/tools/modelCiView.test.ts b/apps/web/src/viewer/tools/modelCiView.test.ts new file mode 100644 index 00000000..fde35878 --- /dev/null +++ b/apps/web/src/viewer/tools/modelCiView.test.ts @@ -0,0 +1,124 @@ +import { describe, expect, it } from "vitest"; + +import type { CiReport } from "./modelCiView"; +import { headline, headlineKind, isUnrun, renderCiReport } from "./modelCiView"; + +/** + * CI-LATEST-DARK. `ciRun` was wired and `ciLatest` was not, so the stored badge — persisted + * precisely so it need not be recomputed — could only be seen by recomputing it. + * + * Wiring it handed the renderer a state `ciRun` can never produce. `model_ci.latest` returns + * `{overall: "none", badge: "NONE", checks: [], note: "No CI run yet."}` for a project that has + * never run, and the old inline line `${r.passed ?? 0}/${r.total_checks ?? r.checks.length} passed` + * renders that as **"0/0 passed"** — a score, for something never scored. That is the case pinned + * hardest here, because it is the one the previous renderer had never been asked about. + */ + +const R = (over: Partial = {}): CiReport => ({ + overall: "pass", badge: "PASS", ran_at: "2026-09-25T07:00:00Z", + total_checks: 5, passed: 5, failed: 0, warned: 0, + checks: [{ key: "rules", label: "Rule library", status: "pass", summary: "12/12 rules pass" }], + ...over, +} as CiReport); + +/** Exactly what `/ci/latest` returns for a project that has never run CI — measured, not invented. */ +const UNRUN = (): CiReport => + ({ overall: "none", badge: "NONE", checks: [], note: "No CI run yet." } as CiReport); + +const mount = () => { + const el = document.createElement("div"); + document.body.replaceChildren(el); + return el; +}; + +/** A real run that had nothing applicable to check: `ran_at` set, a verdict, and no checks. This is + * NOT "never run", and a mutation proved the distinction was unasserted — `isUnrun` reduced to + * `!r.checks.length` passed every test here, because no fixture had an empty `checks` on a report + * that had actually run. *Run it* and *it ran and found nothing to check* are different findings, + * and conflating them is the same defect this whole session is about, one level down. */ +const RAN_EMPTY = (): CiReport => + ({ overall: "pass", badge: "PASS", ran_at: "2026-09-25T07:00:00Z", + total_checks: 0, passed: 0, checks: [] } as CiReport); + +describe("a project that never ran CI is not a project that scored zero", () => { + it("is recognised", () => { + expect(isUnrun(UNRUN())).toBe(true); + expect(isUnrun(R())).toBe(false); + }); + + it("is not confused with a run that had nothing to check", () => { + expect(isUnrun(RAN_EMPTY())).toBe(false); + expect(headline(RAN_EMPTY())).not.toContain("Not run yet"); + expect(headline(RAN_EMPTY())).toContain("PASS"); + }); + + it("shows NO score — '0/0 passed' is a result, and there is no result", () => { + const h = headline(UNRUN()); + expect(h).not.toContain("passed"); + expect(h).not.toContain("0/0"); + }); + + it("says so in the engine's own words", () => { + expect(headline(UNRUN())).toContain("Not run yet"); + expect(headline(UNRUN())).toContain("No CI run yet."); + }); + + it("is not coloured as a pass", () => { + expect(headlineKind(UNRUN())).toBe("warn"); + expect(headlineKind(UNRUN())).not.toBe("ok"); + }); + + it("tells the reader what running it would buy", () => { + const el = mount(); + renderCiReport(el, UNRUN()); + expect(el.textContent).toContain("stores a badge"); + expect(el.textContent).toContain("instead of recomputing"); + }); +}); + +describe("a real report reads as it always did", () => { + it("keeps the overall badge, the timestamp and the score", () => { + const h = headline(R()); + expect(h).toContain("PASS"); + expect(h).toContain("2026-09-25T07:00:00Z"); + expect(h).toContain("5/5 passed"); + }); + + it("colours a failure as bad and a pass as ok", () => { + expect(headlineKind(R({ overall: "fail", badge: "FAIL" }))).toBe("bad"); + expect(headlineKind(R())).toBe("ok"); + expect(headlineKind(R({ overall: "warn", badge: "WARN" }))).toBe(""); + }); + + it("falls back to the check count when the server sent no total", () => { + expect(headline(R({ total_checks: undefined, passed: 1 }))).toContain("1/1 passed"); + }); + + it("lists every check", () => { + const el = mount(); + renderCiReport(el, R({ checks: [ + { key: "rules", label: "Rule library", status: "pass", summary: "12/12" }, + { key: "clash", label: "Clash", status: "fail", summary: "3 hard clashes" }] })); + expect(el.textContent).toContain("Rule library"); + expect(el.textContent).toContain("3 hard clashes"); + }); + + it("escapes a summary, which carries rule text an author wrote", () => { + const el = mount(); + renderCiReport(el, R({ checks: [{ key: "k", label: "", status: "fail", + summary: "x" }] })); + expect(el.querySelector("img")).toBeNull(); + }); +}); + +describe("the two shapes are one shape", () => { + it("a stored report and a fresh run render through the same path", () => { + // `ciRun` and `ciLatest` both return ModelCiReport — no re-spelling, so one renderer serves + // both and a drift in either would show up here rather than in only one of two copies. + const a = mount(); renderCiReport(a, R()); + const b = mount(); renderCiReport(b, R({ ran_at: undefined })); + expect(a.textContent).toContain("5/5 passed"); + expect(b.textContent).toContain("5/5 passed"); + expect(b.textContent).not.toContain("undefined"); + }); +}); diff --git a/apps/web/src/viewer/tools/modelCiView.ts b/apps/web/src/viewer/tools/modelCiView.ts new file mode 100644 index 00000000..495d0f2a --- /dev/null +++ b/apps/web/src/viewer/tools/modelCiView.ts @@ -0,0 +1,66 @@ +/** MODEL-CI — the stored quality gate, and the state its renderer had never been handed. + * + * `ciRun` executes the check pack (rule library, data completeness, clash, pinned IDS, quantity + * drift) and **persists the report**; the tool's own footnote says why: *"the badge is stored so + * every model version carries a quality gate."* `ciLatest` reads that stored report back, and was + * callerless — so the only way to see the last result was to compute it again. **A result persisted + * specifically so it need not be recomputed, reachable only by recomputing it.** + * + * WIRING IT EXPOSED A STATE THE RENDERER COULD NOT DESCRIBE. `ciRun` always returns a real report, + * so the inline version never met an unrun project — but `ciLatest` returns + * `{overall: "none", badge: "NONE", checks: [], note: "No CI run yet."}`, and the old line + * `${r.passed ?? 0}/${r.total_checks ?? r.checks.length} passed` renders that as **"0/0 passed"**, + * which reads as a result rather than as an absence. *A renderer is only as sound as the states it + * has been handed*, and it had been handed one. So `isUnrun` is a branch, not a caveat: an unrun + * project gets the engine's own sentence and no score. + * + * The engine itself is clean and was measured before any of this — `overall: "none"`, `badge: + * "NONE"`, and a note saying so. It already refuses to let a no-run read as a pass, which is the + * whole thesis of this session's sweep, implemented before I arrived. + */ +import type { ApiClient } from "../../api/client"; +import { escapeHtml } from "../../ui/feedback"; +import { resultNote } from "../../ui/result"; + +export type CiReport = Awaited>; + +const MARK: Record = { + pass: "✅", warn: "🟡", fail: "🔴", skip: "➖", none: "➖", +}; + +/** Has this project never run CI? `ciRun` cannot produce this; `ciLatest` can. */ +export function isUnrun(r: CiReport): boolean { + return r.overall === "none" || (!r.checks.length && r.ran_at == null); +} + +/** The headline, in the engine's own terms. */ +export function headline(r: CiReport): string { + if (isUnrun(r)) { + return `${MARK.none} Not run yet — ${escapeHtml(r.note || "no stored report for this model")}`; + } + return `Overall ${MARK[r.overall] || ""} ${escapeHtml(r.badge)}` + + (r.ran_at ? ` · ${escapeHtml(r.ran_at)}` : "") + + ` · ${r.passed ?? 0}/${r.total_checks ?? r.checks.length} passed`; +} + +export function headlineKind(r: CiReport): "ok" | "bad" | "warn" | "" { + if (isUnrun(r)) return "warn"; + return r.overall === "fail" ? "bad" : r.overall === "pass" ? "ok" : ""; +} + +/** Render a report — stored or freshly run; they are the same `ModelCiReport` shape. */ +export function renderCiReport(body: HTMLElement, r: CiReport): void { + body.appendChild(resultNote(headline(r), headlineKind(r))); + for (const chk of r.checks) { + body.appendChild(resultNote( + `${MARK[chk.status] || "•"} ${escapeHtml(chk.label)} — ${escapeHtml(chk.summary)}`, + chk.status === "fail" ? "" : "ok")); + } + body.appendChild(resultNote( + isUnrun(r) + ? "Nothing has been checked against this model yet. Running the pack stores a badge that " + + "every later version carries, so this panel opens on the stored result instead of " + + "recomputing the rule library, completeness, clash, IDS and quantity-drift checks." + : "Checks compose the rule library + data-completeness gates; the badge is stored so every " + + "model version carries a quality gate. Add rules via the ✔ Rule check tool.", "")); +} diff --git a/apps/web/src/viewer/tools/qaSection.ts b/apps/web/src/viewer/tools/qaSection.ts index 657c3aba..2503322e 100644 --- a/apps/web/src/viewer/tools/qaSection.ts +++ b/apps/web/src/viewer/tools/qaSection.ts @@ -17,6 +17,7 @@ import { LayerManager } from "../../tools/layers"; import { ModelLoader } from "../loader"; import { SelectionSets } from "../selectionSets"; import { renderRoundtripDiff } from "./roundtripDiffView"; +import { isUnrun, renderCiReport } from "./modelCiView"; import { renderScanCard } from "./scanVerifyView"; /** @@ -771,22 +772,25 @@ export function buildQaSection(d: QaDeps): void { body.appendChild(resultNote("AABB-level checks on the clash geometry path: seven table-driven clearances (doors high; MEP/code at medium), straight-line egress, accessible clear width. Property rules live in ✔ Rule check.", "")); }); }))); - b.appendChild(toolBtn2("▢ Model CI (quality gate)", () => withLoading(container, "Running model CI checks", async () => { + // Opens on the STORED report — the badge is persisted precisely so it need not be + // recomputed, and re-running the pack is the expensive path, not the default one. + b.appendChild(toolBtn2("▢ Model CI (quality gate)", () => withLoading(container, "Reading the stored model CI report", async () => { let r; - try { r = await api.ciRun(pid); } + try { r = await api.ciLatest(pid); } catch (e) { toast((e as Error).message, "error"); return; } - const mark: Record = { pass: "✅", warn: "🟡", fail: "🔴", skip: "➖", none: "➖" }; out.textContent = `CI: ${r.badge}`; showResult("Model CI — quality gate", (body) => { - body.appendChild(resultNote(`Overall ${mark[r!.overall] || ""} ${escapeHtml(r!.badge)}` - + (r!.ran_at ? ` · ${escapeHtml(r!.ran_at)}` : "") - + ` · ${r!.passed ?? 0}/${r!.total_checks ?? r!.checks.length} passed`, - r!.overall === "fail" ? "bad" : r!.overall === "pass" ? "ok" : "")); - for (const chk of r!.checks) { - body.appendChild(resultNote(`${mark[chk.status] || "•"} ${escapeHtml(chk.label)} — ${escapeHtml(chk.summary)}`, - chk.status === "fail" ? "" : "ok")); - } - body.appendChild(resultNote("Checks compose the rule library + data-completeness gates; the badge is stored so every model version carries a quality gate. Add rules via the ✔ Rule check tool.", "")); + const paint = (rep: typeof r) => { body.replaceChildren(); renderCiReport(body, rep!); + const run = document.createElement("button"); + run.className = "mini-btn on"; run.style.marginTop = "6px"; + run.textContent = isUnrun(rep!) ? "▶ Run the check pack" : "⟳ Run again"; + run.onclick = async () => { + run.disabled = true; run.textContent = "running the check pack…"; + try { const fresh = await api.ciRun(pid); out.textContent = `CI: ${fresh.badge}`; paint(fresh); } + catch (e) { toast((e as Error).message, "error"); run.disabled = false; } + }; + body.appendChild(run); }; + paint(r); }); }))); b.appendChild(toolBtn2("🔗 Coordinate clashes (grouped issues)", () => withLoading(container, "Queueing federated clash + coordination", async () => { diff --git a/docs/roadmap.md b/docs/roadmap.md index e8a2ab64..1034658f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,7 +672,39 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) -- ◧ **DEGENERATE-SWEEP — the ledger of what was measured, so the negative half is not re-run** +- ✅ **CI-LATEST-DARK — the badge is stored so it need not be recomputed, and recomputing it was the + only way to see it** *(XS — Lane E; **CLOSED 2026-09-25**; gated by + `apps/web/src/viewer/tools/modelCiView.test.ts`)* + + `ciRun` had a caller; `ciLatest` did not. The MODEL-CI pack — rule library, data completeness, + clash, pinned IDS, quantity drift — **persists its report**, and the tool's own footnote says why: + *"the badge is stored so every model version carries a quality gate."* Opening the tool ran the + whole pack again. *A result persisted specifically so it need not be recomputed, reachable only by + recomputing it.* The tool now opens on the stored report and offers **⟳ Run again**; an unrun + project gets **▶ Run the check pack**. + + **Wiring it handed the renderer a state `ciRun` can never produce, and it could not describe it.** + `model_ci.latest` returns `{overall: "none", badge: "NONE", checks: [], note: "No CI run yet."}`, + and the inline line `${r.passed ?? 0}/${r.total_checks ?? r.checks.length} passed` renders that as + **"0/0 passed"** — a score, for something never scored. *A renderer is only as sound as the states + it has been handed*, and for as long as `ciRun` was its only source it had never been handed this + one. `isUnrun` is a branch rather than a caveat: no score, the engine's own sentence, and a warn + colour rather than a pass. + + **The engine was clean before any of this** and is in the DEGENERATE-SWEEP ledger as such: it + already refuses to let a no-run read as a pass. The defect was entirely in what could reach it. + + **The surviving mutation named a third state.** Reducing `isUnrun` to `!r.checks.length` passed + every test, because no fixture had an empty `checks` on a report that had actually RUN — a run with + nothing applicable to check. *Run it* and *it ran and found nothing to check* are different + findings, and conflating them is this session's own theme one level down. Both are fixtures now, + and `isUnrun` keyed on either field alone reds. + + Extracted to a module rather than grown in place: `apps/web/src/viewer/tools/qaSection.ts` is under + a down-only size ratchet and had eight lines of headroom. + + +- ✅ **DEGENERATE-SWEEP — the ledger of what was measured, so the negative half is not re-run** *(the sweep behind nine of this session's items; a SNAPSHOT, not a gate — see the last paragraph)* Every item from RENTROLL-DARK onward began the same way: **call the engine with nothing to From ba3bddd8d28758ba744c6f4a8c9b6206e10d704a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 08:24:02 +0000 Subject: [PATCH 20/21] PORTAL-TXN-DARK: record a transaction with neither end built, rather than build half of it POST /shared/{token}/decision is complete and careful: public by design, inputs whitelisted and length-capped, a hard per-token decision cap, unknown or revoked token -> 404. It records a timestamped approve / acknowledge / decline and is explicit that it is "NOT a payment and NOT an e-signature of record." NOTHING CAN REACH IT, AND NOTHING CAN READ WHAT IT STORES. the page a client sees read-only HTML -- grepped: no Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- docs/roadmap.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 1034658f..fe7ef5b9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,39 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- 🟡 **PORTAL-TXN-DARK — the client-decision loop is unreachable at BOTH ends** + *(M — `services/api/src/aec_api/client_portal.py` + `apps/web/src/portal/`; **OPEN — the write half + needs the user's call**; measured 2026-09-25)* + + `POST /shared/{token}/decision` is complete and careful: public by design, inputs whitelisted and + length-capped, a hard per-token decision cap, unknown or revoked token → 404. It records a + timestamped **approve / acknowledge / decline** and is explicit that it is *"NOT a payment and NOT + an e-signature of record"*. **Nothing can reach it, and nothing can read what it stores.** + + | | | + |---|---| + | the page a client actually sees (`GET /shared/{token}`) | **read-only HTML** — grepped: no ` Date: Fri, 25 Sep 2026 08:29:35 +0000 Subject: [PATCH 21/21] SSO-DOOR-DERIVED: the fourth sign-in door arrived guarded, and the gate could not see it The concurrency sweep record in docs/roadmap.md ends with a prediction: "a fourth sign-in path is the risk." IT ARRIVED, AND THE PREDICTION HALF CAME TRUE. routers/scim.py auto-provisions users and DOES route through auth.get_or_create_sso_user, so the code was right. What was wrong is that test_sso_provision_race tested three doors from a hardcoded DOORS list and printed "all 3 auto-provisioning doors (OAuth, SAML, massing.cloud)" -- silent about the fourth, and about any fifth. A registry reports on what it contains, and its silence is indistinguishable from a clean bill: test_gap_records' own sentence, arriving from the other direction. Not a stale OPEN this time, but a population that quietly stopped being whole. THE POPULATION IS DERIVED TWICE, FROM TWO ANGLES, AND NEITHER NEEDS AN EXEMPTION LIST: * every CALLER of the helper must be a known door -- refuses a fifth door wired correctly but never race-tested; * every site CONSTRUCTING a User must reach the helper or be a by-request creation -- refuses a fifth door that provisions inline, which is the dangerous shape. Both are proved by writing a real fifth door into the tree and running the gate: wired to the helper it reds the first, provisioning inline it reds the second. The by-request set (register, create_user, the admin bootstrap) is a real category rather than a fudge -- each guards with an explicit 409 and none is a sign-in door. THE WALK DOUBLE-COUNTED ON ITS FIRST DRAFT AND DID NOT FAIL, IT ANSWERED. Attributing each User(...) to every enclosing function put a construction inside a nested _make_user under both the factory and its route: 11 sites where an independent probe found 7, and "8 via the helper" when there are four. The precondition floor was calibrated on the inflated number, so it would have been satisfied by a walk finding half the tree. Attribution is innermost-wins by line range now, and the floor is 7. Caught by comparing the gate's count against a derivation written separately -- which is the only reason it was caught at all. RECORDED AND NOT FIXED, DELIBERATELY: register and create_user are check-then-insert, so a true race yields a 500 where 409 was intended. That is low severity and saying so matters. Unlike a sign-in, where both racers are the same legitimate user and the loser must succeed, the loser here is claiming a taken username and its request correctly fails either way. A wrong status code on an already-failing request is not the defect the SSO doors had, and inflating it to match would make the ledger less useful, not more. Verified: test_sso_provision_race, test_seeding_sweep, test_saml, test_sso, test_scim, test_auth, test_claude_md_gates, test_roadmap_status and test_gap_records green; ruff clean; 559 web shell/tooling/register tests green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA --- CHANGELOG.md | 12 +++ docs/roadmap.md | 39 ++++++++ services/api/test_sso_provision_race.py | 123 +++++++++++++++++++++++- 3 files changed, 172 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e662032..9ed213aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build ## Unreleased +### SSO-DOOR-DERIVED — the sign-in door list is derived from the helper's callers + +The roadmap's concurrency record predicted "a fourth sign-in path is the risk". SCIM is that fourth +path, and it correctly routes through `auth.get_or_create_sso_user` — but `test_sso_provision_race` +tested a hardcoded list of three and reported "all 3 auto-provisioning doors", silent about the +fourth and about any fifth. + +The population is now derived twice: every caller of the helper must be a known door, and every site +constructing a `User` must either reach the helper or be a by-request creation guarded with a 409. +Both are proved by writing a real fifth door into the tree — wired to the helper it reds the first, +provisioning inline it reds the second. + ### CI-LATEST-DARK — the Model CI tool opens on the stored badge instead of re-running the pack `ciRun` was wired and `ciLatest` was not, so the persisted model-CI report — stored precisely so the diff --git a/docs/roadmap.md b/docs/roadmap.md index fe7ef5b9..0bc80466 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,45 @@ concurrency record names the specific thing to watch, a fourth sign-in path. three named axes were spent — true, and this was a fourth nobody had named. ### Band 2 — built but unreachable (cheapest real value in the file) +- ✅ ⭐ **SSO-DOOR-DERIVED — the fourth sign-in door arrived guarded, and the gate watching for it + could not see it** *(XS — Lane C; **CLOSED 2026-09-25**; the derivation lives in + `services/api/test_sso_provision_race.py` beside the behavioural arms it bounds)* + + The concurrency sweep record on this page ends with a prediction: *"a fourth sign-in path is the + risk."* **It arrived, and the prediction half came true.** `routers/scim.py` auto-provisions users + and **does** route through `auth.get_or_create_sso_user`, so the code was right. What was wrong is + that `test_sso_provision_race` tested three doors from a hardcoded `DOORS` list and printed + *"all 3 auto-provisioning doors (OAuth, SAML, massing.cloud)"* — **silent about the fourth, and + about any fifth.** *A registry reports on what it contains, and its silence is indistinguishable + from a clean bill* — `test_gap_records`' own sentence, arriving from the other direction: not a + stale OPEN, but a population that quietly stopped being whole. + + **The population is derived twice, from two angles, and neither needs an exemption list:** + + | derivation | what it refuses | + |---|---| + | every CALLER of the helper is a known door | a fifth door wired correctly but never race-tested | + | every site CONSTRUCTING a `User` reaches the helper or is a by-request creation | a fifth door that provisions inline, which is the dangerous shape | + + Both are mutation-proved by writing a real fifth door into the tree: wired to the helper, it reds + the first; provisioning inline, it reds the second. The by-request set — `register`, `create_user` + and the admin bootstrap — is a real category rather than a fudge: each guards with an explicit + 409 and none is a sign-in door. + + **The walk double-counted on its first draft and did not fail, it ANSWERED.** Attributing each + `User(...)` to every enclosing function put a construction inside a nested `_make_user` under both + the factory and its route: **11 sites where an independent probe found 7**, and "8 via the helper" + when there are four. The precondition floor was calibrated on the inflated number, so it would + have been satisfied by a walk finding half the tree. Attribution is innermost-wins by line range + now, and the floor is 7. + + *Recorded and not fixed, deliberately:* `register` and `create_user` are check-then-insert, so a + true race yields a **500 where 409 was intended**. That is low severity and saying so matters — + unlike a sign-in, where both racers are the same legitimate user and the loser must succeed, the + loser here is claiming a taken username and its request correctly fails either way. A wrong status + code on an already-failing request is not the defect the SSO doors had. + + - 🟡 **PORTAL-TXN-DARK — the client-decision loop is unreachable at BOTH ends** *(M — `services/api/src/aec_api/client_portal.py` + `apps/web/src/portal/`; **OPEN — the write half needs the user's call**; measured 2026-09-25)* diff --git a/services/api/test_sso_provision_race.py b/services/api/test_sso_provision_race.py index 2ded2192..7e5ff541 100644 --- a/services/api/test_sso_provision_race.py +++ b/services/api/test_sso_provision_race.py @@ -326,12 +326,131 @@ def _racing_settings_get(entity, ident, *a, _r=_real, _s=seen_s, **kw): db.close() settings_store._cache.pop(SKEY, None) +# ================================================================================================= +# IS `DOORS` STILL THE WHOLE POPULATION? +# +# The roadmap's concurrency record predicted the exact failure this block exists to stop: *"a fourth +# sign-in path is the risk."* **The fourth path arrived and the prediction half came true** — SCIM +# auto-provisions at `routers/scim.py`, and it DOES route through `auth.get_or_create_sso_user`, so +# the code was fine. What was not fine is that everything above tested three doors by name and said +# so in its verdict line, which is *"a registry reports on what it contains, and its silence is +# indistinguishable from a clean bill"* — the sentence `test_gap_records` was built around, arriving +# here from the other direction: not a stale OPEN, but a population that quietly stopped being whole. +# +# So the population is derived twice, from two different angles, and NEITHER needs an exemption list: +# +# 1. every CALLER of the helper must be covered by `DOORS` — a door added and wired correctly can +# no longer go untested; +# 2. every site that CONSTRUCTS a `User` must either reach the helper, or be a creation made by +# explicit request rather than by auto-provisioning on sign-in. That second set is named, and it +# is a real category rather than a fudge: `register`, `create_user` and the admin bootstrap all +# guard with an explicit 409 and are not sign-in doors at all. +# ================================================================================================= +import ast as _ast # noqa: E402 +import pathlib # noqa: E402 + +_SRC = pathlib.Path(__file__).resolve().parent / "src" / "aec_api" + + +def _helper_callers() -> set[str]: + """Modules calling `get_or_create_sso_user`, excluding the module that defines it.""" + out: set[str] = set() + for f in sorted(_SRC.rglob("*.py")): + try: + tree = _ast.parse(f.read_text(encoding="utf-8")) + except SyntaxError: + continue + for n in _ast.walk(tree): + if isinstance(n, _ast.Call) and isinstance(n.func, _ast.Attribute) \ + and n.func.attr == "get_or_create_sso_user": + out.add(f.stem) + return out + + +def _user_construction_sites() -> set[str]: + """`module.function` for every site constructing a `User(...)`, attributed to the INNERMOST + enclosing function. + + The first draft walked every function and took any `User(...)` beneath it, so a construction + inside a nested `_make_user` was attributed to the nested factory AND to the route enclosing it. + It reported **11 sites** where an independent probe found **7**, and said "8 via the helper" when + there are four. *A walk that double-counts does not fail, it answers* — and the floor below was + calibrated on the inflated number, so it would have been satisfied by a walk finding half the + tree. Attribution is by line range now, innermost wins.""" + out: set[str] = set() + for f in sorted(_SRC.rglob("*.py")): + try: + tree = _ast.parse(f.read_text(encoding="utf-8")) + except SyntaxError: + continue + funcs = [n for n in _ast.walk(tree) + if isinstance(n, (_ast.FunctionDef, _ast.AsyncFunctionDef))] + for n in _ast.walk(tree): + if not (isinstance(n, _ast.Call) and isinstance(n.func, _ast.Name) and n.func.id == "User"): + continue + enclosing = [fn for fn in funcs + if fn.lineno <= n.lineno <= (fn.end_lineno or fn.lineno)] + if not enclosing: + continue + innermost = max(enclosing, key=lambda fn: fn.lineno) + out.add(f"{f.stem}.{innermost.name}") + return out + + +#: Sites that create a user BY EXPLICIT REQUEST, not by auto-provisioning on sign-in. Each guards +#: with a 409 and none is a sign-in door, so the savepoint idiom does not apply to them. (Their +#: check-then-insert does degrade a 409 into a 500 under a true race — recorded in the roadmap as +#: low severity on purpose: unlike a sign-in, the loser's request is one that correctly fails.) +BY_REQUEST = {"auth.register", "auth.create_user", "auth.require_admin_user"} + +_callers = _helper_callers() +_doors = {d for d, _u, _s in DOORS} +#: `scim` is the fourth door and is covered by the mapping below rather than by a behavioural arm: +#: it provisions from a directory push, not from an interactive sign-in, so it has no callback to +#: drive. What matters here is that it cannot be ADDED without this file noticing. +_CALLER_TO_DOOR = {"auth": "oauth", "saml": "saml", "cloud": "cloud", "scim": "scim"} + +_unmapped = _callers - set(_CALLER_TO_DOOR) +if _unmapped: + FAILED.append(f"a NEW sign-in door calls the helper and this file does not know it: " + f"{sorted(_unmapped)} — add it to _CALLER_TO_DOOR and give it a DOORS arm") + print(f"FAIL every helper caller is a known door unmapped={sorted(_unmapped)}") +else: + print(f"PASS every helper caller is a known door {sorted(_callers)}") + +_expected_behavioural = {d for c, d in _CALLER_TO_DOOR.items() if c in _callers and d != "scim"} +if not _expected_behavioural <= _doors: + FAILED.append(f"a door calls the helper but has no behavioural arm: " + f"{sorted(_expected_behavioural - _doors)}") + print(f"FAIL every interactive door has a race arm missing={sorted(_expected_behavioural - _doors)}") +else: + print(f"PASS every interactive door has a race arm {sorted(_doors)}") + +_sites = _user_construction_sites() +_via_helper = {f"{c}.{fn}" for c in _callers for fn in ("_make_user",)} +_stray = {s for s in _sites if s not in BY_REQUEST and s.split(".")[0] not in _callers} +if _stray: + FAILED.append(f"a User is constructed outside both the helper and the by-request set: {sorted(_stray)}") + print(f"FAIL no User is created outside the two known shapes stray={sorted(_stray)}") +else: + print(f"PASS no User is created outside the two known shapes " + f"{len(_sites)} site(s): {len(_sites) - len(BY_REQUEST)} auto-provisioning, {len(BY_REQUEST)} by request") + +if len(_callers) < 4 or len(_sites) < 7: + FAILED.append(f"PRECONDITION: the AST walk under-counted (callers={len(_callers)}, " + f"sites={len(_sites)}) — a walk that finds nothing reports a clean tree") + print("FAIL PRECONDITION: the walk found the population it reasons about") +else: + print(f"PASS PRECONDITION: the walk found the population it reasons about " + f"{len(_callers)} caller(s), {len(_sites)} construction site(s)") + if FAILED: print("FAILED:", ", ".join(FAILED)) sys.exit(1) print( - f"SSO PROVISION RACE OK - all {len(DOORS)} auto-provisioning doors (OAuth, SAML, massing.cloud) " - "go through one helper whose INSERT sits in a SAVEPOINT: losing the seeding race hands back the " + f"SSO PROVISION RACE OK - all {len(_callers)} auto-provisioning doors ({', '.join(sorted(_callers))}) " + "go through one helper whose INSERT sits in a SAVEPOINT -- and the door list is DERIVED from " + "the helper's callers, so a fifth cannot arrive unseen. Losing the seeding race hands back the " "winner's row instead of raising, the session stays usable so the sign-in audit entry still " "commits, exactly one account exists, and an uncontested first sign-in still creates one." )