@instruments/materialgraph 0.1.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.
package/types.d.ts ADDED
@@ -0,0 +1,749 @@
1
+ /**
2
+ * Hand-mirrored wire types for the MaterialGraph public API (spec 0.5.0).
3
+ *
4
+ * These interfaces intentionally have NO runtime dependency on any
5
+ * `@materialgraph/*` package — they are a standalone, publishable mirror of the
6
+ * shapes in `apps/web/public/openapi.yaml` and the captured fixtures under
7
+ * `docs/surface/fixtures/v0.5.0/`. The `contract-parity` test re-checks them
8
+ * against the real Zod schemas so the mirror cannot silently drift.
9
+ * TOLERANT-PARSING CONTRACT: the public surface declares open response enums —
10
+ * "clients must tolerate unknown enum values and unknown fields". Every union
11
+ * that mirrors a server enum is therefore widened with `(string & {})` so a new
12
+ * server value still type-checks, and objects that may grow fields are read
13
+ * structurally (unknown keys pass through untouched — the client never strips).
14
+ */
15
+ import type { SearchFilter, SearchSuggestion } from "./search-refinement-types.js";
16
+ /** A widened string-literal union: known members autocomplete, unknown values still type-check. */
17
+ export type OpenEnum<T extends string> = T | (string & {});
18
+ /** One palette swatch of a material's main image. */
19
+ export interface PaletteSwatch {
20
+ hex: string;
21
+ proportion: number;
22
+ /** Stored human colour name for the swatch, when one was extracted. */
23
+ name?: string | null;
24
+ }
25
+ /** Loose colour metadata for a search result's main image; every field is independently optional. */
26
+ export interface ColourMeta {
27
+ mood?: string;
28
+ dominantFamily?: string;
29
+ dominantHex?: string;
30
+ }
31
+ /** OKLab triple. */
32
+ export interface OklabTriple {
33
+ l: number;
34
+ a: number;
35
+ b: number;
36
+ }
37
+ /** The material's dominant-colour block (tile/detail projections). */
38
+ export interface MaterialColour {
39
+ dominantHex: string | null;
40
+ hsl: {
41
+ h: number;
42
+ s: number;
43
+ l: number;
44
+ } | null;
45
+ oklab: OklabTriple | null;
46
+ chroma: number | null;
47
+ vibrancy: number | null;
48
+ }
49
+ /** Where the served image came from. */
50
+ export type ImageSource = OpenEnum<"r2" | "origin">;
51
+ /** The grid-card projection of a material. */
52
+ export interface MaterialTile {
53
+ variantId: string;
54
+ brand: string;
55
+ product: string;
56
+ variantName: string | null;
57
+ imageUrl: string | null;
58
+ imageSource: ImageSource | null;
59
+ category: string | null;
60
+ colour: MaterialColour;
61
+ }
62
+ /** A canonical attribute/term pair carried on a detail record. */
63
+ export interface MaterialFacet {
64
+ attributeId: string;
65
+ term: string;
66
+ }
67
+ /**
68
+ * One dimensional configuration of a material. Numeric fields are millimetres
69
+ * (mass in kilograms); string fields are the source's own descriptors. The
70
+ * index signature preserves the corpus's long tail of rarer keys.
71
+ */
72
+ export interface DimensionEntry {
73
+ label?: string;
74
+ tile_size?: string;
75
+ measurement_basis?: string;
76
+ width_mm?: number;
77
+ length_mm?: number;
78
+ height_mm?: number;
79
+ depth_mm?: number;
80
+ thickness_mm?: number;
81
+ diameter_mm?: number;
82
+ visible_projection_mm?: number;
83
+ weight_kg?: number;
84
+ [key: string]: unknown;
85
+ }
86
+ /** The record projection of a material — the tile plus detail-only fields. */
87
+ export interface MaterialDetail extends MaterialTile {
88
+ brandSlug: string;
89
+ productId: string;
90
+ color: string | null;
91
+ materialBankId: string | null;
92
+ installationImageUrl: string | null;
93
+ displayCategory: string | null;
94
+ categoryPath: string[];
95
+ palette: PaletteSwatch[];
96
+ facets: MaterialFacet[];
97
+ dimensions: DimensionEntry[] | null;
98
+ }
99
+ /** Per-signal scores, present on a result when `explain` was requested. */
100
+ export interface SearchResultSignals {
101
+ image: number | null;
102
+ category: number | null;
103
+ colour: number | null;
104
+ lexical: number | null;
105
+ }
106
+ /** An accepted/measured visual meaning — descriptive evidence, never a ranking cause. */
107
+ export interface VisualMeaning {
108
+ id: string;
109
+ label: string;
110
+ values: string[];
111
+ basis: OpenEnum<"canonical_attribute" | "image_colour_analysis">;
112
+ confidence?: number;
113
+ sourceBasis?: string;
114
+ version?: string;
115
+ appliedAt?: string;
116
+ }
117
+ /** One material in a search / match result list. */
118
+ export interface SearchResultItem {
119
+ variantId: string;
120
+ brand: string;
121
+ product: string;
122
+ brandSlug: string | null;
123
+ productId: string | null;
124
+ color: string | null;
125
+ variantName: string | null;
126
+ imageUrl: string | null;
127
+ hasImage: boolean;
128
+ palette: PaletteSwatch[];
129
+ colourMeta?: ColourMeta;
130
+ /** Fused score — range [0, sum of active weights]; see `maxScore`. */
131
+ score: number;
132
+ signals?: SearchResultSignals;
133
+ retired?: boolean;
134
+ siblingCount?: number;
135
+ visualMeanings?: VisualMeaning[];
136
+ }
137
+ /** The single candidate source a query resolved to. */
138
+ export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical">;
139
+ /** Rerank-term report (absent in colour mode — there colour IS the source). */
140
+ export interface SearchRerankReport {
141
+ colour?: OpenEnum<"ok" | "failed" | "skipped">;
142
+ colourWeight?: number;
143
+ colourLabel?: string;
144
+ }
145
+ /** The full search response (both GET and POST forms return this envelope). */
146
+ export interface SearchResponse {
147
+ results: SearchResultItem[];
148
+ suggestions: SearchSuggestion[];
149
+ mode: SearchMode;
150
+ maxScore: number;
151
+ cursor?: string;
152
+ rerank?: SearchRerankReport;
153
+ variantCount?: number;
154
+ productCount?: number;
155
+ note?: string;
156
+ timings?: Record<string, number>;
157
+ /** Echo of the resolved query input (raw image bytes/vector are never echoed). */
158
+ query?: Record<string, unknown>;
159
+ /** A `getVariantBrief` echo of the seed variant, present only in `variantId` seed mode. */
160
+ seed?: MaterialTile | Record<string, unknown>;
161
+ }
162
+ /** An inferred product-type category from the query image, with its boost. */
163
+ export interface MatchCategoryInference {
164
+ productTypeId: string;
165
+ displayName: string;
166
+ boost: number;
167
+ }
168
+ /** The Material Match response (non-streaming form). */
169
+ export interface MatchResponse {
170
+ results: SearchResultItem[];
171
+ inferredCategories: MatchCategoryInference[];
172
+ qualityFloor: number;
173
+ poolSize: number;
174
+ droppedBelowFloor: number;
175
+ droppedAsDuplicateProduct: number;
176
+ note?: string;
177
+ }
178
+ /** Coarse group + fine type of a colour match's primary product type. */
179
+ export interface MatchCategory {
180
+ group: string | null;
181
+ type: string | null;
182
+ }
183
+ /** One colour-palette match. */
184
+ export interface ByColourMatch {
185
+ variantId: string;
186
+ brand: string;
187
+ product: string;
188
+ color: string | null;
189
+ variantName: string | null;
190
+ imageUrl: string | null;
191
+ hex: string | null;
192
+ distance: number;
193
+ closeness: number;
194
+ category: MatchCategory | null;
195
+ oklab: OklabTriple | null;
196
+ }
197
+ /** The `search/colours` response. */
198
+ export interface ByColourResponse {
199
+ query: {
200
+ colours: string[];
201
+ categories?: string[];
202
+ oklab?: OklabTriple;
203
+ };
204
+ count: number;
205
+ matches: ByColourMatch[];
206
+ }
207
+ /** One item from the pure colour-scalar browse. */
208
+ export interface BrowseResultItem {
209
+ variantId: string;
210
+ brand: string;
211
+ product: string;
212
+ brandSlug: string | null;
213
+ productId: string | null;
214
+ color: string | null;
215
+ variantName: string | null;
216
+ imageUrl: string | null;
217
+ scalars: {
218
+ l: number | null;
219
+ chroma: number | null;
220
+ vibrancy: number | null;
221
+ };
222
+ retired?: boolean;
223
+ }
224
+ /** A single page of the colour browse. `cursor` is the keyset for the next page. */
225
+ export interface BrowseResponse {
226
+ results: BrowseResultItem[];
227
+ cursor?: string;
228
+ }
229
+ export type ColourDictionaryId = OpenEnum<"colour_family" | "colour_mood" | "colour_undertone">;
230
+ export interface ColourFacetTerm {
231
+ id: string;
232
+ label: string;
233
+ aliases: string[];
234
+ variants: number;
235
+ brands: number;
236
+ }
237
+ export interface ColourFacetDictionary {
238
+ attributeId: ColourDictionaryId;
239
+ name: string;
240
+ description: string;
241
+ terms: ColourFacetTerm[];
242
+ }
243
+ export interface ColourFacetsResponse {
244
+ count: number;
245
+ dictionaries: ColourFacetDictionary[];
246
+ }
247
+ export interface CategoryNode {
248
+ key: string;
249
+ name: string;
250
+ parentKey: string | null;
251
+ path: string;
252
+ level: OpenEnum<"group" | "type">;
253
+ }
254
+ export interface TaxonomyResponse {
255
+ count: number;
256
+ categories: CategoryNode[];
257
+ }
258
+ export interface DemandMomentum {
259
+ state: OpenEnum<"rising" | "fading" | "stable" | "emerging" | "insufficient_volume">;
260
+ percentChange: number | null;
261
+ trailingSpecCount: number;
262
+ priorSpecCount: number;
263
+ }
264
+ export interface CompanionDemand {
265
+ specCount: number;
266
+ projectCount: number;
267
+ momentum: DemandMomentum | null;
268
+ }
269
+ export interface ProjectTypeShare {
270
+ projectType: string;
271
+ projectCount: number;
272
+ }
273
+ /** A pairing item backed by real co-specification (order) evidence. */
274
+ export interface CospecPairing {
275
+ source: "cospec" | "considered";
276
+ variantId: string;
277
+ brand: string;
278
+ product: string;
279
+ color: string | null;
280
+ variantName: string | null;
281
+ imageUrl: string | null;
282
+ brandSlug: string;
283
+ productId: string;
284
+ category: string | null;
285
+ projectCount: number;
286
+ customerCount: number;
287
+ lift: number;
288
+ liftLabel: string;
289
+ demand: CompanionDemand | null;
290
+ topProjectTypes: ProjectTypeShare[];
291
+ }
292
+ /** A pairing item topped up from colour palette-similarity (merely looks adjacent). */
293
+ export interface ColourPairing extends MaterialTile {
294
+ source: "colour";
295
+ score: number;
296
+ }
297
+ export type PairingItem = CospecPairing | ColourPairing;
298
+ export interface PairingsResponse {
299
+ seed: MaterialTile;
300
+ items: PairingItem[];
301
+ /** How many items came from real order evidence (the cospec arm). */
302
+ cospecCount: number;
303
+ }
304
+ export interface MaterialResponse {
305
+ material: MaterialDetail;
306
+ /** Present only when `include: "siblings"` was requested. */
307
+ siblings?: MaterialTile[];
308
+ }
309
+ export interface SiblingsResponse {
310
+ siblings: MaterialTile[];
311
+ }
312
+ /** Batch read of tile projections. */
313
+ export interface MaterialsBatchTileResponse {
314
+ materials: MaterialTile[];
315
+ missingIds: string[];
316
+ }
317
+ /** Batch read of detail projections. */
318
+ export interface MaterialsBatchDetailResponse {
319
+ materials: MaterialDetail[];
320
+ missingIds: string[];
321
+ }
322
+ export interface ResolveColourResponse {
323
+ match: {
324
+ hex: string;
325
+ name: string;
326
+ code?: string;
327
+ libraryId?: string;
328
+ } | null;
329
+ }
330
+ export interface SuggestColourResponse {
331
+ suggestions: Array<{
332
+ term: string;
333
+ hex: string;
334
+ libraryId?: string;
335
+ kind: string;
336
+ }>;
337
+ }
338
+ export interface AttributeCandidate {
339
+ attributeId: string;
340
+ name: string;
341
+ description: string;
342
+ score: number;
343
+ facetable: boolean;
344
+ blockIds: string[];
345
+ }
346
+ export interface ResolveAttributeResponse {
347
+ query: string;
348
+ candidates: AttributeCandidate[];
349
+ }
350
+ export type * from "./catalog-discovery-types.js";
351
+ export interface BySpecMatchedCell {
352
+ attributeId: string;
353
+ confidence: number | null;
354
+ sourceBasis: string | null;
355
+ }
356
+ export interface BySpecMatch {
357
+ variantId: string;
358
+ brand: string;
359
+ product: string;
360
+ variant: string;
361
+ color: string | null;
362
+ imageUrl: string | null;
363
+ hex: string | null;
364
+ matched: BySpecMatchedCell[];
365
+ minConfidence: number | null;
366
+ }
367
+ export interface BySpecResponse {
368
+ criteria: Array<{
369
+ attribute: string;
370
+ value: string;
371
+ }>;
372
+ totalMatches: number;
373
+ brandsSpanned: number;
374
+ matches: BySpecMatch[];
375
+ }
376
+ export interface WhoamiResponse {
377
+ principal: string;
378
+ scopes: string[];
379
+ }
380
+ /** A single colour entry in a search palette — exactly one of `hex` or `name`. */
381
+ export interface ColourInput {
382
+ hex?: string;
383
+ name?: string;
384
+ weight?: number;
385
+ role?: OpenEnum<"any" | "is" | "accent">;
386
+ sourceName?: string;
387
+ }
388
+ /** A raw incoming image to search by. */
389
+ export type SearchImageInput = {
390
+ type: "url";
391
+ url: string;
392
+ } | {
393
+ type: "base64";
394
+ data: string;
395
+ };
396
+ /**
397
+ * The POST `/search` JSON body — the `searchMaterials` contract. Every field is
398
+ * optional; supply at least one signal (`text`, `colours`, `mood`, `similarTo`,
399
+ * or `image`). Passed through to the server verbatim.
400
+ */
401
+ export interface SearchInput {
402
+ text?: string;
403
+ textModes?: Array<OpenEnum<"visual" | "lexical">>;
404
+ colours?: ColourInput[];
405
+ colourLogic?: OpenEnum<"all" | "any">;
406
+ colourTolerance?: OpenEnum<"tight" | "normal" | "loose">;
407
+ colourAppearance?: {
408
+ lightness?: {
409
+ min: number;
410
+ max: number;
411
+ };
412
+ warmth?: {
413
+ min: number;
414
+ max: number;
415
+ };
416
+ };
417
+ mood?: string;
418
+ sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
419
+ similarTo?: {
420
+ variantId: string;
421
+ } | {
422
+ vector: string;
423
+ };
424
+ image?: SearchImageInput;
425
+ category?: {
426
+ boost?: string;
427
+ filter?: string[];
428
+ };
429
+ weights?: {
430
+ image?: number;
431
+ category?: number;
432
+ colour?: number;
433
+ lexical?: number;
434
+ };
435
+ filters?: SearchFilter[];
436
+ limit?: number;
437
+ cursor?: string;
438
+ explain?: boolean;
439
+ collapse?: OpenEnum<"none" | "product">;
440
+ }
441
+ /** Query-string params for the GET `/search` form. */
442
+ export interface SearchGetParams {
443
+ q?: string;
444
+ colours?: string;
445
+ logic?: OpenEnum<"all" | "any">;
446
+ mood?: string;
447
+ sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
448
+ tol?: OpenEnum<"tight" | "normal" | "loose">;
449
+ variantId?: string;
450
+ limit?: number;
451
+ /** "0" or "1" — include per-signal `signals` on each result. */
452
+ explain?: OpenEnum<"0" | "1">;
453
+ }
454
+ /** Params for `search/colours`. */
455
+ export interface ByColourParams {
456
+ /** A single hex (with or without `#`). */
457
+ hex?: string;
458
+ /** A palette — repeated as one `colour` query entry each. */
459
+ colour?: string | string[];
460
+ /** OKLab triple (all-or-none). */
461
+ l?: number;
462
+ a?: number;
463
+ b?: number;
464
+ /** HARD category filter — display names or taxonomy ids (repeatable). */
465
+ category?: string | string[];
466
+ limit?: number;
467
+ }
468
+ /** Params for the pure colour `search/browse` surface. */
469
+ export interface BrowseParams {
470
+ sort: OpenEnum<"light" | "dark" | "vivid" | "muted" | "colourful">;
471
+ cursor?: string;
472
+ limit?: number;
473
+ /** Comma-separated product-type ids to restrict the browse to. */
474
+ types?: string;
475
+ }
476
+ /** The Material Match request body — exactly one of `imageBase64`, `imageUrl`, `vector`. */
477
+ export interface MatchInput {
478
+ imageBase64?: string;
479
+ imageUrl?: string;
480
+ vector?: string;
481
+ limit?: number;
482
+ qualityFloor?: number;
483
+ categoryTopK?: number;
484
+ categoryMinBoost?: number;
485
+ detectionLabel?: string;
486
+ materialFunction?: string;
487
+ detectionCategory?: string;
488
+ requestedType?: OpenEnum<"swatch" | "installation" | "flatplan">;
489
+ }
490
+ /** Options for a batch material read. */
491
+ export interface MaterialsBatchOptions {
492
+ view?: "tile" | "detail";
493
+ idKind?: OpenEnum<"variantId" | "materialBankId">;
494
+ }
495
+ /** Options for a pairings read. */
496
+ export interface PairingsOptions {
497
+ /** Comma-separated coarse category display names; filters both arms. */
498
+ categories?: string;
499
+ limit?: number;
500
+ }
501
+ /** Options for `catalog/attributes/resolve`. */
502
+ export interface ResolveAttributeOptions {
503
+ limit?: number;
504
+ facetableOnly?: boolean;
505
+ }
506
+ /** Options for `search/specifications`. */
507
+ export interface BySpecOptions {
508
+ limit?: number;
509
+ }
510
+ /** A resolved seed material (identity block shared by the demand reads). */
511
+ export interface DemandSeed {
512
+ variantId: string;
513
+ brand: string;
514
+ product: string;
515
+ color: string | null;
516
+ variantName: string | null;
517
+ imageUrl: string | null;
518
+ brandSlug: string;
519
+ productId: string;
520
+ }
521
+ export interface DemandQuarter {
522
+ quarter: string;
523
+ specCount: number;
524
+ projectCount: number;
525
+ }
526
+ /** Headline demand stats for a seed: counts, quarterly series, momentum. */
527
+ export interface MaterialDemandStats {
528
+ specCount: number;
529
+ projectCount: number;
530
+ customerCount: number;
531
+ byQuarter: DemandQuarter[];
532
+ momentum: DemandMomentum;
533
+ }
534
+ /** `getMaterialDemand` result, passed through verbatim by the `/demand` route. */
535
+ export interface MaterialDemandResult {
536
+ found: boolean;
537
+ query: string;
538
+ seed: DemandSeed | null;
539
+ window: "trailing_36mo";
540
+ demand: MaterialDemandStats | null;
541
+ }
542
+ /**
543
+ * `getCoSpecifiedMaterials` result, passed through verbatim. The `companions`
544
+ * list (materials specified alongside the seed) is included but left loosely
545
+ * typed — there is no captured fixture to pin its full shape.
546
+ */
547
+ export interface CoSpecifiedResult {
548
+ found: boolean;
549
+ resolution: OpenEnum<"resolved" | "ambiguous" | "not_found">;
550
+ query: string;
551
+ seed: DemandSeed | null;
552
+ seedCategory: string | null;
553
+ seedAlternatives: DemandSeed[];
554
+ companions?: unknown[];
555
+ }
556
+ /** The `/api/v1/demand` envelope — both domain reads side by side, verbatim. */
557
+ export interface DemandResponse {
558
+ query: string;
559
+ demand: MaterialDemandResult;
560
+ cospec: CoSpecifiedResult;
561
+ }
562
+ export interface CoordinateSeed extends DemandSeed {
563
+ /** The input reference this seed resolved from. */
564
+ resolvedFrom: string;
565
+ }
566
+ export interface CoordinateCompanion {
567
+ variantId: string;
568
+ brand: string;
569
+ product: string;
570
+ color: string | null;
571
+ variantName: string | null;
572
+ imageUrl: string | null;
573
+ brandSlug: string;
574
+ productId: string;
575
+ totalLift: number;
576
+ liftLabel: string;
577
+ seedMatchCount: number;
578
+ seedContributions: Array<{
579
+ seedVariantId: string;
580
+ lift: number;
581
+ }>;
582
+ demand: CompanionDemand | null;
583
+ swatches: Array<{
584
+ hex: string;
585
+ proportion: number;
586
+ }>;
587
+ /** Annotate-only appearance similarity — never reorders companions in v1. */
588
+ colourFit: number | null;
589
+ }
590
+ export interface CoordinateResponse {
591
+ seeds: CoordinateSeed[];
592
+ unresolved: string[];
593
+ window: "trailing_36mo";
594
+ companions: CoordinateCompanion[];
595
+ }
596
+ export interface ExtractedPalette {
597
+ colors: Array<{
598
+ hex: string;
599
+ name: string;
600
+ proportion: number;
601
+ }>;
602
+ dominant: {
603
+ family: string;
604
+ hex: string;
605
+ name: string;
606
+ oklch: string;
607
+ } | null;
608
+ mood: string;
609
+ }
610
+ export interface ExtractPaletteResponse {
611
+ palette: ExtractedPalette | null;
612
+ }
613
+ export interface SpecFacetValue {
614
+ value: string;
615
+ variants: number;
616
+ brands: number;
617
+ label?: string;
618
+ }
619
+ export interface SpecFacet {
620
+ attributeId: string;
621
+ name: string;
622
+ isArray: boolean;
623
+ brands: number;
624
+ values: SpecFacetValue[];
625
+ description?: string;
626
+ }
627
+ export interface SpecFacetsListResponse {
628
+ mode: "facets";
629
+ count: number;
630
+ facets: SpecFacet[];
631
+ }
632
+ export interface SpecFacetsDistributionResponse {
633
+ mode: "distribution";
634
+ attributeId: string;
635
+ count: number;
636
+ values: SpecFacetValue[];
637
+ }
638
+ export interface ApplicableSpecAttribute {
639
+ id: string;
640
+ name: string;
641
+ valueKind: string;
642
+ required: boolean;
643
+ }
644
+ export interface ApplicableSpecBlock {
645
+ blockId: string;
646
+ name: string;
647
+ applicability: OpenEnum<"required" | "recommended" | "optional" | "unusual">;
648
+ attributes: ApplicableSpecAttribute[];
649
+ }
650
+ export interface ApplicableSpecsResponse {
651
+ found: boolean;
652
+ productType: {
653
+ id: string;
654
+ name: string;
655
+ } | null;
656
+ blocks: ApplicableSpecBlock[];
657
+ hint?: string;
658
+ }
659
+ export interface GlossaryAttributeMatch {
660
+ kind: "attribute";
661
+ id: string;
662
+ name: string;
663
+ description: string;
664
+ valueKind: string;
665
+ unit?: string;
666
+ facetable: boolean;
667
+ comparable: boolean;
668
+ dictionaryId?: string;
669
+ usedInBlocks: string[];
670
+ }
671
+ export interface GlossaryValueMatch {
672
+ kind: "value";
673
+ id: string;
674
+ label: string;
675
+ description?: string;
676
+ aliases: string[];
677
+ dictionaryId: string;
678
+ dictionaryName: string;
679
+ attributeIds: string[];
680
+ }
681
+ export interface GlossaryTermResponse {
682
+ found: boolean;
683
+ term: string;
684
+ match: GlossaryAttributeMatch | GlossaryValueMatch | null;
685
+ alternates?: Array<{
686
+ kind: OpenEnum<"attribute" | "value">;
687
+ id: string;
688
+ label: string;
689
+ }>;
690
+ }
691
+ export interface BrandProfile {
692
+ id: string;
693
+ slug: string;
694
+ name: string;
695
+ marketSegment: string;
696
+ headquartersCity: string | null;
697
+ headquartersCountry: string | null;
698
+ parentBrand: {
699
+ id: string;
700
+ name: string;
701
+ } | null;
702
+ positioning: string | null;
703
+ ownershipType: string | null;
704
+ foundedYear: number | null;
705
+ description: string | null;
706
+ website: string | null;
707
+ }
708
+ export interface BrandProfileResponse {
709
+ found: boolean;
710
+ query: string;
711
+ brand: BrandProfile | null;
712
+ /** Trade / spec signals ON RECORD, keyed by attribute id. Absent = no value held (never infer false). */
713
+ trade: Record<string, boolean | string>;
714
+ links: Array<{
715
+ label: string;
716
+ url: string;
717
+ }>;
718
+ alternates: Array<{
719
+ id: string;
720
+ name: string;
721
+ }>;
722
+ }
723
+ /** Seed a demand read by EXACTLY ONE of `variantId` or `material`. */
724
+ export interface DemandParams {
725
+ variantId?: string;
726
+ material?: string;
727
+ }
728
+ /** Body for `POST /api/v1/pairings/coordinate`. */
729
+ export interface CoordinateInput {
730
+ materials?: string[];
731
+ variantIds?: string[];
732
+ projectType?: string;
733
+ limit?: number;
734
+ }
735
+ /** Body for `POST /api/v1/search/palettes/extract`. */
736
+ export interface ExtractPaletteInput {
737
+ /** The query image as a data URI or bare base64 string. */
738
+ image: string;
739
+ /** Max palette swatches to return (default 6). */
740
+ max?: number;
741
+ }
742
+ /** Options for a cacheable GET batch read (`materialsCached`). */
743
+ export interface MaterialsBatchGetOptions extends MaterialsBatchOptions {
744
+ }
745
+ /** Options for `brands/{slug}`. */
746
+ export interface BrandOptions {
747
+ /** Alternate fuzzy query; takes precedence over the path slug when present. */
748
+ q?: string;
749
+ }