@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.
Files changed (56) hide show
  1. package/by-spec-types.d.ts +73 -0
  2. package/by-spec-types.js +7 -0
  3. package/client.d.ts +186 -7
  4. package/client.js +242 -125
  5. package/codes-client.d.ts +82 -0
  6. package/codes-client.js +73 -0
  7. package/codes-types.d.ts +238 -0
  8. package/codes-types.js +1 -0
  9. package/colour-aggregate-types.d.ts +93 -0
  10. package/colour-aggregate-types.js +1 -0
  11. package/colours-client.d.ts +52 -0
  12. package/colours-client.js +39 -0
  13. package/composition-types.d.ts +343 -0
  14. package/composition-types.js +10 -0
  15. package/http-transport.d.ts +74 -0
  16. package/http-transport.js +201 -0
  17. package/index.d.ts +19 -0
  18. package/index.js +9 -0
  19. package/match-stream-types.d.ts +150 -0
  20. package/match-stream-types.js +28 -0
  21. package/match-stream.d.ts +22 -0
  22. package/match-stream.js +280 -0
  23. package/material-block-types.d.ts +23 -2
  24. package/material-types.d.ts +92 -0
  25. package/material-types.js +1 -0
  26. package/ndjson.d.ts +46 -0
  27. package/ndjson.js +103 -0
  28. package/operations.d.ts +6 -2
  29. package/operations.js +30 -2
  30. package/package.json +1 -1
  31. package/project-types.d.ts +67 -0
  32. package/project-types.js +16 -0
  33. package/projects.d.ts +13 -0
  34. package/projects.js +46 -0
  35. package/run-events.d.ts +82 -0
  36. package/run-events.js +29 -0
  37. package/run-types.d.ts +266 -0
  38. package/run-types.js +6 -0
  39. package/runs.d.ts +84 -0
  40. package/runs.js +253 -0
  41. package/schedule-types.d.ts +88 -0
  42. package/schedule-types.js +12 -0
  43. package/search-refinement-types.d.ts +26 -1
  44. package/search-result-validation.d.ts +27 -0
  45. package/search-result-validation.js +174 -0
  46. package/source-material-validation.d.ts +3 -0
  47. package/source-material-validation.js +109 -0
  48. package/spec-types.d.ts +82 -0
  49. package/spec-types.js +44 -0
  50. package/specifications.d.ts +72 -0
  51. package/specifications.js +82 -0
  52. package/sse.d.ts +35 -0
  53. package/sse.js +147 -0
  54. package/types.d.ts +204 -77
  55. package/validation.d.ts +119 -0
  56. package/validation.js +241 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Hand-mirrored wire types for the advisory building-code surface
3
+ * (`GET /api/v1/codes/context`, `GET /api/v1/codes/requirements`,
4
+ * `POST /api/v1/codes/advisory` — MG-433, MG-674).
5
+ *
6
+ * Like the rest of the SDK these interfaces have NO runtime dependency on any
7
+ * `@materialgraph/*` package — they mirror the `codeAdvisoryInputSchema` /
8
+ * `codeAdvisoryResultSchema` Zod contracts the route is built from. Response
9
+ * enums follow the SDK's TOLERANT-PARSING contract (widened with `OpenEnum` so a
10
+ * new server value still type-checks); `requirement.scale` is an open IBC-scale
11
+ * id and `caveat` is the AHJ advisory sentence, both read as plain strings.
12
+ *
13
+ * ADVISORY ONLY: every verdict and the envelope carry the AHJ caveat — this is
14
+ * model code as adopted, never a legal determination.
15
+ */
16
+ import type { OpenEnum } from "./types.js";
17
+ /**
18
+ * Badge a bounded set of materials against the code-requirement registry for one
19
+ * (jurisdiction, occupancy, space, sprinklered) context. `results` preserve
20
+ * input order and cardinality (duplicate ids are echoed).
21
+ */
22
+ export interface CodeContextRequest {
23
+ /** A seeded jurisdiction id (e.g. `"us-tx"`); an unknown id is a 400. */
24
+ jurisdictionId: string;
25
+ /**
26
+ * The IBC occupancy group for the space (e.g. `"I-2"` hospital, `"A-2"`
27
+ * restaurant). Required unless `roomType` correlates to one.
28
+ */
29
+ occupancyGroup?: string;
30
+ /**
31
+ * The finish location: `"corridors"`, `"exit_stairs_passageways"`, or
32
+ * `"rooms_enclosed_spaces"`. Required unless `roomType` correlates to one.
33
+ */
34
+ spaceType?: string;
35
+ /**
36
+ * An `ms_room` term id (e.g. `"hotel_room"`) to correlate the regulatory axes
37
+ * from. Explicit `occupancyGroup` / `spaceType` always win. A room that
38
+ * correlates to neither axis is a 400 naming the axis — never a guess.
39
+ */
40
+ roomType?: string;
41
+ /** Whether the space is sprinklered — relaxes several fire-finish floors. No default. */
42
+ sprinklered: boolean;
43
+ }
44
+ export interface CodeAdvisoryInput extends CodeContextRequest {
45
+ /** 1–100 opaque variant ids to badge, echoed back in `results` in this exact order. */
46
+ variantIds: string[];
47
+ }
48
+ /** The resolved jurisdiction the verdicts were evaluated against. */
49
+ export interface CodeAdvisoryJurisdiction {
50
+ id: string;
51
+ name: string;
52
+ codeFamily: string;
53
+ edition: string;
54
+ adoptionNote: string;
55
+ }
56
+ /** The echoed evaluation context (the space the badges apply to). */
57
+ export interface CodeAdvisoryContext {
58
+ occupancyGroup: string;
59
+ spaceType: string;
60
+ sprinklered: boolean;
61
+ }
62
+ /**
63
+ * What one material is, and therefore what it was held to. `dimensions` are the
64
+ * regulatory dimensions the material can be scored on; `basis` names which
65
+ * source(s) produced them: a mapped `productTypeId`, `applicationIds`, both, or
66
+ * `unresolved` when neither yielded anything. `unresolved` does NOT mean the
67
+ * material is answerable for nothing — it means we could not tell what the
68
+ * material is, so every requirement the space imposes was scored against it and
69
+ * `notApplicable` on the result is empty. That is a coverage gap in the
70
+ * product-type/application map, not a narrow material. A short `requirements`
71
+ * list under `product_type` / `application` / `both` is a genuine intersection;
72
+ * a full list under `unresolved` is the map falling short.
73
+ */
74
+ export interface MaterialCodeProfile {
75
+ /** The variant's accepted primary product type, or null if it has none. */
76
+ productTypeId: string | null;
77
+ /** The variant's accepted `applications` terms, as stored. */
78
+ applicationIds: string[];
79
+ /** Regulatory dimensions this material can be held to. Empty ONLY when `basis` is `unresolved`. */
80
+ dimensions: OpenEnum<"fire" | "floor" | "slip" | "decorative">[];
81
+ basis: OpenEnum<"product_type" | "application" | "both" | "unresolved">;
82
+ }
83
+ /** Why one requirement the space imposes was excluded from this material's `requirements`. */
84
+ export interface CodeRequirementExclusion {
85
+ requirementId: string;
86
+ dimension: OpenEnum<"fire" | "floor" | "slip" | "decorative">;
87
+ reason: OpenEnum<"not_a_material_dimension">;
88
+ /** One readable line, e.g. `"Slip resistance is not a dimension wallcovering answers for."`. */
89
+ detail: string;
90
+ }
91
+ /** The threshold one requirement imposes: a class floor and/or a numeric floor. */
92
+ export interface CodeRequirementThreshold {
93
+ /** The IBC classification scale this requirement is measured on (open id). */
94
+ scale: string;
95
+ /** The minimum acceptable class on `scale`, or null when only a numeric floor applies. */
96
+ minClass: string | null;
97
+ /** A numeric floor (e.g. a DCOF minimum), or null when only a class floor applies. */
98
+ numericFloor: {
99
+ metric: string;
100
+ value: number;
101
+ unit: string;
102
+ } | null;
103
+ }
104
+ /** The accepted fact a verdict was decided from (null when the material carried none). */
105
+ export interface CodeRequirementEvidence {
106
+ attributeId: OpenEnum<"fire_classifications" | "slip_resistance_dcof">;
107
+ value: string;
108
+ confidence: number | null;
109
+ sourceBasis: string | null;
110
+ }
111
+ /** One requirement badged against one material. */
112
+ export interface CodeRequirementVerdict {
113
+ requirementId: string;
114
+ dimension: OpenEnum<"fire" | "floor" | "slip" | "decorative">;
115
+ section: string;
116
+ requirement: CodeRequirementThreshold;
117
+ /** `meets` / `shortfall` / `unknown` — `unknown` when no defensible fact maps onto `scale`. */
118
+ verdict: OpenEnum<"meets" | "shortfall" | "unknown">;
119
+ evidence: CodeRequirementEvidence | null;
120
+ /** The AHJ advisory caveat — model code as adopted, never a legal determination. */
121
+ caveat: string;
122
+ }
123
+ /**
124
+ * One candidate's requirement verdicts folded into a single status and sentence.
125
+ * A shortfall outranks an unknown, and an unknown NEVER rolls up to a pass:
126
+ * `not_applicable` (nothing applied) is deliberately distinct from `meets`.
127
+ */
128
+ export interface CodeAdvisorySummary {
129
+ status: OpenEnum<"meets" | "shortfall" | "unverified" | "not_applicable">;
130
+ /** One deterministic sentence naming what failed, e.g. `"Falls short on fire for A-2 corridors."`. */
131
+ headline: string;
132
+ shortfallDimensions: OpenEnum<"fire" | "floor" | "slip" | "decorative">[];
133
+ unverifiedDimensions: OpenEnum<"fire" | "floor" | "slip" | "decorative">[];
134
+ }
135
+ /** Per-variant advisory outcome, in input order. */
136
+ export interface CodeAdvisoryVariantResult {
137
+ variantId: string;
138
+ /** `evaluated` when the id resolved; `unknown_variant` (with `requirements: []`) when it did not. */
139
+ state: OpenEnum<"evaluated" | "unknown_variant">;
140
+ /** What this material is and what it was scored against. Null only for `state: "unknown_variant"`. */
141
+ material: MaterialCodeProfile | null;
142
+ /** Verdicts for the requirements this material is answerable for — the answerable subset only. */
143
+ requirements: CodeRequirementVerdict[];
144
+ /** Requirements the space imposes that this material cannot answer. Empty when nothing was excluded. */
145
+ notApplicable: CodeRequirementExclusion[];
146
+ /** The rolled-up card, or null for an id that resolved to nothing. */
147
+ summary: CodeAdvisorySummary | null;
148
+ }
149
+ /** The `POST /api/v1/codes/advisory` envelope — per-variant, per-requirement verdicts. */
150
+ export interface CodeAdvisoryResponse {
151
+ jurisdiction: CodeAdvisoryJurisdiction;
152
+ /** The context AS SUPPLIED, echoed verbatim. */
153
+ context: CodeAdvisoryContext;
154
+ /** The canonical axes the registry was queried with, and where they came from. */
155
+ resolvedContext: ResolvedCodeContext;
156
+ /** The AHJ advisory caveat, at the envelope level too. */
157
+ caveat: string;
158
+ results: CodeAdvisoryVariantResult[];
159
+ }
160
+ /** Where a resolved axis came from: stated outright, or correlated from a room type. */
161
+ export interface ResolvedCodeContext {
162
+ jurisdictionId: string;
163
+ occupancyGroup: string;
164
+ /** The canonical finish-location term id the registry was queried with. */
165
+ spaceType: string;
166
+ sprinklered: boolean;
167
+ basis: OpenEnum<"explicit" | "derived_from_room">;
168
+ /** The `ms_room` term the axes were correlated from, or null. */
169
+ roomType: string | null;
170
+ }
171
+ /** How far a jurisdiction's rows have been reviewed against that AHJ's own tables. */
172
+ export type CodeCoverageTier = OpenEnum<"thorough" | "model_baseline">;
173
+ export interface CodeJurisdictionSummary {
174
+ id: string;
175
+ name: string;
176
+ codeFamily: string;
177
+ edition: string;
178
+ coverage: CodeCoverageTier;
179
+ /** One honest line on how the model code actually applies in this jurisdiction. */
180
+ adoptionNote: string;
181
+ }
182
+ /** The `GET /api/v1/codes/context` envelope — the vocabulary for stating a context. */
183
+ export interface CodeContextResponse {
184
+ jurisdictions: CodeJurisdictionSummary[];
185
+ occupancyGroups: {
186
+ id: string;
187
+ label: string;
188
+ }[];
189
+ finishLocations: {
190
+ id: string;
191
+ label: string;
192
+ shortLabel: string;
193
+ }[];
194
+ dimensions: {
195
+ id: OpenEnum<"fire" | "floor" | "slip" | "decorative">;
196
+ label: string;
197
+ description: string;
198
+ /** The MaterialGraph attribute this dimension is scored against. */
199
+ attributeId: string;
200
+ }[];
201
+ scales: {
202
+ id: string;
203
+ label: string;
204
+ detail: string;
205
+ /** Ordered BEST FIRST: index 0 outranks index 1. */
206
+ classes: string[];
207
+ }[];
208
+ /** `ms_room` → regulatory axes. `null` where the registry refuses to correlate. */
209
+ roomCorrelations: {
210
+ roomType: string;
211
+ label: string;
212
+ occupancyGroup: string | null;
213
+ finishLocation: string | null;
214
+ }[];
215
+ /** The AHJ advisory caveat. */
216
+ caveat: string;
217
+ }
218
+ /** One requirement a context imposes, independent of any candidate material. */
219
+ export interface CodeRequirementListing {
220
+ requirementId: string;
221
+ dimension: OpenEnum<"fire" | "floor" | "slip" | "decorative">;
222
+ /** The code section this demand is cited from. */
223
+ section: string;
224
+ attributeId: string;
225
+ requirement: CodeRequirementThreshold;
226
+ /** One readable line, e.g. `"Class A or better · ASTM E84 / UL 723"`. */
227
+ label: string;
228
+ }
229
+ /**
230
+ * The `GET /api/v1/codes/requirements` envelope. An empty `requirements` array
231
+ * means the registry holds nothing for this context — it is not a pass.
232
+ */
233
+ export interface CodeRequirementsResponse {
234
+ jurisdiction: CodeJurisdictionSummary;
235
+ context: ResolvedCodeContext;
236
+ requirements: CodeRequirementListing[];
237
+ caveat: string;
238
+ }
package/codes-types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Wire types for the colour AGGREGATE surface — `GET /api/v1/colours/profiles`
3
+ * and `GET /api/v1/colours/stats`.
4
+ *
5
+ * Kept out of `types.ts` for the same reason `material-types.ts` and
6
+ * `codes-types.ts` are: the shared mirror is at its source-size budget. Same
7
+ * tolerant-parsing contract as the rest of the mirror — response enums are
8
+ * widened so a new server value still type-checks.
9
+ */
10
+ import type { OpenEnum } from "./types.js";
11
+ /** Which taxonomy tier a colour profile's `categoryKey` names. */
12
+ export type CategoryTier = OpenEnum<"group" | "type">;
13
+ /** How the endpoint picks which colours to keep. */
14
+ export type ColourProfileSelect = OpenEnum<"frequency" | "diverse">;
15
+ /** One representative colour bucket of a category. */
16
+ export interface CategoryColour {
17
+ hex: string;
18
+ oklab: {
19
+ l: number;
20
+ a: number;
21
+ b: number;
22
+ };
23
+ variantCount: number;
24
+ /** This bucket's share of the category's coloured variants, 0–1. */
25
+ proportion: number;
26
+ }
27
+ export interface CategoryColourProfile {
28
+ categoryKey: string;
29
+ level: CategoryTier;
30
+ name: string;
31
+ path: string;
32
+ variantCount: number;
33
+ avgLightness: number;
34
+ avgChroma: number;
35
+ colours: CategoryColour[];
36
+ }
37
+ /**
38
+ * The snapshot a profile response was computed from. Compare `variantCount`
39
+ * against `colours/stats`' `corpus.withColour` to see how complete the
40
+ * underlying materialized view currently is, and `refreshedAt` for its age.
41
+ */
42
+ export interface CategoryColourProfilesBasis {
43
+ variantCount: number;
44
+ refreshedAt: string | null;
45
+ }
46
+ export interface CategoryColourProfilesResponse {
47
+ profiles: CategoryColourProfile[];
48
+ basis: CategoryColourProfilesBasis;
49
+ }
50
+ /** Request parameters for `colours.profiles()`. */
51
+ export interface ColourProfilesParams {
52
+ /** 1–8 category keys, at the tier named by `groupBy`. */
53
+ productTypeIds: readonly string[];
54
+ /** `"group"` merges each group's leaf product types into one palette. */
55
+ groupBy?: "group" | "type";
56
+ /** Representative colours per category, 1–64. */
57
+ topN?: number;
58
+ /** `"diverse"` maximises visual distinctness instead of frequency. */
59
+ select?: "frequency" | "diverse";
60
+ }
61
+ export interface ColourStatsCorpus {
62
+ variants: number;
63
+ withColour: number;
64
+ /** `withColour / variants`, 0–1. */
65
+ coverage: number;
66
+ }
67
+ export interface ColourStatsFamily {
68
+ id: string;
69
+ label: string;
70
+ variants: number;
71
+ brands: number;
72
+ }
73
+ export interface ColourStatsGroup {
74
+ key: string;
75
+ name: string;
76
+ productTypes: number;
77
+ variants: number;
78
+ }
79
+ /** Where each section came from, how fresh it is, and what it does NOT answer. */
80
+ export interface ColourStatsBasis {
81
+ corpus: string;
82
+ families: string;
83
+ groups: string;
84
+ groupsRefreshedAt: string | null;
85
+ groupsVariantCount: number;
86
+ omissions: string[];
87
+ }
88
+ export interface ColourStatsResponse {
89
+ corpus: ColourStatsCorpus;
90
+ families: ColourStatsFamily[];
91
+ groups: ColourStatsGroup[];
92
+ basis: ColourStatsBasis;
93
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The colour AGGREGATE surface, namespaced as `mg.colours`.
3
+ *
4
+ * Two reads that answer the same question at two altitudes — "how much colour
5
+ * does the graph hold at all" (`stats`) and "what colours does this category
6
+ * come in" (`profiles`) — so they live together rather than as two more flat
7
+ * methods on an already-large client facade. `mg.codes` and `mg.browse` set the
8
+ * precedent for a namespace when a capability is a small family rather than a
9
+ * single verb.
10
+ *
11
+ * The namespace holds no transport of its own: the client hands it a bound
12
+ * `get`, so auth, rate-limit accounting and error shaping stay in one place.
13
+ *
14
+ * Both responses carry a `basis`. These are reads of a materialized view that is
15
+ * refreshed on a schedule, not on write, so a caller can and should check how
16
+ * much of the corpus the answer actually covers before treating it as complete.
17
+ */
18
+ import type { CategoryColourProfilesResponse, ColourProfilesParams, ColourStatsResponse } from "./colour-aggregate-types.js";
19
+ import type { QueryParams } from "./http-transport.js";
20
+ import type { SdkOperationId } from "./operations.js";
21
+ /** The client's own request primitive, handed down so this file owns no transport. */
22
+ export interface ColoursTransport {
23
+ get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
24
+ }
25
+ export declare class ColoursClient {
26
+ #private;
27
+ constructor(transport: ColoursTransport);
28
+ /**
29
+ * The representative colour palette of 1–8 categories in ONE call
30
+ * (GET `/api/v1/colours/profiles`).
31
+ *
32
+ * `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
33
+ * under a group into one histogram BEFORE ranking — a group's palette, not the
34
+ * union of its leaves' heads. `select: "diverse"` returns visually spread
35
+ * swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
36
+ * frequency if fewer survive) instead of the most frequent ones; a client
37
+ * cannot recover that spread from a truncated frequency list, which is why it
38
+ * is a server mode rather than a local sort.
39
+ *
40
+ * A selected category with no colour data comes back as a zero-count profile,
41
+ * not a missing key.
42
+ */
43
+ profiles(params: ColourProfilesParams, signal?: AbortSignal): Promise<CategoryColourProfilesResponse>;
44
+ /**
45
+ * Corpus colour coverage (GET `/api/v1/colours/stats`): how much of the graph
46
+ * carries colour at all, the split across the canonical colour families, and
47
+ * coverage per product-type group. `basis` states the source and freshness of
48
+ * each section plus `basis.omissions` — what this call deliberately does not
49
+ * answer, listed rather than silently absent.
50
+ */
51
+ stats(signal?: AbortSignal): Promise<ColourStatsResponse>;
52
+ }
@@ -0,0 +1,39 @@
1
+ export class ColoursClient {
2
+ #transport;
3
+ constructor(transport) {
4
+ this.#transport = transport;
5
+ }
6
+ /**
7
+ * The representative colour palette of 1–8 categories in ONE call
8
+ * (GET `/api/v1/colours/profiles`).
9
+ *
10
+ * `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
11
+ * under a group into one histogram BEFORE ranking — a group's palette, not the
12
+ * union of its leaves' heads. `select: "diverse"` returns visually spread
13
+ * swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
14
+ * frequency if fewer survive) instead of the most frequent ones; a client
15
+ * cannot recover that spread from a truncated frequency list, which is why it
16
+ * is a server mode rather than a local sort.
17
+ *
18
+ * A selected category with no colour data comes back as a zero-count profile,
19
+ * not a missing key.
20
+ */
21
+ profiles(params, signal) {
22
+ return this.#transport.get("get_category_colour_profiles", {
23
+ productTypeIds: params.productTypeIds.join(","),
24
+ groupBy: params.groupBy,
25
+ topN: params.topN,
26
+ select: params.select,
27
+ }, signal);
28
+ }
29
+ /**
30
+ * Corpus colour coverage (GET `/api/v1/colours/stats`): how much of the graph
31
+ * carries colour at all, the split across the canonical colour families, and
32
+ * coverage per product-type group. `basis` states the source and freshness of
33
+ * each section plus `basis.omissions` — what this call deliberately does not
34
+ * answer, listed rather than silently absent.
35
+ */
36
+ stats(signal) {
37
+ return this.#transport.get("get_colour_stats", undefined, signal);
38
+ }
39
+ }