@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,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finish-schedule wire types (MG-616) — the hand-mirror of
|
|
3
|
+
* `@materialgraph/products/schemes/finish-schedule`. Kept in their own module
|
|
4
|
+
* rather than `types.ts`, which sits at its source-size baseline.
|
|
5
|
+
*
|
|
6
|
+
* A finish schedule is a completed compose scheme rendered as ONE ROW PER SLOT:
|
|
7
|
+
* the shape a specifier issues. The contract that makes it trustworthy is that
|
|
8
|
+
* every row carries the IDENTICAL, fixed column set, and every attribute cell
|
|
9
|
+
* states its own verification alongside its value. An unknown is an explicit
|
|
10
|
+
* `missing` cell with a null value — never a blank that reads as complete.
|
|
11
|
+
*/
|
|
12
|
+
/** One schedule column: the attribute and its header text. */
|
|
13
|
+
export interface FinishScheduleColumn {
|
|
14
|
+
attributeId: string;
|
|
15
|
+
/** The registry presentation label. */
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The six verification states a schedule cell can report, mirroring the shared
|
|
20
|
+
* verdict contract. On this public surface a `proposed` (pending-review) value
|
|
21
|
+
* is projected to `missing`, so it can never be told apart from a value that
|
|
22
|
+
* never existed.
|
|
23
|
+
*/
|
|
24
|
+
export type FinishScheduleCellState = "matched" | "deviated" | "failed" | "unverified" | "missing" | "not_evaluated";
|
|
25
|
+
/**
|
|
26
|
+
* One attribute cell of one row: the value AND how far it can be trusted.
|
|
27
|
+
*
|
|
28
|
+
* Columns the Run's criteria evaluated carry the real verdict state. Columns
|
|
29
|
+
* backfilled from the catalogue (nothing in the brief asked about them) only
|
|
30
|
+
* ever read `matched` or `missing`: they assert presence-with-trust, not that
|
|
31
|
+
* the brief was met.
|
|
32
|
+
*/
|
|
33
|
+
export interface FinishScheduleCell {
|
|
34
|
+
attributeId: string;
|
|
35
|
+
label: string;
|
|
36
|
+
/** The display value, or null when unknown. */
|
|
37
|
+
value: string | null;
|
|
38
|
+
state: FinishScheduleCellState;
|
|
39
|
+
confidence: number | null;
|
|
40
|
+
sourceBasis: string | null;
|
|
41
|
+
}
|
|
42
|
+
/** The rolled-up grade for one row, from its evaluated criterion cells only. */
|
|
43
|
+
export type FinishScheduleGrade = "meets_all" | "close" | "hard_no";
|
|
44
|
+
/** One row: one slot, one specified material, fully graded. */
|
|
45
|
+
export interface FinishScheduleRow {
|
|
46
|
+
slotId: string;
|
|
47
|
+
slotName: string;
|
|
48
|
+
/** The space NAME, or null for a spaceless slot. */
|
|
49
|
+
space: string | null;
|
|
50
|
+
element: string | null;
|
|
51
|
+
/** True when the designer pinned this pick rather than the engine choosing it. */
|
|
52
|
+
anchored: boolean;
|
|
53
|
+
brand: string;
|
|
54
|
+
product: string;
|
|
55
|
+
variant: string;
|
|
56
|
+
variantId: string;
|
|
57
|
+
/** Relative path to the public material record (`/materials/{variantId}`). */
|
|
58
|
+
recordUrl: string;
|
|
59
|
+
imageUrl: string | null;
|
|
60
|
+
hex: string | null;
|
|
61
|
+
/** Always the full, identically-ordered column set. */
|
|
62
|
+
attributes: FinishScheduleCell[];
|
|
63
|
+
fitNotes: string[];
|
|
64
|
+
grade: FinishScheduleGrade;
|
|
65
|
+
}
|
|
66
|
+
/** A whole composed scheme as an issuable finish schedule. */
|
|
67
|
+
export interface FinishSchedule {
|
|
68
|
+
runId: string;
|
|
69
|
+
schemeId: string;
|
|
70
|
+
title: string;
|
|
71
|
+
/** The compose contract version stamped on the Run, or `"unknown"`. */
|
|
72
|
+
composeVersion: string;
|
|
73
|
+
/** ISO timestamp the export was compiled at. */
|
|
74
|
+
generatedAt: string;
|
|
75
|
+
/** The stable column order every row's `attributes` array follows. */
|
|
76
|
+
columns: FinishScheduleColumn[];
|
|
77
|
+
rows: FinishScheduleRow[];
|
|
78
|
+
}
|
|
79
|
+
/** `GET /api/v1/runs/{runId}/schedule`. */
|
|
80
|
+
export interface FinishScheduleResponse {
|
|
81
|
+
schedule: FinishSchedule;
|
|
82
|
+
}
|
|
83
|
+
/** Options for {@link getRunSchedule}. */
|
|
84
|
+
export interface GetRunScheduleOptions {
|
|
85
|
+
/** Which composed scheme to export; defaults to the Run's first scheme. */
|
|
86
|
+
schemeId?: string;
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finish-schedule wire types (MG-616) — the hand-mirror of
|
|
3
|
+
* `@materialgraph/products/schemes/finish-schedule`. Kept in their own module
|
|
4
|
+
* rather than `types.ts`, which sits at its source-size baseline.
|
|
5
|
+
*
|
|
6
|
+
* A finish schedule is a completed compose scheme rendered as ONE ROW PER SLOT:
|
|
7
|
+
* the shape a specifier issues. The contract that makes it trustworthy is that
|
|
8
|
+
* every row carries the IDENTICAL, fixed column set, and every attribute cell
|
|
9
|
+
* states its own verification alongside its value. An unknown is an explicit
|
|
10
|
+
* `missing` cell with a null value — never a blank that reads as complete.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
/** Types for structured refinements returned by MaterialGraph search. */
|
|
2
2
|
import type { SearchInput } from "./types.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* A hard canonical-attribute filter accepted by the search engine — either a
|
|
5
|
+
* term match (`{ attributeId, term }`) or a presence check
|
|
6
|
+
* (`{ attributeId, hasValue: true }`). Applied as a restriction BEFORE ranking.
|
|
7
|
+
*
|
|
8
|
+
* The `attributeId` may be a real registry id (`"fire_classifications"`,
|
|
9
|
+
* `"environmental_product_declaration"`) OR the virtual
|
|
10
|
+
* `"certification_programme"` (see `CERTIFICATION_PROGRAMME_ATTRIBUTE_ID`),
|
|
11
|
+
* which matches a programme — GREENGUARD, FloorScore, LEED, an EPD — wherever it
|
|
12
|
+
* landed across the four certification buckets. Presence (`hasValue`) is NOT
|
|
13
|
+
* supported for the virtual programme attribute. The typed `spec-types` mirror
|
|
14
|
+
* and the `specifications` builders produce values assignable to this type.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Optional per-filter TRUST bar the matching value must clear. `minConfidence` is
|
|
18
|
+
* an explicit confidence floor; `sourceBasis` demands an EXACT source basis (closed
|
|
19
|
+
* enum — an unknown basis is rejected); `meetsRequirement` folds the attribute's
|
|
20
|
+
* registry confidence requirement server-side.
|
|
21
|
+
*/
|
|
22
|
+
export interface SearchFilterTrust {
|
|
23
|
+
minConfidence?: number;
|
|
24
|
+
sourceBasis?: "catalog_feed" | "declared" | "derived" | "tested";
|
|
25
|
+
meetsRequirement?: true;
|
|
26
|
+
}
|
|
4
27
|
export type SearchFilter = {
|
|
5
28
|
attributeId: string;
|
|
6
29
|
term: string;
|
|
30
|
+
trust?: SearchFilterTrust;
|
|
7
31
|
} | {
|
|
8
32
|
attributeId: string;
|
|
9
33
|
hasValue: true;
|
|
34
|
+
trust?: SearchFilterTrust;
|
|
10
35
|
};
|
|
11
36
|
/** One structured refinement returned with a search response. */
|
|
12
37
|
export interface SearchSuggestion {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ValidationFailure } from "./validation.js";
|
|
2
|
+
/** `verdictStateSchema` — the six live cell states. */
|
|
3
|
+
export declare const VERDICT_STATES: readonly ["matched", "deviated", "failed", "unverified", "missing", "not_evaluated"];
|
|
4
|
+
/** `attributeVerdictSchema.strength`. */
|
|
5
|
+
export declare const VERDICT_STRENGTHS: readonly ["required", "preferred"];
|
|
6
|
+
/** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
|
|
7
|
+
export declare const VERDICT_BASELINE_KINDS: readonly ["term", "presence", "seed", "image"];
|
|
8
|
+
/** The `image` baseline arm's own `source` enum. */
|
|
9
|
+
export declare const VERDICT_IMAGE_SOURCES: readonly ["variant", "vector"];
|
|
10
|
+
/** `verdictDeviationSchema.direction`. */
|
|
11
|
+
export declare const VERDICT_DEVIATION_DIRECTIONS: readonly ["above", "below", "other"];
|
|
12
|
+
/** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
|
|
13
|
+
export declare const VERDICT_TRUST_GAPS: readonly ["below_confidence", "basis_unmet", "proposed"];
|
|
14
|
+
/** `verdictGradeSchema` — the rolled-up card grade. */
|
|
15
|
+
export declare const VERDICT_GRADES: readonly ["meets_all", "close", "hard_no"];
|
|
16
|
+
/** `visualMeaningSchema.basis` — where a meaning came from. */
|
|
17
|
+
export declare const VISUAL_MEANING_BASES: readonly ["canonical_attribute", "image_colour_analysis"];
|
|
18
|
+
/**
|
|
19
|
+
* Prove one wire value is a {@link SearchResultItem}, raising through `fail` at
|
|
20
|
+
* the first field that does not match `searchResultItemSchema`. Checks the shape
|
|
21
|
+
* only — unknown keys ride through untouched, per the SDK's pass-through
|
|
22
|
+
* contract (nothing on the emit path parses, so stripping would invent a
|
|
23
|
+
* normalisation the wire never had).
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkSearchResultItem(value: unknown, what: string, fail: ValidationFailure): void;
|
|
26
|
+
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
27
|
+
export declare function checkSearchResultItems(items: readonly unknown[], what: string, fail: ValidationFailure): void;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { requireArray, requireBoolean, requireEnum, requireNonEmptyString, requireNonEmptyStringArray, requireNullableBoundedNumber, requireNullableEnum, requireNullableNonEmptyString, requireNullableNumber, requireNullableString, requireNumber, requireOptionalBoolean, requireOptionalNumber, requireOptionalString, requireRecord, requireString, requireIntegerAtLeast, } from "./validation.js";
|
|
2
|
+
// ── Closed enum members (canonical `z.enum` mirrors) ─────────────────────────
|
|
3
|
+
/** `verdictStateSchema` — the six live cell states. */
|
|
4
|
+
export const VERDICT_STATES = [
|
|
5
|
+
"matched",
|
|
6
|
+
"deviated",
|
|
7
|
+
"failed",
|
|
8
|
+
"unverified",
|
|
9
|
+
"missing",
|
|
10
|
+
"not_evaluated",
|
|
11
|
+
];
|
|
12
|
+
/** `attributeVerdictSchema.strength`. */
|
|
13
|
+
export const VERDICT_STRENGTHS = ["required", "preferred"];
|
|
14
|
+
/** `verdictBaselineSchema` discriminant — the four arms of what a cell was judged against. */
|
|
15
|
+
export const VERDICT_BASELINE_KINDS = ["term", "presence", "seed", "image"];
|
|
16
|
+
/** The `image` baseline arm's own `source` enum. */
|
|
17
|
+
export const VERDICT_IMAGE_SOURCES = ["variant", "vector"];
|
|
18
|
+
/** `verdictDeviationSchema.direction`. */
|
|
19
|
+
export const VERDICT_DEVIATION_DIRECTIONS = ["above", "below", "other"];
|
|
20
|
+
/** `verdictTrustGapSchema` — why a real value did not clear the trust bar. */
|
|
21
|
+
export const VERDICT_TRUST_GAPS = ["below_confidence", "basis_unmet", "proposed"];
|
|
22
|
+
/** `verdictGradeSchema` — the rolled-up card grade. */
|
|
23
|
+
export const VERDICT_GRADES = ["meets_all", "close", "hard_no"];
|
|
24
|
+
/** `visualMeaningSchema.basis` — where a meaning came from. */
|
|
25
|
+
export const VISUAL_MEANING_BASES = ["canonical_attribute", "image_colour_analysis"];
|
|
26
|
+
// ── Leaf checks ──────────────────────────────────────────────────────────────
|
|
27
|
+
/** `palette[]` — `{ hex, proportion, name: string.nullable().optional() }`. */
|
|
28
|
+
function checkPaletteSwatch(value, what, fail) {
|
|
29
|
+
const record = requireRecord(value, what, fail);
|
|
30
|
+
requireString(record.hex, `${what}.hex`, fail);
|
|
31
|
+
requireNumber(record.proportion, `${what}.proportion`, fail);
|
|
32
|
+
// `.nullable().optional()` — absent OR null OR a string.
|
|
33
|
+
if (record.name !== undefined)
|
|
34
|
+
requireNullableString(record.name, `${what}.name`, fail);
|
|
35
|
+
}
|
|
36
|
+
/** `colourMeta` — three independently optional strings. */
|
|
37
|
+
function checkColourMeta(value, what, fail) {
|
|
38
|
+
const record = requireRecord(value, what, fail);
|
|
39
|
+
requireOptionalString(record.mood, `${what}.mood`, fail);
|
|
40
|
+
requireOptionalString(record.dominantFamily, `${what}.dominantFamily`, fail);
|
|
41
|
+
requireOptionalString(record.dominantHex, `${what}.dominantHex`, fail);
|
|
42
|
+
}
|
|
43
|
+
/** `searchResultSignalsSchema` — five nullable numbers, all required keys. */
|
|
44
|
+
function checkSignals(value, what, fail) {
|
|
45
|
+
const record = requireRecord(value, what, fail);
|
|
46
|
+
for (const signal of ["image", "category", "colour", "lexical", "text"]) {
|
|
47
|
+
requireNullableNumber(record[signal], `${what}.${signal}`, fail);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** `visualMeaningSchema` — note the NON-EMPTY `values` array and the closed `basis`. */
|
|
51
|
+
function checkVisualMeaning(value, what, fail) {
|
|
52
|
+
const record = requireRecord(value, what, fail);
|
|
53
|
+
requireString(record.id, `${what}.id`, fail);
|
|
54
|
+
requireString(record.label, `${what}.label`, fail);
|
|
55
|
+
requireNonEmptyStringArray(record.values, `${what}.values`, fail);
|
|
56
|
+
requireEnum(record.basis, VISUAL_MEANING_BASES, `${what}.basis`, fail);
|
|
57
|
+
requireOptionalNumber(record.confidence, `${what}.confidence`, fail);
|
|
58
|
+
requireOptionalString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
59
|
+
requireOptionalString(record.version, `${what}.version`, fail);
|
|
60
|
+
requireOptionalString(record.appliedAt, `${what}.appliedAt`, fail);
|
|
61
|
+
}
|
|
62
|
+
// ── Verdict subtree ──────────────────────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* `verdictBaselineSchema` — a discriminated union on `kind`. An unknown
|
|
65
|
+
* discriminant is rejected, exactly as `z.discriminatedUnion` rejects it, and
|
|
66
|
+
* each arm carries only the fields it needs.
|
|
67
|
+
*/
|
|
68
|
+
function checkVerdictBaseline(value, what, fail) {
|
|
69
|
+
const record = requireRecord(value, what, fail);
|
|
70
|
+
const kind = requireEnum(record.kind, VERDICT_BASELINE_KINDS, `${what}.kind`, fail);
|
|
71
|
+
switch (kind) {
|
|
72
|
+
case "term":
|
|
73
|
+
requireNonEmptyString(record.term, `${what}.term`, fail);
|
|
74
|
+
return;
|
|
75
|
+
case "presence":
|
|
76
|
+
return;
|
|
77
|
+
case "seed":
|
|
78
|
+
requireNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
79
|
+
return;
|
|
80
|
+
default:
|
|
81
|
+
// `image` — the like-this-picture arm.
|
|
82
|
+
requireEnum(record.source, VERDICT_IMAGE_SOURCES, `${what}.source`, fail);
|
|
83
|
+
requireNullableNonEmptyString(record.variantId, `${what}.variantId`, fail);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** `verdictDeviationSchema` — a named near-miss. */
|
|
87
|
+
function checkVerdictDeviation(value, what, fail) {
|
|
88
|
+
const record = requireRecord(value, what, fail);
|
|
89
|
+
requireEnum(record.direction, VERDICT_DEVIATION_DIRECTIONS, `${what}.direction`, fail);
|
|
90
|
+
requireNullableNumber(record.delta, `${what}.delta`, fail);
|
|
91
|
+
requireNullableString(record.unit, `${what}.unit`, fail);
|
|
92
|
+
requireNonEmptyString(record.label, `${what}.label`, fail);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `attributeVerdictSchema` — nine fields plus the TWO cross-field invariants its
|
|
96
|
+
* `.superRefine()` enforces. Those are relational rather than field-wise, which
|
|
97
|
+
* is no obstacle to a hand-rolled validator: they are two conditionals, and the
|
|
98
|
+
* schema's own doc calls them out as the reason a malformed cell can never reach
|
|
99
|
+
* a consumer.
|
|
100
|
+
*/
|
|
101
|
+
function checkAttributeVerdict(value, what, fail) {
|
|
102
|
+
const record = requireRecord(value, what, fail);
|
|
103
|
+
requireNonEmptyString(record.attributeId, `${what}.attributeId`, fail);
|
|
104
|
+
const state = requireEnum(record.state, VERDICT_STATES, `${what}.state`, fail);
|
|
105
|
+
requireEnum(record.strength, VERDICT_STRENGTHS, `${what}.strength`, fail);
|
|
106
|
+
checkVerdictBaseline(record.baseline, `${what}.baseline`, fail);
|
|
107
|
+
requireNullableString(record.value, `${what}.value`, fail);
|
|
108
|
+
requireNullableBoundedNumber(record.confidence, `${what}.confidence`, fail, 0, 1);
|
|
109
|
+
requireNullableString(record.sourceBasis, `${what}.sourceBasis`, fail);
|
|
110
|
+
const deviation = record.deviation;
|
|
111
|
+
if (deviation !== null)
|
|
112
|
+
checkVerdictDeviation(deviation, `${what}.deviation`, fail);
|
|
113
|
+
const trustGap = requireNullableEnum(record.trustGap, VERDICT_TRUST_GAPS, `${what}.trustGap`, fail);
|
|
114
|
+
// superRefine invariant 1 — a deviation only describes a `deviated` cell.
|
|
115
|
+
if (deviation !== null && state !== "deviated") {
|
|
116
|
+
throw fail(`${what}.deviation is set on a cell whose state is "${state}", not "deviated"`);
|
|
117
|
+
}
|
|
118
|
+
// superRefine invariant 2 — a trust gap only explains an `unverified` cell.
|
|
119
|
+
if (trustGap !== null && state !== "unverified") {
|
|
120
|
+
throw fail(`${what}.trustGap is set on a cell whose state is "${state}", not "unverified"`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** `candidateVerdictSchema` — the whole report card. */
|
|
124
|
+
function checkCandidateVerdict(value, what, fail) {
|
|
125
|
+
const record = requireRecord(value, what, fail);
|
|
126
|
+
const cells = requireArray(record.cells, `${what}.cells`, fail);
|
|
127
|
+
cells.forEach((cell, index) => checkAttributeVerdict(cell, `${what}.cells[${index}]`, fail));
|
|
128
|
+
requireEnum(record.grade, VERDICT_GRADES, `${what}.grade`, fail);
|
|
129
|
+
requireNullableBoundedNumber(record.minConfidence, `${what}.minConfidence`, fail, 0, 1);
|
|
130
|
+
}
|
|
131
|
+
// ── The item ─────────────────────────────────────────────────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Prove one wire value is a {@link SearchResultItem}, raising through `fail` at
|
|
134
|
+
* the first field that does not match `searchResultItemSchema`. Checks the shape
|
|
135
|
+
* only — unknown keys ride through untouched, per the SDK's pass-through
|
|
136
|
+
* contract (nothing on the emit path parses, so stripping would invent a
|
|
137
|
+
* normalisation the wire never had).
|
|
138
|
+
*/
|
|
139
|
+
export function checkSearchResultItem(value, what, fail) {
|
|
140
|
+
const record = requireRecord(value, what, fail);
|
|
141
|
+
requireString(record.variantId, `${what}.variantId`, fail);
|
|
142
|
+
requireString(record.brand, `${what}.brand`, fail);
|
|
143
|
+
requireString(record.product, `${what}.product`, fail);
|
|
144
|
+
requireNullableString(record.brandSlug, `${what}.brandSlug`, fail);
|
|
145
|
+
requireNullableString(record.productId, `${what}.productId`, fail);
|
|
146
|
+
requireNullableString(record.color, `${what}.color`, fail);
|
|
147
|
+
requireNullableString(record.variantName, `${what}.variantName`, fail);
|
|
148
|
+
requireNullableString(record.imageUrl, `${what}.imageUrl`, fail);
|
|
149
|
+
requireBoolean(record.hasImage, `${what}.hasImage`, fail);
|
|
150
|
+
requireNumber(record.score, `${what}.score`, fail);
|
|
151
|
+
const palette = requireArray(record.palette, `${what}.palette`, fail);
|
|
152
|
+
palette.forEach((swatch, index) => checkPaletteSwatch(swatch, `${what}.palette[${index}]`, fail));
|
|
153
|
+
if (record.colourMeta !== undefined) {
|
|
154
|
+
checkColourMeta(record.colourMeta, `${what}.colourMeta`, fail);
|
|
155
|
+
}
|
|
156
|
+
if (record.signals !== undefined)
|
|
157
|
+
checkSignals(record.signals, `${what}.signals`, fail);
|
|
158
|
+
requireOptionalBoolean(record.retired, `${what}.retired`, fail);
|
|
159
|
+
requireOptionalBoolean(record.belowFloor, `${what}.belowFloor`, fail);
|
|
160
|
+
if (record.siblingCount !== undefined) {
|
|
161
|
+
requireIntegerAtLeast(record.siblingCount, `${what}.siblingCount`, fail, 1);
|
|
162
|
+
}
|
|
163
|
+
if (record.visualMeanings !== undefined) {
|
|
164
|
+
const meanings = requireArray(record.visualMeanings, `${what}.visualMeanings`, fail);
|
|
165
|
+
meanings.forEach((meaning, index) => checkVisualMeaning(meaning, `${what}.visualMeanings[${index}]`, fail));
|
|
166
|
+
}
|
|
167
|
+
if (record.verdict !== undefined) {
|
|
168
|
+
checkCandidateVerdict(record.verdict, `${what}.verdict`, fail);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Prove every entry of a `results[]` array is a {@link SearchResultItem}. */
|
|
172
|
+
export function checkSearchResultItems(items, what, fail) {
|
|
173
|
+
items.forEach((item, index) => checkSearchResultItem(item, `${what}[${index}]`, fail));
|
|
174
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime validator for `GET /api/v1/source-material/{batchId}` — the
|
|
3
|
+
* hand-rolled, zero-runtime-dependency mirror of
|
|
4
|
+
* `sourceMaterialBatchDetailSchema` and its transitive closure
|
|
5
|
+
* (`sourceMaterialBatchDetailFileSchema`, `sourceImageAnalysisSchema`,
|
|
6
|
+
* `sourceImageAnalysisResultSchema`) in
|
|
7
|
+
* `packages/sources/src/source-material/contract.ts`.
|
|
8
|
+
*
|
|
9
|
+
* WHY IT EXISTS. `contract-parity.test.ts` proves the SDK's STATIC types match
|
|
10
|
+
* the canonical schemas; that is a compile-time proof about shapes and validates
|
|
11
|
+
* nothing at run time. This is the run-time half, and it must ACCEPT and REJECT
|
|
12
|
+
* exactly what the schema does — pinned by `source-material-parity.test.ts`.
|
|
13
|
+
*
|
|
14
|
+
* TWO CONTRACT PROPERTIES IT ENFORCES, both easy to lose in a refactor:
|
|
15
|
+
* 1. `analyses` is REQUIRED and an array on every file. `[]` means "nothing
|
|
16
|
+
* computed yet"; an absent field is a broken response, not an empty one.
|
|
17
|
+
* Preprocessing is manual today and automatic later — only the trigger
|
|
18
|
+
* changes, never this contract.
|
|
19
|
+
* 2. `result` is a JSON record all the way down (`z.json()`), not `unknown`.
|
|
20
|
+
* See `requireJsonValue` for why the schema is that strict and why this
|
|
21
|
+
* mirror cannot actually fire over the wire.
|
|
22
|
+
*
|
|
23
|
+
* ENUMS ARE CLOSED here, matching the canonical `z.enum(…)` members, while the
|
|
24
|
+
* published types are `OpenEnum` so a newer server value keeps type-checking.
|
|
25
|
+
* The validator follows the SCHEMA: the type stays wider than what is proven,
|
|
26
|
+
* which is imprecise but never a lie — the same choice
|
|
27
|
+
* `search-result-validation.ts` makes for `basis`.
|
|
28
|
+
*
|
|
29
|
+
* Package-internal (not re-exported from `index.ts`): a published symbol is a
|
|
30
|
+
* permanent contract, and promoting it later is additive.
|
|
31
|
+
*/
|
|
32
|
+
import { MaterialGraphError } from "./errors.js";
|
|
33
|
+
import { requireArray, requireEnum, requireSafeInteger, requireJsonRecord, requireNonEmptyString, requireRecord, requireString, } from "./validation.js";
|
|
34
|
+
/** `sourceImageAnalysisKindSchema` members, verbatim. */
|
|
35
|
+
const ANALYSIS_KINDS = ["scene_match"];
|
|
36
|
+
/** `sourceImageAnalysisStatusSchema` members, verbatim. */
|
|
37
|
+
const ANALYSIS_STATUSES = ["ok", "failed"];
|
|
38
|
+
/** `sourceMaterialRoleSchema` members, verbatim. */
|
|
39
|
+
const SOURCE_ROLES = [
|
|
40
|
+
"brief",
|
|
41
|
+
"reference_image",
|
|
42
|
+
"document",
|
|
43
|
+
"document_image",
|
|
44
|
+
"inspiration_image",
|
|
45
|
+
"unclassified",
|
|
46
|
+
];
|
|
47
|
+
function detailFailure(batchId) {
|
|
48
|
+
return (reason) => MaterialGraphError.unexpected(`GET /api/v1/source-material/${batchId} returned a malformed body: ${reason}`);
|
|
49
|
+
}
|
|
50
|
+
function checkAnalysis(value, what, fail) {
|
|
51
|
+
const record = requireRecord(value, what, fail);
|
|
52
|
+
// `model` and `analysedAt` ARE `.min(1)` canonically — fields this endpoint
|
|
53
|
+
// introduces, where an empty value is meaningless and `model` already cannot
|
|
54
|
+
// be written empty. Parity here is achieved by the schema being strict, never
|
|
55
|
+
// by this mirror freelancing past it.
|
|
56
|
+
return {
|
|
57
|
+
kind: requireEnum(record.kind, ANALYSIS_KINDS, `${what}.kind`, fail),
|
|
58
|
+
model: requireNonEmptyString(record.model, `${what}.model`, fail),
|
|
59
|
+
status: requireEnum(record.status, ANALYSIS_STATUSES, `${what}.status`, fail),
|
|
60
|
+
result: requireJsonRecord(record.result, `${what}.result`, fail),
|
|
61
|
+
analysedAt: requireNonEmptyString(record.analysedAt, `${what}.analysedAt`, fail),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function checkFile(value, what, fail) {
|
|
65
|
+
const record = requireRecord(value, what, fail);
|
|
66
|
+
// NOT `requireDefaultedArray`: the schema has no `.default([])`, so an absent
|
|
67
|
+
// `analyses` is a malformed body rather than an empty one. Keeping that
|
|
68
|
+
// distinction is the whole point of the always-an-array contract.
|
|
69
|
+
const analyses = requireArray(record.analyses, `${what}.analyses`, fail);
|
|
70
|
+
// `requireString`, NOT `requireNonEmptyString`, for the fields this endpoint
|
|
71
|
+
// INHERITS from the shared source-material contract (`sourceId`, `sha256`,
|
|
72
|
+
// `url` — plus `batchId`/`createdAt` below). Those are plain `z.string()`
|
|
73
|
+
// there and shared with the upload path; a client that rejected `""` would
|
|
74
|
+
// refuse a response the server can legitimately produce, and tightening them
|
|
75
|
+
// is a change to a contract this endpoint does not own.
|
|
76
|
+
return {
|
|
77
|
+
sourceId: requireString(record.sourceId, `${what}.sourceId`, fail),
|
|
78
|
+
role: requireEnum(record.role, SOURCE_ROLES, `${what}.role`, fail),
|
|
79
|
+
filename: requireString(record.filename, `${what}.filename`, fail),
|
|
80
|
+
mediaType: requireString(record.mediaType, `${what}.mediaType`, fail),
|
|
81
|
+
sha256: requireString(record.sha256, `${what}.sha256`, fail),
|
|
82
|
+
// `requireSafeInteger`, NOT `requireIntegerAtLeast(…, 0)`. Canonically these
|
|
83
|
+
// are a bare `z.number().int()` on the INHERITED
|
|
84
|
+
// `sourceMaterialFileReceiptSchema`, which carries no lower bound — so a
|
|
85
|
+
// negative is accepted there and must be accepted here.
|
|
86
|
+
//
|
|
87
|
+
// A negative `bytes` is nonsense and a negative `pages` more so, and this is
|
|
88
|
+
// still not the place to say so: a client validator holding an opinion the
|
|
89
|
+
// contract does not is how the two drift, and how a response the server may
|
|
90
|
+
// legitimately produce starts throwing at the boundary. Tightening those
|
|
91
|
+
// fields is a canonical change to a schema shared with the upload path, and
|
|
92
|
+
// belongs in its own ticket with this mirror following after.
|
|
93
|
+
bytes: requireSafeInteger(record.bytes, `${what}.bytes`, fail),
|
|
94
|
+
pages: record.pages === null ? null : requireSafeInteger(record.pages, `${what}.pages`, fail),
|
|
95
|
+
url: requireString(record.url, `${what}.url`, fail),
|
|
96
|
+
analyses: analyses.map((analysis, index) => checkAnalysis(analysis, `${what}.analyses[${index}]`, fail)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Validate a batch-detail body, returning it typed, or throw explaining why. */
|
|
100
|
+
export function checkSourceMaterialBatchDetail(value, batchId) {
|
|
101
|
+
const fail = detailFailure(batchId);
|
|
102
|
+
const record = requireRecord(value, "body", fail);
|
|
103
|
+
const sources = requireArray(record.sources, "body.sources", fail);
|
|
104
|
+
return {
|
|
105
|
+
batchId: requireString(record.batchId, "body.batchId", fail),
|
|
106
|
+
createdAt: requireString(record.createdAt, "body.createdAt", fail),
|
|
107
|
+
sources: sources.map((file, index) => checkFile(file, `body.sources[${index}]`, fail)),
|
|
108
|
+
};
|
|
109
|
+
}
|
package/spec-types.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The specification surface — the typed vocabulary for asking a material
|
|
3
|
+
* "does it meet this spec?": certifications (GREENGUARD, FloorScore, Declare),
|
|
4
|
+
* VOC / emissions testing (CDPH v1.2), sustainability programmes (LEED, EPDs),
|
|
5
|
+
* and fire / abrasion / slip classifications.
|
|
6
|
+
*
|
|
7
|
+
* Every shape here is a hand-authored, zero-dependency mirror of the server's
|
|
8
|
+
* spec filter contract (`specFilterSchema` in
|
|
9
|
+
* `@materialgraph/products/search/schema`). It exists so a caller answering a
|
|
10
|
+
* question like "find me a GREENGUARD-certified alternative" or "the same fire
|
|
11
|
+
* class, but with an EPD on record" reaches for a NAMED, typed criterion instead
|
|
12
|
+
* of hand-assembling a magic `attribute:value` string.
|
|
13
|
+
*
|
|
14
|
+
* These criteria drive two operations equally:
|
|
15
|
+
* - `client.search({ filters: [...] })` — spec gates layered onto a ranked
|
|
16
|
+
* (colour / visual / lexical) search.
|
|
17
|
+
* - `client.bySpec(...)` — the value-anchored, spec-only primitive, which takes
|
|
18
|
+
* the same criteria as `attribute:value` strings (build them with the helpers
|
|
19
|
+
* in `./specifications`).
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
|
23
|
+
* landed at ingest.
|
|
24
|
+
*
|
|
25
|
+
* Certifications are not stored under one attribute: each record is an object
|
|
26
|
+
* (`{ programme, name, status, … }`) split across FOUR real object-array
|
|
27
|
+
* attributes by intent — general `certifications`,
|
|
28
|
+
* `sustainability_certifications`, `material_health_certifications`, and
|
|
29
|
+
* `voc_emissions_testing_certificate` (GREENGUARD Gold / FloorScore / CDPH land
|
|
30
|
+
* in the last). A caller shouldn't have to know which bucket a programme fell
|
|
31
|
+
* into, so this single virtual id matches the `programme` term across all four.
|
|
32
|
+
*
|
|
33
|
+
* The `term` may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
34
|
+
* server-side to its canonical programme token, or a canonical token directly
|
|
35
|
+
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // "find a GREENGUARD-certified alternative"
|
|
39
|
+
* client.search({
|
|
40
|
+
* similarTo: { variantId: seed },
|
|
41
|
+
* filters: [{ attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, term: "GREENGUARD Gold" }],
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
export declare const CERTIFICATION_PROGRAMME_ATTRIBUTE_ID = "certification_programme";
|
|
45
|
+
/** The literal type of {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. */
|
|
46
|
+
export type CertificationProgrammeAttributeId = typeof CERTIFICATION_PROGRAMME_ATTRIBUTE_ID;
|
|
47
|
+
/**
|
|
48
|
+
* A term-equality spec criterion: the attribute must carry this canonical value,
|
|
49
|
+
* e.g. `{ attributeId: "fire_classifications", term: "astm_e84_class_a" }`.
|
|
50
|
+
* Discover valid `attributeId`/`term` pairs via `client.specificationFacets()`
|
|
51
|
+
* and `client.attributeValues()`, or resolve free text with
|
|
52
|
+
* `client.resolveAttribute()`.
|
|
53
|
+
*/
|
|
54
|
+
export interface SpecTermFilter {
|
|
55
|
+
attributeId: string;
|
|
56
|
+
term: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A presence spec criterion: the attribute must have ANY accepted value —
|
|
60
|
+
* "has an EPD on record", "has a measured slip figure at all". Not supported for
|
|
61
|
+
* {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID} (a programme is matched by name,
|
|
62
|
+
* never by bare presence).
|
|
63
|
+
*/
|
|
64
|
+
export interface SpecPresenceFilter {
|
|
65
|
+
attributeId: string;
|
|
66
|
+
hasValue: true;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A certification-programme criterion — a {@link SpecTermFilter} pinned to the
|
|
70
|
+
* virtual {@link CERTIFICATION_PROGRAMME_ATTRIBUTE_ID}. Structurally assignable
|
|
71
|
+
* to {@link SpecFilter}, so it drops straight into `search({ filters })`.
|
|
72
|
+
*/
|
|
73
|
+
export interface CertificationProgrammeFilter {
|
|
74
|
+
attributeId: CertificationProgrammeAttributeId;
|
|
75
|
+
term: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A hard, canonical-attribute spec criterion — either a term match or a
|
|
79
|
+
* presence check. The union the search engine applies as a restriction BEFORE
|
|
80
|
+
* ranking. Identical in shape to the SDK's `SearchFilter`.
|
|
81
|
+
*/
|
|
82
|
+
export type SpecFilter = SpecTermFilter | SpecPresenceFilter;
|
package/spec-types.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The specification surface — the typed vocabulary for asking a material
|
|
3
|
+
* "does it meet this spec?": certifications (GREENGUARD, FloorScore, Declare),
|
|
4
|
+
* VOC / emissions testing (CDPH v1.2), sustainability programmes (LEED, EPDs),
|
|
5
|
+
* and fire / abrasion / slip classifications.
|
|
6
|
+
*
|
|
7
|
+
* Every shape here is a hand-authored, zero-dependency mirror of the server's
|
|
8
|
+
* spec filter contract (`specFilterSchema` in
|
|
9
|
+
* `@materialgraph/products/search/schema`). It exists so a caller answering a
|
|
10
|
+
* question like "find me a GREENGUARD-certified alternative" or "the same fire
|
|
11
|
+
* class, but with an EPD on record" reaches for a NAMED, typed criterion instead
|
|
12
|
+
* of hand-assembling a magic `attribute:value` string.
|
|
13
|
+
*
|
|
14
|
+
* These criteria drive two operations equally:
|
|
15
|
+
* - `client.search({ filters: [...] })` — spec gates layered onto a ranked
|
|
16
|
+
* (colour / visual / lexical) search.
|
|
17
|
+
* - `client.bySpec(...)` — the value-anchored, spec-only primitive, which takes
|
|
18
|
+
* the same criteria as `attribute:value` strings (build them with the helpers
|
|
19
|
+
* in `./specifications`).
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The virtual attribute id that matches a certification PROGRAMME wherever it
|
|
23
|
+
* landed at ingest.
|
|
24
|
+
*
|
|
25
|
+
* Certifications are not stored under one attribute: each record is an object
|
|
26
|
+
* (`{ programme, name, status, … }`) split across FOUR real object-array
|
|
27
|
+
* attributes by intent — general `certifications`,
|
|
28
|
+
* `sustainability_certifications`, `material_health_certifications`, and
|
|
29
|
+
* `voc_emissions_testing_certificate` (GREENGUARD Gold / FloorScore / CDPH land
|
|
30
|
+
* in the last). A caller shouldn't have to know which bucket a programme fell
|
|
31
|
+
* into, so this single virtual id matches the `programme` term across all four.
|
|
32
|
+
*
|
|
33
|
+
* The `term` may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
34
|
+
* server-side to its canonical programme token, or a canonical token directly
|
|
35
|
+
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // "find a GREENGUARD-certified alternative"
|
|
39
|
+
* client.search({
|
|
40
|
+
* similarTo: { variantId: seed },
|
|
41
|
+
* filters: [{ attributeId: CERTIFICATION_PROGRAMME_ATTRIBUTE_ID, term: "GREENGUARD Gold" }],
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
export const CERTIFICATION_PROGRAMME_ATTRIBUTE_ID = "certification_programme";
|
|
@@ -0,0 +1,72 @@
|
|
|
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 { type CertificationProgrammeFilter, type SpecPresenceFilter, type SpecTermFilter } 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 declare function specTerm(attributeId: string, term: string): SpecTermFilter;
|
|
27
|
+
/**
|
|
28
|
+
* A presence criterion for `search({ filters })` — the attribute must have ANY
|
|
29
|
+
* accepted value.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // "…and it must have an EPD on record"
|
|
33
|
+
* client.search({ similarTo: { variantId }, filters: [hasSpec("environmental_product_declaration")] });
|
|
34
|
+
*/
|
|
35
|
+
export declare function hasSpec(attributeId: string): SpecPresenceFilter;
|
|
36
|
+
/**
|
|
37
|
+
* A certification-programme criterion for `search({ filters })`, pinned to the
|
|
38
|
+
* virtual attribute so a programme matches wherever it landed at ingest
|
|
39
|
+
* (compliance / sustainability / material-health / VOC buckets).
|
|
40
|
+
*
|
|
41
|
+
* The value may be a display name or alias ("GREENGUARD Gold", "LEED") — the
|
|
42
|
+
* server resolves it to its canonical programme token before matching — or a
|
|
43
|
+
* canonical token directly (e.g. `greenguard_certificate`,
|
|
44
|
+
* `has_greenguard_gold_certification`).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // "find a GREENGUARD-certified alternative"
|
|
48
|
+
* client.search({ similarTo: { variantId: seed }, filters: [certificationProgramme("GREENGUARD Gold")] });
|
|
49
|
+
*/
|
|
50
|
+
export declare function certificationProgramme(programme: string): CertificationProgrammeFilter;
|
|
51
|
+
/**
|
|
52
|
+
* An `attribute:value` criterion string for `bySpec`. The `bySpec` route splits
|
|
53
|
+
* on the FIRST `:`, so a value may itself contain colons; the attribute id may
|
|
54
|
+
* not.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* client.bySpec([specCriterion("primary_material", "textile"), specCriterion("fire_classifications", "astm_e84_class_a")]);
|
|
58
|
+
*/
|
|
59
|
+
export declare function specCriterion(attributeId: string, value: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* An `attribute:value` criterion string for `bySpec` that filters by
|
|
62
|
+
* certification PROGRAMME across all four certification buckets.
|
|
63
|
+
*
|
|
64
|
+
* The value may be a display name or alias ("GREENGUARD Gold", "LEED"), resolved
|
|
65
|
+
* server-side to its canonical programme token, or a canonical token directly
|
|
66
|
+
* (e.g. `greenguard_certificate`, `has_greenguard_gold_certification`).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // every brand's GREENGUARD-Gold materials
|
|
70
|
+
* client.bySpec([certificationProgrammeCriterion("GREENGUARD Gold")]);
|
|
71
|
+
*/
|
|
72
|
+
export declare function certificationProgrammeCriterion(programme: string): string;
|