@instruments/materialgraph 0.32.0 → 0.35.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/association-types.d.ts +11 -11
- package/associations-client.d.ts +7 -7
- package/associations-client.js +5 -5
- package/brand-types.d.ts +37 -41
- package/brand-types.js +3 -5
- package/brands-client.d.ts +22 -9
- package/brands-client.js +20 -6
- package/by-spec-types.d.ts +2 -3
- package/catalog-discovery-types.d.ts +3 -3
- package/catalogue-client.d.ts +2 -50
- package/catalogue-client.js +2 -10
- package/catalogue-types.d.ts +1 -1
- package/categories-client.d.ts +3 -5
- package/categories-client.js +1 -1
- package/category-types.d.ts +17 -30
- package/citation-types.d.ts +2 -2
- package/client-vocabulary.d.ts +2 -2
- package/client-vocabulary.js +4 -1
- package/client.d.ts +75 -102
- package/client.js +83 -100
- package/color-profiles-types.d.ts +70 -0
- package/colors-client.d.ts +76 -0
- package/colors-client.js +52 -0
- package/find-companions-types.d.ts +8 -8
- package/{find-variants-by-colour-types.d.ts → find-variants-by-color-types.d.ts} +70 -64
- package/find-variants-by-vector-types.d.ts +4 -1
- package/get-variant-types.d.ts +14 -2
- package/http-transport.d.ts +1 -1
- package/http-transport.js +1 -2
- package/identifiers.d.ts +2 -2
- package/identifiers.js +1 -1
- package/index.d.ts +5 -13
- package/index.js +1 -3
- package/list-variant-facets-types.d.ts +4 -4
- package/list-variants-types.d.ts +108 -19
- package/material-asset-types.d.ts +3 -3
- package/material-block-types.d.ts +9 -9
- package/material-types.d.ts +10 -8
- package/operations.d.ts +2 -2
- package/operations.js +14 -24
- package/package.json +2 -2
- package/{resolve-colour-types.d.ts → resolve-color-types.d.ts} +18 -18
- package/response-schemas.generated.d.ts +2 -2
- package/response-schemas.generated.js +40 -68
- package/response-schemas.js +1 -1
- package/samplize-catalogue-types.d.ts +4 -4
- package/samplize-colorvec.js +6 -6
- package/{corpus-scope-types.d.ts → search-catalogue-subset-types.d.ts} +3 -3
- package/search-variants-types.d.ts +91 -0
- package/types.d.ts +31 -283
- package/webmcp.generated.js +4 -9
- package/attribution-types.d.ts +0 -51
- package/attribution-types.js +0 -13
- package/by-colour-types.d.ts +0 -320
- package/colour-region-types.d.ts +0 -18
- package/colours-client.d.ts +0 -70
- package/colours-client.js +0 -38
- package/material-query-plan-types.d.ts +0 -70
- package/palette-search-types.d.ts +0 -171
- package/palette-search-types.js +0 -1
- package/query-understanding-types.d.ts +0 -254
- package/query-understanding-types.js +0 -1
- package/resolve-colour-types.js +0 -1
- package/search-card-types.d.ts +0 -86
- package/search-card-types.js +0 -1
- package/search-index-plan-types.d.ts +0 -74
- package/search-index-plan-types.js +0 -1
- package/search-refinement-types.d.ts +0 -103
- package/search-refinement-types.js +0 -2
- package/search-request-types.d.ts +0 -170
- package/search-request-types.js +0 -1
- package/search-response-receipt-types.d.ts +0 -24
- package/search-response-receipt-types.js +0 -1
- package/search-response-types.d.ts +0 -11
- package/search-response-types.js +0 -1
- package/search-result-validation.d.ts +0 -9
- package/search-result-validation.js +0 -83
- package/spec-types.d.ts +0 -109
- package/spec-types.js +0 -54
- package/specifications.d.ts +0 -34
- package/specifications.js +0 -56
- package/validation.d.ts +0 -43
- package/validation.js +0 -117
- /package/{by-colour-types.js → color-profiles-types.js} +0 -0
- /package/{colour-region-types.js → find-variants-by-color-types.js} +0 -0
- /package/{corpus-scope-types.js → resolve-color-types.js} +0 -0
- /package/{find-variants-by-colour-types.js → search-catalogue-subset-types.js} +0 -0
- /package/{material-query-plan-types.js → search-variants-types.js} +0 -0
package/response-schemas.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ```ts
|
|
11
11
|
* import { responseSchema } from "@instruments/materialgraph/response-schemas";
|
|
12
12
|
*
|
|
13
|
-
* createTool({ outputSchema: responseSchema("
|
|
13
|
+
* createTool({ outputSchema: responseSchema("search_variants"), … });
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
16
16
|
* Keyed by OpenAPI `operationId`, the same id `SDK_OPERATIONS` pairs with each
|
|
@@ -30,7 +30,7 @@ export type SamplizeCatalogueProduct = {
|
|
|
30
30
|
/** The primary sample variant count, never an aggregate of product orders. */
|
|
31
31
|
demand: SamplizeDemand | null;
|
|
32
32
|
collectionMemberships: SamplizeCollectionMembership[];
|
|
33
|
-
|
|
33
|
+
colorAnalysis: Record<string, unknown>;
|
|
34
34
|
oklab: {
|
|
35
35
|
L: number;
|
|
36
36
|
a: number;
|
|
@@ -51,7 +51,7 @@ export type SamplizeCatalogueProduct = {
|
|
|
51
51
|
};
|
|
52
52
|
export type SamplizeCataloguePage = {
|
|
53
53
|
schemaVersion: 2;
|
|
54
|
-
|
|
54
|
+
searchCatalogueSubset: "samplizeCatalogue";
|
|
55
55
|
sourceVersion: string;
|
|
56
56
|
source: Record<string, unknown>;
|
|
57
57
|
/** Number of eligible records matching the supplied filters. */
|
|
@@ -66,7 +66,7 @@ export type SamplizeHexObservation = {
|
|
|
66
66
|
publisher: string;
|
|
67
67
|
source: string;
|
|
68
68
|
sourceUrl: string | null;
|
|
69
|
-
|
|
69
|
+
valueOrigin: string;
|
|
70
70
|
observedAt: string | null;
|
|
71
71
|
differsFromSelected: boolean;
|
|
72
72
|
};
|
|
@@ -83,7 +83,7 @@ export type SamplizeLrvObservation = {
|
|
|
83
83
|
export type SamplizeDemand = {
|
|
84
84
|
field: "recently_ordered_count";
|
|
85
85
|
sourceVariantId: string;
|
|
86
|
-
|
|
86
|
+
defaultAttributeLevel: "shopify_variant";
|
|
87
87
|
recentlyOrderedCount: number;
|
|
88
88
|
/** The publisher does not supply the time window. */
|
|
89
89
|
window: null;
|
package/samplize-colorvec.js
CHANGED
|
@@ -4,7 +4,7 @@ export const SAMPLIZE_COLORVEC_EMBEDDING_VERSION = EMBEDDING_VERSION;
|
|
|
4
4
|
const decodeVector = (encoded) => {
|
|
5
5
|
const bytes = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
|
|
6
6
|
if (bytes.length !== EMBEDDING_VECTOR_SIZE * 4) {
|
|
7
|
-
throw new Error("Invalid
|
|
7
|
+
throw new Error("Invalid color vector size.");
|
|
8
8
|
}
|
|
9
9
|
const view = new DataView(bytes.buffer);
|
|
10
10
|
return Array.from({ length: EMBEDDING_VECTOR_SIZE }, (_, i) => view.getFloat32(i * 4, true));
|
|
@@ -13,7 +13,7 @@ const facets = ["brand", "family", "lightness", "intensity"];
|
|
|
13
13
|
const identityOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
14
14
|
export const samplizeSolidColorVector = (hex) => {
|
|
15
15
|
if (!/^#[0-9a-f]{6}$/i.test(hex)) {
|
|
16
|
-
throw new Error("A six-digit published
|
|
16
|
+
throw new Error("A six-digit published color is required.");
|
|
17
17
|
}
|
|
18
18
|
return paletteToEmbedding([{ hex, proportion: 1 }], { validateOklab: true });
|
|
19
19
|
};
|
|
@@ -78,12 +78,12 @@ export const selectSamplizeEligiblePaints = (paints, input) => {
|
|
|
78
78
|
export const createSamplizeColorVecIndex = (cache) => {
|
|
79
79
|
if (cache.embeddingVersion !== EMBEDDING_VERSION ||
|
|
80
80
|
cache.rankingVersion !== SAMPLIZE_COLORVEC_RANKING_VERSION) {
|
|
81
|
-
throw new Error("The
|
|
81
|
+
throw new Error("The color cache uses an incompatible embedding or ranking version. Refresh it before searching.");
|
|
82
82
|
}
|
|
83
83
|
if (!cache.sourceVersion ||
|
|
84
84
|
!cache.dictionaryVersion ||
|
|
85
85
|
!Number.isFinite(Date.parse(cache.generatedAt))) {
|
|
86
|
-
throw new Error("The
|
|
86
|
+
throw new Error("The color cache is missing its provenance.");
|
|
87
87
|
}
|
|
88
88
|
const paints = cache.paints.map((p) => ({
|
|
89
89
|
...p,
|
|
@@ -95,7 +95,7 @@ export const createSamplizeColorVecIndex = (cache) => {
|
|
|
95
95
|
paint.vector.length !== EMBEDDING_VECTOR_SIZE ||
|
|
96
96
|
!paint.vector.every(Number.isFinite) ||
|
|
97
97
|
![paint.oklab.L, paint.oklab.a, paint.oklab.b].every(Number.isFinite)) {
|
|
98
|
-
throw new Error("The
|
|
98
|
+
throw new Error("The color cache contains invalid or duplicate paint records.");
|
|
99
99
|
}
|
|
100
100
|
identities.add(paint.variantId);
|
|
101
101
|
}
|
|
@@ -128,7 +128,7 @@ export const createSamplizeColorVecIndex = (cache) => {
|
|
|
128
128
|
palette.some((p) => !Number.isFinite(p.proportion) ||
|
|
129
129
|
p.proportion <= 0 ||
|
|
130
130
|
p.proportion > 1)) {
|
|
131
|
-
throw new Error("Supply up to twelve positive image
|
|
131
|
+
throw new Error("Supply up to twelve positive image color shares.");
|
|
132
132
|
}
|
|
133
133
|
const total = palette.reduce((sum, p) => sum + p.proportion, 0);
|
|
134
134
|
const buckets = palette.map((p, position) => ({
|
|
@@ -43,15 +43,15 @@ import type { OpenEnum } from "./types.js";
|
|
|
43
43
|
* that were never in the Material Bank feed; if you mean orderable, use one of
|
|
44
44
|
* the channel-backed scopes above.
|
|
45
45
|
*/
|
|
46
|
-
export type
|
|
46
|
+
export type SearchCatalogueSubset = OpenEnum<"samplizeCatalogue" | "all" | "designShopOrderable" | "materialBankEuOrderable" | "materialBankNaAndCatalogue" | "materialBankNaOrderable" | "materialBankOrderable">;
|
|
47
47
|
/**
|
|
48
48
|
* The corpus a response AND its counts were drawn from. Present only when a
|
|
49
49
|
* narrowing scope applied — absence means the whole graph. Read it alongside
|
|
50
50
|
* the counts: a short page under a scope is a small SCOPED corpus, never a
|
|
51
51
|
* truncated one.
|
|
52
52
|
*/
|
|
53
|
-
export type
|
|
54
|
-
scope:
|
|
53
|
+
export type SearchCatalogueSubsetReceipt = {
|
|
54
|
+
scope: SearchCatalogueSubset;
|
|
55
55
|
/** What the scope admits, in one sentence, for a human reader. */
|
|
56
56
|
description: string;
|
|
57
57
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module search-variants-types
|
|
3
|
+
* @description Hand-maintained mirror of `search_variants`'s (MG-2223) input
|
|
4
|
+
* and output wire shapes. The source of truth is
|
|
5
|
+
* `packages/products/src/search-variants/search-variants.ts` and
|
|
6
|
+
* `compose-conditions.ts`; this SDK has no runtime dependency on
|
|
7
|
+
* `@materialgraph/products`, so the shapes are copied here and
|
|
8
|
+
* `contract-parity.test.ts` keeps them honest.
|
|
9
|
+
*/
|
|
10
|
+
import type { ColorRangeCondition, FilterGroups, ListVariantsInclude, ListVariantsOutput, OptionalFilterGroups } from "./list-variants-types.js";
|
|
11
|
+
import type { SearchCatalogueSubset } from "./search-catalogue-subset-types.js";
|
|
12
|
+
/** Switches one of a previous answer's `choices`: its `handle`, and the `group` and `id` of the alternative to read it as. */
|
|
13
|
+
export type SearchVariantsSelection = {
|
|
14
|
+
group: string;
|
|
15
|
+
handle: string;
|
|
16
|
+
id: string;
|
|
17
|
+
};
|
|
18
|
+
export type SearchVariantsInput = {
|
|
19
|
+
/** Attribute ids to project under each item's `specifications`, as `listVariants()` takes. */
|
|
20
|
+
attributes?: string[];
|
|
21
|
+
/** The previous page's `pageInfo.endCursor`. Tied to the vocabulary version the query was read against, and not a `listVariants()` cursor. */
|
|
22
|
+
cursor?: string;
|
|
23
|
+
/** Optional per-item fields, as `listVariants()` takes; `image` adds each row's image. */
|
|
24
|
+
include?: ListVariantsInclude[];
|
|
25
|
+
/** 1–50; defaults to 10. */
|
|
26
|
+
limit?: number;
|
|
27
|
+
/** What to find, in plain words. 1–200 characters. */
|
|
28
|
+
query: string;
|
|
29
|
+
/** Only this corpus scope. Absent or all: every variant. */
|
|
30
|
+
searchCatalogueSubset?: SearchCatalogueSubset;
|
|
31
|
+
/** Up to eight choices to switch, from the previous answer's `choices`. */
|
|
32
|
+
selections?: SearchVariantsSelection[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What became of one phrase of the query. `filter` narrowed the page,
|
|
36
|
+
* `preference` only ranked it, `not_applied` did neither and `note` says why.
|
|
37
|
+
*/
|
|
38
|
+
export type SearchVariantsUnderstood = {
|
|
39
|
+
applied: "filter" | "preference" | "not_applied";
|
|
40
|
+
/** The parser's condition id. */
|
|
41
|
+
id: string;
|
|
42
|
+
kind: "category" | "brand" | "term" | "number" | "color" | "paint";
|
|
43
|
+
/** What the words were read as. */
|
|
44
|
+
label: string;
|
|
45
|
+
note?: string;
|
|
46
|
+
/** The query words it came from. */
|
|
47
|
+
text: string;
|
|
48
|
+
};
|
|
49
|
+
/** One reading of an ambiguous phrase. */
|
|
50
|
+
export type SearchVariantsReading = {
|
|
51
|
+
group: string;
|
|
52
|
+
id: string;
|
|
53
|
+
kind: string;
|
|
54
|
+
label: string;
|
|
55
|
+
};
|
|
56
|
+
/** A phrase with more than one meaning: the reading used, and the others a caller may switch to by `handle`. */
|
|
57
|
+
export type SearchVariantsChoice = {
|
|
58
|
+
alternatives: SearchVariantsReading[];
|
|
59
|
+
basis: string;
|
|
60
|
+
handle: string;
|
|
61
|
+
reason: string;
|
|
62
|
+
selected: SearchVariantsReading;
|
|
63
|
+
text: string;
|
|
64
|
+
};
|
|
65
|
+
/** The `listVariants()` page, with how the words were read beside it. */
|
|
66
|
+
export type SearchVariantsOutput = ListVariantsOutput & {
|
|
67
|
+
choices?: SearchVariantsChoice[];
|
|
68
|
+
/** The measured-color regions that ran. */
|
|
69
|
+
colors?: ColorRangeCondition[];
|
|
70
|
+
/** Words corrected to catalogue spellings. */
|
|
71
|
+
corrections?: {
|
|
72
|
+
correction: string;
|
|
73
|
+
word: string;
|
|
74
|
+
}[];
|
|
75
|
+
/** The `listVariants()` filters that ran: send them, with `optionalFilters` and `name`, to `listVariants()` to narrow or widen this search. */
|
|
76
|
+
filters?: FilterGroups;
|
|
77
|
+
/** The paint name looked up, as `listVariants()` takes it. */
|
|
78
|
+
name?: string;
|
|
79
|
+
/** A row per product, or per variant for a paint or color. */
|
|
80
|
+
oneRowPer: "product" | "variant";
|
|
81
|
+
/** The `listVariants()` preferences that ran. */
|
|
82
|
+
optionalFilters?: OptionalFilterGroups;
|
|
83
|
+
/** How the search ended. */
|
|
84
|
+
outcome: "page" | "no_match" | "nothing_understood" | "out_of_scope" | "brand_not_in_catalogue";
|
|
85
|
+
/** The fixed search scope, also present in `filters`. */
|
|
86
|
+
scope?: FilterGroups;
|
|
87
|
+
/** One entry per phrase the parser read, in the parser's order. */
|
|
88
|
+
understood: SearchVariantsUnderstood[];
|
|
89
|
+
/** Words nothing in the vocabulary could read. */
|
|
90
|
+
unresolved?: string[];
|
|
91
|
+
};
|
package/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Cursor, ProductId, VariantId } from "./identifiers.js";
|
|
2
|
+
import type { FilterOp } from "./list-variants-types.js";
|
|
3
|
+
import type { MaterialTile } from "./material-types.js";
|
|
2
4
|
/**
|
|
3
5
|
* Hand-mirrored wire types for the MaterialGraph public API (spec 0.5.0).
|
|
4
6
|
* These interfaces intentionally have NO runtime dependency on any
|
|
@@ -19,52 +21,21 @@ import type { ResponseAttribution } from "./attribution-types.js";
|
|
|
19
21
|
* are canonical vocabulary you discover.
|
|
20
22
|
* 2. **Absent is not false.** An optional field that is missing means UNKNOWN,
|
|
21
23
|
* not "known to be absent in reality" — most dangerously for booleans.
|
|
22
|
-
* Values carry `confidence` and `
|
|
23
|
-
* 3. **A page is not a corpus.**
|
|
24
|
-
*
|
|
25
|
-
* match set. Follow the cursor for the rest, and never report a page size as
|
|
26
|
-
* a total.
|
|
24
|
+
* Values carry `confidence` and `valueOrigin`; report them as claims.
|
|
25
|
+
* 3. **A page is not a corpus.** A list is this page. Follow the cursor for
|
|
26
|
+
* the rest, and never report a page size as a total.
|
|
27
27
|
*/
|
|
28
|
-
import type {
|
|
29
|
-
import type { Cursor, ProductId, VariantId } from "./identifiers.js";
|
|
30
|
-
import type { FilterOp } from "./list-variants-types.js";
|
|
31
|
-
import type { MaterialTile } from "./material-types.js";
|
|
32
|
-
import type { SearchMode, SearchResponseExplainReceipts } from "./search-response-receipt-types.js";
|
|
33
|
-
export type { SearchResponse } from "./search-response-types.js";
|
|
34
|
-
export type * from "./search-response-receipt-types.js";
|
|
28
|
+
import type { SearchCatalogueSubset } from "./search-catalogue-subset-types.js";
|
|
35
29
|
export type OpenEnum<T extends string> = T | (string & {});
|
|
36
30
|
export type { JsonValue } from "./json-value-types.js";
|
|
37
|
-
/**
|
|
38
|
-
* One palette swatch of a material's main image — colour DERIVED FROM PRODUCT
|
|
39
|
-
* IMAGERY, so read it as visual likeness, never as a declared colour spec.
|
|
40
|
-
*/
|
|
41
|
-
export type PaletteSwatch = {
|
|
42
|
-
hex: string;
|
|
43
|
-
proportion: number;
|
|
44
|
-
/** Stored human colour name for the swatch, when one was extracted. */
|
|
45
|
-
name?: string | null;
|
|
46
|
-
/**
|
|
47
|
-
* `hex` in OKLab, derived server-side so you can sort or measure swatches
|
|
48
|
-
* without converting them yourself. Additive, so optional: the colour lane and
|
|
49
|
-
* the material record carry it, other surfaces may not, and absent means "not
|
|
50
|
-
* derived here" — never "achromatic".
|
|
51
|
-
*/
|
|
52
|
-
oklab?: OklabTriple;
|
|
53
|
-
};
|
|
54
|
-
/** Loose colour metadata for a search result's main image; every field is independently optional. */
|
|
55
|
-
export type ColourMeta = {
|
|
56
|
-
mood?: string;
|
|
57
|
-
dominantFamily?: string;
|
|
58
|
-
dominantHex?: string;
|
|
59
|
-
};
|
|
60
31
|
/** OKLab triple. */
|
|
61
32
|
export type OklabTriple = {
|
|
62
33
|
l: number;
|
|
63
34
|
a: number;
|
|
64
35
|
b: number;
|
|
65
36
|
};
|
|
66
|
-
/** The material's dominant-
|
|
67
|
-
export type
|
|
37
|
+
/** The material's dominant-color block (tile/detail projections). */
|
|
38
|
+
export type MaterialColor = {
|
|
68
39
|
dominantHex: string | null;
|
|
69
40
|
hsl: {
|
|
70
41
|
h: number;
|
|
@@ -78,210 +49,6 @@ export type MaterialColour = {
|
|
|
78
49
|
/** Where the served image came from. */
|
|
79
50
|
export type ImageSource = OpenEnum<"r2" | "origin">;
|
|
80
51
|
export type { MaterialAvailability, MaterialTile } from "./material-types.js";
|
|
81
|
-
/**
|
|
82
|
-
* One material in a search / match result list.
|
|
83
|
-
*
|
|
84
|
-
* ABSENT IS NOT FALSE. The optional fields here (`colourMeta`, `signals`,
|
|
85
|
-
* `retired`, `productType`) are absent when the request did not ask for them or
|
|
86
|
-
* the graph holds no answer — never because the answer is "no". A missing
|
|
87
|
-
* `retired` is not "still current".
|
|
88
|
-
*/
|
|
89
|
-
export type SearchResultItem = {
|
|
90
|
-
/** This hit's attribution key — see {@link ResponseAttribution}. Opaque. */
|
|
91
|
-
resultId?: string;
|
|
92
|
-
/** Accepted sampleability fact. Absent means unknown, never false. */
|
|
93
|
-
sampleable?: boolean;
|
|
94
|
-
/** Opaque — round-trip verbatim into `getVariant()`, `pairings()`, `similarTo`. */
|
|
95
|
-
variantId: VariantId;
|
|
96
|
-
brand: string;
|
|
97
|
-
product: string;
|
|
98
|
-
brandSlug: string | null;
|
|
99
|
-
productId: ProductId | null;
|
|
100
|
-
/**
|
|
101
|
-
* The variant's Material Bank SKU, unlike our opaque `variantId`; this is the
|
|
102
|
-
* id you are expected to match
|
|
103
|
-
* against Material Bank's own catalogue, so a result list can be joined to a
|
|
104
|
-
* cart, an order or an analytics event without a per-result lookup.
|
|
105
|
-
* Populated on EVERY lane: `search()` (all four arms), `match()`, and
|
|
106
|
-
* full search responses.
|
|
107
|
-
* ABSENT means no MB identity is known for the variant — never that the SKU
|
|
108
|
-
* cannot be ordered. Presence identifies the source; it does not establish sampleability.
|
|
109
|
-
*/
|
|
110
|
-
materialBankId?: string;
|
|
111
|
-
manufacturerCode?: string;
|
|
112
|
-
projectSaveCapability?: "allowed";
|
|
113
|
-
sourceStatus?: "provisional";
|
|
114
|
-
/**
|
|
115
|
-
* What kind of material this is — the variant's primary product type, as a
|
|
116
|
-
* display name (`"Wall Tile"`, `"Modular Carpet"`, `"Interior Paint"`).
|
|
117
|
-
*
|
|
118
|
-
* This is the field that lets you tell a wall tile from a bar stool without
|
|
119
|
-
* parsing `product`. Before it existed, callers grading Match results had to
|
|
120
|
-
* regex the product's NAME, and one such grader scored pendant lights and
|
|
121
|
-
* electrical sockets as correct answers for a wall — because they are neither
|
|
122
|
-
* a stool nor a chair.
|
|
123
|
-
*
|
|
124
|
-
* ABSENT means no primary type is on record for the variant. Unknown, never
|
|
125
|
-
* "uncategorised" and never "not a material".
|
|
126
|
-
*/
|
|
127
|
-
productType?: string;
|
|
128
|
-
color: string | null;
|
|
129
|
-
variantName: string | null;
|
|
130
|
-
imageUrl: string | null;
|
|
131
|
-
/**
|
|
132
|
-
* SHA-256 of the displayed image `imageUrl` points at, as a hex digest. Key a
|
|
133
|
-
* cache or dedupe on it rather than the URL. ABSENT means no hash is on
|
|
134
|
-
* record, never that there is no image.
|
|
135
|
-
*/
|
|
136
|
-
imageSha256?: string;
|
|
137
|
-
hasImage: boolean;
|
|
138
|
-
/**
|
|
139
|
-
* How many eligible variants the row's product has, the same set
|
|
140
|
-
* `listVariants` pages, so a follow-up list returns this many: the card's
|
|
141
|
-
* "12 colourways". ABSENT means the product is unknown or has no eligible
|
|
142
|
-
* variant.
|
|
143
|
-
*/
|
|
144
|
-
variantCount?: number;
|
|
145
|
-
palette: PaletteSwatch[];
|
|
146
|
-
colourMeta?: ColourMeta;
|
|
147
|
-
/**
|
|
148
|
-
* Present when this material's imagery carries nothing beyond ONE colour, so
|
|
149
|
-
* a surface should render that colour rather than the photograph — a paint's
|
|
150
|
-
* photo is a flat fill, and the colour we hold structured beats a JPEG of it.
|
|
151
|
-
*
|
|
152
|
-
* The hex is EXTRACTED FROM the product image (a visual likeness), never a
|
|
153
|
-
* manufacturer-declared colour specification; do not present it as one.
|
|
154
|
-
* ABSENT means the question was not established (unknown product type,
|
|
155
|
-
* unextracted or multi-swatch palette) and the image should be rendered — it
|
|
156
|
-
* NEVER asserts "this is not a coating".
|
|
157
|
-
*/
|
|
158
|
-
colourChip?: {
|
|
159
|
-
hex: string;
|
|
160
|
-
};
|
|
161
|
-
/** Selected mode's weighted primary score; see maxScore. */
|
|
162
|
-
score: number;
|
|
163
|
-
retired?: boolean;
|
|
164
|
-
/**
|
|
165
|
-
* Resolved values for the attribute ids sent in `returnAttributes`, keyed by
|
|
166
|
-
* attribute id. ABSENT entirely when `returnAttributes` was not sent.
|
|
167
|
-
*
|
|
168
|
-
* Three states, all different, and reading them as two is the mistake this
|
|
169
|
-
* shape exists to prevent: no `attributes` key means you did not ask; a key
|
|
170
|
-
* whose value is `null` means this material carries no accepted value for that
|
|
171
|
-
* attribute; anything else is what the graph holds. Values resolve with the
|
|
172
|
-
* normal variant-over-product precedence, and an array is a multi-valued
|
|
173
|
-
* attribute rather than a list of candidates.
|
|
174
|
-
*/
|
|
175
|
-
attributes?: Record<string, string | string[] | null>;
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* A feed total with its honesty basis — THE CLAIM ABOUT THE MATCH SET, which is
|
|
179
|
-
* a different number from `results.length` (this page).
|
|
180
|
-
*
|
|
181
|
-
* `exact` — the true count. Say "473 materials match".
|
|
182
|
-
* `lowerBound` — a floor (cap hit or an ANN-pool bound). Say "at least
|
|
183
|
-
* 1,000", never "1,000". Never a silent approximation.
|
|
184
|
-
* `unknown` — the count query itself faulted (MG-2104): `value` is the pool
|
|
185
|
-
* this page was drawn from, NOT a claim about the corpus — do not print it as
|
|
186
|
-
* a bound. Check the response's `degraded.parts` for `"variant-count"`.
|
|
187
|
-
*
|
|
188
|
-
* Only `lowerBound` ever tells you a cap was hit. The SEARCH feed counts
|
|
189
|
-
* exactly to 1,000 and reports a floor beyond it, because it counts over
|
|
190
|
-
* attribute and tsquery universes where an unbounded count is a timeout. Read
|
|
191
|
-
* `basis`; never assume a ceiling.
|
|
192
|
-
*
|
|
193
|
-
* A response with no `counts` at all states no total: `count: true` is opt-in,
|
|
194
|
-
* and the absence of a total is not a total of zero.
|
|
195
|
-
*/
|
|
196
|
-
export type CountedTotal = {
|
|
197
|
-
value: number;
|
|
198
|
-
basis: OpenEnum<"exact" | "lowerBound" | "unknown">;
|
|
199
|
-
};
|
|
200
|
-
/**
|
|
201
|
-
* One value within a facet's counts: the CANONICAL token (send it back verbatim
|
|
202
|
-
* as a filter term), its display label, and how many variants in the counted
|
|
203
|
-
* set carry it.
|
|
204
|
-
*/
|
|
205
|
-
export type SearchFacetValue = {
|
|
206
|
-
value: string;
|
|
207
|
-
label: string;
|
|
208
|
-
variants: number;
|
|
209
|
-
};
|
|
210
|
-
/**
|
|
211
|
-
* One facet's counts. `basis` is the honesty label, and — per the negotiated
|
|
212
|
-
* MG-1010 contract — its union is CLOSED: exactly `"exact"` or `"lowerBound"`,
|
|
213
|
-
* never widened, which is why it is not an {@link OpenEnum}. `exact` — each
|
|
214
|
-
* `variants` figure is a true count under the query's whole predicate
|
|
215
|
-
* (filter-driven queries only). `lowerBound` — counted over the ranked
|
|
216
|
-
* candidate pool (`SearchFacets.poolSize`), so each figure is a floor; a
|
|
217
|
-
* pool-counted value is NEVER `exact`. `truncated` is ALWAYS present: true
|
|
218
|
-
* when more distinct values existed beyond the reported list.
|
|
219
|
-
*/
|
|
220
|
-
export type SearchFacetCount = {
|
|
221
|
-
values: SearchFacetValue[];
|
|
222
|
-
truncated: boolean;
|
|
223
|
-
basis: "exact" | "lowerBound";
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* A requested facet that was not counted, with its reason: `"unsupported"` —
|
|
227
|
-
* the id is known but facet counting does not cover it; `"timeout"` — the
|
|
228
|
-
* facet stage's own time budget expired, and the RESULTS are unaffected (the
|
|
229
|
-
* rail could not count; the page is intact). Render it as "couldn't count",
|
|
230
|
-
* never as zero.
|
|
231
|
-
*/
|
|
232
|
-
export type SearchAbsentFacet = {
|
|
233
|
-
facetId: string;
|
|
234
|
-
reason: OpenEnum<"unsupported" | "timeout">;
|
|
235
|
-
};
|
|
236
|
-
/**
|
|
237
|
-
* Per-query facet counts (MG-1010) — present ONLY when the request sent
|
|
238
|
-
* `facetCounts`. `counts` is keyed by requested facet id; every requested
|
|
239
|
-
* facet not in `absent` has an entry, including empty ones. `poolSize` is
|
|
240
|
-
* present exactly when the counts are pool-derived.
|
|
241
|
-
*/
|
|
242
|
-
export type SearchFacets = {
|
|
243
|
-
poolSize?: number;
|
|
244
|
-
requested: string[];
|
|
245
|
-
absent: SearchAbsentFacet[];
|
|
246
|
-
counts: Record<string, SearchFacetCount>;
|
|
247
|
-
};
|
|
248
|
-
/**
|
|
249
|
-
* The search response envelope. `view` discriminates the `results` union:
|
|
250
|
-
* `"full"` (default) carries {@link SearchResultItem}s, `"card"` the lean
|
|
251
|
-
* {@link SearchCardItem}s. Every other field is shared.
|
|
252
|
-
*
|
|
253
|
-
* A PAGE, NOT A CORPUS. `results.length` is what you were handed; follow
|
|
254
|
-
* `cursor` for the rest. "I found 24 materials" is wrong whenever `limit` was
|
|
255
|
-
* 24.
|
|
256
|
-
*/
|
|
257
|
-
export type SearchData = {
|
|
258
|
-
/** Result attribution — see {@link ResponseAttribution}. `basis` is `mode` verbatim here. */
|
|
259
|
-
attribution?: ResponseAttribution;
|
|
260
|
-
/**
|
|
261
|
-
* Present when a word no material carries was corrected before the search
|
|
262
|
-
* ran: the page shows results for `to` (ADR 0031).
|
|
263
|
-
*/
|
|
264
|
-
correctedQuery?: {
|
|
265
|
-
from: string;
|
|
266
|
-
to: string;
|
|
267
|
-
};
|
|
268
|
-
view: OpenEnum<"full" | "card">;
|
|
269
|
-
/** THIS PAGE. Its length is a page size, never a total. */
|
|
270
|
-
results: SearchResultItem[];
|
|
271
|
-
/** Always empty: the one search offers no refinements. */
|
|
272
|
-
suggestions: never[];
|
|
273
|
-
mode: SearchMode;
|
|
274
|
-
/** The first result's rank score, 1. */
|
|
275
|
-
maxScore: number;
|
|
276
|
-
cursor?: Cursor;
|
|
277
|
-
/** One or more sentences written for a person; absent when there is nothing to say. */
|
|
278
|
-
note?: string;
|
|
279
|
-
timings?: Record<string, number>;
|
|
280
|
-
/** Echo of the resolved query input (raw image bytes/vector are never echoed). */
|
|
281
|
-
query?: Record<string, unknown>;
|
|
282
|
-
/** A `getVariantBrief` echo of the seed variant, present only in `variantId` seed mode. */
|
|
283
|
-
seed?: MaterialTile | Record<string, unknown>;
|
|
284
|
-
} & SearchResponseExplainReceipts;
|
|
285
52
|
export type DemandMomentum = {
|
|
286
53
|
state: OpenEnum<"rising" | "fading" | "stable" | "emerging" | "insufficient_volume">;
|
|
287
54
|
percentChange: number | null;
|
|
@@ -311,13 +78,13 @@ export type ProjectTypeShare = {
|
|
|
311
78
|
* carries no `sampled` edges, so a single response can be entirely
|
|
312
79
|
* `considered`.
|
|
313
80
|
*
|
|
314
|
-
* See {@link
|
|
81
|
+
* See {@link ColorPairing} for the third, non-evidential arm.
|
|
315
82
|
*/
|
|
316
83
|
export type CospecPairing = {
|
|
317
84
|
/** The evidence tier. `cospec` = orders; `considered` = boards. Read it before phrasing. */
|
|
318
85
|
source: "cospec" | "considered";
|
|
319
86
|
/**
|
|
320
|
-
* Membership of the requested `
|
|
87
|
+
* Membership of the requested `searchCatalogueSubset`: true in, false out, null when
|
|
321
88
|
* the graph cannot say. Absent when no narrowing scope was asked for. It
|
|
322
89
|
* flags, it never removes (MG-2292).
|
|
323
90
|
*/
|
|
@@ -338,7 +105,7 @@ export type CospecPairing = {
|
|
|
338
105
|
* - `companion` — a DIFFERENT BRAND entirely. This is the cross-brand claim
|
|
339
106
|
* the rail exists to make.
|
|
340
107
|
* - `brand_sibling` — the seed's own brand, a different product.
|
|
341
|
-
* - `
|
|
108
|
+
* - `colorway` — the seed's own product, restyled.
|
|
342
109
|
*
|
|
343
110
|
* **70.6% of edges are same-brand**, so a consumer that ignores this reports
|
|
344
111
|
* the seed's own family as cross-brand intelligence (MG-1007).
|
|
@@ -346,7 +113,7 @@ export type CospecPairing = {
|
|
|
346
113
|
* Optional because responses captured before the field existed are genuinely
|
|
347
114
|
* in the wild; absent means "from an older version", never "unrelated".
|
|
348
115
|
*/
|
|
349
|
-
relation?: "companion" | "brand_sibling" | "
|
|
116
|
+
relation?: "companion" | "brand_sibling" | "colorway";
|
|
350
117
|
category: string | null;
|
|
351
118
|
projectCount: number;
|
|
352
119
|
customerCount: number;
|
|
@@ -354,41 +121,41 @@ export type CospecPairing = {
|
|
|
354
121
|
liftLabel: string;
|
|
355
122
|
demand: CompanionDemand | null;
|
|
356
123
|
topProjectTypes: ProjectTypeShare[];
|
|
357
|
-
/** Graph-owned
|
|
124
|
+
/** Graph-owned color ranking facts, present only for an explicitly ordered rail. */
|
|
358
125
|
ranking?: PairingRanking;
|
|
359
126
|
};
|
|
360
127
|
/**
|
|
361
|
-
* A pairing item topped up from
|
|
128
|
+
* A pairing item topped up from color palette-similarity — a LOOK-ALIKE, not
|
|
362
129
|
* evidence. Nobody ordered these together and nobody boarded them together; the
|
|
363
130
|
* palettes sit near each other in OKLab. Say "shares a palette with" and never
|
|
364
131
|
* "specified together" or "often paired with".
|
|
365
132
|
*/
|
|
366
|
-
export type
|
|
367
|
-
source: "
|
|
133
|
+
export type ColorPairing = {
|
|
134
|
+
source: "color";
|
|
368
135
|
score: number;
|
|
369
136
|
/**
|
|
370
|
-
* Membership of the requested `
|
|
137
|
+
* Membership of the requested `searchCatalogueSubset`: true in, false out, null when
|
|
371
138
|
* the graph cannot say. Absent when no narrowing scope was asked for. It
|
|
372
139
|
* flags, it never removes (MG-2292).
|
|
373
140
|
*/
|
|
374
141
|
inScope?: boolean | null;
|
|
375
|
-
/** Graph-owned
|
|
142
|
+
/** Graph-owned color ranking facts, present only for an explicitly ordered rail. */
|
|
376
143
|
ranking?: PairingRanking;
|
|
377
144
|
} & MaterialTile;
|
|
378
145
|
export type PairingOrderMode = "relevance" | "lightness_asc" | "lightness_desc";
|
|
379
146
|
export type PairingRanking = {
|
|
380
|
-
|
|
147
|
+
anchorColorSimilarity: number | null;
|
|
381
148
|
lightness: number | null;
|
|
382
|
-
|
|
149
|
+
anchorColorSimilarityMeasurable: boolean;
|
|
383
150
|
lightnessMeasurable: boolean;
|
|
384
|
-
basis: OpenEnum<"
|
|
151
|
+
basis: OpenEnum<"dominant_color_oklab">;
|
|
385
152
|
};
|
|
386
153
|
export type PairingOrdering = {
|
|
387
154
|
requested: OpenEnum<PairingOrderMode>;
|
|
388
155
|
applied: OpenEnum<PairingOrderMode>;
|
|
389
156
|
direction: OpenEnum<"ascending" | "descending">;
|
|
390
|
-
|
|
391
|
-
|
|
157
|
+
anchorColorBasis: OpenEnum<"one_minus_oklab_euclidean_distance">;
|
|
158
|
+
colorSpace: OpenEnum<"oklab">;
|
|
392
159
|
lightnessRange: {
|
|
393
160
|
min: 0;
|
|
394
161
|
max: 1;
|
|
@@ -398,17 +165,17 @@ export type PairingOrdering = {
|
|
|
398
165
|
};
|
|
399
166
|
/**
|
|
400
167
|
* One pairing, discriminated by `source` into three tiers of very different
|
|
401
|
-
* strength: `cospec` (real orders) > `considered` (board activity) > `
|
|
168
|
+
* strength: `cospec` (real orders) > `considered` (board activity) > `color`
|
|
402
169
|
* (palette look-alike). Branch on `source` before you write a sentence about it.
|
|
403
170
|
*/
|
|
404
|
-
export type PairingItem = CospecPairing |
|
|
171
|
+
export type PairingItem = CospecPairing | ColorPairing;
|
|
405
172
|
export type PairingsResponse = {
|
|
406
173
|
seed: MaterialTile;
|
|
407
174
|
/** A rail, not a ranked corpus — mixed evidence tiers, capped by `limit`. */
|
|
408
175
|
items: PairingItem[];
|
|
409
176
|
/**
|
|
410
177
|
* How many `items` came from real ORDER evidence (`source: "cospec"`). Zero
|
|
411
|
-
* with a full rail means every item is board activity or
|
|
178
|
+
* with a full rail means every item is board activity or color look-alike —
|
|
412
179
|
* check it before presenting the rail as demand evidence.
|
|
413
180
|
*/
|
|
414
181
|
cospecCount: number;
|
|
@@ -481,43 +248,24 @@ export type ListAttributesResult = {
|
|
|
481
248
|
};
|
|
482
249
|
};
|
|
483
250
|
export type * from "./catalog-discovery-types.js";
|
|
484
|
-
export type * from "./resolve-
|
|
251
|
+
export type * from "./resolve-color-types.js";
|
|
485
252
|
export type * from "./by-spec-types.js";
|
|
486
253
|
export type WhoamiResponse = {
|
|
487
254
|
principal: string;
|
|
488
255
|
scopes: string[];
|
|
489
256
|
};
|
|
490
|
-
/** A single colour entry in a search palette — exactly one of `hex` or `name`. */
|
|
491
|
-
export type ColourInput = {
|
|
492
|
-
hex?: string;
|
|
493
|
-
name?: string;
|
|
494
|
-
weight?: number;
|
|
495
|
-
role?: OpenEnum<"any" | "is" | "accent">;
|
|
496
|
-
sourceName?: string;
|
|
497
|
-
};
|
|
498
|
-
/** A raw incoming image to search by. */
|
|
499
|
-
export type SearchImageInput = {
|
|
500
|
-
type: "url";
|
|
501
|
-
url: string;
|
|
502
|
-
} | {
|
|
503
|
-
type: "base64";
|
|
504
|
-
data: string;
|
|
505
|
-
};
|
|
506
|
-
export type { SearchGetParams, SearchIndexFilter, SearchInput, } from "./search-request-types.js";
|
|
507
|
-
export type { SearchIndexPlan, SearchPlanReceipt, } from "./search-index-plan-types.js";
|
|
508
|
-
export type { ColourRegionRange } from "./colour-region-types.js";
|
|
509
257
|
/** Options for a pairings read. */
|
|
510
258
|
export type PairingsOptions = {
|
|
511
259
|
/** Comma-separated coarse category display names; filters both arms. */
|
|
512
260
|
categories?: string;
|
|
513
261
|
/** Page size, 1–24 inclusive (default 12). The server rejects (400), not clamps, values outside this range. */
|
|
514
262
|
limit?: number;
|
|
515
|
-
/** Return sampled order evidence only, without board or
|
|
263
|
+
/** Return sampled order evidence only, without board or color fallback. */
|
|
516
264
|
sampledOnly?: boolean;
|
|
517
|
-
/** Graph-owned order applied within fixed cospec → considered →
|
|
265
|
+
/** Graph-owned order applied within fixed cospec → considered → color tiers. */
|
|
518
266
|
order?: PairingOrderMode;
|
|
519
267
|
/** Flags each item's `inScope` against this scope; never narrows the rail. */
|
|
520
|
-
|
|
268
|
+
searchCatalogueSubset?: SearchCatalogueSubset;
|
|
521
269
|
/** The previous response's `nextCursor`, passed back verbatim. */
|
|
522
270
|
cursor?: Cursor;
|
|
523
271
|
};
|