diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c4f1813..9ed213aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,724 @@ 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 +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 — +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 +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 +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 +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 +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** +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 — +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: + +> 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: + +> 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 +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: + +> 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: + +> 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. +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 +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 baf7e4a36..4afd1f695 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -410,6 +410,83 @@ 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. + +**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/.gitignore b/apps/web/.gitignore index ac808c270..7f657e7b5 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 b4ae441b0..552639621 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 000000000..8cd42c367 --- /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/client.ts b/apps/web/src/api/client.ts index b745edba4..5b5103c8a 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 fceafcdd5..5beb13945 100644 --- a/apps/web/src/api/clientCallers.test.ts +++ b/apps/web/src/api/clientCallers.test.ts @@ -295,25 +295,32 @@ 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", - "competitiveSupply", "connectElements", "costSummary", "createAssembly", "createGroup", - "createType", "decisionGate", + "connectElements", "costSummary", "createAssembly", "createGroup", + "createType", "docGraph", "draftPost", "drawingSchedulesCalc", "drawingSetPlan", "drawingsSyncStatus", "ebcPathways", "editType", "elements5dMap", "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", "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` 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", - "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. + "reviewPost", "reviewScenario", "reviseDrawing", "runClash", "runMacro", "saveClausePlaybook", - "saveDealAuthority", "saveMacros", "saveViewTemplates", - "scanDeviation", "scopeRegister", "securitiesPackage", "sendDigest", + "saveMacros", "saveViewTemplates", + "scopeRegister", "securitiesPackage", "sendDigest", "setLod", "setPhase", "sharedComment", "sharedDecision", "sharedDigestUrl", "spaceUtilBenchmarks", "speckleStatus", "tieredComps", "topicComments", "updateConnection", "veLog", "verificationDeviations", diff --git a/apps/web/src/api/creDeal.ts b/apps/web/src/api/creDeal.ts index f36248daa..66d4cd882 100644 --- a/apps/web/src/api/creDeal.ts +++ b/apps/web/src/api/creDeal.ts @@ -98,6 +98,115 @@ 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; +} + +/** 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; +} + +/** 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. */ @@ -138,53 +247,71 @@ 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`, { 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 }) }); } - /** 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. */ 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 }) }); @@ -213,8 +340,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/api/model.ts b/apps/web/src/api/model.ts index 6039297e5..23845c7a6 100644 --- a/apps/web/src/api/model.ts +++ b/apps/web/src/api/model.ts @@ -538,8 +538,55 @@ 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 }>; + } + /** 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. */ diff --git a/apps/web/src/api/openapiTypes.ts b/apps/web/src/api/openapiTypes.ts index 935c91012..7e887be3f 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/proforma.ts b/apps/web/src/api/proforma.ts index 3f53c43da..5ed48b1de 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/api/schedule.ts b/apps/web/src/api/schedule.ts index 84926247c..af6622f42 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/api/schema.d.ts b/apps/web/src/api/schema.d.ts index 60a453e9e..5cc9f09cd 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/apps/web/src/api/types.ts b/apps/web/src/api/types.ts index 389b3a0ea..3ddd3503c 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 63f3d4759..d98f55f31 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 c89c69ee5..a3c74a382 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 fe16b7009..e61262271 100644 --- a/apps/web/src/portal/portal.ts +++ b/apps/web/src/portal/portal.ts @@ -1008,7 +1008,7 @@ 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) { for (const a of d.action_items.slice(0, 20)) { const row = el("button", "portal-mod") as HTMLButtonElement; diff --git a/apps/web/src/proforma/authorityCard.test.ts b/apps/web/src/proforma/authorityCard.test.ts new file mode 100644 index 000000000..2df2f2151 --- /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 000000000..f61d1e786 --- /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/covenantCard.test.ts b/apps/web/src/proforma/covenantCard.test.ts new file mode 100644 index 000000000..56af55623 --- /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 000000000..93967ead3 --- /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/decisionGateCard.test.ts b/apps/web/src/proforma/decisionGateCard.test.ts new file mode 100644 index 000000000..6a9c78b54 --- /dev/null +++ b/apps/web/src/proforma/decisionGateCard.test.ts @@ -0,0 +1,203 @@ +import { describe, expect, it, vi } from "vitest"; + +import type { ApiClient } from "../api/client"; +import { coverageOf, 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("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 = { + 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 000000000..2b0cc9fe9 --- /dev/null +++ b/apps/web/src/proforma/decisionGateCard.ts @@ -0,0 +1,188 @@ +/** 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", +}; + +/** 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"; + 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)}` + + (coverageOf(x) ? ` — ` + + `${esc(coverageOf(x)!)}` : ``) + + `
`); + + 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.render.test.ts b/apps/web/src/proforma/proforma.render.test.ts index 9af169471..6a1a903f9 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 06ee8be0e..9a34a6841 100644 --- a/apps/web/src/proforma/proforma.ts +++ b/apps/web/src/proforma/proforma.ts @@ -7,6 +7,13 @@ 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 { 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"; import { toast } from "../ui/feedback"; @@ -148,14 +155,37 @@ 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.cap) into(sections.cap, () => { this.renderBudget(); this.renderSourcesUses(); this.renderSpecialty(); }); + 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. + renderCovenantCard(this.root, { + api: this.api, projectId: this.projectId, setStatus: this.setStatus, + }); + }); const uwSec = sections.uw; if (uwSec) into(uwSec, () => { uwSec.appendChild(form); 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, + }); + // 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(); }); @@ -532,6 +562,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 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)}
`; } } @@ -882,6 +927,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/rentRollQuality.test.ts b/apps/web/src/proforma/rentRollQuality.test.ts new file mode 100644 index 000000000..caca0a313 --- /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 000000000..052c2f17b --- /dev/null +++ b/apps/web/src/proforma/rentRollQuality.ts @@ -0,0 +1,225 @@ +/** 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. + * + * `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, income), + ]); + 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") { + 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); + } + if (ner.status === "rejected" && scrub.status === "rejected") ctx.setStatus("rent-roll quality unavailable"); +} diff --git a/apps/web/src/proforma/residualLandCard.test.ts b/apps/web/src/proforma/residualLandCard.test.ts new file mode 100644 index 000000000..5600ab252 --- /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 000000000..51e158c2f --- /dev/null +++ b/apps/web/src/proforma/residualLandCard.ts @@ -0,0 +1,209 @@ +/** 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"; + // `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); + 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; + } + }; +} diff --git a/apps/web/src/proforma/supplyCard.test.ts b/apps/web/src/proforma/supplyCard.test.ts new file mode 100644 index 000000000..c53030918 --- /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 000000000..eb7099041 --- /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/apps/web/src/proforma/t12Card.test.ts b/apps/web/src/proforma/t12Card.test.ts new file mode 100644 index 000000000..0c33b61c6 --- /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 000000000..b06078509 --- /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/apps/web/src/style.css b/apps/web/src/style.css index 5168114fe..f21ccc51e 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 e92049873..a8da62f7e 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 3293733a4..0d7f2341f 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/apps/web/src/viewer/tools/modelCiView.test.ts b/apps/web/src/viewer/tools/modelCiView.test.ts new file mode 100644 index 000000000..fde35878a --- /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 000000000..495d0f2ab --- /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 a4b28e666..2503322ea 100644 --- a/apps/web/src/viewer/tools/qaSection.ts +++ b/apps/web/src/viewer/tools/qaSection.ts @@ -16,6 +16,9 @@ import { escapeHtml, toast, withLoading } from "../../ui/feedback"; 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"; /** * R39-DECOMP-VIEWER ② — the clash / QA tool section, out of `app.ts`. @@ -339,6 +342,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); } @@ -695,29 +702,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`; - 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"; - apply.textContent = `✓ Apply ${d.changes.length} change(s) + republish`; - 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 = ""; } }; @@ -779,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/apps/web/src/viewer/tools/roundtripDiffView.test.ts b/apps/web/src/viewer/tools/roundtripDiffView.test.ts new file mode 100644 index 000000000..1ab5542a0 --- /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 000000000..684fa5a05 --- /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/apps/web/src/viewer/tools/scanVerifyView.test.ts b/apps/web/src/viewer/tools/scanVerifyView.test.ts new file mode 100644 index 000000000..8d1507c35 --- /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 000000000..86dd63347 --- /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 b2cd5a8d0..0bc80466d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -672,6 +672,353 @@ 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)* + + `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 ` 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`)* + + **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`)* + + **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`)* + + `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 @@ -1114,21 +1461,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)* +- ✅ **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.* - `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.* + | | | + |---|---| + | 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 +1669,296 @@ 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`)* + + `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 + `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`)* + + 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 + `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 + `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 + `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 + `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,12 +4674,12 @@ 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 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 · PORTAL-TXN-DARK (all seven 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.** diff --git a/services/api/run_tests.py b/services/api/run_tests.py index 39e0970c8..53257d991 100644 --- a/services/api/run_tests.py +++ b/services/api/run_tests.py @@ -86,13 +86,13 @@ "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_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: "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", "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/src/aec_api/dashboard.py b/services/api/src/aec_api/dashboard.py index 7434c0243..147afdb30 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/progress_rollup.py b/services/api/src/aec_api/progress_rollup.py index 1019ae568..a9da8c9f9 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/src/aec_api/routers/analysis.py b/services/api/src/aec_api/routers/analysis.py index b84480949..6f615e9c3 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/routers/standards.py b/services/api/src/aec_api/routers/standards.py index 5157f8fe0..bc11a8a1f 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/scan_deviation.py b/services/api/src/aec_api/scan_deviation.py index 41f6345b9..264119326 100644 --- a/services/api/src/aec_api/scan_deviation.py +++ b/services/api/src/aec_api/scan_deviation.py @@ -5,17 +5,42 @@ 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 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_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 + 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 @@ -23,7 +48,24 @@ def analyze(points: Any, reference: Any, tolerance: float = 0.05) -> dict[str, A 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 " + "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 +73,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 +86,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 "")), } @@ -206,9 +255,13 @@ 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.""" +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 +280,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 +309,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/src/aec_api/scope_register.py b/services/api/src/aec_api/scope_register.py index 363cc5dab..04b93557d 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/src/aec_api/spine.py b/services/api/src/aec_api/spine.py index a57918553..b453eaa34 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_progress_unmatched.py b/services/api/test_progress_unmatched.py new file mode 100644 index 000000000..ab61bbaee --- /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.") diff --git a/services/api/test_roadmap_status.py b/services/api/test_roadmap_status.py index f4640e419..bd48c0f62 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_route_reachability.py b/services/api/test_route_reachability.py index c554622c2..5bac9972a 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. diff --git a/services/api/test_scan_trunc.py b/services/api/test_scan_trunc.py new file mode 100644 index 000000000..09661d3f2 --- /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_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. + +**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.") diff --git a/services/api/test_schema_types_agree.py b/services/api/test_schema_types_agree.py new file mode 100644 index 000000000..fedf628a3 --- /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" +) diff --git a/services/api/test_scope_register.py b/services/api/test_scope_register.py index a91b0335a..cd3d993b4 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"): diff --git a/services/api/test_sso_provision_race.py b/services/api/test_sso_provision_race.py index 2ded2192b..7e5ff541f 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." ) diff --git a/services/api/test_trunc_counted.py b/services/api/test_trunc_counted.py new file mode 100644 index 000000000..fa8cbbfdf --- /dev/null +++ b/services/api/test_trunc_counted.py @@ -0,0 +1,218 @@ +"""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) + +# 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 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", + "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.") diff --git a/services/api/test_verdict_coverage.py b/services/api/test_verdict_coverage.py new file mode 100644 index 000000000..ace50b19a --- /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")