@instruments/materialgraph 0.33.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/association-types.d.ts +11 -11
  2. package/associations-client.d.ts +7 -7
  3. package/associations-client.js +5 -5
  4. package/brand-types.d.ts +37 -41
  5. package/brand-types.js +3 -5
  6. package/brands-client.d.ts +22 -9
  7. package/brands-client.js +20 -6
  8. package/by-spec-types.d.ts +1 -1
  9. package/catalog-discovery-types.d.ts +3 -3
  10. package/catalogue-client.d.ts +2 -50
  11. package/catalogue-client.js +2 -10
  12. package/catalogue-types.d.ts +1 -1
  13. package/categories-client.d.ts +3 -5
  14. package/categories-client.js +1 -1
  15. package/category-types.d.ts +17 -30
  16. package/citation-types.d.ts +2 -2
  17. package/client-vocabulary.d.ts +2 -2
  18. package/client-vocabulary.js +4 -1
  19. package/client.d.ts +62 -105
  20. package/client.js +68 -106
  21. package/color-profiles-types.d.ts +70 -0
  22. package/colors-client.d.ts +76 -0
  23. package/colors-client.js +52 -0
  24. package/find-sampled-together-types.d.ts +70 -0
  25. package/{find-companions-types.js → find-sampled-together-types.js} +4 -4
  26. package/{find-variants-by-colour-types.d.ts → find-variants-by-color-types.d.ts} +70 -64
  27. package/find-variants-by-vector-types.d.ts +4 -1
  28. package/get-variant-types.d.ts +14 -2
  29. package/http-transport.d.ts +1 -1
  30. package/http-transport.js +1 -2
  31. package/identifiers.d.ts +2 -2
  32. package/identifiers.js +1 -1
  33. package/index.d.ts +5 -14
  34. package/index.js +1 -3
  35. package/list-variant-facets-types.d.ts +4 -4
  36. package/list-variants-types.d.ts +71 -21
  37. package/material-asset-types.d.ts +3 -3
  38. package/material-block-types.d.ts +9 -9
  39. package/material-types.d.ts +10 -8
  40. package/operations.d.ts +2 -2
  41. package/operations.js +19 -25
  42. package/package.json +2 -2
  43. package/{resolve-colour-types.d.ts → resolve-color-types.d.ts} +18 -18
  44. package/response-schemas.generated.d.ts +2 -2
  45. package/response-schemas.generated.js +41 -71
  46. package/response-schemas.js +1 -1
  47. package/samplize-catalogue-types.d.ts +4 -4
  48. package/samplize-colorvec.js +6 -6
  49. package/{corpus-scope-types.d.ts → search-catalogue-subset-types.d.ts} +3 -3
  50. package/search-variants-types.d.ts +33 -2
  51. package/types.d.ts +32 -284
  52. package/webmcp.generated.js +4 -9
  53. package/attribution-types.d.ts +0 -51
  54. package/attribution-types.js +0 -13
  55. package/by-colour-types.d.ts +0 -320
  56. package/colour-region-types.d.ts +0 -18
  57. package/colours-client.d.ts +0 -70
  58. package/colours-client.js +0 -38
  59. package/find-companions-types.d.ts +0 -70
  60. package/material-query-plan-types.d.ts +0 -70
  61. package/material-query-plan-types.js +0 -1
  62. package/ndjson.d.ts +0 -46
  63. package/ndjson.js +0 -111
  64. package/palette-search-types.d.ts +0 -171
  65. package/palette-search-types.js +0 -1
  66. package/query-understanding-types.d.ts +0 -254
  67. package/query-understanding-types.js +0 -1
  68. package/resolve-colour-types.js +0 -1
  69. package/search-card-types.d.ts +0 -86
  70. package/search-card-types.js +0 -1
  71. package/search-index-plan-types.d.ts +0 -74
  72. package/search-index-plan-types.js +0 -1
  73. package/search-refinement-types.d.ts +0 -103
  74. package/search-refinement-types.js +0 -2
  75. package/search-request-types.d.ts +0 -170
  76. package/search-request-types.js +0 -1
  77. package/search-response-receipt-types.d.ts +0 -24
  78. package/search-response-receipt-types.js +0 -1
  79. package/search-response-types.d.ts +0 -11
  80. package/search-response-types.js +0 -1
  81. package/search-result-validation.d.ts +0 -9
  82. package/search-result-validation.js +0 -83
  83. package/spec-types.d.ts +0 -109
  84. package/spec-types.js +0 -54
  85. package/specifications.d.ts +0 -34
  86. package/specifications.js +0 -56
  87. package/validation.d.ts +0 -43
  88. package/validation.js +0 -117
  89. /package/{by-colour-types.js → color-profiles-types.js} +0 -0
  90. /package/{colour-region-types.js → find-variants-by-color-types.js} +0 -0
  91. /package/{corpus-scope-types.js → resolve-color-types.js} +0 -0
  92. /package/{find-variants-by-colour-types.js → search-catalogue-subset-types.js} +0 -0
@@ -1,70 +0,0 @@
1
- import type { QueryParams } from "./http-transport.js";
2
- import type { SdkOperationId } from "./operations.js";
3
- /**
4
- * The colour surface, namespaced as `mg.colours` — MULTI-COLOUR search
5
- * (`palette`).
6
- *
7
- * A namespace rather than a flat method on an already-large client facade:
8
- * "what goes with these colours" (`palette`). `mg.codes` set the precedent for
9
- * a namespace when a capability is a family rather than a single verb.
10
- *
11
- * The namespace holds no transport of its own: the client hands it a bound
12
- * `get`/`post`, so auth, rate-limit accounting and error shaping stay in one
13
- * place.
14
- *
15
- * `palette`'s response is live and uncached.
16
- *
17
- * ROUTING OUT OF THIS NAMESPACE. Colour is a lens, and this is not the only
18
- * door into it:
19
- * - ONE colour → `mg.byColour({ hex })`. This namespace's `palette` is for N
20
- * colours at stated proportions, which is a different ranking, not a
21
- * convenience wrapper.
22
- * - A colour NAME rather than a hex → `mg.resolveColour(name)` first.
23
- * - Colour WORDS (families, moods, undertones) → `mg.colourFacets()`.
24
- * - Walking the corpus by lightness/vividness → `mg.listVariants({ orderBy })`.
25
- * - Colour as one signal among words and a seed → `mg.search(...)`.
26
- *
27
- * And the honesty rule the whole lens rests on: every colour here is DERIVED
28
- * FROM PRODUCT IMAGERY, not declared by the manufacturer. Report visual
29
- * likeness, never a stated colour specification.
30
- */
31
- import type { SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
32
- /** The client's own request primitives, handed down so this file owns no transport. */
33
- export type ColoursTransport = {
34
- get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
35
- post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
36
- };
37
- export declare class ColoursClient {
38
- #private;
39
- constructor(transport: ColoursTransport);
40
- /**
41
- * THE QUESTION: "what goes with this WHOLE PALETTE?" — N colours at once, at
42
- * stated proportions, not one colour repeated (POST `/api/v1/search/palettes`).
43
- *
44
- * Use `mg.byColour()` for a single colour.
45
- *
46
- * Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
47
- * mapping `byColour` ranks with, and `prop` rewards a candidate whose own
48
- * swatch SHARE sits near the `proportion` you asked for. So the same colours
49
- * at different proportions come back in a different order — a colour at 60%
50
- * of the surface is a different query from one at 5%. `logic: "all"` (the
51
- * default) applies a per-colour eligibility floor and returns the weighted
52
- * geometric mean, so a candidate missing one colour is rejected rather than
53
- * merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
54
- * answers but saturates once several colours score well, so prefer `all`
55
- * when the ORDER is what matters.
56
- *
57
- * Every match carries the candidate's full `palette` plus a per-colour
58
- * receipt naming the swatch that answered each queried colour.
59
- *
60
- * @example
61
- * const { matches } = await mg.colours.palette({
62
- * colours: [
63
- * { hex: "F5EFE0", proportion: 0.6 },
64
- * { hex: "9CAF88", proportion: 0.2 },
65
- * { hex: "3E3B36", proportion: 0.2 },
66
- * ],
67
- * });
68
- */
69
- palette(input: SearchPaletteInput, signal?: AbortSignal): Promise<SearchPaletteResponse>;
70
- }
package/colours-client.js DELETED
@@ -1,38 +0,0 @@
1
- export class ColoursClient {
2
- #transport;
3
- constructor(transport) {
4
- this.#transport = transport;
5
- }
6
- /**
7
- * THE QUESTION: "what goes with this WHOLE PALETTE?" — N colours at once, at
8
- * stated proportions, not one colour repeated (POST `/api/v1/search/palettes`).
9
- *
10
- * Use `mg.byColour()` for a single colour.
11
- *
12
- * Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
13
- * mapping `byColour` ranks with, and `prop` rewards a candidate whose own
14
- * swatch SHARE sits near the `proportion` you asked for. So the same colours
15
- * at different proportions come back in a different order — a colour at 60%
16
- * of the surface is a different query from one at 5%. `logic: "all"` (the
17
- * default) applies a per-colour eligibility floor and returns the weighted
18
- * geometric mean, so a candidate missing one colour is rejected rather than
19
- * merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
20
- * answers but saturates once several colours score well, so prefer `all`
21
- * when the ORDER is what matters.
22
- *
23
- * Every match carries the candidate's full `palette` plus a per-colour
24
- * receipt naming the swatch that answered each queried colour.
25
- *
26
- * @example
27
- * const { matches } = await mg.colours.palette({
28
- * colours: [
29
- * { hex: "F5EFE0", proportion: 0.6 },
30
- * { hex: "9CAF88", proportion: 0.2 },
31
- * { hex: "3E3B36", proportion: 0.2 },
32
- * ],
33
- * });
34
- */
35
- async palette(input, signal) {
36
- return await this.#transport.post("search_by_palette", input, signal);
37
- }
38
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * @module find-companions-types
3
- * @description Hand-maintained mirror of `find_companions`' (MG-2332) input and
4
- * output wire shapes. The source of truth is
5
- * `packages/products/src/cospec/schema.ts` (`findCompanionsInputSchema`,
6
- * `findCompanionsResultSchema`). This SDK has no RUNTIME dependency on
7
- * `@materialgraph/products` (it's a devDependency only, for the parity tests in
8
- * `__tests__/`), so the shapes are copied here rather than imported.
9
- * `contract-parity.test.ts` keeps this mirror honest against the server types,
10
- * the project-type list included.
11
- */
12
- /** The closed sector vocabulary co-specification evidence is tagged with. */
13
- export type CompanionProjectType = "Workplace" | "Residential" | "Institution Education" | "Hospitality: Hotel" | "Mixed-Use & Multi-Unit Residential" | "Healthcare" | "Hospitality: Restaurant" | "Government" | "Senior Living" | "Retail" | "Other" | "Sports Entertainment" | "Wellness" | "Medical Dental" | "Aviation" | "Industrial" | "Tech" | "Transportation" | "Single Family" | "Condominium Cooperative" | "Marine" | "Power & Energy" | "University Design Program: Interior Design" | "Non-Billable" | "University Design Program: Interior Architecture" | "University Design Program: Architecture" | "Historic Preservation";
14
- /** At least one of `variantIds` and `materials`, at most twelve together. */
15
- export type FindCompanionsInput = {
16
- /** Optional row fields: image adds imageUrl, colour adds swatches. */
17
- include?: ("image" | "colour")[];
18
- /** Ops and audit switch: leave sample cards in. Default false. */
19
- includeSamplingArtifacts?: boolean;
20
- /** Companions to return, default 20, at most 100. */
21
- limit?: number;
22
- /** Names to resolve when you hold no ids. Misses come back unresolved. */
23
- materials?: string[];
24
- /** Only companions sampled together in projects of this sector. */
25
- projectType?: CompanionProjectType;
26
- /** Variants to find companions for. */
27
- variantIds?: string[];
28
- };
29
- export type FindCompanionsRow = {
30
- brand: string;
31
- /** Palette likeness to the seeds. Present with `include: ["colour"]`; never reorders. */
32
- colourFit?: number | null;
33
- /** Present with `include: ["image"]`. */
34
- imageUrl?: string | null;
35
- /** Mean lift over the seeds it pairs with; above 1 is above average. */
36
- lift: number;
37
- product: string;
38
- /** Why it was suggested, in one line. */
39
- reason: string;
40
- /** `companion` is another brand; `brand_sibling` and `colourway` share the seed's. */
41
- relation: "companion" | "brand_sibling" | "colourway";
42
- /** How many seeds it was sampled with. */
43
- seedMatchCount: number;
44
- /** Present with `include: ["colour"]`. */
45
- swatches?: {
46
- hex: string;
47
- proportion: number;
48
- }[];
49
- variantId: string;
50
- variantName: string | null;
51
- };
52
- export type FindCompanionsOutput = {
53
- /** Strongest first: seeds matched, then summed lift, one row per product. */
54
- companions: FindCompanionsRow[];
55
- /** The seeds as resolved, so a caller sees what a name became. */
56
- seeds: {
57
- brand: string;
58
- /** Present with `include: ["image"]`. */
59
- imageUrl?: string | null;
60
- product: string;
61
- /** The id or name this seed came from. */
62
- resolvedFrom: string;
63
- variantId: string;
64
- variantName: string | null;
65
- }[];
66
- /** Seed ids outside the materials catalogue; present only when at least one was. */
67
- excludedByCorpusScope?: string[];
68
- /** References that matched no variant. */
69
- unresolved: string[];
70
- };
@@ -1,70 +0,0 @@
1
- /** Hand-mirrored wire types for `POST /api/v1/search/plan`. */
2
- import type { OpenEnum } from "./types.js";
3
- /** One wanted value: hard filters the page, soft only orders it. */
4
- export type MaterialQueryWant = {
5
- attributeId: string;
6
- terms: string[];
7
- phrase: string;
8
- strength: OpenEnum<"hard" | "soft">;
9
- fill: number;
10
- };
11
- export type MaterialQueryExclusion = {
12
- attributeId: string;
13
- term: string;
14
- phrase: string;
15
- };
16
- /** The dictionary reading of one query, as the index search runs it. */
17
- export type MaterialQueryReading = {
18
- normalised: string;
19
- category: {
20
- productTypeIds: string[];
21
- label: string;
22
- phrase: string;
23
- } | null;
24
- brands: {
25
- ids: string[];
26
- names: string[];
27
- phrase: string;
28
- } | null;
29
- wants: MaterialQueryWant[];
30
- excludes: MaterialQueryExclusion[];
31
- excludeBrands: string[];
32
- colour: {
33
- phrase: string;
34
- familyId: string;
35
- hex: string;
36
- oklab: [number, number, number];
37
- family12: string[];
38
- } | null;
39
- vendorColourNames: string[];
40
- leftover: string[];
41
- softAlone: boolean;
42
- };
43
- /** Present when a word no material carries was corrected: showing results for `to`. */
44
- export type MaterialQueryCorrection = {
45
- from: string;
46
- to: string;
47
- words: {
48
- word: string;
49
- correction: string;
50
- }[];
51
- };
52
- /** One search over the index. */
53
- export type MaterialSearchRequest = {
54
- text: string;
55
- view?: OpenEnum<"card" | "full">;
56
- limit?: number;
57
- cursor?: string;
58
- };
59
- export type MaterialQueryPlan = {
60
- version: OpenEnum<"2">;
61
- rawQuery: string;
62
- normalisedQuery: string;
63
- readingVersion: string;
64
- source: OpenEnum<"dictionary" | "jev">;
65
- cached: boolean;
66
- plan: MaterialQueryReading;
67
- correction: MaterialQueryCorrection | null;
68
- /** The one search to run, in the phrasing that was read. */
69
- requests: MaterialSearchRequest[];
70
- };
@@ -1 +0,0 @@
1
- export {};
package/ndjson.d.ts DELETED
@@ -1,46 +0,0 @@
1
- /**
2
- * A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
3
- * primitive for line-oriented JSON streams. Hand-rolled so the
4
- * SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
5
- * where `fetch` yields a byte `ReadableStream`.
6
- *
7
- * Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
8
- * nothing but "text arrives in chunks": SSE frames are `field: value` lines
9
- * terminated by a BLANK line, with comment lines and multi-line `data:`
10
- * accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
11
- * NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
12
- * named `{"type"` and silently discard every frame. The two also want OPPOSITE
13
- * end-of-stream policies: SSE dispatches a trailing partial frame as valid,
14
- * while a truncated NDJSON line must surface as an error (enforced by the
15
- * caller, which parses whatever `flush` returns rather than dropping it).
16
- *
17
- * Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
18
- * left by a CRLF-normalising proxy stays as trailing whitespace on the line,
19
- * which `JSON.parse` ignores.
20
- */
21
- /**
22
- * A push-based NDJSON line assembler. Feed it decoded text with {@link push};
23
- * it returns the lines that text COMPLETED, terminators removed. Any partial
24
- * trailing line is held in an internal buffer until a later chunk terminates it,
25
- * or until {@link flush} surrenders it at end of stream.
26
- */
27
- export declare class NdjsonDecoder {
28
- #private;
29
- /** Absorb one decoded chunk and return the lines it completed, in order. */
30
- push(text: string): string[];
31
- /**
32
- * Surrender the unterminated remainder at end of stream — a well-formed
33
- * NDJSON stream's final line need not carry a trailing newline. The remainder
34
- * is RETURNED, never dropped: a truncated frame is the caller's error to
35
- * raise, not this decoder's fragment to discard.
36
- */
37
- flush(): string[];
38
- }
39
- /**
40
- * Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
41
- * content are yielded — a blank separator line carries no frame and must never
42
- * reach `JSON.parse`, where it would become an empty object. Stops cleanly when
43
- * the stream ends or `signal` aborts (mid-read abort rejections are swallowed
44
- * once the signal is set).
45
- */
46
- export declare function readNdjsonLines(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<string, void, unknown>;
package/ndjson.js DELETED
@@ -1,111 +0,0 @@
1
- /**
2
- * A minimal, dependency-free NDJSON (newline-delimited JSON) line reader — the
3
- * primitive for line-oriented JSON streams. Hand-rolled so the
4
- * SDK stays zero-runtime-dep and works unchanged inside Node route handlers,
5
- * where `fetch` yields a byte `ReadableStream`.
6
- *
7
- * Deliberately a SIBLING of `sse.ts`, not a reuse of it. NDJSON and SSE agree on
8
- * nothing but "text arrives in chunks": SSE frames are `field: value` lines
9
- * terminated by a BLANK line, with comment lines and multi-line `data:`
10
- * accumulation, whereas an NDJSON frame is exactly one line of JSON. Feeding
11
- * NDJSON through the SSE tokenizer would read `{"type":"step",…}` as a field
12
- * named `{"type"` and silently discard every frame. The two also want OPPOSITE
13
- * end-of-stream policies: SSE dispatches a trailing partial frame as valid,
14
- * while a truncated NDJSON line must surface as an error (enforced by the
15
- * caller, which parses whatever `flush` returns rather than dropping it).
16
- *
17
- * Line endings: the reader splits on `\n` only, per the NDJSON contract. A `\r`
18
- * left by a CRLF-normalising proxy stays as trailing whitespace on the line,
19
- * which `JSON.parse` ignores.
20
- */
21
- /**
22
- * A push-based NDJSON line assembler. Feed it decoded text with {@link push};
23
- * it returns the lines that text COMPLETED, terminators removed. Any partial
24
- * trailing line is held in an internal buffer until a later chunk terminates it,
25
- * or until {@link flush} surrenders it at end of stream.
26
- */
27
- export class NdjsonDecoder {
28
- #buffer = "";
29
- /** Absorb one decoded chunk and return the lines it completed, in order. */
30
- push(text) {
31
- this.#buffer += text;
32
- const lines = [];
33
- for (;;) {
34
- const index = this.#buffer.indexOf("\n");
35
- if (index === -1) {
36
- break;
37
- }
38
- lines.push(this.#buffer.slice(0, index));
39
- this.#buffer = this.#buffer.slice(index + 1);
40
- }
41
- return lines;
42
- }
43
- /**
44
- * Surrender the unterminated remainder at end of stream — a well-formed
45
- * NDJSON stream's final line need not carry a trailing newline. The remainder
46
- * is RETURNED, never dropped: a truncated frame is the caller's error to
47
- * raise, not this decoder's fragment to discard.
48
- */
49
- flush() {
50
- if (this.#buffer.length === 0) {
51
- return [];
52
- }
53
- const remainder = this.#buffer;
54
- this.#buffer = "";
55
- return [remainder];
56
- }
57
- }
58
- /**
59
- * Iterate the NDJSON lines of a byte stream. Only lines with non-whitespace
60
- * content are yielded — a blank separator line carries no frame and must never
61
- * reach `JSON.parse`, where it would become an empty object. Stops cleanly when
62
- * the stream ends or `signal` aborts (mid-read abort rejections are swallowed
63
- * once the signal is set).
64
- */
65
- export async function* readNdjsonLines(body, signal) {
66
- const reader = body.getReader();
67
- const decoder = new TextDecoder();
68
- const ndjson = new NdjsonDecoder();
69
- try {
70
- for (;;) {
71
- if (signal?.aborted) {
72
- return;
73
- }
74
- let result;
75
- try {
76
- result = await reader.read();
77
- }
78
- catch (error) {
79
- if (signal?.aborted) {
80
- return;
81
- }
82
- throw error;
83
- }
84
- if (result.done) {
85
- // `decode()` with no argument flushes any dangling multi-byte sequence.
86
- for (const line of ndjson.push(decoder.decode())) {
87
- if (line.trim().length > 0) {
88
- yield line;
89
- }
90
- }
91
- for (const line of ndjson.flush()) {
92
- if (line.trim().length > 0) {
93
- yield line;
94
- }
95
- }
96
- return;
97
- }
98
- for (const line of ndjson.push(decoder.decode(result.value, { stream: true }))) {
99
- if (signal?.aborted) {
100
- return;
101
- }
102
- if (line.trim().length > 0) {
103
- yield line;
104
- }
105
- }
106
- }
107
- }
108
- finally {
109
- reader.releaseLock();
110
- }
111
- }
@@ -1,171 +0,0 @@
1
- import type { ColourCategoryGate } from "./by-colour-types.js";
2
- import type { CorpusScope, CorpusScopeReceipt } from "./corpus-scope-types.js";
3
- /**
4
- * Wire types for MULTI-COLOUR search — `POST /api/v1/search/palettes`.
5
- *
6
- * Kept out of `types.ts` for the repository's source-file size budget. Same
7
- * tolerant-parsing contract as the rest of the mirror — response enums are
8
- * widened so a new server value still type-checks.
9
- *
10
- * The MATCH SEMANTICS these types carry are the server's, stated on
11
- * {@link SearchPaletteInput.logic} rather than left to the endpoint docs.
12
- */
13
- import type { VariantId } from "./identifiers.js";
14
- import type { OklabTriple, OpenEnum } from "./types.js";
15
- /** How the per-colour scores are combined across a palette. */
16
- export type PaletteLogic = OpenEnum<"all" | "any">;
17
- /** One queried palette colour — exactly one of `hex` or `oklab`. */
18
- export type PaletteQueryColour = {
19
- hex?: string;
20
- oklab?: OklabTriple;
21
- /**
22
- * Share of the surface this colour should occupy, in (0,1]. A colour stated
23
- * below 5% is dropped; otherwise not applied, since a palette is ordered by
24
- * its first colour alone.
25
- */
26
- proportion?: number;
27
- /** Relative importance. Not applied: a palette is ordered by its first colour. */
28
- weight?: number;
29
- };
30
- export type SearchPaletteInput = {
31
- /**
32
- * HARD category filter: category-group or product-type names or keys.
33
- * Absent, the page holds surface materials only, so lights, taps and
34
- * furniture are left out.
35
- */
36
- category?: string[];
37
- corpusScope?: CorpusScope;
38
- /**
39
- * The palette to match, 1–8 colours. The page is ordered by the FIRST colour;
40
- * the rest come back in `query.droppedColours`.
41
- */
42
- colours: PaletteQueryColour[];
43
- /**
44
- * Kept so existing callers still parse: the one search orders the page by
45
- * the first colour with no eligibility floor, so `all` and `any` return the
46
- * same page.
47
- */
48
- logic?: PaletteLogic;
49
- /** Results to return; defaults to 24, capped at 48. */
50
- limit?: number;
51
- };
52
- /**
53
- * One swatch used for scoring. Samplize paint uses its selected published hex;
54
- * other materials use colorscope palettes extracted from imagery. `name` is null when no human
55
- * name was resolved — unnamed, not colourless.
56
- */
57
- export type CandidateSwatch = {
58
- hex: string;
59
- proportion: number;
60
- name: string | null;
61
- };
62
- /** Why a queried colour (or gradient sample) scored what it scored. */
63
- export type ColourMatchReceipt = {
64
- queryHex: string;
65
- /** `colourScore × proportionScore`, so `colourScore` on the one search. */
66
- score: number;
67
- /**
68
- * Closeness in [0,1] of the matched swatch to the queried colour, from plain
69
- * OKLab distance with lightness counted in full. Not a confidence.
70
- */
71
- colourScore: number;
72
- /** Proportion agreement; always 1, since no proportion is applied. */
73
- proportionScore: number;
74
- /** The swatch that produced the score, or null when nothing matched. */
75
- matched: CandidateSwatch | null;
76
- /**
77
- * Whether `matched` has a hue compatible with the queried colour, at the
78
- * response's `hueGate` thresholds. A flag, never a gate: an incompatible
79
- * swatch still scores and ranks. Absent when there is no `matched` swatch.
80
- */
81
- hueCompatible?: boolean;
82
- };
83
- /** Identity and evidence shared by both multi-colour match shapes. */
84
- export type MultiColourMatchBase = {
85
- /** Opaque — round-trip verbatim. */
86
- variantId: VariantId;
87
- brand: string;
88
- product: string;
89
- brandSlug: string | null;
90
- productId: string | null;
91
- color: string | null;
92
- variantName: string | null;
93
- imageUrl: string | null;
94
- /**
95
- * SHA-256 of the displayed image `imageUrl` points at, as a hex digest.
96
- * ABSENT means no hash is on record, never that there is no image.
97
- */
98
- imageSha256?: string;
99
- /** MB has retired or hidden this SKU. */
100
- retired: boolean;
101
- /**
102
- * The catalog GROUP — "Flooring", "Textile Materials", "Coatings" — or null
103
- * when the material has no accepted primary product type.
104
- *
105
- * A palette query is cross-category BY INTENT: a designer building a scheme,
106
- * not browsing one aisle. So this is the field that says whether a result
107
- * answers the question — one real palette returned 30 of 40 results in a
108
- * single category while every one of the fourteen groups held something
109
- * closer on the same colours (MG-1245). Group by it, and keep null as its own
110
- * bucket: null means UNKNOWN, not uncategorised, and it is not rare.
111
- */
112
- category: string | null;
113
- /** The candidate's full extracted palette — the evidence scored against. */
114
- palette: CandidateSwatch[];
115
- /**
116
- * Closeness in [0,1] of the material's own colour to the searched colour,
117
- * the page's order. Not a confidence or a percentage.
118
- */
119
- score: number;
120
- };
121
- export type PaletteMatch = {
122
- /** One receipt per queried colour, in query order. */
123
- colours: ColourMatchReceipt[];
124
- } & MultiColourMatchBase;
125
- /**
126
- * The thresholds behind every receipt's `hueCompatible`: the exact values
127
- * passed to colorscope's `isHueCompatible`, fixed server-side with no request
128
- * knob. A FLAG, never admission or ranking: every swatch still scores, and the
129
- * page and its order are the same with or without it.
130
- */
131
- export type HueGateReceipt = {
132
- /** Below this query chroma the colour is achromatic and any hue is admitted. */
133
- achromaticQueryChroma: number;
134
- /** A swatch must carry at least this share of the query colour's chroma. */
135
- minChromaRatio: number;
136
- /** Floor on the cosine agreement between the two hue vectors. */
137
- minHueCosine: number;
138
- };
139
- /**
140
- * A PAGE, NOT A CORPUS. `count` is `matches.length` under `limit` (24 default,
141
- * 48 max) — this surface states no corpus total and has no cursor, so "N
142
- * materials match this palette" is not a claim you can make from it.
143
- */
144
- export type SearchPaletteResponse = {
145
- corpusScope?: CorpusScopeReceipt;
146
- /** The query as interpreted — check it before trusting a surprising order. */
147
- query: {
148
- /** Colours that actually ran after minor stated shares were omitted. */
149
- colours: {
150
- hex: string;
151
- proportion?: number;
152
- weight?: number;
153
- }[];
154
- /** Colours not searched because their stated share was below 5%. */
155
- droppedColours: {
156
- hex: string;
157
- proportion?: number;
158
- weight?: number;
159
- }[];
160
- logic: PaletteLogic;
161
- /** `no_effective_colours` means no material query ran. */
162
- outcome: OpenEnum<"matches" | "no_effective_colours">;
163
- };
164
- /** The category gate the page ran under. */
165
- categoryGate?: ColourCategoryGate;
166
- /** The thresholds behind each receipt's `hueCompatible`. Absent only on responses stored before it existed. */
167
- hueGate?: HueGateReceipt;
168
- /** `matches.length`, capped by `limit`. Never a corpus total. */
169
- count: number;
170
- matches: PaletteMatch[];
171
- };
@@ -1 +0,0 @@
1
- export {};