Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Querying the graph

fluree doc search is a convenience. Everything it does is a query you can write yourself, and the graph goes further than it shows.

From a hit to the page

A chunk cites the elements it was built from; a PDF element knows its page and box:

PREFIX doc: <https://ns.flur.ee/doc#>
PREFIX nif: <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#>

SELECT ?el ?page ?box ?start ?end WHERE {
  <urn:fluree:doc:msa-2024.pdf/chunk/97> doc:sourceElement ?el .
  ?el doc:pageIndex ?page ; doc:bbox ?box ;
      nif:beginIndex ?start ; nif:endIndex ?end .
}

doc:bbox is x0,y0,x1,y1 in PDF units with a top-left origin, and the document node’s doc:pages gives each page’s size, so a box scales onto a rendered page image without a coordinate flip. The offsets index into the document’s text projection, the string every nif:beginIndex in the document shares.

Containment is po:contains, so “chunks under this heading” is a traversal:

PREFIX doc:  <https://ns.flur.ee/doc#>
PREFIX doco: <http://purl.org/spar/doco/>
PREFIX po:   <http://www.essepuntato.it/2008/12/pattern#>
PREFIX nif:  <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#>

SELECT ?chunk ?text WHERE {
  ?section a doco:Section ; po:contains ?title .
  ?title a doco:SectionTitle ; nif:isString "12. Term and Termination" .
  ?section po:contains+ ?el .
  ?chunk a doc:Chunk ; doc:sourceElement ?el ; doc:text ?text .
}

Across ledgers, on the entities you already have

A mention points at the entity’s own IRI from the --entities source, so a dataset query joins the source ledger and the documents ledger with no mapping. Everything a memo says about a person, next to what the people ledger knows about them:

PREFIX doc: <https://ns.flur.ee/doc#>
PREFIX nif: <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#>
PREFIX schema: <https://schema.org/>

SELECT ?name ?email ?file ?said
FROM <people:main>
FROM <memos:main>
WHERE {
  ?person a schema:Person ; schema:name ?name ; schema:email ?email .
  ?m nif:entity ?person ; nif:anchorOf ?said ; doc:sourceDocument ?d .
  ?d doc:relativePath ?file .
}

Relations are reified with their evidence, so a review of what the model claimed and how it was judged is one query:

PREFIX doc: <https://ns.flur.ee/doc#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT ?verdict ?s ?p ?o ?excerpt ?reason WHERE {
  ?r a doc:Relation ; doc:verdict ?verdict ;
     rdf:subject ?s ; rdf:predicate ?p ; rdf:object ?o ; doc:excerpt ?excerpt .
  OPTIONAL { ?r doc:rejectionReason ?reason }
} ORDER BY ?verdict

Admitted relations are also plain edges, so ?person schema:worksFor ?org works as it would over hand-written data.

Vector and full-text search by hand

The indexes are graph sources. A vector search takes a query vector — the same embedding endpoint the ingest used — and joins the hits to chunk data:

{
  "@context": {"doc": "https://ns.flur.ee/doc#", "f": "https://ns.flur.ee/db#"},
  "from": "contracts:main",
  "where": [
    {"f:graphSource": "contracts-vectors:main", "f:queryVector": [0.012, -0.041, "…"],
     "f:searchLimit": 10, "f:searchResult": {"f:resultId": "?c", "f:resultScore": "?score"}},
    {"@id": "?c", "doc:text": "?text", "doc:headerPath": "?path", "doc:sourceDocument": "?d"}
  ],
  "select": ["?score", "?d", "?path", "?text"],
  "orderBy": [["desc", "?score"]]
}

Replace the first pattern with {"f:graphSource": "contracts-text:main", "f:searchText": "termination notice", …} for BM25. fluree doc search --mode hybrid runs both and fuses them by reciprocal rank; by hand you can do the same and add filters on doc:sourceDocument, doc:headerPath, the entities a chunk mentions (?m nif:referenceContext ?c ; nif:entity <…>) or anything else in the graph, which the command does not do.

Time travel and branches

Every ingest is a commit. fluree query contracts --at 3 sees the corpus as it was; fluree branch create gives an experiment its own copy to re-ingest with different chunking or a different model, and fluree branch diff shows what changed.