r/semanticweb • u/DougEgge • 6h ago
Who said it? How I model a text where most of the claims aren't the narrator's (schema map, RDF 1.2, andthree open questions)

As I described in my prior post two weeks ago, I'm building a knowledge graph of the Bible: its people, places and events, and what each passage says about them, every link pinned to its verse.
In any text with speakers, who said something is part of what was said. A court transcript, a novel, an interview: a knowledge graph that drops the speaker publishes each character's words as the text's own facts. So here every relation is a claim with its verse, how certain it is, and who makes it, and only the narrator's own claims are asserted as plain facts.
An example, with a little Bible context. The Bible is a diverse collection of writing: stories, poems, letters, genealogies, visions, you name it. One of its stories is about a man named Job, and a whole book of the Bible, written in ancient Hebrew, tells it. Job loses almost everything, and most of the book is an argument: his friends insist, speech after speech, that he must have deserved it. At the end God rebukes them: "you have not spoken about Me accurately" (Job 42:7).
One friend, Eliphaz, tells Job, "Your own mouth, not mine, condemns you" (Job 15:6). Stored two ways:
The first publishes a speaker's charge as the book's own verdict. The second is what the book actually records.
Your replies to that first post changed the graph:
- moving to RDF 1.2;
- aligning with PROV;
- treating where a passage starts and ends, and what genre it is, as someone's reading published with Web Annotation, rather than as a fact.
This is where it stands today.
The schema, as a map: bibleontology.org/ns/map
Four vocabularies, drawn as pictures you can click down through:
- the text: sentences and how they connect (because, so that, until), read from the original Hebrew and Greek;
- what it's about: people, places, ideas, events;
- what it says: the relations between them, each pointing at its verse;
- who says it: every relation is a claim with a voice.
It's the schema, not the data: every box is a class, property or list drawn from the published vocabulary files, and opens the next level down or the term's definition.
Claims, not facts
Every relation is a claim: the verses it rests on, how certain it is (explicit, implied, conjectured or disputed), and whose voice it is in (the narrator, a character, or a reader).
Why not just a "speaker" column to filter on? Inside one database, that works. But once the data leaves it, any tool that doesn't know about the column reads every row as true. RDF 1.1's reification could describe a statement without asserting it, clumsily (four triples a claim, nothing a store treats as one statement). RDF 1.2 makes the statement a term a claim can point at, without the statement itself being in the graph.
c:3a11f4736068231d a claim:Claim ;
rdf:reifies <<( e:job bor:described_as e:testified_against_by_his_own_lips )>> ;
claim:verse p:Job.15.6 ;
claim:claimedBy e:eliphaz_the_temanite .
In the RDF 1.2 file, only what the narrator states or clearly implies is written as an ordinary triple. Eliphaz's line is there, credited to Eliphaz, but no plain query will hand it back as fact.
I tried named graphs first, one per speaker. The same plain query then matched nothing in Jena and 32 triples in RDF4J, Eliphaz's verdict among them, because RDF4J's default graph is the union of all graphs: its documented default, since SPARQL leaves that to the store (GraphDB and Jena's TDB2 can do the same). The protection depended on which database you used, so I became an early adopter of RDF 1.2 and moved it into the data. It is early: RDF 1.2's core concepts are a W3C Candidate Recommendation, but the TriG and SPARQL 1.2 syntaxes the files and queries use are still Working Drafts, and not every tool reads them yet. The 1.1 file still ships; with it, query GRAPH claim:narrator. (The full before-and-after, with every query and how to reproduce it in both stores, is in RDF 1.2 in practice.)
The default is a claim too
A rule like "only the narrator is asserted" is only as good as the data people actually download. So before posting, I counted what the published file asserts instead of trusting my code. More than half of it wasn't the narrator at all. It was my own work: the themes and story patterns I had grouped passages into, which had defaulted to the narrator's voice because I never said whose they were. They are now credited to an editors' voice and not asserted, and a test repeats that count against the published files.
Who is "the narrator" when the author is speaking?
In a story the narrator is easy to find: the voice outside the speeches. But much of the Bible has no narrator in that sense. Psalm 23 opens "A Psalm of David. The LORD is my shepherd": the heading names David, and the "my" is the psalmist's. A letter that opens "Paul, an apostle" (Galatians 1:1) is Paul speaking, and the whole letter is his speech, with no quotation marks to find. I wrestled with this for a while, and landed on a principle: where the text has no narrator, someone is still speaking. In a letter the speaker isn't found sentence by sentence; "this is a letter from Paul" comes down from the book itself, and covers every sentence in it. The people who speak inside a book (Job's friends, Pharaoh, the serpent) keep voices of their own, and the same Paul is a character with his own voice when Acts quotes his speeches.
The published claims haven't fully caught up with that principle: part of Titus is credited to Paul, while the rest of Titus, and Galatians and Psalm 23, still sit in the general narrator voice.
Why nothing is inferred
This one is deliberate. So much of the Bible is figurative, and inference quietly reads figures literally. Paul tells the church in Corinth, "you yourselves are God's temple" (1 Corinthians 3:16). Store that as a type, in a class of buildings, and a reasoner will conclude, correctly from what it was given, that the Corinthians are a building. Store it as an identity, the Corinthians are the temple, with one "temple" record for the word, and they become the house Solomon built and the Babylonians burned, because that is what the graph says of the temple. Storing a figure as a type or an identity turns it into a fact, and where figure ends and fact begins is exactly where readers of this text disagree. So inference here wouldn't be neutral: it takes a figure at its type, and the type is the literal reading, every time. The graph keeps the figure as a figure, a separate record that signifies what the text itself identifies it with, each link with its verse, and checks the data with SHACL instead. What the text plainly entails (two sons of one father are brothers) is recorded once, as an "implied" claim with its verse.

Deep dive (optional): one word, two cards.
Some verses are read as meaning different temples. In 2 Thessalonians 2:3-4, the "man of lawlessness" "will seat himself in the temple of God, proclaiming himself to be God." That temple is read differently by different readers. I don't take a stand: the verse is on both cards, under "Where readers differ", with the readings it has been given named and none chosen.
Three questions I haven't settled
- Verbs and events. "Saul loved David" (1 Sam 16:21) is stored under one general relation, with the verb on the claim. Today it looks like this (abridged):
- I don't assert
e:saul bor:states e:davidas a plain triple: every sentence with Saul as subject and David as object would collapse into it. The claim is already an event node of sorts. Would you give the event a node of its own, apart from the claims that record it, so two books can describe one event? - One narrator or one per book? One narrator voice covers the text's own sentences today, wherever the book has no other speaker. Samuel and Chronicles tell the same census and name different instigators (2 Sam 24:1, 1 Chr 21:1). Neither instigator is recorded yet: with one voice it would be one disputed claim; with a voice per book, or per author, two narrators disagreeing. Which would you publish?
- Figures of speech. If you've modelled figurative language in a graph (metaphor, simile, personification), how did you keep it from being read as fact?
It's public: github.com/bibleontology/bibleontology
- The README is the way in: the data (21,394 claims, about 2.4 MB compressed), the vocabularies and shapes, and the tests.
- Licences: code Apache-2.0; graph and vocabularies CC BY 4.0. The sentence structure is derived from the MACULA Hebrew and Greek Linguistic Datasets (© Biblica, Inc., CC BY 4.0); full credits are in the README.
- The name: not affiliated with the earlier Bible Ontology (bibleontology.com, about 2009–2011).
- Built solo, with Claude Code doing most of the typing and AI models doing much of the extraction, checked by rules and tests. The decisions and the mistakes are mine.
- This post, too, is a collaboration with Claude. I went back and forth with it on every paragraph, and what's here is mine to answer for.
- Not measured yet: how often the extracted claims are right. Every claim links to its verse, but no sample has been hand-checked for an error rate yet. One known fault: some links inside speeches (how one clause grounds another, a speaker's simile) still sit in the narrator's voice, so they are asserted as plain triples. In Job that is 83, 14 of them Eliphaz's in chapter 15.
This picks up the conversation from my first post. That one made the work better, and I'd like to continue it.
