glam/docs/ENUM_CLASS_SINGLE_SOURCE.md
2025-12-07 00:26:01 +01:00

200 lines
4.9 KiB
Markdown

# Enum vs Class: Single Source of Truth Principle
**Document Type**: Design Decision
**Status**: Active
**Last Updated**: 2025-12-06
---
## Executive Summary
This project follows a strict **Single Source of Truth** principle for schema elements. When a concept transitions from an enum (simple value list) to a class hierarchy (rich structure with properties), the original enum **MUST be deleted** to prevent dual representation.
---
## Background
### What are Enums?
LinkML enums define constrained value sets:
```yaml
DataTierEnum:
permissible_values:
TIER_1_AUTHORITATIVE:
description: Authoritative source
TIER_2_VERIFIED:
description: Verified data
```
**Enums are good for**:
- Simple value constraints
- Values based on external standards (ISO codes)
- Values that don't need properties or relationships
### What are Classes?
LinkML classes define structured entities with properties:
```yaml
Curator:
is_a: StaffRole
class_uri: schema:curator
slots:
- role_category
- typical_domains
- common_variants
```
**Classes are good for**:
- Concepts needing properties
- Inheritance hierarchies
- Rich documentation per value
- Relationships to other entities
---
## The Rule
> **When an enum is promoted to a class hierarchy, the enum MUST be deleted.**
### Why This Matters
| Scenario | Single Source | Dual Source |
|----------|--------------|-------------|
| Where is the definition? | One place | Two places (confusion) |
| Where do I make changes? | One file | Two files (maintenance burden) |
| Can they drift apart? | No | Yes (bugs) |
| Which is authoritative? | Clear | Ambiguous |
---
## Decision Workflow
```
Is this concept a simple value constraint?
├─ YES → Use enum
│ Examples: DataTierEnum, CountryCodeEnum
└─ NO, it needs properties/relationships/rich docs
Create class hierarchy
Update slot ranges: enum → class
DELETE the enum (archive it)
Document the migration
```
---
## Case Study: StaffRoleTypeEnum → StaffRole
### Before (Enum)
```yaml
# StaffRoleTypeEnum.yaml
StaffRoleTypeEnum:
permissible_values:
CURATOR:
description: Museum curator
CONSERVATOR:
description: Conservator
```
**Limitations**:
- Can't distinguish formal title from de facto work
- Can't add `role_category`, `typical_domains`, `common_variants`
- Can't express inheritance (e.g., `DataScientist is_a DataRole`)
### After (Classes)
```yaml
# StaffRole.yaml (abstract base)
StaffRole:
abstract: true
description: |
Base class for staff role categories.
**IMPORTANT**: These are FORMAL job appellations/titles.
Actual de facto work may differ or stretch beyond these classifications.
slots:
- role_id
- role_name
- role_category
- typical_domains
- typical_responsibilities
# StaffRoles.yaml (51 subclasses)
Curator:
is_a: StaffRole
class_uri: schema:curator
slot_usage:
role_category:
equals_string: CURATORIAL
```
### Migration Steps Taken
1. Created `modules/classes/StaffRole.yaml` (base class with RoleCategoryEnum)
2. Created `modules/classes/StaffRoles.yaml` (51 specialized subclasses)
3. Updated `modules/slots/staff_role.yaml`: `range: StaffRoleTypeEnum``range: StaffRole`
4. Updated `modules/classes/PersonObservation.yaml` slot definition
5. Updated `01_custodian_name_modular.yaml` imports (removed enum, added classes)
6. Archived enum to `archive/enums/StaffRoleTypeEnum.yaml.archived_20251206`
---
## Enums That Remain Permanent
Not all enums should become classes. These remain as enums:
| Enum | Rationale |
|------|-----------|
| `DataTierEnum` | Simple 4-tier hierarchy, no properties needed |
| `DataSourceEnum` | Fixed source types, external classification |
| `InstitutionTypeEnum` | Matches GLAMORCUBESFIXPHDNT taxonomy, single-letter codes |
| `CountryCodeEnum` | ISO 3166-1 standard, external authority |
| `LanguageCodeEnum` | ISO 639 standard, external authority |
**Indicators of permanent enums**:
- Based on external standards with defined values
- Values are simple strings without properties
- No need for inheritance or relationships
- Used purely for validation constraints
---
## Verification
After any enum-to-class migration:
```bash
# Ensure enum is only in archive
grep -r "StaffRoleTypeEnum" schemas/20251121/linkml/modules/
# Should return NO results
# Verify classes are imported
grep "StaffRole" schemas/20251121/linkml/01_custodian_name_modular.yaml
# Should show class imports
```
---
## Related Documentation
- `.opencode/ENUM_TO_CLASS_PRINCIPLE.md` - Agent-facing rules
- `schemas/20251121/linkml/archive/enums/README.md` - Archive directory
- LinkML Enums: https://linkml.io/linkml/schemas/enums.html
- LinkML Classes: https://linkml.io/linkml/schemas/models.html
---
## Change Log
| Date | Change |
|------|--------|
| 2025-12-06 | Initial document created |
| 2025-12-06 | StaffRoleTypeEnum migrated to StaffRole class hierarchy |