Update LinkML manifest generation timestamp and enhance MappingExplorer with schema validation

- Updated the generated timestamp in the LinkML manifest file.
- Added new CSS styles for schema status and warning indicators in MappingExplorer.
- Implemented schema validation logic in MappingExplorer to check field validity against the loaded LinkML schema.
- Enhanced the UI to display schema status and warnings for invalid fields in the mapping interface.
- Refactored field details panel to show schema validity messages for target classes and slots.
- Updated various target classes and slots in custodian data mappings for consistency and accuracy.
This commit is contained in:
kempersc 2026-02-18 18:44:03 +01:00
parent a202c61435
commit b34a8ac777
41 changed files with 4822 additions and 3413 deletions

View file

@ -6,8 +6,9 @@
"scripts": {
"sync-schemas": "rsync -av --delete --exclude=\"archive/\" ../schemas/20251121/linkml/ public/schemas/20251121/linkml/",
"generate-manifest": "node scripts/generate-schema-manifest.cjs",
"dev": "pnpm run sync-schemas && pnpm run generate-manifest && vite",
"build": "pnpm run sync-schemas && pnpm run generate-manifest && tsc -b && vite build",
"generate-uml": "node scripts/generate-ontology-uml.cjs",
"dev": "pnpm run sync-schemas && pnpm run generate-manifest && pnpm run generate-uml && vite",
"build": "pnpm run sync-schemas && pnpm run generate-manifest && pnpm run generate-uml && tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest",

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ prefixes:
schema: http://schema.org/
crm: http://www.cidoc-crm.org/cidoc-crm/
gn: http://www.geonames.org/ontology#
rdac: http://rdaregistry.info/Elements/c/
wdt: http://www.wikidata.org/prop/direct/
dcterms: http://purl.org/dc/terms/
prov: http://www.w3.org/ns/prov#
@ -58,6 +59,7 @@ classes:
close_mappings:
- crm:E53_Place
- gn:Feature
- rdac:C10009 # RDA Registry: Place (RDA Classes)
slots:
- has_label
- has_name

View file

@ -1,5 +1,5 @@
{
"generated": "2026-02-18T11:39:46.457Z",
"generated": "2026-02-18T16:59:17.611Z",
"schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2183,
"categoryCounts": {

View file

@ -12,6 +12,7 @@ prefixes:
gbif: http://rs.gbif.org/terms/
aat: http://vocab.getty.edu/aat/
imports:
- ./ExhibitedObject
- linkml:types
- ../enums/PreservationMethodEnum
- ../metadata
@ -47,6 +48,7 @@ imports:
default_prefix: hc
classes:
BiologicalObject:
is_a: ExhibitedObject
class_uri: crm:E20_Biological_Object
description: >-
Natural specimen or organism-derived item held in a heritage collection, with associated taxonomic identification and preservation metadata.
@ -397,29 +399,64 @@ classes:
in_language: zh
- literal_form: value: https://nde.nl/ontology/hc/taxon/raphus-cucullatus
broad_mappings:
- crm:E20_Biological_Object
close_mappings:
- dwc:Occurrence
- gbif:Specimen
related_mappings:
- crm:E19_Physical_Object
- crm:E22_Human-Made_Object
- schema:Taxon
predicate: EXACT_SYNONYM
slots:
- identified_by
- has_label
- has_name
- has_rank
- has_authority
- commented_on
- identified_through
- has_specimen
- symbolize
- has_status
- has_gender
- has_stage
- contain
- has_quantity
- has_method
- has_detail
- prepared_on
- prepared_by
- acquired_through
- in_place
- describe
- acquired_by
- has_habitat
- has_hypernym
- listed_in
- has_provenance
- has_type
in_language: zh
# range: string # uriorcurie
slot_usage:
identified_by:
multivalued: true
inlined: false # Fixed invalid inline for primitive type
inlined_as_list: false # Fixed invalid inline for primitive type
required: false
any_of:
- range: FieldNumber
- range: BOLDIdentifier
- range: WikiDataIdentifier
- range: string # uriorcurie
- range: FieldNumber
- range: BOLDIdentifier
- range: WikiDataIdentifier
- range: string # uriorcurie
examples:
- value:
has_type: FieldNumber
- value:
id: https://nde.nl/ontology/hc/bold-id/NLNAT001-21
has_type: BOLDIdentifier
- value:
has_type: WikiDataIdentifier
- value:
has_type: FieldNumber
- value:
id: https://nde.nl/ontology/hc/bold-id/NLNAT001-21
has_type: BOLDIdentifier
- value:
has_type: WikiDataIdentifier
has_label:
range: TaxonName
inlined: true

View file

@ -36,195 +36,81 @@ classes:
zh: >-
描述平台界面、操作程序或用户实施指南的技术参考资料。
structured_aliases:
- literal_form: documentatie
predicate: EXACT_SYNONYM
in_language: nl
- literal_form: technische handleiding
predicate: EXACT_SYNONYM
in_language: nl
- literal_form: Dokumentation
predicate: EXACT_SYNONYM
in_language: de
- literal_form: technische Anleitung
predicate: EXACT_SYNONYM
in_language: de
- literal_form: documentation
predicate: EXACT_SYNONYM
in_language: fr
- literal_form: guide technique
predicate: EXACT_SYNONYM
in_language: fr
- literal_form: documentación
predicate: EXACT_SYNONYM
in_language: es
- literal_form: guía técnica
predicate: EXACT_SYNONYM
in_language: es
- literal_form: التوثيق
predicate: EXACT_SYNONYM
in_language: ar
- literal_form: الدليل الفني
predicate: EXACT_SYNONYM
in_language: ar
- literal_form: dokumentasi
predicate: EXACT_SYNONYM
in_language: id
- literal_form: panduan teknis
predicate: EXACT_SYNONYM
in_language: id
- literal_form: 文档
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: 技术指南
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: documentation
broad_mappings:
- schema:TechArticle
- foaf:Document
close_mappings:
- crm:E73_Information_Object
predicate: EXACT_SYNONYM
slots:
- identified_by
- has_label
- has_description
- temporal_extent
in_language: zh
- literal_form: technical guide
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: API reference
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: user manual
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: operational guide
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: schema:CreativeWork
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: foaf:Document
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: schema:TechArticle
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: crm:E73_Information_Object
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: dcterms:references
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_description
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: identified_by
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: temporal_extent
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: https://data.rijksmuseum.nl/object-metadata/api/
predicate: EXACT_SYNONYM
in_language: zh
# range: string
slot_usage:
identified_by:
range: uri
required: true
examples:
- value: API Reference Documentation
- value: Developer Integration Guide
- value: https://data.rijksmuseum.nl/object-metadata/api/
has_label:
required: true
examples:
- value: Rijksmuseum Collection API
has_description:
# range: string
examples:
- value: Complete REST API reference with endpoint specifications, authentication, and response formats.
temporal_extent:
range: TimeSpan
inlined: true
required: false
examples:
- value:
begin_of_the_begin: '2015-01-01'
- value: Complete REST API reference with endpoint specifications, authentication, and response formats.
temporal_extent:
required: false
examples:
- value:
begin_of_the_begin: '2015-01-01'
has_verbatim_value: 2015-
comments:
- Generic documentation class replacing domain-specific documentation slots
- Supports multiple documentation types (API, user, developer, system)

View file

@ -186,19 +186,19 @@ classes:
in_language: zh
- literal_form: value: |
- literal_form: "value: |"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: expense_type: ADMINISTRATIVE
- literal_form: "expense_type: ADMINISTRATIVE"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: expense_type: PROGRAM
- literal_form: "expense_type: PROGRAM"
predicate: EXACT_SYNONYM

View file

@ -185,7 +185,7 @@ classes:
in_language: zh
- literal_form: value: |
- literal_form: "value: |"
predicate: EXACT_SYNONYM
@ -197,37 +197,37 @@ classes:
in_language: zh
- literal_form: has_label: Manufacturer name (String or Label)
- literal_form: "has_label: Manufacturer name (String or Label)"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_url: Manufacturer website (URL)
- literal_form: "has_url: Manufacturer website (URL)"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: identified_by: Unique identifier
- literal_form: "identified_by: Unique identifier"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Primary**: `schema:Organization` - Schema.org organization
- literal_form: "**Primary**: `schema:Organization` - Schema.org organization"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Close**: `org:Organization` - W3C ORG organization
- literal_form: "**Close**: `org:Organization` - W3C ORG organization"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Related**: `prov:Agent` - PROV-O agent responsible for production
- literal_form: "**Related**: `prov:Agent` - PROV-O agent responsible for production"
predicate: EXACT_SYNONYM

View file

@ -174,13 +174,13 @@ classes:
in_language: zh
- literal_form: value: PRIMARY
- literal_form: "value: PRIMARY"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: CO_ORGANIZER
- literal_form: "value: CO_ORGANIZER"
predicate: EXACT_SYNONYM
@ -192,43 +192,43 @@ classes:
in_language: zh
- literal_form: PRIMARY: Main organizing institution
- literal_form: "PRIMARY: Main organizing institution"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: CO_ORGANIZER: Partner institution with significant organizational role
- literal_form: "CO_ORGANIZER: Partner institution with significant organizational role"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: SPONSOR_ORGANIZER: Sponsor with curatorial/organizational input
- literal_form: "SPONSOR_ORGANIZER: Sponsor with curatorial/organizational input"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: LENDING_INSTITUTION: Institution lending objects with exhibition involvement
- literal_form: "LENDING_INSTITUTION: Institution lending objects with exhibition involvement"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: HOST_VENUE: Venue hosting a traveling exhibition
- literal_form: "HOST_VENUE: Venue hosting a traveling exhibition"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Primary**: `schema:Role` - Schema.org role
- literal_form: "**Primary**: `schema:Role` - Schema.org role"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Close**: `prov:Role` - PROV-O role in activity
- literal_form: "**Close**: `prov:Role` - PROV-O role in activity"
predicate: EXACT_SYNONYM

View file

@ -380,163 +380,163 @@ classes:
in_language: zh
- literal_form: value: https://nde.nl/ontology/hc/aux/kroller-muller-sculpture
- literal_form: "value: https://nde.nl/ontology/hc/aux/kroller-muller-sculpture"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Kroller-Muller Beeldentuin
- literal_form: "value: Kroller-Muller Beeldentuin"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Paleis Het Loo Tuinen
- literal_form: "value: Paleis Het Loo Tuinen"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Archeologisch Park Matilo
- literal_form: "value: Archeologisch Park Matilo"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: One of Europe's largest sculpture gardens with 160 works set in 25 hectares of park landscape within De Hoge Veluwe National Park.
- literal_form: "value: One of Europe's largest sculpture gardens with 160 works set in 25 hectares of park landscape within De Hoge Veluwe National Park."
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: range: OutdoorSiteTypeEnum
- literal_form: "range: OutdoorSiteTypeEnum"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: range: FeatureType
- literal_form: "range: FeatureType"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: SCULPTURE_GARDEN
- literal_form: "value: SCULPTURE_GARDEN"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: ARCHAEOLOGICAL_SITE
- literal_form: "value: ARCHAEOLOGICAL_SITE"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: FORMAL_GARDEN
- literal_form: "value: FORMAL_GARDEN"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: BotanicalInstitutionClassification
- literal_form: "value: BotanicalInstitutionClassification"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: ZoologicalInstitutionClassification
- literal_form: "value: ZoologicalInstitutionClassification"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: 160
- literal_form: "value: 160"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: 2500
- literal_form: "value: 2500"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 25.0
- literal_form: "area_value: 25.0"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Included with museum ticket
- literal_form: "has_label: Included with museum ticket"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Paved paths
- literal_form: "has_label: Paved paths"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Wheelchair routes available
- literal_form: "has_label: Wheelchair routes available"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 650.0
- literal_form: "area_value: 650.0"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Included with palace ticket
- literal_form: "has_label: Included with palace ticket"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 3.5
- literal_form: "area_value: 3.5"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Free
- literal_form: "has_label: Free"
predicate: EXACT_SYNONYM

View file

@ -200,13 +200,13 @@ classes:
in_language: zh
- literal_form: value: hc:ArchiveOrganizationType
- literal_form: "value: hc:ArchiveOrganizationType"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: hc:HolySacredSiteType
- literal_form: "value: hc:HolySacredSiteType"
predicate: EXACT_SYNONYM

View file

@ -24,6 +24,7 @@ prefixes:
skos: http://www.w3.org/2004/02/skos/core#
owl: http://www.w3.org/2002/07/owl#
schema: http://schema.org/
la: https://linked.art/ns/terms/
imports:
- linkml:types
default_prefix: hc
@ -87,6 +88,7 @@ slots:
close_mappings:
- owl:sameAs # W3C OWL 2 standard - Identity assertion (stronger claim than semantic equivalence).
- schema:sameAs # schemaorg.owl:34129-34148 - "URL unambiguously indicating the item's identity."
- la:equivalent # Linked Art extensions: equivalent instance (skos:exactMatch-like without Concept inference)
aliases:
- is_or_was_equivalent_to
- wikidata_equivalent

View file

@ -23,6 +23,7 @@ prefixes:
schema: http://schema.org/
dcat: http://www.w3.org/ns/dcat#
dcterms: http://purl.org/dc/terms/
ardo: https://w3id.org/ardo/2.0/
imports:
- linkml:types
default_prefix: hc
@ -80,6 +81,8 @@ slots:
- dcat:keyword # dcat3.ttl:1208-1231 - "A keyword or tag describing a resource"
close_mappings:
- dcterms:subject # dcterms.rdf:1968-1988 - "A topic of the resource"
related_mappings:
- ardo:has_keyword # ArDO 2.0: links a thematic subcategory to a keyword (object property)
comments:
- "Used for discovery and classification."
annotations:

View file

@ -25,6 +25,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
org: http://www.w3.org/ns/org#
schema: http://schema.org/
la: https://linked.art/ns/terms/
xsd: http://www.w3.org/2001/XMLSchema#
default_prefix: hc
imports:
@ -80,5 +81,7 @@ slots:
exact_mappings:
- org:hasMember # org.rdf:427-446 - "Indicates a person who is a member of the subject Organization." Domain: Organization, Range: Agent. Organization membership only; this slot also covers collection elements.
- schema:member # schemaorg.owl:26055-26085 - "A member of an Organization or a ProgramMembership." Domain: Organization/ProgramMembership. Does not cover collection elements.
related_mappings:
- la:has_member # Linked Art extensions: membership (Set→Entity)
annotations:
custodian_types: '["*"]'

View file

@ -19,6 +19,7 @@ prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
dcterms: http://purl.org/dc/terms/
pca: http://rds.posccaesar.org/ontology/plm/rdl/
default_prefix: hc
imports:
- linkml:types
@ -82,3 +83,5 @@ slots:
custodian_types: '["*"]'
close_mappings:
- dcterms:conformsTo # dcterms.rdf:987-1010 - "An established standard to which the described resource conforms." Conformance relationship ≠ having a standard.
related_mappings:
- pca:PCA_100003538 # POSC Caesar RDL (PCA PLM core): Standard (class)

View file

@ -24,6 +24,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
pav: http://purl.org/pav/2.3#
default_prefix: hc
imports:
- linkml:types
@ -81,6 +82,7 @@ slots:
multivalued: false
related_mappings:
- dcterms:hasVersion # dcterms.rdf:1371-1395 — "A related resource that is a version, edition, or adaptation." Relates resources to each other, not a version identifier.
- pav:hasVersion # PAV 2.3 (used by ArDO): links a resource to a version resource
aliases:
- has_or_had_version
- api_ver

View file

@ -32,6 +32,7 @@ prefixes:
rico: https://www.ica.org/standards/RiC/ontology#
org: http://www.w3.org/ns/org#
foaf: http://xmlns.com/foaf/0.1/
la: https://linked.art/ns/terms/
default_prefix: hc
imports:
- linkml:types
@ -98,6 +99,7 @@ slots:
- rico:isOrWasMemberOf # RiC-O_1-1.rdf:14505-14550 - Person→Group (restricted domain)
related_mappings:
- foaf:member # foaf.ttl:410-417 - INVERSE: Group→Agent (not Agent→Group)
- la:member_of # Linked Art extensions: membership (Entity→Set)
annotations:
inverse_slot: has_or_had_member
deprecates: is_member_of

View file

@ -35,6 +35,7 @@ prefixes:
time: http://www.w3.org/2006/time#
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
pav: http://purl.org/pav/2.3#
default_prefix: hc
imports:
- linkml:types
@ -110,6 +111,7 @@ slots:
- schema:predecessorOf # schemaorg.owl:30406-30420 - "Previous variant of product; ProductModel domain"
- schema:previousItem # schemaorg.owl:30559-30575 - "Preceding ListItem; ListItem domain"
- dcterms:replaces # dcterms.rdf:1827-1846 - "Supplants/supersedes described resource; implies replacement"
- pav:previousVersion # PAV 2.3 (used by ArDO): previous version link (version chain)
aliases:
- previous_observation
examples:

View file

@ -24,6 +24,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
odrl: http://www.w3.org/ns/odrl/2/
la: https://linked.art/ns/terms/
skos: http://www.w3.org/2004/02/skos/core#
xsd: http://www.w3.org/2001/XMLSchema#
@ -86,5 +87,6 @@ slots:
- is_or_was_transferred_to
related_mappings:
- schema:TransferAction # schemaorg.owl:5808-5812 - "act of transferring animate or inanimate objects"
- la:transferred_to # Linked Art extensions: transferred to (Transfer→Entity)
annotations:
custodian_types: '["*"]'

View file

@ -0,0 +1,356 @@
#!/usr/bin/env node
/*
* Generate a Mermaid UML class diagram from the synced LinkML schemas.
*
* Output: frontend/public/data/heritage_custodian_ontology.mmd
*
* The Visualize page loads this file from /data/heritage_custodian_ontology.mmd.
*/
const fs = require('fs');
const path = require('path');
const SCHEMA_DIR = path.join(__dirname, '../public/schemas/20251121/linkml');
const MANIFEST_PATH = path.join(SCHEMA_DIR, 'manifest.json');
const OUTPUT_PATH = path.join(__dirname, '../public/data/heritage_custodian_ontology.mmd');
function readJson(p) {
return JSON.parse(fs.readFileSync(p, 'utf8'));
}
function readText(p) {
return fs.readFileSync(p, 'utf8');
}
function ensureDir(p) {
const dir = path.dirname(p);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function safeArray(v) {
return Array.isArray(v) ? v : [];
}
function indentOf(line) {
const m = line.match(/^ */);
return m ? m[0].length : 0;
}
function isBlankOrComment(line) {
const t = line.trim();
return t === '' || t.startsWith('#');
}
function stripInlineComment(v) {
// Conservative: only strip comments when preceded by whitespace.
// This avoids clobbering URLs like http://example.com#fragment.
return v.replace(/\s+#.*$/, '').trim();
}
function stripQuotes(v) {
const s = v.trim();
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
return s.slice(1, -1);
}
return s;
}
function collectIndentedBlock(lines, startIndex, startIndent) {
const block = [lines[startIndex]];
for (let i = startIndex + 1; i < lines.length; i++) {
const line = lines[i];
if (isBlankOrComment(line)) {
block.push(line);
continue;
}
const ind = indentOf(line);
if (ind <= startIndent) break;
block.push(line);
}
return block;
}
function findScalarInBlock(blockLines, key, minIndent) {
// Returns a single-line scalar value for `${key}: value`.
// Ignores folded/literal multi-line scalars (>- / |).
for (const line of blockLines) {
if (isBlankOrComment(line)) continue;
if (indentOf(line) < minIndent) continue;
const m = line.match(new RegExp(`^\\s*${key}:\\s*(.+)\\s*$`));
if (!m) continue;
const raw = stripInlineComment(m[1]);
if (raw === '' || raw === '|' || raw === '>-') continue;
return stripQuotes(raw);
}
return null;
}
function findListInBlock(blockLines, key, minIndent) {
// Parses:
// key:
// - item
// - item2
const out = [];
for (let i = 0; i < blockLines.length; i++) {
const line = blockLines[i];
if (isBlankOrComment(line)) continue;
const ind = indentOf(line);
if (ind < minIndent) continue;
const keyMatch = line.match(new RegExp(`^\\s*${key}:\\s*$`));
if (!keyMatch) continue;
const keyIndent = ind;
for (let j = i + 1; j < blockLines.length; j++) {
const l = blockLines[j];
if (isBlankOrComment(l)) continue;
const li = indentOf(l);
if (li <= keyIndent) break;
const m = l.match(/^\s*-\s*(.+)\s*$/);
if (m) {
out.push(stripQuotes(stripInlineComment(m[1])));
}
}
break;
}
return out;
}
function parseSlotUsageOverrides(classBlockLines, minIndent) {
// Parses:
// slot_usage:
// slot_name:
// range: SomeClass
const overrides = new Map();
for (let i = 0; i < classBlockLines.length; i++) {
const line = classBlockLines[i];
if (isBlankOrComment(line)) continue;
const ind = indentOf(line);
if (ind < minIndent) continue;
if (!line.match(/^\s*slot_usage:\s*$/)) continue;
const usageIndent = ind;
let currentSlot = null;
let currentSlotIndent = null;
for (let j = i + 1; j < classBlockLines.length; j++) {
const l = classBlockLines[j];
if (isBlankOrComment(l)) continue;
const li = indentOf(l);
if (li <= usageIndent) break;
// Slot key line: ` slot_name:`
if (li === usageIndent + 2) {
const mKey = l.trim().match(/^([^\s:]+):\s*$/);
if (mKey) {
currentSlot = mKey[1];
currentSlotIndent = li;
continue;
}
}
if (currentSlot && currentSlotIndent !== null && li > currentSlotIndent) {
const mRange = l.match(/^\s*range:\s*(.+)\s*$/);
if (mRange) {
const range = stripQuotes(stripInlineComment(mRange[1]));
if (range) overrides.set(currentSlot, range);
}
}
}
break;
}
return overrides;
}
function extractNamedMappingBlock(text, rootKey, itemName) {
// Extract the YAML-ish block for `rootKey: { itemName: {...} }` without parsing YAML.
// This is resilient to invalid YAML elsewhere in the file.
const lines = text.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (isBlankOrComment(line)) continue;
const rootMatch = line.match(new RegExp(`^\\s*${rootKey}:\\s*$`));
if (!rootMatch) continue;
const rootIndent = indentOf(line);
for (let j = i + 1; j < lines.length; j++) {
const l = lines[j];
if (isBlankOrComment(l)) continue;
const li = indentOf(l);
if (li <= rootIndent) break;
if (li === rootIndent + 2 && l.trim() === `${itemName}:`) {
return {
blockLines: collectIndentedBlock(lines, j, li),
baseIndent: li,
};
}
}
}
return null;
}
function extractAllNamedMappingBlocks(text, rootKey) {
// Extract all mapping entry blocks under `rootKey:`
// Example:
// classes:
// Foo:
// ...
// Bar:
// ...
const lines = text.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (isBlankOrComment(line)) continue;
const rootMatch = line.match(new RegExp(`^\\s*${rootKey}:\\s*$`));
if (!rootMatch) continue;
const rootIndent = indentOf(line);
const blocks = [];
for (let j = i + 1; j < lines.length; j++) {
const l = lines[j];
if (isBlankOrComment(l)) continue;
const li = indentOf(l);
if (li <= rootIndent) break;
if (li === rootIndent + 2) {
const mKey = l.trim().match(/^([^\s:]+):\s*$/);
if (mKey) {
blocks.push({
name: mKey[1],
blockLines: collectIndentedBlock(lines, j, li),
baseIndent: li,
});
}
}
}
return blocks;
}
return [];
}
function main() {
if (!fs.existsSync(MANIFEST_PATH)) {
console.error(`[generate-ontology-uml] Missing manifest: ${MANIFEST_PATH}`);
console.error('[generate-ontology-uml] Run pnpm run generate-manifest first.');
process.exit(1);
}
const manifest = readJson(MANIFEST_PATH);
const classCategory = (manifest.categories || []).find((c) => c.name === 'class');
const slotCategory = (manifest.categories || []).find((c) => c.name === 'slot');
const classFiles = safeArray(classCategory && classCategory.files);
const slotFiles = safeArray(slotCategory && slotCategory.files);
const slotIndex = new Map();
for (const f of slotFiles) {
const fullPath = path.join(SCHEMA_DIR, f.path);
if (!fs.existsSync(fullPath)) continue;
const text = readText(fullPath);
const slotBlocks = extractAllNamedMappingBlocks(text, 'slots');
for (const b of slotBlocks) {
const range = findScalarInBlock(b.blockLines, 'range', b.baseIndent + 2);
const multivaluedRaw = findScalarInBlock(b.blockLines, 'multivalued', b.baseIndent + 2);
const multivalued = multivaluedRaw === 'true' || multivaluedRaw === 'True';
if (!slotIndex.has(b.name)) {
slotIndex.set(b.name, {
range,
multivalued,
});
}
}
}
const classIndex = new Map();
for (const f of classFiles) {
const fullPath = path.join(SCHEMA_DIR, f.path);
if (!fs.existsSync(fullPath)) continue;
const text = readText(fullPath);
const classBlocks = extractAllNamedMappingBlocks(text, 'classes');
for (const b of classBlocks) {
const baseIndent = b.baseIndent;
const parent = findScalarInBlock(b.blockLines, 'is_a', baseIndent + 2);
const slots = findListInBlock(b.blockLines, 'slots', baseIndent + 2);
const overrides = parseSlotUsageOverrides(b.blockLines, baseIndent + 2);
if (!classIndex.has(b.name)) {
classIndex.set(b.name, {
is_a: parent,
slots,
slot_usage_overrides: overrides,
});
}
}
}
const classNames = [...classIndex.keys()].sort((a, b) => a.localeCompare(b));
const lines = [];
lines.push('```mermaid');
lines.push('classDiagram');
lines.push(`%% Generated from LinkML manifest: /schemas/20251121/linkml/manifest.json`);
if (manifest.generated) {
lines.push(`%% Manifest generated: ${manifest.generated}`);
}
lines.push('direction LR');
for (const c of classNames) {
lines.push(`class ${c}`);
}
const edgeSet = new Set();
const addEdge = (a, b, kind, label) => {
const key = `${a}|${b}|${kind}|${label || ''}`;
if (edgeSet.has(key)) return;
edgeSet.add(key);
if (kind === 'inheritance') {
lines.push(`${b} <|-- ${a}`);
return;
}
if (kind === 'association') {
lines.push(`${a} --> ${b} : ${label}`);
}
};
for (const [className, classDef] of classIndex.entries()) {
const parent = classDef.is_a;
if (parent && classIndex.has(parent)) {
addEdge(className, parent, 'inheritance');
}
const slots = safeArray(classDef.slots);
const slotUsageOverrides = classDef.slot_usage_overrides instanceof Map ? classDef.slot_usage_overrides : new Map();
for (const slotName of slots) {
const slotDef = slotIndex.get(slotName) || {};
const range = slotUsageOverrides.get(slotName) || slotDef.range;
if (!range) continue;
if (classIndex.has(range)) {
addEdge(className, range, 'association', slotName);
}
}
}
lines.push('```');
lines.push('');
ensureDir(OUTPUT_PATH);
fs.writeFileSync(OUTPUT_PATH, lines.join('\n'), 'utf8');
console.log(`[generate-ontology-uml] Wrote ${OUTPUT_PATH}`);
console.log(`[generate-ontology-uml] Classes: ${classIndex.size}, Slots indexed: ${slotIndex.size}, Edges: ${edgeSet.size}`);
}
main();

View file

@ -263,6 +263,25 @@
flex-shrink: 0;
}
.mapping-explorer__schema-status {
display: flex;
align-items: center;
height: 34px;
padding: 0 10px;
border-radius: 6px;
border: 1px solid #e0e0e0;
background: #ffffff;
color: #172a59;
font-size: 12px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
white-space: nowrap;
}
.mapping-explorer__schema-status--warn {
border-color: #fa5200;
box-shadow: 0 0 0 2px rgba(250, 82, 0, 0.15);
}
/* ============================================================================
* STATISTICS BAR
* ============================================================================ */
@ -528,6 +547,29 @@
white-space: nowrap;
}
.mapping-explorer__schema-warning {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 999px;
border: 1px solid rgba(250, 82, 0, 0.45);
background: rgba(250, 82, 0, 0.12);
color: #fa5200;
font-size: 12px;
font-weight: 700;
line-height: 1;
}
.mapping-explorer__schema-warning-text {
margin-left: 8px;
font-size: 12px;
color: #fa5200;
font-weight: 600;
}
/* ============================================================================
* CENTER PANEL: MAPPING ARROWS
* ============================================================================ */

View file

@ -25,6 +25,7 @@ import {
type MappingStatus,
type CategoryGroup,
} from '../../lib/linkml/custodian-data-mappings';
import { loadManifest } from '../../lib/linkml/schema-loader';
import './MappingExplorer.css';
// ============================================================================
@ -139,6 +140,7 @@ interface SourceTreeItemProps {
isSelected: boolean;
selectedField: FieldMapping | null;
dataSource: DataSourceType;
getFieldSchemaValidity?: (field: FieldMapping) => { classOk: boolean; slotOk: boolean };
onToggle: () => void;
onSelectSource: () => void;
onSelectField: (field: FieldMapping) => void;
@ -156,6 +158,7 @@ const SourceTreeItem: React.FC<SourceTreeItemProps> = ({
isSelected,
selectedField,
dataSource,
getFieldSchemaValidity,
onToggle,
onSelectSource,
onSelectField,
@ -182,19 +185,32 @@ const SourceTreeItem: React.FC<SourceTreeItemProps> = ({
{isExpanded && (
<div className="mapping-explorer__field-list">
{mapping.fields.map((field, idx) => (
<div
key={idx}
className={`mapping-explorer__field-item ${selectedField === field ? 'mapping-explorer__field-item--selected' : ''}`}
onClick={() => onSelectField(field)}
>
<span className="mapping-explorer__field-path">
{(field.sourcePath ?? '—').replace(`${mapping.sourceBlock}.`, '')}
</span>
{field.status && <StatusBadge status={field.status} />}
<TransformationBadge type={field.transformation} />
</div>
))}
{mapping.fields.map((field, idx) => {
const validity = getFieldSchemaValidity?.(field);
const isInvalid = validity ? (!validity.classOk || !validity.slotOk) : false;
return (
<div
key={idx}
className={`mapping-explorer__field-item ${selectedField === field ? 'mapping-explorer__field-item--selected' : ''}`}
onClick={() => onSelectField(field)}
>
<span className="mapping-explorer__field-path">
{(field.sourcePath ?? '—').replace(`${mapping.sourceBlock}.`, '')}
</span>
{isInvalid && (
<span
className="mapping-explorer__schema-warning"
title="Target class/slot not found in current LinkML schema"
>
!
</span>
)}
{field.status && <StatusBadge status={field.status} />}
<TransformationBadge type={field.transformation} />
</div>
);
})}
</div>
)}
</div>
@ -234,6 +250,7 @@ interface FieldDetailsPanelProps {
field: FieldMapping;
sourceBlock: string;
viewMode: 'linkml' | 'typedb' | 'rdf';
schemaValidity?: { classOk: boolean; slotOk: boolean };
translations: {
copyPath: string;
copyRdf: string;
@ -243,7 +260,7 @@ interface FieldDetailsPanelProps {
};
}
const FieldDetailsPanel: React.FC<FieldDetailsPanelProps> = ({ field, sourceBlock: _sourceBlock, viewMode, translations }) => {
const FieldDetailsPanel: React.FC<FieldDetailsPanelProps> = ({ field, sourceBlock: _sourceBlock, viewMode, schemaValidity, translations }) => {
const [copiedField, setCopiedField] = useState<string | null>(null);
const copyToClipboard = (text: string, fieldName: string) => {
@ -301,30 +318,36 @@ const FieldDetailsPanel: React.FC<FieldDetailsPanelProps> = ({ field, sourceBloc
)}
</div>
{viewMode === 'linkml' && (
<div className="mapping-explorer__details-section">
<h5>
<FileCode size={16} className="mapping-explorer__section-icon" />
LinkML Target
</h5>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Class:</span>
<code className="mapping-explorer__details-value mapping-explorer__details-value--class">
{field.targetClass}
</code>
{viewMode === 'linkml' && (
<div className="mapping-explorer__details-section">
<h5>
<FileCode size={16} className="mapping-explorer__section-icon" />
LinkML Target
</h5>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Class:</span>
<code className="mapping-explorer__details-value mapping-explorer__details-value--class">
{field.targetClass}
</code>
{schemaValidity && !schemaValidity.classOk && (
<span className="mapping-explorer__schema-warning-text">Unknown in schema</span>
)}
</div>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Slot:</span>
<code className="mapping-explorer__details-value">{field.targetSlot}</code>
{schemaValidity && !schemaValidity.slotOk && (
<span className="mapping-explorer__schema-warning-text">Unknown in schema</span>
)}
</div>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Required:</span>
<span className={`mapping-explorer__details-value ${field.required ? 'mapping-explorer__details-value--required' : ''}`}>
{field.required ? 'Yes' : 'No'}
</span>
</div>
</div>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Slot:</span>
<code className="mapping-explorer__details-value">{field.targetSlot}</code>
</div>
<div className="mapping-explorer__details-row">
<span className="mapping-explorer__details-label">Required:</span>
<span className={`mapping-explorer__details-value ${field.required ? 'mapping-explorer__details-value--required' : ''}`}>
{field.required ? 'Yes' : 'No'}
</span>
</div>
</div>
)}
)}
{viewMode === 'typedb' && field.typedbEntity && (
<div className="mapping-explorer__details-section">
@ -522,6 +545,8 @@ export const MappingExplorer: React.FC<MappingExplorerProps> = ({ language = 'en
const [viewMode, setViewMode] = useState<'linkml' | 'typedb' | 'rdf'>('linkml');
const [isExporting, setIsExporting] = useState(false);
const [schemaIndex, setSchemaIndex] = useState<{ classes: Set<string>; slots: Set<string>; generated?: string } | null>(null);
// Reset selection when data source changes
useEffect(() => {
const defaultSource = dataSource === 'custodian' ? 'ghcid' : 'profile_identity';
@ -539,6 +564,56 @@ export const MappingExplorer: React.FC<MappingExplorerProps> = ({ language = 'en
const currentMappings = useMemo(() => getMappingsForDataSource(dataSource), [dataSource]);
const currentCategories = useMemo(() => getCategoriesForDataSource(dataSource), [dataSource]);
// Load LinkML schema manifest for validation (best-effort)
useEffect(() => {
let cancelled = false;
(async () => {
try {
const manifest = await loadManifest();
if (!manifest || cancelled) return;
const classes = new Set<string>();
const slots = new Set<string>();
for (const category of manifest.categories ?? []) {
if (category.name === 'class') {
for (const f of category.files ?? []) classes.add(f.name);
}
if (category.name === 'slot') {
for (const f of category.files ?? []) slots.add(f.name);
}
}
setSchemaIndex({ classes, slots, generated: manifest.generated });
} catch (e) {
// Ignore - mapping explorer should remain usable without schema assets
console.warn('[MappingExplorer] Could not load LinkML manifest for validation', e);
}
})();
return () => {
cancelled = true;
};
}, []);
const getFieldSchemaValidity = useCallback((field: FieldMapping) => {
if (!schemaIndex) return { classOk: true, slotOk: true };
const classOk = field.targetClass ? schemaIndex.classes.has(field.targetClass) : true;
const slotOk = field.targetSlot ? schemaIndex.slots.has(field.targetSlot) : true;
return { classOk, slotOk };
}, [schemaIndex]);
const schemaMismatchCounts = useMemo(() => {
if (!schemaIndex) return { invalidFields: 0 };
let invalidFields = 0;
currentMappings.forEach(m => {
m.fields.forEach(f => {
const v = getFieldSchemaValidity(f);
if (!v.classOk || !v.slotOk) invalidFields++;
});
});
return { invalidFields };
}, [schemaIndex, currentMappings, getFieldSchemaValidity]);
// Status counts for statistics panel
const statusCounts = useMemo((): StatusCounts => {
const counts: StatusCounts = { mapped: 0, partial: 0, out_of_scope: 0, future: 0 };
@ -849,6 +924,15 @@ export const MappingExplorer: React.FC<MappingExplorerProps> = ({ language = 'en
<Download size={16} />
{isExporting ? t('exporting') : t('exportLinkMLMap')}
</button>
{schemaIndex && (
<div
className={`mapping-explorer__schema-status ${schemaMismatchCounts.invalidFields > 0 ? 'mapping-explorer__schema-status--warn' : ''}`}
title={`LinkML schema manifest loaded${schemaIndex.generated ? ` (generated: ${schemaIndex.generated})` : ''}`}
>
Schema: {schemaMismatchCounts.invalidFields === 0 ? 'OK' : `${schemaMismatchCounts.invalidFields} invalid targets`}
</div>
)}
</div>
</div>
@ -872,6 +956,7 @@ export const MappingExplorer: React.FC<MappingExplorerProps> = ({ language = 'en
isSelected={selectedSource === mapping.sourceBlock}
selectedField={selectedField}
dataSource={dataSource}
getFieldSchemaValidity={getFieldSchemaValidity}
onToggle={() => toggleSource(mapping.sourceBlock)}
onSelectSource={() => handleSelectSource(mapping.sourceBlock)}
onSelectField={handleSelectField}
@ -913,6 +998,7 @@ export const MappingExplorer: React.FC<MappingExplorerProps> = ({ language = 'en
field={selectedField}
sourceBlock={selectedSource}
viewMode={viewMode}
schemaValidity={getFieldSchemaValidity(selectedField)}
translations={{
copyPath: t('copyPath'),
copyRdf: t('copyRdf'),

View file

@ -508,7 +508,7 @@ Example: NL-NH-AMS-M-RM (Rijksmuseum, Amsterdam, Netherlands)
GHCIDs are deterministically generated and hashed to multiple UUID formats
for different use cases (UUID v5 for primary, UUID v8 for future-proofing).
`.trim(),
linkmlClass: 'GHCID',
linkmlClass: 'GHCIdentifier',
typedbEntity: 'ghcid',
provenance: {
sourceType: 'computed',
@ -518,8 +518,8 @@ for different use cases (UUID v5 for primary, UUID v8 for future-proofing).
{
sourcePath: 'ghcid.ghcid_current',
sourceDescription: 'Current GHCID string',
targetClass: 'GHCID',
targetSlot: 'identified_by',
targetClass: 'GHCIdentifier',
targetSlot: 'has_value',
transformation: 'direct',
typedbEntity: 'ghcid',
typedbAttribute: 'ghcid-string',
@ -534,8 +534,8 @@ for different use cases (UUID v5 for primary, UUID v8 for future-proofing).
{
sourcePath: 'ghcid.ghcid_uuid',
sourceDescription: 'UUID v5 derived from GHCID string',
targetClass: 'GHCID',
targetSlot: 'identified_by',
targetClass: 'GHCIdentifier',
targetSlot: 'has_value',
transformation: 'computed',
transformationDetails: 'UUID v5 generated using SHA-1 hash of GHCID string with heritage namespace',
typedbEntity: 'ghcid',
@ -550,8 +550,8 @@ for different use cases (UUID v5 for primary, UUID v8 for future-proofing).
{
sourcePath: 'ghcid.ghcid_numeric',
sourceDescription: '64-bit numeric ID for database optimization',
targetClass: 'GHCID',
targetSlot: 'identified_by',
targetClass: 'GHCIdentifier',
targetSlot: 'has_value',
transformation: 'computed',
transformationDetails: 'SHA-256 hash truncated to 64-bit integer',
typedbEntity: 'ghcid',
@ -561,8 +561,8 @@ for different use cases (UUID v5 for primary, UUID v8 for future-proofing).
{
sourcePath: 'ghcid.location_resolution',
sourceDescription: 'GeoNames resolution metadata',
targetClass: 'GHCID',
targetSlot: 'has_location',
targetClass: 'LocationResolution',
targetSlot: 'based_on',
transformation: 'nested',
transformationDetails: 'Maps to LocationResolution class with GeoNames provenance',
typedbEntity: 'location-resolution',
@ -784,7 +784,7 @@ Each enrichment creates a CustodianObservation with google_maps_api provenance.
apiEndpoint: 'https://maps.googleapis.com/maps/api/place/',
updateFrequency: 'On-demand',
},
generatedClasses: ['Place', 'GeoCoordinates', 'OpeningHours'],
generatedClasses: ['Place', 'SourceCoordinates', 'OpeningHours'],
fields: [
{
sourcePath: 'google_maps_enrichment.place_id',
@ -804,7 +804,7 @@ Each enrichment creates a CustodianObservation with google_maps_api provenance.
{
sourcePath: 'google_maps_enrichment.coordinates.latitude',
sourceDescription: 'Latitude coordinate',
targetClass: 'GeoCoordinates',
targetClass: 'SourceCoordinates',
targetSlot: 'has_latitude',
transformation: 'nested',
typedbEntity: 'geo-coordinates',
@ -820,7 +820,7 @@ Each enrichment creates a CustodianObservation with google_maps_api provenance.
{
sourcePath: 'google_maps_enrichment.coordinates.longitude',
sourceDescription: 'Longitude coordinate',
targetClass: 'GeoCoordinates',
targetClass: 'SourceCoordinates',
targetSlot: 'has_longitude',
transformation: 'nested',
typedbEntity: 'geo-coordinates',
@ -964,7 +964,7 @@ Creates a CustodianObservation with wikidata_api provenance.
dataTier: 'TIER_3_CROWD_SOURCED',
apiEndpoint: 'https://www.wikidata.org/wiki/Special:EntityData/',
},
generatedClasses: ['WikidataEntity', 'Sitelink'],
generatedClasses: ['WikidataEntity', 'WikidataSitelinks'],
fields: [
{
sourcePath: 'wikidata_enrichment.entity_id',
@ -1006,7 +1006,7 @@ Creates a CustodianObservation with wikidata_api provenance.
{
sourcePath: 'wikidata_enrichment.sitelinks',
sourceDescription: 'Links to Wikipedia articles',
targetClass: 'Sitelink',
targetClass: 'WikidataSitelinks',
targetSlot: 'has_url',
transformation: 'array_map',
transformationDetails: 'Each sitelink maps to Wikipedia article URL',
@ -1033,7 +1033,7 @@ Creates a CustodianObservation with wikidata_api provenance.
{
sourcePath: 'wikidata_enrichment.coordinates',
sourceDescription: 'Geographic coordinates from Wikidata (P625)',
targetClass: 'GeoCoordinates',
targetClass: 'WikidataCoordinates',
targetSlot: 'has_coordinates',
transformation: 'nested',
typedbEntity: 'geo-coordinates',
@ -1104,7 +1104,7 @@ This is the single source of truth for the custodian's physical location.
sourcePath: 'location.city',
sourceDescription: 'City name',
targetClass: 'Place',
targetSlot: 'cover_place',
targetSlot: 'has_locality',
transformation: 'direct',
typedbEntity: 'place',
typedbAttribute: 'city',
@ -1119,7 +1119,7 @@ This is the single source of truth for the custodian's physical location.
sourcePath: 'location.country',
sourceDescription: 'ISO 3166-1 alpha-2 country code',
targetClass: 'Place',
targetSlot: 'cover_country',
targetSlot: 'in_country',
transformation: 'lookup',
transformationDetails: 'Maps to CountryCodeEnum',
typedbEntity: 'place',
@ -1139,7 +1139,7 @@ This is the single source of truth for the custodian's physical location.
sourcePath: 'location.region',
sourceDescription: 'Region/province name',
targetClass: 'Place',
targetSlot: 'region',
targetSlot: 'has_geographic_subdivision',
transformation: 'direct',
typedbEntity: 'place',
typedbAttribute: 'region',
@ -1214,7 +1214,7 @@ All claims must have XPath provenance per Rule 6.
sourcePath: 'web_enrichment.source_url',
sourceDescription: 'URL of scraped page',
targetClass: 'WebObservation',
targetSlot: 'source_url',
targetSlot: 'retrieved_from',
transformation: 'direct',
typedbEntity: 'web-observation',
typedbAttribute: 'source-url',
@ -1226,7 +1226,7 @@ All claims must have XPath provenance per Rule 6.
sourcePath: 'web_enrichment.retrieved_on',
sourceDescription: 'Timestamp when page was archived',
targetClass: 'WebObservation',
targetSlot: 'retrieved_on',
targetSlot: 'retrieved_at',
transformation: 'temporal',
typedbEntity: 'web-observation',
typedbAttribute: 'retrieved-on',
@ -1326,7 +1326,7 @@ Claims without XPath provenance are fabricated and must be removed per Rule 6.
sourcePath: 'web_claims[].source_url',
sourceDescription: 'URL where claim was extracted',
targetClass: 'WebClaim',
targetSlot: 'source_url',
targetSlot: 'retrieved_from',
transformation: 'direct',
typedbEntity: 'web-claim',
typedbAttribute: 'source-url',
@ -2222,7 +2222,7 @@ Classes: RegionalArchive, ProvincialArchive, ProvincialHistoricalArchive,
sourcePath: 'region',
sourceDescription: 'Geographic region served',
targetClass: 'RegionalArchive',
targetSlot: 'region',
targetSlot: 'cover_geographic_subdivision',
transformation: 'direct',
typedbEntity: 'regional-archive',
typedbAttribute: 'region',
@ -3500,7 +3500,7 @@ Supports:
sourcePath: 'person.experience[].role',
sourceDescription: 'Job title/role',
targetClass: 'WorkExperience',
targetSlot: 'role_title',
targetSlot: 'has_position',
transformation: 'direct',
typedbEntity: 'work-experience',
typedbAttribute: 'role-title',
@ -3510,8 +3510,8 @@ Supports:
{
sourcePath: 'person.education[].institution',
sourceDescription: 'Educational institution',
targetClass: 'EducationCredential',
targetSlot: 'affiliated_with',
targetClass: 'Education',
targetSlot: 'has_label',
transformation: 'direct',
typedbEntity: 'education-credential',
typedbAttribute: 'institution',
@ -3520,7 +3520,7 @@ Supports:
},
],
generatedClasses: [
'WorkExperience', 'EducationCredential', 'StaffRole', 'StaffRoles',
'WorkExperience', 'Education', 'StaffRole', 'StaffRoles',
],
exampleYaml: `
# Work and education
@ -3645,7 +3645,7 @@ Includes:
{
sourcePath: 'api_endpoints.iiif',
sourceDescription: 'IIIF Image API',
targetClass: 'IIPImageServer',
targetClass: 'IIIF',
targetSlot: 'has_url',
transformation: 'direct',
typedbEntity: 'iip-image-server',
@ -3656,7 +3656,7 @@ Includes:
],
generatedClasses: [
'DataServiceEndpoint', 'OAIPMHEndpoint', 'SearchAPI', 'FileAPI', 'EADDownload',
'METSAPI', 'IIPImageServer', 'InternetOfThings',
'METSAPI', 'IIIF', 'InternetOfThings',
],
exampleYaml: `
# API endpoints
@ -4208,7 +4208,7 @@ Includes:
sourcePath: 'funding.agendas',
sourceDescription: 'Funding agendas',
targetClass: 'FundingAgenda',
targetSlot: 'related_agenda',
targetSlot: 'has_detail',
transformation: 'array_direct',
typedbEntity: 'funding-agenda',
typedbAttribute: 'agenda-name',
@ -4797,7 +4797,7 @@ location, and organizational metadata like company size and industry.
This data is crucial for understanding a person's professional
trajectory and their experience in heritage-related roles.
`.trim(),
linkmlClass: 'CareerPosition',
linkmlClass: 'WorkExperience',
typedbEntity: 'career-position',
provenance: {
sourceType: 'external_api',
@ -4807,8 +4807,8 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].organization',
sourceDescription: 'Employer organization name',
targetClass: 'CareerPosition',
targetSlot: 'affiliated_with',
targetClass: 'WorkExperience',
targetSlot: 'employed_by',
typedbAttribute: 'organization-name',
rdfPredicate: 'schema:worksFor',
transformation: 'direct',
@ -4818,7 +4818,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].organization_linkedin',
sourceDescription: 'LinkedIn URL for organization',
targetClass: 'CareerPosition',
targetClass: 'Employer',
targetSlot: 'has_url',
typedbAttribute: 'organization-linkedin-url',
rdfPredicate: 'schema:sameAs',
@ -4829,8 +4829,8 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].role',
sourceDescription: 'Job title/role',
targetClass: 'CareerPosition',
targetSlot: 'has_role',
targetClass: 'WorkExperience',
targetSlot: 'has_position',
typedbAttribute: 'role-title',
rdfPredicate: 'schema:jobTitle',
transformation: 'direct',
@ -4840,8 +4840,8 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].role_english',
sourceDescription: 'English translation of role',
targetClass: 'CareerPosition',
targetSlot: 'has_role',
targetClass: 'WorkExperience',
targetSlot: 'has_position',
typedbAttribute: 'role-title-english',
rdfPredicate: 'schema:jobTitle',
transformation: 'direct',
@ -4851,7 +4851,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].dates',
sourceDescription: 'Employment date range',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'temporal_extent',
typedbAttribute: 'date-range',
rdfPredicate: 'schema:temporalCoverage',
@ -4862,7 +4862,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].duration',
sourceDescription: 'Employment duration',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'temporal_extent',
typedbAttribute: 'duration',
rdfPredicate: 'schema:duration',
@ -4873,7 +4873,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].location',
sourceDescription: 'Work location',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'has_location',
typedbAttribute: 'work-location',
rdfPredicate: 'schema:workLocation',
@ -4884,7 +4884,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].current',
sourceDescription: 'Is current position',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'current',
typedbAttribute: 'is-current',
rdfPredicate: 'schema:currentPosition',
@ -4895,7 +4895,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].company_size',
sourceDescription: 'Company employee count range',
targetClass: 'CareerPosition',
targetClass: 'Employer',
targetSlot: 'has_detail',
typedbAttribute: 'company-size',
rdfPredicate: 'schema:numberOfEmployees',
@ -4906,7 +4906,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].company_founded',
sourceDescription: 'Year company was founded',
targetClass: 'CareerPosition',
targetClass: 'Employer',
targetSlot: 'founded_through',
typedbAttribute: 'company-founded-year',
rdfPredicate: 'schema:foundingDate',
@ -4917,7 +4917,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].company_type',
sourceDescription: 'Type of company',
targetClass: 'CareerPosition',
targetClass: 'Employer',
targetSlot: 'has_type',
typedbAttribute: 'company-type',
rdfPredicate: 'schema:additionalType',
@ -4928,7 +4928,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].industry',
sourceDescription: 'Industry sector',
targetClass: 'CareerPosition',
targetClass: 'Employer',
targetSlot: 'has_domain',
typedbAttribute: 'industry',
rdfPredicate: 'schema:industry',
@ -4939,7 +4939,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].department',
sourceDescription: 'Department within organization',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'department_of',
typedbAttribute: 'department',
rdfPredicate: 'schema:department',
@ -4950,7 +4950,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].level',
sourceDescription: 'Seniority level',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'has_level',
typedbAttribute: 'seniority-level',
rdfPredicate: 'schema:occupationalCategory',
@ -4961,7 +4961,7 @@ trajectory and their experience in heritage-related roles.
{
sourcePath: 'profile_data.career_history[].description',
sourceDescription: 'Role description',
targetClass: 'CareerPosition',
targetClass: 'WorkExperience',
targetSlot: 'has_description',
typedbAttribute: 'role-description',
rdfPredicate: 'schema:description',
@ -5175,7 +5175,7 @@ current institution, sector role, and years of heritage experience.
This provides a quick overview of where the person fits
within the heritage ecosystem.
`.trim(),
linkmlClass: 'HeritageRelevance',
linkmlClass: 'HeritageRelevanceAssessment',
typedbEntity: 'heritage-relevance',
provenance: {
sourceType: 'computed',
@ -5185,7 +5185,7 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.heritage_type',
sourceDescription: 'Heritage type code',
targetClass: 'HeritageRelevance',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_type',
typedbAttribute: 'heritage-type-code',
rdfPredicate: 'glam:heritageType',
@ -5196,8 +5196,8 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.heritage_type_label',
sourceDescription: 'Heritage type label',
targetClass: 'HeritageRelevance',
targetSlot: 'has_label',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_note',
typedbAttribute: 'heritage-type-label',
rdfPredicate: 'rdfs:label',
transformation: 'direct',
@ -5207,8 +5207,8 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.current_institution',
sourceDescription: 'Current heritage institution',
targetClass: 'HeritageRelevance',
targetSlot: 'affiliated_with',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_note',
typedbAttribute: 'current-institution',
rdfPredicate: 'schema:worksFor',
transformation: 'direct',
@ -5218,7 +5218,7 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.institution_type',
sourceDescription: 'Type of institution',
targetClass: 'HeritageRelevance',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_type',
typedbAttribute: 'institution-type',
rdfPredicate: 'schema:additionalType',
@ -5229,8 +5229,8 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.sector_role',
sourceDescription: 'Role within heritage sector',
targetClass: 'HeritageRelevance',
targetSlot: 'has_role',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_note',
typedbAttribute: 'sector-role',
rdfPredicate: 'schema:jobTitle',
transformation: 'direct',
@ -5240,8 +5240,8 @@ within the heritage ecosystem.
{
sourcePath: 'heritage_sector_relevance.years_in_heritage',
sourceDescription: 'Years of heritage experience',
targetClass: 'HeritageRelevance',
targetSlot: 'has_detail',
targetClass: 'HeritageRelevanceAssessment',
targetSlot: 'has_score',
typedbAttribute: 'years-in-heritage',
rdfPredicate: 'schema:experienceYears',
transformation: 'direct',
@ -5273,7 +5273,7 @@ Extracted from full career history with relevance annotations.
Includes both current and past positions at heritage institutions
with notes explaining their relevance to the GLAM sector.
`.trim(),
linkmlClass: 'HeritageExperience',
linkmlClass: 'WorkExperience',
typedbEntity: 'heritage-experience',
provenance: {
sourceType: 'computed',
@ -5283,8 +5283,8 @@ with notes explaining their relevance to the GLAM sector.
{
sourcePath: 'profile_data.heritage_relevant_experience[].organization',
sourceDescription: 'Heritage organization name',
targetClass: 'HeritageExperience',
targetSlot: 'affiliated_with',
targetClass: 'WorkExperience',
targetSlot: 'employed_by',
typedbAttribute: 'heritage-org-name',
rdfPredicate: 'schema:worksFor',
transformation: 'direct',
@ -5294,8 +5294,8 @@ with notes explaining their relevance to the GLAM sector.
{
sourcePath: 'profile_data.heritage_relevant_experience[].role',
sourceDescription: 'Role at heritage organization',
targetClass: 'HeritageExperience',
targetSlot: 'has_role',
targetClass: 'WorkExperience',
targetSlot: 'has_position',
typedbAttribute: 'heritage-role',
rdfPredicate: 'schema:jobTitle',
transformation: 'direct',
@ -5305,8 +5305,8 @@ with notes explaining their relevance to the GLAM sector.
{
sourcePath: 'profile_data.heritage_relevant_experience[].relevance',
sourceDescription: 'Relevance explanation',
targetClass: 'HeritageExperience',
targetSlot: 'has_significance',
targetClass: 'WorkExperience',
targetSlot: 'has_note',
typedbAttribute: 'relevance-notes',
rdfPredicate: 'schema:description',
transformation: 'direct',
@ -5316,7 +5316,7 @@ with notes explaining their relevance to the GLAM sector.
{
sourcePath: 'profile_data.heritage_relevant_experience[].current',
sourceDescription: 'Is current position',
targetClass: 'HeritageExperience',
targetClass: 'WorkExperience',
targetSlot: 'current',
typedbAttribute: 'is-current-heritage',
rdfPredicate: 'schema:currentPosition',
@ -5325,7 +5325,7 @@ with notes explaining their relevance to the GLAM sector.
notes: 'Whether this is a current position',
},
],
generatedClasses: ['HeritageExperience'],
generatedClasses: ['WorkExperience'],
exampleYaml: `
profile_data:
heritage_relevant_experience:
@ -5354,7 +5354,7 @@ role title, and heritage classification.
These affiliations enable network analysis across the heritage
sector workforce.
`.trim(),
linkmlClass: 'Affiliation',
linkmlClass: 'Membership',
typedbEntity: 'affiliation',
provenance: {
sourceType: 'computed',
@ -5364,7 +5364,7 @@ sector workforce.
{
sourcePath: 'affiliations[].custodian_name',
sourceDescription: 'Heritage custodian name',
targetClass: 'Affiliation',
targetClass: 'Membership',
targetSlot: 'has_name',
typedbAttribute: 'custodian-name',
rdfPredicate: 'schema:memberOf',
@ -5375,8 +5375,8 @@ sector workforce.
{
sourcePath: 'affiliations[].custodian_slug',
sourceDescription: 'Custodian identifier slug',
targetClass: 'Affiliation',
targetSlot: 'identified_by',
targetClass: 'Membership',
targetSlot: 'has_slug',
typedbAttribute: 'custodian-slug',
rdfPredicate: 'schema:identifier',
transformation: 'direct',
@ -5386,8 +5386,8 @@ sector workforce.
{
sourcePath: 'affiliations[].role_title',
sourceDescription: 'Role at custodian',
targetClass: 'Affiliation',
targetSlot: 'role_title',
targetClass: 'Membership',
targetSlot: 'has_role',
typedbAttribute: 'affiliation-role',
rdfPredicate: 'schema:jobTitle',
transformation: 'direct',
@ -5397,8 +5397,8 @@ sector workforce.
{
sourcePath: 'affiliations[].heritage_relevant',
sourceDescription: 'Is heritage relevant',
targetClass: 'Affiliation',
targetSlot: 'has_significance',
targetClass: 'Membership',
targetSlot: 'has_note',
typedbAttribute: 'is-heritage-relevant',
rdfPredicate: 'glam:heritageRelevant',
transformation: 'direct',
@ -5408,7 +5408,7 @@ sector workforce.
{
sourcePath: 'affiliations[].heritage_type',
sourceDescription: 'Heritage type code',
targetClass: 'Affiliation',
targetClass: 'Membership',
targetSlot: 'has_type',
typedbAttribute: 'affiliation-heritage-type',
rdfPredicate: 'glam:heritageType',
@ -5419,7 +5419,7 @@ sector workforce.
{
sourcePath: 'affiliations[].current',
sourceDescription: 'Is current affiliation',
targetClass: 'Affiliation',
targetClass: 'Membership',
targetSlot: 'current',
typedbAttribute: 'is-current-affiliation',
rdfPredicate: 'schema:currentPosition',
@ -5430,7 +5430,7 @@ sector workforce.
{
sourcePath: 'affiliations[].observed_on',
sourceDescription: 'Observation timestamp',
targetClass: 'Affiliation',
targetClass: 'Membership',
targetSlot: 'observed_in',
typedbAttribute: 'observed-on',
rdfPredicate: 'prov:generatedAtTime',
@ -5441,8 +5441,8 @@ sector workforce.
{
sourcePath: 'affiliations[].source_url',
sourceDescription: 'Source URL for affiliation',
targetClass: 'Affiliation',
targetSlot: 'source_url',
targetClass: 'Membership',
targetSlot: 'retrieved_from',
typedbAttribute: 'affiliation-source-url',
rdfPredicate: 'prov:wasDerivedFrom',
transformation: 'direct',
@ -5450,7 +5450,7 @@ sector workforce.
notes: 'URL where affiliation was discovered',
},
],
generatedClasses: ['Affiliation'],
generatedClasses: ['Membership'],
exampleYaml: `
affiliations:
- custodian_name: Nationaal Archief
@ -5478,7 +5478,7 @@ and custodian records (heritage institution YAML files).
These links enable navigation between person profiles and
the institutions they work for.
`.trim(),
linkmlClass: 'LinkedRecords',
linkmlClass: 'FileLocation',
typedbEntity: 'linked-records',
provenance: {
sourceType: 'computed',
@ -5488,8 +5488,8 @@ the institutions they work for.
{
sourcePath: 'linked_records.staff_record.file',
sourceDescription: 'Staff record file path',
targetClass: 'LinkedRecords',
targetSlot: 'has_file_location',
targetClass: 'FileLocation',
targetSlot: 'has_value',
typedbAttribute: 'staff-record-path',
rdfPredicate: 'prov:wasDerivedFrom',
transformation: 'direct',
@ -5499,8 +5499,8 @@ the institutions they work for.
{
sourcePath: 'linked_records.staff_record.staff_id',
sourceDescription: 'Staff record ID',
targetClass: 'LinkedRecords',
targetSlot: 'staff_id',
targetClass: 'Identifier',
targetSlot: 'has_value',
typedbAttribute: 'staff-id',
rdfPredicate: 'schema:identifier',
transformation: 'direct',
@ -5510,8 +5510,8 @@ the institutions they work for.
{
sourcePath: 'linked_records.custodian_record.ghcid',
sourceDescription: 'Custodian GHCID',
targetClass: 'LinkedRecords',
targetSlot: 'identified_by',
targetClass: 'GHCIdentifier',
targetSlot: 'has_value',
typedbAttribute: 'linked-ghcid',
rdfPredicate: 'glam:ghcid',
transformation: 'direct',
@ -5521,7 +5521,7 @@ the institutions they work for.
{
sourcePath: 'linked_records.custodian_record.notes',
sourceDescription: 'Custodian record notes',
targetClass: 'LinkedRecords',
targetClass: 'FileLocation',
targetSlot: 'has_note',
typedbAttribute: 'custodian-notes',
rdfPredicate: 'schema:description',
@ -5530,7 +5530,7 @@ the institutions they work for.
notes: 'Additional notes about the custodian link',
},
],
generatedClasses: ['LinkedRecords'],
generatedClasses: ['FileLocation'],
exampleYaml: `
linked_records:
staff_record:
@ -5555,7 +5555,7 @@ with confidence scores indicating reliability.
Also includes profile photo URLs and external lookup service links.
`.trim(),
linkmlClass: 'ContactData',
linkmlClass: 'ContactDetails',
typedbEntity: 'contact-data',
provenance: {
sourceType: 'computed',
@ -5565,7 +5565,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.provenance.source',
sourceDescription: 'Contact data source',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_source',
typedbAttribute: 'contact-source',
rdfPredicate: 'prov:wasAttributedTo',
@ -5576,7 +5576,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.emails[].email',
sourceDescription: 'Email address',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_email_address',
typedbAttribute: 'email-address',
rdfPredicate: 'schema:email',
@ -5587,7 +5587,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.emails[].type',
sourceDescription: 'Email type',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_type',
typedbAttribute: 'email-type',
rdfPredicate: 'schema:contactType',
@ -5598,7 +5598,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.emails[].confidence',
sourceDescription: 'Email confidence score',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_confidence_measure',
typedbAttribute: 'email-confidence',
rdfPredicate: 'prov:confidence',
@ -5609,7 +5609,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.emails[].verified',
sourceDescription: 'Email verification status',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_detail',
typedbAttribute: 'email-verified',
rdfPredicate: 'schema:verified',
@ -5620,7 +5620,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.profile_photo_url',
sourceDescription: 'Profile photo URL',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_image',
typedbAttribute: 'profile-photo',
rdfPredicate: 'schema:image',
@ -5631,7 +5631,7 @@ Also includes profile photo URLs and external lookup service links.
{
sourcePath: 'contact_data.rocketreach_url',
sourceDescription: 'RocketReach lookup URL',
targetClass: 'ContactData',
targetClass: 'ContactDetails',
targetSlot: 'has_url',
typedbAttribute: 'rocketreach-url',
rdfPredicate: 'schema:sameAs',
@ -5640,7 +5640,7 @@ Also includes profile photo URLs and external lookup service links.
notes: 'Link to RocketReach profile lookup',
},
],
generatedClasses: ['ContactData'],
generatedClasses: ['ContactDetails'],
exampleYaml: `
contact_data:
provenance:
@ -5685,7 +5685,7 @@ reproducibility of the extraction process.
sourcePath: 'extraction_metadata.source_file',
sourceDescription: 'Source file path',
targetClass: 'ExtractionMetadata',
targetSlot: 'source_file',
targetSlot: 'has_file_location',
typedbAttribute: 'source-file-path',
rdfPredicate: 'prov:wasDerivedFrom',
transformation: 'direct',
@ -5696,7 +5696,7 @@ reproducibility of the extraction process.
sourcePath: 'extraction_metadata.staff_id',
sourceDescription: 'Staff identifier',
targetClass: 'ExtractionMetadata',
targetSlot: 'staff_id',
targetSlot: 'has_slug',
typedbAttribute: 'extraction-staff-id',
rdfPredicate: 'schema:identifier',
transformation: 'direct',
@ -5762,7 +5762,7 @@ reproducibility of the extraction process.
sourcePath: 'extraction_metadata.request_id',
sourceDescription: 'API request identifier',
targetClass: 'ExtractionMetadata',
targetSlot: 'request_id',
targetSlot: 'has_code',
typedbAttribute: 'api-request-id',
rdfPredicate: 'schema:identifier',
transformation: 'direct',
@ -5830,7 +5830,7 @@ This follows the WebObservation pattern for verifiable data claims.
sourcePath: 'web_claims[].source_url',
sourceDescription: 'URL source of claim',
targetClass: 'WebClaim',
targetSlot: 'source_url',
targetSlot: 'retrieved_from',
typedbAttribute: 'claim-source-url',
rdfPredicate: 'prov:wasDerivedFrom',
transformation: 'direct',
@ -5841,7 +5841,7 @@ This follows the WebObservation pattern for verifiable data claims.
sourcePath: 'web_claims[].retrieved_on',
sourceDescription: 'Retrieval timestamp',
targetClass: 'WebClaim',
targetSlot: 'retrieved_on',
targetSlot: 'retrieved_at',
typedbAttribute: 'claim-retrieved-on',
rdfPredicate: 'prov:generatedAtTime',
transformation: 'direct',
@ -5852,7 +5852,7 @@ This follows the WebObservation pattern for verifiable data claims.
sourcePath: 'web_claims[].retrieval_agent',
sourceDescription: 'Agent that retrieved claim',
targetClass: 'WebClaim',
targetSlot: 'retrieval_agent',
targetSlot: 'retrieved_by',
typedbAttribute: 'claim-retrieval-agent',
rdfPredicate: 'prov:wasAttributedTo',
transformation: 'direct',
@ -5941,7 +5941,7 @@ showing the semantic alignment between the ontologies.
sourcePath: 'extraction_metadata.linkedin_url',
sourceDescription: 'LinkedIn profile URL as primary source',
targetClass: 'PersonObservation',
targetSlot: 'source_url',
targetSlot: 'retrieved_from',
typedbAttribute: 'source-url',
rdfPredicate: 'prov:hadPrimarySource',
transformation: 'direct',
@ -5952,8 +5952,8 @@ showing the semantic alignment between the ontologies.
{
sourcePath: 'affiliations[].role_title',
sourceDescription: 'Role at heritage institution',
targetClass: 'Affiliation',
targetSlot: 'role_title',
targetClass: 'Membership',
targetSlot: 'has_role',
typedbAttribute: 'role-title',
rdfPredicate: 'pico:hasRole',
transformation: 'direct',

View file

@ -7,6 +7,7 @@ prefixes:
schema: http://schema.org/
crm: http://www.cidoc-crm.org/cidoc-crm/
gn: http://www.geonames.org/ontology#
rdac: http://rdaregistry.info/Elements/c/
wdt: http://www.wikidata.org/prop/direct/
dcterms: http://purl.org/dc/terms/
prov: http://www.w3.org/ns/prov#
@ -58,6 +59,7 @@ classes:
close_mappings:
- crm:E53_Place
- gn:Feature
- rdac:C10009 # RDA Registry: Place (RDA Classes)
slots:
- has_label
- has_name

View file

@ -1,5 +1,5 @@
{
"generated": "2026-02-18T10:16:43.406Z",
"generated": "2026-02-18T17:44:03.891Z",
"schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2183,
"categoryCounts": {

View file

@ -12,6 +12,7 @@ prefixes:
gbif: http://rs.gbif.org/terms/
aat: http://vocab.getty.edu/aat/
imports:
- ./ExhibitedObject
- linkml:types
- ../enums/PreservationMethodEnum
- ../metadata
@ -47,6 +48,7 @@ imports:
default_prefix: hc
classes:
BiologicalObject:
is_a: ExhibitedObject
class_uri: crm:E20_Biological_Object
description: >-
Natural specimen or organism-derived item held in a heritage collection, with associated taxonomic identification and preservation metadata.
@ -397,29 +399,64 @@ classes:
in_language: zh
- literal_form: value: https://nde.nl/ontology/hc/taxon/raphus-cucullatus
broad_mappings:
- crm:E20_Biological_Object
close_mappings:
- dwc:Occurrence
- gbif:Specimen
related_mappings:
- crm:E19_Physical_Object
- crm:E22_Human-Made_Object
- schema:Taxon
predicate: EXACT_SYNONYM
slots:
- identified_by
- has_label
- has_name
- has_rank
- has_authority
- commented_on
- identified_through
- has_specimen
- symbolize
- has_status
- has_gender
- has_stage
- contain
- has_quantity
- has_method
- has_detail
- prepared_on
- prepared_by
- acquired_through
- in_place
- describe
- acquired_by
- has_habitat
- has_hypernym
- listed_in
- has_provenance
- has_type
in_language: zh
# range: string # uriorcurie
slot_usage:
identified_by:
multivalued: true
inlined: false # Fixed invalid inline for primitive type
inlined_as_list: false # Fixed invalid inline for primitive type
required: false
any_of:
- range: FieldNumber
- range: BOLDIdentifier
- range: WikiDataIdentifier
- range: string # uriorcurie
- range: FieldNumber
- range: BOLDIdentifier
- range: WikiDataIdentifier
- range: string # uriorcurie
examples:
- value:
has_type: FieldNumber
- value:
id: https://nde.nl/ontology/hc/bold-id/NLNAT001-21
has_type: BOLDIdentifier
- value:
has_type: WikiDataIdentifier
- value:
has_type: FieldNumber
- value:
id: https://nde.nl/ontology/hc/bold-id/NLNAT001-21
has_type: BOLDIdentifier
- value:
has_type: WikiDataIdentifier
has_label:
range: TaxonName
inlined: true

View file

@ -36,195 +36,81 @@ classes:
zh: >-
描述平台界面、操作程序或用户实施指南的技术参考资料。
structured_aliases:
- literal_form: documentatie
predicate: EXACT_SYNONYM
in_language: nl
- literal_form: technische handleiding
predicate: EXACT_SYNONYM
in_language: nl
- literal_form: Dokumentation
predicate: EXACT_SYNONYM
in_language: de
- literal_form: technische Anleitung
predicate: EXACT_SYNONYM
in_language: de
- literal_form: documentation
predicate: EXACT_SYNONYM
in_language: fr
- literal_form: guide technique
predicate: EXACT_SYNONYM
in_language: fr
- literal_form: documentación
predicate: EXACT_SYNONYM
in_language: es
- literal_form: guía técnica
predicate: EXACT_SYNONYM
in_language: es
- literal_form: التوثيق
predicate: EXACT_SYNONYM
in_language: ar
- literal_form: الدليل الفني
predicate: EXACT_SYNONYM
in_language: ar
- literal_form: dokumentasi
predicate: EXACT_SYNONYM
in_language: id
- literal_form: panduan teknis
predicate: EXACT_SYNONYM
in_language: id
- literal_form: 文档
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: 技术指南
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: documentation
broad_mappings:
- schema:TechArticle
- foaf:Document
close_mappings:
- crm:E73_Information_Object
predicate: EXACT_SYNONYM
slots:
- identified_by
- has_label
- has_description
- temporal_extent
in_language: zh
- literal_form: technical guide
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: API reference
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: user manual
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: operational guide
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: schema:CreativeWork
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: foaf:Document
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: schema:TechArticle
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: crm:E73_Information_Object
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: dcterms:references
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_description
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: identified_by
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: temporal_extent
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: https://data.rijksmuseum.nl/object-metadata/api/
predicate: EXACT_SYNONYM
in_language: zh
# range: string
slot_usage:
identified_by:
range: uri
required: true
examples:
- value: API Reference Documentation
- value: Developer Integration Guide
- value: https://data.rijksmuseum.nl/object-metadata/api/
has_label:
required: true
examples:
- value: Rijksmuseum Collection API
has_description:
# range: string
examples:
- value: Complete REST API reference with endpoint specifications, authentication, and response formats.
temporal_extent:
range: TimeSpan
inlined: true
required: false
examples:
- value:
begin_of_the_begin: '2015-01-01'
- value: Complete REST API reference with endpoint specifications, authentication, and response formats.
temporal_extent:
required: false
examples:
- value:
begin_of_the_begin: '2015-01-01'
has_verbatim_value: 2015-
comments:
- Generic documentation class replacing domain-specific documentation slots
- Supports multiple documentation types (API, user, developer, system)

View file

@ -186,19 +186,19 @@ classes:
in_language: zh
- literal_form: value: |
- literal_form: "value: |"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: expense_type: ADMINISTRATIVE
- literal_form: "expense_type: ADMINISTRATIVE"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: expense_type: PROGRAM
- literal_form: "expense_type: PROGRAM"
predicate: EXACT_SYNONYM

View file

@ -185,7 +185,7 @@ classes:
in_language: zh
- literal_form: value: |
- literal_form: "value: |"
predicate: EXACT_SYNONYM
@ -197,37 +197,37 @@ classes:
in_language: zh
- literal_form: has_label: Manufacturer name (String or Label)
- literal_form: "has_label: Manufacturer name (String or Label)"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_url: Manufacturer website (URL)
- literal_form: "has_url: Manufacturer website (URL)"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: identified_by: Unique identifier
- literal_form: "identified_by: Unique identifier"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Primary**: `schema:Organization` - Schema.org organization
- literal_form: "**Primary**: `schema:Organization` - Schema.org organization"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Close**: `org:Organization` - W3C ORG organization
- literal_form: "**Close**: `org:Organization` - W3C ORG organization"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Related**: `prov:Agent` - PROV-O agent responsible for production
- literal_form: "**Related**: `prov:Agent` - PROV-O agent responsible for production"
predicate: EXACT_SYNONYM

View file

@ -174,13 +174,13 @@ classes:
in_language: zh
- literal_form: value: PRIMARY
- literal_form: "value: PRIMARY"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: CO_ORGANIZER
- literal_form: "value: CO_ORGANIZER"
predicate: EXACT_SYNONYM
@ -192,43 +192,43 @@ classes:
in_language: zh
- literal_form: PRIMARY: Main organizing institution
- literal_form: "PRIMARY: Main organizing institution"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: CO_ORGANIZER: Partner institution with significant organizational role
- literal_form: "CO_ORGANIZER: Partner institution with significant organizational role"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: SPONSOR_ORGANIZER: Sponsor with curatorial/organizational input
- literal_form: "SPONSOR_ORGANIZER: Sponsor with curatorial/organizational input"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: LENDING_INSTITUTION: Institution lending objects with exhibition involvement
- literal_form: "LENDING_INSTITUTION: Institution lending objects with exhibition involvement"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: HOST_VENUE: Venue hosting a traveling exhibition
- literal_form: "HOST_VENUE: Venue hosting a traveling exhibition"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Primary**: `schema:Role` - Schema.org role
- literal_form: "**Primary**: `schema:Role` - Schema.org role"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: **Close**: `prov:Role` - PROV-O role in activity
- literal_form: "**Close**: `prov:Role` - PROV-O role in activity"
predicate: EXACT_SYNONYM

View file

@ -380,163 +380,163 @@ classes:
in_language: zh
- literal_form: value: https://nde.nl/ontology/hc/aux/kroller-muller-sculpture
- literal_form: "value: https://nde.nl/ontology/hc/aux/kroller-muller-sculpture"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Kroller-Muller Beeldentuin
- literal_form: "value: Kroller-Muller Beeldentuin"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Paleis Het Loo Tuinen
- literal_form: "value: Paleis Het Loo Tuinen"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: Archeologisch Park Matilo
- literal_form: "value: Archeologisch Park Matilo"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: One of Europe's largest sculpture gardens with 160 works set in 25 hectares of park landscape within De Hoge Veluwe National Park.
- literal_form: "value: One of Europe's largest sculpture gardens with 160 works set in 25 hectares of park landscape within De Hoge Veluwe National Park."
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: range: OutdoorSiteTypeEnum
- literal_form: "range: OutdoorSiteTypeEnum"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: range: FeatureType
- literal_form: "range: FeatureType"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: SCULPTURE_GARDEN
- literal_form: "value: SCULPTURE_GARDEN"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: ARCHAEOLOGICAL_SITE
- literal_form: "value: ARCHAEOLOGICAL_SITE"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: FORMAL_GARDEN
- literal_form: "value: FORMAL_GARDEN"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: BotanicalInstitutionClassification
- literal_form: "value: BotanicalInstitutionClassification"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: ZoologicalInstitutionClassification
- literal_form: "value: ZoologicalInstitutionClassification"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: 160
- literal_form: "value: 160"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: 2500
- literal_form: "value: 2500"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 25.0
- literal_form: "area_value: 25.0"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Included with museum ticket
- literal_form: "has_label: Included with museum ticket"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Paved paths
- literal_form: "has_label: Paved paths"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Wheelchair routes available
- literal_form: "has_label: Wheelchair routes available"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 650.0
- literal_form: "area_value: 650.0"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Included with palace ticket
- literal_form: "has_label: Included with palace ticket"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value:
- literal_form: "value:"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: area_value: 3.5
- literal_form: "area_value: 3.5"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: has_label: Free
- literal_form: "has_label: Free"
predicate: EXACT_SYNONYM

View file

@ -200,13 +200,13 @@ classes:
in_language: zh
- literal_form: value: hc:ArchiveOrganizationType
- literal_form: "value: hc:ArchiveOrganizationType"
predicate: EXACT_SYNONYM
in_language: zh
- literal_form: value: hc:HolySacredSiteType
- literal_form: "value: hc:HolySacredSiteType"
predicate: EXACT_SYNONYM

View file

@ -8,6 +8,7 @@ prefixes:
crm: http://www.cidoc-crm.org/cidoc-crm/
rico: https://www.ica.org/standards/RiC/ontology#
foaf: http://xmlns.com/foaf/0.1/
rdau: http://rdaregistry.info/Elements/u/
dcterms: http://purl.org/dc/terms/
prov: http://www.w3.org/ns/prov#
skos: http://www.w3.org/2004/02/skos/core#
@ -44,6 +45,7 @@ slots:
close_mappings:
- crm:P7_took_place_at
- rico:birthPlace
- rdau:P60593 # RDA Registry (Unconstrained): has place of birth
comments:
- MIGRATED from birth_place slot (Rule 53)
- Supports historical vs. modern place names

View file

@ -24,6 +24,7 @@ prefixes:
skos: http://www.w3.org/2004/02/skos/core#
owl: http://www.w3.org/2002/07/owl#
schema: http://schema.org/
la: https://linked.art/ns/terms/
imports:
- linkml:types
default_prefix: hc
@ -87,6 +88,7 @@ slots:
close_mappings:
- owl:sameAs # W3C OWL 2 standard - Identity assertion (stronger claim than semantic equivalence).
- schema:sameAs # schemaorg.owl:34129-34148 - "URL unambiguously indicating the item's identity."
- la:equivalent # Linked Art extensions: equivalent instance (skos:exactMatch-like without Concept inference)
aliases:
- is_or_was_equivalent_to
- wikidata_equivalent

View file

@ -23,6 +23,7 @@ prefixes:
schema: http://schema.org/
dcat: http://www.w3.org/ns/dcat#
dcterms: http://purl.org/dc/terms/
ardo: https://w3id.org/ardo/2.0/
imports:
- linkml:types
default_prefix: hc
@ -80,6 +81,8 @@ slots:
- dcat:keyword # dcat3.ttl:1208-1231 - "A keyword or tag describing a resource"
close_mappings:
- dcterms:subject # dcterms.rdf:1968-1988 - "A topic of the resource"
related_mappings:
- ardo:has_keyword # ArDO 2.0: links a thematic subcategory to a keyword (object property)
comments:
- "Used for discovery and classification."
annotations:

View file

@ -25,6 +25,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
org: http://www.w3.org/ns/org#
schema: http://schema.org/
la: https://linked.art/ns/terms/
xsd: http://www.w3.org/2001/XMLSchema#
default_prefix: hc
imports:
@ -80,5 +81,7 @@ slots:
exact_mappings:
- org:hasMember # org.rdf:427-446 - "Indicates a person who is a member of the subject Organization." Domain: Organization, Range: Agent. Organization membership only; this slot also covers collection elements.
- schema:member # schemaorg.owl:26055-26085 - "A member of an Organization or a ProgramMembership." Domain: Organization/ProgramMembership. Does not cover collection elements.
related_mappings:
- la:has_member # Linked Art extensions: membership (Set→Entity)
annotations:
custodian_types: '["*"]'

View file

@ -19,6 +19,7 @@ prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
dcterms: http://purl.org/dc/terms/
pca: http://rds.posccaesar.org/ontology/plm/rdl/
default_prefix: hc
imports:
- linkml:types
@ -82,3 +83,5 @@ slots:
custodian_types: '["*"]'
close_mappings:
- dcterms:conformsTo # dcterms.rdf:987-1010 - "An established standard to which the described resource conforms." Conformance relationship ≠ having a standard.
related_mappings:
- pca:PCA_100003538 # POSC Caesar RDL (PCA PLM core): Standard (class)

View file

@ -24,6 +24,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
pav: http://purl.org/pav/2.3#
default_prefix: hc
imports:
- linkml:types
@ -81,6 +82,7 @@ slots:
multivalued: false
related_mappings:
- dcterms:hasVersion # dcterms.rdf:1371-1395 — "A related resource that is a version, edition, or adaptation." Relates resources to each other, not a version identifier.
- pav:hasVersion # PAV 2.3 (used by ArDO): links a resource to a version resource
aliases:
- has_or_had_version
- api_ver

View file

@ -32,6 +32,7 @@ prefixes:
rico: https://www.ica.org/standards/RiC/ontology#
org: http://www.w3.org/ns/org#
foaf: http://xmlns.com/foaf/0.1/
la: https://linked.art/ns/terms/
default_prefix: hc
imports:
- linkml:types
@ -98,6 +99,7 @@ slots:
- rico:isOrWasMemberOf # RiC-O_1-1.rdf:14505-14550 - Person→Group (restricted domain)
related_mappings:
- foaf:member # foaf.ttl:410-417 - INVERSE: Group→Agent (not Agent→Group)
- la:member_of # Linked Art extensions: membership (Entity→Set)
annotations:
inverse_slot: has_or_had_member
deprecates: is_member_of

View file

@ -35,6 +35,7 @@ prefixes:
time: http://www.w3.org/2006/time#
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
pav: http://purl.org/pav/2.3#
default_prefix: hc
imports:
- linkml:types
@ -110,6 +111,7 @@ slots:
- schema:predecessorOf # schemaorg.owl:30406-30420 - "Previous variant of product; ProductModel domain"
- schema:previousItem # schemaorg.owl:30559-30575 - "Preceding ListItem; ListItem domain"
- dcterms:replaces # dcterms.rdf:1827-1846 - "Supplants/supersedes described resource; implies replacement"
- pav:previousVersion # PAV 2.3 (used by ArDO): previous version link (version chain)
aliases:
- previous_observation
examples:

View file

@ -24,6 +24,7 @@ prefixes:
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
odrl: http://www.w3.org/ns/odrl/2/
la: https://linked.art/ns/terms/
skos: http://www.w3.org/2004/02/skos/core#
xsd: http://www.w3.org/2001/XMLSchema#
@ -86,5 +87,6 @@ slots:
- is_or_was_transferred_to
related_mappings:
- schema:TransferAction # schemaorg.owl:5808-5812 - "act of transferring animate or inanimate objects"
- la:transferred_to # Linked Art extensions: transferred to (Transfer→Entity)
annotations:
custodian_types: '["*"]'