@glossarist/concept-browser 0.7.78 → 0.7.81
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/package.json +2 -2
- package/scripts/build-edges.js +109 -39
- package/scripts/generate-data.mjs +63 -27
- package/src/__tests__/build-edges-redundancy.test.ts +101 -0
- package/src/__tests__/composables/use-concept-diff.test.ts +240 -0
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +132 -0
- package/src/__tests__/composables/use-dataset-stats.test.ts +96 -0
- package/src/__tests__/config/page-types.test.ts +82 -0
- package/src/__tests__/model-bridge-bridges.test.ts +3 -2
- package/src/__tests__/model-bridge-citation.test.ts +1 -1
- package/src/__tests__/ontology-registry.test.ts +2 -2
- package/src/__tests__/partitive-relation-styling.test.ts +82 -0
- package/src/__tests__/partitive-relation.test.ts +285 -0
- package/src/adapters/GraphDataSource.ts +16 -6
- package/src/adapters/dataset-stats.ts +13 -0
- package/src/adapters/non-verbal/glossarist-augment.d.ts +155 -4
- package/src/adapters/types.ts +27 -17
- package/src/components/AppSidebar.vue +14 -57
- package/src/components/ConceptDetail.vue +29 -0
- package/src/components/ConceptDiffModal.vue +120 -0
- package/src/components/ConceptDiffView.vue +192 -0
- package/src/components/ConceptEditionRail.vue +61 -36
- package/src/components/PartitiveRelationList.vue +139 -0
- package/src/components/groups/DatasetGroupRenderer.vue +14 -4
- package/src/components/groups/DefaultGroupSidebar.vue +28 -11
- package/src/components/groups/LineageGroupSidebar.vue +17 -29
- package/src/composables/use-concept-diff.ts +231 -0
- package/src/composables/use-concept-edges.ts +66 -45
- package/src/composables/use-dataset-stats.ts +52 -0
- package/src/config/page-types.ts +67 -0
- package/src/config/types.ts +6 -1
- package/src/config/use-site-config.ts +13 -29
- package/src/data/taxonomies.json +24 -1
- package/src/i18n/locales/eng.yml +6 -0
- package/src/main.ts +1 -1
- package/src/style.css +1 -1
- package/src/utils/partitive-relation-styling.ts +62 -0
- package/src/views/DatasetView.vue +5 -3
- package/src/views/StatsView.vue +9 -22
- package/src/__tests__/hyperedge-styling.test.ts +0 -57
- package/src/__tests__/partitive-hyperedge.test.ts +0 -199
- package/src/components/PartitiveHyperedgeList.vue +0 -112
- package/src/islands/BibliographyIsland.vue +0 -26
- package/src/islands/ConceptDetailIsland.vue +0 -27
- package/src/islands/ConceptDiffIsland.vue +0 -214
- package/src/islands/ConceptIsland.vue +0 -102
- package/src/islands/GraphIsland.vue +0 -15
- package/src/islands/GroupRendererIsland.vue +0 -15
- package/src/islands/HeaderIsland.vue +0 -10
- package/src/islands/NonVerbalIsland.vue +0 -75
- package/src/islands/RelationSphereIsland.vue +0 -20
- package/src/islands/SearchIsland.vue +0 -23
- package/src/islands/SeriesCardIsland.vue +0 -12
- package/src/islands/ShortcutsIsland.vue +0 -13
- package/src/islands/SidebarIsland.vue +0 -15
- package/src/islands/SupersessionDiffIsland.vue +0 -267
- package/src/utils/hyperedge-styling.ts +0 -48
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { PartitiveRelationWire } from '../adapters/types';
|
|
3
|
+
|
|
4
|
+
// Re-implements the extractor under test so we can test it in isolation
|
|
5
|
+
// without spinning up the full build-edges.js pipeline. The production
|
|
6
|
+
// extractor in scripts/build-edges.js uses the same logic.
|
|
7
|
+
function extractPartitiveRelations(
|
|
8
|
+
concept: { '@id': string; 'gl:partitiveRelations'?: any[] },
|
|
9
|
+
registerId: string,
|
|
10
|
+
uriBase: string,
|
|
11
|
+
urnMap: Map<string, string>,
|
|
12
|
+
): PartitiveRelationWire[] {
|
|
13
|
+
const relations: PartitiveRelationWire[] = [];
|
|
14
|
+
const sourceUri = concept['@id'];
|
|
15
|
+
|
|
16
|
+
const resolveConceptUri = (ref: any): string | null => {
|
|
17
|
+
const source = ref?.['gl:source'] || ref?.source;
|
|
18
|
+
const id = ref?.['gl:id'] || ref?.id;
|
|
19
|
+
if (!source || !id) return null;
|
|
20
|
+
const reg = urnMap.get(source) || source;
|
|
21
|
+
return `${uriBase}/${reg}/concept/${id}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const resolvePlurality = (plurality: any) => {
|
|
25
|
+
if (!plurality) return null;
|
|
26
|
+
const isShared = plurality['gl:isShared'] ?? plurality.is_shared;
|
|
27
|
+
if (typeof isShared !== 'boolean') return null;
|
|
28
|
+
const isUncertain = plurality['gl:isUncertain'] ?? plurality.is_uncertain ?? false;
|
|
29
|
+
const sharedTypeRef = plurality['gl:sharedType'] ?? plurality.shared_type;
|
|
30
|
+
let sharedType = null;
|
|
31
|
+
if (sharedTypeRef) {
|
|
32
|
+
const s = sharedTypeRef['gl:source'] ?? sharedTypeRef.source;
|
|
33
|
+
const i = sharedTypeRef['gl:id'] ?? sharedTypeRef.id;
|
|
34
|
+
if (s && i) sharedType = `${s}:${i}`;
|
|
35
|
+
}
|
|
36
|
+
return { isShared, isUncertain, sharedType };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const rel of concept['gl:partitiveRelations'] || []) {
|
|
40
|
+
const comprehensive = resolveConceptUri(rel['gl:comprehensive']);
|
|
41
|
+
if (!comprehensive) continue;
|
|
42
|
+
const partitives = (rel['gl:hasPartitive'] || [])
|
|
43
|
+
.map((member: any) => {
|
|
44
|
+
const ref = member['gl:ref'] || member;
|
|
45
|
+
const uri = resolveConceptUri(ref);
|
|
46
|
+
if (!uri || uri === sourceUri) return null;
|
|
47
|
+
const certainty = member['gl:certainty'] || 'confirmed';
|
|
48
|
+
if (certainty !== 'confirmed' && certainty !== 'possible') {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Invalid partitive member certainty: "${certainty}". Allowed: confirmed, possible`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return { uri, certainty };
|
|
54
|
+
})
|
|
55
|
+
.filter((p: any): p is { uri: string; certainty: 'confirmed' | 'possible' } => p !== null);
|
|
56
|
+
if (partitives.length === 0) continue;
|
|
57
|
+
const completeness = rel['gl:completeness'] || 'complete';
|
|
58
|
+
if (completeness !== 'complete' && completeness !== 'partial') {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Invalid partitive relation completeness: "${completeness}". Allowed: complete, partial`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
relations.push({
|
|
64
|
+
source: sourceUri,
|
|
65
|
+
comprehensive,
|
|
66
|
+
partitives,
|
|
67
|
+
completeness,
|
|
68
|
+
plurality: resolvePlurality(rel['gl:hasPlurality']) as PartitiveRelationWire['plurality'],
|
|
69
|
+
criterion: rel['gl:criterion'],
|
|
70
|
+
register: registerId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return relations;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('extractPartitiveRelations', () => {
|
|
77
|
+
const uriBase = 'https://example.org';
|
|
78
|
+
const urnMap = new Map([['urn:vim:pub:v:2:2012', 'vim-2012']]);
|
|
79
|
+
const registerId = 'vim-2012';
|
|
80
|
+
const sourceUri = `${uriBase}/${registerId}/concept/2.9`;
|
|
81
|
+
|
|
82
|
+
it('extracts a complete relation with two confirmed members', () => {
|
|
83
|
+
const concept = {
|
|
84
|
+
'@id': sourceUri,
|
|
85
|
+
'gl:partitiveRelations': [
|
|
86
|
+
{
|
|
87
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.9' },
|
|
88
|
+
'gl:hasPartitive': [
|
|
89
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.10' } },
|
|
90
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.26' } },
|
|
91
|
+
],
|
|
92
|
+
'gl:completeness': 'complete',
|
|
93
|
+
'gl:criterion': { eng: 'measurement result composition' },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const result = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
99
|
+
expect(result).toHaveLength(1);
|
|
100
|
+
expect(result[0].comprehensive).toBe(`${uriBase}/${registerId}/concept/2.9`);
|
|
101
|
+
expect(result[0].partitives).toEqual([
|
|
102
|
+
{ uri: `${uriBase}/${registerId}/concept/2.10`, certainty: 'confirmed' },
|
|
103
|
+
{ uri: `${uriBase}/${registerId}/concept/2.26`, certainty: 'confirmed' },
|
|
104
|
+
]);
|
|
105
|
+
expect(result[0].completeness).toBe('complete');
|
|
106
|
+
expect(result[0].criterion).toEqual({ eng: 'measurement result composition' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('defaults completeness to complete when omitted', () => {
|
|
110
|
+
const concept = {
|
|
111
|
+
'@id': sourceUri,
|
|
112
|
+
'gl:partitiveRelations': [
|
|
113
|
+
{
|
|
114
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.9' },
|
|
115
|
+
'gl:hasPartitive': [
|
|
116
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.10' } },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
122
|
+
expect(rel.completeness).toBe('complete');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('preserves partial completeness', () => {
|
|
126
|
+
const concept = {
|
|
127
|
+
'@id': sourceUri,
|
|
128
|
+
'gl:partitiveRelations': [
|
|
129
|
+
{
|
|
130
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
131
|
+
'gl:hasPartitive': [
|
|
132
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
133
|
+
],
|
|
134
|
+
'gl:completeness': 'partial',
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
139
|
+
expect(rel.completeness).toBe('partial');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('resolves plurality block (isShared + sharedType)', () => {
|
|
143
|
+
const concept = {
|
|
144
|
+
'@id': sourceUri,
|
|
145
|
+
'gl:partitiveRelations': [
|
|
146
|
+
{
|
|
147
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
148
|
+
'gl:hasPartitive': [
|
|
149
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
150
|
+
],
|
|
151
|
+
'gl:hasPlurality': {
|
|
152
|
+
'gl:isShared': true,
|
|
153
|
+
'gl:isUncertain': false,
|
|
154
|
+
'gl:sharedType': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.99' },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
160
|
+
expect(rel.plurality).toEqual({
|
|
161
|
+
isShared: true,
|
|
162
|
+
isUncertain: false,
|
|
163
|
+
sharedType: 'urn:vim:pub:v:2:2012:1.99',
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns null plurality when isShared missing', () => {
|
|
168
|
+
const concept = {
|
|
169
|
+
'@id': sourceUri,
|
|
170
|
+
'gl:partitiveRelations': [
|
|
171
|
+
{
|
|
172
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
173
|
+
'gl:hasPartitive': [
|
|
174
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
175
|
+
],
|
|
176
|
+
'gl:hasPlurality': { 'gl:isUncertain': true },
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
181
|
+
expect(rel.plurality).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('preserves per-member certainty', () => {
|
|
185
|
+
const concept = {
|
|
186
|
+
'@id': sourceUri,
|
|
187
|
+
'gl:partitiveRelations': [
|
|
188
|
+
{
|
|
189
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
190
|
+
'gl:hasPartitive': [
|
|
191
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
192
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.5' }, 'gl:certainty': 'possible' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
198
|
+
expect(rel.partitives).toEqual([
|
|
199
|
+
{ uri: `${uriBase}/${registerId}/concept/1.4`, certainty: 'confirmed' },
|
|
200
|
+
{ uri: `${uriBase}/${registerId}/concept/1.5`, certainty: 'possible' },
|
|
201
|
+
]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('throws on invalid completeness', () => {
|
|
205
|
+
const concept = {
|
|
206
|
+
'@id': sourceUri,
|
|
207
|
+
'gl:partitiveRelations': [
|
|
208
|
+
{
|
|
209
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
210
|
+
'gl:hasPartitive': [
|
|
211
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
212
|
+
],
|
|
213
|
+
'gl:completeness': 'closed',
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
expect(() => extractPartitiveRelations(concept, registerId, uriBase, urnMap)).toThrow(/Invalid.*completeness.*closed/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('throws on invalid member certainty', () => {
|
|
221
|
+
const concept = {
|
|
222
|
+
'@id': sourceUri,
|
|
223
|
+
'gl:partitiveRelations': [
|
|
224
|
+
{
|
|
225
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.3' },
|
|
226
|
+
'gl:hasPartitive': [
|
|
227
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' }, 'gl:certainty': 'maybe' },
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
expect(() => extractPartitiveRelations(concept, registerId, uriBase, urnMap)).toThrow(/Invalid.*certainty.*maybe/);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('skips a relation whose comprehensive cannot be resolved', () => {
|
|
236
|
+
const concept = {
|
|
237
|
+
'@id': sourceUri,
|
|
238
|
+
'gl:partitiveRelations': [
|
|
239
|
+
{
|
|
240
|
+
'gl:comprehensive': { 'gl:id': '1.3' },
|
|
241
|
+
'gl:hasPartitive': [
|
|
242
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '1.4' } },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
expect(extractPartitiveRelations(concept, registerId, uriBase, urnMap)).toHaveLength(0);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('skips a relation with no resolvable partitives', () => {
|
|
251
|
+
const concept = {
|
|
252
|
+
'@id': sourceUri,
|
|
253
|
+
'gl:partitiveRelations': [
|
|
254
|
+
{
|
|
255
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.9' },
|
|
256
|
+
'gl:hasPartitive': [{ 'gl:ref': { 'gl:id': '1.4' } }],
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
expect(extractPartitiveRelations(concept, registerId, uriBase, urnMap)).toHaveLength(0);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('excludes the source concept from partitives (self-loop guard)', () => {
|
|
264
|
+
const concept = {
|
|
265
|
+
'@id': sourceUri,
|
|
266
|
+
'gl:partitiveRelations': [
|
|
267
|
+
{
|
|
268
|
+
'gl:comprehensive': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.9' },
|
|
269
|
+
'gl:hasPartitive': [
|
|
270
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.10' } },
|
|
271
|
+
{ 'gl:ref': { 'gl:source': 'urn:vim:pub:v:2:2012', 'gl:id': '2.9' } },
|
|
272
|
+
],
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
const [rel] = extractPartitiveRelations(concept, registerId, uriBase, urnMap);
|
|
277
|
+
expect(rel.partitives).toHaveLength(1);
|
|
278
|
+
expect(rel.partitives[0].uri).toBe(`${uriBase}/${registerId}/concept/2.10`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('returns empty array when concept has no partitiveRelations', () => {
|
|
282
|
+
const concept = { '@id': sourceUri };
|
|
283
|
+
expect(extractPartitiveRelations(concept, registerId, uriBase, urnMap)).toEqual([]);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GraphEdge, GraphNode,
|
|
1
|
+
import type { GraphEdge, GraphNode, PartitiveRelationWire, SectionNode } from './types';
|
|
2
2
|
import type { Concept, RelatedConcept } from 'glossarist';
|
|
3
3
|
import type { DatasetAdapter } from './DatasetAdapter';
|
|
4
4
|
import { UriRouter } from './UriRouter';
|
|
@@ -30,6 +30,16 @@ function resolveRefTarget(rc: RelatedConcept, uriBase: string, registerId: strin
|
|
|
30
30
|
return ref.source || '';
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function contentLabel(content: unknown): string | undefined {
|
|
34
|
+
if (content == null) return undefined;
|
|
35
|
+
if (typeof content === 'string') return content;
|
|
36
|
+
if (typeof content === 'object' && content !== null) {
|
|
37
|
+
const obj = content as Record<string, string>;
|
|
38
|
+
return obj.default ?? obj.eng ?? Object.values(obj)[0];
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
export class GraphDataSource {
|
|
34
44
|
constructor(private adapter: DatasetAdapter) {}
|
|
35
45
|
|
|
@@ -58,11 +68,11 @@ export class GraphDataSource {
|
|
|
58
68
|
return data.edges ?? [];
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
async
|
|
62
|
-
const resp = await fetch(`${this.baseUrl}/
|
|
71
|
+
async loadPartitiveRelations(): Promise<PartitiveRelationWire[]> {
|
|
72
|
+
const resp = await fetch(`${this.baseUrl}/partitive_relations.json`);
|
|
63
73
|
if (!resp.ok) return [];
|
|
64
74
|
const data = await resp.json();
|
|
65
|
-
return data.hyperedges ?? [];
|
|
75
|
+
return data.relations ?? data.hyperedges ?? [];
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
async loadGraphNodes(): Promise<{ uriPrefix: string; nodes: [string, Record<string, string>, string][] }> {
|
|
@@ -90,7 +100,7 @@ export class GraphDataSource {
|
|
|
90
100
|
source: sourceUri,
|
|
91
101
|
target,
|
|
92
102
|
type: rc.type || 'references',
|
|
93
|
-
label: rc.content
|
|
103
|
+
label: contentLabel(rc.content),
|
|
94
104
|
register: parsed?.registerId ?? this.registerId,
|
|
95
105
|
});
|
|
96
106
|
}
|
|
@@ -107,7 +117,7 @@ export class GraphDataSource {
|
|
|
107
117
|
source: sourceUri,
|
|
108
118
|
target,
|
|
109
119
|
type: rc.type || 'references',
|
|
110
|
-
label: rc.content
|
|
120
|
+
label: contentLabel(rc.content),
|
|
111
121
|
register: parsed?.registerId ?? this.registerId,
|
|
112
122
|
lang,
|
|
113
123
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataset-level stats shape. The data pipeline emits this as
|
|
3
|
+
* `public/data/{registerId}/stats.json`. Consumers fetch via HTTP.
|
|
4
|
+
*
|
|
5
|
+
* Kept in adapters/types.ts (the SSOT for wire shapes) so all views
|
|
6
|
+
* that need stats share the same definition.
|
|
7
|
+
*/
|
|
8
|
+
export interface DatasetStats {
|
|
9
|
+
sourceCount: number;
|
|
10
|
+
sources: Array<{ ref: string; types: string[]; conceptCount: number }>;
|
|
11
|
+
relationshipCount: number;
|
|
12
|
+
relationshipTypes: Record<string, number>;
|
|
13
|
+
}
|
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
// This file declares the runtime shape so consumer code can be type-checked.
|
|
11
11
|
// DELETE this file when upstream ships proper declarations — tracked by
|
|
12
12
|
// PR glossarist/glossarist-js#31 (targets v0.4.3+).
|
|
13
|
+
//
|
|
14
|
+
// v2 PartitiveRelation block (below) tracks glossarist 0.4.20 — the runtime
|
|
15
|
+
// exports PartitiveRelation/PartitiveMember/TypeSharedPlurality from
|
|
16
|
+
// src/models/index.js, but the d.ts has not been updated. Remove the v2
|
|
17
|
+
// block when upstream PRs the d.ts changes.
|
|
13
18
|
|
|
14
19
|
import type { ConceptSource, Citation, GlossaristModel } from 'glossarist';
|
|
15
20
|
|
|
16
21
|
declare module 'glossarist' {
|
|
17
|
-
interface ConceptSource {
|
|
18
|
-
sourced_from?: Citation[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
22
|
class RegistrableModel extends GlossaristModel {
|
|
22
23
|
static register(type: string, cls: typeof RegistrableModel): void;
|
|
23
24
|
static fromData(data: Record<string, unknown>): RegistrableModel;
|
|
@@ -134,4 +135,154 @@ declare module 'glossarist' {
|
|
|
134
135
|
static fromYAML(yamlString: string): BibliographyData;
|
|
135
136
|
static fromJSON(data: Record<string, unknown>): BibliographyData;
|
|
136
137
|
}
|
|
138
|
+
|
|
139
|
+
// ── v2 PartitiveRelation (glossarist 0.4.20) ────────────────────────────
|
|
140
|
+
// Runtime exports these from src/models/index.js; d.ts is stale.
|
|
141
|
+
|
|
142
|
+
type Completeness = 'complete' | 'partial';
|
|
143
|
+
const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
|
|
144
|
+
const COMPLETENESS_VALUES: readonly Completeness[];
|
|
145
|
+
const DEFAULT_COMPLETENESS: Completeness;
|
|
146
|
+
function isValidCompleteness(value: unknown): value is Completeness;
|
|
147
|
+
|
|
148
|
+
type MemberCertainty = 'confirmed' | 'possible';
|
|
149
|
+
const MEMBER_CERTAINTY: { readonly CONFIRMED: 'confirmed'; readonly POSSIBLE: 'possible' };
|
|
150
|
+
const MEMBER_CERTAINTY_VALUES: readonly MemberCertainty[];
|
|
151
|
+
const DEFAULT_MEMBER_CERTAINTY: MemberCertainty;
|
|
152
|
+
function isValidMemberCertainty(value: unknown): value is MemberCertainty;
|
|
153
|
+
|
|
154
|
+
class TypeSharedPlurality extends GlossaristModel {
|
|
155
|
+
constructor(data?: {
|
|
156
|
+
isShared?: boolean;
|
|
157
|
+
is_shared?: boolean;
|
|
158
|
+
isUncertain?: boolean;
|
|
159
|
+
is_uncertain?: boolean;
|
|
160
|
+
sharedType?: ConceptRef;
|
|
161
|
+
shared_type?: ConceptRef;
|
|
162
|
+
});
|
|
163
|
+
readonly isShared: boolean;
|
|
164
|
+
readonly isUncertain: boolean;
|
|
165
|
+
readonly sharedType: ConceptRef | null;
|
|
166
|
+
hasSharedType(): boolean;
|
|
167
|
+
toJSON(): { is_shared: boolean; is_uncertain?: boolean; shared_type?: ReturnType<ConceptRef['toJSON']> };
|
|
168
|
+
static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class PartitiveMember extends GlossaristModel {
|
|
172
|
+
constructor(data?: { ref?: ConceptRef; certainty?: MemberCertainty });
|
|
173
|
+
readonly ref: ConceptRef;
|
|
174
|
+
readonly certainty: MemberCertainty;
|
|
175
|
+
readonly isConfirmed: boolean;
|
|
176
|
+
readonly isPossible: boolean;
|
|
177
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; certainty?: MemberCertainty };
|
|
178
|
+
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
179
|
+
static identityOf(value: unknown): string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class PartitiveRelation extends GlossaristModel {
|
|
183
|
+
constructor(data?: {
|
|
184
|
+
comprehensive?: ConceptRef;
|
|
185
|
+
partitives?: PartitiveMember[];
|
|
186
|
+
completeness?: Completeness;
|
|
187
|
+
plurality?: TypeSharedPlurality;
|
|
188
|
+
criterion?: Record<string, string> | string;
|
|
189
|
+
});
|
|
190
|
+
readonly comprehensive: ConceptRef;
|
|
191
|
+
readonly partitives: PartitiveMember[];
|
|
192
|
+
readonly completeness: Completeness;
|
|
193
|
+
readonly plurality: TypeSharedPlurality | null;
|
|
194
|
+
readonly criterion: Record<string, string> | null;
|
|
195
|
+
readonly isComplete: boolean;
|
|
196
|
+
readonly isPartial: boolean;
|
|
197
|
+
readonly isCoordinate: boolean;
|
|
198
|
+
hasPlurality(): boolean;
|
|
199
|
+
hasCriterion(): boolean;
|
|
200
|
+
toJSON(): {
|
|
201
|
+
comprehensive: ReturnType<ConceptRef['toJSON']>;
|
|
202
|
+
partitives: ReturnType<PartitiveMember['toJSON']>[];
|
|
203
|
+
completeness: Completeness;
|
|
204
|
+
plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
|
|
205
|
+
criterion?: Record<string, string>;
|
|
206
|
+
};
|
|
207
|
+
static fromJSON(data: Record<string, unknown>): PartitiveRelation;
|
|
208
|
+
static identityOf(value: unknown): string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface Concept {
|
|
212
|
+
readonly partitiveRelations: PartitiveRelation[];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Re-declare the same exports under the 'glossarist/models' subpath so
|
|
217
|
+
// deep-import callers can use them. Upstream's d.ts is stale on this
|
|
218
|
+
// subpath too.
|
|
219
|
+
declare module 'glossarist/models' {
|
|
220
|
+
export type Completeness = 'complete' | 'partial';
|
|
221
|
+
export const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
|
|
222
|
+
export const COMPLETENESS_VALUES: readonly Completeness[];
|
|
223
|
+
export const DEFAULT_COMPLETENESS: Completeness;
|
|
224
|
+
export function isValidCompleteness(value: unknown): value is Completeness;
|
|
225
|
+
|
|
226
|
+
export type MemberCertainty = 'confirmed' | 'possible';
|
|
227
|
+
export const MEMBER_CERTAINTY: { readonly CONFIRMED: 'confirmed'; readonly POSSIBLE: 'possible' };
|
|
228
|
+
export const MEMBER_CERTAINTY_VALUES: readonly MemberCertainty[];
|
|
229
|
+
export const DEFAULT_MEMBER_CERTAINTY: MemberCertainty;
|
|
230
|
+
export function isValidMemberCertainty(value: unknown): value is MemberCertainty;
|
|
231
|
+
|
|
232
|
+
export class TypeSharedPlurality extends GlossaristModel {
|
|
233
|
+
constructor(data?: {
|
|
234
|
+
isShared?: boolean;
|
|
235
|
+
is_shared?: boolean;
|
|
236
|
+
isUncertain?: boolean;
|
|
237
|
+
is_uncertain?: boolean;
|
|
238
|
+
sharedType?: ConceptRef;
|
|
239
|
+
shared_type?: ConceptRef;
|
|
240
|
+
});
|
|
241
|
+
readonly isShared: boolean;
|
|
242
|
+
readonly isUncertain: boolean;
|
|
243
|
+
readonly sharedType: ConceptRef | null;
|
|
244
|
+
hasSharedType(): boolean;
|
|
245
|
+
toJSON(): { is_shared: boolean; is_uncertain?: boolean; shared_type?: ReturnType<ConceptRef['toJSON']> };
|
|
246
|
+
static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export class PartitiveMember extends GlossaristModel {
|
|
250
|
+
constructor(data?: { ref?: ConceptRef; certainty?: MemberCertainty });
|
|
251
|
+
readonly ref: ConceptRef;
|
|
252
|
+
readonly certainty: MemberCertainty;
|
|
253
|
+
readonly isConfirmed: boolean;
|
|
254
|
+
readonly isPossible: boolean;
|
|
255
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; certainty?: MemberCertainty };
|
|
256
|
+
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
257
|
+
static identityOf(value: unknown): string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export class PartitiveRelation extends GlossaristModel {
|
|
261
|
+
constructor(data?: {
|
|
262
|
+
comprehensive?: ConceptRef;
|
|
263
|
+
partitives?: PartitiveMember[];
|
|
264
|
+
completeness?: Completeness;
|
|
265
|
+
plurality?: TypeSharedPlurality;
|
|
266
|
+
criterion?: Record<string, string> | string;
|
|
267
|
+
});
|
|
268
|
+
readonly comprehensive: ConceptRef;
|
|
269
|
+
readonly partitives: PartitiveMember[];
|
|
270
|
+
readonly completeness: Completeness;
|
|
271
|
+
readonly plurality: TypeSharedPlurality | null;
|
|
272
|
+
readonly criterion: Record<string, string> | null;
|
|
273
|
+
readonly isComplete: boolean;
|
|
274
|
+
readonly isPartial: boolean;
|
|
275
|
+
readonly isCoordinate: boolean;
|
|
276
|
+
hasPlurality(): boolean;
|
|
277
|
+
hasCriterion(): boolean;
|
|
278
|
+
toJSON(): {
|
|
279
|
+
comprehensive: ReturnType<ConceptRef['toJSON']>;
|
|
280
|
+
partitives: ReturnType<PartitiveMember['toJSON']>[];
|
|
281
|
+
completeness: Completeness;
|
|
282
|
+
plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
|
|
283
|
+
criterion?: Record<string, string>;
|
|
284
|
+
};
|
|
285
|
+
static fromJSON(data: Record<string, unknown>): PartitiveRelation;
|
|
286
|
+
static identityOf(value: unknown): string;
|
|
287
|
+
}
|
|
137
288
|
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -149,31 +149,41 @@ export interface GraphEdge {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
152
|
+
* Wire-shape projection of a PartitiveRelation — used by:
|
|
153
|
+
* - build-edges.js output (partitive_relations.json)
|
|
154
|
+
* - GraphDataSource (loads the wire file for tools/future consumers)
|
|
155
|
+
* - use-concept-edges composable (projects the glossarist-js model
|
|
156
|
+
* into this shape for display, resolving ConceptRef → URI)
|
|
154
157
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
158
|
+
* The glossarist-js `PartitiveRelation` class is the model SSOT
|
|
159
|
+
* (ConceptRef-based). This interface is the *resolved-for-display*
|
|
160
|
+
* shape — keep the two concerns separate.
|
|
157
161
|
*
|
|
158
|
-
*
|
|
162
|
+
* Renamed from `PartitiveRelation` to avoid collision with the
|
|
163
|
+
* upstream model class. v2 shape per concept-model
|
|
164
|
+
* TODO.partitive-relation-v2.
|
|
159
165
|
*/
|
|
160
|
-
export interface
|
|
161
|
-
/** URI of the concept that owns this hyperedge (the source). */
|
|
166
|
+
export interface PartitiveRelationWire {
|
|
162
167
|
source: string;
|
|
163
|
-
/** URI of the comprehensive (whole) concept — usually equals `source`. */
|
|
164
168
|
comprehensive: string;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/** Diagram notation flags. Empty array if none. */
|
|
170
|
-
markers: ('double' | 'dashed')[];
|
|
171
|
-
/** Optional human-readable label/note. */
|
|
172
|
-
label?: string;
|
|
173
|
-
/** Owning dataset id. */
|
|
169
|
+
partitives: PartitiveMemberWire[];
|
|
170
|
+
completeness: 'complete' | 'partial';
|
|
171
|
+
plurality: TypeSharedPluralityWire | null;
|
|
172
|
+
criterion?: Record<string, string>;
|
|
174
173
|
register: string;
|
|
175
174
|
}
|
|
176
175
|
|
|
176
|
+
export interface PartitiveMemberWire {
|
|
177
|
+
uri: string;
|
|
178
|
+
certainty: 'confirmed' | 'possible';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface TypeSharedPluralityWire {
|
|
182
|
+
isShared: boolean;
|
|
183
|
+
isUncertain: boolean;
|
|
184
|
+
sharedType?: string | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
export interface GraphNode {
|
|
178
188
|
uri: string;
|
|
179
189
|
register: string;
|