/** * schema-custodian-mapping.ts - Maps LinkML schema elements to CustodianTypes * * This module provides mappings between schema elements (classes, slots, enums) * and the GLAMORCUBESFIXPHDNT custodian types they primarily relate to. * * Used by the CustodianTypeIndicator component to show which type(s) a schema * element is most relevant to. * * PRIORITY ORDER: * 1. Read `custodian_types` annotation from LinkML schema (via linkmlSchemaService) * 2. Fall back to static mapping (CLASS_TO_CUSTODIAN_TYPE, etc.) * 3. Default to all 19 types (universal) */ import type { CustodianTypeCode } from './custodian-types'; import { linkmlSchemaService } from './linkml/linkml-schema-service'; /** * Mapping of schema class names to relevant custodian types * * Key: Class name (as it appears in LinkML schema) * Value: Array of CustodianTypeCode(s) the class relates to * * Most classes relate to ALL types (universal), but some are type-specific. */ export const CLASS_TO_CUSTODIAN_TYPE: Record = { // Universal classes (apply to all custodian types) 'CustodianObservation': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'CustodianName': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'CustodianReconstruction': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'Location': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'GHCID': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'Provenance': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], // Place-related classes 'FeaturePlace': ['F'], // Features (monuments, statues) 'FeaturePlaceClass': ['F'], // Collection-related classes 'Collection': ['G', 'L', 'A', 'M', 'B', 'H'], // Galleries, Libraries, Archives, Museums, Botanical, Holy sites 'CollectionItem': ['G', 'L', 'A', 'M', 'B', 'H'], // Digital platform classes 'DigitalPlatform': ['D'], // Digital platforms 'DigitalPlatformClass': ['D'], 'WebObservation': ['D'], 'WebClaim': ['D'], // Archive-specific 'ArchivalFonds': ['A'], // Archives 'ArchivalSeries': ['A'], 'ArchivalRecord': ['A'], // Library-specific 'BibliographicRecord': ['L'], // Libraries 'Catalog': ['L'], // Museum-specific 'Exhibition': ['M', 'G'], // Museums, Galleries 'MuseumObject': ['M'], // Research-related 'ResearchProject': ['R'], // Research centers 'Publication': ['R', 'L'], // Research centers, Libraries // Education-related 'Course': ['E'], // Education providers 'LearningResource': ['E', 'D'], // Education, Digital platforms // Religious heritage 'ReligiousCollection': ['H'], // Holy sites 'LiturgicalObject': ['H'], // Botanical/Zoo 'LivingCollection': ['B'], // Botanical gardens/zoos 'Specimen': ['B'], // Intangible heritage 'IntangibleHeritage': ['I'], // Intangible heritage groups 'Performance': ['I'], 'Tradition': ['I'], // Organizational 'StaffRole': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'E', 'S', 'H', 'N'], 'OrganizationalChange': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], // Personal collections 'PersonalCollection': ['P'], // Personal collections 'PrivateArchive': ['P'], // Corporate 'CorporateCollection': ['C'], // Corporations 'CorporateArchive': ['C'], // Society-related 'SocietyMembership': ['S'], // Collecting societies 'HeemkundigeKring': ['S'], // Taste/Smell heritage 'CulinaryHeritage': ['T'], // Taste/smell heritage 'Recipe': ['T'], 'Formulation': ['T'], // NGO-specific 'AdvocacyOrganization': ['N'], // NGOs 'HeritageInitiative': ['N'], }; /** * Mapping of schema slot names to relevant custodian types */ export const SLOT_TO_CUSTODIAN_TYPE: Record = { // Universal slots 'custodian_name': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'location': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'ghcid': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'provenance': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], // Archive-specific slots 'fonds': ['A'], 'finding_aid': ['A'], 'archival_hierarchy': ['A'], // Library-specific slots 'call_number': ['L'], 'bibliographic_record': ['L'], 'catalog_entry': ['L'], // Museum-specific slots 'accession_number': ['M'], 'exhibition_history': ['M', 'G'], 'conservation_status': ['M', 'G'], // Digital platform slots 'platform_url': ['D'], 'api_endpoint': ['D'], 'metadata_format': ['D', 'L', 'A', 'M'], // Religious heritage slots 'denomination': ['H'], 'consecration_date': ['H'], 'liturgical_calendar': ['H'], // Botanical/Zoo slots 'species': ['B'], 'habitat': ['B'], 'conservation_program': ['B'], // Intangible heritage slots 'tradition_type': ['I'], 'transmission_method': ['I'], 'practitioners': ['I'], // Taste/Smell slots 'recipe_origin': ['T'], 'ingredients': ['T'], 'preparation_method': ['T'], }; /** * Mapping of enum names to relevant custodian types */ export const ENUM_TO_CUSTODIAN_TYPE: Record = { // Universal enums 'CustodianTypeEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'DataTierEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'DataSourceEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'CountryCodeEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], 'LanguageCodeEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T'], // Type-specific enums 'ArchivalLevelEnum': ['A'], 'BibliographicFormatEnum': ['L'], 'ExhibitionTypeEnum': ['M', 'G'], 'DigitalPlatformTypeEnum': ['D'], 'ReligiousDenominationEnum': ['H'], 'SpeciesClassificationEnum': ['B'], 'IntangibleHeritageTypeEnum': ['I'], 'CulinaryHeritageTypeEnum': ['T'], 'StaffRoleTypeEnum': ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'E', 'S', 'H', 'N'], }; /** * Default types for elements not explicitly mapped (universal) */ export const DEFAULT_CUSTODIAN_TYPES: CustodianTypeCode[] = [ 'G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T' ]; /** * Get custodian types for a schema class */ export function getCustodianTypesForClass(className: string): CustodianTypeCode[] { return CLASS_TO_CUSTODIAN_TYPE[className] || DEFAULT_CUSTODIAN_TYPES; } /** * Get custodian types for a schema slot */ export function getCustodianTypesForSlot(slotName: string): CustodianTypeCode[] { return SLOT_TO_CUSTODIAN_TYPE[slotName] || DEFAULT_CUSTODIAN_TYPES; } /** * Get custodian types for a schema enum */ export function getCustodianTypesForEnum(enumName: string): CustodianTypeCode[] { return ENUM_TO_CUSTODIAN_TYPE[enumName] || DEFAULT_CUSTODIAN_TYPES; } /** * Check if a schema element is universal (applies to all types) */ export function isUniversalElement(types: CustodianTypeCode[]): boolean { return types.length >= 19; // All 19 types } /** * Get the primary custodian type for a schema element * Returns the first type, or 'U' (Unknown) if empty */ export function getPrimaryCustodianType(types: CustodianTypeCode[]): CustodianTypeCode { if (types.length === 0) return 'U'; // For universal elements, return the most common types first if (isUniversalElement(types)) { return 'M'; // Museum as default primary for universal } return types[0]; } /** * Get a compact representation of custodian types * For universal: returns "ALL" * For few types: returns joined string (e.g., "MAL") * For many types: returns abbreviated (e.g., "MA+5") */ export function getCompactTypeRepresentation(types: CustodianTypeCode[]): string { if (types.length === 0) return '?'; if (isUniversalElement(types)) return 'ALL'; if (types.length <= 4) return types.join(''); return types.slice(0, 2).join('') + `+${types.length - 2}`; } // ============================================================================ // ASYNC FUNCTIONS - Read from LinkML schema annotations with static fallback // ============================================================================ /** * Validate that a string array contains valid CustodianTypeCodes */ function validateCustodianTypes(types: string[]): CustodianTypeCode[] { const validCodes: CustodianTypeCode[] = ['G', 'L', 'A', 'M', 'O', 'R', 'C', 'U', 'B', 'E', 'S', 'F', 'I', 'X', 'P', 'H', 'D', 'N', 'T']; return types.filter((t): t is CustodianTypeCode => validCodes.includes(t as CustodianTypeCode)); } /** * Get custodian types for a schema class (async version) * * Priority: * 1. Read from LinkML schema annotations (custodian_types) * 2. Fall back to static mapping * 3. Default to EMPTY ARRAY (no types assigned) - cube will show no letters * * NOTE: We return [] instead of DEFAULT_CUSTODIAN_TYPES when no annotation exists * because classes without explicit custodian_types annotations should NOT display * all 19 letters on the cube. Only classes with explicit annotations should show letters. */ export async function getCustodianTypesForClassAsync(className: string): Promise { try { // Try to get from schema annotations first const annotationTypes = await linkmlSchemaService.getClassCustodianTypes(className); if (annotationTypes && annotationTypes.length > 0) { const validated = validateCustodianTypes(annotationTypes); if (validated.length > 0) { return validated; } } } catch (error) { console.warn(`[CustodianMapping] Error reading annotations for class ${className}:`, error); } // Fall back to static mapping, or empty array if no mapping exists // Empty array means "no custodian types assigned" - cube will show no letters return CLASS_TO_CUSTODIAN_TYPE[className] || []; } /** * Get custodian types for a schema slot (async version) * * Priority: * 1. Read from slot's own LinkML schema annotations (custodian_types) * 2. Inherit from parent class(es) that use this slot * 3. Fall back to static mapping * 4. Return empty array (no types assigned - cube shows no letters) */ export async function getCustodianTypesForSlotAsync(slotName: string): Promise { try { // 1. Try slot's own annotation first const annotationTypes = await linkmlSchemaService.getSlotCustodianTypes(slotName); if (annotationTypes && annotationTypes.length > 0) { const validated = validateCustodianTypes(annotationTypes); if (validated.length > 0) { return validated; } } // 2. Try to inherit from parent class(es) that use this slot const parentClasses = await linkmlSchemaService.getClassesUsingSlot(slotName); if (parentClasses.length > 0) { const inheritedTypes = new Set(); for (const className of parentClasses) { const classTypes = await linkmlSchemaService.getClassCustodianTypes(className); if (classTypes && classTypes.length > 0) { const validated = validateCustodianTypes(classTypes); validated.forEach(t => inheritedTypes.add(t)); } } if (inheritedTypes.size > 0) { return Array.from(inheritedTypes); } } } catch (error) { console.warn(`[CustodianMapping] Error reading annotations for slot ${slotName}:`, error); } // 3. Fall back to static mapping, or empty array if no mapping exists // Empty array means "no custodian types assigned" - cube will show no letters return SLOT_TO_CUSTODIAN_TYPE[slotName] || []; } /** * Get custodian types for a schema enum (async version) * * Priority: * 1. Read from enum's LinkML schema annotations (custodian_types) * 2. Fall back to static mapping * 3. Return empty array (no types assigned - cube shows no letters) */ export async function getCustodianTypesForEnumAsync(enumName: string): Promise { try { const annotationTypes = await linkmlSchemaService.getEnumCustodianTypes(enumName); if (annotationTypes && annotationTypes.length > 0) { const validated = validateCustodianTypes(annotationTypes); if (validated.length > 0) { return validated; } } } catch (error) { console.warn(`[CustodianMapping] Error reading annotations for enum ${enumName}:`, error); } // Fall back to static mapping, or empty array if no mapping exists // Empty array means "no custodian types assigned" - cube will show no letters return ENUM_TO_CUSTODIAN_TYPE[enumName] || []; }