@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/spec-types.d.ts
CHANGED
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
* class, but with an EPD on record" reaches for a NAMED, typed criterion instead
|
|
12
12
|
* of hand-assembling a magic `attribute:value` string.
|
|
13
13
|
*
|
|
14
|
-
* These criteria drive two operations equally
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* These criteria drive two operations equally, and choosing between them IS the
|
|
15
|
+
* routing decision:
|
|
16
|
+
* - `client.search({ filters: [...] })` — spec gates layered onto a RANKED
|
|
17
|
+
* (colour / visual / lexical) search. Reach for it when relevance still
|
|
18
|
+
* matters: "something like this seed, but Class A".
|
|
19
|
+
* - `client.bySpec(...)` — the value-anchored, SPEC-ONLY primitive, which takes
|
|
18
20
|
* the same criteria as `attribute:value` strings (build them with the helpers
|
|
19
|
-
* in `./specifications`).
|
|
21
|
+
* in `./specifications`). Reach for it when the spec is the whole question and
|
|
22
|
+
* ranking would be noise.
|
|
23
|
+
*
|
|
24
|
+
* Discover the tokens before you spend them: `client.specificationFacets()` for
|
|
25
|
+
* what is facetable at all, `client.attributeValues()` for one attribute's
|
|
26
|
+
* values, `client.resolveAttribute()` / `client.resolveAttributeValue()` to turn
|
|
27
|
+
* a phrase into a canonical pair. A hand-guessed token returns an honest empty
|
|
28
|
+
* answer, which is the hardest kind of bug to see.
|
|
20
29
|
*/
|
|
21
30
|
/**
|
|
22
31
|
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
package/spec-types.js
CHANGED
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
* class, but with an EPD on record" reaches for a NAMED, typed criterion instead
|
|
12
12
|
* of hand-assembling a magic `attribute:value` string.
|
|
13
13
|
*
|
|
14
|
-
* These criteria drive two operations equally
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* These criteria drive two operations equally, and choosing between them IS the
|
|
15
|
+
* routing decision:
|
|
16
|
+
* - `client.search({ filters: [...] })` — spec gates layered onto a RANKED
|
|
17
|
+
* (colour / visual / lexical) search. Reach for it when relevance still
|
|
18
|
+
* matters: "something like this seed, but Class A".
|
|
19
|
+
* - `client.bySpec(...)` — the value-anchored, SPEC-ONLY primitive, which takes
|
|
18
20
|
* the same criteria as `attribute:value` strings (build them with the helpers
|
|
19
|
-
* in `./specifications`).
|
|
21
|
+
* in `./specifications`). Reach for it when the spec is the whole question and
|
|
22
|
+
* ranking would be noise.
|
|
23
|
+
*
|
|
24
|
+
* Discover the tokens before you spend them: `client.specificationFacets()` for
|
|
25
|
+
* what is facetable at all, `client.attributeValues()` for one attribute's
|
|
26
|
+
* values, `client.resolveAttribute()` / `client.resolveAttributeValue()` to turn
|
|
27
|
+
* a phrase into a canonical pair. A hand-guessed token returns an honest empty
|
|
28
|
+
* answer, which is the hardest kind of bug to see.
|
|
20
29
|
*/
|
|
21
30
|
/**
|
|
22
31
|
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
package/suggest-types.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* Split out of `types.ts` for the repository's source-file size budget, the same
|
|
7
7
|
* reason `catalog-discovery-types.ts` sits beside it; both are re-exported from
|
|
8
8
|
* `types.ts`, so importers see no difference.
|
|
9
|
+
*
|
|
10
|
+
* THE LENS: entity typeahead — it ranks NAMES, not materials. `mg.search()` is
|
|
11
|
+
* what answers a committed query; `mg.suggestColour()` covers colour names and
|
|
12
|
+
* `mg.searchVocabulary()` covers attribute/value tokens. Suggestions are a page
|
|
13
|
+
* of the best matches, never a census of matching entities.
|
|
9
14
|
*/
|
|
10
15
|
/** Which kind of entity a suggestion is — and therefore what `refId` means. */
|
|
11
16
|
export type SuggestKind = "product" | "brand" | "category";
|
|
@@ -25,8 +30,12 @@ export interface Suggestion {
|
|
|
25
30
|
brandSlug: string | null;
|
|
26
31
|
brandName: string | null;
|
|
27
32
|
categoryKey: string | null;
|
|
28
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* A representative variant, so a client can open a material record directly.
|
|
35
|
+
* Opaque, and null when none was picked — not a product without variants.
|
|
36
|
+
*/
|
|
29
37
|
variantId: string | null;
|
|
38
|
+
/** Variants behind this entity IN MATERIALGRAPH — a coverage figure. */
|
|
30
39
|
variantCount: number;
|
|
31
40
|
}
|
|
32
41
|
/** The suggest response: the query as interpreted, plus the ranked suggestions. */
|
package/suggest-types.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* Split out of `types.ts` for the repository's source-file size budget, the same
|
|
7
7
|
* reason `catalog-discovery-types.ts` sits beside it; both are re-exported from
|
|
8
8
|
* `types.ts`, so importers see no difference.
|
|
9
|
+
*
|
|
10
|
+
* THE LENS: entity typeahead — it ranks NAMES, not materials. `mg.search()` is
|
|
11
|
+
* what answers a committed query; `mg.suggestColour()` covers colour names and
|
|
12
|
+
* `mg.searchVocabulary()` covers attribute/value tokens. Suggestions are a page
|
|
13
|
+
* of the best matches, never a census of matching entities.
|
|
9
14
|
*/
|
|
10
15
|
/**
|
|
11
16
|
* Shape the query string for a suggest call.
|
package/types.d.ts
CHANGED
|
@@ -11,8 +11,22 @@
|
|
|
11
11
|
* that mirrors a server enum is therefore widened with `(string & {})` so a new
|
|
12
12
|
* server value still type-checks, and objects that may grow fields are read
|
|
13
13
|
* structurally (unknown keys pass through untouched — the client never strips).
|
|
14
|
+
*
|
|
15
|
+
* READING CONTRACT — the three things callers get wrong, stated once here and
|
|
16
|
+
* repeated on the types where each one bites:
|
|
17
|
+
* 1. **Ids are opaque.** Round-trip them verbatim; never parse or construct
|
|
18
|
+
* one. See `./identifiers.ts` for which strings are opaque tokens and which
|
|
19
|
+
* are canonical vocabulary you discover.
|
|
20
|
+
* 2. **Absent is not false.** An optional field that is missing means UNKNOWN,
|
|
21
|
+
* not "known to be absent in reality" — most dangerously for booleans.
|
|
22
|
+
* Values carry `confidence` and `sourceBasis`; report them as claims.
|
|
23
|
+
* 3. **A page is not a corpus.** `results.length` is this page.
|
|
24
|
+
* {@link CountedTotal} — with the `basis` it states — is the claim about the
|
|
25
|
+
* match set. Follow the cursor for the rest, and never report a page size as
|
|
26
|
+
* a total.
|
|
14
27
|
*/
|
|
15
28
|
import type { BySpecVerdict } from "./by-spec-types.js";
|
|
29
|
+
import type { Cursor, ProductId, VariantId } from "./identifiers.js";
|
|
16
30
|
import type { MaterialDetail, MaterialTile } from "./material-types.js";
|
|
17
31
|
import type { SearchFilter, SearchSuggestion } from "./search-refinement-types.js";
|
|
18
32
|
/** A widened string-literal union: known members autocomplete, unknown values still type-check. */
|
|
@@ -79,13 +93,22 @@ export interface VisualMeaning {
|
|
|
79
93
|
version?: string;
|
|
80
94
|
appliedAt?: string;
|
|
81
95
|
}
|
|
82
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* One material in a search / match result list.
|
|
98
|
+
*
|
|
99
|
+
* ABSENT IS NOT FALSE. The optional fields here (`colourMeta`, `signals`,
|
|
100
|
+
* `retired`, `siblingCount`, `visualMeanings`, `verdict`) are absent when the
|
|
101
|
+
* request did not ask for them or the graph holds no answer — never because the
|
|
102
|
+
* answer is "no". A missing `retired` is not "still current"; a missing
|
|
103
|
+
* `visualMeanings` is not "this material has no visual meaning".
|
|
104
|
+
*/
|
|
83
105
|
export interface SearchResultItem {
|
|
84
|
-
|
|
106
|
+
/** Opaque — round-trip verbatim into `material()`, `pairings()`, `similarTo`. */
|
|
107
|
+
variantId: VariantId;
|
|
85
108
|
brand: string;
|
|
86
109
|
product: string;
|
|
87
110
|
brandSlug: string | null;
|
|
88
|
-
productId:
|
|
111
|
+
productId: ProductId | null;
|
|
89
112
|
color: string | null;
|
|
90
113
|
variantName: string | null;
|
|
91
114
|
imageUrl: string | null;
|
|
@@ -112,10 +135,18 @@ export interface SearchResultItem {
|
|
|
112
135
|
*/
|
|
113
136
|
verdict?: BySpecVerdict;
|
|
114
137
|
}
|
|
115
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* The lean `view:"card"` result item — identity + name + thumbnail + score.
|
|
140
|
+
*
|
|
141
|
+
* A PROJECTION, not a smaller answer: a field the card omits (palette, verdict,
|
|
142
|
+
* signals, availability) is unread here, never known-absent. Re-read the same
|
|
143
|
+
* `variantId` at `view: "full"` or via `material()` before claiming anything the
|
|
144
|
+
* card does not carry.
|
|
145
|
+
*/
|
|
116
146
|
export interface SearchCardItem {
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
/** Opaque — round-trip verbatim. */
|
|
148
|
+
variantId: VariantId;
|
|
149
|
+
productId: ProductId | null;
|
|
119
150
|
brandSlug: string | null;
|
|
120
151
|
brand: string;
|
|
121
152
|
/** Product name (the full item's `product`). */
|
|
@@ -125,15 +156,26 @@ export interface SearchCardItem {
|
|
|
125
156
|
score: number;
|
|
126
157
|
}
|
|
127
158
|
/**
|
|
128
|
-
* A feed total with its honesty basis
|
|
129
|
-
*
|
|
130
|
-
*
|
|
159
|
+
* A feed total with its honesty basis — THE CLAIM ABOUT THE MATCH SET, which is
|
|
160
|
+
* a different number from `results.length` (this page).
|
|
161
|
+
*
|
|
162
|
+
* `exact` — the true count (≤ the cap of 1,000, or a facet-MV read). Say "473
|
|
163
|
+
* materials match".
|
|
164
|
+
* `lowerBound` — a floor (cap hit, ANN-pool bound, or a degraded count). Say
|
|
165
|
+
* "at least 1,000", never "1,000". Never a silent approximation.
|
|
166
|
+
*
|
|
167
|
+
* A response with no `counts` at all states no total: `count: true` is opt-in,
|
|
168
|
+
* and the absence of a total is not a total of zero.
|
|
131
169
|
*/
|
|
132
170
|
export interface CountedTotal {
|
|
133
171
|
value: number;
|
|
134
172
|
basis: OpenEnum<"exact" | "lowerBound">;
|
|
135
173
|
}
|
|
136
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* Feed counts — present ONLY under `count:true` or `collapse:"product"`. This is
|
|
176
|
+
* the corpus-level claim; `results.length` is the page. Never report one as the
|
|
177
|
+
* other.
|
|
178
|
+
*/
|
|
137
179
|
export interface SearchCounts {
|
|
138
180
|
variants: CountedTotal;
|
|
139
181
|
products?: CountedTotal;
|
|
@@ -152,14 +194,21 @@ export interface SearchRerankReport {
|
|
|
152
194
|
* {@link SearchCardItem}s. Every other field is shared. `counts` is present
|
|
153
195
|
* under `count:true` or `collapse:"product"` (replacing the former
|
|
154
196
|
* `variantCount`/`productCount`).
|
|
197
|
+
*
|
|
198
|
+
* A PAGE, NOT A CORPUS. `results.length` is what you were handed;
|
|
199
|
+
* {@link SearchCounts} is the only statement about the match set, and only when
|
|
200
|
+
* you asked for it. Follow `cursor` for the rest. "I found 24 materials" is
|
|
201
|
+
* wrong whenever `limit` was 24.
|
|
155
202
|
*/
|
|
156
203
|
export interface SearchResponse {
|
|
157
204
|
view: OpenEnum<"full" | "card">;
|
|
205
|
+
/** THIS PAGE. Its length is a page size, never a total. */
|
|
158
206
|
results: SearchResultItem[];
|
|
159
207
|
suggestions: SearchSuggestion[];
|
|
160
208
|
mode: SearchMode;
|
|
161
209
|
maxScore: number;
|
|
162
|
-
|
|
210
|
+
/** Opaque keyset for the next page. Absent = end of the walk, not an error. */
|
|
211
|
+
cursor?: Cursor;
|
|
163
212
|
rerank?: SearchRerankReport;
|
|
164
213
|
counts?: SearchCounts;
|
|
165
214
|
note?: string;
|
|
@@ -180,8 +229,17 @@ export interface MatchCategoryInference {
|
|
|
180
229
|
displayName: string;
|
|
181
230
|
boost: number;
|
|
182
231
|
}
|
|
183
|
-
/**
|
|
232
|
+
/**
|
|
233
|
+
* The Material Match response (non-streaming form).
|
|
234
|
+
*
|
|
235
|
+
* `poolSize` is the candidate pool the floor was applied to, NOT a corpus total
|
|
236
|
+
* and NOT "how many materials look like this". An empty `results` with
|
|
237
|
+
* `droppedBelowFloor > 0` means "candidates existed and none was good enough" —
|
|
238
|
+
* a different answer from `poolSize: 0`, which means the image found nothing to
|
|
239
|
+
* search against at all.
|
|
240
|
+
*/
|
|
184
241
|
export interface MatchResponse {
|
|
242
|
+
/** THIS answer, capped by `limit`. Not a total. */
|
|
185
243
|
results: SearchResultItem[];
|
|
186
244
|
inferredCategories: MatchCategoryInference[];
|
|
187
245
|
qualityFloor: number;
|
|
@@ -195,9 +253,17 @@ export interface MatchCategory {
|
|
|
195
253
|
group: string | null;
|
|
196
254
|
type: string | null;
|
|
197
255
|
}
|
|
198
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* One colour-palette match.
|
|
258
|
+
*
|
|
259
|
+
* `hex`, `distance` and `closeness` are OKLab geometry over colour EXTRACTED
|
|
260
|
+
* FROM PRODUCT IMAGERY, not declared by the manufacturer. Report it as visual
|
|
261
|
+
* likeness ("reads close to this colour"), never as a stated colour
|
|
262
|
+
* specification.
|
|
263
|
+
*/
|
|
199
264
|
export interface ByColourMatch {
|
|
200
|
-
|
|
265
|
+
/** Opaque — round-trip verbatim. */
|
|
266
|
+
variantId: VariantId;
|
|
201
267
|
brand: string;
|
|
202
268
|
product: string;
|
|
203
269
|
color: string | null;
|
|
@@ -216,16 +282,18 @@ export interface ByColourResponse {
|
|
|
216
282
|
categories?: string[];
|
|
217
283
|
oklab?: OklabTriple;
|
|
218
284
|
};
|
|
285
|
+
/** `matches.length` — THIS PAGE. Not a corpus total; this surface states none. */
|
|
219
286
|
count: number;
|
|
220
287
|
matches: ByColourMatch[];
|
|
221
288
|
}
|
|
222
289
|
/** One item from the pure colour-scalar browse. */
|
|
223
290
|
export interface BrowseResultItem {
|
|
224
|
-
|
|
291
|
+
/** Opaque — round-trip verbatim. */
|
|
292
|
+
variantId: VariantId;
|
|
225
293
|
brand: string;
|
|
226
294
|
product: string;
|
|
227
295
|
brandSlug: string | null;
|
|
228
|
-
productId:
|
|
296
|
+
productId: ProductId | null;
|
|
229
297
|
color: string | null;
|
|
230
298
|
variantName: string | null;
|
|
231
299
|
imageUrl: string | null;
|
|
@@ -236,10 +304,17 @@ export interface BrowseResultItem {
|
|
|
236
304
|
};
|
|
237
305
|
retired?: boolean;
|
|
238
306
|
}
|
|
239
|
-
/**
|
|
307
|
+
/**
|
|
308
|
+
* A single page of the colour browse — the SDK's one genuinely cursor-paginated
|
|
309
|
+
* surface. `results.length` is this page and the browse states no total at all,
|
|
310
|
+
* so a corpus claim can only come from walking every page
|
|
311
|
+
* (`client.browse.pages(...)` does the loop).
|
|
312
|
+
*/
|
|
240
313
|
export interface BrowseResponse {
|
|
314
|
+
/** THIS PAGE. */
|
|
241
315
|
results: BrowseResultItem[];
|
|
242
|
-
|
|
316
|
+
/** Opaque keyset for the next page. Absent = the walk is finished. */
|
|
317
|
+
cursor?: Cursor;
|
|
243
318
|
}
|
|
244
319
|
export type ColourDictionaryId = OpenEnum<"colour_family" | "colour_mood" | "colour_undertone">;
|
|
245
320
|
export interface ColourFacetTerm {
|
|
@@ -285,17 +360,34 @@ export interface ProjectTypeShare {
|
|
|
285
360
|
projectType: string;
|
|
286
361
|
projectCount: number;
|
|
287
362
|
}
|
|
288
|
-
/**
|
|
363
|
+
/**
|
|
364
|
+
* A pairing item from the behavioural arm — real order evidence, or board
|
|
365
|
+
* curation when this seed has no order edges.
|
|
366
|
+
*
|
|
367
|
+
* WHAT YOU MAY CLAIM, per `source`:
|
|
368
|
+
* - `cospec` — real ORDER co-occurrence. The two materials were ordered
|
|
369
|
+
* together on real projects. "Specified together" is honest here.
|
|
370
|
+
* - `considered` — BOARD ACTIVITY only (moodboard curation). Somebody put them
|
|
371
|
+
* on a board together; nobody is known to have bought them together. Say
|
|
372
|
+
* "often boarded together" / "considered alongside" and NEVER "specified
|
|
373
|
+
* together". The server falls back to this arm seed-by-seed when the seed
|
|
374
|
+
* carries no `sampled` edges, so a single response can be entirely
|
|
375
|
+
* `considered`.
|
|
376
|
+
*
|
|
377
|
+
* See {@link ColourPairing} for the third, non-evidential arm.
|
|
378
|
+
*/
|
|
289
379
|
export interface CospecPairing {
|
|
380
|
+
/** The evidence tier. `cospec` = orders; `considered` = boards. Read it before phrasing. */
|
|
290
381
|
source: "cospec" | "considered";
|
|
291
|
-
|
|
382
|
+
/** Opaque — round-trip verbatim. */
|
|
383
|
+
variantId: VariantId;
|
|
292
384
|
brand: string;
|
|
293
385
|
product: string;
|
|
294
386
|
color: string | null;
|
|
295
387
|
variantName: string | null;
|
|
296
388
|
imageUrl: string | null;
|
|
297
389
|
brandSlug: string;
|
|
298
|
-
productId:
|
|
390
|
+
productId: ProductId;
|
|
299
391
|
category: string | null;
|
|
300
392
|
projectCount: number;
|
|
301
393
|
customerCount: number;
|
|
@@ -304,16 +396,31 @@ export interface CospecPairing {
|
|
|
304
396
|
demand: CompanionDemand | null;
|
|
305
397
|
topProjectTypes: ProjectTypeShare[];
|
|
306
398
|
}
|
|
307
|
-
/**
|
|
399
|
+
/**
|
|
400
|
+
* A pairing item topped up from colour palette-similarity — a LOOK-ALIKE, not
|
|
401
|
+
* evidence. Nobody ordered these together and nobody boarded them together; the
|
|
402
|
+
* palettes sit near each other in OKLab. Say "shares a palette with" and never
|
|
403
|
+
* "specified together" or "often paired with".
|
|
404
|
+
*/
|
|
308
405
|
export interface ColourPairing extends MaterialTile {
|
|
309
406
|
source: "colour";
|
|
310
407
|
score: number;
|
|
311
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* One pairing, discriminated by `source` into three tiers of very different
|
|
411
|
+
* strength: `cospec` (real orders) > `considered` (board activity) > `colour`
|
|
412
|
+
* (palette look-alike). Branch on `source` before you write a sentence about it.
|
|
413
|
+
*/
|
|
312
414
|
export type PairingItem = CospecPairing | ColourPairing;
|
|
313
415
|
export interface PairingsResponse {
|
|
314
416
|
seed: MaterialTile;
|
|
417
|
+
/** A rail, not a ranked corpus — mixed evidence tiers, capped by `limit`. */
|
|
315
418
|
items: PairingItem[];
|
|
316
|
-
/**
|
|
419
|
+
/**
|
|
420
|
+
* How many `items` came from real ORDER evidence (`source: "cospec"`). Zero
|
|
421
|
+
* with a full rail means every item is board activity or colour look-alike —
|
|
422
|
+
* check it before presenting the rail as demand evidence.
|
|
423
|
+
*/
|
|
317
424
|
cospecCount: number;
|
|
318
425
|
}
|
|
319
426
|
export interface MaterialResponse {
|
|
@@ -324,15 +431,23 @@ export interface MaterialResponse {
|
|
|
324
431
|
export interface SiblingsResponse {
|
|
325
432
|
siblings: MaterialTile[];
|
|
326
433
|
}
|
|
327
|
-
/**
|
|
434
|
+
/**
|
|
435
|
+
* Batch read of tile projections. `materials` is NOT positional — resolve by
|
|
436
|
+
* `variantId`, and read `missingIds` rather than inferring absence from length.
|
|
437
|
+
*/
|
|
328
438
|
export interface MaterialsBatchTileResponse {
|
|
329
439
|
materials: MaterialTile[];
|
|
330
|
-
|
|
440
|
+
/** Requested ids that resolved to nothing. Echoed verbatim, opaque. */
|
|
441
|
+
missingIds: VariantId[];
|
|
331
442
|
}
|
|
332
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* Batch read of detail projections. `materials` is NOT positional — resolve by
|
|
445
|
+
* `variantId`, and read `missingIds` rather than inferring absence from length.
|
|
446
|
+
*/
|
|
333
447
|
export interface MaterialsBatchDetailResponse {
|
|
334
448
|
materials: MaterialDetail[];
|
|
335
|
-
|
|
449
|
+
/** Requested ids that resolved to nothing. Echoed verbatim, opaque. */
|
|
450
|
+
missingIds: VariantId[];
|
|
336
451
|
}
|
|
337
452
|
export interface ResolveColourResponse {
|
|
338
453
|
match: {
|
|
@@ -414,7 +529,7 @@ export interface SearchInput {
|
|
|
414
529
|
* seed variant, so it is the only form `verdicts` can diff a candidate against.
|
|
415
530
|
*/
|
|
416
531
|
similarTo?: {
|
|
417
|
-
variantId:
|
|
532
|
+
variantId: VariantId;
|
|
418
533
|
} | {
|
|
419
534
|
vector: string;
|
|
420
535
|
};
|
|
@@ -438,11 +553,16 @@ export interface SearchInput {
|
|
|
438
553
|
* demotes a failed/missing preferred hit WITHIN its page only.
|
|
439
554
|
*/
|
|
440
555
|
preferences?: SearchFilter[];
|
|
556
|
+
/** Page size — the size of ONE page, never a bound on the match set. */
|
|
441
557
|
limit?: number;
|
|
442
|
-
cursor
|
|
558
|
+
/** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
|
|
559
|
+
cursor?: Cursor;
|
|
443
560
|
explain?: boolean;
|
|
444
561
|
collapse?: OpenEnum<"none" | "product">;
|
|
445
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools.
|
|
564
|
+
* Without it the response states NO total — and a page size is not one.
|
|
565
|
+
*/
|
|
446
566
|
count?: boolean;
|
|
447
567
|
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
448
568
|
hasImages?: boolean;
|
|
@@ -464,7 +584,8 @@ export interface SearchGetParams {
|
|
|
464
584
|
mood?: string;
|
|
465
585
|
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
466
586
|
tol?: OpenEnum<"tight" | "normal" | "loose">;
|
|
467
|
-
|
|
587
|
+
/** Seed the search from this variant's image. Opaque — pass it through verbatim. */
|
|
588
|
+
variantId?: VariantId;
|
|
468
589
|
limit?: number;
|
|
469
590
|
/** "0" or "1" — include per-signal `signals` on each result. */
|
|
470
591
|
explain?: OpenEnum<"0" | "1">;
|
|
@@ -492,7 +613,12 @@ export interface ByColourParams {
|
|
|
492
613
|
/** Params for the pure colour `search/browse` surface. */
|
|
493
614
|
export interface BrowseParams {
|
|
494
615
|
sort: OpenEnum<"light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
495
|
-
|
|
616
|
+
/**
|
|
617
|
+
* The previous page's `cursor`, verbatim. Opaque keyset state — not an offset.
|
|
618
|
+
* Changing `sort`, `limit` or `types` invalidates it.
|
|
619
|
+
*/
|
|
620
|
+
cursor?: Cursor;
|
|
621
|
+
/** Page size. */
|
|
496
622
|
limit?: number;
|
|
497
623
|
/** Comma-separated product-type ids to restrict the browse to. */
|
|
498
624
|
types?: string;
|
|
@@ -525,7 +651,14 @@ export interface MatchInput {
|
|
|
525
651
|
}
|
|
526
652
|
/** Options for a batch material read. */
|
|
527
653
|
export interface MaterialsBatchOptions {
|
|
654
|
+
/** Projection depth. `tile` omits fields rather than reporting them absent. */
|
|
528
655
|
view?: "tile" | "detail";
|
|
656
|
+
/**
|
|
657
|
+
* Which id space `ids` are in — MaterialGraph's own opaque `variantId`
|
|
658
|
+
* (default) or Material Bank's `materialBankId`. Both are opaque; they are NOT
|
|
659
|
+
* interchangeable, and passing one under the other's `idKind` returns
|
|
660
|
+
* `missingIds`, not an error.
|
|
661
|
+
*/
|
|
529
662
|
idKind?: OpenEnum<"variantId" | "materialBankId">;
|
|
530
663
|
}
|
|
531
664
|
/** Options for a pairings read. */
|
|
@@ -543,184 +676,28 @@ export interface ResolveAttributeOptions {
|
|
|
543
676
|
export interface BySpecOptions {
|
|
544
677
|
limit?: number;
|
|
545
678
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
variantId: string;
|
|
549
|
-
brand: string;
|
|
550
|
-
product: string;
|
|
551
|
-
color: string | null;
|
|
552
|
-
variantName: string | null;
|
|
553
|
-
imageUrl: string | null;
|
|
554
|
-
brandSlug: string;
|
|
555
|
-
productId: string;
|
|
556
|
-
}
|
|
679
|
+
export type * from "./demand-types.js";
|
|
680
|
+
/** One quarter's tally. Sparse across a series — an absent quarter is not a zero. */
|
|
557
681
|
export interface DemandQuarter {
|
|
558
682
|
quarter: string;
|
|
559
683
|
specCount: number;
|
|
560
684
|
projectCount: number;
|
|
561
685
|
}
|
|
562
|
-
/** Headline demand stats for a seed: counts, quarterly series, momentum. */
|
|
563
|
-
export interface MaterialDemandStats {
|
|
564
|
-
specCount: number;
|
|
565
|
-
projectCount: number;
|
|
566
|
-
customerCount: number;
|
|
567
|
-
byQuarter: DemandQuarter[];
|
|
568
|
-
momentum: DemandMomentum;
|
|
569
|
-
}
|
|
570
|
-
/** `getMaterialDemand` result, passed through verbatim by the `/demand` route. */
|
|
571
|
-
export interface MaterialDemandResult {
|
|
572
|
-
found: boolean;
|
|
573
|
-
query: string;
|
|
574
|
-
seed: DemandSeed | null;
|
|
575
|
-
window: "trailing_36mo";
|
|
576
|
-
demand: MaterialDemandStats | null;
|
|
577
|
-
}
|
|
578
686
|
/**
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
687
|
+
* Which behavioural signal a demand read is scoped to — and therefore what a
|
|
688
|
+
* number drawn from it may honestly be called.
|
|
689
|
+
*
|
|
690
|
+
* - `sampled` — real ORDER co-occurrence. The default everywhere. Present as
|
|
691
|
+
* "specified together" / "specified on N projects".
|
|
692
|
+
* - `considered` — board / moodboard CURATION. Somebody assembled them; nobody
|
|
693
|
+
* is known to have bought them. Present as "considered alongside", never as
|
|
694
|
+
* a specification.
|
|
695
|
+
* - `specified` — the won tier. STATS ONLY: no co-specification edges are built
|
|
696
|
+
* at this grain, so asking for `specified` companions is a rejected request,
|
|
697
|
+
* not an empty result.
|
|
582
698
|
*/
|
|
583
|
-
export interface CoSpecifiedResult {
|
|
584
|
-
found: boolean;
|
|
585
|
-
resolution: OpenEnum<"resolved" | "ambiguous" | "not_found">;
|
|
586
|
-
query: string;
|
|
587
|
-
seed: DemandSeed | null;
|
|
588
|
-
seedCategory: string | null;
|
|
589
|
-
seedAlternatives: DemandSeed[];
|
|
590
|
-
companions?: unknown[];
|
|
591
|
-
}
|
|
592
|
-
/** The `/api/v1/demand` envelope — both domain reads side by side, verbatim. */
|
|
593
|
-
export interface DemandResponse {
|
|
594
|
-
query: string;
|
|
595
|
-
demand: MaterialDemandResult;
|
|
596
|
-
cospec: CoSpecifiedResult;
|
|
597
|
-
}
|
|
598
|
-
/** Which behavioural signal candidate demand is read from. */
|
|
599
699
|
export type DemandSignal = OpenEnum<"sampled" | "considered" | "specified">;
|
|
600
|
-
|
|
601
|
-
* Body for `POST /api/v1/demand/batch` — enrich 1–48 already-selected candidate
|
|
602
|
-
* ids with their real specification demand, in input order and cardinality.
|
|
603
|
-
*/
|
|
604
|
-
export interface DemandBatchInput {
|
|
605
|
-
/** 1–48 canonical variant ids. Order AND cardinality are preserved — duplicates are echoed, never collapsed. */
|
|
606
|
-
variantIds: string[];
|
|
607
|
-
/** The behavioural signal to read demand from. Defaults to `sampled` (order co-occurrence) server-side. */
|
|
608
|
-
signal?: DemandSignal;
|
|
609
|
-
/** Optional declared context (e.g. `{ projectType: "hospitality" }`) — echoed for provenance, never used to rank or filter. */
|
|
610
|
-
context?: Record<string, string>;
|
|
611
|
-
}
|
|
612
|
-
/** Headline population behind the metric — this variant's own counts. */
|
|
613
|
-
export interface DemandBatchPopulation {
|
|
614
|
-
specCount: number;
|
|
615
|
-
projectCount: number;
|
|
616
|
-
customerCount: number;
|
|
617
|
-
}
|
|
618
|
-
/** The consideration→specification funnel (project-grain; `exclusionRate` is annotate-only). */
|
|
619
|
-
export interface DemandBatchFunnel {
|
|
620
|
-
consideredProjects: number;
|
|
621
|
-
specifiedProjects: number;
|
|
622
|
-
excludedCount: number;
|
|
623
|
-
exclusionRate: number;
|
|
624
|
-
}
|
|
625
|
-
/** Per-result guardrail status — present on `internal` (`mg:admin`) results only. */
|
|
626
|
-
export interface DemandBatchEvidence {
|
|
627
|
-
belowPrivacyFloor: boolean;
|
|
628
|
-
belowQualityFloor: boolean;
|
|
629
|
-
suppressedForPublic: boolean;
|
|
630
|
-
}
|
|
631
|
-
/** A variant that carries a usable demand signal. */
|
|
632
|
-
export interface DemandBatchAvailableResult {
|
|
633
|
-
variantId: string;
|
|
634
|
-
state: "available";
|
|
635
|
-
population: DemandBatchPopulation;
|
|
636
|
-
/** Exact per-quarter counts (sparse — zero-spec quarters are absent). */
|
|
637
|
-
intervals: DemandQuarter[];
|
|
638
|
-
momentum: DemandMomentum;
|
|
639
|
-
/** The funnel; null when the signal is not `sampled` or no `considered` row/session exists. */
|
|
640
|
-
funnel: DemandBatchFunnel | null;
|
|
641
|
-
/** Exact guardrail evidence for `internal` callers; null for public. */
|
|
642
|
-
evidence: DemandBatchEvidence | null;
|
|
643
|
-
}
|
|
644
|
-
/** A variant with a stats row that is below the public quality floor. */
|
|
645
|
-
export interface DemandBatchInsufficientResult {
|
|
646
|
-
variantId: string;
|
|
647
|
-
state: "insufficient_data";
|
|
648
|
-
}
|
|
649
|
-
/** A variant with no stats — indistinguishable from a privacy-suppressed one for public callers. */
|
|
650
|
-
export interface DemandBatchNoSignalResult {
|
|
651
|
-
variantId: string;
|
|
652
|
-
state: "no_signal";
|
|
653
|
-
}
|
|
654
|
-
/** A requested id that resolves to no real variant. */
|
|
655
|
-
export interface DemandBatchUnknownResult {
|
|
656
|
-
variantId: string;
|
|
657
|
-
state: "unknown_variant";
|
|
658
|
-
}
|
|
659
|
-
/** One discriminated result per input id, in input order and cardinality. */
|
|
660
|
-
export type DemandBatchResult = DemandBatchAvailableResult | DemandBatchInsufficientResult | DemandBatchNoSignalResult | DemandBatchUnknownResult;
|
|
661
|
-
/** The `POST /api/v1/demand/batch` envelope. */
|
|
662
|
-
export interface DemandBatchResponse {
|
|
663
|
-
/** The completed ingestion-session id these counts were mined in (the dataset generation). */
|
|
664
|
-
generationId: string;
|
|
665
|
-
metric: "specification_demand";
|
|
666
|
-
/** The metric universe — the window and signal the counts are scoped to. */
|
|
667
|
-
universe: {
|
|
668
|
-
window: "trailing_36mo";
|
|
669
|
-
signal: DemandSignal;
|
|
670
|
-
description: string;
|
|
671
|
-
};
|
|
672
|
-
/** The guardrail status under which `results` were projected. */
|
|
673
|
-
guardrail: {
|
|
674
|
-
audience: OpenEnum<"public" | "internal">;
|
|
675
|
-
minProjectCount: number;
|
|
676
|
-
minCustomerCount: number;
|
|
677
|
-
};
|
|
678
|
-
/** Outcome tallies over `results` — never a denominator for the metric. */
|
|
679
|
-
coverage: {
|
|
680
|
-
requested: number;
|
|
681
|
-
available: number;
|
|
682
|
-
insufficientData: number;
|
|
683
|
-
noSignal: number;
|
|
684
|
-
unknownVariant: number;
|
|
685
|
-
};
|
|
686
|
-
/** The echoed declared context, or null when none was supplied. */
|
|
687
|
-
context: Record<string, string> | null;
|
|
688
|
-
results: DemandBatchResult[];
|
|
689
|
-
}
|
|
690
|
-
export interface CoordinateSeed extends DemandSeed {
|
|
691
|
-
/** The input reference this seed resolved from. */
|
|
692
|
-
resolvedFrom: string;
|
|
693
|
-
}
|
|
694
|
-
export interface CoordinateCompanion {
|
|
695
|
-
variantId: string;
|
|
696
|
-
brand: string;
|
|
697
|
-
product: string;
|
|
698
|
-
color: string | null;
|
|
699
|
-
variantName: string | null;
|
|
700
|
-
imageUrl: string | null;
|
|
701
|
-
brandSlug: string;
|
|
702
|
-
productId: string;
|
|
703
|
-
totalLift: number;
|
|
704
|
-
liftLabel: string;
|
|
705
|
-
seedMatchCount: number;
|
|
706
|
-
seedContributions: Array<{
|
|
707
|
-
seedVariantId: string;
|
|
708
|
-
lift: number;
|
|
709
|
-
}>;
|
|
710
|
-
demand: CompanionDemand | null;
|
|
711
|
-
swatches: Array<{
|
|
712
|
-
hex: string;
|
|
713
|
-
proportion: number;
|
|
714
|
-
}>;
|
|
715
|
-
/** Annotate-only appearance similarity — never reorders companions in v1. */
|
|
716
|
-
colourFit: number | null;
|
|
717
|
-
}
|
|
718
|
-
export interface CoordinateResponse {
|
|
719
|
-
seeds: CoordinateSeed[];
|
|
720
|
-
unresolved: string[];
|
|
721
|
-
window: "trailing_36mo";
|
|
722
|
-
companions: CoordinateCompanion[];
|
|
723
|
-
}
|
|
700
|
+
export type * from "./demand-batch-types.js";
|
|
724
701
|
export interface ExtractedPalette {
|
|
725
702
|
colors: Array<{
|
|
726
703
|
hex: string;
|
|
@@ -848,18 +825,6 @@ export interface BrandProfileResponse {
|
|
|
848
825
|
name: string;
|
|
849
826
|
}>;
|
|
850
827
|
}
|
|
851
|
-
/** Seed a demand read by EXACTLY ONE of `variantId` or `material`. */
|
|
852
|
-
export interface DemandParams {
|
|
853
|
-
variantId?: string;
|
|
854
|
-
material?: string;
|
|
855
|
-
}
|
|
856
|
-
/** Body for `POST /api/v1/pairings/coordinate`. */
|
|
857
|
-
export interface CoordinateInput {
|
|
858
|
-
materials?: string[];
|
|
859
|
-
variantIds?: string[];
|
|
860
|
-
projectType?: string;
|
|
861
|
-
limit?: number;
|
|
862
|
-
}
|
|
863
828
|
/** Body for `POST /api/v1/search/palettes/extract`. */
|
|
864
829
|
export interface ExtractPaletteInput {
|
|
865
830
|
/** The query image as a data URI or bare base64 string. */
|