@instruments/taxonomy 1.1.6 → 1.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/README.md +5 -5
- package/dist/assertions.d.ts +70 -0
- package/dist/authoring.d.ts +33 -0
- package/dist/axes/axis.d.ts +35 -0
- package/dist/axes/construction.d.ts +3 -0
- package/dist/axes/daylight.d.ts +7 -0
- package/dist/axes/element-fabric.d.ts +2 -0
- package/dist/axes/element-fitout.d.ts +2 -0
- package/dist/axes/element-shared.d.ts +13 -0
- package/dist/axes/element.d.ts +2 -0
- package/dist/axes/finish.d.ts +9 -0
- package/dist/axes/format.d.ts +3 -0
- package/dist/axes/index.d.ts +33 -0
- package/dist/axes/installation-layout.d.ts +3 -0
- package/dist/axes/material.d.ts +3 -0
- package/dist/axes/mood-anchors.d.ts +4 -0
- package/dist/axes/mood.d.ts +2 -0
- package/dist/axes/palette.d.ts +7 -0
- package/dist/axes/pattern.d.ts +13 -0
- package/dist/axes/period-anchors.d.ts +4 -0
- package/dist/axes/period.d.ts +29 -0
- package/dist/axes/place-anchors.d.ts +4 -0
- package/dist/axes/place.d.ts +5 -0
- package/dist/axes/product-category.d.ts +3 -0
- package/dist/axes/requirement.d.ts +3 -0
- package/dist/axes/season.d.ts +2 -0
- package/dist/axes/space/arrival.d.ts +2 -0
- package/dist/axes/space/circulation.d.ts +2 -0
- package/dist/axes/space/civic.d.ts +2 -0
- package/dist/axes/space/culture.d.ts +2 -0
- package/dist/axes/space/dining.d.ts +2 -0
- package/dist/axes/space/domestic.d.ts +2 -0
- package/dist/axes/space/fitness.d.ts +2 -0
- package/dist/axes/space/gathering.d.ts +2 -0
- package/dist/axes/space/healthcare.d.ts +2 -0
- package/dist/axes/space/learning.d.ts +2 -0
- package/dist/axes/space/market-neutral.d.ts +1 -0
- package/dist/axes/space/other.d.ts +2 -0
- package/dist/axes/space/outdoor.d.ts +2 -0
- package/dist/axes/space/retail.d.ts +2 -0
- package/dist/axes/space/sanitary.d.ts +2 -0
- package/dist/axes/space/service.d.ts +2 -0
- package/dist/axes/space/shared.d.ts +30 -0
- package/dist/axes/space/sleeping.d.ts +2 -0
- package/dist/axes/space/working.d.ts +2 -0
- package/dist/axes/space-anchors/arrival.d.ts +3 -0
- package/dist/axes/space-anchors/circulation.d.ts +3 -0
- package/dist/axes/space-anchors/civic.d.ts +3 -0
- package/dist/axes/space-anchors/culture.d.ts +3 -0
- package/dist/axes/space-anchors/dining.d.ts +3 -0
- package/dist/axes/space-anchors/domestic.d.ts +3 -0
- package/dist/axes/space-anchors/fitness.d.ts +3 -0
- package/dist/axes/space-anchors/gathering.d.ts +3 -0
- package/dist/axes/space-anchors/healthcare.d.ts +3 -0
- package/dist/axes/space-anchors/learning.d.ts +3 -0
- package/dist/axes/space-anchors/other.d.ts +3 -0
- package/dist/axes/space-anchors/outdoor.d.ts +3 -0
- package/dist/axes/space-anchors/retail.d.ts +3 -0
- package/dist/axes/space-anchors/sanitary.d.ts +3 -0
- package/dist/axes/space-anchors/service.d.ts +3 -0
- package/dist/axes/space-anchors/sleeping.d.ts +3 -0
- package/dist/axes/space-anchors/working.d.ts +3 -0
- package/dist/axes/space-anchors.d.ts +8 -0
- package/dist/axes/space.d.ts +61 -0
- package/dist/axes/style-anchors.d.ts +7 -0
- package/dist/axes/style.d.ts +2 -0
- package/dist/axes/tier.d.ts +42 -0
- package/dist/axes/typology.d.ts +2 -0
- package/dist/axes/unit-shape.d.ts +3 -0
- package/dist/canonicalisation.d.ts +44 -0
- package/dist/crossover-sources.d.ts +52 -0
- package/dist/crosswalks/mapping.d.ts +29 -0
- package/dist/crosswalks/strength.d.ts +19 -0
- package/dist/dictionaries.d.ts +4 -0
- package/dist/estate/concepts.d.ts +117 -0
- package/dist/estate/decomposition.d.ts +40 -0
- package/dist/estate/index.d.ts +4 -0
- package/dist/estate/residue-report.d.ts +45 -0
- package/dist/estate/rows/architecture-projects.d.ts +2 -0
- package/dist/estate/rows/editorial-imagery.d.ts +2 -0
- package/dist/estate/rows/furnishings-retailer.d.ts +2 -0
- package/dist/estate/rows/index.d.ts +2 -0
- package/dist/estate/rows/materials-retrieval.d.ts +2 -0
- package/dist/estate/rows/paint-samples.d.ts +2 -0
- package/dist/estate/rows/rendering-pipeline.d.ts +2 -0
- package/dist/estate/sources.d.ts +106 -0
- package/dist/index.d.ts +5 -339
- package/dist/index.js +902 -670
- package/dist/match.d.ts +107 -0
- package/dist/search-corpus-source.d.ts +6 -0
- package/dist/taste-assertion.d.ts +112 -0
- package/dist/taste.d.ts +2 -0
- package/dist/term.d.ts +352 -0
- package/dist/v1.d.ts +141 -0
- package/package.json +13 -13
package/dist/match.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { RelationRole } from "./term.js";
|
|
2
|
+
/** The role an assertion plays inside a compound expression. */
|
|
3
|
+
export type BundleRole = RelationRole;
|
|
4
|
+
export interface BundleMemberInput {
|
|
5
|
+
/** A qualified canonical term or typed relation, e.g. `style.minimalism`. */
|
|
6
|
+
assertion: string;
|
|
7
|
+
/** Defaults to accent: an unspecified role is evidence of presence, not necessity. */
|
|
8
|
+
role?: BundleRole;
|
|
9
|
+
}
|
|
10
|
+
export interface BundleMember {
|
|
11
|
+
assertion: string;
|
|
12
|
+
role: BundleRole;
|
|
13
|
+
}
|
|
14
|
+
export interface CanonicalBundle {
|
|
15
|
+
kind: "all-of";
|
|
16
|
+
/** Stable, reversible identity derived from the canonical serialization. */
|
|
17
|
+
id: string;
|
|
18
|
+
members: readonly BundleMember[];
|
|
19
|
+
serialization: string;
|
|
20
|
+
}
|
|
21
|
+
export type AlternativeReason = "query-alternative" | "source-ambiguous";
|
|
22
|
+
export interface CanonicalAlternatives {
|
|
23
|
+
kind: "any-of";
|
|
24
|
+
/** Stable, reversible identity derived from the sorted alternative bundle identities. */
|
|
25
|
+
id: string;
|
|
26
|
+
alternatives: readonly CanonicalBundle[];
|
|
27
|
+
reason: AlternativeReason;
|
|
28
|
+
serialization: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const MATCH_WEIGHTS: Readonly<Record<RelationRole, number>>;
|
|
31
|
+
export declare const DEFAULT_BUNDLE_ROLE: BundleRole;
|
|
32
|
+
/**
|
|
33
|
+
* Give a bundle one canonical spelling. Duplicate assertions collapse; constitutive wins when the
|
|
34
|
+
* same assertion was supplied in both roles because removing it would otherwise change identity.
|
|
35
|
+
*/
|
|
36
|
+
export declare const defineBundle: (inputs: readonly BundleMemberInput[]) => CanonicalBundle;
|
|
37
|
+
export declare const defineAlternatives: (inputs: readonly (CanonicalBundle | readonly BundleMemberInput[])[], reason: AlternativeReason) => CanonicalAlternatives;
|
|
38
|
+
export interface BundleMatch {
|
|
39
|
+
eligible: boolean;
|
|
40
|
+
matched: readonly string[];
|
|
41
|
+
missingAccents: readonly string[];
|
|
42
|
+
missingConstitutive: readonly string[];
|
|
43
|
+
/** Fixed-weight score in [0, 1]. Ineligible matches always score zero. */
|
|
44
|
+
score: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Match without penalising extra candidate annotations. Richer annotation should not make an
|
|
48
|
+
* otherwise identical subject rank lower.
|
|
49
|
+
*/
|
|
50
|
+
export declare const matchBundle: (bundle: CanonicalBundle, candidateAssertions: Iterable<string>) => BundleMatch;
|
|
51
|
+
export interface AlternativesMatch {
|
|
52
|
+
alternative: CanonicalBundle;
|
|
53
|
+
match: BundleMatch;
|
|
54
|
+
}
|
|
55
|
+
/** The strongest eligible reading wins; bundle identity makes ties deterministic. */
|
|
56
|
+
export declare const matchAlternatives: (alternatives: CanonicalAlternatives, candidateAssertions: Iterable<string>) => AlternativesMatch;
|
|
57
|
+
/** How well a candidate answered the query. `exact` matched every member, accents included. */
|
|
58
|
+
export type MatchTier = "exact" | "eligible" | "relaxed";
|
|
59
|
+
/** A subject to rank: an opaque id and the assertions annotating it. */
|
|
60
|
+
export interface CandidateAnnotations {
|
|
61
|
+
id: string;
|
|
62
|
+
assertions: readonly string[];
|
|
63
|
+
}
|
|
64
|
+
export interface RankedCandidate {
|
|
65
|
+
id: string;
|
|
66
|
+
tier: MatchTier;
|
|
67
|
+
match: BundleMatch;
|
|
68
|
+
}
|
|
69
|
+
export interface BundleSearch {
|
|
70
|
+
/** The strongest tier present. Absent when no candidate carried even one member. */
|
|
71
|
+
tier?: MatchTier;
|
|
72
|
+
results: readonly RankedCandidate[];
|
|
73
|
+
/** True when nothing was eligible and the relaxed tier was served in its place. */
|
|
74
|
+
relaxed: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Constitutive members NO candidate in this corpus carries. Populated only when nothing was
|
|
77
|
+
* eligible, because that is when it is the answer: these are the conjuncts the corpus cannot
|
|
78
|
+
* satisfy, and naming them is what turns an empty result into a usable one.
|
|
79
|
+
*/
|
|
80
|
+
blocking: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
export interface SearchOptions {
|
|
83
|
+
/** Refuse the relaxed tier: return nothing rather than a labelled partial answer. */
|
|
84
|
+
strict?: boolean;
|
|
85
|
+
/** Truncate the ranked list. Applied after ordering, so it never changes which result is first. */
|
|
86
|
+
limit?: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Rank a corpus against a bundle under the published rule. Deterministic: the same corpus in any
|
|
90
|
+
* input order returns the same list.
|
|
91
|
+
*/
|
|
92
|
+
export declare const searchBundle: (bundle: CanonicalBundle, candidates: Iterable<CandidateAnnotations>, options?: SearchOptions) => BundleSearch;
|
|
93
|
+
export interface MatchTestCandidate {
|
|
94
|
+
assertions: readonly string[];
|
|
95
|
+
id: string;
|
|
96
|
+
}
|
|
97
|
+
export interface MatchTestVector {
|
|
98
|
+
candidates: readonly MatchTestCandidate[];
|
|
99
|
+
expectedEligibleOrder: readonly string[];
|
|
100
|
+
id: string;
|
|
101
|
+
query: CanonicalBundle;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Executable test vectors published for consumers implementing the same rule in another language.
|
|
105
|
+
* One is residential, one commercial; the matcher itself has no market branch.
|
|
106
|
+
*/
|
|
107
|
+
export declare const MATCH_TEST_VECTORS: readonly MatchTestVector[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const SEARCH_CORPUS_SNAPSHOT: "2026-07-31";
|
|
2
|
+
/**
|
|
3
|
+
* The fixture evidences searched language and admission need. Taxonomy authors the concept boundary;
|
|
4
|
+
* neither an individual phrase nor the 244-query fixture as a whole is treated as that definition.
|
|
5
|
+
*/
|
|
6
|
+
export declare const searchCorpusEvidence: (phrases: string) => string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { CanonicalAlternatives, CanonicalBundle } from "./match.js";
|
|
2
|
+
export declare const TASTE_ASSERTION_CONTRACT_VERSION: "1.0.0";
|
|
3
|
+
export declare const TAXONOMY_SNAPSHOT_VERSION: "2026-08-31";
|
|
4
|
+
export type AssertionRole = "taste" | "project_context" | "requirement";
|
|
5
|
+
export type AssertionAuthority = "explicit" | "behavioral" | "inferred";
|
|
6
|
+
export type AssertionPolarity = "positive" | "negative" | "never";
|
|
7
|
+
export type AssertionScope = {
|
|
8
|
+
kind: "person";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "household";
|
|
11
|
+
householdId: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "project";
|
|
14
|
+
projectId: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "room_type";
|
|
17
|
+
roomTypeId: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "room";
|
|
20
|
+
roomId: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "surface";
|
|
23
|
+
roomId: string;
|
|
24
|
+
surfaceId: string;
|
|
25
|
+
};
|
|
26
|
+
export interface AssertionProvenance {
|
|
27
|
+
/** The application, import or instrument that produced the observation. */
|
|
28
|
+
sourceSystem: string;
|
|
29
|
+
/** Version of the source data, prompt, fixture or export used when the evidence was observed. */
|
|
30
|
+
sourceSnapshotVersion: string;
|
|
31
|
+
sourceRecordId?: string;
|
|
32
|
+
sourceUrl?: string;
|
|
33
|
+
/** Optional id for the observation from which this assertion was proposed. */
|
|
34
|
+
observationId?: string;
|
|
35
|
+
}
|
|
36
|
+
interface SourceMeaning {
|
|
37
|
+
/** Source vocabulary field, before canonicalisation. */
|
|
38
|
+
sourceField: string;
|
|
39
|
+
/** Verbatim source value; never replaced by the canonical label. */
|
|
40
|
+
sourceValue: string;
|
|
41
|
+
/** Version of the source-specific mapping policy. */
|
|
42
|
+
crosswalkVersion: string;
|
|
43
|
+
/** Version of the canonical vocabulary against which mapping ran. */
|
|
44
|
+
taxonomySnapshotVersion: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ResolvedAssertionMeaning extends SourceMeaning {
|
|
47
|
+
kind: "resolved";
|
|
48
|
+
canonical: CanonicalBundle;
|
|
49
|
+
}
|
|
50
|
+
export interface AmbiguousAssertionMeaning extends SourceMeaning {
|
|
51
|
+
kind: "ambiguous";
|
|
52
|
+
alternatives: CanonicalAlternatives;
|
|
53
|
+
}
|
|
54
|
+
export interface UnresolvedAssertionMeaning extends SourceMeaning {
|
|
55
|
+
kind: "unresolved";
|
|
56
|
+
}
|
|
57
|
+
export type AssertionMeaning = ResolvedAssertionMeaning | AmbiguousAssertionMeaning | UnresolvedAssertionMeaning;
|
|
58
|
+
interface PortableAssertionBase {
|
|
59
|
+
/** Stable application-owned identifier for this assertion event. */
|
|
60
|
+
id: string;
|
|
61
|
+
contractVersion: typeof TASTE_ASSERTION_CONTRACT_VERSION;
|
|
62
|
+
actorId: string;
|
|
63
|
+
/** Whose taste or project view the assertion describes; never inferred from actorId. */
|
|
64
|
+
subjectId: string;
|
|
65
|
+
/** Project context is explicit even when the assertion's scope is durable and personal. */
|
|
66
|
+
projectId: string;
|
|
67
|
+
authority: AssertionAuthority;
|
|
68
|
+
polarity: AssertionPolarity;
|
|
69
|
+
scope: AssertionScope;
|
|
70
|
+
meaning: AssertionMeaning;
|
|
71
|
+
provenance: AssertionProvenance;
|
|
72
|
+
observedAt: string;
|
|
73
|
+
confirmedAt?: string;
|
|
74
|
+
validFrom?: string;
|
|
75
|
+
validUntil?: string;
|
|
76
|
+
/** Logical withdrawal is append-only; physical privacy deletion is an application concern. */
|
|
77
|
+
retractedAt?: string;
|
|
78
|
+
supersedesAssertionId?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface TasteAssertion extends PortableAssertionBase {
|
|
81
|
+
role: "taste";
|
|
82
|
+
}
|
|
83
|
+
export interface ProjectContextAssertion extends PortableAssertionBase {
|
|
84
|
+
role: "project_context";
|
|
85
|
+
}
|
|
86
|
+
export type RequirementOperator = "equals" | "not_equals" | "at_least" | "at_most" | "includes" | "excludes";
|
|
87
|
+
export interface RequirementConstraint {
|
|
88
|
+
operator: RequirementOperator;
|
|
89
|
+
value: boolean | number | string;
|
|
90
|
+
unit?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface RequirementAssertion extends PortableAssertionBase {
|
|
93
|
+
role: "requirement";
|
|
94
|
+
/** The dimension is canonical meaning; the source-owned required value remains typed here. */
|
|
95
|
+
constraint: RequirementConstraint;
|
|
96
|
+
}
|
|
97
|
+
/** Portable evidence semantics only. Ranking weights and serving policy belong to each consumer. */
|
|
98
|
+
export type PortableTasteAssertion = TasteAssertion | ProjectContextAssertion | RequirementAssertion;
|
|
99
|
+
export interface PortableAssertionTestVector {
|
|
100
|
+
assertion: PortableTasteAssertion;
|
|
101
|
+
id: string;
|
|
102
|
+
expected: {
|
|
103
|
+
actorId: string;
|
|
104
|
+
subjectId: string;
|
|
105
|
+
projectId: string;
|
|
106
|
+
role: AssertionRole;
|
|
107
|
+
meaningKind: AssertionMeaning["kind"];
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Executable fixtures for consumers serialising the portable envelope in another repository. */
|
|
111
|
+
export declare const PORTABLE_ASSERTION_TEST_VECTORS: readonly PortableAssertionTestVector[];
|
|
112
|
+
export {};
|
package/dist/taste.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { assertionMeaningFrom, type AmbiguousCanonicalisation, type CanonicalisationInput, type CanonicalisationVerdict, type DeclinedCanonicalisation, type ResolvedCanonicalisation, type UnresolvedCanonicalisation, } from "./canonicalisation.js";
|
|
2
|
+
export { type AmbiguousAssertionMeaning, type AssertionAuthority, type AssertionMeaning, type AssertionPolarity, type AssertionProvenance, type AssertionRole, type AssertionScope, type PortableAssertionTestVector, type PortableTasteAssertion, PORTABLE_ASSERTION_TEST_VECTORS, type ProjectContextAssertion, type RequirementAssertion, type RequirementConstraint, type RequirementOperator, type ResolvedAssertionMeaning, TAXONOMY_SNAPSHOT_VERSION, TASTE_ASSERTION_CONTRACT_VERSION, type TasteAssertion, type UnresolvedAssertionMeaning, } from "./taste-assertion.js";
|
package/dist/term.d.ts
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/** An external vocabulary this package crosswalks out to. */
|
|
2
|
+
export type ExternalScheme =
|
|
3
|
+
/** Getty Art & Architecture Thesaurus. ODC-By 1.0 - redistributable with attribution. */
|
|
4
|
+
"aat"
|
|
5
|
+
/** Uniclass 2015 (NBS). CC BY-ND 4.0 - referenced by code, never adapted. */
|
|
6
|
+
| "uniclass"
|
|
7
|
+
/** Wikidata Q-items. CC0. */
|
|
8
|
+
| "wikidata";
|
|
9
|
+
/**
|
|
10
|
+
* SKOS mapping strength, using SKOS's own vocabulary so the published RDF is a direct rendering
|
|
11
|
+
* rather than a translation. `exact` asserts the two terms are interchangeable in retrieval;
|
|
12
|
+
* `close` asserts they are near enough to substitute in most contexts but not all; `broad` and
|
|
13
|
+
* `narrow` assert containment in the named direction; `related` asserts association only.
|
|
14
|
+
*/
|
|
15
|
+
export type MatchStrength = "exact" | "close" | "broad" | "narrow" | "related";
|
|
16
|
+
export interface ExternalMapping {
|
|
17
|
+
scheme: ExternalScheme;
|
|
18
|
+
/** The external identifier, verbatim: an AAT subject id, a Uniclass code, a Wikidata Q-number. */
|
|
19
|
+
identifier: string;
|
|
20
|
+
/** The external vocabulary's own label for that identifier, verbatim and unedited. */
|
|
21
|
+
label: string;
|
|
22
|
+
match: MatchStrength;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Where a term's content came from, and when. Ranks the SOURCE, never the accuracy: a term lifted
|
|
26
|
+
* verbatim from an upstream dictionary is `vendored` whether or not the upstream got it right.
|
|
27
|
+
* Using this field as a proxy for correctness is how confident wrong answers get made.
|
|
28
|
+
*/
|
|
29
|
+
export type SourceBasis =
|
|
30
|
+
/** Copied unedited from a named upstream dictionary at a stated snapshot. */
|
|
31
|
+
"vendored"
|
|
32
|
+
/** Written here, from scratch, by a named editor. */
|
|
33
|
+
| "authored"
|
|
34
|
+
/** Written here, but derived from measured usage in a named corpus. */
|
|
35
|
+
| "evidenced"
|
|
36
|
+
/** Written here from one or more named authoritative references, without adopting their term. */
|
|
37
|
+
| "referenced"
|
|
38
|
+
/** Adopted from a published external standard, with its identifier carried. */
|
|
39
|
+
| "standard";
|
|
40
|
+
export interface TermProvenance {
|
|
41
|
+
basis: SourceBasis;
|
|
42
|
+
/**
|
|
43
|
+
* The upstream standard, reference or source. A public standard or vocabulary is named outright
|
|
44
|
+
* and can be re-read by anyone. A private source is written as its kind and an opaque id, e.g.
|
|
45
|
+
* "PRICE_TIERS in a materials-retrieval system (src-05)": the id is stable, so every term citing
|
|
46
|
+
* one source can be shown to cite the same thing, and re-reading it means the steward resolving
|
|
47
|
+
* the id at the `snapshot` date. The date lives in `snapshot` and is never repeated here.
|
|
48
|
+
*/
|
|
49
|
+
source: string;
|
|
50
|
+
/** ISO date the source was read. Absent only for `authored`. */
|
|
51
|
+
snapshot?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A canonical identity governed by ColorScope rather than Taxonomy.
|
|
55
|
+
*
|
|
56
|
+
* This is authored data, not an inference from provenance prose. A vendored source explains where
|
|
57
|
+
* a row came from; it does not by itself decide which package owns the durable identifier.
|
|
58
|
+
*/
|
|
59
|
+
export interface ColorScopeAuthority {
|
|
60
|
+
authority: "colorscope";
|
|
61
|
+
authorityUri: "https://colorscope.materialinstruments.com";
|
|
62
|
+
identifier: string;
|
|
63
|
+
referenceKind: keyof ColorScopeReferenceKinds;
|
|
64
|
+
}
|
|
65
|
+
interface ColorScopeReferenceKinds {
|
|
66
|
+
"chroma-level": true;
|
|
67
|
+
"color-family": true;
|
|
68
|
+
"lightness-level": true;
|
|
69
|
+
"named-color": true;
|
|
70
|
+
"palette-mood": true;
|
|
71
|
+
"temperature-level": true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A DATED NEGATIVE RESULT: a scheme that was searched for this term and did not have it.
|
|
75
|
+
*
|
|
76
|
+
* This is the field that makes the gap publishable. ADR 0001 section 8 softens the planned
|
|
77
|
+
* invariant "every STYLE and PERIOD term carries an AAT mapping" to "carries a mapping OR carries
|
|
78
|
+
* one of these", because AAT has no consumer interiors coverage at all - a live search for
|
|
79
|
+
* *japandi* returns nothing, and *farmhouse*, *transitional* and *coastal* are absent as interior
|
|
80
|
+
* idioms. Without this field an unmapped term is indistinguishable from an unchecked one, and the
|
|
81
|
+
* two are opposites: one is evidence and the other is a to-do.
|
|
82
|
+
*
|
|
83
|
+
* A dated negative against the best art-and-architecture thesaurus in the world is the clearest
|
|
84
|
+
* available statement of why this vocabulary needs to exist.
|
|
85
|
+
*/
|
|
86
|
+
export interface SearchedWithout {
|
|
87
|
+
scheme: ExternalScheme;
|
|
88
|
+
/** ISO date the search was run. */
|
|
89
|
+
date: string;
|
|
90
|
+
/** What was searched, so a reader can repeat it. */
|
|
91
|
+
query: string;
|
|
92
|
+
/** Where the search was run, e.g. the SPARQL endpoint. */
|
|
93
|
+
via: string;
|
|
94
|
+
}
|
|
95
|
+
export type TermStatus = "active" | "deprecated";
|
|
96
|
+
/**
|
|
97
|
+
* A band, never a count.
|
|
98
|
+
*
|
|
99
|
+
* THE SCALE, stated once so every axis uses the same one. Measured against the corpus named on the
|
|
100
|
+
* term: `dominant` is the top decile of values by frequency, `common` the next, `occasional` a
|
|
101
|
+
* value that appears regularly, `rare` one that barely does, and `unattested` a value the corpus
|
|
102
|
+
* never uses. Set `usage` only where the named corpus can actually speak to the term: omitting it
|
|
103
|
+
* says "not measured here", which is honest, whereas `unattested` against an unrelated corpus
|
|
104
|
+
* would read as a claim about the term rather than about the corpus. The usage figures behind these
|
|
105
|
+
* derive from a materials-marketplace catalogue and a furnishings retailer's room schemes; publishing a raw
|
|
106
|
+
* number would publish a figure from someone else's business. The band carries the signal a reader actually needs - is this term common or rare -
|
|
107
|
+
* and nothing more.
|
|
108
|
+
*/
|
|
109
|
+
export type UsageBand = "dominant" | "common" | "occasional" | "rare" | "unattested";
|
|
110
|
+
export interface TermUsage {
|
|
111
|
+
band: UsageBand;
|
|
112
|
+
/** The corpus the band was measured against, and when. */
|
|
113
|
+
corpus: string;
|
|
114
|
+
snapshot: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Why a term carries no usage band when the corpus plainly has something to say about it.
|
|
118
|
+
*
|
|
119
|
+
* The distinguishing case is `style.modernism`. A furnishings retailer tags a dominant share of
|
|
120
|
+
* its schemes `Modern`, but that is the crosswalk row that DECLINES: `Modern` reads as either
|
|
121
|
+
* `style.modernism` or `style.contemporary` and the source never recorded which. Attributing the
|
|
122
|
+
* usage would smuggle in the guess the crosswalk refused to make.
|
|
123
|
+
*
|
|
124
|
+
* Without this field, a reader sees "not measured" and cannot tell that apart from "nobody looked",
|
|
125
|
+
* and those are opposite claims. Same reasoning as `searchedWithout`: an absence somebody reasoned
|
|
126
|
+
* their way to is evidence, and an absence nobody has examined is a to-do.
|
|
127
|
+
*/
|
|
128
|
+
export interface UsageWithheld {
|
|
129
|
+
reason: string;
|
|
130
|
+
/** The corpus that would otherwise have supplied a band. */
|
|
131
|
+
corpus: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A period's extent, as four numbers rather than two.
|
|
135
|
+
*
|
|
136
|
+
* Two dates cannot answer the questions consumers actually ask. "Is 1832 Georgian?" wants the
|
|
137
|
+
* outer edge; "give me a canonical date for Georgian" wants the core. So each period carries a
|
|
138
|
+
* fuzzy interval: `earliest` and `latest` bound what could defensibly be called this period,
|
|
139
|
+
* `start` and `end` bound what uncontroversially is.
|
|
140
|
+
*
|
|
141
|
+
* `end` and `latest` are NULLABLE, and that is load-bearing rather than tidy. An open-ended period
|
|
142
|
+
* closed with the current year needs an arbitrary edit at every release, and the edit is a lie in
|
|
143
|
+
* between: `contemporary` does not end in 2026, it has not ended.
|
|
144
|
+
*
|
|
145
|
+
* SIBLING SPANS ARE ALLOWED TO OVERLAP, deliberately. Georgian and Regency genuinely do. Resolving
|
|
146
|
+
* "what period is 1800?" is therefore an accessor's job, not the data's - see `periodAt`, which
|
|
147
|
+
* prefers the narrowest match and then the un-hedged one. Leaving that to each consumer would get
|
|
148
|
+
* it reinvented differently every time.
|
|
149
|
+
*/
|
|
150
|
+
export interface PeriodSpan {
|
|
151
|
+
/** The earliest year anyone would defensibly call this period. */
|
|
152
|
+
earliest: number;
|
|
153
|
+
/** The first year it uncontroversially is this period. */
|
|
154
|
+
start: number;
|
|
155
|
+
/** The last year it uncontroversially is. Null when the period has not ended. */
|
|
156
|
+
end: number | null;
|
|
157
|
+
/** The last year anyone would defensibly call it this. Null when the period has not ended. */
|
|
158
|
+
latest: number | null;
|
|
159
|
+
/** True when the boundaries are conventional rather than evidenced by an event. */
|
|
160
|
+
circa: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Where these dates apply, as ISO 3166-1 alpha-2, or `global`. Regional variance beyond this
|
|
163
|
+
* belongs in NARROWER TERMS with their own spans, never in a region-to-span map on one term: a
|
|
164
|
+
* map invites partial coverage, and a narrower term is something a relation can point at.
|
|
165
|
+
*/
|
|
166
|
+
regions: readonly string[];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* What KIND of thing a style term is, because the three kinds have different truth conditions and
|
|
170
|
+
* conflating them is most of why style vocabularies rot.
|
|
171
|
+
*
|
|
172
|
+
* A `movement` has an art-historical literature, dates and adherents, and its boundaries are
|
|
173
|
+
* arguable in the way a scholarly claim is arguable. A `tradition` is a body of regional or
|
|
174
|
+
* vernacular practice, defined by what is done rather than by what was declared. A `market-idiom`
|
|
175
|
+
* is a prototype category the trade converged on, defined by resemblance to central cases and by
|
|
176
|
+
* nothing else - it has no manifesto to appeal to, and asking for necessary and sufficient
|
|
177
|
+
* conditions misunderstands it.
|
|
178
|
+
*
|
|
179
|
+
* Publishing the kind does two things. It tells a reader what sort of disagreement to expect: a
|
|
180
|
+
* dispute about Brutalism is a dispute about the record, a dispute about Farmhouse is a dispute
|
|
181
|
+
* about a prototype, and neither settles like the other. And it is the grouping a browse page needs
|
|
182
|
+
* once STYLE passes about fifty terms, when a flat alphabetical list stops being navigable.
|
|
183
|
+
*/
|
|
184
|
+
export type StyleKind = "movement" | "tradition" | "market-idiom";
|
|
185
|
+
/**
|
|
186
|
+
* How a decorator treats this element: the habit a surface is handled by when a scheme is applied
|
|
187
|
+
* to it. A decorating habit, and nothing else.
|
|
188
|
+
*
|
|
189
|
+
* It is NOT part-of composition, which `related` already carries and which cuts across this: a
|
|
190
|
+
* cornice is part of the wall and is painted with the ceiling, so its part-of edge and its
|
|
191
|
+
* placement disagree on purpose. It is NOT compositional role either - whether a surface is the
|
|
192
|
+
* field, the accent or the thing left alone is a decision a scheme makes about one room, and it
|
|
193
|
+
* changes from room to room, while placement is a property of the element itself.
|
|
194
|
+
*
|
|
195
|
+
* `field` is the broad plane a scheme's main colour lands on. `joinery` is shop-made woodwork
|
|
196
|
+
* handled as one body with its own colour. `trim` is the run of profile and opening frame a
|
|
197
|
+
* decorator cuts in by hand. `overhead` and `underfoot` are the two planes a room is bounded by
|
|
198
|
+
* above and below, each with its own conventions. `unpainted` is an element a decorator masks or
|
|
199
|
+
* works around rather than coats - glass, light fittings, sanitaryware and electrical accessories.
|
|
200
|
+
*
|
|
201
|
+
* `feature` is RESERVED and no element carries it. A feature wall is a scheme's decision about one
|
|
202
|
+
* room, not a fact about walls, so assigning it here would be authoring taste as canon. The value
|
|
203
|
+
* exists because consumers need a name for that decision in their own data; taxonomy supplies the
|
|
204
|
+
* word without ever asserting it.
|
|
205
|
+
*/
|
|
206
|
+
export type ElementPlacement = "field" | "joinery" | "trim" | "overhead" | "underfoot" | "feature" | "unpainted";
|
|
207
|
+
export interface Term {
|
|
208
|
+
/** Lowercase kebab-case path segment used only inside this concept's canonical URI. */
|
|
209
|
+
slug: string;
|
|
210
|
+
label: string;
|
|
211
|
+
/** Present when another package, currently ColorScope, owns this term's canonical identity. */
|
|
212
|
+
canonicalAuthority?: ColorScopeAuthority;
|
|
213
|
+
/**
|
|
214
|
+
* Parent term id. Carries TAXONOMIC SUBSUMPTION ONLY - every instance of this term must be an
|
|
215
|
+
* instance of the parent. Part-of, used-in, typically-found-in and evokes are relations, and
|
|
216
|
+
* belong in `related`, never here. See ADR 0001.
|
|
217
|
+
*/
|
|
218
|
+
broaderSlug?: string;
|
|
219
|
+
/** One sentence, stating the boundary rather than restating the label. */
|
|
220
|
+
definition?: string;
|
|
221
|
+
/** Other correct names for this term. */
|
|
222
|
+
altLabels?: readonly string[];
|
|
223
|
+
/**
|
|
224
|
+
* Labels observed in the wild that mean this term but are not correct names for it: upstream
|
|
225
|
+
* spellings, typos, and off-canon coinages. Published, because a crosswalk that hides the wrong
|
|
226
|
+
* forms cannot be used to clean anyone's data.
|
|
227
|
+
*/
|
|
228
|
+
seenAs?: readonly string[];
|
|
229
|
+
/** Non-taxonomic relations to other canonical terms, by predicate. */
|
|
230
|
+
related?: readonly TermRelation[];
|
|
231
|
+
external?: readonly ExternalMapping[];
|
|
232
|
+
/**
|
|
233
|
+
* Schemes searched for this term that did not have it. An absent `external` means nobody looked;
|
|
234
|
+
* a `searchedWithout` entry means somebody looked and the term is not there. Published, dated,
|
|
235
|
+
* and repeatable.
|
|
236
|
+
*/
|
|
237
|
+
searchedWithout?: readonly SearchedWithout[];
|
|
238
|
+
provenance?: TermProvenance;
|
|
239
|
+
/** ISO date this term first entered the published Taxonomy corpus; internal authoring metadata. */
|
|
240
|
+
introducedAt?: string;
|
|
241
|
+
/** ISO date this term's governed content was last reviewed; internal authoring metadata. */
|
|
242
|
+
reviewedAt?: string;
|
|
243
|
+
/**
|
|
244
|
+
* Position on this term's own ordered scale, low to high. Present only on ordinal axes.
|
|
245
|
+
*
|
|
246
|
+
* MOST AXES HERE ARE UNORDERED SETS and must stay that way: `style.brutalism` is not greater
|
|
247
|
+
* than `style.rustic`, and a consumer that sorted them would be inventing a claim. TIER is
|
|
248
|
+
* different - value really is below premium, and "one rung cheaper" is a question people ask of
|
|
249
|
+
* it - so the order is data rather than an accident of array position.
|
|
250
|
+
*
|
|
251
|
+
* Ranks are NOT comparable across facets. `tier.market.premium` and `tier.chain-scale.upscale`
|
|
252
|
+
* both sit at 3 on their own ladders and are not the same claim about the world; the ladders
|
|
253
|
+
* have different lengths and different subjects. Compare within a facet or not at all.
|
|
254
|
+
*
|
|
255
|
+
* Dense from 1 within a facet, and either present on every term there or on none - both checked
|
|
256
|
+
* in `axes.test.ts`, because a half-ranked facet silently drops its unranked half from any sort.
|
|
257
|
+
*/
|
|
258
|
+
rank?: number;
|
|
259
|
+
/** Present on PERIOD terms only. */
|
|
260
|
+
span?: PeriodSpan;
|
|
261
|
+
/** Present on STYLE terms only. */
|
|
262
|
+
styleKind?: StyleKind;
|
|
263
|
+
/** Present on ELEMENT terms only, and on every one of them. */
|
|
264
|
+
placement?: ElementPlacement;
|
|
265
|
+
usage?: TermUsage;
|
|
266
|
+
/**
|
|
267
|
+
* Set instead of `usage` when a corpus has evidence but it cannot honestly be attributed to this
|
|
268
|
+
* term. Never set alongside `usage`.
|
|
269
|
+
*/
|
|
270
|
+
usageWithheld?: UsageWithheld;
|
|
271
|
+
status?: TermStatus;
|
|
272
|
+
/** The term that replaced this one. Required when status is "deprecated". */
|
|
273
|
+
supersededByReference?: string;
|
|
274
|
+
/** Why the term is deprecated, or any other governance note a reader needs. */
|
|
275
|
+
note?: string;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The named predicates a term may carry to another canonical term. Everything that is not ISA
|
|
279
|
+
* lives here, so `broaderSlug` never silently becomes a bag of loose associations.
|
|
280
|
+
*/
|
|
281
|
+
export type RelationPredicate =
|
|
282
|
+
/** This element is physically a component of that element. */
|
|
283
|
+
"part-of"
|
|
284
|
+
/** This application or element is typically found in that space. */
|
|
285
|
+
| "typically-in"
|
|
286
|
+
/** This term is a material-suitability claim about that space. */
|
|
287
|
+
| "suitable-for"
|
|
288
|
+
/**
|
|
289
|
+
* The subject exists to serve that sector's market, wherever the building that contains it
|
|
290
|
+
* stands: a hotel room in a mixed-use tower still serves hospitality, an operating room serves
|
|
291
|
+
* healthcare whoever the landlord is. An IDENTITY claim, never an occurrence claim - "occurs
|
|
292
|
+
* only in that sector" is false for almost any room (a multifamily amenity floor holds
|
|
293
|
+
* restrooms, a commercial kitchen and a gym inside `sector.residential`), which is why most
|
|
294
|
+
* rooms rightly carry no edge.
|
|
295
|
+
*
|
|
296
|
+
* ABSENCE IS A CLAIM, NOT A GAP. A SPACE term carrying no serves edge and inheriting none
|
|
297
|
+
* through `broaderSlug` asserts that no market is part of its identity: it serves whatever
|
|
298
|
+
* market its project serves, and a consumer must read it as unrestricted - fail-open - never as
|
|
299
|
+
* a restriction nobody wrote down. What makes that reading safe is `MARKET_NEUTRAL_SPACES`
|
|
300
|
+
* (`axes/space/market-neutral.ts`, with the authoring criterion): every SPACE term either
|
|
301
|
+
* carries an edge or is registered there as reviewed-neutral, and the build fails naming any
|
|
302
|
+
* term that is neither. A reviewed absence and an unreviewed one cannot be confused - the same
|
|
303
|
+
* move `searchedWithout` and `usageWithheld` make.
|
|
304
|
+
*
|
|
305
|
+
* Authored on SPACE only, and pinned there by test. The claim's shape would generalise, but
|
|
306
|
+
* TYPOLOGY must never carry it: sector moves when the tenant moves and typology does not, so a
|
|
307
|
+
* typology-to-sector edge dies on conversion - ADR 0001's converted warehouse full of flats is
|
|
308
|
+
* the standing counterexample.
|
|
309
|
+
*/
|
|
310
|
+
| "serves"
|
|
311
|
+
/** This style, idiom or scheme draws on that place idiom, period, building typology or season. */
|
|
312
|
+
| "evokes"
|
|
313
|
+
/** This style descends from that style. */
|
|
314
|
+
| "influenced-by"
|
|
315
|
+
/** This style is a deliberate blend of two or more named styles, and is defined by being so. */
|
|
316
|
+
| "blend-of"
|
|
317
|
+
/** This surface reads as a material it is not made of. */
|
|
318
|
+
| "reads-as"
|
|
319
|
+
/** Association with no stronger claim available. */
|
|
320
|
+
| "related-to";
|
|
321
|
+
/**
|
|
322
|
+
* How much of the thing a relation accounts for.
|
|
323
|
+
*
|
|
324
|
+
* Without this, a room whose whole scheme is built on a Japanese reference and a room with one
|
|
325
|
+
* rattan pendant both carry `evokes(place.region.japanese)`, and retrieval cannot tell the theme
|
|
326
|
+
* from the garnish. It matters more as annotation density rises than it does today, which is why
|
|
327
|
+
* it is optional: an edge with no role stated is a claim about presence and nothing more.
|
|
328
|
+
*/
|
|
329
|
+
export type RelationRole =
|
|
330
|
+
/** Remove it and the thing stops being what it is. */
|
|
331
|
+
"constitutive"
|
|
332
|
+
/** Present and deliberate, but the scheme survives without it. */
|
|
333
|
+
| "accent";
|
|
334
|
+
export interface TermRelation {
|
|
335
|
+
predicate: RelationPredicate;
|
|
336
|
+
/**
|
|
337
|
+
* Fully qualified target: `<axis>.<term id>`, or `<axis>.<facet>.<term id>` on a faceted axis.
|
|
338
|
+
*
|
|
339
|
+
* QUALIFICATION IS LOAD-BEARING, not a formatting preference. One `evokes` predicate works only
|
|
340
|
+
* because the target's axis types the reference: `evokes(place.region.nordic)` says "place"
|
|
341
|
+
* exactly once, and a consumer wanting the three-way split into place, period and typology
|
|
342
|
+
* reference recovers it losslessly by joining on the target's axis. A bare `nordic` would make
|
|
343
|
+
* that join impossible and would force a predicate per axis, which grows with the axis count.
|
|
344
|
+
*
|
|
345
|
+
* Twenty-one bare ids currently appear on more than one axis - `wall` on both application and
|
|
346
|
+
* element, `restaurant` on both space and typology, `contemporary` on both style and period -
|
|
347
|
+
* and every one of those pairs is deliberate. Qualification is what keeps them from colliding.
|
|
348
|
+
*/
|
|
349
|
+
target: string;
|
|
350
|
+
role?: RelationRole;
|
|
351
|
+
}
|
|
352
|
+
export {};
|