@instruments/materialgraph 0.18.0 → 0.20.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/association-types.d.ts +29 -4
- package/attribution-types.d.ts +51 -0
- package/attribution-types.js +13 -0
- package/browse-types.d.ts +14 -3
- package/by-colour-types.d.ts +33 -6
- package/by-spec-types.d.ts +4 -0
- package/catalogue-client.d.ts +54 -0
- package/catalogue-client.js +16 -0
- package/catalogue-types.d.ts +3 -1
- package/citation-types.d.ts +34 -0
- package/citation-types.js +1 -0
- package/client-browse.d.ts +1 -0
- package/client-browse.js +1 -0
- package/client-vocabulary.d.ts +10 -0
- package/client-vocabulary.js +32 -0
- package/client.d.ts +22 -45
- package/client.js +40 -55
- package/colours-client.d.ts +8 -3
- package/colours-client.js +8 -0
- package/composition-types.d.ts +6 -0
- package/corpus-scope-types.d.ts +1 -1
- package/errors.d.ts +1 -1
- package/errors.js +2 -0
- package/identify-types.d.ts +72 -0
- package/identify-types.js +7 -0
- package/index.d.ts +10 -0
- package/json-value-types.d.ts +8 -0
- package/json-value-types.js +1 -0
- package/match-stream-types.d.ts +13 -0
- package/match-types.d.ts +13 -0
- package/material-block-types.d.ts +4 -0
- package/material-resources-client.d.ts +3 -0
- package/material-resources-client.js +4 -0
- package/material-types.d.ts +24 -0
- package/operations.d.ts +1 -1
- package/operations.js +64 -2
- package/package.json +8 -1
- package/palette-completion-types.d.ts +115 -0
- package/palette-completion-types.js +1 -0
- package/palette-search-types.d.ts +16 -2
- package/render-assets-types.d.ts +65 -0
- package/render-assets-types.js +1 -0
- package/run-events.d.ts +13 -0
- package/samplize-catalogue-types.d.ts +92 -0
- package/samplize-catalogue-types.js +1 -0
- package/samplize-colorvec.d.ts +70 -0
- package/samplize-colorvec.js +167 -0
- package/schedule-reference-types.d.ts +46 -0
- package/schedule-reference-types.js +1 -0
- package/schedule-references-client.d.ts +14 -0
- package/schedule-references-client.js +26 -0
- package/schedule-types.d.ts +34 -0
- package/search-card-types.d.ts +53 -0
- package/search-card-types.js +1 -0
- package/search-mood-types.d.ts +6 -0
- package/search-mood-types.js +1 -0
- package/search-refinement-types.d.ts +5 -0
- package/search-request-types.d.ts +5 -0
- package/search-response-receipt-types.d.ts +8 -0
- package/search-result-validation.js +10 -0
- package/slot-pool-types.d.ts +2 -0
- package/spec-types.d.ts +6 -1
- package/specifications.d.ts +3 -1
- package/specifications.js +4 -0
- package/substitute-types.d.ts +102 -0
- package/substitute-types.js +1 -0
- package/taxonomy-coverage-types.d.ts +34 -0
- package/taxonomy-coverage-types.js +1 -0
- package/types.d.ts +42 -57
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { EMBEDDING_VERSION, EMBEDDING_VECTOR_SIZE, embeddingCosineSimilarity, paletteToEmbedding, } from "@instruments/colorscope/embedding";
|
|
2
|
+
export const SAMPLIZE_COLORVEC_RANKING_VERSION = "solid-colorvec-cosine-v2";
|
|
3
|
+
export const SAMPLIZE_COLORVEC_EMBEDDING_VERSION = EMBEDDING_VERSION;
|
|
4
|
+
const decodeVector = (encoded) => {
|
|
5
|
+
const bytes = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
|
|
6
|
+
if (bytes.length !== EMBEDDING_VECTOR_SIZE * 4)
|
|
7
|
+
throw new Error("Invalid colour vector size.");
|
|
8
|
+
const view = new DataView(bytes.buffer);
|
|
9
|
+
return Array.from({ length: EMBEDDING_VECTOR_SIZE }, (_, i) => view.getFloat32(i * 4, true));
|
|
10
|
+
};
|
|
11
|
+
const facets = ["brand", "family", "lightness", "intensity"];
|
|
12
|
+
const identityOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
13
|
+
export const samplizeSolidColorVector = (hex) => {
|
|
14
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex))
|
|
15
|
+
throw new Error("A six-digit published colour is required.");
|
|
16
|
+
return paletteToEmbedding([{ hex, proportion: 1 }], { validateOklab: true });
|
|
17
|
+
};
|
|
18
|
+
/** Canonical constraints are shared by full-cache similarity and constrained text retrieval. */
|
|
19
|
+
export function selectSamplizeEligiblePaints(paints, input) {
|
|
20
|
+
const reference = input.reference
|
|
21
|
+
? paints.find((p) => p.variantId === input.reference)
|
|
22
|
+
: undefined;
|
|
23
|
+
if (input.reference && !reference)
|
|
24
|
+
throw new Error("The reference is absent from this catalogue version.");
|
|
25
|
+
const low = input.lightnessMin ?? 0, high = input.lightnessMax ?? 1;
|
|
26
|
+
const cool = input.temperatureMin ?? -0.4, warm = input.temperatureMax ?? 0.4;
|
|
27
|
+
if (![low, high, cool, warm].every(Number.isFinite) ||
|
|
28
|
+
low > high ||
|
|
29
|
+
cool > warm)
|
|
30
|
+
throw new Error("Appearance ranges must be finite and ordered.");
|
|
31
|
+
const candidates = paints.filter((p) => {
|
|
32
|
+
if (!reference)
|
|
33
|
+
return true;
|
|
34
|
+
if (p.variantId === reference.variantId)
|
|
35
|
+
return false;
|
|
36
|
+
if (input.direction === "lighter")
|
|
37
|
+
return p.oklab.L > reference.oklab.L;
|
|
38
|
+
if (input.direction === "darker")
|
|
39
|
+
return p.oklab.L < reference.oklab.L;
|
|
40
|
+
if (input.direction === "muted")
|
|
41
|
+
return (Math.hypot(p.oklab.a, p.oklab.b) <
|
|
42
|
+
Math.hypot(reference.oklab.a, reference.oklab.b));
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
const matches = (p, omit) => p.oklab.L >= low &&
|
|
46
|
+
p.oklab.L <= high &&
|
|
47
|
+
p.oklab.b >= cool &&
|
|
48
|
+
p.oklab.b <= warm &&
|
|
49
|
+
facets.every((f) => f === omit || !input[f]?.length || input[f].includes(p[f]));
|
|
50
|
+
const counts = {
|
|
51
|
+
brand: {},
|
|
52
|
+
family: {},
|
|
53
|
+
lightness: {},
|
|
54
|
+
intensity: {},
|
|
55
|
+
};
|
|
56
|
+
for (const p of candidates)
|
|
57
|
+
for (const f of facets)
|
|
58
|
+
if (matches(p, f))
|
|
59
|
+
counts[f][p[f]] = (counts[f][p[f]] ?? 0) + 1;
|
|
60
|
+
const eligible = candidates.filter((p) => matches(p));
|
|
61
|
+
return { reference, eligible, counts };
|
|
62
|
+
}
|
|
63
|
+
/** Official portable MG ranking over its complete versioned sampling-channel projection. */
|
|
64
|
+
export function createSamplizeColorVecIndex(cache) {
|
|
65
|
+
if (cache.embeddingVersion !== EMBEDDING_VERSION ||
|
|
66
|
+
cache.rankingVersion !== SAMPLIZE_COLORVEC_RANKING_VERSION)
|
|
67
|
+
throw new Error("The colour cache uses an incompatible embedding or ranking version. Refresh it before searching.");
|
|
68
|
+
if (!cache.sourceVersion ||
|
|
69
|
+
!cache.dictionaryVersion ||
|
|
70
|
+
!Number.isFinite(Date.parse(cache.generatedAt)))
|
|
71
|
+
throw new Error("The colour cache is missing its provenance.");
|
|
72
|
+
const paints = cache.paints.map((p) => ({
|
|
73
|
+
...p,
|
|
74
|
+
vector: typeof p.vector === "string" ? decodeVector(p.vector) : p.vector,
|
|
75
|
+
}));
|
|
76
|
+
const identities = new Set();
|
|
77
|
+
for (const paint of paints) {
|
|
78
|
+
if (identities.has(paint.variantId) ||
|
|
79
|
+
paint.vector.length !== EMBEDDING_VECTOR_SIZE ||
|
|
80
|
+
!paint.vector.every(Number.isFinite) ||
|
|
81
|
+
![paint.oklab.L, paint.oklab.a, paint.oklab.b].every(Number.isFinite))
|
|
82
|
+
throw new Error("The colour cache contains invalid or duplicate paint records.");
|
|
83
|
+
identities.add(paint.variantId);
|
|
84
|
+
}
|
|
85
|
+
const vectors = new Map();
|
|
86
|
+
const vectorFor = (hex) => {
|
|
87
|
+
const key = hex.toLowerCase();
|
|
88
|
+
let value = vectors.get(key);
|
|
89
|
+
if (!value) {
|
|
90
|
+
value = samplizeSolidColorVector(hex);
|
|
91
|
+
if (vectors.size >= 512)
|
|
92
|
+
vectors.delete(vectors.keys().next().value);
|
|
93
|
+
vectors.set(key, value);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
search(input) {
|
|
99
|
+
const limit = input.limit ?? 48;
|
|
100
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
101
|
+
throw new Error("Choose between 1 and 100 suggestions.");
|
|
102
|
+
const { reference, eligible, counts } = selectSamplizeEligiblePaints(paints, input);
|
|
103
|
+
const palette = (input.palette?.length
|
|
104
|
+
? input.palette
|
|
105
|
+
: [{ hex: input.hex ?? reference?.hex ?? "", proportion: 1 }])
|
|
106
|
+
.map((p) => ({ ...p, hex: p.hex.toLowerCase() }))
|
|
107
|
+
.sort((a, b) => b.proportion - a.proportion || identityOrder(a.hex, b.hex));
|
|
108
|
+
if (palette.length > 12 ||
|
|
109
|
+
palette.some((p) => !Number.isFinite(p.proportion) ||
|
|
110
|
+
p.proportion <= 0 ||
|
|
111
|
+
p.proportion > 1))
|
|
112
|
+
throw new Error("Supply up to twelve positive image colour shares.");
|
|
113
|
+
const total = palette.reduce((sum, p) => sum + p.proportion, 0);
|
|
114
|
+
const buckets = palette.map((p, position) => ({
|
|
115
|
+
...p,
|
|
116
|
+
position,
|
|
117
|
+
vector: vectorFor(p.hex),
|
|
118
|
+
quota: Math.floor((limit * p.proportion) / total),
|
|
119
|
+
remainder: ((limit * p.proportion) / total) % 1,
|
|
120
|
+
paints: [],
|
|
121
|
+
}));
|
|
122
|
+
let remaining = limit - buckets.reduce((sum, p) => sum + p.quota, 0);
|
|
123
|
+
for (const p of [...buckets].sort((a, b) => b.remainder - a.remainder || a.position - b.position))
|
|
124
|
+
if (remaining-- > 0)
|
|
125
|
+
p.quota++;
|
|
126
|
+
for (const paint of eligible) {
|
|
127
|
+
let best = buckets[0], score = embeddingCosineSimilarity(paint.vector, best.vector);
|
|
128
|
+
for (let i = 1; i < buckets.length; i++) {
|
|
129
|
+
const candidate = buckets[i], next = embeddingCosineSimilarity(paint.vector, candidate.vector);
|
|
130
|
+
if (next > score) {
|
|
131
|
+
best = candidate;
|
|
132
|
+
score = next;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
best.paints.push({ id: paint.variantId, score });
|
|
136
|
+
}
|
|
137
|
+
const groups = buckets
|
|
138
|
+
.sort((a, b) => b.proportion - a.proportion || a.position - b.position)
|
|
139
|
+
.map((p) => {
|
|
140
|
+
const ids = p.paints
|
|
141
|
+
.sort((a, b) => b.score - a.score || identityOrder(a.id, b.id))
|
|
142
|
+
.slice(0, p.quota)
|
|
143
|
+
.map((paint) => paint.id);
|
|
144
|
+
return {
|
|
145
|
+
hex: p.hex,
|
|
146
|
+
proportion: p.proportion,
|
|
147
|
+
quota: p.quota,
|
|
148
|
+
ids,
|
|
149
|
+
shortfall: p.quota - ids.length,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
const ids = groups.flatMap((p) => p.ids);
|
|
153
|
+
return {
|
|
154
|
+
ids,
|
|
155
|
+
groups,
|
|
156
|
+
limited: eligible.length > ids.length,
|
|
157
|
+
eligibleCount: eligible.length,
|
|
158
|
+
counts,
|
|
159
|
+
sourceVersion: cache.sourceVersion,
|
|
160
|
+
dictionaryVersion: cache.dictionaryVersion,
|
|
161
|
+
embeddingVersion: cache.embeddingVersion,
|
|
162
|
+
rankingVersion: cache.rankingVersion,
|
|
163
|
+
countBasis: "complete-catalogue",
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** A personal preference; diminished references stay on the research board. */
|
|
2
|
+
export type ScheduleReferenceInfluence = "kept" | "diminished";
|
|
3
|
+
export interface PutScheduleReferenceFeedback {
|
|
4
|
+
/** Reuse unchanged after an uncertain response; a different command needs a new id. */
|
|
5
|
+
requestId: string;
|
|
6
|
+
/** The version last read for this reference, or zero before its first decision. */
|
|
7
|
+
expectedFeedbackVersion: number;
|
|
8
|
+
sourceRunId: string;
|
|
9
|
+
influence: ScheduleReferenceInfluence;
|
|
10
|
+
}
|
|
11
|
+
export type ScheduleReferenceSource = {
|
|
12
|
+
kind: "image";
|
|
13
|
+
laneId: string;
|
|
14
|
+
imageRef: string;
|
|
15
|
+
sourceName: string;
|
|
16
|
+
sourceDomain: string;
|
|
17
|
+
url: string | null;
|
|
18
|
+
caption: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "web";
|
|
21
|
+
domain: string;
|
|
22
|
+
url: string;
|
|
23
|
+
statement: string;
|
|
24
|
+
implication: string;
|
|
25
|
+
};
|
|
26
|
+
/** Immutable receipt for one decision, including the exact research frame it came from. */
|
|
27
|
+
export interface ScheduleReferenceFeedback {
|
|
28
|
+
eventId: string;
|
|
29
|
+
scheduleId: string;
|
|
30
|
+
referenceId: string;
|
|
31
|
+
version: number;
|
|
32
|
+
requestId: string;
|
|
33
|
+
sourceRunId: string;
|
|
34
|
+
sourceEventSequence: number;
|
|
35
|
+
source: ScheduleReferenceSource;
|
|
36
|
+
scope: {
|
|
37
|
+
kind: "project";
|
|
38
|
+
};
|
|
39
|
+
originalVerdict: "kept" | "set-aside";
|
|
40
|
+
influence: ScheduleReferenceInfluence;
|
|
41
|
+
decidedByPrincipalId: string;
|
|
42
|
+
decidedAt: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ScheduleReferenceFeedbackList {
|
|
45
|
+
items: ScheduleReferenceFeedback[];
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HttpTransport } from "./http-transport.js";
|
|
2
|
+
import type { PutScheduleReferenceFeedback, ScheduleReferenceFeedback, ScheduleReferenceFeedbackList } from "./schedule-reference-types.js";
|
|
3
|
+
/** Schedule-owned preferences, independent of an issued document's revision. */
|
|
4
|
+
export declare class ScheduleReferencesClient {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(transport: HttpTransport);
|
|
7
|
+
/** Read the latest saved preference for each reference. Empty means no decisions yet. */
|
|
8
|
+
list(scheduleId: string, signal?: AbortSignal): Promise<ScheduleReferenceFeedbackList>;
|
|
9
|
+
/**
|
|
10
|
+
* Save or reverse a preference. An exact request replay returns its original receipt;
|
|
11
|
+
* it does not undo newer decisions. A stale reference version raises a 409.
|
|
12
|
+
*/
|
|
13
|
+
put(scheduleId: string, referenceId: string, input: PutScheduleReferenceFeedback, signal?: AbortSignal): Promise<ScheduleReferenceFeedback>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { resolveOperation } from "./operations.js";
|
|
2
|
+
/** Schedule-owned preferences, independent of an issued document's revision. */
|
|
3
|
+
export class ScheduleReferencesClient {
|
|
4
|
+
#transport;
|
|
5
|
+
constructor(transport) {
|
|
6
|
+
this.#transport = transport;
|
|
7
|
+
}
|
|
8
|
+
/** Read the latest saved preference for each reference. Empty means no decisions yet. */
|
|
9
|
+
list(scheduleId, signal) {
|
|
10
|
+
return this.#transport.requestJson(resolveOperation("list_schedule_reference_feedback"), {
|
|
11
|
+
pathParams: { scheduleId },
|
|
12
|
+
signal,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Save or reverse a preference. An exact request replay returns its original receipt;
|
|
17
|
+
* it does not undo newer decisions. A stale reference version raises a 409.
|
|
18
|
+
*/
|
|
19
|
+
put(scheduleId, referenceId, input, signal) {
|
|
20
|
+
return this.#transport.requestJson(resolveOperation("put_schedule_reference_feedback"), {
|
|
21
|
+
pathParams: { scheduleId, referenceId },
|
|
22
|
+
body: input,
|
|
23
|
+
signal,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
package/schedule-types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VerdictCitation } from "./citation-types.js";
|
|
1
2
|
/**
|
|
2
3
|
* Finish-schedule wire types (MG-616) — the hand-mirror of
|
|
3
4
|
* `@materialgraph/products/schemes/finish-schedule`. Kept in their own module
|
|
@@ -51,6 +52,8 @@ export interface FinishScheduleCell {
|
|
|
51
52
|
state: FinishScheduleCellState;
|
|
52
53
|
confidence: number | null;
|
|
53
54
|
sourceBasis: string | null;
|
|
55
|
+
/** Omitted when the exact current claim has no supporting source. */
|
|
56
|
+
citation?: VerdictCitation;
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
56
59
|
* The rolled-up grade for one row, from its EVALUATED criterion cells only.
|
|
@@ -59,6 +62,33 @@ export interface FinishScheduleCell {
|
|
|
59
62
|
* `hard_no` means a required cell failed on a real value.
|
|
60
63
|
*/
|
|
61
64
|
export type FinishScheduleGrade = "meets_all" | "close" | "hard_no";
|
|
65
|
+
/** Exact retained brief passage behind a schedule criterion. */
|
|
66
|
+
export interface FinishScheduleCriterionSourceRef {
|
|
67
|
+
sourceId: string;
|
|
68
|
+
quote: string;
|
|
69
|
+
textRange: {
|
|
70
|
+
start: number;
|
|
71
|
+
end: number;
|
|
72
|
+
unit: "utf16";
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Brief criterion carried into the issued row, including explicit exclusions. */
|
|
76
|
+
export type FinishScheduleCriterion = {
|
|
77
|
+
kind: "attribute" | "attribute-presence" | "attribute-exclusion" | "attribute-numeric" | "unresolved";
|
|
78
|
+
raw: string;
|
|
79
|
+
attributeId?: string;
|
|
80
|
+
term?: string;
|
|
81
|
+
comparator?: "at-least" | "at-most";
|
|
82
|
+
value?: number;
|
|
83
|
+
unit?: string;
|
|
84
|
+
polarity?: "exclusion";
|
|
85
|
+
coverageNote?: string;
|
|
86
|
+
suggestion?: {
|
|
87
|
+
attributeId: string;
|
|
88
|
+
term?: string;
|
|
89
|
+
};
|
|
90
|
+
sourceRefs?: FinishScheduleCriterionSourceRef[];
|
|
91
|
+
};
|
|
62
92
|
/** One numbered section of the schedule, and the heading it prints. */
|
|
63
93
|
export interface FinishScheduleSection {
|
|
64
94
|
/** The hundreds block, e.g. `100`. Also the base of every line number in it. */
|
|
@@ -147,6 +177,10 @@ export interface FinishScheduleOpenSlot {
|
|
|
147
177
|
}
|
|
148
178
|
/** One row: one slot, one specified material, fully graded. */
|
|
149
179
|
export interface FinishScheduleRow {
|
|
180
|
+
/** Criteria carried from the programme; absent on older schedule artifacts. */
|
|
181
|
+
criteria?: FinishScheduleCriterion[];
|
|
182
|
+
requiredCriteria?: FinishScheduleCriterion[];
|
|
183
|
+
preferredCriteria?: FinishScheduleCriterion[];
|
|
150
184
|
/**
|
|
151
185
|
* The printed line reference, e.g. `L-101` — how a drawing, an RFI or a
|
|
152
186
|
* substitution request names this line. Allocated from the Run's PROGRAMME
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/search-card-types
|
|
3
|
+
* @description The `view:"card"` search projection — the lean grid item and the
|
|
4
|
+
* envelope that carries it.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `types.ts` — which is over the repository's source-size limit —
|
|
7
|
+
* along the same seam `match-types.ts` took: one view of one surface, and
|
|
8
|
+
* nothing else. The full-view item and the shared envelope stay in `types.ts`,
|
|
9
|
+
* which this module reads `SearchResponse` from.
|
|
10
|
+
*/
|
|
11
|
+
import type { ProductId, VariantId } from "./identifiers.js";
|
|
12
|
+
import type { OpenEnum, SearchResponse } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* The lean `view:"card"` result item — identity + name + thumbnail + score.
|
|
15
|
+
*
|
|
16
|
+
* A PROJECTION, not a smaller answer: a field the card omits (palette, verdict,
|
|
17
|
+
* signals, availability) is unread here, never known-absent. Re-read the same
|
|
18
|
+
* `variantId` at `view: "full"` or via `material()` before claiming anything the
|
|
19
|
+
* card does not carry.
|
|
20
|
+
*/
|
|
21
|
+
export interface SearchCardItem {
|
|
22
|
+
/** Accepted sampleability fact. Absent means unknown, never false. */
|
|
23
|
+
sampleable?: boolean;
|
|
24
|
+
/** Opaque — round-trip verbatim. */
|
|
25
|
+
variantId: VariantId;
|
|
26
|
+
productId: ProductId | null;
|
|
27
|
+
brandSlug: string | null;
|
|
28
|
+
brand: string;
|
|
29
|
+
/** Product name (the full item's `product`). */
|
|
30
|
+
name: string;
|
|
31
|
+
variantName: string | null;
|
|
32
|
+
imageUrl: string | null;
|
|
33
|
+
projectSaveCapability?: "allowed";
|
|
34
|
+
sourceStatus?: "provisional";
|
|
35
|
+
score: number;
|
|
36
|
+
/**
|
|
37
|
+
* Resolved values for the attribute ids sent in `returnAttributes`, keyed by
|
|
38
|
+
* attribute id. ABSENT entirely when `returnAttributes` was not sent.
|
|
39
|
+
*
|
|
40
|
+
* Three states, all different, and reading them as two is the mistake this
|
|
41
|
+
* shape exists to prevent: no `attributes` key means you did not ask; a key
|
|
42
|
+
* whose value is `null` means this material carries no accepted value for that
|
|
43
|
+
* attribute; anything else is what the graph holds. Values resolve with the
|
|
44
|
+
* normal variant-over-product precedence, and an array is a multi-valued
|
|
45
|
+
* attribute rather than a list of candidates.
|
|
46
|
+
*/
|
|
47
|
+
attributes?: Record<string, string | string[] | null>;
|
|
48
|
+
}
|
|
49
|
+
/** The `view:"card"` response form — the lean grid projection of {@link SearchResponse}. */
|
|
50
|
+
export interface SearchCardResponse extends Omit<SearchResponse, "view" | "results"> {
|
|
51
|
+
view: OpenEnum<"card">;
|
|
52
|
+
results: SearchCardItem[];
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -56,6 +56,11 @@ export type SearchFilter = {
|
|
|
56
56
|
attributeId: string;
|
|
57
57
|
term: string;
|
|
58
58
|
trust?: SearchFilterTrust;
|
|
59
|
+
} | {
|
|
60
|
+
/** Known effective values are required, and this term must be absent. */
|
|
61
|
+
attributeId: string;
|
|
62
|
+
excludesTerm: string;
|
|
63
|
+
trust?: SearchFilterTrust;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* TERM SET — the attribute must equal any ONE of these. OR within the entry;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** samplizeCatalogue admits published US Samplize paint colours, regardless of stock. */
|
|
1
2
|
/**
|
|
2
3
|
* @module search-request-types
|
|
3
4
|
* The two request shapes for the `/search` surface — the POST JSON body and the
|
|
@@ -148,6 +149,8 @@ export interface SearchInput {
|
|
|
148
149
|
refinementToken?: string;
|
|
149
150
|
/** Only return materials with at least one catalog image. Default false (full catalog). */
|
|
150
151
|
hasImages?: boolean;
|
|
152
|
+
/** Require a recorded accepted sampleable=true fact; unknown and false are excluded. */
|
|
153
|
+
sampleableOnly?: boolean;
|
|
151
154
|
/**
|
|
152
155
|
* Opt-in seed-relative verdict cells (MG-629). Only meaningful with
|
|
153
156
|
* `similarTo: { variantId }`: each hit's `verdict` gains `preferred` cells
|
|
@@ -227,6 +230,8 @@ export interface SearchGetParams {
|
|
|
227
230
|
count?: OpenEnum<"true">;
|
|
228
231
|
/** "true" — only return materials with at least one catalog image. */
|
|
229
232
|
hasImages?: OpenEnum<"true">;
|
|
233
|
+
/** Require a recorded accepted sampleable=true fact. */
|
|
234
|
+
sampleableOnly?: OpenEnum<"true" | "false">;
|
|
230
235
|
/** Result projection — `"full"` (default) or the lean `"card"` grid form. */
|
|
231
236
|
view?: OpenEnum<"full" | "card">;
|
|
232
237
|
/**
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
* receipts emitted by ordinary Search responses.
|
|
5
5
|
*/
|
|
6
6
|
import type { OpenEnum } from "./types.js";
|
|
7
|
+
/** Inspectable metadata for an opaque, principal-bound search refinement receipt. */
|
|
8
|
+
export interface SearchRefinementReceipt {
|
|
9
|
+
token: string;
|
|
10
|
+
expiresAt: string;
|
|
11
|
+
poolSize: number;
|
|
12
|
+
schemaVersion: string;
|
|
13
|
+
projectionVersion: number;
|
|
14
|
+
}
|
|
7
15
|
/** The primary ranking source a query resolved to and reports as its mode. */
|
|
8
16
|
export type SearchMode = OpenEnum<"image" | "colour" | "text" | "lexical" | "entity-browse">;
|
|
9
17
|
/**
|
|
@@ -143,6 +143,15 @@ function checkAttributeVerdict(value, what, fail) {
|
|
|
143
143
|
requireNullableString(record.value, `${what}.value`, fail);
|
|
144
144
|
requireNullableBoundedNumber(record.confidence, `${what}.confidence`, fail, 0, 1);
|
|
145
145
|
requireNullableString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
146
|
+
if (record.citation !== undefined) {
|
|
147
|
+
const citation = requireRecord(record.citation, `${what}.citation`, fail);
|
|
148
|
+
requireNonEmptyString(citation.citationId, `${what}.citation.citationId`, fail);
|
|
149
|
+
requireNonEmptyString(citation.sourceType, `${what}.citation.sourceType`, fail);
|
|
150
|
+
const count = requireNumber(citation.count, `${what}.citation.count`, fail);
|
|
151
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
152
|
+
throw fail(`${what}.citation.count must be a positive integer`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
146
155
|
const deviation = record.deviation;
|
|
147
156
|
if (deviation !== null)
|
|
148
157
|
checkVerdictDeviation(deviation, `${what}.deviation`, fail);
|
|
@@ -207,6 +216,7 @@ export function checkSearchResultItem(value, what, fail) {
|
|
|
207
216
|
if (record.signals !== undefined)
|
|
208
217
|
checkSignals(record.signals, `${what}.signals`, fail);
|
|
209
218
|
requireOptionalBoolean(record.retired, `${what}.retired`, fail);
|
|
219
|
+
requireOptionalBoolean(record.sampleable, `${what}.sampleable`, fail);
|
|
210
220
|
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
211
221
|
requireOptionalBoolean(record.admittedOnColour, `${what}.admittedOnColour`, fail);
|
|
212
222
|
if (record.siblingCount !== undefined) {
|
package/slot-pool-types.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* TOLERANT-PARSING CONTRACT: response enums are widened with {@link OpenEnum}
|
|
26
26
|
* so a new server value still type-checks; objects are read structurally.
|
|
27
27
|
*/
|
|
28
|
+
import type { VerdictCitation } from "./citation-types.js";
|
|
28
29
|
import type { VariantId } from "./identifiers.js";
|
|
29
30
|
import type { OpenEnum } from "./types.js";
|
|
30
31
|
/**
|
|
@@ -147,6 +148,7 @@ export interface SlotPoolVerdictCell {
|
|
|
147
148
|
*/
|
|
148
149
|
sourceBasis: string | null;
|
|
149
150
|
trustGap: OpenEnum<"below_confidence" | "basis_unmet" | "proposed"> | null;
|
|
151
|
+
citation?: VerdictCitation;
|
|
150
152
|
}
|
|
151
153
|
/** Why a candidate is labelled. An eligible candidate carries none of these. */
|
|
152
154
|
export type SlotPoolFlag = OpenEnum<"below-floor" | "required-not-applied" | "discontinued" | "superseded" | "unverified-value">;
|
package/spec-types.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export interface SpecTermFilter {
|
|
|
64
64
|
attributeId: string;
|
|
65
65
|
term: string;
|
|
66
66
|
}
|
|
67
|
+
/** A negative spec criterion; absence of a value remains unproven. */
|
|
68
|
+
export interface SpecExclusionFilter {
|
|
69
|
+
attributeId: string;
|
|
70
|
+
excludesTerm: string;
|
|
71
|
+
}
|
|
67
72
|
/**
|
|
68
73
|
* A presence spec criterion: the attribute must have ANY accepted value —
|
|
69
74
|
* "has an EPD on record", "has a measured slip figure at all". Not supported for
|
|
@@ -94,4 +99,4 @@ export interface CertificationProgrammeFilter {
|
|
|
94
99
|
* presence check. The union the search engine applies as a restriction BEFORE
|
|
95
100
|
* ranking. Identical in shape to the SDK's `SearchFilter`.
|
|
96
101
|
*/
|
|
97
|
-
export type SpecFilter = SpecTermFilter | SpecPresenceFilter;
|
|
102
|
+
export type SpecFilter = SpecTermFilter | SpecExclusionFilter | SpecPresenceFilter;
|
package/specifications.d.ts
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* hand-format the `attribute:value` string the `bySpec` route splits on its
|
|
15
15
|
* first `:`.
|
|
16
16
|
*/
|
|
17
|
-
import { type CertificationProgrammeFilter, type SpecPresenceFilter, type SpecTermFilter } from "./spec-types.js";
|
|
17
|
+
import { type CertificationProgrammeFilter, type SpecExclusionFilter, type SpecPresenceFilter, type SpecTermFilter } from "./spec-types.js";
|
|
18
|
+
/** A hard criterion excluding one known canonical value while retaining only variants with known values. */
|
|
19
|
+
export declare function excludeSpec(attributeId: string, excludesTerm: string): SpecExclusionFilter;
|
|
18
20
|
/**
|
|
19
21
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
20
22
|
* carry this canonical value.
|
package/specifications.js
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* first `:`.
|
|
16
16
|
*/
|
|
17
17
|
import { CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, } from "./spec-types.js";
|
|
18
|
+
/** A hard criterion excluding one known canonical value while retaining only variants with known values. */
|
|
19
|
+
export function excludeSpec(attributeId, excludesTerm) {
|
|
20
|
+
return { attributeId, excludesTerm };
|
|
21
|
+
}
|
|
18
22
|
/**
|
|
19
23
|
* A term-equality criterion for `search({ filters })` — the attribute must
|
|
20
24
|
* carry this canonical value.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { BySpecVerdict } from "./by-spec-types.js";
|
|
2
|
+
import type { VariantId } from "./identifiers.js";
|
|
3
|
+
import type { MaterialSuccessor } from "./material-types.js";
|
|
4
|
+
export type SubstituteSignatureFamily = "textile" | "tile" | "coating" | "lighting_electrical" | "acoustic" | "surface" | "furniture" | "sanitaryware" | "fittings" | "architectural_hardware";
|
|
5
|
+
export interface SubstituteSignatureCriterion {
|
|
6
|
+
role: "primary_material" | "performance";
|
|
7
|
+
attributeId: string;
|
|
8
|
+
term: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AvailableSubstituteSignature {
|
|
11
|
+
family: SubstituteSignatureFamily;
|
|
12
|
+
form: string;
|
|
13
|
+
criteria: [SubstituteSignatureCriterion, SubstituteSignatureCriterion];
|
|
14
|
+
}
|
|
15
|
+
export interface UnsupportedSubstituteSignature {
|
|
16
|
+
family: SubstituteSignatureFamily;
|
|
17
|
+
form: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}
|
|
20
|
+
export interface InsufficientSubstituteSignature {
|
|
21
|
+
family: SubstituteSignatureFamily;
|
|
22
|
+
form: string;
|
|
23
|
+
missingRoles: ("primary_material" | "performance")[];
|
|
24
|
+
}
|
|
25
|
+
export interface FindSubstitutesOptions {
|
|
26
|
+
excludeBrands?: string[];
|
|
27
|
+
diversityBoost?: number;
|
|
28
|
+
limit?: number;
|
|
29
|
+
cursor?: string;
|
|
30
|
+
includeSamplingArtifacts?: boolean;
|
|
31
|
+
includeUnavailable?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface SubstituteMatchedCriterion {
|
|
34
|
+
attributeId: string;
|
|
35
|
+
confidence: number | null;
|
|
36
|
+
sourceBasis: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface SubstituteResultItem {
|
|
39
|
+
variantId: VariantId;
|
|
40
|
+
brandSlug: string;
|
|
41
|
+
brand: string;
|
|
42
|
+
product: string;
|
|
43
|
+
variant: string;
|
|
44
|
+
color: string | null;
|
|
45
|
+
imageUrl: string | null;
|
|
46
|
+
hex: string | null;
|
|
47
|
+
matched: SubstituteMatchedCriterion[];
|
|
48
|
+
minConfidence: number | null;
|
|
49
|
+
baseScore: number;
|
|
50
|
+
diversityAdjustment: number;
|
|
51
|
+
score: number;
|
|
52
|
+
verdict: BySpecVerdict;
|
|
53
|
+
}
|
|
54
|
+
export interface AvailableSubstitutesResponse {
|
|
55
|
+
status: "available";
|
|
56
|
+
signatureUsed: AvailableSubstituteSignature;
|
|
57
|
+
items: SubstituteResultItem[];
|
|
58
|
+
candidatePoolSize: number;
|
|
59
|
+
droppedUnavailable: number;
|
|
60
|
+
/**
|
|
61
|
+
* MG-1686, MG-1783. True when the candidate read hit the bounded pool ceiling,
|
|
62
|
+
* so more materials match the signature than were ranked. A cut pool is an
|
|
63
|
+
* even SAMPLE across the id keyspace rather than its lowest window, so the
|
|
64
|
+
* ranked set is representative of the match set without being all of it —
|
|
65
|
+
* treat the items as the best of a sample, never as the best that exist. A cut
|
|
66
|
+
* pool that produced NO items answers `inconclusive` instead, so `available`
|
|
67
|
+
* with an empty `items` always means a complete search that found nothing.
|
|
68
|
+
*/
|
|
69
|
+
poolTruncated: boolean;
|
|
70
|
+
supersededBy: MaterialSuccessor | null;
|
|
71
|
+
nextCursor?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* MG-1686. The search read a bounded window of the matches and nothing in it
|
|
75
|
+
* reached the page, while more matches existed past the window. It is not an
|
|
76
|
+
* answer of "nothing can replace this" — that answer is `available` with an
|
|
77
|
+
* empty `items`. `reason` names what happened and the next move; follow
|
|
78
|
+
* `nextCursor` when it is present.
|
|
79
|
+
*/
|
|
80
|
+
export interface InconclusiveSubstitutesResponse {
|
|
81
|
+
status: "inconclusive";
|
|
82
|
+
signatureUsed: AvailableSubstituteSignature;
|
|
83
|
+
items: [];
|
|
84
|
+
candidatePoolSize: number;
|
|
85
|
+
droppedUnavailable: number;
|
|
86
|
+
reason: string;
|
|
87
|
+
supersededBy: MaterialSuccessor | null;
|
|
88
|
+
nextCursor?: string;
|
|
89
|
+
}
|
|
90
|
+
export interface UnsupportedSubstitutesResponse {
|
|
91
|
+
status: "unsupported";
|
|
92
|
+
signature: UnsupportedSubstituteSignature;
|
|
93
|
+
items: [];
|
|
94
|
+
supersededBy: MaterialSuccessor | null;
|
|
95
|
+
}
|
|
96
|
+
export interface InsufficientSubstitutesResponse {
|
|
97
|
+
status: "insufficient_signature";
|
|
98
|
+
signature: InsufficientSubstituteSignature;
|
|
99
|
+
items: [];
|
|
100
|
+
supersededBy: MaterialSuccessor | null;
|
|
101
|
+
}
|
|
102
|
+
export type FindSubstitutesResponse = AvailableSubstitutesResponse | InconclusiveSubstitutesResponse | UnsupportedSubstitutesResponse | InsufficientSubstitutesResponse;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Exact, source-path-grained status coverage for primary product typing. */
|
|
2
|
+
export interface ProductTypeStatusCoverageCounts {
|
|
3
|
+
variants: number;
|
|
4
|
+
accepted: number;
|
|
5
|
+
proposed: number;
|
|
6
|
+
proposedBelowThreshold: number;
|
|
7
|
+
/** Proposed rows with no confidence remain explicit rather than thresholded. */
|
|
8
|
+
proposedUnknownConfidence: number;
|
|
9
|
+
otherStatus: number;
|
|
10
|
+
missingPrimary: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ProductTypeStatusCoverageSourcePath extends ProductTypeStatusCoverageCounts {
|
|
13
|
+
/** Complete stored source path; null means the product retained no path. */
|
|
14
|
+
sourcePath: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** Current primary-assignment coverage, never an inferred acceptance decision. */
|
|
17
|
+
export interface ProductTypeStatusCoverage {
|
|
18
|
+
computedAt: string;
|
|
19
|
+
basis: {
|
|
20
|
+
count: "exact";
|
|
21
|
+
universe: "non_superseded_variants";
|
|
22
|
+
assignment: "variant_primary_only";
|
|
23
|
+
sourcePath: "products.source_category_path";
|
|
24
|
+
/** Reporting cutoff only: higher-confidence proposals remain proposed. */
|
|
25
|
+
comparisonThreshold: number;
|
|
26
|
+
};
|
|
27
|
+
totals: ProductTypeStatusCoverageCounts;
|
|
28
|
+
sourcePaths: ProductTypeStatusCoverageSourcePath[];
|
|
29
|
+
}
|
|
30
|
+
export interface ProductTypeCoverageOptions {
|
|
31
|
+
[key: string]: number | undefined;
|
|
32
|
+
/** Reporting cutoff in [0,1], default 0.85; it never accepts a proposal. */
|
|
33
|
+
comparisonThreshold?: number;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|