200 lines
4.9 KiB
Markdown
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 |
|