@instruments/materialgraph 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/by-colour-types.d.ts +20 -0
- package/client.d.ts +2 -1
- package/index.d.ts +2 -1
- package/index.js +1 -1
- package/match-stream-types.d.ts +117 -3
- package/match-stream-types.js +24 -2
- package/match-stream.d.ts +1 -1
- package/match-types.d.ts +101 -0
- package/match-types.js +1 -0
- package/package.json +1 -1
- package/run-events.d.ts +11 -0
- package/search-result-validation.js +5 -0
- package/types.d.ts +43 -51
package/by-colour-types.d.ts
CHANGED
|
@@ -67,6 +67,20 @@ export interface ColourFamilyGate {
|
|
|
67
67
|
*/
|
|
68
68
|
dropped: number;
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Receipt for the colour-identity collapse that turns a variant-grain ranking
|
|
72
|
+
* into a page of TILES — one per (brand, product, dominant hex), one per stored
|
|
73
|
+
* photograph. The live endpoint always sends it; it is optional here only so
|
|
74
|
+
* responses captured before the collapse existed still type-check.
|
|
75
|
+
*/
|
|
76
|
+
export interface ColourCollapse {
|
|
77
|
+
/** Ranked rows the collapse examined — this page's pool, never a corpus total. */
|
|
78
|
+
considered: number;
|
|
79
|
+
/** Rows dropped for repeating a better-ranked row's brand, product and hex. */
|
|
80
|
+
droppedByIdentity: number;
|
|
81
|
+
/** Rows dropped for resolving to a better-ranked row's stored photograph. */
|
|
82
|
+
droppedBySharedImage: number;
|
|
83
|
+
}
|
|
70
84
|
/** The `search/colours` response. */
|
|
71
85
|
export interface ByColourResponse {
|
|
72
86
|
query: {
|
|
@@ -85,6 +99,12 @@ export interface ByColourResponse {
|
|
|
85
99
|
* what says so in counts rather than adjectives.
|
|
86
100
|
*/
|
|
87
101
|
familyGate?: ColourFamilyGate;
|
|
102
|
+
/**
|
|
103
|
+
* What the colour-identity collapse cost this page. Matches are TILES: a page
|
|
104
|
+
* can be SHORTER than `limit` when the pool collapsed harder than the
|
|
105
|
+
* overfetch allowed, and this says so in counts rather than adjectives.
|
|
106
|
+
*/
|
|
107
|
+
collapse?: ColourCollapse;
|
|
88
108
|
}
|
|
89
109
|
/** Params for `search/colours`. */
|
|
90
110
|
export interface ByColourParams {
|
package/client.d.ts
CHANGED
|
@@ -18,7 +18,8 @@ import type { MaterialBlockProjection } from "./material-block-types.js";
|
|
|
18
18
|
import type { SchemaResource, SchemaResourceResponse } from "./schema-resource-types.js";
|
|
19
19
|
import type { BatchId, ProjectId, RunId, VariantId } from "./identifiers.js";
|
|
20
20
|
import type { ByColourParams, ByColourResponse } from "./by-colour-types.js";
|
|
21
|
-
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse,
|
|
21
|
+
import type { ApplicableSpecsResponse, BrandOptions, BrandProfileResponse, BySpecOptions, BySpecResponse, ColourFacetsResponse, CoordinateInput, CoordinateResponse, DemandBatchInput, DemandBatchResponse, DemandParams, DemandResponse, ExtractPaletteInput, ExtractPaletteResponse, GlossaryTermResponse, MaterialResponse, MaterialsBatchDetailResponse, MaterialsBatchGetOptions, MaterialsBatchOptions, MaterialsBatchTileResponse, PairingsOptions, PairingsResponse, ResolveAttributeOptions, ResolveAttributeResponse, ResolveAttributeValueOptions, ResolveAttributeValueResponse, ResolveColourResponse, SearchGetParams, SearchInput, SearchResponse, SiblingsResponse, SpecFacetsDistributionResponse, SpecFacetsListResponse, SuggestColourResponse, SuggestOptions, SuggestResponse, TaxonomyResponse, VocabularySearchOptions, WhoamiResponse } from "./types.js";
|
|
22
|
+
import type { MatchInput, MatchResponse } from "./match-types.js";
|
|
22
23
|
/**
|
|
23
24
|
* The MaterialGraph client — every lens on the graph, on one object.
|
|
24
25
|
*
|
package/index.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ export type * from "./run-events.js";
|
|
|
15
15
|
export { MaterialGraphError } from "./errors.js";
|
|
16
16
|
export type { MaterialGraphErrorInit, MaterialGraphErrorKind, MaterialGraphRequestMetadata, ProblemDetail, RateLimit, SanitizedErrorValue, SanitizedIssue, SimpleErrorBody, } from "./errors.js";
|
|
17
17
|
export type * from "./types.js";
|
|
18
|
+
export type * from "./match-types.js";
|
|
18
19
|
export type * from "./by-colour-types.js";
|
|
19
|
-
export { MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
20
|
+
export { MATCH_IMAGE_TYPES, MATCH_OUTCOMES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
20
21
|
export type * from "./match-stream-types.js";
|
|
21
22
|
export { CodesClient } from "./codes-client.js";
|
|
22
23
|
export type * from "./codes-types.js";
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { VARY_AXES } from "./composition-types.js";
|
|
|
5
5
|
export { PROJECT_EVENT_KINDS } from "./project-types.js";
|
|
6
6
|
export { COMPOSITION_PHASE_EVENT_TYPES, COMPOSITION_PROGRAMME_EVENT_TYPES, COMPOSITION_REVIEW_CRITERIA_EVENT_TYPES, parseRunEvent, } from "./run-events.js";
|
|
7
7
|
export { MaterialGraphError } from "./errors.js";
|
|
8
|
-
export { MATCH_IMAGE_TYPES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
8
|
+
export { MATCH_IMAGE_TYPES, MATCH_OUTCOMES, MATCH_MATERIAL_FUNCTIONS, MATCH_STREAM_EVENT_TYPES, MATCH_SURFACE_STATUSES, MATCH_WORKFLOW_STEPS, } from "./match-stream-types.js";
|
|
9
9
|
export { CodesClient } from "./codes-client.js";
|
|
10
10
|
export { ColoursClient } from "./colours-client.js";
|
|
11
11
|
export { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID } from "./spec-types.js";
|
package/match-stream-types.d.ts
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* The value lists are exported so a consumer can build an exhaustive switch or a
|
|
16
16
|
* UI legend from the same source the validator uses.
|
|
17
17
|
*/
|
|
18
|
-
import type {
|
|
18
|
+
import type { SearchResultItem } from "./types.js";
|
|
19
|
+
import type { MatchCategoryInference, MatchColourReading } from "./match-types.js";
|
|
19
20
|
/** How the workflow classified the uploaded image. */
|
|
20
21
|
export declare const MATCH_IMAGE_TYPES: readonly ["swatch", "installation", "flatplan"];
|
|
21
22
|
export type MatchImageType = (typeof MATCH_IMAGE_TYPES)[number];
|
|
@@ -25,8 +26,16 @@ export type MatchMaterialFunction = (typeof MATCH_MATERIAL_FUNCTIONS)[number];
|
|
|
25
26
|
/** The workflow's coarse progress phase, narrated by a `step` event. */
|
|
26
27
|
export declare const MATCH_WORKFLOW_STEPS: readonly ["classifying", "decomposing", "matching", "complete"];
|
|
27
28
|
export type MatchWorkflowStep = (typeof MATCH_WORKFLOW_STEPS)[number];
|
|
28
|
-
/**
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Where one surface stands in the run.
|
|
31
|
+
*
|
|
32
|
+
* `colour_fallback` (appended, additive): the surface completed, but through
|
|
33
|
+
* the colour lane — its visible sample was too small to read texture, so the
|
|
34
|
+
* results match the region's palette, scoped to the detected category. Such a
|
|
35
|
+
* surface carries a `note` saying so and no `senseCheck` verdicts (there is no
|
|
36
|
+
* texture to verify).
|
|
37
|
+
*/
|
|
38
|
+
export declare const MATCH_SURFACE_STATUSES: readonly ["pending", "matching", "complete", "error", "colour_fallback"];
|
|
30
39
|
export type MatchSurfaceStatus = (typeof MATCH_SURFACE_STATUSES)[number];
|
|
31
40
|
/** Every frame type the stream can carry — the union's discriminant values. */
|
|
32
41
|
export declare const MATCH_STREAM_EVENT_TYPES: readonly ["step", "classified", "surfaces", "surface", "heartbeat", "complete", "error"];
|
|
@@ -57,21 +66,106 @@ export interface MatchDetectedSurface {
|
|
|
57
66
|
* arrives repeatedly as the run progresses (`matching` → `complete` / `error`);
|
|
58
67
|
* render by replacing on id rather than appending.
|
|
59
68
|
*
|
|
69
|
+
* A `complete` surface can arrive TWICE: the first frame delivers its finished
|
|
70
|
+
* results as soon as they exist, and a second repeats them unchanged with the
|
|
71
|
+
* verifier's per-candidate verdicts attached (`senseCheck`).
|
|
72
|
+
* Replacing on id handles both; the results never change between them, so
|
|
73
|
+
* nothing a user is looking at moves when the second lands.
|
|
74
|
+
*
|
|
60
75
|
* `results` and `inferredCategories` are always present on a yielded event: the
|
|
61
76
|
* canonical schema defaults each to `[]`, and the reader fills them when the
|
|
62
77
|
* server omits them, so neither ever arrives as `undefined`.
|
|
63
78
|
*/
|
|
79
|
+
/**
|
|
80
|
+
* What a Match actually compared, so a consumer can tell "the catalogue holds
|
|
81
|
+
* nothing close" from "everything ranked badly" (MG-1031). Without it both
|
|
82
|
+
* arrive as an empty list and the difference is unrecoverable.
|
|
83
|
+
*
|
|
84
|
+
* UNITS: `qualityFloor` and `bestRejected` are absolute image-arm cosines in
|
|
85
|
+
* [0,1] — the same unit as each result's `confidence` — so they belong on one
|
|
86
|
+
* axis. `poolSize` and `droppedBelowFloor` are counts and belong on neither.
|
|
87
|
+
*/
|
|
88
|
+
export interface MatchComparison {
|
|
89
|
+
/** Candidates retrieved before the quality floor and product dedup. */
|
|
90
|
+
poolSize: number;
|
|
91
|
+
/** How many of those the quality floor rejected. */
|
|
92
|
+
droppedBelowFloor: number;
|
|
93
|
+
/** The floor they were held to, as an absolute image cosine in [0,1]. */
|
|
94
|
+
qualityFloor: number;
|
|
95
|
+
/**
|
|
96
|
+
* The closest REJECTED candidate, same unit as `qualityFloor`. Absent when
|
|
97
|
+
* nothing was rejected, or when the rejects carried no readable signal.
|
|
98
|
+
*/
|
|
99
|
+
bestRejected?: number;
|
|
100
|
+
}
|
|
64
101
|
export interface MatchSurfaceResult {
|
|
65
102
|
surface: MatchDetectedSurface;
|
|
66
103
|
status: MatchSurfaceStatus;
|
|
67
104
|
results: SearchResultItem[];
|
|
68
105
|
inferredCategories: MatchCategoryInference[];
|
|
106
|
+
/** What this surface compared against. Absent means the server did not report it. */
|
|
107
|
+
comparison?: MatchComparison;
|
|
108
|
+
/**
|
|
109
|
+
* The verifier's reading of each candidate, when it ran.
|
|
110
|
+
*
|
|
111
|
+
* ABSENT means the stage did not run — it is opt-out, and the swatch route
|
|
112
|
+
* never runs it — OR that it is still running, which `senseCheckPending`
|
|
113
|
+
* distinguishes. Neither is the same as every candidate abstaining, and a
|
|
114
|
+
* consumer must not conflate them.
|
|
115
|
+
*
|
|
116
|
+
* `abstained` is NOT a soft rejection: it means the candidate was not judged
|
|
117
|
+
* (no image, no answer, or the judge was unavailable). Never render an
|
|
118
|
+
* abstention as a negative signal about the material.
|
|
119
|
+
*/
|
|
120
|
+
senseCheck?: MatchSenseCheckCell[];
|
|
121
|
+
/**
|
|
122
|
+
* TRUE on the interim frame that delivers finished results while the verifier
|
|
123
|
+
* is still reading them. Render the rows immediately. The follow-up frame
|
|
124
|
+
* carries the SAME rows with verdicts attached, settled once into verdict
|
|
125
|
+
* bands (confirmed → abstained → rejected, stable within each band) — the
|
|
126
|
+
* page's one settling reorder. Nothing is ever removed.
|
|
127
|
+
*/
|
|
128
|
+
senseCheckPending?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Per-candidate colour readings against this surface's own crop, when the
|
|
131
|
+
* colour re-rank ran. `results` are already ordered by the blended score when
|
|
132
|
+
* this is present; this array is the receipt for that order.
|
|
133
|
+
*/
|
|
134
|
+
colour?: MatchColourReading[];
|
|
135
|
+
/** The segment's own extracted palette — what the colour stage matched against. */
|
|
136
|
+
segmentPalette?: Array<{
|
|
137
|
+
hex: string;
|
|
138
|
+
proportion: number;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* One sentence of provenance a consumer may render verbatim, when the server
|
|
142
|
+
* sent one — e.g. the `colour_fallback` explanation carrying the refused
|
|
143
|
+
* sample's pixel size. Absent on ordinary surfaces.
|
|
144
|
+
*/
|
|
145
|
+
note?: string;
|
|
69
146
|
/** Present only on `status: "error"` — why this one surface failed. */
|
|
70
147
|
error?: string;
|
|
71
148
|
}
|
|
149
|
+
/** The verifier's reading of one candidate, keyed back to its variant. */
|
|
150
|
+
export interface MatchSenseCheckCell {
|
|
151
|
+
variantId: string;
|
|
152
|
+
verdict: MatchSenseCheckVerdict;
|
|
153
|
+
reason: string;
|
|
154
|
+
}
|
|
155
|
+
export declare const MATCH_SENSE_CHECK_VERDICTS: readonly ["confirmed", "rejected", "abstained"];
|
|
156
|
+
export type MatchSenseCheckVerdict = (typeof MATCH_SENSE_CHECK_VERDICTS)[number];
|
|
72
157
|
/** The terminal Match workflow result — every surface in its final state. */
|
|
158
|
+
/**
|
|
159
|
+
* How a Match run ended. `no_surfaces` means the detector worked and found no
|
|
160
|
+
* material in the photo — a finding, not a failure. Branch on this rather than
|
|
161
|
+
* on an error message. Absent means `matched`.
|
|
162
|
+
*/
|
|
163
|
+
export declare const MATCH_OUTCOMES: readonly ["matched", "no_surfaces"];
|
|
164
|
+
export type MatchOutcome = (typeof MATCH_OUTCOMES)[number];
|
|
73
165
|
export interface MatchWorkflowResult {
|
|
74
166
|
imageType: MatchImageType;
|
|
167
|
+
/** How the run ended. Absent means `matched`. */
|
|
168
|
+
outcome?: MatchOutcome;
|
|
75
169
|
surfaces: MatchSurfaceResult[];
|
|
76
170
|
/** Present for swatch / single-surface runs that skip decompose. */
|
|
77
171
|
results?: SearchResultItem[];
|
|
@@ -82,6 +176,26 @@ export interface MatchWorkflowResult {
|
|
|
82
176
|
* "nothing above the floor". Do not fold `surfaces[]` to tell those apart.
|
|
83
177
|
*/
|
|
84
178
|
totals: MatchRunTotals;
|
|
179
|
+
/**
|
|
180
|
+
* Whole-image palette matches, sent ONLY when an installation photo yielded
|
|
181
|
+
* no usable surfaces at all. These match the image's overall palette — they
|
|
182
|
+
* are not material identifications, and the note says so. A consumer that
|
|
183
|
+
* ignores this field sees exactly the empty run it saw before the field
|
|
184
|
+
* existed.
|
|
185
|
+
*/
|
|
186
|
+
paletteMatches?: {
|
|
187
|
+
results: SearchResultItem[];
|
|
188
|
+
palette: Array<{
|
|
189
|
+
hex: string;
|
|
190
|
+
proportion: number;
|
|
191
|
+
}>;
|
|
192
|
+
note: string;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* What a SWATCH run compared against — a swatch detects no surfaces, so it has
|
|
196
|
+
* nowhere else to put this. A scene run reports per surface instead.
|
|
197
|
+
*/
|
|
198
|
+
comparison?: MatchComparison;
|
|
85
199
|
}
|
|
86
200
|
/** Progress narration for the phase the workflow just entered. */
|
|
87
201
|
export interface MatchStreamStepEvent {
|
package/match-stream-types.js
CHANGED
|
@@ -14,8 +14,22 @@ export const MATCH_MATERIAL_FUNCTIONS = [
|
|
|
14
14
|
];
|
|
15
15
|
/** The workflow's coarse progress phase, narrated by a `step` event. */
|
|
16
16
|
export const MATCH_WORKFLOW_STEPS = ["classifying", "decomposing", "matching", "complete"];
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Where one surface stands in the run.
|
|
19
|
+
*
|
|
20
|
+
* `colour_fallback` (appended, additive): the surface completed, but through
|
|
21
|
+
* the colour lane — its visible sample was too small to read texture, so the
|
|
22
|
+
* results match the region's palette, scoped to the detected category. Such a
|
|
23
|
+
* surface carries a `note` saying so and no `senseCheck` verdicts (there is no
|
|
24
|
+
* texture to verify).
|
|
25
|
+
*/
|
|
26
|
+
export const MATCH_SURFACE_STATUSES = [
|
|
27
|
+
"pending",
|
|
28
|
+
"matching",
|
|
29
|
+
"complete",
|
|
30
|
+
"error",
|
|
31
|
+
"colour_fallback",
|
|
32
|
+
];
|
|
19
33
|
/** Every frame type the stream can carry — the union's discriminant values. */
|
|
20
34
|
export const MATCH_STREAM_EVENT_TYPES = [
|
|
21
35
|
"step",
|
|
@@ -26,3 +40,11 @@ export const MATCH_STREAM_EVENT_TYPES = [
|
|
|
26
40
|
"complete",
|
|
27
41
|
"error",
|
|
28
42
|
];
|
|
43
|
+
export const MATCH_SENSE_CHECK_VERDICTS = ["confirmed", "rejected", "abstained"];
|
|
44
|
+
/** The terminal Match workflow result — every surface in its final state. */
|
|
45
|
+
/**
|
|
46
|
+
* How a Match run ended. `no_surfaces` means the detector worked and found no
|
|
47
|
+
* material in the photo — a finding, not a failure. Branch on this rather than
|
|
48
|
+
* on an error message. Absent means `matched`.
|
|
49
|
+
*/
|
|
50
|
+
export const MATCH_OUTCOMES = ["matched", "no_surfaces"];
|
package/match-stream.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HttpTransport } from "./http-transport.js";
|
|
2
2
|
import { type MatchStreamEvent } from "./match-stream-types.js";
|
|
3
|
-
import type { MatchInput } from "./types.js";
|
|
3
|
+
import type { MatchInput } from "./match-types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Decode one NDJSON line into a validated {@link MatchStreamEvent}, raising a
|
|
6
6
|
* {@link MaterialGraphError} on anything the contract does not allow. Exported
|
package/match-types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/match-types
|
|
3
|
+
* @description The buffered Material Match request and response, hand-mirrored
|
|
4
|
+
* from the server's Zod contracts.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `types.ts` — which is at the repository's source-size limit —
|
|
7
|
+
* along its natural seam: everything a caller of `client.match()` needs, and
|
|
8
|
+
* nothing else. The STREAMING contracts live in `match-stream-types.ts`.
|
|
9
|
+
*/
|
|
10
|
+
import type { SearchFilter } from "./search-refinement-types.js";
|
|
11
|
+
import type { OpenEnum, PaletteSwatch, SearchResultItem } from "./types.js";
|
|
12
|
+
/** An inferred product-type category from the query image, with its boost. */
|
|
13
|
+
export interface MatchCategoryInference {
|
|
14
|
+
productTypeId: string;
|
|
15
|
+
displayName: string;
|
|
16
|
+
boost: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The Material Match response (non-streaming form).
|
|
20
|
+
*
|
|
21
|
+
* `poolSize` is the candidate pool the floor was applied to, NOT a corpus total
|
|
22
|
+
* and NOT "how many materials look like this". An empty `results` with
|
|
23
|
+
* `droppedBelowFloor > 0` means "candidates existed and none was good enough" —
|
|
24
|
+
* a different answer from `poolSize: 0`, which means the image found nothing to
|
|
25
|
+
* search against at all.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* One candidate's colour reading against the query crop. `measurable: false`
|
|
29
|
+
* means the material carries no palette on record — UNKNOWN, never "no colour"
|
|
30
|
+
* and never "the wrong colour".
|
|
31
|
+
*/
|
|
32
|
+
export interface MatchColourReading {
|
|
33
|
+
variantId: string;
|
|
34
|
+
colourDistance: number | null;
|
|
35
|
+
colourBand: string | null;
|
|
36
|
+
measurable: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface MatchResponse {
|
|
39
|
+
/** THIS answer, capped by `limit`. Not a total. */
|
|
40
|
+
results: SearchResultItem[];
|
|
41
|
+
inferredCategories: MatchCategoryInference[];
|
|
42
|
+
qualityFloor: number;
|
|
43
|
+
poolSize: number;
|
|
44
|
+
droppedBelowFloor: number;
|
|
45
|
+
droppedAsDuplicateProduct: number;
|
|
46
|
+
/**
|
|
47
|
+
* The closest candidate the quality floor REJECTED, in the same [0,1] unit as
|
|
48
|
+
* `qualityFloor`. This is what turns "no matches" into "the closest thing in
|
|
49
|
+
* the catalogue read 0.48 against a 0.55 bar" — the one number that separates
|
|
50
|
+
* an engine that failed from a corpus that does not hold the material. Absent
|
|
51
|
+
* when nothing was rejected; null when the rejects carried no readable signal.
|
|
52
|
+
*/
|
|
53
|
+
bestRejected?: number | null;
|
|
54
|
+
/** The maximum score achievable under the weights this query ran — `score`'s denominator. */
|
|
55
|
+
maxScore: number;
|
|
56
|
+
/**
|
|
57
|
+
* Per-candidate colour readings against the query crop, in `results` order.
|
|
58
|
+
* `null` means the colour stage DID NOT RUN (no image bytes, or it was turned
|
|
59
|
+
* off) — never "no colour".
|
|
60
|
+
*/
|
|
61
|
+
colour?: MatchColourReading[] | null;
|
|
62
|
+
/** The query crop's own extracted palette — what the colour stage compared against. */
|
|
63
|
+
segmentPalette?: PaletteSwatch[] | null;
|
|
64
|
+
note?: string;
|
|
65
|
+
}
|
|
66
|
+
/** The Material Match request body — exactly one of `imageBase64`, `imageUrl`, `vector`. */
|
|
67
|
+
export interface MatchInput {
|
|
68
|
+
imageBase64?: string;
|
|
69
|
+
imageUrl?: string;
|
|
70
|
+
vector?: string;
|
|
71
|
+
limit?: number;
|
|
72
|
+
qualityFloor?: number;
|
|
73
|
+
categoryTopK?: number;
|
|
74
|
+
categoryMinBoost?: number;
|
|
75
|
+
detectionLabel?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The detector's fuller reading of the surface — a SENTENCE, where
|
|
78
|
+
* `detectionLabel` is a phrase. Fused WITH the image into ONE multimodal
|
|
79
|
+
* query vector and preferred over the label when both are present.
|
|
80
|
+
*
|
|
81
|
+
* Distinct from `detectionLabel`, which also buys the category scope: a
|
|
82
|
+
* request can narrow the scope by label and steer retrieval by description in
|
|
83
|
+
* the same call.
|
|
84
|
+
*/
|
|
85
|
+
detectionDescription?: string;
|
|
86
|
+
materialFunction?: string;
|
|
87
|
+
detectionCategory?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Opt-in: also return quality-floor rejects, flagged `belowFloor: true` and
|
|
90
|
+
* appended after the kept results. Absent/false = default behaviour;
|
|
91
|
+
* `droppedBelowFloor` is unchanged.
|
|
92
|
+
*/
|
|
93
|
+
includeBelowFloor?: boolean;
|
|
94
|
+
requestedType?: OpenEnum<"swatch" | "installation" | "flatplan">;
|
|
95
|
+
/** Hard spec gates forwarded verbatim to the inner search — term match or presence, each with an optional trust bar. */
|
|
96
|
+
filters?: SearchFilter[];
|
|
97
|
+
/** Soft criteria (rank-never-exclude). A miss demotes within the page but never removes a hit. */
|
|
98
|
+
preferences?: SearchFilter[];
|
|
99
|
+
/** Opt in to seed-relative verdict cells; inert unless the inner search carries a variant seed. */
|
|
100
|
+
verdicts?: boolean;
|
|
101
|
+
}
|
package/match-types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
package/run-events.d.ts
CHANGED
|
@@ -50,7 +50,18 @@ export interface CompositionReviewCriteriaSuggestion {
|
|
|
50
50
|
export interface CompositionReviewCriteriaUnresolved {
|
|
51
51
|
slotId: string;
|
|
52
52
|
raw: string;
|
|
53
|
+
/**
|
|
54
|
+
* Ranked corrections, best first. MG-994: every one of these is an attribute
|
|
55
|
+
* the slot's own category holds accepted values for, so accepting the top
|
|
56
|
+
* suggestion can never leave the slot unfillable.
|
|
57
|
+
*/
|
|
53
58
|
suggestions: CompositionReviewCriteriaSuggestion[];
|
|
59
|
+
/**
|
|
60
|
+
* MG-994: attribute ids ranked for this phrase and then withheld — the slot's
|
|
61
|
+
* category holds no accepted value for them. ABSENT when nothing was withheld
|
|
62
|
+
* (absent means nothing was removed, not that nothing could be).
|
|
63
|
+
*/
|
|
64
|
+
withheld?: string[];
|
|
54
65
|
}
|
|
55
66
|
/**
|
|
56
67
|
* Payload of a `composition.review-criteria` / `material-box.review-criteria`
|
|
@@ -146,6 +146,11 @@ export function checkSearchResultItem(value, what, fail) {
|
|
|
146
146
|
// `z.string().optional()` — absent, or a string. NOT nullable: the emit path
|
|
147
147
|
// omits the key rather than sending null when no MB identity is known.
|
|
148
148
|
requireOptionalString(record.materialBankId, `${what}.materialBankId`, fail);
|
|
149
|
+
// Same shape as `materialBankId`: absent, or a string. The emit path omits
|
|
150
|
+
// the key when no primary product type is on record rather than sending null,
|
|
151
|
+
// so "we don't know what this is" and "this is a null kind of thing" cannot
|
|
152
|
+
// be confused.
|
|
153
|
+
requireOptionalString(record.productType, `${what}.productType`, fail);
|
|
149
154
|
requireNullableString(record.color, `${what}.color`, fail);
|
|
150
155
|
requireNullableString(record.variantName, `${what}.variantName`, fail);
|
|
151
156
|
requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);
|
package/types.d.ts
CHANGED
|
@@ -130,6 +130,20 @@ export interface SearchResultItem {
|
|
|
130
130
|
* cannot be ordered. Presence is the orderability tell; absence is silence.
|
|
131
131
|
*/
|
|
132
132
|
materialBankId?: string;
|
|
133
|
+
/**
|
|
134
|
+
* What kind of material this is — the variant's primary product type, as a
|
|
135
|
+
* display name (`"Wall Tile"`, `"Modular Carpet"`, `"Interior Paint"`).
|
|
136
|
+
*
|
|
137
|
+
* This is the field that lets you tell a wall tile from a bar stool without
|
|
138
|
+
* parsing `product`. Before it existed, callers grading Match results had to
|
|
139
|
+
* regex the product's NAME, and one such grader scored pendant lights and
|
|
140
|
+
* electrical sockets as correct answers for a wall — because they are neither
|
|
141
|
+
* a stool nor a chair.
|
|
142
|
+
*
|
|
143
|
+
* ABSENT means no primary type is on record for the variant. Unknown, never
|
|
144
|
+
* "uncategorised" and never "not a material".
|
|
145
|
+
*/
|
|
146
|
+
productType?: string;
|
|
133
147
|
color: string | null;
|
|
134
148
|
variantName: string | null;
|
|
135
149
|
imageUrl: string | null;
|
|
@@ -138,6 +152,35 @@ export interface SearchResultItem {
|
|
|
138
152
|
colourMeta?: ColourMeta;
|
|
139
153
|
/** Fused score — range [0, sum of active weights]; see `maxScore`. */
|
|
140
154
|
score: number;
|
|
155
|
+
/**
|
|
156
|
+
* Match only. How close this material is to the query, as an ABSOLUTE
|
|
157
|
+
* image-space similarity in [0,1] adjusted for colour — the same unit as
|
|
158
|
+
* `qualityFloor` and `comparison.bestRejected`, so all three sit on one axis.
|
|
159
|
+
*
|
|
160
|
+
* Never normalised against the page, so the same material reads the same in
|
|
161
|
+
* two different searches. It is the TRUE value and MAY reach 1 — rendering it
|
|
162
|
+
* so that no card claims "100%" is yours to do (round toward zero, or show
|
|
163
|
+
* "99%+"); `Math.round` prints 100% from 0.995 up. Capping it server-side
|
|
164
|
+
* would put it on a different ceiling from `comparison.bestRejected`, which
|
|
165
|
+
* publishes the same unit uncapped, and the two are meant to be comparable.
|
|
166
|
+
* `score` is the ranking value and lives on a different scale.
|
|
167
|
+
*/
|
|
168
|
+
confidence?: number;
|
|
169
|
+
/**
|
|
170
|
+
* `confidence` as a word — `strong` >= 0.85, `good` >= 0.72, `fair` >= 0.55
|
|
171
|
+
* (the default quality floor), `weak` below it and only reachable under
|
|
172
|
+
* `includeBelowFloor`. Present exactly when `confidence` is.
|
|
173
|
+
*/
|
|
174
|
+
confidenceBand?: "strong" | "good" | "fair" | "weak";
|
|
175
|
+
/**
|
|
176
|
+
* Match only. Proportion-weighted OKLab distance from the query crop's palette
|
|
177
|
+
* to this material's. `null` means the material carries no stored palette —
|
|
178
|
+
* UNKNOWN colour, never "no colour" and never "distant". Absent when the
|
|
179
|
+
* colour stage did not run.
|
|
180
|
+
*/
|
|
181
|
+
colourDistance?: number | null;
|
|
182
|
+
/** `colourDistance` as a word, or null when unmeasurable. */
|
|
183
|
+
colourBand?: string | null;
|
|
141
184
|
signals?: SearchResultSignals;
|
|
142
185
|
retired?: boolean;
|
|
143
186
|
/**
|
|
@@ -244,31 +287,6 @@ export interface SearchCardResponse extends Omit<SearchResponse, "view" | "resul
|
|
|
244
287
|
view: OpenEnum<"card">;
|
|
245
288
|
results: SearchCardItem[];
|
|
246
289
|
}
|
|
247
|
-
/** An inferred product-type category from the query image, with its boost. */
|
|
248
|
-
export interface MatchCategoryInference {
|
|
249
|
-
productTypeId: string;
|
|
250
|
-
displayName: string;
|
|
251
|
-
boost: number;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* The Material Match response (non-streaming form).
|
|
255
|
-
*
|
|
256
|
-
* `poolSize` is the candidate pool the floor was applied to, NOT a corpus total
|
|
257
|
-
* and NOT "how many materials look like this". An empty `results` with
|
|
258
|
-
* `droppedBelowFloor > 0` means "candidates existed and none was good enough" —
|
|
259
|
-
* a different answer from `poolSize: 0`, which means the image found nothing to
|
|
260
|
-
* search against at all.
|
|
261
|
-
*/
|
|
262
|
-
export interface MatchResponse {
|
|
263
|
-
/** THIS answer, capped by `limit`. Not a total. */
|
|
264
|
-
results: SearchResultItem[];
|
|
265
|
-
inferredCategories: MatchCategoryInference[];
|
|
266
|
-
qualityFloor: number;
|
|
267
|
-
poolSize: number;
|
|
268
|
-
droppedBelowFloor: number;
|
|
269
|
-
droppedAsDuplicateProduct: number;
|
|
270
|
-
note?: string;
|
|
271
|
-
}
|
|
272
290
|
/** One item from the pure colour-scalar browse. */
|
|
273
291
|
export interface BrowseResultItem {
|
|
274
292
|
/** Opaque — round-trip verbatim. */
|
|
@@ -640,32 +658,6 @@ export interface BrowseParams {
|
|
|
640
658
|
*/
|
|
641
659
|
types?: string;
|
|
642
660
|
}
|
|
643
|
-
/** The Material Match request body — exactly one of `imageBase64`, `imageUrl`, `vector`. */
|
|
644
|
-
export interface MatchInput {
|
|
645
|
-
imageBase64?: string;
|
|
646
|
-
imageUrl?: string;
|
|
647
|
-
vector?: string;
|
|
648
|
-
limit?: number;
|
|
649
|
-
qualityFloor?: number;
|
|
650
|
-
categoryTopK?: number;
|
|
651
|
-
categoryMinBoost?: number;
|
|
652
|
-
detectionLabel?: string;
|
|
653
|
-
materialFunction?: string;
|
|
654
|
-
detectionCategory?: string;
|
|
655
|
-
/**
|
|
656
|
-
* Opt-in: also return quality-floor rejects, flagged `belowFloor: true` and
|
|
657
|
-
* appended after the kept results. Absent/false = default behaviour;
|
|
658
|
-
* `droppedBelowFloor` is unchanged.
|
|
659
|
-
*/
|
|
660
|
-
includeBelowFloor?: boolean;
|
|
661
|
-
requestedType?: OpenEnum<"swatch" | "installation" | "flatplan">;
|
|
662
|
-
/** Hard spec gates forwarded verbatim to the inner search — term match or presence, each with an optional trust bar. */
|
|
663
|
-
filters?: SearchFilter[];
|
|
664
|
-
/** Soft criteria (rank-never-exclude). A miss demotes within the page but never removes a hit. */
|
|
665
|
-
preferences?: SearchFilter[];
|
|
666
|
-
/** Opt in to seed-relative verdict cells; inert unless the inner search carries a variant seed. */
|
|
667
|
-
verdicts?: boolean;
|
|
668
|
-
}
|
|
669
661
|
/** Options for a batch material read. */
|
|
670
662
|
export interface MaterialsBatchOptions {
|
|
671
663
|
/** Projection depth. `tile` omits fields rather than reporting them absent. */
|