Update generated timestamp in manifest.json for accuracy

This commit is contained in:
kempersc 2026-02-01 02:04:00 +01:00
parent 41481f6b21
commit 01e382b53b
24 changed files with 2394 additions and 81 deletions

View file

@ -0,0 +1,31 @@
# Exact Mapping Predicate/Class Distinction Rule
🚨 **CRITICAL**: The `exact_mappings` property implies semantic equivalence. Equivalence can only exist between elements of the same ontological category.
## The Rule
1. **Slots (Predicates)** MUST ONLY have `exact_mappings` to ontology **predicates** (properties).
* ❌ INVALID: Slot `analyzes_or_analyzed` maps to `schema:object` (a Class).
* ✅ VALID: Slot `analyzes_or_analyzed` maps to `crm:P129_is_about` (a Property).
2. **Classes (Entities)** MUST ONLY have `exact_mappings` to ontology **classes** (entities).
* ❌ INVALID: Class `Person` maps to `foaf:name` (a Property).
* ✅ VALID: Class `Person` maps to `foaf:Person` (a Class).
## Rationale
Mapping a slot (which defines a relationship or attribute) to a class (which defines a type of entity) is a category error. `schema:object` represents the *class* of objects, not the *relationship* of "having an object" or "analyzing an object".
## Verification Checklist
When adding or reviewing `exact_mappings`:
- [ ] Is the LinkML element a Class or a Slot?
- [ ] Does the target ontology term represent a Class (usually Capitalized) or a Property (usually lowercase)?
- [ ] Do they match? (Class↔Class, Slot↔Property)
- [ ] If the target ontology uses opaque IDs (like CIDOC-CRM `E55_Type`), verify the type definition in the ontology file.
## Common Pitfalls to Fix
- Mapping slots to `schema:Object` or `schema:Thing`.
- Mapping slots to `skos:Concept`.
- Mapping classes to `schema:name` or `dc:title`.

View file

@ -1,29 +1,43 @@
import os
import yaml
from collections import Counter
import os
def check_duplicates(directory):
def check_dir(directory):
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(".yaml"):
filepath = os.path.join(root, file)
try:
with open(filepath, 'r') as f:
data = yaml.safe_load(f)
path = os.path.join(root, file)
if not data or 'classes' not in data:
continue
with open(path, 'r') as f:
lines = f.readlines()
for class_name, class_def in data['classes'].items():
if 'slots' in class_def and class_def['slots']:
slots = class_def['slots']
counts = Counter(slots)
duplicates = [item for item, count in counts.items() if count > 1]
if duplicates:
print(f"File: {file}, Class: {class_name}, Duplicates: {duplicates}")
# Store (indentation, key) to check for duplicates in the current block
# This is complex to implement perfectly for YAML, but we can look for
# "related_mappings:" specifically.
except Exception as e:
print(f"Error processing {file}: {e}")
related_mappings_indices = [i for i, line in enumerate(lines) if "related_mappings:" in line.strip()]
if __name__ == "__main__":
check_duplicates("/Users/kempersc/apps/glam/schemas/20251121/linkml/modules/classes")
if len(related_mappings_indices) > 1:
# Check indentation
indents = [len(lines[i]) - len(lines[i].lstrip()) for i in related_mappings_indices]
for i in range(len(related_mappings_indices) - 1):
idx1 = related_mappings_indices[i]
idx2 = related_mappings_indices[i+1]
indent1 = indents[i]
indent2 = indents[i+1]
if indent1 == indent2:
# Check if there is a line between them with LOWER indentation (parent key)
parent_found = False
for j in range(idx1 + 1, idx2):
line = lines[j]
if line.strip() and not line.strip().startswith('#'):
curr_indent = len(line) - len(line.lstrip())
if curr_indent < indent1:
parent_found = True
break
if not parent_found:
print(f"Potential duplicate related_mappings in {path} at lines {idx1+1} and {idx2+1}")
check_dir("schemas/20251121/linkml/modules/classes")

2274
data/ontology/odrl.ttl Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"generated": "2026-01-31T20:24:54.633Z",
"generated": "2026-01-31T22:55:48.691Z",
"schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2906,
"categoryCounts": {

View file

@ -1,5 +1,5 @@
{
"generated": "2026-01-31T22:55:48.691Z",
"generated": "2026-02-01T01:04:01.193Z",
"schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2906,
"categoryCounts": {

View file

@ -21,6 +21,8 @@ classes:
\ general labeling context"
class_uri: skos:altLabel
exact_mappings:
- skos:altLabel
close_mappings:
- schema:alternateName
related_mappings:
- rdfs:label

View file

@ -24,7 +24,7 @@ classes:
for hearing accessibility - Subtitles for multilingual content - Closed captions
vs. open captions distinction MIGRATED 2026-01-22: Created per slot_fixes.yaml
feedback to replace simple caption_available string with structured class.'
exact_mappings:
close_mappings:
- schema:caption
slots:
- has_or_had_label

View file

@ -53,11 +53,10 @@ classes:
- \"Art + Culture Center\" \u2192 \"ACC\" (not \"A+CC\")\n- \"Museum/Gallery Amsterdam\" \u2192 \"MGA\" (not \"M/GA\")\n- \"Heritage@Digital\" \u2192 \"HD\" (not \"H@D\")\n- \"Archives (Historical)\" \u2192 \"AH\" (not \"A(H)\")\n\nSee: rules/ABBREVIATION_SPECIAL_CHAR_RULE.md for complete documentation\n\n===========================================================================\nMANDATORY RULE: Diacritics MUST Be Normalized to ASCII in Abbreviations\n===========================================================================\n\nWhen generating abbreviations for GHCID, diacritics (accented characters)\nMUST be normalized to their ASCII base letter equivalents. Only ASCII\nuppercase letters (A-Z) are permitted in the has_or_had_abbreviation component.\n\nRATIONALE:\n1. URI/URL safety - Non-ASCII requires percent-encoding\n2. Cross-system compatibility - ASCII is universally supported\n3. Parsing consistency - No special character handling needed\n4. Human readability - Easier to type\
\ and communicate\n\nDIACRITICS TO NORMALIZE (examples by language):\n- Czech: \u010C\u2192C, \u0158\u2192R, \u0160\u2192S, \u017D\u2192Z, \u011A\u2192E, \u016E\u2192U\n- Polish: \u0141\u2192L, \u0143\u2192N, \xD3\u2192O, \u015A\u2192S, \u0179\u2192Z, \u017B\u2192Z, \u0104\u2192A, \u0118\u2192E\n- German: \xC4\u2192A, \xD6\u2192O, \xDC\u2192U, \xDF\u2192SS\n- French: \xC9\u2192E, \xC8\u2192E, \xCA\u2192E, \xC7\u2192C, \xD4\u2192O\n- Spanish: \xD1\u2192N, \xC1\u2192A, \xC9\u2192E, \xCD\u2192I, \xD3\u2192O, \xDA\u2192U\n- Nordic: \xC5\u2192A, \xC4\u2192A, \xD6\u2192O, \xD8\u2192O, \xC6\u2192AE\n\nEXAMPLES:\n- \"Vlastiv\u011Bdn\xE9 muzeum\" (Czech) \u2192 \"VM\" (not \"VM\" with h\xE1\u010Dek)\n- \"\xD6sterreichische Nationalbibliothek\" (German) \u2192 \"ON\"\n- \"Biblioth\xE8que nationale\" (French) \u2192 \"BN\"\n\nREAL-WORLD EXAMPLE:\n- \u274C WRONG: CZ-VY-TEL-L-VHSPAO\u010CRZS (contains \u010C)\n- \u2705 CORRECT: CZ-VY-TEL-L-VHSPAOCRZS (ASCII only)\n\nIMPLEMENTATION:\n```python\n\
import unicodedata\nnormalized = unicodedata.normalize('NFD', text)\nascii_text = ''.join(c for c in normalized if unicodedata.category(c) != 'Mn')\n```\n\nSee: rules/ABBREVIATION_SPECIAL_CHAR_RULE.md for complete documentation\n\nCan be generated by:\n1. ReconstructionActivity (formal entity resolution) - is_or_was_generated_by link\n2. Direct extraction (simple standardization) - no is_or_was_generated_by link\n"
exact_mappings:
close_mappings:
- skos:prefLabel
- schema:name
- foaf:name
close_mappings:
- rdfs:label
- dcterms:title
- org:legalName

View file

@ -58,10 +58,8 @@ classes:
- `unit_description` (string)
- `type_description` (string)
exact_mappings:
- dcterms:description
close_mappings:
- dcterms:description
- skos:definition
- rdfs:comment

View file

@ -103,8 +103,8 @@ classes:
, \"JavaScript\"]\n```\n"
exact_mappings:
- schema:WebSite
- foaf:homepage
close_mappings:
- foaf:homepage
- schema:WebApplication
- dcat:Catalog
- dcat:DataService

View file

@ -63,8 +63,8 @@ classes:
- value → identifier_value
exact_mappings:
- schema:PropertyValue
- dcterms:identifier
close_mappings:
- dcterms:identifier
- adms:Identifier
- skos:notation
slots:

View file

@ -60,10 +60,8 @@ classes:
- `feature_note` (string) → has_or_had_note with Note class
- Other *_note slots per slot_fixes.yaml
exact_mappings:
- skos:note
close_mappings:
- skos:note
- rdfs:comment
- dcterms:description

View file

@ -203,7 +203,7 @@ classes:
description: Institutional scope type instance
broad_mappings:
- skos:Concept
CollectionScope:
CollectionScopeType:
is_a: ScopeType
class_uri: schema:Collection
description: 'Collection-based scope dimension covering collection types and sizes.

View file

@ -27,7 +27,7 @@ imports:
- ./TemplateSpecificityType
- ./TemplateSpecificityTypes
classes:
VideoPost:
VideoPostType:
is_a: SocialMediaPostType
class_uri: as:Video
description: "Standard video content with no strict duration limit.\n\n**Activity\
@ -35,18 +35,18 @@ classes:
\n**Platforms**:\n- YouTube (primary)\n- Vimeo\n- Facebook Video\n- LinkedIn\
\ Video\n- X/Twitter Video\n\n**Duration Characteristics**:\n- YouTube: Up to\
\ 12 hours (for verified accounts)\n- Vimeo: Varies by plan (500MB-unlimited)\n\
- Facebook: Up to 4 hours\n- LinkedIn: Up to 10 minutes\n\n**Heritage Use Cases**:\n\
\n| Use Case | Description | Typical Duration |\n|----------|-------------|------------------|\n\
| Virtual tours | 360\xB0 or guided exhibition walkthroughs | 10-30 min |\n\
| Conservation | Restoration process documentation | 5-20 min |\n| Interviews\
\ | Curator, artist, or expert conversations | 15-60 min |\n| Lectures | Educational\
\ presentations | 30-90 min |\n| Documentaries | In-depth collection or history\
\ stories | 20-60 min |\n| Exhibition intro | Preview of new exhibitions | 2-5\
\ min |\n\n**Technical Properties**:\n- Resolution: Up to 8K on YouTube\n- Formats:\
\ MP4 (H.264), WebM, MOV\n- Captions: VTT, SRT supported\n- Chapters: Timestamp-based\
\ navigation\n\n**Metadata Captured**:\n- Duration (ISO 8601)\n- Definition\
\ (SD, HD, 4K, 8K)\n- Caption availability\n- View/like/comment counts\n- Tags\
\ and categories\n"
\ - Facebook: Up to 4 hours\n- LinkedIn: Up to 10 minutes\n\n**Heritage Use Cases**:\n\
\ \n| Use Case | Description | Typical Duration |\n|----------|-------------|------------------|\n\
\ | Virtual tours | 360\xB0 or guided exhibition walkthroughs | 10-30 min |\n\
\ | Conservation | Restoration process documentation | 5-20 min |\n| Interviews\
\ \ | Curator, artist, or expert conversations | 15-60 min |\n| Lectures | Educational\
\ \ presentations | 30-90 min |\n| Documentaries | In-depth collection or history\
\ \ stories | 20-60 min |\n| Exhibition intro | Preview of new exhibitions | 2-5\
\ \ min |\n\n**Technical Properties**:\n- Resolution: Up to 8K on YouTube\n- Formats:\
\ \ MP4 (H.264), WebM, MOV\n- Captions: VTT, SRT supported\n- Chapters: Timestamp-based\
\ \ navigation\n\n**Metadata Captured**:\n- Duration (ISO 8601)\n- Definition\
\ \ (SD, HD, 4K, 8K)\n- Caption availability\n- View/like/comment counts\n- Tags\
\ \ and categories\n"
exact_mappings:
- as:Video
- schema:VideoObject
@ -85,12 +85,13 @@ classes:
custodian_types: '[''*'']'
broad_mappings:
- skos:Concept
ShortVideoPost:
ShortVideoPostType:
is_a: SocialMediaPostType
class_uri: hc:ShortVideo
description: 'Short-form video content optimized for mobile viewing and discovery.
**Activity Streams Mapping**: `as:Video` (with duration constraint)
**Schema.org Mapping**: `schema:VideoObject`
@ -192,7 +193,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
ImagePost:
ImagePostType:
is_a: SocialMediaPostType
class_uri: as:Image
description: 'Static image content including photographs, graphics, and artwork
@ -307,7 +308,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
TextPost:
TextPostType:
is_a: SocialMediaPostType
class_uri: as:Note
description: 'Text-based social media posts, typically short-form.
@ -401,7 +402,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
StoryPost:
StoryPostType:
is_a: SocialMediaPostType
class_uri: hc:Story
description: 'Ephemeral content that auto-deletes after 24 hours (typically).
@ -498,7 +499,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
LiveStreamPost:
LiveStreamPostType:
is_a: SocialMediaPostType
class_uri: hc:LiveStream
description: 'Real-time video broadcasting with audience interaction.
@ -598,7 +599,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
AudioPost:
AudioPostType:
is_a: SocialMediaPostType
class_uri: as:Audio
description: 'Audio-only content including podcasts, music, and audio guides.
@ -709,7 +710,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
ArticlePost:
ArticlePostType:
is_a: SocialMediaPostType
class_uri: as:Article
description: 'Long-form written content including blog posts and newsletters.
@ -815,7 +816,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
ThreadPost:
ThreadPostType:
is_a: SocialMediaPostType
class_uri: hc:Thread
description: 'Multi-post sequences forming a connected narrative.
@ -909,7 +910,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
CarouselPost:
CarouselPostType:
is_a: SocialMediaPostType
class_uri: hc:Carousel
description: 'Multi-image or multi-video posts in a swipeable format.
@ -1008,7 +1009,7 @@ classes:
- has_or_had_score
broad_mappings:
- skos:Concept
OtherPost:
OtherPostType:
is_a: SocialMediaPostType
class_uri: as:Object
description: "Fallback type for emerging or uncategorized content formats.\n\n\

View file

@ -25,8 +25,8 @@ classes:
description: A source from which something was derived or generated. Can represent manual creation, automated generation, external services, or imported data. Subclasses may specialize for specific domains.
exact_mappings:
- prov:Entity
- dcterms:source
close_mappings:
- dcterms:source
- schema:CreativeWork
slots:
- has_or_had_type

View file

@ -44,8 +44,9 @@ classes:
**Migration Note**: Created 2026-01-19 per slot_fixes.yaml (Rule 53).
Replaces simple string cms_product_version with structured Version class.
exact_mappings:
- schema:version
- doap:Version
close_mappings:
- schema:version
slots:
- temporal_extent
slot_usage:

View file

@ -59,9 +59,8 @@ classes:
\ formats (closed caption, subtitles etc.)\n> use the MediaObject.encodingFormat property.\"\n\n**SUBTITLE vs CAPTION vs TRANSCRIPT**:\n\n| Type | Time-coded | Purpose | Audience |\n|------|------------|---------|----------|\n| Transcript | Optional | Reading, search | Everyone |\n| Subtitle | Required | Language translation | Hearing viewers |\n| Caption (CC) | Required | Accessibility | Deaf/HoH viewers |\n| SDH | Required | Full accessibility | Deaf viewers, noisy environments |\n\n**SDH (Subtitles for Deaf/Hard-of-Hearing)**:\n\nSDH differs from regular subtitles by including:\n- Speaker identification: \"(John) Hello\"\n- Sound effects: \"[door slams]\", \"[music playing]\"\n- Music descriptions: \"\u266A upbeat jazz \u266A\"\n- Emotional cues: \"[laughing]\", \"[whispering]\"\n\n**SUBTITLE FORMATS**:\n\n| Format | Extension | Features | Use Case |\n|--------|-----------|----------|----------|\n| SRT | .srt | Simple, universal | Most video players |\n| VTT | .vtt | W3C standard,\
\ styling | HTML5 video, web |\n| TTML | .ttml/.dfxp | XML, rich styling | Broadcast, streaming |\n| SBV | .sbv | YouTube native | YouTube uploads |\n| ASS | .ass | Advanced styling | Anime, complex layouts |\n\n**SRT FORMAT EXAMPLE**:\n\n```\n1\n00:00:00,000 --> 00:00:03,500\nWelcome to the Rijksmuseum.\n\n2\n00:00:03,500 --> 00:00:08,200\nToday we'll explore the Night Watch gallery.\n```\n\n**VTT FORMAT EXAMPLE**:\n\n```\nWEBVTT\n\n00:00:00.000 --> 00:00:03.500\nWelcome to the Rijksmuseum.\n\n00:00:03.500 --> 00:00:08.200\nToday we'll explore the Night Watch gallery.\n```\n\n**HERITAGE INSTITUTION CONTEXT**:\n\nSubtitles are critical for heritage video accessibility:\n\n1. **Accessibility Compliance**: WCAG 2.1, Section 508\n2. **Multilingual Access**: Translate for international audiences\n3. **Silent Viewing**: Social media, public displays, quiet spaces\n4. **Search Discovery**: Subtitle text is indexed by platforms\n5. **Preservation**: Text outlasts video format obsolescence\n\
\n**YOUTUBE API INTEGRATION**:\n\nSubtitle tracks from YouTube API populate:\n- `has_or_had_format`: Typically VTT or SRT\n- `generation_method`: PLATFORM_PROVIDED or ASR_AUTOMATIC\n- `content_language`: From track language code\n- `is_or_was_created_through`: YouTube auto-caption flag\n\n**SEGMENTS ARE REQUIRED**:\n\nUnlike VideoTranscript where segments are optional, VideoSubtitle\nREQUIRES the `segments` slot to be populated with VideoTimeSegment\nentries that include start_seconds, end_seconds, and segment_text.\n"
exact_mappings:
- schema:caption
close_mappings:
- schema:caption
- ma:CaptioningFormat
related_mappings:
- schema:transcript

View file

@ -33,13 +33,12 @@ slots:
'
range: string
multivalued: true
slot_uri: dcterms:source
slot_uri: ore:aggregates
exact_mappings:
- dcterms:source
close_mappings:
- prov:wasDerivedFrom
related_mappings:
- ore:aggregates
close_mappings:
- dcterms:source
- prov:wasDerivedFrom
annotations:
custodian_types: '["*"]'
custodian_types_rationale: Applicable to all heritage custodian types.

View file

@ -19,13 +19,13 @@ imports:
slots:
allows_or_allowed:
description: "Generic slot for expressing what activities, equipment, or behaviors are permitted in a heritage custodian facility (past or present).\n**SEMANTICS**:\nUses RiC-O temporal pattern (is_or_was / has_or_had / allows_or_allowed) to capture policies that may change over time. A reading room that \"allowed photography\" in 2020 may have changed policy by 2025.\n**USAGE PATTERN**:\nThe range should be a typed class representing the permitted activity: - `Laptop` - laptop use permission - `Photography` - photography permission - Future: `Food`, `Beverages`, `MobilePhone`, etc.\n**EXAMPLES**:\n```yaml ReadingRoom:\n allows_or_allowed:\n - permitted_item: Laptop\n is_permitted: true\n conditions: \"Must be silent, no external keyboards\"\n - permitted_item: Photography \n is_permitted: true\n conditions: \"Personal research use only, no flash\"\n```"
slot_uri: schema:amenityFeature
slot_uri: odrl:permission
range: uriorcurie
multivalued: true
exact_mappings:
- schema:amenityFeature
close_mappings:
- odrl:permission
close_mappings:
- schema:amenityFeature
annotations:
custodian_types: '["*"]'
custodian_types_rationale: All heritage custodians have visitor policies.

View file

@ -59,5 +59,5 @@ slots:
description: Video frame analysis at 1 fps (VideoFrame instance)
annotations:
custodian_types: '["*"]'
exact_mappings:
broad_mappings:
- schema:object

View file

@ -30,14 +30,13 @@ slots:
'
range: string
multivalued: true
slot_uri: dcterms:isPartOf
slot_uri: ore:isAggregatedBy
exact_mappings:
- dcterms:isPartOf
- ore:isAggregatedBy
close_mappings:
- dcterms:isPartOf
- edm:isShownAt
- schema:includedInDataCatalog
related_mappings:
- ore:isAggregatedBy
annotations:
custodian_types: '["*"]'
custodian_types_rationale: Applicable to all heritage custodian types.

View file

@ -62,7 +62,7 @@ slots:
multivalued: true
inlined: true
inlined_as_list: true
exact_mappings:
related_mappings:
- as:Like
close_mappings:
- schema:interactionStatistic

View file

@ -56,12 +56,11 @@ slots:
description: "Maximum acceptable cumulative annual light exposure in lux-hours per year.\n\nBased on preservation standards:\n- High sensitivity (EN 16893): <15,000 lux-hours/year\n- Medium sensitivity (EN 16893): <150,000 lux-hours/year\n- Textiles (CIE 157): <12,000 lux-hours/year\n- General guidance (ASHRAE): <50,000 lux-hours/year\n\nLight damage is cumulative (reciprocity law). Annual limits mandate:\n- Rotating displays for sensitive works\n- Controlled access periods\n- Dark storage between exhibition periods\n\nExample: 50 lux \xD7 8 hours/day \xD7 250 days = 100,000 lux-hours (too high!)\n"
range: float
slot_uri: hc:maxAnnualLightExposure
exact_mappings:
- quantitykind:LuminousExposure
close_mappings:
- sosa:ObservableProperty
- crm:E54_Dimension
related_mappings:
- quantitykind:LuminousExposure
- wd:Q194411
- schema:maxValue
minimum_value: 0.0

View file

@ -28,13 +28,12 @@ slots:
description: "Maximum acceptable light level in lux (lumens per square meter).\n\nBased on ISO/EN preservation standards:\n- Archives (ISO 11799): <50 lux for paper/parchment\n- High sensitivity (EN 16893): <50 lux (textiles, watercolors, photos)\n- Medium sensitivity (EN 16893): <200 lux (oil paintings, leather)\n- Low sensitivity (EN 16893): <300 lux (ceramics, stone, metals)\n- UK Storage (BS 4971): 0 lux for closed storage\n\nLight damage is cumulative and irreversible. Damage follows the\nreciprocity law: 50 lux \xD7 8 hours = 400 lux \xD7 1 hour (same damage).\n"
range: float
slot_uri: hc:maxLightLux
exact_mappings:
- quantitykind:Illuminance
close_mappings:
- sosa:ObservableProperty
- crm:E54_Dimension
- brick:Illuminance_Sensor
related_mappings:
- quantitykind:Illuminance
- wd:Q194411
- schema:maxValue
minimum_value: 0.0