@instruments/materialgraph 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/by-spec-types.d.ts +73 -0
  2. package/by-spec-types.js +7 -0
  3. package/client.d.ts +68 -23
  4. package/client.js +82 -25
  5. package/codes-client.d.ts +82 -0
  6. package/codes-client.js +73 -0
  7. package/codes-types.d.ts +154 -9
  8. package/colour-aggregate-types.d.ts +93 -0
  9. package/colour-aggregate-types.js +1 -0
  10. package/colours-client.d.ts +52 -0
  11. package/colours-client.js +39 -0
  12. package/composition-types.d.ts +228 -2
  13. package/http-transport.d.ts +19 -2
  14. package/http-transport.js +72 -2
  15. package/index.d.ts +7 -0
  16. package/index.js +3 -0
  17. package/match-stream-types.d.ts +150 -0
  18. package/match-stream-types.js +28 -0
  19. package/match-stream.d.ts +22 -0
  20. package/match-stream.js +280 -0
  21. package/material-block-types.d.ts +23 -2
  22. package/material-types.d.ts +92 -0
  23. package/material-types.js +1 -0
  24. package/ndjson.d.ts +46 -0
  25. package/ndjson.js +103 -0
  26. package/operations.d.ts +1 -1
  27. package/operations.js +6 -0
  28. package/package.json +1 -1
  29. package/run-types.d.ts +65 -1
  30. package/runs.d.ts +45 -3
  31. package/runs.js +82 -43
  32. package/schedule-types.d.ts +88 -0
  33. package/schedule-types.js +12 -0
  34. package/search-refinement-types.d.ts +13 -0
  35. package/search-result-validation.d.ts +27 -0
  36. package/search-result-validation.js +174 -0
  37. package/source-material-validation.d.ts +3 -0
  38. package/source-material-validation.js +109 -0
  39. package/spec-types.d.ts +4 -0
  40. package/spec-types.js +4 -0
  41. package/specifications.d.ts +9 -0
  42. package/specifications.js +9 -0
  43. package/types.d.ts +59 -73
  44. package/validation.d.ts +119 -0
  45. package/validation.js +241 -0
@@ -0,0 +1,73 @@
1
+ export interface BySpecMatchedCell {
2
+ attributeId: string;
3
+ confidence: number | null;
4
+ sourceBasis: string | null;
5
+ }
6
+ /** The six states a verdict cell can report; open per the response-enum policy. */
7
+ export type BySpecVerdictState = "matched" | "deviated" | "failed" | "unverified" | "missing" | "not_evaluated";
8
+ /**
9
+ * One cell of a match's report card — how a single requested criterion scored.
10
+ * Hand-mirrors the `AttributeVerdict` contract from `@materialgraph/contracts`;
11
+ * the OpenAPI `CatalogBySpecResponse` schema is the source of truth on the wire.
12
+ */
13
+ export interface BySpecVerdictCell {
14
+ attributeId: string;
15
+ state: BySpecVerdictState;
16
+ strength: "required" | "preferred";
17
+ baseline: {
18
+ kind: "term";
19
+ term: string;
20
+ } | {
21
+ kind: "presence";
22
+ } | {
23
+ kind: "seed";
24
+ variantId: string;
25
+ } | {
26
+ kind: "image";
27
+ source: "variant" | "vector";
28
+ variantId: string | null;
29
+ };
30
+ value: string | null;
31
+ deviation: {
32
+ direction: "above" | "below" | "other";
33
+ delta: number | null;
34
+ unit: string | null;
35
+ label: string;
36
+ } | null;
37
+ confidence: number | null;
38
+ sourceBasis: string | null;
39
+ trustGap: "below_confidence" | "basis_unmet" | "proposed" | null;
40
+ }
41
+ /** A match's full report card: every cell, the rolled-up grade, weakest confidence. */
42
+ export interface BySpecVerdict {
43
+ cells: BySpecVerdictCell[];
44
+ grade: "meets_all" | "close" | "hard_no";
45
+ minConfidence: number | null;
46
+ }
47
+ /**
48
+ * The verdict a materials-search hit carries (MG-625). It IS the shared
49
+ * {@link BySpecVerdict} shape — the single CandidateVerdict mirror — aliased here
50
+ * so search-surface code can name it for what it is rather than duplicating it.
51
+ */
52
+ export type MaterialVerdict = BySpecVerdict;
53
+ export interface BySpecMatch {
54
+ variantId: string;
55
+ brand: string;
56
+ product: string;
57
+ variant: string;
58
+ color: string | null;
59
+ imageUrl: string | null;
60
+ hex: string | null;
61
+ matched: BySpecMatchedCell[];
62
+ minConfidence: number | null;
63
+ verdict: BySpecVerdict;
64
+ }
65
+ export interface BySpecResponse {
66
+ criteria: Array<{
67
+ attribute: string;
68
+ value: string;
69
+ }>;
70
+ totalMatches: number;
71
+ brandsSpanned: number;
72
+ matches: BySpecMatch[];
73
+ }
@@ -0,0 +1,7 @@
1
+ // ── Catalog / spec — find-by-spec wire types ─────────────────────────────────
2
+ //
3
+ // Hand-maintained mirror of the `CatalogBySpecResponse` OpenAPI schema (the
4
+ // source of truth on the wire); the `sdk-openapi-parity` test gates this against
5
+ // the generated spec. Split out of `types.ts` to keep that file under its growth
6
+ // baseline; re-exported from `types.ts` so importers see no change.
7
+ export {};
package/client.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { type RateLimit } from "./errors.js";
2
- import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, PublicRunResponse, RunEvent, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
2
+ import type { CapabilityManifestResponse, CreatePublicRunRequest, CreatePublicRunResponse, DrainRunEventsOptions, PublicRunResponse, ResumeRunOptions, RunEvent, SourceMaterialBatchDetail, SourceMaterialBatchReceipt, StreamRunEventsOptions, UploadSourceMaterialInput } from "./run-types.js";
3
+ import type { FinishScheduleResponse, GetRunScheduleOptions } from "./schedule-types.js";
4
+ import type { MatchStreamEvent } from "./match-stream-types.js";
3
5
  import type { AppendProjectEventRequest, CreateProjectRequest, ProjectEventReceipt, ProjectListResponse, ProjectState } from "./project-types.js";
4
6
  import type { VarySchemesRequest, VarySchemesResponse } from "./composition-types.js";
7
+ import { CodesClient } from "./codes-client.js";
8
+ import { ColoursClient } from "./colours-client.js";
5
9
  import type { CodeAdvisoryInput, CodeAdvisoryResponse } from "./codes-types.js";
6
10
  import type { BrandCatalogListPage, BrandsOptions } from "./brand-types.js";
7
11
  import type { MaterialAssetProjection } from "./material-asset-types.js";
@@ -26,6 +30,17 @@ export declare class MaterialGraphClient {
26
30
  #private;
27
31
  readonly baseUrl: string;
28
32
  readonly browse: BrowseNamespace;
33
+ /**
34
+ * The advisory building-code surface: `codes.context()` to discover the
35
+ * vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
36
+ * badge candidates against it.
37
+ */
38
+ readonly codes: CodesClient;
39
+ /**
40
+ * The colour AGGREGATE surface: `colours.profiles()` for a category palette,
41
+ * `colours.stats()` for corpus colour coverage.
42
+ */
43
+ readonly colours: ColoursClient;
29
44
  constructor(options: MaterialGraphClientOptions);
30
45
  get lastRateLimit(): RateLimit | null;
31
46
  /**
@@ -42,6 +57,30 @@ export declare class MaterialGraphClient {
42
57
  search(input: SearchInput, signal?: AbortSignal): Promise<SearchResponse>;
43
58
  searchGet(params: SearchGetParams, signal?: AbortSignal): Promise<SearchResponse>;
44
59
  match(input: MatchInput, signal?: AbortSignal): Promise<MatchResponse>;
60
+ /**
61
+ * Material Match as a PROGRESSIVE stream — the same POST
62
+ * `/api/v1/search/match` endpoint negotiated with `Accept:
63
+ * application/x-ndjson`, which runs the scene workflow (classify → decompose →
64
+ * one match per surface) and emits an event as each surface resolves. Use this
65
+ * wherever the user watches a photo come apart; use {@link match} when you only
66
+ * want the finished answer.
67
+ *
68
+ * Iterate it directly — the request opens on the first `next()`:
69
+ *
70
+ * ```ts
71
+ * for await (const event of client.matchStream({ imageBase64 })) {
72
+ * if (event.type === "surface") render(event.surface);
73
+ * }
74
+ * ```
75
+ *
76
+ * Every frame is validated before it is yielded, and the iterator ends only on
77
+ * a `complete` or `error` event — a truncated connection raises rather than
78
+ * passing for a short run. `signal` cancels the in-flight request itself, as
79
+ * does breaking out of the loop; a cancelled stream ends without raising.
80
+ * Requires an image (`imageBase64` or `imageUrl`); a `vector` input is
81
+ * buffered-only.
82
+ */
83
+ matchStream(input: MatchInput, signal?: AbortSignal): AsyncGenerator<MatchStreamEvent, void, unknown>;
45
84
  byColour(params: ByColourParams, signal?: AbortSignal): Promise<ByColourResponse>;
46
85
  /** Get a single material's detail record (GET `/api/v1/materials/{variantId}`). */
47
86
  material(variantId: string, options?: {
@@ -146,26 +185,9 @@ export declare class MaterialGraphClient {
146
185
  */
147
186
  demandBatch(input: DemandBatchInput, signal?: AbortSignal): Promise<DemandBatchResponse>;
148
187
  /**
149
- * Badge a set of materials against the advisory building-code registry for one
150
- * (jurisdiction, occupancy, space, sprinklered) context (POST
151
- * `/api/v1/codes/advisory`, ≤ 48 ids). Every applicable requirement is badged
152
- * `meets` / `shortfall` / `unknown` from the material's accepted fire and
153
- * slip-resistance facts, in input order. ADVISORY ONLY — every verdict and the
154
- * envelope carry the AHJ caveat; this is model code as adopted, never a legal
155
- * determination.
156
- *
157
- * @example
158
- * // Badge this scheme's picks for a Texas hospitality fire-code review.
159
- * const advisory = await mg.codeAdvisory({
160
- * variantIds: scheme.picks.map((p) => p.variantId),
161
- * jurisdictionId: "us-tx",
162
- * occupancyGroup: "A-2", // assembly — restaurant / bar
163
- * spaceType: "corridor",
164
- * sprinklered: true,
165
- * });
166
- * const shortfalls = advisory.results.filter((r) =>
167
- * r.requirements.some((req) => req.verdict === "shortfall"),
168
- * );
188
+ * Badge materials against the advisory building-code registry (POST
189
+ * `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
190
+ * flat because it shipped before the `codes` namespace existed.
169
191
  */
170
192
  codeAdvisory(input: CodeAdvisoryInput, signal?: AbortSignal): Promise<CodeAdvisoryResponse>;
171
193
  /** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
@@ -206,14 +228,37 @@ export declare class MaterialGraphClient {
206
228
  * `{ brief, references }` shape. Requires the `mg:ingest` scope.
207
229
  */
208
230
  uploadSourceMaterial(input: UploadSourceMaterialInput, signal?: AbortSignal): Promise<SourceMaterialBatchReceipt>;
231
+ /**
232
+ * Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
233
+ * file with a fetchable `url` and its analyses inlined. `analyses` is always
234
+ * an array — `[]` means nothing has been computed yet. Analyses are shared by
235
+ * content hash, so re-uploading an already-analysed image returns its analysis
236
+ * immediately. A batch you do not own raises the same 404 as one that does not
237
+ * exist. Requires the `mg:compose` scope.
238
+ */
239
+ sourceMaterialBatch(batchId: string, signal?: AbortSignal): Promise<SourceMaterialBatchDetail>;
209
240
  /** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
210
241
  capabilities(signal?: AbortSignal): Promise<CapabilityManifestResponse>;
211
242
  /** Start an asynchronous capability Run (POST `/api/v1/runs`). */
212
243
  createRun(body: CreatePublicRunRequest, signal?: AbortSignal): Promise<CreatePublicRunResponse>;
213
244
  /** Get a Run's current state (GET `/api/v1/runs/{runId}`). */
214
245
  run(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
215
- /** Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`). */
216
- resumeRun(runId: string, resumeData: unknown, signal?: AbortSignal): Promise<PublicRunResponse>;
246
+ /**
247
+ * Export a completed compose Run's scheme as a finish schedule — one row per
248
+ * slot, every attribute value carrying its source and verification state
249
+ * (GET `/api/v1/runs/{runId}/schedule`). Unknown values are explicit
250
+ * `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
251
+ * `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
252
+ */
253
+ runSchedule(runId: string, options?: GetRunScheduleOptions): Promise<FinishScheduleResponse>;
254
+ /**
255
+ * Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
256
+ *
257
+ * The third argument accepts either an `AbortSignal` (the published form) or
258
+ * {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
259
+ * run has already moved past — the API answers 409 on a mismatch (MG-608).
260
+ */
261
+ resumeRun(runId: string, resumeData: unknown, signalOrOptions?: AbortSignal | ResumeRunOptions): Promise<PublicRunResponse>;
217
262
  /** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
218
263
  cancelRun(runId: string, signal?: AbortSignal): Promise<PublicRunResponse>;
219
264
  /**
package/client.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { MaterialGraphError } from "./errors.js";
2
2
  import { HttpTransport } from "./http-transport.js";
3
3
  import { findSdkOperation } from "./operations.js";
4
- import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
4
+ import { cancelRun, capabilities as capabilitiesRun, createRun, drainRunEvents, getRun, getRunSchedule, getSourceMaterialBatch as getSourceMaterialBatchCall, resumeRun, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
5
+ import { matchStream as matchStreamCall } from "./match-stream.js";
5
6
  import { appendProjectEvent as appendProjectEventCall, createProject as createProjectCall, getProject as getProjectCall, listProjects as listProjectsCall, } from "./projects.js";
7
+ import { CodesClient } from "./codes-client.js";
8
+ import { ColoursClient } from "./colours-client.js";
6
9
  export const DEFAULT_BASE_URL = "https://beta.materialgraph.com";
7
10
  export const MAX_BATCH_IDS = 100;
8
11
  const SCHEMA_RESOURCE_OPERATION_IDS = {
@@ -26,6 +29,17 @@ function operation(operationId) {
26
29
  export class MaterialGraphClient {
27
30
  baseUrl;
28
31
  browse;
32
+ /**
33
+ * The advisory building-code surface: `codes.context()` to discover the
34
+ * vocabulary, `codes.requirements()` to state a space, `codes.advisory()` to
35
+ * badge candidates against it.
36
+ */
37
+ codes;
38
+ /**
39
+ * The colour AGGREGATE surface: `colours.profiles()` for a category palette,
40
+ * `colours.stats()` for corpus colour coverage.
41
+ */
42
+ colours;
29
43
  #transport;
30
44
  constructor(options) {
31
45
  if (!options.apiKey) {
@@ -40,6 +54,13 @@ export class MaterialGraphClient {
40
54
  const browse = (params, signal) => this.#browsePage(params, signal);
41
55
  browse.pages = (params, signal) => this.#browsePages(params, signal);
42
56
  this.browse = browse;
57
+ this.codes = new CodesClient({
58
+ get: (operationId, query, signal) => this.#get(operationId, query, signal),
59
+ post: (operationId, body, signal) => this.#post(operationId, body, signal),
60
+ });
61
+ this.colours = new ColoursClient({
62
+ get: (operationId, query, signal) => this.#get(operationId, query, signal),
63
+ });
43
64
  }
44
65
  get lastRateLimit() {
45
66
  return this.#transport.lastRateLimit;
@@ -70,6 +91,32 @@ export class MaterialGraphClient {
70
91
  match(input, signal) {
71
92
  return this.#post("match_material", input, signal);
72
93
  }
94
+ /**
95
+ * Material Match as a PROGRESSIVE stream — the same POST
96
+ * `/api/v1/search/match` endpoint negotiated with `Accept:
97
+ * application/x-ndjson`, which runs the scene workflow (classify → decompose →
98
+ * one match per surface) and emits an event as each surface resolves. Use this
99
+ * wherever the user watches a photo come apart; use {@link match} when you only
100
+ * want the finished answer.
101
+ *
102
+ * Iterate it directly — the request opens on the first `next()`:
103
+ *
104
+ * ```ts
105
+ * for await (const event of client.matchStream({ imageBase64 })) {
106
+ * if (event.type === "surface") render(event.surface);
107
+ * }
108
+ * ```
109
+ *
110
+ * Every frame is validated before it is yielded, and the iterator ends only on
111
+ * a `complete` or `error` event — a truncated connection raises rather than
112
+ * passing for a short run. `signal` cancels the in-flight request itself, as
113
+ * does breaking out of the loop; a cancelled stream ends without raising.
114
+ * Requires an image (`imageBase64` or `imageUrl`); a `vector` input is
115
+ * buffered-only.
116
+ */
117
+ matchStream(input, signal) {
118
+ return matchStreamCall(this.#transport, input, signal);
119
+ }
73
120
  byColour(params, signal) {
74
121
  return this.#get("search_by_colour", {
75
122
  hex: params.hex,
@@ -235,29 +282,12 @@ export class MaterialGraphClient {
235
282
  }
236
283
  // ── Codes (advisory building-code compliance) ────────────────────────────────
237
284
  /**
238
- * Badge a set of materials against the advisory building-code registry for one
239
- * (jurisdiction, occupancy, space, sprinklered) context (POST
240
- * `/api/v1/codes/advisory`, ≤ 48 ids). Every applicable requirement is badged
241
- * `meets` / `shortfall` / `unknown` from the material's accepted fire and
242
- * slip-resistance facts, in input order. ADVISORY ONLY — every verdict and the
243
- * envelope carry the AHJ caveat; this is model code as adopted, never a legal
244
- * determination.
245
- *
246
- * @example
247
- * // Badge this scheme's picks for a Texas hospitality fire-code review.
248
- * const advisory = await mg.codeAdvisory({
249
- * variantIds: scheme.picks.map((p) => p.variantId),
250
- * jurisdictionId: "us-tx",
251
- * occupancyGroup: "A-2", // assembly — restaurant / bar
252
- * spaceType: "corridor",
253
- * sprinklered: true,
254
- * });
255
- * const shortfalls = advisory.results.filter((r) =>
256
- * r.requirements.some((req) => req.verdict === "shortfall"),
257
- * );
285
+ * Badge materials against the advisory building-code registry (POST
286
+ * `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
287
+ * flat because it shipped before the `codes` namespace existed.
258
288
  */
259
289
  codeAdvisory(input, signal) {
260
- return this.#post("resolveCodeAdvisory", input, signal);
290
+ return this.codes.advisory(input, signal);
261
291
  }
262
292
  // ── Colour helpers (extract) ─────────────────────────────────────────────────
263
293
  /** Extract a named colour palette from an uploaded image (POST `/api/v1/search/palettes/extract`). */
@@ -321,6 +351,17 @@ export class MaterialGraphClient {
321
351
  uploadSourceMaterial(input, signal) {
322
352
  return uploadSourceMaterial(this.#transport, input, signal);
323
353
  }
354
+ /**
355
+ * Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
356
+ * file with a fetchable `url` and its analyses inlined. `analyses` is always
357
+ * an array — `[]` means nothing has been computed yet. Analyses are shared by
358
+ * content hash, so re-uploading an already-analysed image returns its analysis
359
+ * immediately. A batch you do not own raises the same 404 as one that does not
360
+ * exist. Requires the `mg:compose` scope.
361
+ */
362
+ sourceMaterialBatch(batchId, signal) {
363
+ return getSourceMaterialBatchCall(this.#transport, batchId, signal);
364
+ }
324
365
  /** List invocable agents, workflows, and tools (GET `/api/v1/capabilities`). */
325
366
  capabilities(signal) {
326
367
  return capabilitiesRun(this.#transport, signal);
@@ -333,9 +374,25 @@ export class MaterialGraphClient {
333
374
  run(runId, signal) {
334
375
  return getRun(this.#transport, runId, signal);
335
376
  }
336
- /** Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`). */
337
- resumeRun(runId, resumeData, signal) {
338
- return resumeRun(this.#transport, runId, resumeData, signal);
377
+ /**
378
+ * Export a completed compose Run's scheme as a finish schedule — one row per
379
+ * slot, every attribute value carrying its source and verification state
380
+ * (GET `/api/v1/runs/{runId}/schedule`). Unknown values are explicit
381
+ * `missing` cells, never blanks. The `.xlsx` form is a direct-URL download:
382
+ * `GET /api/v1/runs/{runId}/schedule.xlsx` with the same bearer key.
383
+ */
384
+ runSchedule(runId, options) {
385
+ return getRunSchedule(this.#transport, runId, options);
386
+ }
387
+ /**
388
+ * Resume a suspended Run (POST `/api/v1/runs/{runId}/resume`).
389
+ *
390
+ * The third argument accepts either an `AbortSignal` (the published form) or
391
+ * {@link ResumeRunOptions}, whose `stepId` guards against resuming a step the
392
+ * run has already moved past — the API answers 409 on a mismatch (MG-608).
393
+ */
394
+ resumeRun(runId, resumeData, signalOrOptions) {
395
+ return resumeRun(this.#transport, runId, resumeData, signalOrOptions);
339
396
  }
340
397
  /** Request durable Run cancellation (POST `/api/v1/runs/{runId}/cancel`). */
341
398
  cancelRun(runId, signal) {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The advisory building-code surface, namespaced as `mg.codes`.
3
+ *
4
+ * Three calls that are one workflow — discover the vocabulary, state the space,
5
+ * then badge candidates against it — so they live together rather than as three
6
+ * flat methods on an already-large client facade. `mg.browse` set the precedent
7
+ * for a namespace when a capability is a small family rather than a single verb.
8
+ *
9
+ * The namespace holds no transport of its own: the client hands it bound
10
+ * `get`/`post` callbacks, so auth, rate-limit accounting and error shaping stay
11
+ * in exactly one place.
12
+ *
13
+ * ADVISORY ONLY: every verdict and every envelope carries the AHJ caveat — this
14
+ * is model code as adopted, never a legal determination.
15
+ */
16
+ import type { CodeAdvisoryInput, CodeAdvisoryResponse, CodeContextRequest, CodeContextResponse, CodeRequirementsResponse } from "./codes-types.js";
17
+ import type { QueryParams } from "./http-transport.js";
18
+ import type { SdkOperationId } from "./operations.js";
19
+ /** The client's own request primitives, handed down so this file owns no transport. */
20
+ export interface CodesTransport {
21
+ get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
22
+ post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
23
+ }
24
+ export declare class CodesClient {
25
+ #private;
26
+ constructor(transport: CodesTransport);
27
+ /**
28
+ * The vocabulary for stating a code context (GET `/api/v1/codes/context`):
29
+ * seeded jurisdictions and their coverage tier, the occupancy groups the
30
+ * registry keys on, finish locations, dimensions, classification scales with
31
+ * classes ordered best-first, and which `ms_room` term correlates to which
32
+ * regulatory axes.
33
+ *
34
+ * Start here — every value the other two calls accept is listed in this one
35
+ * response, so nothing need be hardcoded. A correlation the registry refuses to
36
+ * make comes back `null`, never a guess.
37
+ *
38
+ * @example
39
+ * const vocab = await mg.codes.context();
40
+ * const texas = vocab.jurisdictions.find((j) => j.id === "us-tx");
41
+ * // `model_baseline` means unamended model IBC stood in for this AHJ's own table.
42
+ * console.log(texas?.coverage, texas?.adoptionNote);
43
+ */
44
+ context(signal?: AbortSignal): Promise<CodeContextResponse>;
45
+ /**
46
+ * What a space demands, before any candidate exists (GET
47
+ * `/api/v1/codes/requirements`). State the space by explicit axes, or by a
48
+ * `roomType` the axes are correlated from.
49
+ *
50
+ * An empty `requirements` array means nothing in the registry applies to this
51
+ * context. It is NOT a pass.
52
+ *
53
+ * @example
54
+ * const { requirements } = await mg.codes.requirements({
55
+ * jurisdictionId: "us-tx",
56
+ * occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
57
+ * spaceType: "corridors",
58
+ * sprinklered: true,
59
+ * });
60
+ * requirements.map((r) => `${r.dimension}: ${r.label} (${r.section})`);
61
+ */
62
+ requirements(input: CodeContextRequest, signal?: AbortSignal): Promise<CodeRequirementsResponse>;
63
+ /**
64
+ * Badge materials against the advisory building-code registry for one context
65
+ * (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
66
+ * badged `meets` / `shortfall` / `unknown` from the material's accepted fire
67
+ * and slip facts, in input order; each material also carries a rolled-up
68
+ * `summary`, and `resolvedContext` reports the axes actually used.
69
+ *
70
+ * @example
71
+ * const advisory = await mg.codes.advisory({
72
+ * variantIds: scheme.picks.map((p) => p.variantId),
73
+ * jurisdictionId: "us-tx",
74
+ * occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
75
+ * spaceType: "corridors", // corridors | exit_stairs_passageways | rooms_enclosed_spaces
76
+ * sprinklered: true,
77
+ * });
78
+ * // `unverified` means no comparable rating is on file — common, never a pass.
79
+ * const blocked = advisory.results.filter((r) => r.summary?.status === "shortfall");
80
+ */
81
+ advisory(input: CodeAdvisoryInput, signal?: AbortSignal): Promise<CodeAdvisoryResponse>;
82
+ }
@@ -0,0 +1,73 @@
1
+ export class CodesClient {
2
+ #transport;
3
+ constructor(transport) {
4
+ this.#transport = transport;
5
+ }
6
+ /**
7
+ * The vocabulary for stating a code context (GET `/api/v1/codes/context`):
8
+ * seeded jurisdictions and their coverage tier, the occupancy groups the
9
+ * registry keys on, finish locations, dimensions, classification scales with
10
+ * classes ordered best-first, and which `ms_room` term correlates to which
11
+ * regulatory axes.
12
+ *
13
+ * Start here — every value the other two calls accept is listed in this one
14
+ * response, so nothing need be hardcoded. A correlation the registry refuses to
15
+ * make comes back `null`, never a guess.
16
+ *
17
+ * @example
18
+ * const vocab = await mg.codes.context();
19
+ * const texas = vocab.jurisdictions.find((j) => j.id === "us-tx");
20
+ * // `model_baseline` means unamended model IBC stood in for this AHJ's own table.
21
+ * console.log(texas?.coverage, texas?.adoptionNote);
22
+ */
23
+ context(signal) {
24
+ return this.#transport.get("list_code_context", undefined, signal);
25
+ }
26
+ /**
27
+ * What a space demands, before any candidate exists (GET
28
+ * `/api/v1/codes/requirements`). State the space by explicit axes, or by a
29
+ * `roomType` the axes are correlated from.
30
+ *
31
+ * An empty `requirements` array means nothing in the registry applies to this
32
+ * context. It is NOT a pass.
33
+ *
34
+ * @example
35
+ * const { requirements } = await mg.codes.requirements({
36
+ * jurisdictionId: "us-tx",
37
+ * occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
38
+ * spaceType: "corridors",
39
+ * sprinklered: true,
40
+ * });
41
+ * requirements.map((r) => `${r.dimension}: ${r.label} (${r.section})`);
42
+ */
43
+ requirements(input, signal) {
44
+ return this.#transport.get("list_code_requirements", {
45
+ jurisdiction: input.jurisdictionId,
46
+ sprinklered: input.sprinklered,
47
+ occupancy: input.occupancyGroup,
48
+ space: input.spaceType,
49
+ room: input.roomType,
50
+ }, signal);
51
+ }
52
+ /**
53
+ * Badge materials against the advisory building-code registry for one context
54
+ * (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
55
+ * badged `meets` / `shortfall` / `unknown` from the material's accepted fire
56
+ * and slip facts, in input order; each material also carries a rolled-up
57
+ * `summary`, and `resolvedContext` reports the axes actually used.
58
+ *
59
+ * @example
60
+ * const advisory = await mg.codes.advisory({
61
+ * variantIds: scheme.picks.map((p) => p.variantId),
62
+ * jurisdictionId: "us-tx",
63
+ * occupancyGroup: "A-2", // or: roomType: "hotel_room" to correlate the axes
64
+ * spaceType: "corridors", // corridors | exit_stairs_passageways | rooms_enclosed_spaces
65
+ * sprinklered: true,
66
+ * });
67
+ * // `unverified` means no comparable rating is on file — common, never a pass.
68
+ * const blocked = advisory.results.filter((r) => r.summary?.status === "shortfall");
69
+ */
70
+ advisory(input, signal) {
71
+ return this.#transport.post("resolveCodeAdvisory", input, signal);
72
+ }
73
+ }