Skip to content

search: include bare rdf:Property entities (currently invisible to search_entities) #121

@JohnRDOrazio

Description

@JohnRDOrazio

Summary

Follow-up to #117. search_entities() iterates only the three OWL property subkinds (OWL.ObjectProperty, OWL.DatatypeProperty, OWL.AnnotationProperty). Properties typed as bare rdf:Property — common in pre-OWL ontologies, RDFS-only vocabularies, and loosely-typed legacy data — are silently excluded from search results.

Current code

ontokit/services/ontology.py (after #117):

type_mapping: dict[str, list[tuple[URIRef, str, str | None]]] = {
    "class": [(OWL.Class, "class", None)],
    "property": [
        (OWL.ObjectProperty, "property", "object"),
        (OWL.DatatypeProperty, "property", "data"),
        (OWL.AnnotationProperty, "property", "annotation"),
    ],
    "individual": [(OWL.NamedIndividual, "individual", None)],
}

A property declared as :hasFoo rdf:type rdf:Property (no OWL subtype) is invisible to the entity search endpoint and won't show up in any frontend autocomplete, picker, or tree.

Why it matters

  • Pre-OWL ontologies (RDF/RDFS-only) declare every property as rdf:Property.
  • Many SKOS-style or hybrid vocabularies use bare rdf:Property for properties they don't want to commit to a specific OWL kind.
  • Some imported third-party vocabularies are loosely typed.

Proposed fix

Add (RDF.Property, "property", None) to the property mapping, then dedupe results by IRI so a property typed as both owl:ObjectProperty AND rdf:Property (belt-and-suspenders, not uncommon) isn't emitted twice. The OWL-subkind result wins; the bare rdf:Property result is dropped.

property_kind=None on the bare-rdf:Property results is already the shape the schema's defensive fallback was built for — see #117.

Sketch

type_mapping["property"].append((RDF.Property, "property", None))

# After the loop, dedupe: keep the entry with non-None property_kind
# when an IRI appears multiple times.
seen: dict[str, EntitySearchResult] = {}
for r in results:
    existing = seen.get(r.iri)
    if existing is None or (existing.property_kind is None and r.property_kind is not None):
        seen[r.iri] = r
results = list(seen.values())

Acceptance

  • A property declared as :hasFoo rdf:type rdf:Property (no OWL subtype) appears in search_entities results
  • The result's property_kind is None
  • A property typed as both owl:ObjectProperty AND rdf:Property is emitted exactly once with property_kind="object" (no duplicate with None)
  • Existing tests for OWL subkinds still pass unchanged
  • New tests cover both the bare-rdf:Property case and the dedupe case

Out of scope

  • Other RDF property characteristics (owl:FunctionalProperty, TransitiveProperty, etc.) — those are modifiers, not kinds; they decorate an existing OWL subtype rather than replace it
  • owl:OntologyProperty (mostly built-in vocabulary, rarely user-defined)
  • owl:DeprecatedProperty (a flag, not a kind — already surfaced via the deprecated field on EntitySearchResult)

Origin

Surfaced during review of #117 / PR #120 — the defensive property_kind=None fallback was added there but doesn't actually fire today because the iteration is keyed on the three OWL subkinds. This issue closes that gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions