@instruments/materialgraph 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brand-types.d.ts +20 -1
- package/brand-types.js +7 -0
- package/by-spec-types.d.ts +62 -3
- package/catalog-discovery-types.d.ts +19 -2
- package/client-browse.d.ts +7 -1
- package/client.d.ts +339 -53
- package/client.js +313 -32
- package/codes-client.d.ts +36 -0
- package/codes-client.js +26 -0
- package/codes-types.d.ts +77 -11
- package/colours-client.d.ts +41 -6
- package/colours-client.js +27 -6
- package/composition-types.d.ts +27 -18
- package/demand-batch-types.d.ts +118 -0
- package/demand-batch-types.js +1 -0
- package/demand-types.d.ts +134 -0
- package/demand-types.js +1 -0
- package/identifiers.d.ts +71 -0
- package/identifiers.js +26 -0
- package/index.d.ts +1 -0
- package/material-asset-types.d.ts +20 -1
- package/material-block-types.d.ts +30 -1
- package/material-types.d.ts +45 -7
- package/package.json +1 -1
- package/palette-search-types.d.ts +17 -2
- package/project-types.d.ts +20 -8
- package/project-types.js +0 -7
- package/run-types.d.ts +20 -8
- package/schedule-types.d.ts +18 -7
- package/schedule-types.js +0 -11
- package/schema-resource-types.d.ts +15 -0
- package/search-refinement-types.d.ts +24 -4
- package/spec-types.d.ts +14 -5
- package/spec-types.js +14 -5
- package/suggest-types.d.ts +10 -1
- package/suggest-types.js +5 -0
- package/types.d.ts +179 -214
package/client.js
CHANGED
|
@@ -28,18 +28,69 @@ function operation(operationId) {
|
|
|
28
28
|
}
|
|
29
29
|
return match;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The MaterialGraph client — every lens on the graph, on one object.
|
|
33
|
+
*
|
|
34
|
+
* PICK YOUR LENS BY THE QUESTION, not by the noun. Most wrong answers from this
|
|
35
|
+
* API come from routing a question to a lens that cannot see it:
|
|
36
|
+
*
|
|
37
|
+
* | The question | The lens |
|
|
38
|
+
* | --- | --- |
|
|
39
|
+
* | "find me something like this / matching these words" | {@link MaterialGraphClient.search} |
|
|
40
|
+
* | "what's IN this photo?" | {@link MaterialGraphClient.match} / {@link MaterialGraphClient.matchStream} |
|
|
41
|
+
* | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.bySpec} |
|
|
42
|
+
* | "what's near this colour / this palette / this gradient?" | {@link MaterialGraphClient.byColour}, `colours.palette()`, `colours.gradient()` |
|
|
43
|
+
* | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.coordinate} |
|
|
44
|
+
* | "how much is this specified?" | {@link MaterialGraphClient.demand}, {@link MaterialGraphClient.demandBatch} |
|
|
45
|
+
* | "is this allowed in an A-2 corridor?" | `codes.*` |
|
|
46
|
+
* | "what can I even ask for?" | {@link MaterialGraphClient.specificationFacets}, {@link MaterialGraphClient.attributeValues}, {@link MaterialGraphClient.taxonomy}, {@link MaterialGraphClient.colourFacets}, {@link MaterialGraphClient.glossary} |
|
|
47
|
+
* | "do the whole job for me, async" | {@link MaterialGraphClient.createRun} + `mg:compose` |
|
|
48
|
+
*
|
|
49
|
+
* Two structural rules that fall out of that table:
|
|
50
|
+
* - **Colour is a separate lens.** {@link MaterialGraphClient.search} matches
|
|
51
|
+
* WORDS. A colour word in `text` finds materials whose text mentions it, not
|
|
52
|
+
* materials that look it. Route visual intent to the colour lenses.
|
|
53
|
+
* - **Relationships hang off a material, not off a query.** You cannot ask "what
|
|
54
|
+
* pairs with beige oak"; you resolve a material first, then ask what pairs
|
|
55
|
+
* with THAT `variantId`.
|
|
56
|
+
*
|
|
57
|
+
* And three reading rules that apply to every lens: ids are opaque (see
|
|
58
|
+
* `./identifiers.ts`), absent is not false, and a page is not a corpus.
|
|
59
|
+
*/
|
|
31
60
|
export class MaterialGraphClient {
|
|
32
61
|
baseUrl;
|
|
62
|
+
/**
|
|
63
|
+
* THE QUESTION: "show me everything, ordered lightest / most vivid / most
|
|
64
|
+
* muted." Pure colour scalars — no query, no relevance. `browse({ sort })` is
|
|
65
|
+
* one page; `browse.pages({ sort })` walks them all, and only the walk can
|
|
66
|
+
* support a corpus claim.
|
|
67
|
+
*
|
|
68
|
+
* Instead use {@link MaterialGraphClient.byColour} for materials NEAR a colour,
|
|
69
|
+
* {@link MaterialGraphClient.search} when there is anything to rank against,
|
|
70
|
+
* and `colours.profiles()` for a category's palette rather than its members.
|
|
71
|
+
*/
|
|
33
72
|
browse;
|
|
34
73
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
74
|
+
* THE QUESTION: "is this material allowed HERE?" — a jurisdiction + occupancy +
|
|
75
|
+
* finish location, not a property of the material alone. `codes.context()`
|
|
76
|
+
* discovers the vocabulary, `codes.requirements()` states a space,
|
|
77
|
+
* `codes.advisory()` badges candidates.
|
|
78
|
+
*
|
|
79
|
+
* Instead use {@link MaterialGraphClient.bySpec} to find materials by a
|
|
80
|
+
* classification you already know you need; codes is what tells you WHICH one
|
|
81
|
+
* the space demands. ADVISORY ONLY: model code as adopted, never a
|
|
82
|
+
* determination for an authority having jurisdiction.
|
|
38
83
|
*/
|
|
39
84
|
codes;
|
|
40
85
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
86
|
+
* THE QUESTION: "what colours exist here?" (`profiles`, `stats`) and "what
|
|
87
|
+
* matches this whole SET of colours?" (`palette`, `gradient`).
|
|
88
|
+
*
|
|
89
|
+
* Instead use {@link MaterialGraphClient.byColour} for ONE colour,
|
|
90
|
+
* {@link MaterialGraphClient.browse} to walk by a colour scalar, and
|
|
91
|
+
* {@link MaterialGraphClient.colourFacets} for the colour VOCABULARY rather
|
|
92
|
+
* than values. Colour throughout is derived from product imagery — visual
|
|
93
|
+
* likeness, never a declared colour specification.
|
|
43
94
|
*/
|
|
44
95
|
colours;
|
|
45
96
|
#transport;
|
|
@@ -73,6 +124,16 @@ export class MaterialGraphClient {
|
|
|
73
124
|
return this.#transport.requestJson(operation(operationId), { body, signal });
|
|
74
125
|
}
|
|
75
126
|
/**
|
|
127
|
+
* THE QUESTION: "find me materials like this" — a RANKED answer over words, a
|
|
128
|
+
* seed image, colours and a mood, optionally gated by hard specs.
|
|
129
|
+
*
|
|
130
|
+
* Instead use {@link MaterialGraphClient.bySpec} when the spec IS the question
|
|
131
|
+
* (ranking would be noise), {@link MaterialGraphClient.byColour} /
|
|
132
|
+
* `colours.palette()` for visual colour intent (`text` matches WORDS — "sage
|
|
133
|
+
* green" finds materials that SAY sage, not ones that look it),
|
|
134
|
+
* {@link MaterialGraphClient.match} for a photo, {@link MaterialGraphClient.pairings}
|
|
135
|
+
* for what goes WITH something, {@link MaterialGraphClient.suggest} for typeahead.
|
|
136
|
+
*
|
|
76
137
|
* The full search primitive (POST `/api/v1/search`). Layer a hard
|
|
77
138
|
* specification gate onto a ranked (colour / visual / lexical / seed) search
|
|
78
139
|
* via `input.filters` — each entry is a typed {@link SearchFilter} criterion:
|
|
@@ -86,9 +147,28 @@ export class MaterialGraphClient {
|
|
|
86
147
|
search(input, signal) {
|
|
87
148
|
return this.#post("search_materials_post", input, signal);
|
|
88
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* The same question as {@link MaterialGraphClient.search}; the difference is
|
|
152
|
+
* transport — a cacheable, link-shareable GET for queries that must survive in
|
|
153
|
+
* a URL. `filters`, `preferences`, `verdicts`, image bytes and `weights` have
|
|
154
|
+
* no query-string form, so anything using those must go through `search`.
|
|
155
|
+
*/
|
|
89
156
|
searchGet(params, signal) {
|
|
90
157
|
return this.#get("search_materials_get", { ...params }, signal);
|
|
91
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* THE QUESTION: "what materials are in this PHOTO?" — Material Match classifies
|
|
161
|
+
* the image, decomposes it into surfaces, and answers each one.
|
|
162
|
+
*
|
|
163
|
+
* Instead use {@link MaterialGraphClient.matchStream} whenever a human is
|
|
164
|
+
* watching, {@link MaterialGraphClient.search} with `image` for ONE ranked list
|
|
165
|
+
* rather than a per-surface decomposition, and
|
|
166
|
+
* {@link MaterialGraphClient.extractPalette} for the image's colours only.
|
|
167
|
+
*
|
|
168
|
+
* Exactly one of `imageBase64`, `imageUrl` or `vector`. Read `qualityFloor` and
|
|
169
|
+
* `droppedBelowFloor` before reporting an empty result: "nothing was good
|
|
170
|
+
* enough" and "nothing was found" are different answers.
|
|
171
|
+
*/
|
|
92
172
|
match(input, signal) {
|
|
93
173
|
return this.#post("match_material", input, signal);
|
|
94
174
|
}
|
|
@@ -118,6 +198,17 @@ export class MaterialGraphClient {
|
|
|
118
198
|
matchStream(input, signal) {
|
|
119
199
|
return matchStreamCall(this.#transport, input, signal);
|
|
120
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* THE QUESTION: "what materials are near THIS colour?" — one colour (or a small
|
|
203
|
+
* palette), ranked by OKLab closeness to colour extracted from product imagery.
|
|
204
|
+
*
|
|
205
|
+
* Instead use `colours.palette()` for N colours at stated proportions (a
|
|
206
|
+
* different ranking, not a wrapper), `colours.gradient()` for a transition,
|
|
207
|
+
* {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
|
|
208
|
+
* {@link MaterialGraphClient.browse} to walk by lightness/vividness, and
|
|
209
|
+
* {@link MaterialGraphClient.search} when colour is one signal among many.
|
|
210
|
+
* Report matches as visual likeness, never a stated colour specification.
|
|
211
|
+
*/
|
|
121
212
|
byColour(params, signal) {
|
|
122
213
|
return this.#get("search_by_colour", {
|
|
123
214
|
hex: params.hex,
|
|
@@ -130,31 +221,78 @@ export class MaterialGraphClient {
|
|
|
130
221
|
}, signal);
|
|
131
222
|
}
|
|
132
223
|
// ── Materials ──────────────────────────────────────────────────────────────
|
|
133
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* THE QUESTION: "tell me everything about THIS material" (GET
|
|
226
|
+
* `/api/v1/materials/{variantId}`).
|
|
227
|
+
*
|
|
228
|
+
* The hinge of the API: every relationship lens
|
|
229
|
+
* ({@link MaterialGraphClient.siblings}, {@link MaterialGraphClient.pairings},
|
|
230
|
+
* {@link MaterialGraphClient.demand}, `codes.advisory()`) hangs off a
|
|
231
|
+
* `variantId`. Instead use {@link MaterialGraphClient.materials} for many ids,
|
|
232
|
+
* {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
|
|
233
|
+
* checked", {@link MaterialGraphClient.search} when you have no id yet.
|
|
234
|
+
*
|
|
235
|
+
* @param variantId Opaque — pass back exactly what you were given.
|
|
236
|
+
*/
|
|
134
237
|
material(variantId, options, signal) {
|
|
135
238
|
return this.#get("get_material", options?.includeSiblings ? { include: "siblings" } : undefined, signal, { variantId });
|
|
136
239
|
}
|
|
137
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* THE QUESTION: "what other colourways and finishes does this product come in?"
|
|
242
|
+
* — the SAME product, different variants.
|
|
243
|
+
*
|
|
244
|
+
* Instead use {@link MaterialGraphClient.pairings} for a DIFFERENT product that
|
|
245
|
+
* goes with this one, and `search({ similarTo })` for one that LOOKS like it.
|
|
246
|
+
*/
|
|
138
247
|
siblings(variantId, signal) {
|
|
139
248
|
return this.#get("get_material_siblings", undefined, signal, { variantId });
|
|
140
249
|
}
|
|
141
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* THE QUESTION: "what do we actually KNOW about this material, and what have we
|
|
252
|
+
* simply not checked?" The only read that distinguishes a determined absence
|
|
253
|
+
* from an unasked question — every field carries a `status`, so this is the one
|
|
254
|
+
* surface entitled to back a negative ("no fire rating").
|
|
255
|
+
* {@link MaterialGraphClient.material} cannot: there an absence is merely unknown.
|
|
256
|
+
*/
|
|
142
257
|
materialBlocks(variantId, signal) {
|
|
143
258
|
return this.#get("list_material_blocks", undefined, signal, {
|
|
144
259
|
variantId,
|
|
145
260
|
});
|
|
146
261
|
}
|
|
147
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* THE QUESTION: "what images and documents may I SHOW for this material?" — the
|
|
264
|
+
* redistribution-safe set (GET `/api/v1/materials/{variantId}/assets`), not
|
|
265
|
+
* every file on record.
|
|
266
|
+
*/
|
|
148
267
|
materialAssets(variantId, signal) {
|
|
149
268
|
return this.#get("list_material_assets", undefined, signal, {
|
|
150
269
|
variantId,
|
|
151
270
|
});
|
|
152
271
|
}
|
|
153
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* THE QUESTION: "what does the schema DEFINE?" — the governed static registry.
|
|
274
|
+
*
|
|
275
|
+
* Definitions, not data: use {@link MaterialGraphClient.specificationFacets} for
|
|
276
|
+
* what the corpus actually holds and {@link MaterialGraphClient.glossary} to
|
|
277
|
+
* explain one term.
|
|
278
|
+
*/
|
|
154
279
|
schema(resource, signal) {
|
|
155
280
|
return this.#get(SCHEMA_RESOURCE_OPERATION_IDS[resource], undefined, signal);
|
|
156
281
|
}
|
|
157
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* THE QUESTION: "what goes WITH this material?" — the companionship rail, hung
|
|
284
|
+
* off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
|
|
285
|
+
*
|
|
286
|
+
* Instead use {@link MaterialGraphClient.coordinate} for a whole palette of
|
|
287
|
+
* picks, {@link MaterialGraphClient.siblings} for the same product in another
|
|
288
|
+
* colour, and `search({ similarTo })` for a LIKE-THIS substitute — similar and
|
|
289
|
+
* complementary are opposite questions.
|
|
290
|
+
*
|
|
291
|
+
* READ `item.source` BEFORE PHRASING: the rail mixes three tiers of evidence —
|
|
292
|
+
* `cospec` (real orders), `considered` (board activity), `colour` (palette
|
|
293
|
+
* look-alike). Only `cospec` supports "specified together". `cospecCount` tells
|
|
294
|
+
* you how much of the rail is real order evidence.
|
|
295
|
+
*/
|
|
158
296
|
pairings(variantId, options, signal) {
|
|
159
297
|
return this.#get("get_material_pairings", { categories: options?.categories, limit: options?.limit }, signal, { variantId });
|
|
160
298
|
}
|
|
@@ -175,11 +313,20 @@ export class MaterialGraphClient {
|
|
|
175
313
|
}, signal);
|
|
176
314
|
}
|
|
177
315
|
// ── Colour helpers ───────────────────────────────────────────────────────────
|
|
178
|
-
/**
|
|
316
|
+
/**
|
|
317
|
+
* THE QUESTION: "what colour WORDS may I use?" — family, mood and undertone
|
|
318
|
+
* with live counts. Vocabulary, not values: instead use `colours.profiles()`
|
|
319
|
+
* for a category's palette, {@link MaterialGraphClient.byColour} to search by a
|
|
320
|
+
* colour, {@link MaterialGraphClient.resolveColour} to turn a NAME into a hex.
|
|
321
|
+
*/
|
|
179
322
|
colourFacets(signal) {
|
|
180
323
|
return this.#get("get_colour_facets", undefined, signal);
|
|
181
324
|
}
|
|
182
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* THE QUESTION: "what hex is 'Hague Blue'?" — the step before
|
|
327
|
+
* {@link MaterialGraphClient.byColour} when you hold a name rather than a hex.
|
|
328
|
+
* A `null` match means unrecognised, not "no such colour".
|
|
329
|
+
*/
|
|
183
330
|
resolveColour(name, signal) {
|
|
184
331
|
return this.#get("resolve_colour_name", { name }, signal);
|
|
185
332
|
}
|
|
@@ -188,6 +335,11 @@ export class MaterialGraphClient {
|
|
|
188
335
|
return this.#get("suggest_colour_names", { q, limit: options?.limit }, signal);
|
|
189
336
|
}
|
|
190
337
|
/**
|
|
338
|
+
* THE QUESTION: "what is the user starting to type?" It ranks NAMES, not
|
|
339
|
+
* materials — instead use {@link MaterialGraphClient.search} once they commit,
|
|
340
|
+
* {@link MaterialGraphClient.suggestColour} for colour names, and
|
|
341
|
+
* {@link MaterialGraphClient.searchVocabulary} for attribute/value tokens.
|
|
342
|
+
*
|
|
191
343
|
* Entity typeahead across products, brands and categories
|
|
192
344
|
* (GET `/api/v1/search/suggest`). Built for per-keystroke use: one indexed
|
|
193
345
|
* lookup, no embedding call and no model. Every suggestion carries the
|
|
@@ -202,19 +354,53 @@ export class MaterialGraphClient {
|
|
|
202
354
|
return this.#get("search_suggest", suggestQuery(q, options), signal);
|
|
203
355
|
}
|
|
204
356
|
// ── Catalog ──────────────────────────────────────────────────────────────────
|
|
205
|
-
/**
|
|
357
|
+
/**
|
|
358
|
+
* THE QUESTION: "the user said 'fire rating' — which canonical attribute is
|
|
359
|
+
* that?" Free text in, canonical `attributeId`s out; pair with
|
|
360
|
+
* {@link MaterialGraphClient.resolveAttributeValue} for the VALUE, then spend
|
|
361
|
+
* the pair on {@link MaterialGraphClient.bySpec} or `search({ filters })`.
|
|
362
|
+
* Instead use {@link MaterialGraphClient.specificationFacets} to browse rather
|
|
363
|
+
* than resolve.
|
|
364
|
+
*/
|
|
206
365
|
resolveAttribute(q, options, signal) {
|
|
207
366
|
return this.#get("catalog_resolve_attribute", { q, limit: options?.limit, facetableOnly: options?.facetableOnly }, signal);
|
|
208
367
|
}
|
|
209
|
-
/**
|
|
368
|
+
/**
|
|
369
|
+
* THE QUESTION: "the user said 'class A' — what canonical term is that, under
|
|
370
|
+
* THIS attribute?" The value half of the pair
|
|
371
|
+
* {@link MaterialGraphClient.resolveAttribute} starts. Read
|
|
372
|
+
* `resolution.recommendedAction`, not just `best`: `ambiguous` means ask.
|
|
373
|
+
*
|
|
374
|
+
* @param attributeId Canonical vocabulary, not an opaque id — discover it, do
|
|
375
|
+
* not invent it.
|
|
376
|
+
*/
|
|
210
377
|
resolveAttributeValue(attributeId, query, options, signal) {
|
|
211
378
|
return this.#get("catalog_resolve_value", { q: query, limit: options?.limit }, signal, { attributeId });
|
|
212
379
|
}
|
|
213
|
-
/**
|
|
380
|
+
/**
|
|
381
|
+
* THE QUESTION: "is 'FloorScore' an attribute or a value, and what is it called
|
|
382
|
+
* here?" The catch-all when you do not know which half of an `attribute:value`
|
|
383
|
+
* pair a phrase belongs to. Instead use
|
|
384
|
+
* {@link MaterialGraphClient.resolveAttribute} /
|
|
385
|
+
* {@link MaterialGraphClient.resolveAttributeValue} once you do, and
|
|
386
|
+
* {@link MaterialGraphClient.glossary} to EXPLAIN a term rather than find it.
|
|
387
|
+
*/
|
|
214
388
|
searchVocabulary(query, options, signal) {
|
|
215
389
|
return this.#get("catalog_vocabulary_search", { q: query, limit: options?.limit, kind: options?.kind }, signal);
|
|
216
390
|
}
|
|
217
391
|
/**
|
|
392
|
+
* THE QUESTION: "which materials, from ANYONE, are ASTM E84 Class A AND
|
|
393
|
+
* GREENGUARD-certified?" — hard specifications, no ranking, every brand at once
|
|
394
|
+
* whatever each called the value in source.
|
|
395
|
+
*
|
|
396
|
+
* Instead use `search({ filters })` when the spec merely GATES a ranked
|
|
397
|
+
* question, `codes.advisory()` when the real question is what a space demands,
|
|
398
|
+
* and {@link MaterialGraphClient.specificationFacets} first if you do not know
|
|
399
|
+
* the canonical tokens — guessing one returns an honest, empty answer.
|
|
400
|
+
*
|
|
401
|
+
* A PAGE, NOT A CORPUS, unusually so: `totalMatches` is the size of the
|
|
402
|
+
* returned sample under `limit`, not a corpus count. See {@link BySpecResponse}.
|
|
403
|
+
*
|
|
218
404
|
* Find materials by canonical specification across every brand — the
|
|
219
405
|
* value-anchored, spec-only primitive (GET `/api/v1/search/specifications`).
|
|
220
406
|
* Each `spec` is an `attribute:value` criterion; a variant must satisfy ALL of
|
|
@@ -229,16 +415,31 @@ export class MaterialGraphClient {
|
|
|
229
415
|
return this.#get("catalog_by_spec", { spec: Array.isArray(spec) ? spec : [spec], limit: options?.limit }, signal);
|
|
230
416
|
}
|
|
231
417
|
// ── Taxonomy + meta ──────────────────────────────────────────────────────────
|
|
232
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* THE QUESTION: "what kinds of thing exist, and what are their canonical keys?"
|
|
420
|
+
* The source of every `categoryKey` / product-type id the other lenses accept
|
|
421
|
+
* (`search({ category })`, `colours.profiles()`,
|
|
422
|
+
* {@link MaterialGraphClient.applicableSpecs}). Discover, never hand-write.
|
|
423
|
+
*/
|
|
233
424
|
taxonomy(signal) {
|
|
234
425
|
return this.#get("get_category_taxonomy", undefined, signal);
|
|
235
426
|
}
|
|
236
|
-
/**
|
|
427
|
+
/**
|
|
428
|
+
* THE QUESTION: "what is this key allowed to do?" Check `scopes` before
|
|
429
|
+
* offering a capability: the write surfaces need `mg:ingest` / `mg:compose`,
|
|
430
|
+
* and `mg:admin` unlocks extra evidence fields.
|
|
431
|
+
*/
|
|
237
432
|
whoami(signal) {
|
|
238
433
|
return this.#get("whoami", undefined, signal);
|
|
239
434
|
}
|
|
240
435
|
// ── Pairings / demand ────────────────────────────────────────────────────────
|
|
241
436
|
/**
|
|
437
|
+
* THE QUESTION: "here is my whole scheme so far — what completes it?" The
|
|
438
|
+
* multi-seed form of {@link MaterialGraphClient.pairings}: lift is summed across
|
|
439
|
+
* seeds, so a companion suiting one pick ranks below one suiting several.
|
|
440
|
+
* Instead use `pairings` for a single material and `colours.palette()` when the
|
|
441
|
+
* scheme is COLOURS rather than materials. Check `unresolved` before presenting.
|
|
442
|
+
*
|
|
242
443
|
* Coordinate a palette of already-chosen materials (POST
|
|
243
444
|
* `/api/v1/pairings/coordinate`). Returns demand-lift-ranked companions with
|
|
244
445
|
* colour swatches and an annotate-only `colourFit` (never reorders in v1).
|
|
@@ -247,13 +448,25 @@ export class MaterialGraphClient {
|
|
|
247
448
|
return this.#post("coordinate_materials", input, signal);
|
|
248
449
|
}
|
|
249
450
|
/**
|
|
250
|
-
*
|
|
251
|
-
*
|
|
451
|
+
* THE QUESTION: "how much is this material actually specified, and what gets
|
|
452
|
+
* specified alongside it?" — real order behaviour, trailing 36 months.
|
|
453
|
+
*
|
|
454
|
+
* Instead use {@link MaterialGraphClient.demandBatch} for many ids (this one
|
|
455
|
+
* also resolves free text, which the batch deliberately does not) and
|
|
456
|
+
* {@link MaterialGraphClient.pairings} for a presentable rail. A null `demand`
|
|
457
|
+
* means UNREAD, never zero. Provide EXACTLY ONE of `variantId` or `material`.
|
|
252
458
|
*/
|
|
253
459
|
demand(params, signal) {
|
|
254
460
|
return this.#get("get_material_demand", { variantId: params.variantId, material: params.material }, signal);
|
|
255
461
|
}
|
|
256
462
|
/**
|
|
463
|
+
* THE QUESTION: "for each of these picks I already hold, how much momentum does
|
|
464
|
+
* it have?" Enrichment, never discovery or ranking. Instead use
|
|
465
|
+
* {@link MaterialGraphClient.demand} for one material named in free text and
|
|
466
|
+
* {@link MaterialGraphClient.search} to find candidates at all. Only
|
|
467
|
+
* `state: "available"` carries numbers; the other three mean "no number", never
|
|
468
|
+
* "no demand".
|
|
469
|
+
*
|
|
257
470
|
* Enrich a bounded set of ALREADY-CHOSEN candidate ids with their real
|
|
258
471
|
* specification demand (POST `/api/v1/demand/batch`, 1–48 ids). The bounded
|
|
259
472
|
* batch complement to {@link demand}: no fuzzy resolution, ranking, or
|
|
@@ -278,20 +491,36 @@ export class MaterialGraphClient {
|
|
|
278
491
|
}
|
|
279
492
|
// ── Codes (advisory building-code compliance) ────────────────────────────────
|
|
280
493
|
/**
|
|
494
|
+
* THE QUESTION: "may I put these materials in an A-2 corridor in Texas?"
|
|
495
|
+
*
|
|
281
496
|
* Badge materials against the advisory building-code registry (POST
|
|
282
497
|
* `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
|
|
283
|
-
* flat because it shipped before the `codes` namespace existed
|
|
498
|
+
* flat because it shipped before the `codes` namespace existed — prefer
|
|
499
|
+
* `mg.codes.advisory()` in new code, and start at `mg.codes.context()` to
|
|
500
|
+
* discover every accepted context value. `unverified` is not a pass.
|
|
284
501
|
*/
|
|
285
502
|
codeAdvisory(input, signal) {
|
|
286
503
|
return this.codes.advisory(input, signal);
|
|
287
504
|
}
|
|
288
505
|
// ── Colour helpers (extract) ─────────────────────────────────────────────────
|
|
289
|
-
/**
|
|
506
|
+
/**
|
|
507
|
+
* THE QUESTION: "what colours are in this image?" — the palette only, no
|
|
508
|
+
* materials. The bridge from an inspiration image into the colour lens: extract
|
|
509
|
+
* here, then feed the swatches to `colours.palette()` or
|
|
510
|
+
* {@link MaterialGraphClient.byColour}. Instead use
|
|
511
|
+
* {@link MaterialGraphClient.match} for the MATERIALS in the photo.
|
|
512
|
+
*/
|
|
290
513
|
extractPalette(input, signal) {
|
|
291
514
|
return this.#post("extract_image_palette", input, signal);
|
|
292
515
|
}
|
|
293
516
|
// ── Catalog (facets / applicable specs / glossary) ───────────────────────────
|
|
294
517
|
/**
|
|
518
|
+
* THE QUESTION: "what can I even filter on, and what values does the corpus
|
|
519
|
+
* actually hold?" The start of every spec-driven workflow. Instead use
|
|
520
|
+
* {@link MaterialGraphClient.attributeValues} for ONE attribute's distribution,
|
|
521
|
+
* {@link MaterialGraphClient.applicableSpecs} for what a product type COULD
|
|
522
|
+
* carry, {@link MaterialGraphClient.schema} for the definition.
|
|
523
|
+
*
|
|
295
524
|
* Discover the live specification vocabulary (GET
|
|
296
525
|
* `/api/v1/catalog/specifications/facets`) — the facetable spec attributes that
|
|
297
526
|
* have real product-graph data, each with its top canonical values and the
|
|
@@ -318,20 +547,43 @@ export class MaterialGraphClient {
|
|
|
318
547
|
attributeId,
|
|
319
548
|
});
|
|
320
549
|
}
|
|
321
|
-
/**
|
|
550
|
+
/**
|
|
551
|
+
* THE QUESTION: "what specs COULD a material of this type carry?" — the
|
|
552
|
+
* template, not the data. Use it to know which absences are meaningful; instead
|
|
553
|
+
* use {@link MaterialGraphClient.specificationFacets} for what the corpus holds
|
|
554
|
+
* and {@link MaterialGraphClient.materialBlocks} for one material.
|
|
555
|
+
*
|
|
556
|
+
* @param key A canonical taxonomy key from {@link MaterialGraphClient.taxonomy}.
|
|
557
|
+
*/
|
|
322
558
|
applicableSpecs(key, signal) {
|
|
323
559
|
return this.#get("get_applicable_specs", undefined, signal, { key });
|
|
324
560
|
}
|
|
325
|
-
/**
|
|
561
|
+
/**
|
|
562
|
+
* THE QUESTION: "what does 'DCOF' MEAN?" — one term explained, for a human.
|
|
563
|
+
* Explanation, not retrieval: instead use
|
|
564
|
+
* {@link MaterialGraphClient.searchVocabulary} to FIND a token and
|
|
565
|
+
* {@link MaterialGraphClient.attributeValues} for the values it takes.
|
|
566
|
+
*/
|
|
326
567
|
glossary(term, signal) {
|
|
327
568
|
return this.#get("explain_material_term", undefined, signal, { term });
|
|
328
569
|
}
|
|
329
570
|
// ── Brands ───────────────────────────────────────────────────────────────────
|
|
330
|
-
/**
|
|
571
|
+
/**
|
|
572
|
+
* THE QUESTION: "whose catalogs are in here, and how big is each?" The SDK's
|
|
573
|
+
* one OFFSET-paginated surface: read `meta.total` for the collection size and
|
|
574
|
+
* `meta.hasMore` to keep walking. Instead use {@link MaterialGraphClient.brand}
|
|
575
|
+
* for one brand's profile and trade signals.
|
|
576
|
+
*/
|
|
331
577
|
brands(options = {}, signal) {
|
|
332
578
|
return this.#get("list_brands", { q: options.q, limit: options.limit, offset: options.offset }, signal);
|
|
333
579
|
}
|
|
334
|
-
/**
|
|
580
|
+
/**
|
|
581
|
+
* THE QUESTION: "who is this brand, and what do they claim?" — profile plus the
|
|
582
|
+
* trade/spec signals ON RECORD. `trade` is keyed by attribute id and an ABSENT
|
|
583
|
+
* key means no value is held — never infer `false`. Instead use
|
|
584
|
+
* {@link MaterialGraphClient.search} to find their materials; this describes the
|
|
585
|
+
* company, not the catalog.
|
|
586
|
+
*/
|
|
335
587
|
brand(brandSlug, options, signal) {
|
|
336
588
|
return this.#get("get_brand_profile", { q: options?.q }, signal, {
|
|
337
589
|
brandSlug,
|
|
@@ -358,15 +610,33 @@ export class MaterialGraphClient {
|
|
|
358
610
|
sourceMaterialBatch(batchId, signal) {
|
|
359
611
|
return getSourceMaterialBatchCall(this.#transport, batchId, signal);
|
|
360
612
|
}
|
|
361
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* THE QUESTION: "what can the hosted agentic surface be asked to DO?" Start
|
|
615
|
+
* here before {@link MaterialGraphClient.createRun}: each capability carries its
|
|
616
|
+
* live input/output schemas, scopes and side-effect class, so nothing about a
|
|
617
|
+
* Run need be hardcoded.
|
|
618
|
+
*/
|
|
362
619
|
capabilities(signal) {
|
|
363
620
|
return capabilitiesRun(this.#transport, signal);
|
|
364
621
|
}
|
|
365
|
-
/**
|
|
622
|
+
/**
|
|
623
|
+
* THE QUESTION: "do a whole job for me" — the ASYNC lens. Everything else here
|
|
624
|
+
* answers in one round trip; a Run may suspend for human input and finish
|
|
625
|
+
* minutes later. Follow with {@link MaterialGraphClient.streamRunEvents} (live)
|
|
626
|
+
* or {@link MaterialGraphClient.run} (poll); resume via
|
|
627
|
+
* {@link MaterialGraphClient.resumeRun}; pick the capability from
|
|
628
|
+
* {@link MaterialGraphClient.capabilities}. Pass `idempotencyKey` so a retry
|
|
629
|
+
* replays rather than duplicates.
|
|
630
|
+
*/
|
|
366
631
|
createRun(body, signal) {
|
|
367
632
|
return createRun(this.#transport, body, signal);
|
|
368
633
|
}
|
|
369
|
-
/**
|
|
634
|
+
/**
|
|
635
|
+
* THE QUESTION: "where has this Run got to?" — the caller-owned, redacted
|
|
636
|
+
* ledger record. Polling; prefer {@link MaterialGraphClient.streamRunEvents}
|
|
637
|
+
* when a human is waiting. `suspended` is not a failure — it means the Run is
|
|
638
|
+
* asking you something (see {@link MaterialGraphClient.resumeRun}).
|
|
639
|
+
*/
|
|
370
640
|
run(runId, signal) {
|
|
371
641
|
return getRun(this.#transport, runId, signal);
|
|
372
642
|
}
|
|
@@ -390,7 +660,11 @@ export class MaterialGraphClient {
|
|
|
390
660
|
resumeRun(runId, resumeData, signalOrOptions) {
|
|
391
661
|
return resumeRun(this.#transport, runId, resumeData, signalOrOptions);
|
|
392
662
|
}
|
|
393
|
-
/**
|
|
663
|
+
/**
|
|
664
|
+
* THE QUESTION: "stop this Run." Requests DURABLE cancellation — the Run
|
|
665
|
+
* acknowledges and unwinds; the call returning does not mean it has stopped.
|
|
666
|
+
* Poll {@link MaterialGraphClient.run} for the terminal state.
|
|
667
|
+
*/
|
|
394
668
|
cancelRun(runId, signal) {
|
|
395
669
|
return cancelRun(this.#transport, runId, signal);
|
|
396
670
|
}
|
|
@@ -421,7 +695,11 @@ export class MaterialGraphClient {
|
|
|
421
695
|
createProject(body = {}, signal) {
|
|
422
696
|
return createProjectCall(this.#transport, body, signal);
|
|
423
697
|
}
|
|
424
|
-
/**
|
|
698
|
+
/**
|
|
699
|
+
* THE QUESTION: "what has happened on this project?" — the append-only event log
|
|
700
|
+
* FOLDED into a read model, plus the raw `events`. Read the folded collections;
|
|
701
|
+
* the log is there for audit.
|
|
702
|
+
*/
|
|
425
703
|
getProject(projectId, signal) {
|
|
426
704
|
return getProjectCall(this.#transport, projectId, signal);
|
|
427
705
|
}
|
|
@@ -432,7 +710,10 @@ export class MaterialGraphClient {
|
|
|
432
710
|
appendProjectEvent(projectId, body, signal) {
|
|
433
711
|
return appendProjectEventCall(this.#transport, projectId, body, signal);
|
|
434
712
|
}
|
|
435
|
-
/**
|
|
713
|
+
/**
|
|
714
|
+
* THE QUESTION: "what Boxes do I have?" Scoped to the authenticated principal —
|
|
715
|
+
* another key's projects are invisible, not forbidden.
|
|
716
|
+
*/
|
|
436
717
|
listProjects(signal) {
|
|
437
718
|
return listProjectsCall(this.#transport, signal);
|
|
438
719
|
}
|
package/codes-client.d.ts
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
*
|
|
13
13
|
* ADVISORY ONLY: every verdict and every envelope carries the AHJ caveat — this
|
|
14
14
|
* is model code as adopted, never a legal determination.
|
|
15
|
+
*
|
|
16
|
+
* ROUTING. Codes answers "is this material allowed in THIS SPACE?" — a question
|
|
17
|
+
* about a jurisdiction, an occupancy group and a finish location, not about a
|
|
18
|
+
* material alone.
|
|
19
|
+
* - "which materials carry Class A?" → `mg.bySpec("fire_classifications:...")`.
|
|
20
|
+
* That is a property question; this namespace is what tells you the space
|
|
21
|
+
* demands Class A in the first place.
|
|
22
|
+
* - "what does this one material hold?" → `mg.materialBlocks(variantId)`.
|
|
23
|
+
* - "find me something like this, but compliant" → `mg.search({ filters })`
|
|
24
|
+
* once `requirements()` has told you which filter to apply.
|
|
15
25
|
*/
|
|
16
26
|
import type { CodeAdvisoryInput, CodeAdvisoryResponse, CodeContextRequest, CodeContextResponse, CodeRequirementsResponse } from "./codes-types.js";
|
|
17
27
|
import type { QueryParams } from "./http-transport.js";
|
|
@@ -25,6 +35,13 @@ export declare class CodesClient {
|
|
|
25
35
|
#private;
|
|
26
36
|
constructor(transport: CodesTransport);
|
|
27
37
|
/**
|
|
38
|
+
* THE QUESTION: "what may I even say about a space?" — the vocabulary for
|
|
39
|
+
* stating a code context, and the first call of the three.
|
|
40
|
+
*
|
|
41
|
+
* Everything {@link CodesClient.requirements} and {@link CodesClient.advisory}
|
|
42
|
+
* accept is listed here, so nothing need be hardcoded and a 400 for an unknown
|
|
43
|
+
* jurisdiction / occupancy / space is always avoidable.
|
|
44
|
+
*
|
|
28
45
|
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
29
46
|
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
30
47
|
* registry keys on, finish locations, dimensions, classification scales with
|
|
@@ -43,6 +60,12 @@ export declare class CodesClient {
|
|
|
43
60
|
*/
|
|
44
61
|
context(signal?: AbortSignal): Promise<CodeContextResponse>;
|
|
45
62
|
/**
|
|
63
|
+
* THE QUESTION: "what does this space DEMAND?" — before any candidate exists.
|
|
64
|
+
*
|
|
65
|
+
* Run it before shortlisting, not after: it tells you which specification to
|
|
66
|
+
* filter on, which you then spend on `mg.bySpec()` or `mg.search({ filters })`.
|
|
67
|
+
* Use {@link CodesClient.advisory} once you hold candidates to badge.
|
|
68
|
+
*
|
|
46
69
|
* What a space demands, before any candidate exists (GET
|
|
47
70
|
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
48
71
|
* `roomType` the axes are correlated from.
|
|
@@ -61,6 +84,19 @@ export declare class CodesClient {
|
|
|
61
84
|
*/
|
|
62
85
|
requirements(input: CodeContextRequest, signal?: AbortSignal): Promise<CodeRequirementsResponse>;
|
|
63
86
|
/**
|
|
87
|
+
* THE QUESTION: "do THESE candidates satisfy what this space demands?" — the
|
|
88
|
+
* third call, once you hold materials.
|
|
89
|
+
*
|
|
90
|
+
* Context is CONDITIONAL: supply `occupancyGroup` + `spaceType`, or a
|
|
91
|
+
* `roomType` for them to correlate from, or a mix — an explicit axis always
|
|
92
|
+
* wins, and an axis that resolves to nothing is a 400 naming it. The full rule,
|
|
93
|
+
* with the validator's own messages, is on {@link CodeContextRequest}.
|
|
94
|
+
*
|
|
95
|
+
* Read `summary.status` honestly: `unverified` means no comparable rating is on
|
|
96
|
+
* file. It is not a pass, and it is common. Use {@link CodesClient.requirements}
|
|
97
|
+
* when you have no candidates yet, and `mg.materialBlocks()` to see whether a
|
|
98
|
+
* material's fire/slip facts are missing or merely unverified.
|
|
99
|
+
*
|
|
64
100
|
* Badge materials against the advisory building-code registry for one context
|
|
65
101
|
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
66
102
|
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|
package/codes-client.js
CHANGED
|
@@ -4,6 +4,13 @@ export class CodesClient {
|
|
|
4
4
|
this.#transport = transport;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
+
* THE QUESTION: "what may I even say about a space?" — the vocabulary for
|
|
8
|
+
* stating a code context, and the first call of the three.
|
|
9
|
+
*
|
|
10
|
+
* Everything {@link CodesClient.requirements} and {@link CodesClient.advisory}
|
|
11
|
+
* accept is listed here, so nothing need be hardcoded and a 400 for an unknown
|
|
12
|
+
* jurisdiction / occupancy / space is always avoidable.
|
|
13
|
+
*
|
|
7
14
|
* The vocabulary for stating a code context (GET `/api/v1/codes/context`):
|
|
8
15
|
* seeded jurisdictions and their coverage tier, the occupancy groups the
|
|
9
16
|
* registry keys on, finish locations, dimensions, classification scales with
|
|
@@ -24,6 +31,12 @@ export class CodesClient {
|
|
|
24
31
|
return this.#transport.get("list_code_context", undefined, signal);
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
34
|
+
* THE QUESTION: "what does this space DEMAND?" — before any candidate exists.
|
|
35
|
+
*
|
|
36
|
+
* Run it before shortlisting, not after: it tells you which specification to
|
|
37
|
+
* filter on, which you then spend on `mg.bySpec()` or `mg.search({ filters })`.
|
|
38
|
+
* Use {@link CodesClient.advisory} once you hold candidates to badge.
|
|
39
|
+
*
|
|
27
40
|
* What a space demands, before any candidate exists (GET
|
|
28
41
|
* `/api/v1/codes/requirements`). State the space by explicit axes, or by a
|
|
29
42
|
* `roomType` the axes are correlated from.
|
|
@@ -50,6 +63,19 @@ export class CodesClient {
|
|
|
50
63
|
}, signal);
|
|
51
64
|
}
|
|
52
65
|
/**
|
|
66
|
+
* THE QUESTION: "do THESE candidates satisfy what this space demands?" — the
|
|
67
|
+
* third call, once you hold materials.
|
|
68
|
+
*
|
|
69
|
+
* Context is CONDITIONAL: supply `occupancyGroup` + `spaceType`, or a
|
|
70
|
+
* `roomType` for them to correlate from, or a mix — an explicit axis always
|
|
71
|
+
* wins, and an axis that resolves to nothing is a 400 naming it. The full rule,
|
|
72
|
+
* with the validator's own messages, is on {@link CodeContextRequest}.
|
|
73
|
+
*
|
|
74
|
+
* Read `summary.status` honestly: `unverified` means no comparable rating is on
|
|
75
|
+
* file. It is not a pass, and it is common. Use {@link CodesClient.requirements}
|
|
76
|
+
* when you have no candidates yet, and `mg.materialBlocks()` to see whether a
|
|
77
|
+
* material's fire/slip facts are missing or merely unverified.
|
|
78
|
+
*
|
|
53
79
|
* Badge materials against the advisory building-code registry for one context
|
|
54
80
|
* (POST `/api/v1/codes/advisory`, ≤ 100 ids). Each applicable requirement is
|
|
55
81
|
* badged `meets` / `shortfall` / `unknown` from the material's accepted fire
|