@instruments/materialgraph 0.9.1 → 0.11.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/brands-client.d.ts +48 -0
- package/brands-client.js +45 -0
- package/browse-types.d.ts +147 -0
- package/browse-types.js +1 -0
- package/by-colour-types.d.ts +105 -5
- package/client-browse.d.ts +13 -3
- package/client-browse.js +12 -2
- package/client-exploration.d.ts +9 -0
- package/client-exploration.js +7 -0
- package/client-source-material-comments.d.ts +51 -0
- package/client-source-material-comments.js +40 -0
- package/client.d.ts +64 -71
- package/client.js +90 -78
- package/colour-aggregate-types.d.ts +58 -0
- package/colours-client.d.ts +8 -2
- package/colours-client.js +15 -2
- package/composition-client.d.ts +33 -0
- package/composition-client.js +27 -0
- package/composition-types.d.ts +46 -0
- package/coordinate-client.d.ts +23 -0
- package/coordinate-client.js +25 -0
- package/errors.js +24 -8
- package/exploration-types.d.ts +110 -0
- package/exploration-types.js +1 -0
- package/fan-deck-types.d.ts +42 -0
- package/fan-deck-types.js +9 -0
- package/http-transport.js +24 -5
- package/index.d.ts +7 -3
- package/index.js +2 -2
- package/match-stream-types.d.ts +1 -1
- package/match-stream-types.js +16 -3
- package/match-stream.js +14 -4
- package/material-types.d.ts +1 -1
- package/operations.d.ts +1 -1
- package/operations.js +187 -26
- package/package.json +1 -1
- package/palette-search-types.d.ts +31 -0
- package/resolve-client.d.ts +22 -3
- package/resolve-client.js +31 -4
- package/resolve-types.d.ts +70 -1
- package/run-events.d.ts +1 -1
- package/run-events.js +4 -1
- package/run-types.d.ts +132 -0
- package/runs.d.ts +35 -1
- package/runs.js +69 -5
- package/schedule-types.d.ts +128 -0
- package/schema-resource-types.d.ts +1 -1
- package/search-refinement-types.d.ts +39 -4
- package/search-request-types.d.ts +172 -0
- package/search-request-types.js +1 -0
- package/search-result-validation.js +21 -4
- package/slot-pool-types.d.ts +370 -0
- package/slot-pool-types.js +1 -0
- package/source-material-validation.d.ts +5 -1
- package/source-material-validation.js +114 -5
- package/spec-facet-types.d.ts +53 -0
- package/spec-facet-types.js +7 -0
- package/specifications.d.ts +26 -0
- package/specifications.js +32 -0
- package/sse.js +3 -1
- package/suggest-types.js +3 -1
- package/types.d.ts +176 -216
- package/validation.d.ts +12 -0
- package/validation.js +23 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module search-request-types
|
|
3
|
+
* The two request shapes for the `/search` surface — the POST JSON body and the
|
|
4
|
+
* GET query-string form. Split out of `types.ts` so neither file outgrows the
|
|
5
|
+
* repo's source-size gate; every name here is re-exported from `types.ts`, so
|
|
6
|
+
* no import path changes.
|
|
7
|
+
*/
|
|
8
|
+
import type { Cursor, VariantId } from "./identifiers.js";
|
|
9
|
+
import type { SearchFilter } from "./search-refinement-types.js";
|
|
10
|
+
import type { ColourInput, OpenEnum, SearchImageInput } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* The POST `/search` JSON body — the `searchMaterials` contract. Every field is
|
|
13
|
+
* optional; supply at least one signal (`text`, `colours`, `mood`, `similarTo`,
|
|
14
|
+
* or `image`). Passed through to the server verbatim.
|
|
15
|
+
*/
|
|
16
|
+
export interface SearchInput {
|
|
17
|
+
text?: string;
|
|
18
|
+
textModes?: Array<OpenEnum<"visual" | "lexical">>;
|
|
19
|
+
colours?: ColourInput[];
|
|
20
|
+
colourLogic?: OpenEnum<"all" | "any">;
|
|
21
|
+
colourTolerance?: OpenEnum<"tight" | "normal" | "loose">;
|
|
22
|
+
colourAppearance?: {
|
|
23
|
+
lightness?: {
|
|
24
|
+
min: number;
|
|
25
|
+
max: number;
|
|
26
|
+
};
|
|
27
|
+
warmth?: {
|
|
28
|
+
min: number;
|
|
29
|
+
max: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
mood?: string;
|
|
33
|
+
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
34
|
+
/**
|
|
35
|
+
* Seed a visual search from an existing variant's image (`variantId`), from a
|
|
36
|
+
* Material Bank SKU (`materialBankId`), or from a pre-computed image vector
|
|
37
|
+
* (`vector`).
|
|
38
|
+
*
|
|
39
|
+
* `materialBankId` exists so a caller already holding a SKU does not have to
|
|
40
|
+
* ask us to translate it before it can ask us anything useful — one call, not
|
|
41
|
+
* two. It resolves to the variant server-side and then behaves identically to
|
|
42
|
+
* the `{ variantId }` form, seed verdicts included. A SKU that names no
|
|
43
|
+
* variant is a 400 carrying the offending value: never an empty result set,
|
|
44
|
+
* never a silent fall back to unseeded search.
|
|
45
|
+
*
|
|
46
|
+
* The `{ vector }` form carries no seed variant, so it is the only one
|
|
47
|
+
* `verdicts` cannot diff a candidate against.
|
|
48
|
+
*/
|
|
49
|
+
similarTo?: {
|
|
50
|
+
variantId: VariantId;
|
|
51
|
+
} | {
|
|
52
|
+
materialBankId: string;
|
|
53
|
+
} | {
|
|
54
|
+
vector: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Canonical variants that must not appear in the returned page. The server
|
|
58
|
+
* applies these exclusions before the page limit, so consumers can request
|
|
59
|
+
* unseen results without shortening an already-bounded page client-side.
|
|
60
|
+
* Maximum 256; ids are opaque.
|
|
61
|
+
*/
|
|
62
|
+
excludeVariantIds?: VariantId[];
|
|
63
|
+
image?: SearchImageInput;
|
|
64
|
+
category?: {
|
|
65
|
+
boost?: string;
|
|
66
|
+
filter?: string[];
|
|
67
|
+
};
|
|
68
|
+
weights?: {
|
|
69
|
+
image?: number;
|
|
70
|
+
category?: number;
|
|
71
|
+
colour?: number;
|
|
72
|
+
lexical?: number;
|
|
73
|
+
/** Text rerank multiplier applied when a seeded search also carries `text` (default 0.4). */
|
|
74
|
+
text?: number;
|
|
75
|
+
};
|
|
76
|
+
filters?: SearchFilter[];
|
|
77
|
+
/**
|
|
78
|
+
* SOFT spec criteria (rank-never-exclude). Same shape as `filters`; a preference
|
|
79
|
+
* never removes a result — it adds a `preferred` cell to each hit's `verdict` and
|
|
80
|
+
* demotes a failed/missing preferred hit WITHIN its page only.
|
|
81
|
+
*/
|
|
82
|
+
preferences?: SearchFilter[];
|
|
83
|
+
/** Page size — the size of ONE page, never a bound on the match set. */
|
|
84
|
+
limit?: number;
|
|
85
|
+
/** The previous response's `cursor`, passed back verbatim. Never synthesise one. */
|
|
86
|
+
cursor?: Cursor;
|
|
87
|
+
explain?: boolean;
|
|
88
|
+
collapse?: OpenEnum<"none" | "product">;
|
|
89
|
+
/**
|
|
90
|
+
* Opt-in feed total: EXACT up to 1,000, `lowerBound` beyond / for ANN pools.
|
|
91
|
+
* Without it the response states NO total — and a page size is not one.
|
|
92
|
+
*/
|
|
93
|
+
count?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Opt-in per-query facet counts (MG-1010): up to 10 facet ids — `"brand"`,
|
|
96
|
+
* `"category"`, or spec attribute ids — and the response carries a `facets`
|
|
97
|
+
* block counting each facet's values WITHIN this query's match set. An
|
|
98
|
+
* unknown id is a 400 naming it; a known but uncountable id lands in
|
|
99
|
+
* `facets.absent`. Absent = no `facets` in the response, no counting cost.
|
|
100
|
+
*/
|
|
101
|
+
facetCounts?: string[];
|
|
102
|
+
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
103
|
+
hasImages?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Opt-in seed-relative verdict cells (MG-629). Only meaningful with
|
|
106
|
+
* `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
|
|
107
|
+
* diffing its comparable attributes (numeric + a `colour` axis) against the
|
|
108
|
+
* seed's, with directional deviations. Inert on every other seed form.
|
|
109
|
+
*/
|
|
110
|
+
verdicts?: boolean;
|
|
111
|
+
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
112
|
+
view?: OpenEnum<"full" | "card">;
|
|
113
|
+
/**
|
|
114
|
+
* Budget profile for the search primitive.
|
|
115
|
+
*
|
|
116
|
+
* `"search"` (default) is the full engine. `"typeahead"` is the as-you-type
|
|
117
|
+
* profile: lexical-only with prefix match on the final token, forced
|
|
118
|
+
* `view: "card"`, `limit` capped at 24, and no mood/colour detection tiers,
|
|
119
|
+
* semantic fallback, category boost, colour rerank, refinement suggestions,
|
|
120
|
+
* counts, or meanings. `textModes` is ignored under typeahead.
|
|
121
|
+
*/
|
|
122
|
+
intent?: OpenEnum<"search" | "typeahead">;
|
|
123
|
+
/**
|
|
124
|
+
* Which corpus to answer from.
|
|
125
|
+
*
|
|
126
|
+
* **ABSENT means the WHOLE GRAPH** — every material MaterialGraph knows,
|
|
127
|
+
* orderable or not. Absence is never a narrower default, so a caller that
|
|
128
|
+
* says nothing gets materials it may not be able to buy.
|
|
129
|
+
* `"materialBankOrderable"` restricts retrieval to materials carrying a
|
|
130
|
+
* Material Bank identity. `"designShopOrderable"` restricts it to what a
|
|
131
|
+
* consumer can buy on DesignShop today — about 16,200 materials, with the
|
|
132
|
+
* products DesignShop hides from browse excluded.
|
|
133
|
+
*
|
|
134
|
+
* A scope answers what is BUYABLE. It never answers what is SUITABLE: scope
|
|
135
|
+
* on channel, then refine on sector. A trade-only material can be right for a
|
|
136
|
+
* room and still unbuyable, and most of what a consumer can buy is
|
|
137
|
+
* commercial-suited product.
|
|
138
|
+
*
|
|
139
|
+
* The scope is applied INSIDE the query: `limit` fills with eligible rows,
|
|
140
|
+
* `counts` describe the SCOPED corpus, and the response echoes the scope.
|
|
141
|
+
* That is what post-filtering a page cannot give you — a client-side filter
|
|
142
|
+
* shortens the page exactly when the query drifts toward the corpus beyond
|
|
143
|
+
* the Material Bank feed, which is unpredictable from outside.
|
|
144
|
+
*/
|
|
145
|
+
corpusScope?: OpenEnum<"all" | "materialBankOrderable" | "designShopOrderable">;
|
|
146
|
+
/**
|
|
147
|
+
* OPS/AUDIT escape hatch — do NOT use for normal search. Default behaviour
|
|
148
|
+
* (absent or false) excludes sampling-artifact products: multi-swatch sample
|
|
149
|
+
* cards, which are orderable samples and never specifiable materials.
|
|
150
|
+
*/
|
|
151
|
+
includeSamplingArtifacts?: boolean;
|
|
152
|
+
}
|
|
153
|
+
/** Query-string params for the GET `/search` form. */
|
|
154
|
+
export interface SearchGetParams {
|
|
155
|
+
q?: string;
|
|
156
|
+
colours?: string;
|
|
157
|
+
logic?: OpenEnum<"all" | "any">;
|
|
158
|
+
mood?: string;
|
|
159
|
+
sort?: OpenEnum<"relevance" | "light" | "dark" | "vivid" | "muted" | "colourful">;
|
|
160
|
+
tol?: OpenEnum<"tight" | "normal" | "loose">;
|
|
161
|
+
/** Seed the search from this variant's image. Opaque — pass it through verbatim. */
|
|
162
|
+
variantId?: VariantId;
|
|
163
|
+
limit?: number;
|
|
164
|
+
/** "0" or "1" — include per-signal `signals` on each result. */
|
|
165
|
+
explain?: OpenEnum<"0" | "1">;
|
|
166
|
+
/** "true" — opt in to a `counts.variants` feed total. */
|
|
167
|
+
count?: OpenEnum<"true">;
|
|
168
|
+
/** "true" — only return materials with at least one catalog image. */
|
|
169
|
+
hasImages?: OpenEnum<"true">;
|
|
170
|
+
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
171
|
+
view?: OpenEnum<"full" | "card">;
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -12,17 +12,33 @@ export const VERDICT_STATES = [
|
|
|
12
12
|
/** `attributeVerdictSchema.strength`. */
|
|
13
13
|
export const VERDICT_STRENGTHS = ["required", "preferred"];
|
|
14
14
|
/** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
|
|
15
|
-
export const VERDICT_BASELINE_KINDS = [
|
|
15
|
+
export const VERDICT_BASELINE_KINDS = [
|
|
16
|
+
"term",
|
|
17
|
+
"presence",
|
|
18
|
+
"seed",
|
|
19
|
+
"image",
|
|
20
|
+
];
|
|
16
21
|
/** The `image` baseline arm's own `source` enum. */
|
|
17
22
|
export const VERDICT_IMAGE_SOURCES = ["variant", "vector"];
|
|
18
23
|
/** `verdictDeviationSchema.direction`. */
|
|
19
|
-
export const VERDICT_DEVIATION_DIRECTIONS = [
|
|
24
|
+
export const VERDICT_DEVIATION_DIRECTIONS = [
|
|
25
|
+
"above",
|
|
26
|
+
"below",
|
|
27
|
+
"other",
|
|
28
|
+
];
|
|
20
29
|
/** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
|
|
21
|
-
export const VERDICT_TRUST_GAPS = [
|
|
30
|
+
export const VERDICT_TRUST_GAPS = [
|
|
31
|
+
"below_confidence",
|
|
32
|
+
"basis_unmet",
|
|
33
|
+
"proposed",
|
|
34
|
+
];
|
|
22
35
|
/** `verdictGradeSchema` — the rolled-up card grade. */
|
|
23
36
|
export const VERDICT_GRADES = ["meets_all", "close", "hard_no"];
|
|
24
37
|
/** `visualMeaningSchema.basis` — where a meaning came from. */
|
|
25
|
-
export const VISUAL_MEANING_BASES = [
|
|
38
|
+
export const VISUAL_MEANING_BASES = [
|
|
39
|
+
"canonical_attribute",
|
|
40
|
+
"image_colour_analysis",
|
|
41
|
+
];
|
|
26
42
|
// ── Leaf checks ──────────────────────────────────────────────────────────────
|
|
27
43
|
/** `palette[]` — `{ hex, proportion, name: string.nullable().optional() }`. */
|
|
28
44
|
function checkPaletteSwatch(value, what, fail) {
|
|
@@ -165,6 +181,7 @@ export function checkSearchResultItem(value, what, fail) {
|
|
|
165
181
|
checkSignals(record.signals, `${what}.signals`, fail);
|
|
166
182
|
requireOptionalBoolean(record.retired, `${what}.retired`, fail);
|
|
167
183
|
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
184
|
+
requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
|
|
168
185
|
if (record.siblingCount !== undefined) {
|
|
169
186
|
requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
|
|
170
187
|
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-mirrored wire types for the slot-pool surface
|
|
3
|
+
* (`POST /api/v1/schemes/pools`, operation `slot_pools`, MG-1301). Like the rest
|
|
4
|
+
* of the SDK these interfaces carry NO runtime dependency on any
|
|
5
|
+
* `@materialgraph/*` package — they mirror the `SlotPoolsRequest` /
|
|
6
|
+
* `SlotPoolsResponse` shapes in `apps/web/public/openapi.yaml`.
|
|
7
|
+
*
|
|
8
|
+
* THE QUESTION IT ANSWERS: "for each of these slots, what should I be choosing
|
|
9
|
+
* between?" One call, up to 24 slots, one ranked and gated pool each. There is
|
|
10
|
+
* no project and no run — the request is self-contained and the ownership of the
|
|
11
|
+
* work stays with you.
|
|
12
|
+
*
|
|
13
|
+
* THREE THINGS TO READ CORRECTLY, because getting them wrong turns a careful
|
|
14
|
+
* answer into a false one:
|
|
15
|
+
* 1. A pool with a `shortfall` of kind `search-failed` was NOT answered. The
|
|
16
|
+
* other pools in the same response were. Never read that empty pool as
|
|
17
|
+
* "nothing matched".
|
|
18
|
+
* 2. `not_evaluated` and `missing` are not `failed`. The first means the engine
|
|
19
|
+
* did not check the criterion at the tier that ran; the second means the
|
|
20
|
+
* material holds no accepted value for it. Only `failed` means a value
|
|
21
|
+
* exists and does not satisfy the ask.
|
|
22
|
+
* 3. `eligible: false` does not mean "bad". It means this candidate cannot be
|
|
23
|
+
* placed automatically — read `flags` for which of the reasons applies.
|
|
24
|
+
*
|
|
25
|
+
* TOLERANT-PARSING CONTRACT: response enums are widened with {@link OpenEnum}
|
|
26
|
+
* so a new server value still type-checks; objects are read structurally.
|
|
27
|
+
*/
|
|
28
|
+
import type { VariantId } from "./identifiers.js";
|
|
29
|
+
import type { OpenEnum } from "./types.js";
|
|
30
|
+
/**
|
|
31
|
+
* One requirement or preference. Request-side, so this is a STRICT union.
|
|
32
|
+
* `attribute` asks for a canonical term, `attribute-presence` merely that the
|
|
33
|
+
* attribute is recorded, `attribute-numeric` a one-sided bound. `unresolved`
|
|
34
|
+
* carries brief language nothing bound to — it is never a filter and never
|
|
35
|
+
* blamed for a coverage shortfall.
|
|
36
|
+
*/
|
|
37
|
+
export type SlotPoolCriterion = {
|
|
38
|
+
kind: "attribute";
|
|
39
|
+
attributeId: string;
|
|
40
|
+
term: string;
|
|
41
|
+
raw: string;
|
|
42
|
+
} | {
|
|
43
|
+
kind: "attribute-presence";
|
|
44
|
+
attributeId: string;
|
|
45
|
+
raw: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "attribute-numeric";
|
|
48
|
+
attributeId: string;
|
|
49
|
+
comparator: "at-least" | "at-most";
|
|
50
|
+
value: number;
|
|
51
|
+
unit?: string;
|
|
52
|
+
raw: string;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "unresolved";
|
|
55
|
+
raw: string;
|
|
56
|
+
suggestion?: {
|
|
57
|
+
attributeId: string;
|
|
58
|
+
term?: string;
|
|
59
|
+
};
|
|
60
|
+
coverageNote?: string;
|
|
61
|
+
};
|
|
62
|
+
/** A colour cue for a slot. Colours rerank within the retrieved set; they never admit. */
|
|
63
|
+
export interface SlotPoolColourCue {
|
|
64
|
+
/** `#rrggbb`. */
|
|
65
|
+
hex: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What to do with a candidate the catalogue no longer lists. Neither value ever
|
|
70
|
+
* marks such a candidate eligible — the policy decides only whether it is
|
|
71
|
+
* returned at all.
|
|
72
|
+
*/
|
|
73
|
+
export type SlotPoolAvailabilityPolicy = "flag-unavailable" | "exclude-unavailable";
|
|
74
|
+
/** One slot of work. Self-contained: nothing in it identifies a project. */
|
|
75
|
+
export interface SlotPoolTask {
|
|
76
|
+
/**
|
|
77
|
+
* YOUR key for this task, opaque to MaterialGraph and returned verbatim on the
|
|
78
|
+
* matching pool. This is how you correlate 24 results; it is never parsed,
|
|
79
|
+
* normalised or reused as a server id. Must be unique within a request.
|
|
80
|
+
*/
|
|
81
|
+
taskKey: string;
|
|
82
|
+
/**
|
|
83
|
+
* The catalog category. OMITTING IT yields an empty pool with a `no-category`
|
|
84
|
+
* shortfall — category is the only cross-kind gate, and retrieval will not
|
|
85
|
+
* rank an ungated corpus.
|
|
86
|
+
*/
|
|
87
|
+
category?: string;
|
|
88
|
+
/** A short VISUAL description of the material wanted — not a keyword list. */
|
|
89
|
+
intent?: string;
|
|
90
|
+
required?: SlotPoolCriterion[];
|
|
91
|
+
preferred?: SlotPoolCriterion[];
|
|
92
|
+
colours?: SlotPoolColourCue[];
|
|
93
|
+
/** Rank this task around an already-chosen variant. Ids are opaque tokens. */
|
|
94
|
+
anchorSeed?: {
|
|
95
|
+
variantId: VariantId;
|
|
96
|
+
};
|
|
97
|
+
exclude?: {
|
|
98
|
+
variantIds?: VariantId[];
|
|
99
|
+
brandSlugs?: string[];
|
|
100
|
+
};
|
|
101
|
+
availability?: SlotPoolAvailabilityPolicy;
|
|
102
|
+
/** Candidates returned for this task. Max 48, default 12. */
|
|
103
|
+
limit?: number;
|
|
104
|
+
}
|
|
105
|
+
/** Body for `POST /api/v1/schemes/pools` ({@link MaterialGraphClient.slotPools}). */
|
|
106
|
+
export interface SlotPoolsRequest {
|
|
107
|
+
/** Up to 24 tasks. Each is independent: one failing costs that one. */
|
|
108
|
+
tasks: SlotPoolTask[];
|
|
109
|
+
/**
|
|
110
|
+
* Include the co-specification evidence among the returned candidates
|
|
111
|
+
* (default true). One bounded read — never one request per candidate.
|
|
112
|
+
*/
|
|
113
|
+
coSpecification?: boolean;
|
|
114
|
+
}
|
|
115
|
+
/** The six live verdict states, plus the two legacy tokens older rows carry. */
|
|
116
|
+
export type SlotPoolVerdictState = OpenEnum<"matched" | "deviated" | "failed" | "unverified" | "missing" | "not_evaluated">;
|
|
117
|
+
/** One cell of a candidate's report card against one criterion. */
|
|
118
|
+
export interface SlotPoolVerdictCell {
|
|
119
|
+
attributeId: string;
|
|
120
|
+
state: SlotPoolVerdictState;
|
|
121
|
+
strength: OpenEnum<"required" | "preferred">;
|
|
122
|
+
baseline: {
|
|
123
|
+
kind: "term";
|
|
124
|
+
term: string;
|
|
125
|
+
} | {
|
|
126
|
+
kind: "presence";
|
|
127
|
+
} | {
|
|
128
|
+
kind: "seed";
|
|
129
|
+
variantId: VariantId;
|
|
130
|
+
} | {
|
|
131
|
+
kind: "image";
|
|
132
|
+
source: OpenEnum<"variant" | "vector">;
|
|
133
|
+
variantId: VariantId | null;
|
|
134
|
+
};
|
|
135
|
+
/** The candidate's OWN accepted value, or null when it holds none. */
|
|
136
|
+
value: string | null;
|
|
137
|
+
deviation: {
|
|
138
|
+
direction: OpenEnum<"above" | "below" | "other">;
|
|
139
|
+
delta: number | null;
|
|
140
|
+
unit: string | null;
|
|
141
|
+
label: string;
|
|
142
|
+
} | null;
|
|
143
|
+
confidence: number | null;
|
|
144
|
+
/**
|
|
145
|
+
* WHERE the value came from — it ranks the SOURCE, not the accuracy. It says
|
|
146
|
+
* nothing about whether the value is right.
|
|
147
|
+
*/
|
|
148
|
+
sourceBasis: string | null;
|
|
149
|
+
trustGap: OpenEnum<"below_confidence" | "basis_unmet" | "proposed"> | null;
|
|
150
|
+
}
|
|
151
|
+
/** Why a candidate is labelled. An eligible candidate carries none of these. */
|
|
152
|
+
export type SlotPoolFlag = OpenEnum<"below-floor" | "required-not-applied" | "discontinued" | "superseded" | "unverified-value">;
|
|
153
|
+
export type SlotPoolAvailability = OpenEnum<"available" | "discontinued" | "superseded" | "unknown">;
|
|
154
|
+
/** The declared successor of a withdrawn material, one hop only. */
|
|
155
|
+
export interface SlotPoolSuccessor {
|
|
156
|
+
variantId: VariantId;
|
|
157
|
+
brandSlug: string;
|
|
158
|
+
productId: string;
|
|
159
|
+
brand: string;
|
|
160
|
+
product: string;
|
|
161
|
+
variant: string;
|
|
162
|
+
color: string | null;
|
|
163
|
+
availability: SlotPoolAvailability;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The features a composer scores on. Colours are EXTRACTED FROM PRODUCT IMAGERY
|
|
167
|
+
* — visual likeness, never a declared colour specification. An empty `palette`
|
|
168
|
+
* or `attributes` means nothing is on record, never that the material has none.
|
|
169
|
+
*/
|
|
170
|
+
export interface SlotPoolCandidateFeatures {
|
|
171
|
+
colour: {
|
|
172
|
+
dominantHex: string | null;
|
|
173
|
+
hsl: {
|
|
174
|
+
h: number;
|
|
175
|
+
s: number;
|
|
176
|
+
l: number;
|
|
177
|
+
} | null;
|
|
178
|
+
oklab: {
|
|
179
|
+
l: number;
|
|
180
|
+
a: number;
|
|
181
|
+
b: number;
|
|
182
|
+
} | null;
|
|
183
|
+
chroma: number | null;
|
|
184
|
+
vibrancy: number | null;
|
|
185
|
+
sourceBasis?: string;
|
|
186
|
+
};
|
|
187
|
+
palette: {
|
|
188
|
+
hex: string;
|
|
189
|
+
proportion: number;
|
|
190
|
+
name?: string | null;
|
|
191
|
+
oklab?: {
|
|
192
|
+
l: number;
|
|
193
|
+
a: number;
|
|
194
|
+
b: number;
|
|
195
|
+
};
|
|
196
|
+
}[];
|
|
197
|
+
attributes: {
|
|
198
|
+
attributeId: string;
|
|
199
|
+
term: string;
|
|
200
|
+
}[];
|
|
201
|
+
dimensions: Record<string, unknown>[] | null;
|
|
202
|
+
}
|
|
203
|
+
export interface SlotPoolCandidate {
|
|
204
|
+
variantId: VariantId;
|
|
205
|
+
productId: string;
|
|
206
|
+
brandSlug: string;
|
|
207
|
+
brand: string;
|
|
208
|
+
product: string;
|
|
209
|
+
variant: string;
|
|
210
|
+
category: string | null;
|
|
211
|
+
materialBankId: string | null;
|
|
212
|
+
imageUrl: string | null;
|
|
213
|
+
availability: SlotPoolAvailability;
|
|
214
|
+
supersededBy: SlotPoolSuccessor | null;
|
|
215
|
+
/**
|
|
216
|
+
* True only when every required criterion was applied, the catalogue still
|
|
217
|
+
* lists the material, and no exclusion touched it. Anything else needs a
|
|
218
|
+
* human decision — read `flags` for which reason.
|
|
219
|
+
*/
|
|
220
|
+
eligible: boolean;
|
|
221
|
+
flags: SlotPoolFlag[];
|
|
222
|
+
score: {
|
|
223
|
+
/** Normalised to [0,1] against the best score on THIS pool's page. */
|
|
224
|
+
retrieval: number;
|
|
225
|
+
/** `unverified` + `not_evaluated` cells. A soft penalty, not a failure count. */
|
|
226
|
+
unknownCount: number;
|
|
227
|
+
verdictCounts: {
|
|
228
|
+
matched: number;
|
|
229
|
+
deviated: number;
|
|
230
|
+
failed: number;
|
|
231
|
+
unverified: number;
|
|
232
|
+
missing: number;
|
|
233
|
+
notEvaluated: number;
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
verdict: {
|
|
237
|
+
cells: SlotPoolVerdictCell[];
|
|
238
|
+
grade: OpenEnum<"meets_all" | "close" | "hard_no">;
|
|
239
|
+
minConfidence: number | null;
|
|
240
|
+
};
|
|
241
|
+
features: SlotPoolCandidateFeatures;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Why a pool is short. Four different facts, and none of them is "nothing in
|
|
245
|
+
* the catalogue matches your brief".
|
|
246
|
+
*/
|
|
247
|
+
export type SlotPoolShortfall = {
|
|
248
|
+
kind: "no-category";
|
|
249
|
+
} | {
|
|
250
|
+
kind: "category-coverage";
|
|
251
|
+
category: string;
|
|
252
|
+
attributeIds: string[];
|
|
253
|
+
gaps?: ({
|
|
254
|
+
level: "attribute";
|
|
255
|
+
attributeId: string;
|
|
256
|
+
} | {
|
|
257
|
+
level: "value";
|
|
258
|
+
attributeId: string;
|
|
259
|
+
term: string;
|
|
260
|
+
})[];
|
|
261
|
+
} | {
|
|
262
|
+
kind: "search-failed";
|
|
263
|
+
detail: string;
|
|
264
|
+
} | {
|
|
265
|
+
kind: "no-match";
|
|
266
|
+
tier: string;
|
|
267
|
+
consideredCount: number;
|
|
268
|
+
};
|
|
269
|
+
export interface SlotPool {
|
|
270
|
+
/** Your own key, verbatim. */
|
|
271
|
+
taskKey: string;
|
|
272
|
+
category: string | null;
|
|
273
|
+
/** The relaxation rung the returned candidates were drawn at. */
|
|
274
|
+
retrievalTier: OpenEnum<"strict" | "presence" | "category-only">;
|
|
275
|
+
/**
|
|
276
|
+
* HOW the candidates were retrieved. DERIVED from the retrieval plan and the
|
|
277
|
+
* pool's receipt — MaterialGraph does not report an arm per candidate.
|
|
278
|
+
*
|
|
279
|
+
* It is constant across the relaxation rungs, so it cannot tell you how far
|
|
280
|
+
* the ladder went. `retrievalTier` beside it names the rung the pool settled
|
|
281
|
+
* on: the arm says how, the tier says how strictly.
|
|
282
|
+
*/
|
|
283
|
+
arm: OpenEnum<"visual-text" | "image-similarity" | "none">;
|
|
284
|
+
candidates: SlotPoolCandidate[];
|
|
285
|
+
/** How much retrieval scored before this task's own `limit` trimmed the page. */
|
|
286
|
+
consideredCount: number;
|
|
287
|
+
returnedCount: number;
|
|
288
|
+
excludedCount: number;
|
|
289
|
+
unavailableDroppedCount: number;
|
|
290
|
+
/**
|
|
291
|
+
* The criteria nothing on this page could be graded on — a statement about
|
|
292
|
+
* catalogue COVERAGE, never about the candidates.
|
|
293
|
+
*/
|
|
294
|
+
unknownCoverage: {
|
|
295
|
+
evaluatedCriteria: number;
|
|
296
|
+
/** Every returned candidate graded `not_evaluated` on these. */
|
|
297
|
+
uncheckedCriteria: string[];
|
|
298
|
+
/** Every returned candidate graded `missing` on these. */
|
|
299
|
+
unheldCriteria: string[];
|
|
300
|
+
};
|
|
301
|
+
shortfall: SlotPoolShortfall | null;
|
|
302
|
+
seededFrom: {
|
|
303
|
+
variantId: VariantId;
|
|
304
|
+
} | null;
|
|
305
|
+
warnings: string[];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The sparse pairing graph among the returned candidates. Each unordered pair
|
|
309
|
+
* appears ONCE and the relation is symmetric. An empty `edges` under a null
|
|
310
|
+
* `datasetSessionId` means the dataset is absent, NOT that these materials are
|
|
311
|
+
* never specified together.
|
|
312
|
+
*/
|
|
313
|
+
export interface SlotPoolCoSpecification {
|
|
314
|
+
edges: {
|
|
315
|
+
from: VariantId;
|
|
316
|
+
to: VariantId;
|
|
317
|
+
projectCount: number;
|
|
318
|
+
}[];
|
|
319
|
+
coverage: {
|
|
320
|
+
candidateCount: number;
|
|
321
|
+
coveredCount: number;
|
|
322
|
+
truncated: boolean;
|
|
323
|
+
datasetSessionId: string | null;
|
|
324
|
+
window: string;
|
|
325
|
+
signal: string;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/** The `200` body from `POST /api/v1/schemes/pools`. */
|
|
329
|
+
export interface SlotPoolsResponse {
|
|
330
|
+
versions: {
|
|
331
|
+
contract: string;
|
|
332
|
+
retrieval: string;
|
|
333
|
+
evaluator: string;
|
|
334
|
+
dataset: {
|
|
335
|
+
coSpecificationSessionId: string | null;
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
/** One pool per task, in request order. Correlate by `taskKey`, not by index. */
|
|
339
|
+
pools: SlotPool[];
|
|
340
|
+
coSpecification: SlotPoolCoSpecification | null;
|
|
341
|
+
summary: {
|
|
342
|
+
tasks: number;
|
|
343
|
+
eligibleCandidates: number;
|
|
344
|
+
flaggedCandidates: number;
|
|
345
|
+
tasksWithEligibleCandidates: number;
|
|
346
|
+
tasksWithoutEligibleCandidates: number;
|
|
347
|
+
/** Non-zero means some tasks were NOT answered; their pools say so. */
|
|
348
|
+
searchFailed: number;
|
|
349
|
+
noCategory: number;
|
|
350
|
+
categoryCoverage: number;
|
|
351
|
+
noMatch: number;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* CALL VOLUME — how many of each bounded operation the request performed.
|
|
355
|
+
*
|
|
356
|
+
* Never a cost or token figure. Slot retrieval is database and embedding work
|
|
357
|
+
* and does not go through the model-call path that carries token and spend
|
|
358
|
+
* accounting, so no such figure exists for it. Do not render these as usage
|
|
359
|
+
* or spend.
|
|
360
|
+
*/
|
|
361
|
+
calls: {
|
|
362
|
+
searchesRun: number;
|
|
363
|
+
searchesFailed: number;
|
|
364
|
+
candidatesConsidered: number;
|
|
365
|
+
candidatesReturned: number;
|
|
366
|
+
hydrationBatches: number;
|
|
367
|
+
coSpecificationQueries: number;
|
|
368
|
+
};
|
|
369
|
+
warnings: string[];
|
|
370
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import type { SourceMaterialBatchDetail } from "./run-types.js";
|
|
1
|
+
import type { PostSourceMaterialCommentResult, SourceMaterialBatchDetail, SourceMaterialCommentThread } from "./run-types.js";
|
|
2
2
|
/** Validate a batch-detail body, returning it typed, or throw explaining why. */
|
|
3
3
|
export declare function checkSourceMaterialBatchDetail(value: unknown, batchId: string): SourceMaterialBatchDetail;
|
|
4
|
+
/** Validate a comment-thread body, returning it typed, or throw explaining why. */
|
|
5
|
+
export declare function checkSourceMaterialCommentThread(value: unknown, sourceId: string): SourceMaterialCommentThread;
|
|
6
|
+
/** Validate a posted-comment body, returning it typed, or throw explaining why. */
|
|
7
|
+
export declare function checkPostSourceMaterialCommentResult(value: unknown, sourceId: string): PostSourceMaterialCommentResult;
|