Querying record harm

Example SPARQL Queries

This document provides example SPARQL queries for common use cases with the Record Harm Ontology.

Setup

from rdflib import Graph

g = Graph()
g.parse("ontology/record-harm-ontology.ttl", format="turtle")
g.parse("examples/example-harm-events.ttl", format="turtle")

Basic Queries

List All Prime Harms

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:PrimeHarm ;
          rdfs:label ?label .
}
ORDER BY ?label

List All Composite Harms with Dependencies

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?composite ?label ?dependency ?depLabel WHERE {
    ?composite a ex:CompositeHarm ;
               rdfs:label ?label ;
               ex:buildsUpon ?dependency .
    ?dependency rdfs:label ?depLabel .
}
ORDER BY ?label ?depLabel

Find Harms Targeting Specific Aspect

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?harm ?label ?definition WHERE {
    ?harm ex:targetsAspect ex:Authenticity ;
          rdfs:label ?label ;
          skos:definition ?definition .
}

Dependency Analysis

Find All Transitive Dependencies

v3.0: the dependency graph is currently flat — every composite builds directly on primes, maximum chain depth 1 — so ex:buildsUpon+ returns the same rows as ex:buildsUpon here. The two-hop chain that used to exist (Obfuscation → Suppression → Omission) was the defect fixed in v3.0. The + form is kept because it remains correct if a composite is ever given a composite dependency.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?composite ?label ?prime ?primeLabel WHERE {
    ?composite a ex:CompositeHarm ;
               rdfs:label ?label ;
               ex:buildsUpon+ ?prime .
    ?prime a ex:PrimeHarm ;
           rdfs:label ?primeLabel .
}
ORDER BY ?label ?primeLabel

Find Harms That Build Upon Omission

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm ex:buildsUpon ex:Omission ;
          rdfs:label ?label .
}

Count Underlying Prime Harms

Counts the distinct prime harms in each composite's transitive closure (how many primes it ultimately rests on) — not path depth / longest chain, which would need a recursive longest-path query.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label (COUNT(DISTINCT ?prime) as ?primeCount) WHERE {
    ?harm a ex:CompositeHarm ;
          rdfs:label ?label ;
          ex:buildsUpon+ ?prime .
    ?prime a ex:PrimeHarm .
}
GROUP BY ?harm ?label
ORDER BY DESC(?primeCount)

Record Location (bearer and nesting)

New in v3.1. Where a record lives is stated with ex:bearer, and what it is made of with ex:hasElement. Neither needed a new harm type — the inside-agent / inside-community distinction is a property of the record, not a dimension of harm.

Inside-agent vs inside-community records

A record borne by exactly one agent resides inside that agent; one borne by several, or by a collective agent, resides in a community.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?record ?label (COUNT(DISTINCT ?bearer) as ?bearers) WHERE {
    ?record a ex:Record ;
            rdfs:label ?label ;
            ex:bearer ?bearer .
}
GROUP BY ?record ?label
ORDER BY ?bearers

Bearer count alone cannot tell a single individual bearer from a single collective one — a parish register borne by the parish returns 1, same as a private memory. That is deliberate: per the note on ex:Agent, the individual/collective distinction is left to an external vocabulary, so add ?bearer a foaf:Group (or prov:Organization) to split them.

Self-directed harm (repression, self-deception)

The perpetrator of the event is also the bearer of the record it harms. Inexpressible before ex:bearer existed.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?event ?eventLabel ?harmType ?agent WHERE {
    ?event a ex:HarmEvent ;
           ex:perpetrator ?agent ;
           ex:harms ?record ;
           ex:ofType ?harmType .
    ?record ex:bearer ?agent .
    OPTIONAL { ?event rdfs:label ?eventLabel }
}

Records nested inside a community register

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?whole ?wholeLabel ?element ?elementLabel WHERE {
    ?whole ex:hasElement+ ?element .
    OPTIONAL { ?whole rdfs:label ?wholeLabel }
    OPTIONAL { ?element rdfs:label ?elementLabel }
}

ex:hasElement is asymmetric and irreflexive but not transitive (the v2.3 precedent — transitivity would make it non-simple in OWL 2 DL and forbid those two guards), so nesting is walked with + exactly as ex:buildsUpon is. A nesting cycle deeper than two steps violates no declared OWL axiom and is caught instead by ex:RecordAcyclicShape in the shapes file.

Agent-to-community boundary: what a register leaves out

Records held by some agent that are not elements of a given register. Candidate ex:Omission from that register — the modeled form of refusing a record entry.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?record ?label ?bearer WHERE {
    ?record a ex:Record ;
            ex:bearer ?bearer .
    OPTIONAL { ?record rdfs:label ?label }
    FILTER NOT EXISTS { ex:ParishRegister ex:hasElement+ ?record }
    FILTER (?record != ex:ParishRegister)
}

This finds candidates, not harms. Most records a community does not hold were never owed to it; omission is a harm only where the record should have been part of the register, and nothing in this ontology supplies the "should" (see the scope boundary note at ex:RecordHarm).

Event Queries

List All Harm Events

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dc: <http://purl.org/dc/terms/>

SELECT ?event ?label ?type ?date ?severity WHERE {
    ?event a ex:HarmEvent ;
           rdfs:label ?label ;
           ex:ofType ?harmType ;
           dc:date ?date .
    ?harmType rdfs:label ?type .
    OPTIONAL { ?event ex:severity ?severity }
}
ORDER BY DESC(?date)

Find High-Severity Events

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?event ?label ?severity ?record WHERE {
    ?event a ex:HarmEvent ;
           rdfs:label ?label ;
           ex:severity ?severity ;
           ex:harms ?record .
    FILTER(?severity >= 8)
}
ORDER BY DESC(?severity)

Events by Perpetrator

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?perpetrator ?perpLabel (COUNT(?event) as ?eventCount) WHERE {
    ?event a ex:HarmEvent ;
           ex:perpetrator ?perpetrator .
    ?perpetrator rdfs:label ?perpLabel .
}
GROUP BY ?perpetrator ?perpLabel
ORDER BY DESC(?eventCount)

Timeline of Events Against Specific Record

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX dc: <http://purl.org/dc/terms/>

SELECT ?event ?label ?type ?date ?severity WHERE {
    ?event a ex:HarmEvent ;
           rdfs:label ?label ;
           ex:harms ex:AuditLog2026Q2 ;
           ex:ofType ?harmType ;
           dc:date ?date .
    ?harmType rdfs:label ?type .
    OPTIONAL { ?event ex:severity ?severity }
}
ORDER BY ?date

Pattern Analysis

Identify Potential Cover-up Patterns

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?record ?recordLabel 
       (COUNT(DISTINCT ?suppressionEvent) as ?suppressions)
       (COUNT(DISTINCT ?alterationEvent) as ?alterations)
       (COUNT(DISTINCT ?denialEvent) as ?denials)
WHERE {
    ?record a ex:Record ;
            rdfs:label ?recordLabel .
    
    OPTIONAL {
        ?suppressionEvent ex:ofType ex:Suppression ;
                         ex:harms ?record .
    }
    OPTIONAL {
        ?alterationEvent ex:ofType ex:Alteration ;
                        ex:harms ?record .
    }
    OPTIONAL {
        ?denialEvent ex:ofType ex:Denial ;
                    ex:harms ?record .
    }
}
GROUP BY ?record ?recordLabel
HAVING (?suppressions > 0 && ?alterations > 0 && ?denials > 0)

Aspect Analysis

Aspect Attack Surface

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?aspect ?aspectLabel (COUNT(DISTINCT ?harm) as ?harmCount) WHERE {
    ?harm ex:targetsAspect ?aspect .
    ?aspect skos:prefLabel ?aspectLabel .
}
GROUP BY ?aspect ?aspectLabel
ORDER BY DESC(?harmCount)

Aspect Coverage by Prime

The diagnostic that found the v3.0 defect. Every aspect should appear with at least one prime; an aspect whose primes column is empty means some composite is rooted in a prime that does not actually attack that aspect — before v3.0 ex:Accessibility came back empty, because ex:Suppression was a composite hanging off ex:Omission on an edge that contradicted both definitions.

scripts/validate.py runs this as a hard check (check_aspect_coverage); it is reproduced here because the grouped form is the one worth eyeballing when adding a harm or an aspect.

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?aspectLabel
       (GROUP_CONCAT(DISTINCT ?primeLabel; separator=", ") AS ?primes)
WHERE {
    ?aspect skos:inScheme ex:RecordAspectScheme ;
            skos:prefLabel ?aspectLabel .
    OPTIONAL {
        ?prime a ex:PrimeHarm ;
               ex:targetsAspect ?aspect ;
               rdfs:label ?primeLabel .
    }
}
GROUP BY ?aspectLabel
ORDER BY ?aspectLabel

OPTIONAL is load-bearing here: the point of the query is the aspects with no prime, and an inner join would silently drop exactly those rows. Note also that rdflib raises NotBoundError on GROUP_CONCAT(DISTINCT ...) over a variable left unbound by OPTIONAL, so with rdflib use COALESCE(?primeLabel, "NONE") inside the concat, or do the grouping in Python as check_aspect_coverage does.

Multi-Aspect Harms

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label (COUNT(?aspect) as ?aspectCount) WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label ;
          ex:targetsAspect ?aspect .
}
GROUP BY ?harm ?label
HAVING (?aspectCount > 1)
ORDER BY DESC(?aspectCount)

Detectability and Reversibility

Difficult to Detect Harms

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label ;
          ex:detectability ex:DifficultToDetect .
}

Irreversible Harms

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label ;
          ex:reversibility ex:Irreversible .
}

Risk Matrix (Detectability × Reversibility)

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>

SELECT ?harm ?label ?detectLabel ?reverseLabel WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label ;
          ex:detectability ?detect ;
          ex:reversibility ?reverse .
    ?detect skos:prefLabel ?detectLabel .
    ?reverse skos:prefLabel ?reverseLabel .
}
ORDER BY ?detectLabel ?reverseLabel

Validation Queries

Find Unclassified Harms

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label .
    FILTER NOT EXISTS { ?harm a ex:PrimeHarm }
    FILTER NOT EXISTS { ?harm a ex:CompositeHarm }
}

Find Composites Without Dependencies

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:CompositeHarm ;
          rdfs:label ?label .
    FILTER NOT EXISTS { ?harm ex:buildsUpon ?dep }
}

Find Harms Without Aspect Targeting

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?harm ?label WHERE {
    ?harm a ex:RecordHarm ;
          rdfs:label ?label .
    FILTER NOT EXISTS { ?harm ex:targetsAspect ?aspect }
}

Advanced Queries

Harm Propagation Analysis

Find all harms that could cascade from a prime harm:

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?prime ?primeLabel ?composite ?compositeLabel WHERE {
    ?prime a ex:PrimeHarm ;
           rdfs:label ?primeLabel .
    ?composite ex:buildsUpon+ ?prime ;
               rdfs:label ?compositeLabel .
}
ORDER BY ?primeLabel ?compositeLabel

Event Clustering by Time Window

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>
PREFIX dc: <http://purl.org/dc/terms/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>

SELECT ?record (COUNT(?event) as ?eventCount) WHERE {
    ?event a ex:HarmEvent ;
           ex:harms ?record ;
           dc:date ?date .
    FILTER(?date >= "2026-05-01"^^xsd:date && 
           ?date <= "2026-05-31"^^xsd:date)
}
GROUP BY ?record
HAVING (?eventCount > 1)

Severity Distribution

PREFIX ex: <https://www.epistemic-ontology.net/record-harm#>

SELECT ?severity (COUNT(?event) as ?count) WHERE {
    ?event a ex:HarmEvent ;
           ex:severity ?severity .
}
GROUP BY ?severity
ORDER BY ?severity