glossarist 0.4.39 → 0.4.41

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.
@@ -1,51 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
-
3
- export class GcrStatistics extends GlossaristModel {
4
- constructor(data = {}) {
5
- super();
6
- this.totalConcepts = data.total_concepts ?? data.totalConcepts ?? 0;
7
- this.conceptsWithDefinitions = data.concepts_with_definitions ?? data.conceptsWithDefinitions ?? 0;
8
- this.conceptsByStatus = data.concepts_by_status ?? data.conceptsByStatus ?? {};
9
- }
10
-
11
- get total_concepts() { return this.totalConcepts; }
12
- get concepts_with_definitions() { return this.conceptsWithDefinitions; }
13
- get concepts_by_status() { return this.conceptsByStatus; }
14
-
15
- toJSON() {
16
- const obj = { total_concepts: this.totalConcepts };
17
- if (this.conceptsWithDefinitions > 0) {
18
- obj.concepts_with_definitions = this.conceptsWithDefinitions;
19
- }
20
- if (Object.keys(this.conceptsByStatus).length > 0) {
21
- obj.concepts_by_status = this.conceptsByStatus;
22
- }
23
- return obj;
24
- }
25
-
26
- static fromJSON(data) {
27
- return new GcrStatistics(data);
28
- }
29
-
30
- static fromConcepts(concepts) {
31
- const langs = new Set();
32
- let withDefs = 0;
33
- const byStatus = {};
34
-
35
- for (const concept of concepts) {
36
- for (const lang of concept.languages) {
37
- langs.add(lang);
38
- const lc = concept.localization(lang);
39
- if (lc && lc.definitions.length > 0) withDefs++;
40
- const status = lc?.entryStatus ?? 'unknown';
41
- byStatus[status] = (byStatus[status] ?? 0) + 1;
42
- }
43
- }
44
-
45
- return new GcrStatistics({
46
- total_concepts: concepts.length,
47
- concepts_with_definitions: withDefs,
48
- concepts_by_status: byStatus,
49
- });
50
- }
51
- }
@@ -1,71 +0,0 @@
1
- // Hyperedge color resolver — resolves display colors for a
2
- // PartitiveHyperedge based on its enumeration and the carrying
3
- // concept's category color.
4
- //
5
- // Closed hyperedges render at full intensity (the relationship is
6
- // definitive — these are ALL the parts). Open hyperedges render at
7
- // reduced intensity (the relationship is partial — other parts may
8
- // exist).
9
- //
10
- // Marker-based styling (double = thicker stroke, dashed = dashed
11
- // pattern) is the caller's responsibility; this resolver returns
12
- // the color only, plus a `pattern` hint for marker-based styling.
13
-
14
- import { categoryColorPair } from './relation-colors.js';
15
-
16
- const ENUMERATION_INTENSITY = Object.freeze({
17
- closed: 1.0,
18
- open: 0.55,
19
- });
20
-
21
- const DEFAULT_CATEGORY = 'hierarchical';
22
-
23
- export function resolveHyperedgeColor(hyperedge, options = {}) {
24
- if (!hyperedge) return null;
25
-
26
- const category = options.category ?? DEFAULT_CATEGORY;
27
- const base = categoryColorPair(category, options.overrides);
28
- if (!base) return null;
29
-
30
- const intensity = ENUMERATION_INTENSITY[hyperedge.enumeration] ?? 1.0;
31
- const pattern = derivePattern(hyperedge.markers);
32
-
33
- return {
34
- light: tint(base.light, intensity),
35
- dark: tint(base.dark, intensity),
36
- pattern,
37
- };
38
- }
39
-
40
- function derivePattern(markers) {
41
- const arr = markers ?? [];
42
- if (arr.includes('dashed')) return 'dashed';
43
- if (arr.includes('double')) return 'double';
44
- return null;
45
- }
46
-
47
- // Linear interpolation between the base color and white (light mode)
48
- // or between the base and a neutral (dark mode). Pure-string inputs
49
- // return the original color when intensity === 1.0.
50
- function tint(hex, ratio) {
51
- if (ratio >= 1.0) return hex;
52
- const rgb = parseHex(hex);
53
- if (!rgb) return hex;
54
-
55
- const target = [255, 255, 255];
56
- const mixed = rgb.map((c, i) =>
57
- Math.round(c + (target[i] - c) * (1 - ratio)));
58
- return '#' + mixed.map(c => c.toString(16).padStart(2, '0')).join('');
59
- }
60
-
61
- function parseHex(s) {
62
- if (typeof s !== 'string') return null;
63
- const m = s.match(/^#?([0-9a-f]{6})$/i);
64
- if (!m) return null;
65
- const n = m[1];
66
- return [
67
- parseInt(n.slice(0, 2), 16),
68
- parseInt(n.slice(2, 4), 16),
69
- parseInt(n.slice(4, 6), 16),
70
- ];
71
- }
@@ -1,187 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
- import { Designation } from './designation.js';
3
- import { DetailedDefinition } from './detailed-definition.js';
4
- import { ConceptSource } from './concept-source.js';
5
- import { ConceptDate } from './concept-date.js';
6
- import { NonVerbRep } from './non-verb-rep.js';
7
- import { RelatedConcept } from './related-concept.js';
8
-
9
- function wrapAs(Cls) {
10
- return item => item instanceof Cls ? item : new Cls(item);
11
- }
12
-
13
- const LOC_WIRE_NAMES = Object.freeze({
14
- entryStatus: 'entry_status',
15
- reviewType: 'review_type',
16
- lineageSourceSimilarity: 'lineage_source_similarity',
17
- reviewDate: 'review_date',
18
- reviewDecisionDate: 'review_decision_date',
19
- reviewDecisionEvent: 'review_decision_event',
20
- reviewStatus: 'review_status',
21
- reviewDecision: 'review_decision',
22
- reviewDecisionNotes: 'review_decision_notes',
23
- });
24
-
25
- export class LocalizedConcept extends GlossaristModel {
26
- // Scalar metadata fields the diff layer tracks per localization.
27
- // Adding a new scalar field requires only appending its name here;
28
- // diff/patch/similarity pick it up automatically.
29
- // (Invariant N2 — TODO.hyperedges-v2/07.)
30
- static get DIFF_FIELDS() {
31
- return Object.freeze([
32
- 'entryStatus', 'classification', 'reviewType', 'domain', 'release',
33
- 'lineageSourceSimilarity', 'script', 'system',
34
- 'reviewDate', 'reviewDecisionDate', 'reviewDecisionEvent',
35
- 'reviewStatus', 'reviewDecision', 'reviewDecisionNotes',
36
- ]);
37
- }
38
-
39
- static wireNameFor(field) {
40
- return LOC_WIRE_NAMES[field] ?? field;
41
- }
42
-
43
- constructor(data = {}) {
44
- super();
45
- this.languageCode = data.language_code ?? data.languageCode ?? null;
46
- this.script = data.script ?? null;
47
- this.system = data.system ?? null;
48
- this.entryStatus = data.entry_status ?? data.entryStatus ?? null;
49
- this.classification = data.classification ?? null;
50
- this.reviewType = data.review_type ?? data.reviewType ?? null;
51
- this.domain = data.domain ?? null;
52
- this.release = data.release ?? null;
53
- this.lineageSourceSimilarity = data.lineage_source_similarity ?? data.lineageSourceSimilarity ?? null;
54
-
55
- this.reviewDate = data.review_date ?? data.reviewDate ?? null;
56
- this.reviewDecisionDate = data.review_decision_date ?? data.reviewDecisionDate ?? null;
57
- this.reviewDecisionEvent = data.review_decision_event ?? data.reviewDecisionEvent ?? null;
58
- this.reviewStatus = data.review_status ?? data.reviewStatus ?? null;
59
- this.reviewDecision = data.review_decision ?? data.reviewDecision ?? null;
60
- this.reviewDecisionNotes = data.review_decision_notes ?? data.reviewDecisionNotes ?? null;
61
-
62
- this._rawTerms = data.terms ?? [];
63
- this._rawDefinition = data.definition ?? [];
64
- this._rawSources = data.sources ?? [];
65
- this._rawNotes = data.notes ?? [];
66
- this._rawAnnotations = data.annotations ?? [];
67
- this._rawExamples = data.examples ?? [];
68
- this._rawDates = data.dates ?? [];
69
- this._rawNonVerbal = data.non_verbal_rep ?? data.non_verb ?? [];
70
- this._rawRelated = data.related ?? [];
71
-
72
- this._terms = null;
73
- this._definitions = null;
74
- this._sources = null;
75
- this._notes = null;
76
- this._annotations = null;
77
- this._examples = null;
78
- this._dates = null;
79
- this._nonVerbal = null;
80
- this._related = null;
81
- }
82
-
83
- get terms() {
84
- return this._lazy('_terms', '_rawTerms', t => Designation.fromData(t));
85
- }
86
-
87
- get definitions() {
88
- return this._lazy('_definitions', '_rawDefinition', wrapAs(DetailedDefinition));
89
- }
90
-
91
- get definition() {
92
- return this.definitions;
93
- }
94
-
95
- get sources() {
96
- return this._lazy('_sources', '_rawSources', wrapAs(ConceptSource));
97
- }
98
-
99
- get notes() {
100
- return this._lazy('_notes', '_rawNotes', wrapAs(DetailedDefinition));
101
- }
102
-
103
- get annotations() {
104
- return this._lazy('_annotations', '_rawAnnotations', wrapAs(DetailedDefinition));
105
- }
106
-
107
- get examples() {
108
- return this._lazy('_examples', '_rawExamples', wrapAs(DetailedDefinition));
109
- }
110
-
111
- get dates() {
112
- return this._lazy('_dates', '_rawDates', wrapAs(ConceptDate));
113
- }
114
-
115
- get nonVerbalRep() {
116
- return this._lazy('_nonVerbal', '_rawNonVerbal', wrapAs(NonVerbRep));
117
- }
118
-
119
- get related() {
120
- return this._lazy('_related', '_rawRelated', wrapAs(RelatedConcept));
121
- }
122
-
123
- get primaryDesignation() {
124
- return this.terms[0]?.designation ?? null;
125
- }
126
-
127
- get primaryDefinition() {
128
- return this.definitions[0]?.content ?? null;
129
- }
130
-
131
- *_textSlots() {
132
- yield ['definitions', this.definitions];
133
- yield ['notes', this.notes];
134
- yield ['examples', this.examples];
135
- yield ['annotations', this.annotations];
136
- }
137
-
138
- /**
139
- * Yield every content-text fragment in this localization, recursing
140
- * through nested examples. `basePath` prefixes every emitted
141
- * `source` path; pass `localizations.<lang>` to get paths consistent
142
- * with the rest of the codebase. Designations are not included.
143
- */
144
- *walkTexts(basePath) {
145
- for (const [name, arr] of this._textSlots()) {
146
- for (let i = 0; i < arr.length; i++) {
147
- yield* arr[i].walkTexts(`${basePath}.${name}[${i}]`);
148
- }
149
- }
150
- }
151
-
152
- toJSON() {
153
- const obj = {};
154
- if (this.languageCode) obj.language_code = this.languageCode;
155
- if (this.script) obj.script = this.script;
156
- if (this.system) obj.system = this.system;
157
- if (this.entryStatus) obj.entry_status = this.entryStatus;
158
- if (this.classification) obj.classification = this.classification;
159
- if (this.reviewType) obj.review_type = this.reviewType;
160
- if (this.domain) obj.domain = this.domain;
161
- if (this.release) obj.release = this.release;
162
- if (this.lineageSourceSimilarity != null) obj.lineage_source_similarity = this.lineageSourceSimilarity;
163
-
164
- if (this.reviewDate) obj.review_date = this.reviewDate;
165
- if (this.reviewDecisionDate) obj.review_decision_date = this.reviewDecisionDate;
166
- if (this.reviewDecisionEvent) obj.review_decision_event = this.reviewDecisionEvent;
167
- if (this.reviewStatus) obj.review_status = this.reviewStatus;
168
- if (this.reviewDecision) obj.review_decision = this.reviewDecision;
169
- if (this.reviewDecisionNotes) obj.review_decision_notes = this.reviewDecisionNotes;
170
-
171
- this._serialize(obj, 'terms', '_terms', '_rawTerms');
172
- this._serialize(obj, 'definition', '_definitions', '_rawDefinition');
173
- this._serialize(obj, 'notes', '_notes', '_rawNotes');
174
- this._serialize(obj, 'annotations', '_annotations', '_rawAnnotations');
175
- this._serialize(obj, 'examples', '_examples', '_rawExamples');
176
- this._serialize(obj, 'sources', '_sources', '_rawSources');
177
- this._serialize(obj, 'dates', '_dates', '_rawDates');
178
- this._serialize(obj, 'non_verbal_rep', '_nonVerbal', '_rawNonVerbal');
179
- this._serialize(obj, 'related', '_related', '_rawRelated');
180
-
181
- return obj;
182
- }
183
-
184
- static fromJSON(data) {
185
- return new LocalizedConcept(data);
186
- }
187
- }
@@ -1,253 +0,0 @@
1
- import { GlossaristModel } from './base.js';
2
- import { Section } from './section.js';
3
- import { resolveColor } from './dataset-color.js';
4
-
5
- export const REGISTER_STATUSES = Object.freeze([
6
- 'current',
7
- 'superseded',
8
- 'withdrawn',
9
- 'draft',
10
- ]);
11
-
12
- export class Register extends GlossaristModel {
13
- constructor(data = {}) {
14
- super();
15
- this.schemaVersion = data.schema_version ?? data.schemaVersion ?? '3';
16
- this.id = data.id ?? null;
17
- this.name = data.name ?? null;
18
- this.ref = data.ref ?? null;
19
- this.refAliases = data.refAliases ?? data.ref_aliases ?? [];
20
- this.year = data.year ?? null;
21
- this.urn = data.urn ?? null;
22
- this.urnAliases = data.urnAliases ?? data.urn_aliases ?? [];
23
- this.status = data.status ?? null;
24
- this.supersedes = data.supersedes ?? null;
25
- this.owner = data.owner ?? null;
26
- this.sourceRepo = data.sourceRepo ?? data.source_repo ?? null;
27
- this.tags = data.tags ?? [];
28
- this.languages = data.languages ?? [];
29
- this.languageOrder = data.languageOrder ?? data.language_order ?? [];
30
- this.ordering = data.ordering ?? null;
31
- this.logo = data.logo ?? null;
32
- this.color = data.color ?? null;
33
- this.description = data.description ?? {};
34
- this.about = data.about ?? {};
35
- this.provenance = data.provenance ?? [];
36
- this.contributors = data.contributors ?? [];
37
- this.sections = (data.sections ?? []).map(s =>
38
- s instanceof Section ? s : new Section(s)
39
- );
40
- this._raw = this._extractRaw(data);
41
- }
42
-
43
- _extractRaw(data) {
44
- const known = new Set([
45
- 'schema_version', 'schemaVersion',
46
- 'id', 'name', 'ref', 'refAliases', 'ref_aliases',
47
- 'year', 'urn', 'urnAliases', 'urn_aliases',
48
- 'status', 'supersedes', 'owner',
49
- 'sourceRepo', 'source_repo', 'tags',
50
- 'languages', 'languageOrder', 'language_order',
51
- 'ordering', 'logo', 'color', 'description', 'about',
52
- 'provenance', 'contributors', 'sections',
53
- ]);
54
- const extra = {};
55
- for (const [k, v] of Object.entries(data)) {
56
- if (!known.has(k)) extra[k] = v;
57
- }
58
- return extra;
59
- }
60
-
61
- sectionById(id) {
62
- for (const section of this.sections) {
63
- if (section.id === id) return section;
64
- const found = section.descendantById(id);
65
- if (found) return found;
66
- }
67
- return null;
68
- }
69
-
70
- // Walks the section tree upward from `sectionId`, returning the
71
- // ancestor chain in immediate-parent-first order. The returned array
72
- // does NOT include sectionId itself. Returns [] when sectionId is
73
- // unknown or is a top-level section.
74
- //
75
- // Mirrors glossarist-ruby's section-cascading walk (commit 43dca6b)
76
- // and the ontology's owl:TransitiveProperty declaration on
77
- // gloss:hasParentSection.
78
- sectionAncestorIds(sectionId) {
79
- if (!sectionId) return [];
80
- for (const root of this.sections) {
81
- const chain = _ancestorChain(root, sectionId);
82
- if (chain) return chain;
83
- }
84
- return [];
85
- }
86
-
87
- // Returns the closure of `sectionId`: the section plus all of its
88
- // ancestors. Concept-section membership tests should intersect the
89
- // concept's section list with this closure so a concept in section
90
- // "3.1.1" matches a filter on "3.1" or "3".
91
- sectionClosure(sectionId) {
92
- return [sectionId, ...this.sectionAncestorIds(sectionId)];
93
- }
94
-
95
- // Walks the section tree DOWNWARD from `sectionId`, returning all
96
- // descendant section IDs in pre-order (immediate children first,
97
- // then their children). The returned array does NOT include
98
- // sectionId itself. Returns [] when sectionId is unknown or is a
99
- // leaf section. Useful for filtering concepts by parent section
100
- // (include descendants) — the inverse direction of sectionClosure.
101
- sectionDescendantIds(sectionId) {
102
- if (!sectionId) return [];
103
- const section = this.sectionById(sectionId);
104
- if (!section) return [];
105
- const out = [];
106
- for (const child of section.children ?? []) {
107
- out.push(child.id);
108
- out.push(...this.sectionDescendantIds(child.id));
109
- }
110
- return out;
111
- }
112
-
113
- // Returns the descendant closure of `sectionId`: the section plus
114
- // all of its descendants. Useful for "show me everything in section
115
- // X or below" queries.
116
- sectionDescendantClosure(sectionId) {
117
- return [sectionId, ...this.sectionDescendantIds(sectionId)];
118
- }
119
-
120
- // Returns all section IDs the concept belongs to: the concept's own
121
- // sections plus every ancestor of each. Concept-side section
122
- // membership comes from `concept.sections` if set, otherwise from
123
- // `concept.groups` (a flat list of section IDs as strings).
124
- //
125
- // Returns the union (deduped) so callers can do a single
126
- // intersection test.
127
- conceptSectionIds(concept) {
128
- const own = conceptSectionIdList(concept);
129
- if (own.length === 0) return [];
130
- const closure = new Set();
131
- for (const id of own) {
132
- for (const ancestor of this.sectionClosure(id)) closure.add(ancestor);
133
- }
134
- return [...closure];
135
- }
136
-
137
- sectionName(sectionId, lang) {
138
- const section = this.sectionById(sectionId);
139
- return section ? section.name(lang) : null;
140
- }
141
-
142
- // Localized display name. Returns the best-matching name string for
143
- // the requested language, falling back to English, then to the first
144
- // available language, then to null. Accepts three input shapes:
145
- // - null/undefined → null
146
- // - string → returned as-is for every language
147
- // - { eng, fra, ... } → language-resolved
148
- displayName(lang) {
149
- if (this.name == null) return null;
150
- if (typeof this.name === 'string') return this.name;
151
- if (typeof this.name === 'object') {
152
- return this.name[lang]
153
- ?? this.name.eng
154
- ?? Object.values(this.name)[0]
155
- ?? null;
156
- }
157
- return null;
158
- }
159
-
160
- // Returns the hex color for the requested UI mode, falling back to
161
- // the single hex when the spec is a string. Returns null when no
162
- // color is set or when the requested mode is missing.
163
- resolvedColor(mode) {
164
- return resolveColor(this.color, mode);
165
- }
166
-
167
- toJSON() {
168
- const obj = { ...this._raw, schema_version: this.schemaVersion };
169
- if (this.id != null) obj.id = this.id;
170
- if (this.name != null) obj.name = this.name;
171
- if (this.ref != null) obj.ref = this.ref;
172
- if (this.refAliases.length > 0) obj.refAliases = [...this.refAliases];
173
- if (this.year != null) obj.year = this.year;
174
- if (this.urn != null) obj.urn = this.urn;
175
- if (this.urnAliases.length > 0) obj.urnAliases = [...this.urnAliases];
176
- if (this.status != null) obj.status = this.status;
177
- if (this.supersedes != null) obj.supersedes = this.supersedes;
178
- if (this.owner != null) obj.owner = this.owner;
179
- if (this.sourceRepo != null) obj.sourceRepo = this.sourceRepo;
180
- if (this.tags.length > 0) obj.tags = [...this.tags];
181
- if (this.languages.length > 0) obj.languages = [...this.languages];
182
- if (this.languageOrder.length > 0) obj.languageOrder = [...this.languageOrder];
183
- if (this.ordering != null) obj.ordering = this.ordering;
184
- if (this.logo != null) obj.logo = { ...this.logo };
185
- if (this.color != null) {
186
- obj.color = (typeof this.color === 'object' && this.color !== null)
187
- ? { ...this.color }
188
- : this.color;
189
- }
190
- if (Object.keys(this.description).length > 0) obj.description = { ...this.description };
191
- if (Object.keys(this.about).length > 0) obj.about = { ...this.about };
192
- if (this.provenance.length > 0) obj.provenance = this.provenance.map(p => ({ ...p }));
193
- if (this.contributors.length > 0) obj.contributors = this.contributors.map(c => ({ ...c }));
194
- if (this.sections.length > 0) obj.sections = this.sections.map(s => s.toJSON());
195
- return obj;
196
- }
197
-
198
- static fromJSON(data) {
199
- const instance = new Register(data);
200
- const snakeToCamel = k => k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
201
- return new Proxy(instance, {
202
- get(target, prop) {
203
- if (prop in target) return target[prop];
204
- if (prop in target._raw) return target._raw[prop];
205
- const camel = snakeToCamel(prop);
206
- if (camel in target) return target[camel];
207
- return undefined;
208
- },
209
- });
210
- }
211
- }
212
-
213
- // Recursive walk: returns the ancestor chain of `targetId` within the
214
- // subtree rooted at `section`, or null if targetId is not in this
215
- // subtree. The chain is immediate-parent-first; the section itself is
216
- // not included. Built root-first, then reversed for the documented order.
217
- function _ancestorChain(section, targetId, ancestors = []) {
218
- if (section.id === targetId) return [...ancestors].reverse();
219
- for (const child of section.children) {
220
- const found = _ancestorChain(child, targetId, [...ancestors, section.id]);
221
- if (found) return found;
222
- }
223
- return null;
224
- }
225
-
226
- // Returns the list of section IDs a concept claims membership in.
227
- // Concepts may carry section IDs via either `sections` (preferred) or
228
- // `groups` (legacy). Each entry may be a string or an object with an
229
- // `id` field; both forms are flattened to a string list.
230
- function conceptSectionIdList(concept) {
231
- if (!concept) return [];
232
- const fromSections = concept.sections
233
- ? _flattenSectionIds(concept.sections)
234
- : [];
235
- const fromGroups = concept.groups
236
- ? _flattenSectionIds(concept.groups)
237
- : [];
238
- return [...fromSections, ...fromGroups];
239
- }
240
-
241
- function _flattenSectionIds(value) {
242
- if (!Array.isArray(value)) return [];
243
- const out = [];
244
- for (const entry of value) {
245
- if (typeof entry === 'string') {
246
- out.push(entry);
247
- } else if (entry && typeof entry === 'object') {
248
- const id = entry.id ?? entry.sectionId ?? entry.ref?.id;
249
- if (id) out.push(String(id));
250
- }
251
- }
252
- return out;
253
- }