feat: Add SchemaElementPopup component for displaying LinkML schema element previews

- Implemented a draggable, resizable, and minimizable popup component for displaying previews of LinkML schema elements (classes, slots, enums).
- Integrated loading states and error handling for fetching element information.
- Added navigation functionality to go to full element view.
- Enhanced user experience with type badges and detailed descriptions for each element type.

chore: Migrate AudioEventSegment, BayNumber, BoxNumber, and BudgetStatus classes to new YAML schema format

- Created new YAML definitions for AudioEventSegment, BayNumber, BoxNumber, and BudgetStatus classes with detailed descriptions and attributes.
- Migrated from deprecated slots to new class structures as part of Rule 53.
- Updated imports and prefixes for consistency across schemas.

chore: Archive deprecated slots for audio_event_segments, bay_number, and box_number

- Archived previous slot definitions for audio_event_segments, bay_number, and box_number to maintain historical records.
- Updated slot descriptions and ensured proper URI mappings for future reference.
This commit is contained in:
kempersc 2026-01-14 15:13:06 +01:00
parent b927bc4b43
commit 7c7d8c0270
17 changed files with 1652 additions and 49 deletions

View file

@ -1,5 +1,5 @@
{ {
"generated": "2026-01-14T12:29:52.423Z", "generated": "2026-01-14T14:05:38.322Z",
"schemaRoot": "/schemas/20251121/linkml", "schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2884, "totalFiles": 2884,
"categoryCounts": { "categoryCounts": {

View file

@ -1316,3 +1316,26 @@ fixes:
type: slot type: slot
- label: TimeSpan - label: TimeSpan
type: class type: class
- original_slot_id: https://nde.nl/ontology/hc/slot/xpath_matched_text
revision:
- label: has_or_had_text
type: slot
- label: TextSegment
type: class
- original_slot_id: https://nde.nl/ontology/hc/slot/xpath_match_score
revision:
- label: has_or_had_score
type: slot
- label: XPathScore
type: class
- original_slot_id: https://nde.nl/ontology/hc/slot/xpath
revision:
- label: has_or_had_provenance
type: slot
- label: Provenance
type: class
- label: has_or_had_provenance_path
type: slot
- label: XPath
type: class
-

View file

@ -0,0 +1,479 @@
/**
* SchemaElementPopup.css
*
* Styles for the schema element popup shown when clicking on class/slot/enum
* names in the Imports/Exports accordion sections in the LinkML viewer.
*/
.schema-popup {
position: fixed;
width: 400px;
height: 320px;
max-height: calc(100vh - 100px);
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10200; /* Above SemanticDetailsPanel (10100) */
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid #e5e7eb;
transition: box-shadow 0.2s;
}
/* Dragging state */
.schema-popup--dragging {
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25), 0 8px 20px rgba(0, 0, 0, 0.15);
opacity: 0.95;
}
/* Minimized state */
.schema-popup--minimized {
height: auto !important;
max-height: 52px;
overflow: hidden;
}
.schema-popup--minimized .schema-popup__header {
border-bottom: none;
}
/* Header - default blue for classes */
.schema-popup__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
color: #ffffff;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
cursor: grab;
flex-shrink: 0;
}
.schema-popup--dragging .schema-popup__header {
cursor: grabbing;
}
.schema-popup__title {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
min-width: 0;
}
.schema-popup__name {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 14px;
font-weight: 600;
color: #ffffff;
background: rgba(255, 255, 255, 0.15);
padding: 3px 8px;
border-radius: 4px;
}
.schema-popup__controls {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.schema-popup__minimize,
.schema-popup__close {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
width: 28px;
height: 28px;
border-radius: 6px;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.schema-popup__minimize:hover,
.schema-popup__close:hover {
background: rgba(255, 255, 255, 0.2);
}
/* Type badge */
.schema-popup__type-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
color: white;
text-transform: uppercase;
letter-spacing: 0.03em;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* Content */
.schema-popup__content {
padding: 16px;
overflow-y: auto;
overscroll-behavior: contain;
flex: 1;
}
.schema-popup__loading,
.schema-popup__error {
padding: 24px 16px;
text-align: center;
color: #64748b;
}
.schema-popup__error {
color: #dc2626;
background: #fef2f2;
border-radius: 8px;
margin: 8px;
}
/* Sections */
.schema-popup__section {
margin-bottom: 14px;
padding-bottom: 14px;
border-bottom: 1px solid #f1f5f9;
}
.schema-popup__section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.schema-popup__section-label {
font-size: 11px;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
display: block;
margin-bottom: 6px;
}
.schema-popup__description {
margin: 0;
font-size: 13px;
line-height: 1.6;
color: #475569;
}
/* URI display */
.schema-popup__uri {
display: block;
background: #f1f5f9;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
color: #475569;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
word-break: break-all;
}
/* Values */
.schema-popup__value {
display: inline-block;
background: #f1f5f9;
padding: 2px 8px;
border-radius: 4px;
font-size: 13px;
color: #1e40af;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
}
.schema-popup__count {
font-size: 13px;
color: #475569;
}
/* Properties (for slots) */
.schema-popup__properties {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.schema-popup__prop {
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
background: #f1f5f9;
color: #64748b;
}
.schema-popup__prop--true {
background: #dcfce7;
color: #166534;
}
/* Mappings summary */
.schema-popup__mappings {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.schema-popup__mapping-badge {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.schema-popup__mapping-badge--exact {
background: #dcfce7;
color: #166534;
}
.schema-popup__mapping-badge--close {
background: #dbeafe;
color: #1e40af;
}
.schema-popup__mapping-badge--related {
background: #fef3c7;
color: #92400e;
}
/* Actions */
.schema-popup__actions {
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid #f1f5f9;
}
.schema-popup__navigate {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
color: white;
font-size: 13px;
font-weight: 500;
text-decoration: none;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
}
.schema-popup__navigate:hover {
background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 100%);
transform: translateY(-1px);
}
/* Resize handles */
.schema-popup__resize {
position: absolute;
z-index: 10;
}
.schema-popup__resize--n {
top: 0;
left: 8px;
right: 8px;
height: 6px;
cursor: n-resize;
}
.schema-popup__resize--s {
bottom: 0;
left: 8px;
right: 8px;
height: 6px;
cursor: s-resize;
}
.schema-popup__resize--e {
right: 0;
top: 8px;
bottom: 8px;
width: 6px;
cursor: e-resize;
}
.schema-popup__resize--w {
left: 0;
top: 8px;
bottom: 8px;
width: 6px;
cursor: w-resize;
}
.schema-popup__resize--ne {
top: 0;
right: 0;
width: 12px;
height: 12px;
cursor: ne-resize;
}
.schema-popup__resize--nw {
top: 0;
left: 0;
width: 12px;
height: 12px;
cursor: nw-resize;
}
.schema-popup__resize--se {
bottom: 0;
right: 0;
width: 12px;
height: 12px;
cursor: se-resize;
}
.schema-popup__resize--sw {
bottom: 0;
left: 0;
width: 12px;
height: 12px;
cursor: sw-resize;
}
/* SE corner grip indicator */
.schema-popup__resize--se::after {
content: '';
position: absolute;
right: 3px;
bottom: 3px;
width: 8px;
height: 8px;
border-right: 2px solid #94a3b8;
border-bottom: 2px solid #94a3b8;
opacity: 0.5;
transition: opacity 0.2s;
}
.schema-popup__resize--se:hover::after {
opacity: 1;
}
/* Dark mode */
[data-theme="dark"] .schema-popup {
background: #1e293b;
border-color: #334155;
}
[data-theme="dark"] .schema-popup__header {
background: linear-gradient(135deg, #1e3a8a 0%, #1d4ed8 100%);
}
[data-theme="dark"] .schema-popup__content {
background: #1e293b;
}
[data-theme="dark"] .schema-popup__section {
border-bottom-color: #334155;
}
[data-theme="dark"] .schema-popup__section-label {
color: #94a3b8;
}
[data-theme="dark"] .schema-popup__description {
color: #cbd5e1;
}
[data-theme="dark"] .schema-popup__uri {
background: #334155;
color: #e2e8f0;
}
[data-theme="dark"] .schema-popup__value {
background: #334155;
color: #93c5fd;
}
[data-theme="dark"] .schema-popup__count {
color: #94a3b8;
}
[data-theme="dark"] .schema-popup__prop {
background: #334155;
color: #94a3b8;
}
[data-theme="dark"] .schema-popup__prop--true {
background: rgba(34, 197, 94, 0.15);
color: #86efac;
}
[data-theme="dark"] .schema-popup__mapping-badge--exact {
background: rgba(34, 197, 94, 0.15);
color: #86efac;
}
[data-theme="dark"] .schema-popup__mapping-badge--close {
background: rgba(59, 130, 246, 0.15);
color: #93c5fd;
}
[data-theme="dark"] .schema-popup__mapping-badge--related {
background: rgba(245, 158, 11, 0.15);
color: #fcd34d;
}
[data-theme="dark"] .schema-popup__actions {
border-top-color: #334155;
}
[data-theme="dark"] .schema-popup__error {
background: rgba(220, 38, 38, 0.15);
color: #fca5a5;
}
[data-theme="dark"] .schema-popup__loading {
color: #94a3b8;
}
[data-theme="dark"] .schema-popup__resize--se::after {
border-color: #64748b;
}
/* Responsive - Full screen on mobile */
@media (max-width: 600px) {
.schema-popup {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
max-height: 100vh !important;
border-radius: 0;
}
.schema-popup__header {
cursor: default;
}
.schema-popup__resize {
display: none;
}
.schema-popup__minimize {
display: none;
}
.schema-popup__close {
width: 40px;
height: 40px;
font-size: 24px;
}
}

View file

@ -0,0 +1,549 @@
/**
* SchemaElementPopup.tsx
*
* A popup component that displays LinkML schema element previews when clicking on
* class/slot/enum names in the Imports/Exports accordion sections.
*
* Features:
* - Draggable (drag from header)
* - Minimizable (collapse to title bar only)
* - Resizable (drag edges and corners)
* - Shows preview info for classes, slots, and enums
* - "Go to" button to navigate to full element view
*/
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback } from 'react';
import { linkmlSchemaService } from '../../lib/linkml/linkml-schema-service';
import { isTargetInsideAny } from '../../utils/dom';
import './SchemaElementPopup.css';
export type SchemaElementType = 'class' | 'slot' | 'enum';
interface SchemaElementPopupProps {
/** The element name to look up */
elementName: string;
/** Type of element */
elementType: SchemaElementType;
/** Called when user wants to close the popup */
onClose: () => void;
/** Called when user clicks "Go to" button */
onNavigate: (elementName: string, elementType: SchemaElementType) => void;
/** Initial position (optional, defaults to center) */
initialPosition?: { x: number; y: number };
}
interface Position {
x: number;
y: number;
}
interface Size {
width: number;
height: number;
}
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | null;
// Minimum panel dimensions
const MIN_WIDTH = 300;
const MIN_HEIGHT = 150;
const DEFAULT_WIDTH = 400;
const DEFAULT_HEIGHT = 320;
/**
* Unified element info structure
*/
interface ElementInfo {
name: string;
type: SchemaElementType;
description?: string;
uri?: string;
parentClass?: string;
range?: string;
required?: boolean;
multivalued?: boolean;
slotCount?: number;
valueCount?: number;
mappings?: {
exact?: string[];
close?: string[];
related?: string[];
};
}
export const SchemaElementPopup: React.FC<SchemaElementPopupProps> = ({
elementName,
elementType,
onClose,
onNavigate,
initialPosition,
}) => {
const [elementInfo, setElementInfo] = useState<ElementInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Draggable state
const [position, setPosition] = useState<Position | null>(initialPosition || null);
const [isPositioned, setIsPositioned] = useState(!!initialPosition);
const [isDragging, setIsDragging] = useState(false);
const dragStartRef = useRef<{ x: number; y: number; posX: number; posY: number } | null>(null);
const panelRef = useRef<HTMLDivElement>(null);
// Minimized state
const [isMinimized, setIsMinimized] = useState(false);
// Resize state
const [size, setSize] = useState<Size>({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT });
const [isResizing, setIsResizing] = useState(false);
const [resizeDirection, setResizeDirection] = useState<ResizeDirection>(null);
const resizeStartRef = useRef<{
x: number;
y: number;
width: number;
height: number;
posX: number;
posY: number;
} | null>(null);
// Load element information
useEffect(() => {
const loadInfo = async () => {
setLoading(true);
setError(null);
try {
let info: ElementInfo | null = null;
if (elementType === 'class') {
const semanticInfo = await linkmlSchemaService.getClassSemanticInfo(elementName);
if (semanticInfo) {
info = {
name: semanticInfo.className,
type: 'class',
description: semanticInfo.description,
uri: semanticInfo.classUri,
parentClass: semanticInfo.parentClass,
slotCount: semanticInfo.slots?.length || 0,
mappings: semanticInfo.mappings,
};
}
} else if (elementType === 'slot') {
const slotInfo = await linkmlSchemaService.getSlotInfo(elementName);
if (slotInfo) {
info = {
name: slotInfo.name,
type: 'slot',
description: slotInfo.description,
uri: slotInfo.slotUri,
range: slotInfo.range,
required: slotInfo.required,
multivalued: slotInfo.multivalued,
};
}
} else if (elementType === 'enum') {
const enumInfo = await linkmlSchemaService.getEnumSemanticInfo(elementName);
if (enumInfo) {
info = {
name: enumInfo.enumName,
type: 'enum',
description: enumInfo.description,
// EnumSemanticInfo doesn't have URI - leave undefined
valueCount: enumInfo.values?.length || 0,
};
}
}
if (info) {
setElementInfo(info);
} else {
setError(`Could not find ${elementType} "${elementName}"`);
}
} catch (err) {
console.error('[SchemaElementPopup] Error loading element info:', err);
setError(`Failed to load: ${err}`);
} finally {
setLoading(false);
}
};
loadInfo();
}, [elementName, elementType]);
// Center the popup on initial load if no position provided
useLayoutEffect(() => {
if (!initialPosition && panelRef.current && !position) {
const rect = panelRef.current.getBoundingClientRect();
setPosition({
x: (window.innerWidth - rect.width) / 2,
y: (window.innerHeight - rect.height) / 3,
});
setIsPositioned(true);
}
}, [initialPosition, position]);
// Drag handlers
const handleMouseDown = useCallback((e: React.MouseEvent) => {
if (isTargetInsideAny(e.target, ['.schema-popup__close', '.schema-popup__minimize', '.schema-popup__navigate'])) {
return;
}
e.preventDefault();
setIsDragging(true);
const panel = panelRef.current;
if (panel) {
const rect = panel.getBoundingClientRect();
const currentX = position?.x ?? rect.left;
const currentY = position?.y ?? rect.top;
dragStartRef.current = {
x: e.clientX,
y: e.clientY,
posX: currentX,
posY: currentY,
};
}
}, [position]);
const handleMouseMove = useCallback((e: MouseEvent) => {
if (!isDragging || !dragStartRef.current) return;
const deltaX = e.clientX - dragStartRef.current.x;
const deltaY = e.clientY - dragStartRef.current.y;
const newX = dragStartRef.current.posX + deltaX;
const newY = dragStartRef.current.posY + deltaY;
const maxX = window.innerWidth - 100;
const maxY = window.innerHeight - 50;
setPosition({
x: Math.max(0, Math.min(newX, maxX)),
y: Math.max(0, Math.min(newY, maxY)),
});
}, [isDragging]);
const handleMouseUp = useCallback(() => {
setIsDragging(false);
dragStartRef.current = null;
}, []);
// Add/remove global mouse event listeners for dragging
useEffect(() => {
if (isDragging) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
document.body.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';
} else {
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
}, [isDragging, handleMouseMove, handleMouseUp]);
// Toggle minimize
const toggleMinimize = useCallback(() => {
setIsMinimized(prev => !prev);
}, []);
// Resize handlers
const handleResizeMouseDown = useCallback((direction: ResizeDirection) => (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setIsResizing(true);
setResizeDirection(direction);
const panel = panelRef.current;
if (panel) {
const rect = panel.getBoundingClientRect();
resizeStartRef.current = {
x: e.clientX,
y: e.clientY,
width: size.width,
height: size.height,
posX: position?.x ?? rect.left,
posY: position?.y ?? rect.top,
};
}
}, [size, position]);
const handleResizeMouseMove = useCallback((e: MouseEvent) => {
if (!isResizing || !resizeStartRef.current || !resizeDirection) return;
const deltaX = e.clientX - resizeStartRef.current.x;
const deltaY = e.clientY - resizeStartRef.current.y;
let newWidth = resizeStartRef.current.width;
let newHeight = resizeStartRef.current.height;
let newX = resizeStartRef.current.posX;
let newY = resizeStartRef.current.posY;
if (resizeDirection.includes('e')) {
newWidth = Math.max(MIN_WIDTH, resizeStartRef.current.width + deltaX);
}
if (resizeDirection.includes('w')) {
const potentialWidth = resizeStartRef.current.width - deltaX;
if (potentialWidth >= MIN_WIDTH) {
newWidth = potentialWidth;
newX = resizeStartRef.current.posX + deltaX;
}
}
if (resizeDirection.includes('s')) {
newHeight = Math.max(MIN_HEIGHT, resizeStartRef.current.height + deltaY);
}
if (resizeDirection.includes('n')) {
const potentialHeight = resizeStartRef.current.height - deltaY;
if (potentialHeight >= MIN_HEIGHT) {
newHeight = potentialHeight;
newY = resizeStartRef.current.posY + deltaY;
}
}
setSize({ width: newWidth, height: newHeight });
if (resizeDirection.includes('w') || resizeDirection.includes('n')) {
setPosition({ x: newX, y: newY });
}
}, [isResizing, resizeDirection]);
const handleResizeMouseUp = useCallback(() => {
setIsResizing(false);
setResizeDirection(null);
resizeStartRef.current = null;
}, []);
// Add/remove global mouse event listeners for resizing
useEffect(() => {
if (isResizing) {
window.addEventListener('mousemove', handleResizeMouseMove);
window.addEventListener('mouseup', handleResizeMouseUp);
document.body.style.userSelect = 'none';
}
return () => {
window.removeEventListener('mousemove', handleResizeMouseMove);
window.removeEventListener('mouseup', handleResizeMouseUp);
if (!isDragging) {
document.body.style.userSelect = '';
}
};
}, [isResizing, handleResizeMouseMove, handleResizeMouseUp, isDragging]);
// Handle navigate button click
const handleNavigate = useCallback(() => {
onNavigate(elementName, elementType);
onClose();
}, [elementName, elementType, onNavigate, onClose]);
// Render type badge
const renderTypeBadge = (type: SchemaElementType) => {
const colors: Record<SchemaElementType, string> = {
'class': '#3B82F6', // Blue
'slot': '#10B981', // Green
'enum': '#F59E0B', // Amber
};
return (
<span
className="schema-popup__type-badge"
style={{ backgroundColor: colors[type] }}
>
{type}
</span>
);
};
// Calculate panel style
const panelStyle: React.CSSProperties = {
width: size.width,
height: isMinimized ? 'auto' : size.height,
visibility: isPositioned ? 'visible' : 'hidden',
...(position && {
position: 'fixed',
left: position.x,
top: position.y,
}),
};
return (
<div
ref={panelRef}
className={`schema-popup ${isMinimized ? 'schema-popup--minimized' : ''} ${isDragging ? 'schema-popup--dragging' : ''}`}
style={panelStyle}
>
{/* Header */}
<div
className="schema-popup__header"
onMouseDown={handleMouseDown}
>
<div className="schema-popup__title">
<code className="schema-popup__name">{elementName}</code>
{renderTypeBadge(elementType)}
</div>
<div className="schema-popup__controls">
<button
className="schema-popup__minimize"
onClick={toggleMinimize}
title={isMinimized ? 'Expand' : 'Minimize'}
>
{isMinimized ? '□' : ''}
</button>
<button
className="schema-popup__close"
onClick={onClose}
title="Close"
>
×
</button>
</div>
</div>
{/* Content */}
{!isMinimized && (
<div className="schema-popup__content">
{loading && (
<div className="schema-popup__loading">
Loading {elementType} information...
</div>
)}
{error && (
<div className="schema-popup__error">
{error}
</div>
)}
{!loading && !error && elementInfo && (
<>
{/* Description */}
{elementInfo.description && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Description</span>
<p className="schema-popup__description">{elementInfo.description}</p>
</div>
)}
{/* URI */}
{elementInfo.uri && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">URI</span>
<code className="schema-popup__uri">{elementInfo.uri}</code>
</div>
)}
{/* Parent class (for classes) */}
{elementInfo.parentClass && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Parent Class</span>
<code className="schema-popup__value">{elementInfo.parentClass}</code>
</div>
)}
{/* Range (for slots) */}
{elementInfo.range && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Range</span>
<code className="schema-popup__value">{elementInfo.range}</code>
</div>
)}
{/* Properties (for slots) */}
{elementInfo.type === 'slot' && (
<div className="schema-popup__section schema-popup__properties">
{elementInfo.required !== undefined && (
<span className={`schema-popup__prop ${elementInfo.required ? 'schema-popup__prop--true' : ''}`}>
{elementInfo.required ? 'required' : 'optional'}
</span>
)}
{elementInfo.multivalued !== undefined && (
<span className={`schema-popup__prop ${elementInfo.multivalued ? 'schema-popup__prop--true' : ''}`}>
{elementInfo.multivalued ? 'multivalued' : 'single-valued'}
</span>
)}
</div>
)}
{/* Slot count (for classes) */}
{elementInfo.slotCount !== undefined && elementInfo.slotCount > 0 && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Slots</span>
<span className="schema-popup__count">{elementInfo.slotCount} slots</span>
</div>
)}
{/* Value count (for enums) */}
{elementInfo.valueCount !== undefined && elementInfo.valueCount > 0 && (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Values</span>
<span className="schema-popup__count">{elementInfo.valueCount} permissible values</span>
</div>
)}
{/* Mappings (summary) */}
{elementInfo.mappings && (
(elementInfo.mappings.exact?.length ||
elementInfo.mappings.close?.length ||
elementInfo.mappings.related?.length) ? (
<div className="schema-popup__section">
<span className="schema-popup__section-label">Mappings</span>
<div className="schema-popup__mappings">
{elementInfo.mappings.exact?.length ? (
<span className="schema-popup__mapping-badge schema-popup__mapping-badge--exact">
{elementInfo.mappings.exact.length} exact
</span>
) : null}
{elementInfo.mappings.close?.length ? (
<span className="schema-popup__mapping-badge schema-popup__mapping-badge--close">
{elementInfo.mappings.close.length} close
</span>
) : null}
{elementInfo.mappings.related?.length ? (
<span className="schema-popup__mapping-badge schema-popup__mapping-badge--related">
{elementInfo.mappings.related.length} related
</span>
) : null}
</div>
</div>
) : null
)}
{/* Navigate button */}
<div className="schema-popup__actions">
<button
className="schema-popup__navigate"
onClick={handleNavigate}
>
Go to {elementType}
</button>
</div>
</>
)}
</div>
)}
{/* Resize handles */}
{!isMinimized && (
<>
<div className="schema-popup__resize schema-popup__resize--n" onMouseDown={handleResizeMouseDown('n')} />
<div className="schema-popup__resize schema-popup__resize--s" onMouseDown={handleResizeMouseDown('s')} />
<div className="schema-popup__resize schema-popup__resize--e" onMouseDown={handleResizeMouseDown('e')} />
<div className="schema-popup__resize schema-popup__resize--w" onMouseDown={handleResizeMouseDown('w')} />
<div className="schema-popup__resize schema-popup__resize--ne" onMouseDown={handleResizeMouseDown('ne')} />
<div className="schema-popup__resize schema-popup__resize--nw" onMouseDown={handleResizeMouseDown('nw')} />
<div className="schema-popup__resize schema-popup__resize--se" onMouseDown={handleResizeMouseDown('se')} />
<div className="schema-popup__resize schema-popup__resize--sw" onMouseDown={handleResizeMouseDown('sw')} />
</>
)}
</div>
);
};
export default SchemaElementPopup;

View file

@ -46,6 +46,7 @@ import {
isUniversalElement, isUniversalElement,
} from '../lib/schema-custodian-mapping'; } from '../lib/schema-custodian-mapping';
import { OntologyTermPopup } from '../components/ontology/OntologyTermPopup'; import { OntologyTermPopup } from '../components/ontology/OntologyTermPopup';
import { SchemaElementPopup, type SchemaElementType } from '../components/linkml/SchemaElementPopup';
import './LinkMLViewerPage.css'; import './LinkMLViewerPage.css';
/** /**
@ -1299,6 +1300,9 @@ const LinkMLViewerPage: React.FC = () => {
// State for ontology term popup (shows when clicking mapping tags like rico:Rule) // State for ontology term popup (shows when clicking mapping tags like rico:Rule)
const [ontologyPopupCurie, setOntologyPopupCurie] = useState<string | null>(null); const [ontologyPopupCurie, setOntologyPopupCurie] = useState<string | null>(null);
// State for schema element popup (shows when clicking class/slot/enum links in Imports/Exports sections)
const [schemaElementPopup, setSchemaElementPopup] = useState<{ name: string; type: SchemaElementType } | null>(null);
// Sync custodian filter to URL params // Sync custodian filter to URL params
useEffect(() => { useEffect(() => {
const currentParam = searchParams.get('custodian'); const currentParam = searchParams.get('custodian');
@ -1835,6 +1839,18 @@ const LinkMLViewerPage: React.FC = () => {
} }
}, [categories, setSearchParams]); }, [categories, setSearchParams]);
// Handler for when user clicks "Go to" in the schema element popup
const handleSchemaElementNavigate = useCallback((elementName: string, elementType: SchemaElementType) => {
if (elementType === 'class') {
navigateToClass(elementName);
} else if (elementType === 'enum') {
navigateToEnum(elementName);
} else if (elementType === 'slot') {
navigateToSlot(elementName);
}
setSchemaElementPopup(null);
}, [navigateToClass, navigateToEnum, navigateToSlot]);
// Track if initialization has already happened (prevents re-init on URL param changes) // Track if initialization has already happened (prevents re-init on URL param changes)
const isInitializedRef = useRef(false); const isInitializedRef = useRef(false);
@ -2470,7 +2486,7 @@ const LinkMLViewerPage: React.FC = () => {
<div className="linkml-viewer__imports-list"> <div className="linkml-viewer__imports-list">
<button <button
className="linkml-viewer__imports-link" className="linkml-viewer__imports-link"
onClick={() => navigateToClass(classImports[cls.name].parentClass!)} onClick={() => setSchemaElementPopup({ name: classImports[cls.name].parentClass!, type: 'class' })}
> >
{classImports[cls.name].parentClass} {classImports[cls.name].parentClass}
</button> </button>
@ -2486,7 +2502,7 @@ const LinkMLViewerPage: React.FC = () => {
<button <button
key={mixin} key={mixin}
className="linkml-viewer__imports-link" className="linkml-viewer__imports-link"
onClick={() => navigateToClass(mixin)} onClick={() => setSchemaElementPopup({ name: mixin, type: 'class' })}
> >
{mixin} {mixin}
</button> </button>
@ -3191,11 +3207,10 @@ const LinkMLViewerPage: React.FC = () => {
className="linkml-viewer__imports-link" className="linkml-viewer__imports-link"
onClick={() => { onClick={() => {
const range = slotImports[slot.name].rangeType!; const range = slotImports[slot.name].rangeType!;
if (range.isClass) { setSchemaElementPopup({
navigateToClass(range.name); name: range.name,
} else if (range.isEnum) { type: range.isClass ? 'class' : 'enum'
navigateToEnum(range.name); });
}
}} }}
> >
{slotImports[slot.name].rangeType!.name} {slotImports[slot.name].rangeType!.name}
@ -3214,11 +3229,10 @@ const LinkMLViewerPage: React.FC = () => {
key={type.name} key={type.name}
className="linkml-viewer__imports-link" className="linkml-viewer__imports-link"
onClick={() => { onClick={() => {
if (type.isClass) { setSchemaElementPopup({
navigateToClass(type.name); name: type.name,
} else if (type.isEnum) { type: type.isClass ? 'class' : 'enum'
navigateToEnum(type.name); });
}
}} }}
> >
{type.name} {type.name}
@ -3267,7 +3281,7 @@ const LinkMLViewerPage: React.FC = () => {
<button <button
key={cls} key={cls}
className="linkml-viewer__exports-link" className="linkml-viewer__exports-link"
onClick={() => navigateToClass(cls)} onClick={() => setSchemaElementPopup({ name: cls, type: 'class' })}
> >
{cls} {cls}
</button> </button>
@ -3284,7 +3298,7 @@ const LinkMLViewerPage: React.FC = () => {
<button <button
key={cls} key={cls}
className="linkml-viewer__exports-link" className="linkml-viewer__exports-link"
onClick={() => navigateToClass(cls)} onClick={() => setSchemaElementPopup({ name: cls, type: 'class' })}
title={`Overrides: ${overrides.join(', ')}`} title={`Overrides: ${overrides.join(', ')}`}
> >
{cls} <span className="linkml-viewer__exports-via">{overrides.length} {overrides.length === 1 ? 'override' : 'overrides'}</span> {cls} <span className="linkml-viewer__exports-via">{overrides.length} {overrides.length === 1 ? 'override' : 'overrides'}</span>

View file

@ -1,5 +1,5 @@
{ {
"generated": "2026-01-14T14:05:38.322Z", "generated": "2026-01-14T14:13:07.428Z",
"schemaRoot": "/schemas/20251121/linkml", "schemaRoot": "/schemas/20251121/linkml",
"totalFiles": 2884, "totalFiles": 2884,
"categoryCounts": { "categoryCounts": {

View file

@ -0,0 +1,172 @@
id: https://nde.nl/ontology/hc/class/AudioEventSegment
name: audio_event_segment_class
title: Audio Event Segment Class
description: |
A temporal segment of audio containing a detected audio event (speech, music, silence, etc.).
MIGRATED from audio_event_segments slot (Rule 53).
Uses generic has_or_had_segment slot with range narrowed to AudioEventSegment.
imports:
- linkml:types
- ../slots/start_seconds
- ../slots/end_seconds
- ../slots/start_time
- ../slots/end_time
- ../slots/segment_index
- ../slots/segment_text
- ../slots/confidence
- ../slots/specificity_annotation
- ../slots/template_specificity
- ./SpecificityAnnotation
- ./TemplateSpecificityScores
- ../enums/AudioEventTypeEnum
prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
crm: http://www.cidoc-crm.org/cidoc-crm/
oa: http://www.w3.org/ns/oa#
ma: http://www.w3.org/ns/ma-ont#
default_prefix: hc
classes:
AudioEventSegment:
class_uri: hc:AudioEventSegment
description: |
A temporal segment of audio containing a detected audio event.
**DEFINITION**:
AudioEventSegment represents a bounded temporal portion of audio content
where a specific type of audio event has been detected. This includes:
- Speech segments (with optional speaker/language info)
- Music segments (with optional genre/type info)
- Silence segments (gaps between audio)
- Sound event segments (applause, laughter, ambient sounds)
- Noise segments (for quality assessment)
**RELATIONSHIP TO VideoTimeSegment**:
AudioEventSegment is a specialized sibling of VideoTimeSegment:
- Both extend CIDOC-CRM E52_Time-Span concept
- VideoTimeSegment: general video temporal segments
- AudioEventSegment: audio-specific event segments
**AUDIO EVENT TYPES**:
| Event Type | Description | Example |
|------------|-------------|---------|
| SPEECH | Human speech detected | Interview segment |
| MUSIC | Music detected | Background soundtrack |
| SILENCE | Very low or no audio | Gap between segments |
| SOUND_EVENT | Non-speech/music sounds | Applause, footsteps |
| NOISE | Noise/interference | Quality issue marker |
| MIXED | Multiple event types | Overlapping audio |
**HERITAGE USE CASES**:
| Content Type | Application |
|--------------|-------------|
| Oral histories | Speech segment identification |
| Virtual tours | Background music detection |
| Lecture recordings | Audience reaction segments |
| Conservation videos | Narration vs ambient sound |
| Archival footage | Audio quality assessment |
**PROVENANCE**:
Created as part of slot migration (Rule 53) from deprecated
`audio_event_segments` slot to generic `has_or_had_segment` pattern.
exact_mappings:
- hc:AudioEventSegment
close_mappings:
- crm:E52_Time-Span
- ma:MediaFragment
related_mappings:
- oa:FragmentSelector
slots:
- start_seconds
- end_seconds
- start_time
- end_time
- segment_index
- segment_text
- confidence
- specificity_annotation
- template_specificity
attributes:
audio_event_type:
range: AudioEventTypeEnum
required: true
description: The type of audio event detected in this segment.
examples:
- value: SPEECH
description: Speech detected in this segment
- value: MUSIC
description: Music detected in this segment
slot_usage:
start_seconds:
range: float
required: true
minimum_value: 0.0
description: Start time in seconds for this audio event segment.
examples:
- value: 0.0
description: Audio event starts at beginning
- value: 45.5
description: Audio event starts at 45.5 seconds
end_seconds:
range: float
required: true
minimum_value: 0.0
description: End time in seconds for this audio event segment.
examples:
- value: 15.0
description: Audio event ends at 15 seconds
- value: 60.0
description: Audio event ends at 1 minute
start_time:
range: string
required: false
pattern: "^PT(\\d+H)?(\\d+M)?(\\d+(\\.\\d+)?S)?$"
description: Start time in ISO 8601 duration format.
examples:
- value: PT0M30S
description: 30 seconds from start
end_time:
range: string
required: false
pattern: "^PT(\\d+H)?(\\d+M)?(\\d+(\\.\\d+)?S)?$"
description: End time in ISO 8601 duration format.
examples:
- value: PT0M45S
description: 45 seconds from start
segment_text:
range: string
required: false
description: Text content for this segment (e.g., speech transcript, music description).
examples:
- value: "Welcome to the Rijksmuseum"
description: Speech transcript text
- value: "Classical background music"
description: Music segment description
confidence:
range: float
required: false
minimum_value: 0.0
maximum_value: 1.0
description: Confidence score (0.0-1.0) for the audio event detection.
examples:
- value: 0.95
description: High confidence detection
- value: 0.72
description: Medium confidence detection
comments:
- Audio event segment for speech, music, silence, sound event detection
- Temporal boundaries with start/end seconds (primary) and ISO 8601 (secondary)
- Confidence scoring for AI-generated detections
- Part of Rule 53 slot migration from audio_event_segments
see_also:
- https://www.w3.org/TR/media-frags/
- https://www.w3.org/ns/ma-ont

View file

@ -0,0 +1,92 @@
id: https://nde.nl/ontology/hc/class/BayNumber
name: bay_number_class
title: Bay Number Class
description: |
A storage bay or section identifier within a storage row.
MIGRATED from bay_number slot (Rule 53).
Uses generic has_or_had_identifier slot with range narrowed to BayNumber.
imports:
- linkml:types
- ../slots/identifier_value
- ../slots/specificity_annotation
- ../slots/template_specificity
- ./SpecificityAnnotation
- ./TemplateSpecificityScores
prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
crm: http://www.cidoc-crm.org/cidoc-crm/
default_prefix: hc
classes:
BayNumber:
class_uri: hc:BayNumber
description: |
An identifier for a storage bay or section within a row/aisle of a storage facility.
**DEFINITION**:
BayNumber represents a discrete location identifier within a storage system.
In heritage storage facilities, storage is typically organized hierarchically:
```
Storage Facility
└── Zone (environmental control)
└── Row/Aisle (physical corridor)
└── Bay/Section (THIS CLASS - vertical unit in row)
└── Shelf (horizontal level within bay)
└── Storage Unit (box, drawer, etc.)
```
**TYPICAL VALUES**:
| Format | Example | Description |
|--------|---------|-------------|
| Numeric | "3", "12" | Sequential bay numbers |
| Alphabetic | "A", "C", "AA" | Lettered bays |
| Mixed | "3A", "B2" | Combined formats |
| Descriptive | "North-3" | Location-based |
**HERITAGE USE CASES**:
| Institution Type | Bay Naming Convention |
|------------------|----------------------|
| Archives | Sequential numeric (1, 2, 3...) |
| Museums | Alphanumeric by collection area |
| Libraries | By call number range |
| Natural history | By specimen type |
**PROVENANCE**:
Created as part of slot migration (Rule 53) from deprecated
`bay_number` slot to generic `has_or_had_identifier` pattern.
exact_mappings:
- hc:BayNumber
close_mappings:
- crm:E42_Identifier
related_mappings:
- schema:identifier
slots:
- specificity_annotation
- template_specificity
attributes:
value:
range: string
required: true
description: The bay number/identifier value.
examples:
- value: "3"
description: Numeric bay number
- value: "C"
description: Alphabetic bay identifier
- value: "North-3"
description: Location-descriptive bay
comments:
- Storage bay identifier within a row/aisle
- Part of hierarchical storage location addressing
- Part of Rule 53 slot migration from bay_number
see_also:
- https://nde.nl/ontology/hc/StorageUnit

View file

@ -0,0 +1,97 @@
id: https://nde.nl/ontology/hc/class/BoxNumber
name: box_number_class
title: Box Number Class
description: |
A storage box number or position identifier on a shelf.
MIGRATED from box_number slot (Rule 53).
Uses generic has_or_had_identifier slot with range narrowed to BoxNumber.
imports:
- linkml:types
- ../slots/identifier_value
- ../slots/specificity_annotation
- ../slots/template_specificity
- ./SpecificityAnnotation
- ./TemplateSpecificityScores
prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
crm: http://www.cidoc-crm.org/cidoc-crm/
default_prefix: hc
classes:
BoxNumber:
class_uri: hc:BoxNumber
description: |
An identifier for a storage box or its position on a shelf.
**DEFINITION**:
BoxNumber represents the position or identifier of a storage box within
a storage unit hierarchy. Archive boxes are the most common physical
containers for heritage materials, particularly in archives.
```
Shelf
└── Box 1 (THIS CLASS - position on shelf)
└── Box 2
└── Box 3
...
```
**TYPICAL VALUES**:
| Type | Example | Description |
|------|---------|-------------|
| Sequential | 1, 2, 3, 12 | Position on shelf left-to-right |
| Inventory | 145, 2024-0042 | Unique box inventory number |
| Combined | 12.3 | Bay 12, Box 3 |
**ARCHIVE BOX STANDARDS**:
Heritage institutions typically use acid-free archive boxes conforming to:
- ISO 16245 (Boxes for documents)
- ANSI/NISO Z39.77 (Guidelines for materials in archives)
**HERITAGE USE CASES**:
| Material Type | Box Format |
|---------------|------------|
| Documents | Standard archive box (legal/letter) |
| Photographs | Photo storage boxes |
| Oversized | Flat boxes, tubes |
| Fragile | Custom padded boxes |
**PROVENANCE**:
Created as part of slot migration (Rule 53) from deprecated
`box_number` slot to generic `has_or_had_identifier` pattern.
exact_mappings:
- hc:BoxNumber
close_mappings:
- crm:E42_Identifier
related_mappings:
- schema:identifier
slots:
- specificity_annotation
- template_specificity
attributes:
value:
range: integer
required: true
minimum_value: 1
description: The box number (position on shelf or inventory number).
examples:
- value: 12
description: Box at position 12 on shelf
- value: 145
description: Archive box inventory number 145
comments:
- Storage box position identifier
- Typically integer representing shelf position or inventory number
- Part of Rule 53 slot migration from box_number
see_also:
- https://nde.nl/ontology/hc/StorageUnit
- https://www.wikidata.org/wiki/Q854619

View file

@ -16,7 +16,10 @@ imports:
- ../slots/budget_currency - ../slots/budget_currency
- ../slots/has_or_had_description - ../slots/has_or_had_description
- ../slots/has_or_had_label - ../slots/has_or_had_label
- ../slots/budget_status # REMOVED - migrated to has_or_had_status with range BudgetStatus (Rule 53)
# - ../slots/budget_status
- ../slots/has_or_had_status
- ./BudgetStatus
- ../slots/has_or_had_type - ../slots/has_or_had_type
- ../slots/capital_budget - ../slots/capital_budget
- ./BudgetType - ./BudgetType
@ -97,7 +100,9 @@ classes:
- budget_currency - budget_currency
- has_or_had_description - has_or_had_description
- has_or_had_label - has_or_had_label
- budget_status # MIGRATED from budget_status to has_or_had_status (Rule 53)
# - budget_status
- has_or_had_status
- has_or_had_type - has_or_had_type
- capital_budget - capital_budget
- digitization_budget - digitization_budget
@ -249,12 +254,25 @@ classes:
description: Agent (person/organization) that approved this budget description: Agent (person/organization) that approved this budget
range: string range: string
required: false required: false
budget_status: # MIGRATED from budget_status to has_or_had_status (Rule 53)
range: string # budget_status:
# range: string
# required: true
# examples:
# - value: ACTIVE
# description: Current fiscal year budget in effect
has_or_had_status:
description: |
MIGRATED from budget_status (Rule 53).
Current status of this budget in its lifecycle.
Uses BudgetStatus class for structured status tracking.
range: BudgetStatus
required: true required: true
examples: examples:
- value: ACTIVE - value: '{value: "ACTIVE", effective_date: "2024-01-01"}'
description: Current fiscal year budget in effect description: Budget currently in effect
- value: '{value: "DRAFT", effective_date: "2023-10-01"}'
description: Budget under development
revision_number: revision_number:
range: integer range: integer
required: false required: false
@ -319,7 +337,9 @@ classes:
endowment_draw: 5000000.0 endowment_draw: 5000000.0
approval_date: '2023-11-15' approval_date: '2023-11-15'
is_or_was_approved_by: Board of Directors is_or_was_approved_by: Board of Directors
budget_status: ACTIVE has_or_had_status:
value: ACTIVE
effective_date: '2024-01-01'
refers_to_custodian: https://nde.nl/ontology/hc/nl-nh-ams-m-rm-q190804 refers_to_custodian: https://nde.nl/ontology/hc/nl-nh-ams-m-rm-q190804
description: Major museum annual operating budget description: Major museum annual operating budget
- value: - value:
@ -341,6 +361,8 @@ classes:
internal_funding: 2500000.0 internal_funding: 2500000.0
approval_date: '2024-03-01' approval_date: '2024-03-01'
is_or_was_approved_by: Province of Noord-Holland is_or_was_approved_by: Province of Noord-Holland
budget_status: ACTIVE has_or_had_status:
value: ACTIVE
effective_date: '2024-04-01'
refers_to_custodian: https://nde.nl/ontology/hc/nl-nh-haa-a-nha refers_to_custodian: https://nde.nl/ontology/hc/nl-nh-haa-a-nha
description: Regional archive government-funded budget description: Regional archive government-funded budget

View file

@ -0,0 +1,105 @@
id: https://nde.nl/ontology/hc/class/BudgetStatus
name: budget_status_class
title: Budget Status Class
description: |
Status of a heritage custodian budget throughout its lifecycle.
MIGRATED from budget_status slot (Rule 53).
Uses generic has_or_had_status slot with range narrowed to BudgetStatus.
imports:
- linkml:types
- ../slots/specificity_annotation
- ../slots/template_specificity
- ./SpecificityAnnotation
- ./TemplateSpecificityScores
prefixes:
linkml: https://w3id.org/linkml/
hc: https://nde.nl/ontology/hc/
schema: http://schema.org/
dcterms: http://purl.org/dc/terms/
frapo: http://purl.org/cerif/frapo/
default_prefix: hc
classes:
BudgetStatus:
class_uri: hc:BudgetStatus
description: |
Status of a budget document throughout its lifecycle.
**DEFINITION**:
BudgetStatus represents the current state of a budget document
as it moves through the approval and execution lifecycle.
**BUDGET LIFECYCLE STAGES**:
```
DRAFT → PROPOSED → APPROVED → ACTIVE → REVISED → CLOSED
↓ ↓
REJECTED SUPERSEDED
```
**STATUS VALUES**:
| Status | Description | Typical Duration |
|--------|-------------|------------------|
| DRAFT | Under development | Weeks/months |
| PROPOSED | Submitted for approval | Days/weeks |
| APPROVED | Officially approved | Until fiscal start |
| ACTIVE | Currently in effect | Fiscal year |
| REVISED | Modified after approval | Variable |
| CLOSED | Fiscal period ended | Permanent |
| REJECTED | Not approved | Terminal |
| SUPERSEDED | Replaced by revision | Terminal |
**HERITAGE INSTITUTION CONTEXT**:
Heritage institution budgets typically follow these approval paths:
| Institution Type | Approval Authority |
|------------------|-------------------|
| Museum (stichting) | Board of Directors |
| Regional Archive | Provincial Government |
| National Library | Ministry of Culture |
| University Collection | University Board |
**PROVENANCE**:
Created as part of slot migration (Rule 53) from deprecated
`budget_status` slot to generic `has_or_had_status` pattern.
exact_mappings:
- hc:BudgetStatus
close_mappings:
- schema:status
related_mappings:
- dcterms:status
slots:
- specificity_annotation
- template_specificity
attributes:
value:
range: string
required: true
description: |
The budget status value.
Valid values: DRAFT, PROPOSED, APPROVED, ACTIVE, REVISED, CLOSED, REJECTED, SUPERSEDED
examples:
- value: ACTIVE
description: Budget currently in effect
- value: DRAFT
description: Budget under development
- value: CLOSED
description: Fiscal period ended
effective_date:
range: date
required: false
description: Date when this status became effective.
examples:
- value: "2024-01-01"
description: Status effective from start of year
comments:
- Budget lifecycle status tracking
- Supports audit trail of budget state changes
- Part of Rule 53 slot migration from budget_status
see_also:
- https://nde.nl/ontology/hc/Budget

View file

@ -17,8 +17,13 @@ imports:
- ../slots/unit_name - ../slots/unit_name
- ../slots/unit_type - ../slots/unit_type
- ../slots/capacity_item - ../slots/capacity_item
- ../slots/bay_number # REMOVED - migrated to has_or_had_identifier with range BayNumber (Rule 53)
- ../slots/box_number # - ../slots/bay_number
# REMOVED - migrated to has_or_had_identifier with range BoxNumber (Rule 53)
# - ../slots/box_number
- ../slots/has_or_had_identifier
- ./BayNumber
- ./BoxNumber
- ../slots/current_item_count - ../slots/current_item_count
- ../slots/drawer_number - ../slots/drawer_number
- ../slots/part_of_storage - ../slots/part_of_storage
@ -75,8 +80,10 @@ classes:
- hc:EnvironmentalZone - hc:EnvironmentalZone
- schema:Place - schema:Place
slots: slots:
- bay_number # MIGRATED from bay_number and box_number to has_or_had_identifier (Rule 53)
- box_number # - bay_number
# - box_number
- has_or_had_identifier
- capacity_item - capacity_item
- current_item_count - current_item_count
- drawer_number - drawer_number
@ -124,11 +131,22 @@ classes:
- value: A - value: A
- value: '12' - value: '12'
- value: North-3 - value: North-3
bay_number: has_or_had_identifier:
description: |
MIGRATED from bay_number and box_number (Rule 53).
Storage location identifiers including bay and box numbers.
Use BayNumber for bay/section identifiers, BoxNumber for box positions.
range: string range: string
multivalued: true
examples: examples:
- value: '3' - value: '[{type: BayNumber, value: "3"}, {type: BoxNumber, value: 12}]'
- value: C description: Bay 3, Box 12
# DEPRECATED - use has_or_had_identifier with range BayNumber
# bay_number:
# range: string
# examples:
# - value: '3'
# - value: C
shelf_number: shelf_number:
range: integer range: integer
examples: examples:
@ -138,10 +156,11 @@ classes:
range: integer range: integer
examples: examples:
- value: 3 - value: 3
box_number: # DEPRECATED - use has_or_had_identifier with range BoxNumber
range: integer # box_number:
examples: # range: integer
- value: 12 # examples:
# - value: 12
capacity_item: capacity_item:
range: integer range: integer
current_item_count: current_item_count:

View file

@ -5,7 +5,10 @@ imports:
- linkml:types - linkml:types
- ./VideoAnnotation - ./VideoAnnotation
- ./VideoTimeSegment - ./VideoTimeSegment
- ../slots/audio_event_segments - ./AudioEventSegment
# REMOVED - migrated to has_or_had_segment with range AudioEventSegment (Rule 53)
# - ../slots/audio_event_segments
- ../slots/has_or_had_segment
- ../slots/has_audio_quality_score - ../slots/has_audio_quality_score
- ../slots/diarization_confidence - ../slots/diarization_confidence
- ../slots/diarization_enabled - ../slots/diarization_enabled
@ -112,7 +115,9 @@ classes:
- wikidata:Q11028 - wikidata:Q11028
- wikidata:Q638 - wikidata:Q638
slots: slots:
- audio_event_segments # MIGRATED from audio_event_segments to has_or_had_segment (Rule 53)
# - audio_event_segments
- has_or_had_segment
- audio_quality_score - audio_quality_score
- diarization_enabled - diarization_enabled
- has_or_had_diarization_segment - has_or_had_diarization_segment
@ -138,6 +143,20 @@ classes:
- has_or_had_speech_segment - has_or_had_speech_segment
- template_specificity - template_specificity
slot_usage: slot_usage:
has_or_had_segment:
description: |
MIGRATED from audio_event_segments (Rule 53).
Audio event segments detected in the video content.
range: AudioEventSegment
multivalued: true
required: false
inlined_as_list: true
examples:
- value: '[{audio_event_type: SPEECH, start_seconds: 0.0, end_seconds: 15.0, segment_text: "Speech detected - Speaker 1", confidence: 0.95}]'
description: Speech detection segment
- value: '[{audio_event_type: MUSIC, start_seconds: 30.0, end_seconds: 60.0, segment_text: "Background classical music", confidence: 0.88}]'
description: Music detection segment
# NOTE: has_audio_event_segment is deprecated - use has_or_had_segment above
has_audio_event_segment: has_audio_event_segment:
range: VideoTimeSegment range: VideoTimeSegment
multivalued: true multivalued: true

View file

@ -546,10 +546,14 @@ fixes:
- original_slot_id: https://nde.nl/ontology/hc/slot/audio_event_segments - original_slot_id: https://nde.nl/ontology/hc/slot/audio_event_segments
processed: processed:
status: false status: true
timestamp: null timestamp: "2026-01-14T10:30:00Z"
session: null session: "slot-migration-session-8"
notes: "Maps to existing AudioEventSegment class" notes: |
MIGRATED: audio_event_segments → has_or_had_segment + AudioEventSegment
- Created AudioEventSegment.yaml class (hc:AudioEventSegment)
- Updated VideoAudioAnnotation.yaml: imports, slots, slot_usage
- Archived to modules/slots/archive/audio_event_segments_archived_20260114.yaml
revision: revision:
- label: has_or_had_segment - label: has_or_had_segment
type: slot type: slot
@ -686,10 +690,14 @@ fixes:
- original_slot_id: https://nde.nl/ontology/hc/slot/bay_number - original_slot_id: https://nde.nl/ontology/hc/slot/bay_number
processed: processed:
status: false status: true
timestamp: null timestamp: "2026-01-14T10:45:00Z"
session: null session: "slot-migration-session-8"
notes: "Requires BayNumber class creation" notes: |
MIGRATED: bay_number → has_or_had_identifier + BayNumber
- Created BayNumber.yaml class (hc:BayNumber)
- Updated StorageUnit.yaml: imports, slots, slot_usage
- Archived to modules/slots/archive/bay_number_archived_20260114.yaml
revision: revision:
- label: has_or_had_identifier - label: has_or_had_identifier
type: slot type: slot
@ -939,10 +947,14 @@ fixes:
- original_slot_id: https://nde.nl/ontology/hc/slot/box_number - original_slot_id: https://nde.nl/ontology/hc/slot/box_number
processed: processed:
status: false status: true
timestamp: null timestamp: "2026-01-14T10:45:00Z"
session: null session: "slot-migration-session-8"
notes: "Requires BoxNumber class creation" notes: |
MIGRATED: box_number → has_or_had_identifier + BoxNumber
- Created BoxNumber.yaml class (hc:BoxNumber)
- Updated StorageUnit.yaml: imports, slots, slot_usage
- Archived to modules/slots/archive/box_number_archived_20260114.yaml
revision: revision:
- label: has_or_had_identifier - label: has_or_had_identifier
type: slot type: slot