@instruments/materialgraph 0.4.0 → 0.6.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/client.js CHANGED
@@ -60,6 +60,7 @@ export class MaterialGraphClient {
60
60
  });
61
61
  this.colours = new ColoursClient({
62
62
  get: (operationId, query, signal) => this.#get(operationId, query, signal),
63
+ post: (operationId, body, signal) => this.#post(operationId, body, signal),
63
64
  });
64
65
  }
65
66
  get lastRateLimit() {
@@ -8,8 +8,12 @@
8
8
  * widened so a new server value still type-checks.
9
9
  */
10
10
  import type { OpenEnum } from "./types.js";
11
- /** Which taxonomy tier a colour profile's `categoryKey` names. */
12
- export type CategoryTier = OpenEnum<"group" | "type">;
11
+ /**
12
+ * Which taxonomy tier a colour profile's `categoryKey` names — or `"merged"`,
13
+ * where the key is a composite over a caller-defined key set rather than a
14
+ * taxonomy node.
15
+ */
16
+ export type CategoryTier = OpenEnum<"group" | "type" | "merged">;
13
17
  /** How the endpoint picks which colours to keep. */
14
18
  export type ColourProfileSelect = OpenEnum<"frequency" | "diverse">;
15
19
  /** One representative colour bucket of a category. */
@@ -29,6 +33,11 @@ export interface CategoryColourProfile {
29
33
  level: CategoryTier;
30
34
  name: string;
31
35
  path: string;
36
+ /**
37
+ * Under `groupBy: "merged"`, the requested keys in composite-key order, so
38
+ * `categoryKey` never has to be split. Absent on a single-category profile.
39
+ */
40
+ members?: string[];
32
41
  variantCount: number;
33
42
  avgLightness: number;
34
43
  avgChroma: number;
@@ -51,8 +60,11 @@ export interface CategoryColourProfilesResponse {
51
60
  export interface ColourProfilesParams {
52
61
  /** 1–8 category keys, at the tier named by `groupBy`. */
53
62
  productTypeIds: readonly string[];
54
- /** `"group"` merges each group's leaf product types into one palette. */
55
- groupBy?: "group" | "type";
63
+ /**
64
+ * `"group"` merges each group's leaf product types into one palette;
65
+ * `"merged"` merges EVERY key's leaves into a single combined palette.
66
+ */
67
+ groupBy?: "group" | "type" | "merged";
56
68
  /** Representative colours per category, 1–64. */
57
69
  topN?: number;
58
70
  /** `"diverse"` maximises visual distinctness instead of frequency. */
@@ -1,26 +1,31 @@
1
1
  /**
2
- * The colour AGGREGATE surface, namespaced as `mg.colours`.
2
+ * The colour surface, namespaced as `mg.colours` — the aggregate reads
3
+ * (`stats`, `profiles`) plus MULTI-COLOUR search (`palette`, `gradient`).
3
4
  *
4
- * Two reads that answer the same question at two altitudes — "how much colour
5
- * does the graph hold at all" (`stats`) and "what colours does this category
6
- * come in" (`profiles`) — so they live together rather than as two more flat
7
- * methods on an already-large client facade. `mg.codes` and `mg.browse` set the
8
- * precedent for a namespace when a capability is a small family rather than a
9
- * single verb.
5
+ * One colour family rather than four more flat methods on an already-large
6
+ * client facade: "how much colour does the graph hold at all" (`stats`), "what
7
+ * colours does this category come in" (`profiles`), "what goes with these
8
+ * colours" (`palette`), and "what moves from this colour to that one"
9
+ * (`gradient`). `mg.codes` and `mg.browse` set the precedent for a namespace
10
+ * when a capability is a family rather than a single verb.
10
11
  *
11
12
  * The namespace holds no transport of its own: the client hands it a bound
12
- * `get`, so auth, rate-limit accounting and error shaping stay in one place.
13
+ * `get`/`post`, so auth, rate-limit accounting and error shaping stay in one
14
+ * place.
13
15
  *
14
- * Both responses carry a `basis`. These are reads of a materialized view that is
15
- * refreshed on a schedule, not on write, so a caller can and should check how
16
- * much of the corpus the answer actually covers before treating it as complete.
16
+ * The two AGGREGATE responses carry a `basis`. Those are reads of a
17
+ * materialized view refreshed on a schedule, not on write, so a caller can and
18
+ * should check how much of the corpus the answer actually covers before
19
+ * treating it as complete. The two SEARCH responses are live and uncached.
17
20
  */
18
21
  import type { CategoryColourProfilesResponse, ColourProfilesParams, ColourStatsResponse } from "./colour-aggregate-types.js";
19
22
  import type { QueryParams } from "./http-transport.js";
20
23
  import type { SdkOperationId } from "./operations.js";
21
- /** The client's own request primitive, handed down so this file owns no transport. */
24
+ import type { SearchGradientInput, SearchGradientResponse, SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
25
+ /** The client's own request primitives, handed down so this file owns no transport. */
22
26
  export interface ColoursTransport {
23
27
  get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
28
+ post<T>(operationId: SdkOperationId, body: unknown, signal?: AbortSignal): Promise<T>;
24
29
  }
25
30
  export declare class ColoursClient {
26
31
  #private;
@@ -37,6 +42,13 @@ export declare class ColoursClient {
37
42
  * cannot recover that spread from a truncated frequency list, which is why it
38
43
  * is a server mode rather than a local sort.
39
44
  *
45
+ * `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
46
+ * over the union of their leaves — the palette of a caller-defined facet such
47
+ * as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
48
+ * single node for, and that no combination of per-key responses reconstructs.
49
+ * Its keys may sit at either tier, `categoryKey` is those keys sorted and
50
+ * joined with `+`, and `members` hands them back so nothing has to split it.
51
+ *
40
52
  * A selected category with no colour data comes back as a zero-count profile,
41
53
  * not a missing key.
42
54
  */
@@ -49,4 +61,42 @@ export declare class ColoursClient {
49
61
  * answer, listed rather than silently absent.
50
62
  */
51
63
  stats(signal?: AbortSignal): Promise<ColourStatsResponse>;
64
+ /**
65
+ * Find materials that go with a WHOLE PALETTE — N colours at once, not one
66
+ * colour repeated (POST `/api/v1/search/palettes`).
67
+ *
68
+ * Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
69
+ * mapping `byColour` ranks with, and `prop` rewards a candidate whose own
70
+ * swatch SHARE sits near the `proportion` you asked for. So the same colours
71
+ * at different proportions come back in a different order — a colour at 60%
72
+ * of the surface is a different query from one at 5%. `logic: "all"` (the
73
+ * default) applies a per-colour eligibility floor and returns the weighted
74
+ * geometric mean, so a candidate missing one colour is rejected rather than
75
+ * merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
76
+ * answers but saturates once several colours score well, so prefer `all`
77
+ * when the ORDER is what matters.
78
+ *
79
+ * Every match carries the candidate's full `palette` plus a per-colour
80
+ * receipt naming the swatch that answered each queried colour.
81
+ *
82
+ * @example
83
+ * const { matches } = await mg.colours.palette({
84
+ * colours: [
85
+ * { hex: "F5EFE0", proportion: 0.6 },
86
+ * { hex: "9CAF88", proportion: 0.2 },
87
+ * { hex: "3E3B36", proportion: 0.2 },
88
+ * ],
89
+ * });
90
+ */
91
+ palette(input: SearchPaletteInput, signal?: AbortSignal): Promise<SearchPaletteResponse>;
92
+ /**
93
+ * Find materials that move ALONG a colour transition — "cream to rust", in
94
+ * order (POST `/api/v1/search/gradients`).
95
+ *
96
+ * Not a palette: the score is multiplied by `coverage`, the share of the path
97
+ * the candidate actually spans, so a material owning only one end ranks below
98
+ * one that travels the whole way, and a flat single-colour material is
99
+ * rejected outright however close its hue sits to the path.
100
+ */
101
+ gradient(input: SearchGradientInput, signal?: AbortSignal): Promise<SearchGradientResponse>;
52
102
  }
package/colours-client.js CHANGED
@@ -15,6 +15,13 @@ export class ColoursClient {
15
15
  * cannot recover that spread from a truncated frequency list, which is why it
16
16
  * is a server mode rather than a local sort.
17
17
  *
18
+ * `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
19
+ * over the union of their leaves — the palette of a caller-defined facet such
20
+ * as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
21
+ * single node for, and that no combination of per-key responses reconstructs.
22
+ * Its keys may sit at either tier, `categoryKey` is those keys sorted and
23
+ * joined with `+`, and `members` hands them back so nothing has to split it.
24
+ *
18
25
  * A selected category with no colour data comes back as a zero-count profile,
19
26
  * not a missing key.
20
27
  */
@@ -36,4 +43,46 @@ export class ColoursClient {
36
43
  stats(signal) {
37
44
  return this.#transport.get("get_colour_stats", undefined, signal);
38
45
  }
46
+ /**
47
+ * Find materials that go with a WHOLE PALETTE — N colours at once, not one
48
+ * colour repeated (POST `/api/v1/search/palettes`).
49
+ *
50
+ * Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
51
+ * mapping `byColour` ranks with, and `prop` rewards a candidate whose own
52
+ * swatch SHARE sits near the `proportion` you asked for. So the same colours
53
+ * at different proportions come back in a different order — a colour at 60%
54
+ * of the surface is a different query from one at 5%. `logic: "all"` (the
55
+ * default) applies a per-colour eligibility floor and returns the weighted
56
+ * geometric mean, so a candidate missing one colour is rejected rather than
57
+ * merely ranked low; `logic: "any"` is a noisy-OR that accepts partial
58
+ * answers but saturates once several colours score well, so prefer `all`
59
+ * when the ORDER is what matters.
60
+ *
61
+ * Every match carries the candidate's full `palette` plus a per-colour
62
+ * receipt naming the swatch that answered each queried colour.
63
+ *
64
+ * @example
65
+ * const { matches } = await mg.colours.palette({
66
+ * colours: [
67
+ * { hex: "F5EFE0", proportion: 0.6 },
68
+ * { hex: "9CAF88", proportion: 0.2 },
69
+ * { hex: "3E3B36", proportion: 0.2 },
70
+ * ],
71
+ * });
72
+ */
73
+ palette(input, signal) {
74
+ return this.#transport.post("search_by_palette", input, signal);
75
+ }
76
+ /**
77
+ * Find materials that move ALONG a colour transition — "cream to rust", in
78
+ * order (POST `/api/v1/search/gradients`).
79
+ *
80
+ * Not a palette: the score is multiplied by `coverage`, the share of the path
81
+ * the candidate actually spans, so a material owning only one end ranks below
82
+ * one that travels the whole way, and a flat single-colour material is
83
+ * rejected outright however close its hue sits to the path.
84
+ */
85
+ gradient(input, signal) {
86
+ return this.#transport.post("search_by_gradient", input, signal);
87
+ }
39
88
  }
package/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export type * from "./codes-types.js";
21
21
  export { ColoursClient } from "./colours-client.js";
22
22
  export type { ColoursTransport } from "./colours-client.js";
23
23
  export type * from "./colour-aggregate-types.js";
24
+ export type * from "./palette-search-types.js";
24
25
  export type * from "./brand-types.js";
25
26
  export type * from "./material-asset-types.js";
26
27
  export type * from "./material-block-types.js";
package/operations.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export declare const MATERIALGRAPH_API_VERSION = "0.7.0";
2
- declare const OPERATION_DEFINITIONS: readonly [readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["suggest", "GET", "/api/v1/search/suggest", "search_suggest"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
2
+ declare const OPERATION_DEFINITIONS: readonly [readonly ["search", "POST", "/api/v1/search", "search_materials_post"], readonly ["searchGet", "GET", "/api/v1/search", "search_materials_get"], readonly ["match", "POST", "/api/v1/search/match", "match_material"], readonly ["byColour", "GET", "/api/v1/search/colours", "search_by_colour"], readonly ["material", "GET", "/api/v1/materials/{variantId}", "get_material"], readonly ["siblings", "GET", "/api/v1/materials/{variantId}/siblings", "get_material_siblings"], readonly ["materialBlocks", "GET", "/api/v1/materials/{variantId}/blocks", "list_material_blocks"], readonly ["materialAssets", "GET", "/api/v1/materials/{variantId}/assets", "list_material_assets"], readonly ["schema", "GET", "/api/v1/schema/blocks", "list_schema_blocks"], readonly ["schema", "GET", "/api/v1/schema/attributes", "list_schema_attributes"], readonly ["schema", "GET", "/api/v1/schema/verticals", "list_schema_verticals"], readonly ["schema", "GET", "/api/v1/schema/product-types", "list_schema_product_types"], readonly ["schema", "GET", "/api/v1/schema/units", "list_schema_units"], readonly ["schema", "GET", "/api/v1/schema/value-dictionaries", "list_schema_value_dictionaries"], readonly ["schema", "GET", "/api/v1/schema/source-requirements", "list_schema_source_requirements"], readonly ["schema", "GET", "/api/v1/schema/product-type-templates", "list_schema_product_type_templates"], readonly ["schema", "GET", "/api/v1/schema/mappings", "list_schema_mappings"], readonly ["pairings", "GET", "/api/v1/materials/{variantId}/pairings", "get_material_pairings"], readonly ["materials", "POST", "/api/v1/materials/batch", "get_materials_batch_post"], readonly ["materialsCached", "GET", "/api/v1/materials/batch", "get_materials_batch_get"], readonly ["browse", "GET", "/api/v1/search/browse", "search_browse_by_colour"], readonly ["colourFacets", "GET", "/api/v1/colours/facets", "get_colour_facets"], readonly ["colours.profiles", "GET", "/api/v1/colours/profiles", "get_category_colour_profiles"], readonly ["colours.stats", "GET", "/api/v1/colours/stats", "get_colour_stats"], readonly ["resolveColour", "GET", "/api/v1/search/colours/resolve", "resolve_colour_name"], readonly ["suggestColour", "GET", "/api/v1/search/colours/suggest", "suggest_colour_names"], readonly ["suggest", "GET", "/api/v1/search/suggest", "search_suggest"], readonly ["resolveAttribute", "GET", "/api/v1/catalog/attributes/resolve", "catalog_resolve_attribute"], readonly ["resolveAttributeValue", "GET", "/api/v1/catalog/attributes/{attributeId}/values/resolve", "catalog_resolve_value"], readonly ["searchVocabulary", "GET", "/api/v1/catalog/vocabulary/search", "catalog_vocabulary_search"], readonly ["bySpec", "GET", "/api/v1/search/specifications", "catalog_by_spec"], readonly ["taxonomy", "GET", "/api/v1/taxonomy/categories", "get_category_taxonomy"], readonly ["whoami", "GET", "/api/v1/whoami", "whoami"], readonly ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"], readonly ["demand", "GET", "/api/v1/demand", "get_material_demand"], readonly ["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"], readonly ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"], readonly ["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"], readonly ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"], readonly ["attributeValues", "GET", "/api/v1/catalog/attributes/{attributeId}/values", "list_attribute_values"], readonly ["applicableSpecs", "GET", "/api/v1/taxonomy/categories/{key}/specs", "get_applicable_specs"], readonly ["glossary", "GET", "/api/v1/glossary/{term}", "explain_material_term"], readonly ["brands", "GET", "/api/v1/brands", "list_brands"], readonly ["brand", "GET", "/api/v1/brands/{brandSlug}", "get_brand_profile"], readonly ["uploadSourceMaterial", "POST", "/api/v1/source-material", "upload_source_material"], readonly ["sourceMaterialBatch", "GET", "/api/v1/source-material/{batchId}", "get_source_material_batch"], readonly ["capabilities", "GET", "/api/v1/capabilities", "list_public_capabilities"], readonly ["createRun", "POST", "/api/v1/runs", "create_public_run"], readonly ["run", "GET", "/api/v1/runs/{runId}", "get_public_run"], readonly ["runSchedule", "GET", "/api/v1/runs/{runId}/schedule", "get_run_finish_schedule"], readonly ["streamRunEvents", "GET", "/api/v1/runs/{runId}/events", "list_public_run_events"], readonly ["resumeRun", "POST", "/api/v1/runs/{runId}/resume", "resume_public_run"], readonly ["cancelRun", "POST", "/api/v1/runs/{runId}/cancel", "cancel_public_run"], readonly ["varyScheme", "POST", "/api/v1/schemes/vary", "vary_schemes"], readonly ["createProject", "POST", "/api/v1/projects", "create_box_project"], readonly ["listProjects", "GET", "/api/v1/projects", "list_box_projects"], readonly ["getProject", "GET", "/api/v1/projects/{projectId}", "get_box_project"], readonly ["appendProjectEvent", "POST", "/api/v1/projects/{projectId}/events", "append_box_project_event"], readonly ["demandBatch", "POST", "/api/v1/demand/batch", "get_demand_batch"], readonly ["codeAdvisory", "POST", "/api/v1/codes/advisory", "resolveCodeAdvisory"], readonly ["codes.context", "GET", "/api/v1/codes/context", "list_code_context"], readonly ["codes.requirements", "GET", "/api/v1/codes/requirements", "list_code_requirements"]];
3
3
  export type SdkOperationId = (typeof OPERATION_DEFINITIONS)[number][3];
4
4
  export type SdkOperation = Readonly<{
5
5
  clientMethod: (typeof OPERATION_DEFINITIONS)[number][0];
package/operations.js CHANGED
@@ -40,7 +40,9 @@ const OPERATION_DEFINITIONS = [
40
40
  ["whoami", "GET", "/api/v1/whoami", "whoami"],
41
41
  ["coordinate", "POST", "/api/v1/pairings/coordinate", "coordinate_materials"],
42
42
  ["demand", "GET", "/api/v1/demand", "get_material_demand"],
43
+ ["colours.palette", "POST", "/api/v1/search/palettes", "search_by_palette"],
43
44
  ["extractPalette", "POST", "/api/v1/search/palettes/extract", "extract_image_palette"],
45
+ ["colours.gradient", "POST", "/api/v1/search/gradients", "search_by_gradient"],
44
46
  ["specificationFacets", "GET", "/api/v1/catalog/specifications/facets", "list_spec_facets"],
45
47
  [
46
48
  "attributeValues",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruments/materialgraph",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Wire types for MULTI-COLOUR search — `POST /api/v1/search/palettes` and
3
+ * `POST /api/v1/search/gradients`.
4
+ *
5
+ * Kept out of `types.ts` for the same reason `colour-aggregate-types.ts` and
6
+ * `codes-types.ts` are: the shared mirror is at its source-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} and {@link GradientMatch.coverage} rather
12
+ * than left to the endpoint docs.
13
+ */
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 interface PaletteQueryColour {
19
+ hex?: string;
20
+ oklab?: OklabTriple;
21
+ /**
22
+ * Share of the surface this colour should occupy, in (0,1]. Stating it
23
+ * CHANGES the ranking: a colour asked for at 0.6 rewards candidates whose own
24
+ * swatch sits near 0.6, not merely candidates that contain the colour.
25
+ */
26
+ proportion?: number;
27
+ /** Relative importance; defaults to 1 and is normalised across the query. */
28
+ weight?: number;
29
+ }
30
+ /** One gradient stop — exactly one of `hex` or `oklab`. Order is meaningful. */
31
+ export interface GradientStop {
32
+ hex?: string;
33
+ oklab?: OklabTriple;
34
+ }
35
+ export interface SearchPaletteInput {
36
+ /** The palette to match, 1–8 colours. */
37
+ colours: PaletteQueryColour[];
38
+ /**
39
+ * `all` (the default) requires every queried colour to clear a per-colour
40
+ * eligibility floor and scores the weighted geometric mean; `any` is a
41
+ * noisy-OR and never rejects a candidate for missing one colour.
42
+ */
43
+ logic?: PaletteLogic;
44
+ /** Results to return; defaults to 24, capped at 48. */
45
+ limit?: number;
46
+ }
47
+ export interface SearchGradientInput {
48
+ /** The ordered colour transition, 2–6 stops. */
49
+ stops: GradientStop[];
50
+ /** Results to return; defaults to 24, capped at 48. */
51
+ limit?: number;
52
+ }
53
+ /** One swatch of a candidate's colorscope-extracted palette. */
54
+ export interface CandidateSwatch {
55
+ hex: string;
56
+ proportion: number;
57
+ name: string | null;
58
+ }
59
+ /** Why a queried colour (or gradient sample) scored what it scored. */
60
+ export interface ColourMatchReceipt {
61
+ queryHex: string;
62
+ /** `colourScore × proportionScore` for the best-matching swatch. */
63
+ score: number;
64
+ /** OKLab closeness alone, in [0,1]. */
65
+ colourScore: number;
66
+ /** Proportion agreement alone, in [0,1]. */
67
+ proportionScore: number;
68
+ /** The swatch that produced the score, or null when nothing matched. */
69
+ matched: CandidateSwatch | null;
70
+ }
71
+ /** Identity and evidence shared by both multi-colour match shapes. */
72
+ export interface MultiColourMatchBase {
73
+ variantId: string;
74
+ brand: string;
75
+ product: string;
76
+ brandSlug: string | null;
77
+ productId: string | null;
78
+ color: string | null;
79
+ variantName: string | null;
80
+ imageUrl: string | null;
81
+ /** MB has retired or hidden this SKU. */
82
+ retired: boolean;
83
+ /** The candidate's full extracted palette — the evidence scored against. */
84
+ palette: CandidateSwatch[];
85
+ /** The aggregate match score in [0,1]; higher is closer. */
86
+ score: number;
87
+ }
88
+ export interface PaletteMatch extends MultiColourMatchBase {
89
+ /** One receipt per queried colour, in query order. */
90
+ colours: ColourMatchReceipt[];
91
+ }
92
+ export interface GradientMatch extends MultiColourMatchBase {
93
+ /**
94
+ * Distinct matched thirds of the path over 3. A material owning only one end
95
+ * of the transition caps out around 0.33 — coverage is what separates a real
96
+ * gradient match from a good match on a single stop.
97
+ */
98
+ coverage: number;
99
+ /** One receipt per sampled point along the path, in path order. */
100
+ samples: ColourMatchReceipt[];
101
+ }
102
+ export interface SearchPaletteResponse {
103
+ query: {
104
+ colours: Array<{
105
+ hex: string;
106
+ proportion?: number;
107
+ weight?: number;
108
+ }>;
109
+ logic: PaletteLogic;
110
+ };
111
+ count: number;
112
+ matches: PaletteMatch[];
113
+ }
114
+ export interface SearchGradientResponse {
115
+ query: {
116
+ stops: string[];
117
+ /** The sampled path points the search actually ran against. */
118
+ samples: string[];
119
+ };
120
+ count: number;
121
+ matches: GradientMatch[];
122
+ }
@@ -0,0 +1 @@
1
+ export {};