@glossarist/concept-browser 0.7.90 → 0.7.92

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.
@@ -170,12 +170,14 @@ declare module 'glossarist' {
170
170
  function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
171
171
 
172
172
  interface Concept {
173
- readonly partitiveRelations: PartitiveRelation[];
173
+ readonly relations: AbstractHyperedge[];
174
+ readonly partitiveRelations: PartitiveHyperedge[];
175
+ readonly genericRelations: GenericHyperedge[];
174
176
  }
175
177
 
176
178
  // Re-exposed here so callers importing from the top-level `glossarist`
177
179
  // entry get the MECE-augmented shape, not the stale d.ts type.
178
- export class PartitiveMember extends GlossaristModel {
180
+ export class HyperedgeMember extends GlossaristModel {
179
181
  constructor(data?: {
180
182
  ref?: ConceptRef;
181
183
  presence?: PartitivePresence;
@@ -189,36 +191,43 @@ declare module 'glossarist' {
189
191
  readonly isRequired: boolean;
190
192
  readonly isOptional: boolean;
191
193
  get isDelimiting(): boolean;
192
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
193
- static fromJSON(data: Record<string, unknown>): PartitiveMember;
194
+ toJSON(): Record<string, unknown>;
195
+ static fromJSON(data: Record<string, unknown>): HyperedgeMember;
194
196
  static identityOf(value: unknown): string;
195
197
  }
196
198
 
197
- export class PartitiveRelation extends GlossaristModel {
198
- constructor(data?: {
199
- comprehensive?: ConceptRef;
200
- partitives?: PartitiveMember[];
201
- completeness?: Completeness;
202
- criterion?: Record<string, string> | string;
203
- });
199
+ export class PartitiveMember extends HyperedgeMember {}
200
+ export class GenericMember extends HyperedgeMember {}
201
+
202
+ export class AbstractHyperedge extends GlossaristModel {
204
203
  readonly comprehensive: ConceptRef;
205
- readonly partitives: PartitiveMember[];
204
+ readonly members: HyperedgeMember[];
206
205
  readonly completeness: Completeness;
207
206
  readonly criterion: Record<string, string> | null;
207
+ readonly sources: ConceptSource[];
208
+ readonly notes: Record<string, string> | null;
209
+ readonly status: string | null;
208
210
  readonly isComplete: boolean;
209
211
  readonly isPartial: boolean;
210
212
  readonly isCoordinate: boolean;
211
- hasPlurality(): boolean;
212
- hasCriterion(): boolean;
213
- toJSON(): {
214
- comprehensive: ReturnType<ConceptRef['toJSON']>;
215
- partitives: ReturnType<PartitiveMember['toJSON']>[];
216
- completeness: Completeness;
217
- criterion?: Record<string, string>;
218
- };
219
- static fromJSON(data: Record<string, unknown>): PartitiveRelation;
213
+ toJSON(): Record<string, unknown>;
214
+ static fromJSON(data: Record<string, unknown>): AbstractHyperedge;
215
+ static identityOf(value: unknown): string;
216
+ }
217
+
218
+ export class PartitiveHyperedge extends AbstractHyperedge {
219
+ get partitives(): HyperedgeMember[];
220
+ static fromJSON(data: Record<string, unknown>): PartitiveHyperedge;
220
221
  static identityOf(value: unknown): string;
221
222
  }
223
+
224
+ export class GenericHyperedge extends AbstractHyperedge {
225
+ static fromJSON(data: Record<string, unknown>): GenericHyperedge;
226
+ static identityOf(value: unknown): string;
227
+ }
228
+
229
+ // Legacy aliases (deprecated — use Hyperedge-based names)
230
+ export class PartitiveRelation extends PartitiveHyperedge {}
222
231
  }
223
232
 
224
233
  // Re-declare the same exports under the 'glossarist/models' subpath so
@@ -261,9 +270,9 @@ declare module 'glossarist/models' {
261
270
  // (TypeSharedPlurality removed — superseded by presence/count on
262
271
  // PartitiveMember per MECE refactor. Was dead code.)
263
272
 
264
- // ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
273
+ // ── Hyperedge classes (glossarist 0.4.33 — unified n-ary model) ─
265
274
 
266
- export class PartitiveMember extends GlossaristModel {
275
+ export class HyperedgeMember extends GlossaristModel {
267
276
  constructor(data?: {
268
277
  ref?: ConceptRef;
269
278
  presence?: PartitivePresence;
@@ -274,37 +283,44 @@ declare module 'glossarist/models' {
274
283
  readonly presence: PartitivePresence;
275
284
  readonly count: PartitiveCount;
276
285
  readonly is_delimiting: boolean;
277
- readonly isRequired: boolean;
278
- readonly isOptional: boolean;
286
+ required(): boolean;
287
+ optional(): boolean;
279
288
  get isDelimiting(): boolean;
280
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
281
- static fromJSON(data: Record<string, unknown>): PartitiveMember;
289
+ toJSON(): Record<string, unknown>;
290
+ static fromJSON(data: Record<string, unknown>): HyperedgeMember;
282
291
  static identityOf(value: unknown): string;
283
292
  }
284
293
 
285
- export class PartitiveRelation extends GlossaristModel {
286
- constructor(data?: {
287
- comprehensive?: ConceptRef;
288
- partitives?: PartitiveMember[];
289
- completeness?: Completeness;
290
- criterion?: Record<string, string> | string;
291
- });
294
+ export class PartitiveMember extends HyperedgeMember {}
295
+ export class GenericMember extends HyperedgeMember {}
296
+
297
+ export class AbstractHyperedge extends GlossaristModel {
292
298
  readonly comprehensive: ConceptRef;
293
- readonly partitives: PartitiveMember[];
299
+ readonly members: HyperedgeMember[];
294
300
  readonly completeness: Completeness;
295
301
  readonly criterion: Record<string, string> | null;
302
+ readonly sources: ConceptSource[];
303
+ readonly notes: Record<string, string> | null;
304
+ readonly status: string | null;
296
305
  readonly isComplete: boolean;
297
306
  readonly isPartial: boolean;
298
307
  readonly isCoordinate: boolean;
299
- hasPlurality(): boolean;
300
- hasCriterion(): boolean;
301
- toJSON(): {
302
- comprehensive: ReturnType<ConceptRef['toJSON']>;
303
- partitives: ReturnType<PartitiveMember['toJSON']>[];
304
- completeness: Completeness;
305
- criterion?: Record<string, string>;
306
- };
307
- static fromJSON(data: Record<string, unknown>): PartitiveRelation;
308
+ toJSON(): Record<string, unknown>;
309
+ static fromJSON(data: Record<string, unknown>): AbstractHyperedge;
310
+ static identityOf(value: unknown): string;
311
+ }
312
+
313
+ export class PartitiveHyperedge extends AbstractHyperedge {
314
+ get partitives(): HyperedgeMember[];
315
+ static fromJSON(data: Record<string, unknown>): PartitiveHyperedge;
316
+ static identityOf(value: unknown): string;
317
+ }
318
+
319
+ export class GenericHyperedge extends AbstractHyperedge {
320
+ static fromJSON(data: Record<string, unknown>): GenericHyperedge;
308
321
  static identityOf(value: unknown): string;
309
322
  }
323
+
324
+ // Legacy aliases (deprecated)
325
+ export class PartitiveRelation extends PartitiveHyperedge {}
310
326
  }
@@ -179,6 +179,30 @@ export interface PartitiveMemberWire {
179
179
  isDelimiting: boolean;
180
180
  }
181
181
 
182
+ /**
183
+ * GenericRelationWire — ISO 704 / 1087-1 / 12620 generic (genus/
184
+ * species) decomposition, mirror of PartitiveRelationWire.
185
+ *
186
+ * The OIML V 2-200:2010 dataset has ~8 generic hyperedges with
187
+ * multiple criterion groups per comprehensive; this shape supports
188
+ * that natively. See docs/design/generic-relation.md (concept-model).
189
+ */
190
+ export interface GenericRelationWire {
191
+ source: string;
192
+ comprehensive: string;
193
+ members: GenericMemberWire[];
194
+ completeness: 'complete' | 'partial';
195
+ criterion?: Record<string, string>;
196
+ register: string;
197
+ }
198
+
199
+ export interface GenericMemberWire {
200
+ uri: string;
201
+ presence: 'required' | 'optional';
202
+ count: 'exactly_one' | 'at_least_one' | 'multiple';
203
+ isDelimiting: boolean;
204
+ }
205
+
182
206
  export interface GraphNode {
183
207
  uri: string;
184
208
  register: string;
@@ -37,6 +37,7 @@ export const GL = {
37
37
  LOCALIZED_CONCEPT: 'gl:localizedConcept',
38
38
  RELATED: 'gl:related',
39
39
  PARTITIVE_RELATIONS: 'gl:partitiveRelations',
40
+ GENERIC_RELATIONS: 'gl:genericRelations',
40
41
  TAGS: 'gl:tags',
41
42
 
42
43
  // LocalizedConcept
@@ -139,6 +140,8 @@ export const GL = {
139
140
  // PartitiveRelation
140
141
  COMPREHENSIVE: 'gl:comprehensive',
141
142
  HAS_PARTITIVE: 'gl:hasPartitive',
143
+ HAS_GENERIC: 'gl:hasGeneric',
144
+ HAS_MEMBER: 'gl:members',
142
145
  COMPLETENESS: 'gl:completeness',
143
146
  CRITERION: 'gl:criterion',
144
147
 
@@ -28,6 +28,7 @@ import ConceptEditionRail from './ConceptEditionRail.vue';
28
28
  import NonVerbalRepDisplay from './NonVerbalRepDisplay.vue';
29
29
  import NonVerbalList from './non-verbal/NonVerbalList.vue';
30
30
  import PartitiveRelationList from './PartitiveRelationList.vue';
31
+ import GenericRelationList from './GenericRelationList.vue';
31
32
  import ConceptDiffModal from './ConceptDiffModal.vue';
32
33
  import CitationDisplay from './CitationDisplay.vue';
33
34
  import DesignationList from './DesignationList.vue';
@@ -75,6 +76,7 @@ const {
75
76
  inverseEdgeType,
76
77
  conceptRelated,
77
78
  conceptPartitiveRelations,
79
+ conceptGenericRelations,
78
80
  getResolvedRef,
79
81
  relatedLabel,
80
82
  navigateEdge,
@@ -577,6 +579,14 @@ const nonVerbalReps = computed(() => {
577
579
  :register-id="registerId"
578
580
  />
579
581
 
582
+ <!-- Generic relations — genus/species decompositions (ISO 704) -->
583
+ <GenericRelationList
584
+ v-if="conceptGenericRelations.length"
585
+ :relations="conceptGenericRelations"
586
+ :manifest="manifest"
587
+ :register-id="registerId"
588
+ />
589
+
580
590
  <!-- Edition series — supersession chain across vocabulary editions -->
581
591
  <ConceptEditionRail
582
592
  :concept-uri="conceptUri(props.concept, props.registerId, props.manifest.uriBase)"
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * GenericRelationList — renders one-to-many generic (genus/species)
4
+ * decompositions as ISO 704 rake diagrams. Mirror of
5
+ * PartitiveRelationList with type label swapped.
6
+ *
7
+ * Each relation card shows:
8
+ * - completeness badge (Complete / Partial)
9
+ * - the comprehensive (genus) designation as the card title
10
+ * - optional criterion as italic text
11
+ * - the rake diagram itself (PartitiveRelationDiagram, reused —
12
+ * the line notation is identical for generic rakes)
13
+ *
14
+ * OIML V 2-200:2010 has ~8 generic hyperedges; 5.1 measurement
15
+ * standard alone has 6 criterion groups. This component is the
16
+ * primary rendering surface for those datasets.
17
+ */
18
+ import type { GenericRelationWire, Manifest } from '../adapters/types';
19
+ import { useRouter } from 'vue-router';
20
+ import { useVocabularyStore } from '../stores/vocabulary';
21
+ import { getFactory } from '../adapters/factory';
22
+ import { useI18n, locale } from '../i18n';
23
+ import { completenessLabel } from '../utils/partitive-relation-styling';
24
+ import { presenceLabel, countLabel } from '../utils/partitive-multiplicity';
25
+ import { resolveDesignation } from '../utils/resolve-designation';
26
+ import PartitiveRelationDiagram, {
27
+ type PartitiveMemberLabeled,
28
+ } from './PartitiveRelationDiagram.vue';
29
+
30
+ const props = defineProps<{
31
+ relations: GenericRelationWire[];
32
+ manifest: Manifest;
33
+ registerId: string;
34
+ }>();
35
+
36
+ const router = useRouter();
37
+ const store = useVocabularyStore();
38
+ const factory = getFactory();
39
+ const { t } = useI18n();
40
+
41
+ function designationFor(uri: string): string {
42
+ return resolveDesignation(uri, store, factory, locale.value);
43
+ }
44
+
45
+ function memberLabel(member: GenericRelationWire['members'][number]): PartitiveMemberLabeled {
46
+ return {
47
+ uri: member.uri,
48
+ presence: member.presence,
49
+ count: member.count,
50
+ isDelimiting: member.isDelimiting,
51
+ label: designationFor(member.uri),
52
+ };
53
+ }
54
+ </script>
55
+
56
+ <template>
57
+ <section class="generic-relations" aria-label="Generic relations">
58
+ <div
59
+ v-for="(rel, i) in relations"
60
+ :key="`generic-${i}-${rel.comprehensive}`"
61
+ class="generic-relation-card"
62
+ >
63
+ <header class="relation-header">
64
+ <span class="badge badge-completeness" :class="`completeness-${rel.completeness}`">
65
+ {{ completenessLabel(rel.completeness) }}
66
+ </span>
67
+ <span class="badge badge-type">{{ t('relations.generic') }}</span>
68
+ <h4 class="comprehensive">{{ designationFor(rel.comprehensive) }}</h4>
69
+ </header>
70
+
71
+ <p v-if="rel.criterion" class="criterion">
72
+ <em>{{ rel.criterion[locale] || rel.criterion.eng || Object.values(rel.criterion)[0] }}</em>
73
+ </p>
74
+
75
+ <PartitiveRelationDiagram
76
+ :partitives="rel.members.map(memberLabel)"
77
+ :comprehensive-label="designationFor(rel.comprehensive)"
78
+ :completeness="rel.completeness"
79
+ />
80
+
81
+ <ul class="member-presence-list" aria-label="Member presence and count">
82
+ <li v-for="(m, j) in rel.members" :key="j">
83
+ <span class="member-label">{{ designationFor(m.uri) }}</span>
84
+ <span v-if="m.presence && m.presence !== 'required'" class="member-presence">{{ presenceLabel(m.presence) }}</span>
85
+ <span v-if="m.count && m.count !== 'exactly_one'" class="member-count">{{ countLabel(m.count) }}</span>
86
+ <span v-if="m.isDelimiting" class="member-delimiting">{{ t('relations.delimiting') }}</span>
87
+ </li>
88
+ </ul>
89
+ </div>
90
+ </section>
91
+ </template>
92
+
93
+ <style scoped>
94
+ .generic-relation-card {
95
+ border: 1px solid var(--color-border, #ccc);
96
+ border-radius: 6px;
97
+ padding: 1rem;
98
+ margin-bottom: 1rem;
99
+ background: var(--color-surface, #fff);
100
+ }
101
+
102
+ .relation-header {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 0.5rem;
106
+ flex-wrap: wrap;
107
+ }
108
+
109
+ .badge {
110
+ font-size: 0.75rem;
111
+ font-weight: 600;
112
+ padding: 0.15rem 0.5rem;
113
+ border-radius: 4px;
114
+ text-transform: uppercase;
115
+ letter-spacing: 0.05em;
116
+ }
117
+
118
+ .badge-completeness.completeness-complete {
119
+ background: var(--color-success-soft, #d4edda);
120
+ color: var(--color-success-fg, #155724);
121
+ }
122
+
123
+ .badge-completeness.completeness-partial {
124
+ background: var(--color-warn-soft, #fff3cd);
125
+ color: var(--color-warn-fg, #856404);
126
+ }
127
+
128
+ .badge-type {
129
+ background: var(--color-accent-soft, #d1ecf1);
130
+ color: var(--color-accent-fg, #0c5460);
131
+ }
132
+
133
+ .comprehensive {
134
+ margin: 0;
135
+ font-size: 1.1rem;
136
+ flex: 1 1 auto;
137
+ }
138
+
139
+ .criterion {
140
+ color: var(--color-text-muted, #6c757d);
141
+ margin: 0.5rem 0;
142
+ font-size: 0.95rem;
143
+ }
144
+
145
+ .member-presence-list {
146
+ list-style: none;
147
+ padding: 0;
148
+ margin: 0.5rem 0 0;
149
+ font-size: 0.85rem;
150
+ }
151
+
152
+ .member-presence-list li {
153
+ display: flex;
154
+ gap: 0.5rem;
155
+ align-items: center;
156
+ padding: 0.15rem 0;
157
+ }
158
+
159
+ .member-presence-list .member-presence,
160
+ .member-presence-list .member-count,
161
+ .member-presence-list .member-delimiting {
162
+ color: var(--color-text-muted, #6c757d);
163
+ font-size: 0.75rem;
164
+ border: 1px solid var(--color-border-light, #eee);
165
+ padding: 0.05rem 0.4rem;
166
+ border-radius: 3px;
167
+ }
168
+ </style>
@@ -20,6 +20,7 @@ import { getFactory } from '../adapters/factory';
20
20
  import { useI18n, locale } from '../i18n';
21
21
  import { completenessLabel } from '../utils/partitive-relation-styling';
22
22
  import { presenceLabel, countLabel } from '../utils/partitive-multiplicity';
23
+ import { resolveDesignation } from '../utils/resolve-designation';
23
24
  import PartitiveRelationDiagram, {
24
25
  type PartitiveMemberLabeled,
25
26
  } from './PartitiveRelationDiagram.vue';
@@ -36,25 +37,7 @@ const factory = getFactory();
36
37
  const { t } = useI18n();
37
38
 
38
39
  function designationFor(uri: string): string {
39
- const node = store.graph.getNode(uri);
40
- if (node) {
41
- const des = node.designations[locale.value]
42
- || node.designations.eng
43
- || Object.values(node.designations)[0];
44
- if (des) return des;
45
- }
46
- const resolution = factory.resolve(uri);
47
- if (resolution.type === 'internal') {
48
- const adapter = store.datasets.get(resolution.registerId);
49
- const entry = adapter?.getIndexEntry(resolution.conceptId);
50
- if (entry) {
51
- const des = entry.designations[locale.value]
52
- || entry.designations.eng
53
- || Object.values(entry.designations)[0];
54
- if (des) return des;
55
- }
56
- }
57
- return resolution.type === 'internal' ? resolution.conceptId : uri;
40
+ return resolveDesignation(uri, store, factory, locale.value);
58
41
  }
59
42
 
60
43
  function navigate(uri: string) {