# Rule 54: Broaden Generic Predicate Ranges Instead of Creating Bespoke Predicates 🚨 **CRITICAL**: When fixing gen-owl "Ambiguous type" warnings, **broaden the range of generic predicates** rather than creating specialized bespoke predicates. ## The Problem gen-owl "Ambiguous type" warnings occur when a slot is used as both: - **DatatypeProperty** (base range: `string`, `integer`, `uri`, etc.) - **ObjectProperty** (slot_usage override range: a class like `Description`, `SubtitleFormatEnum`) This creates OWL ambiguity because OWL requires properties to be either DatatypeProperty OR ObjectProperty, not both. ## ❌ WRONG Approach: Create Bespoke Predicates ```yaml # DON'T DO THIS - creates proliferation of rare-use predicates slots: has_or_had_subtitle_format: # Only used by VideoSubtitle range: SubtitleFormatEnum has_or_had_transcript_format: # Only used by VideoTranscript range: TranscriptFormat ``` **Why This Is Wrong**: - Creates **predicate proliferation** (schema bloat) - Bespoke predicates are **rarely reused** across classes - **Increases cognitive load** for schema users - **Fragments the ontology** unnecessarily - Violates the principle of schema parsimony ## ✅ CORRECT Approach: Broaden Generic Predicate Ranges ```yaml # DO THIS - make the generic predicate flexible enough slots: has_or_had_format: range: uriorcurie # Broadened from string description: | The format of a resource. Classes narrow this to specific enum types (SubtitleFormatEnum, TranscriptFormatEnum) via slot_usage. ``` Then in class files, use `slot_usage` to narrow the range: ```yaml classes: VideoSubtitle: slots: - has_or_had_format slot_usage: has_or_had_format: range: SubtitleFormatEnum # Narrowed for this class required: true ``` ## Range Broadening Options | Original Range | Broadened Range | When to Use | |----------------|-----------------|-------------| | `string` | `uriorcurie` | When class overrides use URI-identified types or enums | | `string` | `Any` | When truly polymorphic (strings AND class instances) | | Specific class | Common base class | When multiple subclasses are used | ## Decision Tree ``` gen-owl warning: "Ambiguous type for: SLOTNAME" ↓ Is base slot range a primitive (string, integer, uri)? ├─ YES → Broaden to uriorcurie or Any │ - Edit modules/slots/SLOTNAME.yaml │ - Change range: string → range: uriorcurie │ - Document change with Rule 54 reference │ - Keep class-level slot_usage overrides (they narrow the range) │ └─ NO → Consider if base slot needs common ancestor class - Create abstract base class if needed - Or broaden to uriorcurie ``` ## Implementation Workflow 1. **Identify warning**: `gen-owl ... 2>&1 | grep "Ambiguous type for:"` 2. **Check base slot range**: ```bash cat modules/slots/SLOTNAME.yaml | grep -A5 "^slots:" | grep "range:" ``` 3. **Find class overrides**: ```bash for f in modules/classes/*.yaml; do grep -l "SLOTNAME" "$f" && grep -A3 "SLOTNAME:" "$f" | grep "range:" done ``` 4. **Broaden base range**: - Edit `modules/slots/SLOTNAME.yaml` - Change `range: string` → `range: uriorcurie` - Add annotation documenting the change 5. **Verify fix**: Run gen-owl and confirm warning is gone 6. **Keep slot_usage overrides**: Class-level range narrowing is fine and expected ## Examples ### Example 1: has_or_had_format **Before (caused warning)**: ```yaml # Base slot slots: has_or_had_format: range: string # DatatypeProperty # Class override classes: VideoSubtitle: slot_usage: has_or_had_format: range: SubtitleFormatEnum # ObjectProperty → CONFLICT! ``` **After (fixed)**: ```yaml # Base slot - broadened slots: has_or_had_format: range: uriorcurie # Now ObjectProperty-compatible # Class override - unchanged, still narrows classes: VideoSubtitle: slot_usage: has_or_had_format: range: SubtitleFormatEnum # Valid narrowing ``` ### Example 2: has_or_had_hypernym **Before**: `range: string` (DatatypeProperty) **After**: `range: uriorcurie` (ObjectProperty-compatible) Classes that override to class ranges now work without ambiguity. ## Validation After broadening, run: ```bash gen-owl 01_custodian_name_modular.yaml 2>&1 | grep "Ambiguous type for: SLOTNAME" ``` The warning should disappear without creating new predicates. ## Anti-Patterns to Avoid | ❌ Anti-Pattern | ✅ Correct Pattern | |----------------|-------------------| | Create `has_or_had_subtitle_format` | Broaden `has_or_had_format` to `uriorcurie` | | Create `has_or_had_entity_type` | Broaden `has_or_had_type` to `uriorcurie` | | Create `has_or_had_X_label` | Broaden `has_or_had_label` to `uriorcurie` | | Create `has_or_had_X_status` | Broaden `has_or_had_status` to `uriorcurie` | ## Rationale This approach: 1. **Reduces schema complexity** - Fewer predicates to understand 2. **Promotes reuse** - Generic predicates work across domains 3. **Maintains OWL consistency** - Single property type per predicate 4. **Preserves type safety** - slot_usage still enforces class-specific ranges 5. **Follows semantic web best practices** - Broad predicates, narrow contexts ## See Also - Rule 38: Slot Centralization and Semantic URI Requirements - Rule 39: Slot Naming Convention (RiC-O Style) - Rule 49: Slot Usage Minimization - LinkML Documentation: [slot_usage](https://linkml.io/linkml-model/latest/docs/slot_usage/)