# Enum-to-Class Principle: Single Source of Truth **Version**: 1.0 **Last Updated**: 2025-12-06 **Status**: Active Rule --- ## Core Principle **Enums are TEMPORARY scaffolding. Once an enum is promoted to a class hierarchy, the enum MUST be deleted to maintain a Single Source of Truth.** --- ## Rationale ### The Problem: Dual Representation When both an enum AND a class hierarchy exist for the same concept: - **Data sync issues**: Enum values and class names can drift apart - **Maintenance burden**: Changes must be made in two places - **Developer confusion**: Which one should I use? - **Validation conflicts**: Enum constraints vs class ranges may diverge ### The Solution: Single Source of Truth - **Enums**: Use for simple, fixed value constraints (e.g., `DataTierEnum: TIER_1, TIER_2, TIER_3, TIER_4`) - **Classes**: Use when the concept needs properties, relationships, or rich documentation - **NEVER BOTH**: Once promoted to classes, DELETE the enum --- ## When to Promote Enum to Classes **Promote when the concept needs**: | Need | Enum Can Do? | Class Required? | |------|-------------|-----------------| | Fixed value constraint | ✅ Yes | ✅ Yes | | Properties (e.g., `role_category`, `typical_domains`) | ❌ No | ✅ Yes | | Rich description per value | Limited | ✅ Yes | | Relationships to other entities | ❌ No | ✅ Yes | | Inheritance hierarchy | ❌ No | ✅ Yes | | Independent identity (URI) | Limited | ✅ Yes | | Ontology class mapping (`class_uri`) | Via `meaning` | ✅ Native | **Rule of thumb**: If you're adding detailed documentation to each enum value, or want to attach properties, it's time to promote to classes. --- ## Promotion Workflow ### Step 1: Create Class Hierarchy ```yaml # modules/classes/StaffRole.yaml (base class) StaffRole: abstract: true description: Base class for staff role categories slots: - role_id - role_name - role_category - typical_domains # modules/classes/StaffRoles.yaml (subclasses) Curator: is_a: StaffRole description: Museum curator specializing in collection research... Conservator: is_a: StaffRole description: Conservator specializing in preservation... ``` ### Step 2: Update Slot Ranges ```yaml # BEFORE (enum) staff_role: range: StaffRoleTypeEnum # AFTER (class) staff_role: range: StaffRole ``` ### Step 3: Update Modular Schema Imports ```yaml # REMOVE enum import # - modules/enums/StaffRoleTypeEnum # DELETED # ADD class imports - modules/classes/StaffRole - modules/classes/StaffRoles ``` ### Step 4: Archive the Enum ```bash mkdir -p schemas/.../archive/enums mv modules/enums/OldEnum.yaml archive/enums/OldEnum.yaml.archived_$(date +%Y%m%d) ``` ### Step 5: Document the Change - Update `archive/enums/README.md` with migration entry - Add comment in modular schema explaining removal - Update any documentation referencing the old enum --- ## Example: StaffRoleTypeEnum → StaffRole **Before** (2025-12-05): ```yaml # StaffRoleTypeEnum.yaml StaffRoleTypeEnum: permissible_values: CURATOR: description: Museum curator CONSERVATOR: description: Conservator # ... 51 values with limited documentation ``` **After** (2025-12-06): ```yaml # StaffRole.yaml (abstract base) StaffRole: abstract: true slots: - role_id - role_name - role_category - typical_domains - typical_responsibilities - requires_qualification # StaffRoles.yaml (51 subclasses) Curator: is_a: StaffRole class_uri: schema:curator description: | Museum curator specializing in collection research... **IMPORTANT - FORMAL TITLE vs DE FACTO WORK**: This is the OFFICIAL job appellation/title. Actual work may differ. slot_usage: role_category: equals_string: CURATORIAL typical_domains: equals_expression: "[Museums, Galleries]" ``` **Why the promotion?** 1. Need to distinguish FORMAL TITLE from DE FACTO WORK 2. Each role has `role_category`, `common_variants`, `typical_domains`, `typical_responsibilities` 3. Roles benefit from inheritance (`Curator is_a StaffRole`) 4. Richer documentation per role --- ## Enums That Should REMAIN Enums Some enums are appropriate as permanent fixtures: | Enum | Why Keep as Enum | |------|------------------| | `DataTierEnum` | Simple 4-value tier (TIER_1 through TIER_4), no properties needed | | `DataSourceEnum` | Fixed source types, simple strings | | `CountryCodeEnum` | ISO 3166-1 standard, no custom properties | | `LanguageCodeEnum` | ISO 639 standard, no custom properties | **Characteristics of "permanent" enums**: - Based on external standards (ISO, etc.) - Simple values with no need for properties - Unlikely to require rich per-value documentation - Used purely for validation/constraint --- ## Anti-Patterns ### ❌ WRONG: Keep Both Enum and Classes ```yaml # modules/enums/StaffRoleTypeEnum.yaml # ← Still exists! # modules/classes/StaffRole.yaml # ← Also exists! # Which one is authoritative? CONFUSION! ``` ### ❌ WRONG: Create Classes but Keep Enum "for backwards compatibility" ```yaml # "Let's keep the enum for old code" # Result: Two sources of truth, guaranteed drift ``` ### ✅ CORRECT: Delete Enum After Creating Classes ```yaml # modules/enums/StaffRoleTypeEnum.yaml # ← ARCHIVED # modules/classes/StaffRole.yaml # ← Single source of truth # modules/classes/StaffRoles.yaml # ← All 51 role subclasses ``` --- ## Verification Checklist After promoting an enum to classes: - [ ] Old enum file moved to `archive/enums/` - [ ] Modular schema import removed for enum - [ ] Modular schema import added for new class(es) - [ ] All slot ranges updated from enum to class - [ ] No grep results for old enum name in active schema files - [ ] `archive/enums/README.md` updated with migration entry - [ ] Comment added in modular schema explaining removal ```bash # Verify enum is fully removed (should return only archive hits) grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/ ``` --- ## See Also - `docs/ENUM_CLASS_SINGLE_SOURCE.md` - Extended documentation - `schemas/20251121/linkml/archive/enums/README.md` - Archive directory - LinkML documentation on enums: https://linkml.io/linkml/schemas/enums.html - LinkML documentation on classes: https://linkml.io/linkml/schemas/models.html