@instruments/materialgraph 0.1.0 → 0.3.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/by-spec-types.d.ts +73 -0
- package/by-spec-types.js +7 -0
- package/client.d.ts +186 -7
- package/client.js +242 -125
- package/codes-client.d.ts +82 -0
- package/codes-client.js +73 -0
- package/codes-types.d.ts +238 -0
- package/codes-types.js +1 -0
- package/colour-aggregate-types.d.ts +93 -0
- package/colour-aggregate-types.js +1 -0
- package/colours-client.d.ts +52 -0
- package/colours-client.js +39 -0
- package/composition-types.d.ts +343 -0
- package/composition-types.js +10 -0
- package/http-transport.d.ts +74 -0
- package/http-transport.js +201 -0
- package/index.d.ts +19 -0
- package/index.js +9 -0
- package/match-stream-types.d.ts +150 -0
- package/match-stream-types.js +28 -0
- package/match-stream.d.ts +22 -0
- package/match-stream.js +280 -0
- package/material-block-types.d.ts +23 -2
- package/material-types.d.ts +92 -0
- package/material-types.js +1 -0
- package/ndjson.d.ts +46 -0
- package/ndjson.js +103 -0
- package/operations.d.ts +6 -2
- package/operations.js +30 -2
- package/package.json +1 -1
- package/project-types.d.ts +67 -0
- package/project-types.js +16 -0
- package/projects.d.ts +13 -0
- package/projects.js +46 -0
- package/run-events.d.ts +82 -0
- package/run-events.js +29 -0
- package/run-types.d.ts +266 -0
- package/run-types.js +6 -0
- package/runs.d.ts +84 -0
- package/runs.js +253 -0
- package/schedule-types.d.ts +88 -0
- package/schedule-types.js +12 -0
- package/search-refinement-types.d.ts +26 -1
- package/search-result-validation.d.ts +27 -0
- package/search-result-validation.js +174 -0
- package/source-material-validation.d.ts +3 -0
- package/source-material-validation.js +109 -0
- package/spec-types.d.ts +82 -0
- package/spec-types.js +44 -0
- package/specifications.d.ts +72 -0
- package/specifications.js +82 -0
- package/sse.d.ts +35 -0
- package/sse.js +147 -0
- package/types.d.ts +204 -77
- package/validation.d.ts +119 -0
- package/validation.js +241 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency builders for the specification surface. Turn a plain-language
|
|
3
|
+
* spec intent into a typed criterion the SDK's two spec operations accept:
|
|
4
|
+
*
|
|
5
|
+
* - `search({ filters })` takes {@link SpecFilter} objects — a spec gate layered
|
|
6
|
+
* onto a ranked (colour / visual / lexical) search. Build them with
|
|
7
|
+
* {@link specTerm}, {@link hasSpec}, and {@link certificationProgramme}.
|
|
8
|
+
* - `bySpec(specs)` takes `attribute:value` strings — the value-anchored,
|
|
9
|
+
* spec-only primitive. Build them with {@link specCriterion} and
|
|
10
|
+
* {@link certificationProgrammeCriterion}.
|
|
11
|
+
*
|
|
12
|
+
* The point of these helpers: Desk (and any spec-driven caller) never has to
|
|
13
|
+
* remember that a certification programme lives under a VIRTUAL attribute, nor
|
|
14
|
+
* hand-format the `attribute:value` string the `bySpec` route splits on its
|
|
15
|
+
* first `:`.
|
|
16
|
+
*/
|
|
17
|
+
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, } from "./spec-types.js";
|
|
18
|
+
/**
|
|
19
|
+
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
20
|
+
* carry this canonical value.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // "same fire class as the seed"
|
|
24
|
+
* client.search({ similarTo: { variantId }, filters: [specTerm("fire_classifications", "astm_e84_class_a")] });
|
|
25
|
+
*/
|
|
26
|
+
export function specTerm(attributeId, term) {
|
|
27
|
+
return { attributeId, term };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A presence criterion for `search({ filters })` — the attribute must have ANY
|
|
31
|
+
* accepted value.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // "…and it must have an EPD on record"
|
|
35
|
+
* client.search({ similarTo: { variantId }, filters: [hasSpec("environmental_product_declaration")] });
|
|
36
|
+
*/
|
|
37
|
+
export function hasSpec(attributeId) {
|
|
38
|
+
return { attributeId, hasValue: true };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A certification-programme criterion for `search({ filters })`, pinned to the
|
|
42
|
+
* virtual attribute so a programme matches wherever it landed at ingest
|
|
43
|
+
* (compliance / sustainability / material-health / VOC buckets).
|
|
44
|
+
*
|
|
45
|
+
* The value may be a display name or alias ("GREENGUARD Gold", "LEED") — the
|
|
46
|
+
* server resolves it to its canonical programme token before matching — or a
|
|
47
|
+
* canonical token directly (e.g. `greenguard_certificate`,
|
|
48
|
+
* `has_greenguard_gold_certification`).
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // "find a GREENGUARD-certified alternative"
|
|
52
|
+
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("GREENGUARD Gold")] });
|
|
53
|
+
*/
|
|
54
|
+
export function certificationProgramme(programme) {
|
|
55
|
+
return { attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, term: programme };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
59
|
+
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
60
|
+
* not.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
64
|
+
*/
|
|
65
|
+
export function specCriterion(attributeId, value) {
|
|
66
|
+
return `${attributeId}:${value}`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
70
|
+
* certification PROGRAMME across all four certification buckets.
|
|
71
|
+
*
|
|
72
|
+
* The value may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
73
|
+
* server-side to its canonical programme token, or a canonical token directly
|
|
74
|
+
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // every brand's GREENGUARD-Gold materials
|
|
78
|
+
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
79
|
+
*/
|
|
80
|
+
export function certificationProgrammeCriterion(programme) {
|
|
81
|
+
return specCriterion(CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, programme);
|
|
82
|
+
}
|
package/sse.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, dependency-free Server-Sent Events reader — the primitive behind
|
|
3
|
+
* {@link MaterialGraphClient.streamRunEvents}. Hand-rolled (no `eventsource`,
|
|
4
|
+
* no `undici`) so the SDK stays zero-runtime-dep and works unchanged inside
|
|
5
|
+
* Node route handlers, where `fetch` yields a byte `ReadableStream`.
|
|
6
|
+
*
|
|
7
|
+
* The parser follows the WHATWG event-stream tokenizer closely enough for the
|
|
8
|
+
* `/api/v1/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
|
|
9
|
+
* multi-line `data:` accumulation, comment (`:`-prefixed) lines, and a single
|
|
10
|
+
* optional leading space after the field colon. Frames split across network
|
|
11
|
+
* chunks are held in an internal buffer until their terminating blank line.
|
|
12
|
+
*/
|
|
13
|
+
/** One dispatched SSE frame. `data` is the joined data lines (no trailing newline). */
|
|
14
|
+
export interface SseFrame {
|
|
15
|
+
id?: string;
|
|
16
|
+
event?: string;
|
|
17
|
+
data: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A push-based SSE line assembler. Feed it decoded text with {@link push}; it
|
|
21
|
+
* returns any frames completed by that text. Call {@link flush} once the stream
|
|
22
|
+
* ends to dispatch a final frame that had data but no terminating blank line.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SseDecoder {
|
|
25
|
+
#private;
|
|
26
|
+
push(text: string): SseFrame[];
|
|
27
|
+
/** Dispatch a trailing frame that had fields but no closing blank line. */
|
|
28
|
+
flush(): SseFrame[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Iterate the SSE frames of a byte stream. Stops cleanly when the stream ends
|
|
32
|
+
* or `signal` aborts (mid-read abort rejections are swallowed once the signal
|
|
33
|
+
* is set). Frames with no `data` field (comments, bare `id:`) are not yielded.
|
|
34
|
+
*/
|
|
35
|
+
export declare function readSseFrames(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<SseFrame, void, unknown>;
|
package/sse.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal, dependency-free Server-Sent Events reader — the primitive behind
|
|
3
|
+
* {@link MaterialGraphClient.streamRunEvents}. Hand-rolled (no `eventsource`,
|
|
4
|
+
* no `undici`) so the SDK stays zero-runtime-dep and works unchanged inside
|
|
5
|
+
* Node route handlers, where `fetch` yields a byte `ReadableStream`.
|
|
6
|
+
*
|
|
7
|
+
* The parser follows the WHATWG event-stream tokenizer closely enough for the
|
|
8
|
+
* `/api/v1/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
|
|
9
|
+
* multi-line `data:` accumulation, comment (`:`-prefixed) lines, and a single
|
|
10
|
+
* optional leading space after the field colon. Frames split across network
|
|
11
|
+
* chunks are held in an internal buffer until their terminating blank line.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A push-based SSE line assembler. Feed it decoded text with {@link push}; it
|
|
15
|
+
* returns any frames completed by that text. Call {@link flush} once the stream
|
|
16
|
+
* ends to dispatch a final frame that had data but no terminating blank line.
|
|
17
|
+
*/
|
|
18
|
+
export class SseDecoder {
|
|
19
|
+
#buffer = "";
|
|
20
|
+
#dataLines = [];
|
|
21
|
+
#id;
|
|
22
|
+
#event;
|
|
23
|
+
#hasFields = false;
|
|
24
|
+
push(text) {
|
|
25
|
+
this.#buffer += text;
|
|
26
|
+
const frames = [];
|
|
27
|
+
for (;;) {
|
|
28
|
+
const lineEnd = this.#nextLineEnd();
|
|
29
|
+
if (lineEnd === null)
|
|
30
|
+
break;
|
|
31
|
+
const line = this.#buffer.slice(0, lineEnd.index);
|
|
32
|
+
this.#buffer = this.#buffer.slice(lineEnd.nextIndex);
|
|
33
|
+
const frame = this.#consumeLine(line);
|
|
34
|
+
if (frame)
|
|
35
|
+
frames.push(frame);
|
|
36
|
+
}
|
|
37
|
+
return frames;
|
|
38
|
+
}
|
|
39
|
+
/** Dispatch a trailing frame that had fields but no closing blank line. */
|
|
40
|
+
flush() {
|
|
41
|
+
if (this.#buffer.length > 0) {
|
|
42
|
+
// At end of stream a deferred trailing CR is a real terminator, not data.
|
|
43
|
+
const line = this.#buffer.endsWith("\r") ? this.#buffer.slice(0, -1) : this.#buffer;
|
|
44
|
+
this.#buffer = "";
|
|
45
|
+
const frame = this.#consumeLine(line);
|
|
46
|
+
if (frame)
|
|
47
|
+
return [frame];
|
|
48
|
+
}
|
|
49
|
+
const frame = this.#dispatch();
|
|
50
|
+
return frame ? [frame] : [];
|
|
51
|
+
}
|
|
52
|
+
/** Locate the next line terminator, deferring a lone trailing `\r` (may be `\r\n`). */
|
|
53
|
+
#nextLineEnd() {
|
|
54
|
+
for (let i = 0; i < this.#buffer.length; i += 1) {
|
|
55
|
+
const char = this.#buffer[i];
|
|
56
|
+
if (char === "\n")
|
|
57
|
+
return { index: i, nextIndex: i + 1 };
|
|
58
|
+
if (char === "\r") {
|
|
59
|
+
if (i === this.#buffer.length - 1)
|
|
60
|
+
return null;
|
|
61
|
+
return this.#buffer[i + 1] === "\n"
|
|
62
|
+
? { index: i, nextIndex: i + 2 }
|
|
63
|
+
: { index: i, nextIndex: i + 1 };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
#consumeLine(line) {
|
|
69
|
+
if (line.length === 0)
|
|
70
|
+
return this.#dispatch();
|
|
71
|
+
if (line.startsWith(":"))
|
|
72
|
+
return undefined;
|
|
73
|
+
const colon = line.indexOf(":");
|
|
74
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
75
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
76
|
+
if (value.startsWith(" "))
|
|
77
|
+
value = value.slice(1);
|
|
78
|
+
if (field === "data") {
|
|
79
|
+
this.#dataLines.push(value);
|
|
80
|
+
this.#hasFields = true;
|
|
81
|
+
}
|
|
82
|
+
else if (field === "event") {
|
|
83
|
+
this.#event = value;
|
|
84
|
+
this.#hasFields = true;
|
|
85
|
+
}
|
|
86
|
+
else if (field === "id") {
|
|
87
|
+
this.#id = value;
|
|
88
|
+
this.#hasFields = true;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
#dispatch() {
|
|
93
|
+
if (!this.#hasFields)
|
|
94
|
+
return undefined;
|
|
95
|
+
const frame = { data: this.#dataLines.join("\n") };
|
|
96
|
+
if (this.#event !== undefined)
|
|
97
|
+
frame.event = this.#event;
|
|
98
|
+
if (this.#id !== undefined)
|
|
99
|
+
frame.id = this.#id;
|
|
100
|
+
this.#dataLines = [];
|
|
101
|
+
this.#event = undefined;
|
|
102
|
+
this.#id = undefined;
|
|
103
|
+
this.#hasFields = false;
|
|
104
|
+
return frame;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Iterate the SSE frames of a byte stream. Stops cleanly when the stream ends
|
|
109
|
+
* or `signal` aborts (mid-read abort rejections are swallowed once the signal
|
|
110
|
+
* is set). Frames with no `data` field (comments, bare `id:`) are not yielded.
|
|
111
|
+
*/
|
|
112
|
+
export async function* readSseFrames(body, signal) {
|
|
113
|
+
const reader = body.getReader();
|
|
114
|
+
const decoder = new TextDecoder();
|
|
115
|
+
const sse = new SseDecoder();
|
|
116
|
+
try {
|
|
117
|
+
for (;;) {
|
|
118
|
+
if (signal?.aborted)
|
|
119
|
+
return;
|
|
120
|
+
let result;
|
|
121
|
+
try {
|
|
122
|
+
result = await reader.read();
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (signal?.aborted)
|
|
126
|
+
return;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
if (result.done) {
|
|
130
|
+
for (const frame of sse.push(decoder.decode()))
|
|
131
|
+
yield frame;
|
|
132
|
+
for (const frame of sse.flush())
|
|
133
|
+
yield frame;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const text = decoder.decode(result.value, { stream: true });
|
|
137
|
+
for (const frame of sse.push(text)) {
|
|
138
|
+
if (signal?.aborted)
|
|
139
|
+
return;
|
|
140
|
+
yield frame;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
reader.releaseLock();
|
|
146
|
+
}
|
|
147
|
+
}
|
package/types.d.ts
CHANGED
|
@@ -12,9 +12,19 @@
|
|
|
12
12
|
* server value still type-checks, and objects that may grow fields are read
|
|
13
13
|
* structurally (unknown keys pass through untouched — the client never strips).
|
|
14
14
|
*/
|
|
15
|
+
import type { BySpecVerdict } from "./by-spec-types.js";
|
|
16
|
+
import type { MaterialDetail, MaterialTile } from "./material-types.js";
|
|
15
17
|
import type { SearchFilter, SearchSuggestion } from "./search-refinement-types.js";
|
|
16
18
|
/** A widened string-literal union: known members autocomplete, unknown values still type-check. */
|
|
17
19
|
export type OpenEnum<T extends string> = T | (string & {});
|
|
20
|
+
/**
|
|
21
|
+
* A JSON value, recursively — exactly what survives a `jsonb` round-trip
|
|
22
|
+
* unchanged. Used where a payload's SHAPE belongs to its producer but its values
|
|
23
|
+
* must still be JSON: `Date`, `bigint`, `undefined`, and functions are not.
|
|
24
|
+
*/
|
|
25
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
26
|
+
[key: string]: JsonValue;
|
|
27
|
+
};
|
|
18
28
|
/** One palette swatch of a material's main image. */
|
|
19
29
|
export interface PaletteSwatch {
|
|
20
30
|
hex: string;
|
|
@@ -48,60 +58,15 @@ export interface MaterialColour {
|
|
|
48
58
|
}
|
|
49
59
|
/** Where the served image came from. */
|
|
50
60
|
export type ImageSource = OpenEnum<"r2" | "origin">;
|
|
51
|
-
|
|
52
|
-
export interface MaterialTile {
|
|
53
|
-
variantId: string;
|
|
54
|
-
brand: string;
|
|
55
|
-
product: string;
|
|
56
|
-
variantName: string | null;
|
|
57
|
-
imageUrl: string | null;
|
|
58
|
-
imageSource: ImageSource | null;
|
|
59
|
-
category: string | null;
|
|
60
|
-
colour: MaterialColour;
|
|
61
|
-
}
|
|
62
|
-
/** A canonical attribute/term pair carried on a detail record. */
|
|
63
|
-
export interface MaterialFacet {
|
|
64
|
-
attributeId: string;
|
|
65
|
-
term: string;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* One dimensional configuration of a material. Numeric fields are millimetres
|
|
69
|
-
* (mass in kilograms); string fields are the source's own descriptors. The
|
|
70
|
-
* index signature preserves the corpus's long tail of rarer keys.
|
|
71
|
-
*/
|
|
72
|
-
export interface DimensionEntry {
|
|
73
|
-
label?: string;
|
|
74
|
-
tile_size?: string;
|
|
75
|
-
measurement_basis?: string;
|
|
76
|
-
width_mm?: number;
|
|
77
|
-
length_mm?: number;
|
|
78
|
-
height_mm?: number;
|
|
79
|
-
depth_mm?: number;
|
|
80
|
-
thickness_mm?: number;
|
|
81
|
-
diameter_mm?: number;
|
|
82
|
-
visible_projection_mm?: number;
|
|
83
|
-
weight_kg?: number;
|
|
84
|
-
[key: string]: unknown;
|
|
85
|
-
}
|
|
86
|
-
/** The record projection of a material — the tile plus detail-only fields. */
|
|
87
|
-
export interface MaterialDetail extends MaterialTile {
|
|
88
|
-
brandSlug: string;
|
|
89
|
-
productId: string;
|
|
90
|
-
color: string | null;
|
|
91
|
-
materialBankId: string | null;
|
|
92
|
-
installationImageUrl: string | null;
|
|
93
|
-
displayCategory: string | null;
|
|
94
|
-
categoryPath: string[];
|
|
95
|
-
palette: PaletteSwatch[];
|
|
96
|
-
facets: MaterialFacet[];
|
|
97
|
-
dimensions: DimensionEntry[] | null;
|
|
98
|
-
}
|
|
61
|
+
export type { DimensionEntry, MaterialAvailability, MaterialDetail, MaterialFacet, MaterialSuccessor, MaterialTile, } from "./material-types.js";
|
|
99
62
|
/** Per-signal scores, present on a result when `explain` was requested. */
|
|
100
63
|
export interface SearchResultSignals {
|
|
101
64
|
image: number | null;
|
|
102
65
|
category: number | null;
|
|
103
66
|
colour: number | null;
|
|
104
67
|
lexical: number | null;
|
|
68
|
+
/** Text→image RERANK cosine — non-null only for a seeded search carrying `text`. */
|
|
69
|
+
text: number | null;
|
|
105
70
|
}
|
|
106
71
|
/** An accepted/measured visual meaning — descriptive evidence, never a ranking cause. */
|
|
107
72
|
export interface VisualMeaning {
|
|
@@ -131,8 +96,47 @@ export interface SearchResultItem {
|
|
|
131
96
|
score: number;
|
|
132
97
|
signals?: SearchResultSignals;
|
|
133
98
|
retired?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Present (true) only on quality-floor rejects returned under `includeBelowFloor`
|
|
101
|
+
* (match only) — the item failed the match image-quality floor and would be
|
|
102
|
+
* dropped by default. Appended after the kept results.
|
|
103
|
+
*/
|
|
104
|
+
belowFloor?: boolean;
|
|
134
105
|
siblingCount?: number;
|
|
135
106
|
visualMeanings?: VisualMeaning[];
|
|
107
|
+
/**
|
|
108
|
+
* The shared report card for this hit against the request's `filters` (required
|
|
109
|
+
* cells) and `preferences` (preferred cells). Present (full view only) whenever a
|
|
110
|
+
* spec filter or preference was given. Reuses the by-spec verdict shape — the
|
|
111
|
+
* shared CandidateVerdict mirror; see {@link MaterialVerdict}.
|
|
112
|
+
*/
|
|
113
|
+
verdict?: BySpecVerdict;
|
|
114
|
+
}
|
|
115
|
+
/** The lean `view:"card"` result item — identity + name + thumbnail + score. */
|
|
116
|
+
export interface SearchCardItem {
|
|
117
|
+
variantId: string;
|
|
118
|
+
productId: string | null;
|
|
119
|
+
brandSlug: string | null;
|
|
120
|
+
brand: string;
|
|
121
|
+
/** Product name (the full item's `product`). */
|
|
122
|
+
name: string;
|
|
123
|
+
variantName: string | null;
|
|
124
|
+
imageUrl: string | null;
|
|
125
|
+
score: number;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A feed total with its honesty basis. `exact` — the true count (≤ the cap of
|
|
129
|
+
* 1,000, or a facet-MV read). `lowerBound` — a floor (cap hit, ANN-pool bound,
|
|
130
|
+
* or a degraded count). Never a silent approximation.
|
|
131
|
+
*/
|
|
132
|
+
export interface CountedTotal {
|
|
133
|
+
value: number;
|
|
134
|
+
basis: OpenEnum<"exact" | "lowerBound">;
|
|
135
|
+
}
|
|
136
|
+
/** Feed counts — present under `count:true` or `collapse:"product"`. */
|
|
137
|
+
export interface SearchCounts {
|
|
138
|
+
variants: CountedTotal;
|
|
139
|
+
products?: CountedTotal;
|
|
136
140
|
}
|
|
137
141
|
/** The single candidate source a query resolved to. */
|
|
138
142
|
export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical">;
|
|
@@ -142,16 +146,22 @@ export interface SearchRerankReport {
|
|
|
142
146
|
colourWeight?: number;
|
|
143
147
|
colourLabel?: string;
|
|
144
148
|
}
|
|
145
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* The search response envelope. `view` discriminates the `results` union:
|
|
151
|
+
* `"full"` (default) carries {@link SearchResultItem}s, `"card"` the lean
|
|
152
|
+
* {@link SearchCardItem}s. Every other field is shared. `counts` is present
|
|
153
|
+
* under `count:true` or `collapse:"product"` (replacing the former
|
|
154
|
+
* `variantCount`/`productCount`).
|
|
155
|
+
*/
|
|
146
156
|
export interface SearchResponse {
|
|
157
|
+
view: OpenEnum<"full" | "card">;
|
|
147
158
|
results: SearchResultItem[];
|
|
148
159
|
suggestions: SearchSuggestion[];
|
|
149
160
|
mode: SearchMode;
|
|
150
161
|
maxScore: number;
|
|
151
162
|
cursor?: string;
|
|
152
163
|
rerank?: SearchRerankReport;
|
|
153
|
-
|
|
154
|
-
productCount?: number;
|
|
164
|
+
counts?: SearchCounts;
|
|
155
165
|
note?: string;
|
|
156
166
|
timings?: Record<string, number>;
|
|
157
167
|
/** Echo of the resolved query input (raw image bytes/vector are never echoed). */
|
|
@@ -159,6 +169,11 @@ export interface SearchResponse {
|
|
|
159
169
|
/** A `getVariantBrief` echo of the seed variant, present only in `variantId` seed mode. */
|
|
160
170
|
seed?: MaterialTile | Record<string, unknown>;
|
|
161
171
|
}
|
|
172
|
+
/** The `view:"card"` response form — the lean grid projection of {@link SearchResponse}. */
|
|
173
|
+
export interface SearchCardResponse extends Omit<SearchResponse, "view" | "results"> {
|
|
174
|
+
view: OpenEnum<"card">;
|
|
175
|
+
results: SearchCardItem[];
|
|
176
|
+
}
|
|
162
177
|
/** An inferred product-type category from the query image, with its boost. */
|
|
163
178
|
export interface MatchCategoryInference {
|
|
164
179
|
productTypeId: string;
|
|
@@ -348,31 +363,7 @@ export interface ResolveAttributeResponse {
|
|
|
348
363
|
candidates: AttributeCandidate[];
|
|
349
364
|
}
|
|
350
365
|
export type * from "./catalog-discovery-types.js";
|
|
351
|
-
export
|
|
352
|
-
attributeId: string;
|
|
353
|
-
confidence: number | null;
|
|
354
|
-
sourceBasis: string | null;
|
|
355
|
-
}
|
|
356
|
-
export interface BySpecMatch {
|
|
357
|
-
variantId: string;
|
|
358
|
-
brand: string;
|
|
359
|
-
product: string;
|
|
360
|
-
variant: string;
|
|
361
|
-
color: string | null;
|
|
362
|
-
imageUrl: string | null;
|
|
363
|
-
hex: string | null;
|
|
364
|
-
matched: BySpecMatchedCell[];
|
|
365
|
-
minConfidence: number | null;
|
|
366
|
-
}
|
|
367
|
-
export interface BySpecResponse {
|
|
368
|
-
criteria: Array<{
|
|
369
|
-
attribute: string;
|
|
370
|
-
value: string;
|
|
371
|
-
}>;
|
|
372
|
-
totalMatches: number;
|
|
373
|
-
brandsSpanned: number;
|
|
374
|
-
matches: BySpecMatch[];
|
|
375
|
-
}
|
|
366
|
+
export type * from "./by-spec-types.js";
|
|
376
367
|
export interface WhoamiResponse {
|
|
377
368
|
principal: string;
|
|
378
369
|
scopes: string[];
|
|
@@ -416,6 +407,11 @@ export interface SearchInput {
|
|
|
416
407
|
};
|
|
417
408
|
mood?: string;
|
|
418
409
|
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
410
|
+
/**
|
|
411
|
+
* Seed a visual search from an existing variant's image (`variantId`) or a
|
|
412
|
+
* pre-computed image vector (`vector`). Only the `{ variantId }` form carries a
|
|
413
|
+
* seed variant, so it is the only form `verdicts` can diff a candidate against.
|
|
414
|
+
*/
|
|
419
415
|
similarTo?: {
|
|
420
416
|
variantId: string;
|
|
421
417
|
} | {
|
|
@@ -431,12 +427,33 @@ export interface SearchInput {
|
|
|
431
427
|
category?: number;
|
|
432
428
|
colour?: number;
|
|
433
429
|
lexical?: number;
|
|
430
|
+
/** Text rerank multiplier applied when a seeded search also carries `text` (default 0.4). */
|
|
431
|
+
text?: number;
|
|
434
432
|
};
|
|
435
433
|
filters?: SearchFilter[];
|
|
434
|
+
/**
|
|
435
|
+
* SOFT spec criteria (rank-never-exclude). Same shape as `filters`; a preference
|
|
436
|
+
* never removes a result — it adds a `preferred` cell to each hit's `verdict` and
|
|
437
|
+
* demotes a failed/missing preferred hit WITHIN its page only.
|
|
438
|
+
*/
|
|
439
|
+
preferences?: SearchFilter[];
|
|
436
440
|
limit?: number;
|
|
437
441
|
cursor?: string;
|
|
438
442
|
explain?: boolean;
|
|
439
443
|
collapse?: OpenEnum<"none" | "product">;
|
|
444
|
+
/** Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools. */
|
|
445
|
+
count?: boolean;
|
|
446
|
+
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
447
|
+
hasImages?: boolean;
|
|
448
|
+
/**
|
|
449
|
+
* Opt-in seed-relative verdict cells (MG-629). Only meaningful with
|
|
450
|
+
* `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
|
|
451
|
+
* diffing its comparable attributes (numeric + a `colour` axis) against the
|
|
452
|
+
* seed's, with directional deviations. Inert on every other seed form.
|
|
453
|
+
*/
|
|
454
|
+
verdicts?: boolean;
|
|
455
|
+
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
456
|
+
view?: OpenEnum<"full" | "card">;
|
|
440
457
|
}
|
|
441
458
|
/** Query-string params for the GET `/search` form. */
|
|
442
459
|
export interface SearchGetParams {
|
|
@@ -450,6 +467,12 @@ export interface SearchGetParams {
|
|
|
450
467
|
limit?: number;
|
|
451
468
|
/** "0" or "1" — include per-signal `signals` on each result. */
|
|
452
469
|
explain?: OpenEnum<"0" | "1">;
|
|
470
|
+
/** "true" — opt in to a `counts.variants` feed total. */
|
|
471
|
+
count?: OpenEnum<"true">;
|
|
472
|
+
/** "true" — only return materials with at least one catalog image. */
|
|
473
|
+
hasImages?: OpenEnum<"true">;
|
|
474
|
+
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
475
|
+
view?: OpenEnum<"full" | "card">;
|
|
453
476
|
}
|
|
454
477
|
/** Params for `search/colours`. */
|
|
455
478
|
export interface ByColourParams {
|
|
@@ -485,7 +508,19 @@ export interface MatchInput {
|
|
|
485
508
|
detectionLabel?: string;
|
|
486
509
|
materialFunction?: string;
|
|
487
510
|
detectionCategory?: string;
|
|
511
|
+
/**
|
|
512
|
+
* Opt-in: also return quality-floor rejects, flagged `belowFloor: true` and
|
|
513
|
+
* appended after the kept results. Absent/false = default behaviour;
|
|
514
|
+
* `droppedBelowFloor` is unchanged.
|
|
515
|
+
*/
|
|
516
|
+
includeBelowFloor?: boolean;
|
|
488
517
|
requestedType?: OpenEnum<"swatch" | "installation" | "flatplan">;
|
|
518
|
+
/** Hard spec gates forwarded verbatim to the inner search — term match or presence, each with an optional trust bar. */
|
|
519
|
+
filters?: SearchFilter[];
|
|
520
|
+
/** Soft criteria (rank-never-exclude). A miss demotes within the page but never removes a hit. */
|
|
521
|
+
preferences?: SearchFilter[];
|
|
522
|
+
/** Opt in to seed-relative verdict cells; inert unless the inner search carries a variant seed. */
|
|
523
|
+
verdicts?: boolean;
|
|
489
524
|
}
|
|
490
525
|
/** Options for a batch material read. */
|
|
491
526
|
export interface MaterialsBatchOptions {
|
|
@@ -559,6 +594,98 @@ export interface DemandResponse {
|
|
|
559
594
|
demand: MaterialDemandResult;
|
|
560
595
|
cospec: CoSpecifiedResult;
|
|
561
596
|
}
|
|
597
|
+
/** Which behavioural signal candidate demand is read from. */
|
|
598
|
+
export type DemandSignal = OpenEnum<"sampled" | "considered" | "specified">;
|
|
599
|
+
/**
|
|
600
|
+
* Body for `POST /api/v1/demand/batch` — enrich 1–48 already-selected candidate
|
|
601
|
+
* ids with their real specification demand, in input order and cardinality.
|
|
602
|
+
*/
|
|
603
|
+
export interface DemandBatchInput {
|
|
604
|
+
/** 1–48 canonical variant ids. Order AND cardinality are preserved — duplicates are echoed, never collapsed. */
|
|
605
|
+
variantIds: string[];
|
|
606
|
+
/** The behavioural signal to read demand from. Defaults to `sampled` (order co-occurrence) server-side. */
|
|
607
|
+
signal?: DemandSignal;
|
|
608
|
+
/** Optional declared context (e.g. `{ projectType: "hospitality" }`) — echoed for provenance, never used to rank or filter. */
|
|
609
|
+
context?: Record<string, string>;
|
|
610
|
+
}
|
|
611
|
+
/** Headline population behind the metric — this variant's own counts. */
|
|
612
|
+
export interface DemandBatchPopulation {
|
|
613
|
+
specCount: number;
|
|
614
|
+
projectCount: number;
|
|
615
|
+
customerCount: number;
|
|
616
|
+
}
|
|
617
|
+
/** The consideration→specification funnel (project-grain; `exclusionRate` is annotate-only). */
|
|
618
|
+
export interface DemandBatchFunnel {
|
|
619
|
+
consideredProjects: number;
|
|
620
|
+
specifiedProjects: number;
|
|
621
|
+
excludedCount: number;
|
|
622
|
+
exclusionRate: number;
|
|
623
|
+
}
|
|
624
|
+
/** Per-result guardrail status — present on `internal` (`mg:admin`) results only. */
|
|
625
|
+
export interface DemandBatchEvidence {
|
|
626
|
+
belowPrivacyFloor: boolean;
|
|
627
|
+
belowQualityFloor: boolean;
|
|
628
|
+
suppressedForPublic: boolean;
|
|
629
|
+
}
|
|
630
|
+
/** A variant that carries a usable demand signal. */
|
|
631
|
+
export interface DemandBatchAvailableResult {
|
|
632
|
+
variantId: string;
|
|
633
|
+
state: "available";
|
|
634
|
+
population: DemandBatchPopulation;
|
|
635
|
+
/** Exact per-quarter counts (sparse — zero-spec quarters are absent). */
|
|
636
|
+
intervals: DemandQuarter[];
|
|
637
|
+
momentum: DemandMomentum;
|
|
638
|
+
/** The funnel; null when the signal is not `sampled` or no `considered` row/session exists. */
|
|
639
|
+
funnel: DemandBatchFunnel | null;
|
|
640
|
+
/** Exact guardrail evidence for `internal` callers; null for public. */
|
|
641
|
+
evidence: DemandBatchEvidence | null;
|
|
642
|
+
}
|
|
643
|
+
/** A variant with a stats row that is below the public quality floor. */
|
|
644
|
+
export interface DemandBatchInsufficientResult {
|
|
645
|
+
variantId: string;
|
|
646
|
+
state: "insufficient_data";
|
|
647
|
+
}
|
|
648
|
+
/** A variant with no stats — indistinguishable from a privacy-suppressed one for public callers. */
|
|
649
|
+
export interface DemandBatchNoSignalResult {
|
|
650
|
+
variantId: string;
|
|
651
|
+
state: "no_signal";
|
|
652
|
+
}
|
|
653
|
+
/** A requested id that resolves to no real variant. */
|
|
654
|
+
export interface DemandBatchUnknownResult {
|
|
655
|
+
variantId: string;
|
|
656
|
+
state: "unknown_variant";
|
|
657
|
+
}
|
|
658
|
+
/** One discriminated result per input id, in input order and cardinality. */
|
|
659
|
+
export type DemandBatchResult = DemandBatchAvailableResult | DemandBatchInsufficientResult | DemandBatchNoSignalResult | DemandBatchUnknownResult;
|
|
660
|
+
/** The `POST /api/v1/demand/batch` envelope. */
|
|
661
|
+
export interface DemandBatchResponse {
|
|
662
|
+
/** The completed ingestion-session id these counts were mined in (the dataset generation). */
|
|
663
|
+
generationId: string;
|
|
664
|
+
metric: "specification_demand";
|
|
665
|
+
/** The metric universe — the window and signal the counts are scoped to. */
|
|
666
|
+
universe: {
|
|
667
|
+
window: "trailing_36mo";
|
|
668
|
+
signal: DemandSignal;
|
|
669
|
+
description: string;
|
|
670
|
+
};
|
|
671
|
+
/** The guardrail status under which `results` were projected. */
|
|
672
|
+
guardrail: {
|
|
673
|
+
audience: OpenEnum<"public" | "internal">;
|
|
674
|
+
minProjectCount: number;
|
|
675
|
+
minCustomerCount: number;
|
|
676
|
+
};
|
|
677
|
+
/** Outcome tallies over `results` — never a denominator for the metric. */
|
|
678
|
+
coverage: {
|
|
679
|
+
requested: number;
|
|
680
|
+
available: number;
|
|
681
|
+
insufficientData: number;
|
|
682
|
+
noSignal: number;
|
|
683
|
+
unknownVariant: number;
|
|
684
|
+
};
|
|
685
|
+
/** The echoed declared context, or null when none was supplied. */
|
|
686
|
+
context: Record<string, string> | null;
|
|
687
|
+
results: DemandBatchResult[];
|
|
688
|
+
}
|
|
562
689
|
export interface CoordinateSeed extends DemandSeed {
|
|
563
690
|
/** The input reference this seed resolved from. */
|
|
564
691
|
resolvedFrom: string;
|
|
@@ -742,7 +869,7 @@ export interface ExtractPaletteInput {
|
|
|
742
869
|
/** Options for a cacheable GET batch read (`materialsCached`). */
|
|
743
870
|
export interface MaterialsBatchGetOptions extends MaterialsBatchOptions {
|
|
744
871
|
}
|
|
745
|
-
/** Options for `brands/{
|
|
872
|
+
/** Options for `brands/{brandSlug}`. */
|
|
746
873
|
export interface BrandOptions {
|
|
747
874
|
/** Alternate fuzzy query; takes precedence over the path slug when present. */
|
|
748
875
|
q?: string;
|