← All projects

Qurite

In development

Answers questions about high-stakes documents and proves every answer. Click the claim, see the box on the drawing it came from, or get told there isn't enough evidence.

GoRustLLMProvenanceDocument AI

At a glance

  • Outcome: Ask how many W18X40 callouts are on a sheet and get a count where every one is a clickable box on the actual drawing. Every answer is either supported by citations that resolve to source bytes, or refused. There’s no third state.
  • Status: In development. The live path from upload to answer pins the count to a committed ground truth, but that test needs the parser binary and a pinned pdfium, so it runs on demand instead of with the default suite.
  • Role: Solo. The evidence contract, the confidence model, and the refusal semantics.
  • Stack & libraries: Go services plus pdfParse in Rust for documents, over content-addressed source storage. Three further parsers exist as skeletons that hold the seam open; they don’t extract yet.
  • Source: Private.
  • Limitations: One vertical proven, against a fixture corpus. Born-digital PDFs only, since counting reads the document’s own text layer. Scanned sheets need a vision path with its own accuracy gate, which is parked.

The problem it’s for

Retrieval over documents usually launders away the one thing that makes an answer checkable. A chunk gets embedded, a similar chunk comes back, and the answer has no way to point at where it came from.

But the failure isn’t really about models. There’s no model anywhere in this counting path, and it still came back with about 374 beams where the drawings had 294. A normalizer had quietly folded two different steel shapes into the one being asked about.

That’s the real argument for provenance. Any pipeline over a contract, a spec or a structural set will be wrong sometimes, and the only defense that survives is an answer someone can go and look at.

Deciding what counts as one thing

The hard part of counting isn’t finding text. It’s deciding what counts as one thing.

A structural sheet draws the same beam more than once: a plan view, then a detail that enlarges a corner of it, then a section. Searching for the label finds all of them. So a counted instance isn’t a match. It’s the drawing, its current revision, the role of the region the label sits in, and the content it points at, taken together. A label inside a detail is a cross-reference to the one on the plan, not a second beam, and it gets reported as a caveat instead of quietly dropped. Two revisions of the same sheet never add up.

That region role is the one slot each domain fills for itself. Steel supplies the role of the viewport; the biomedical pack supplies the role of the document. Getting that seam right is what let a second domain reuse the counting engine unchanged.

What makes an answer provable

Each of these is a refusal of something convenient.

Evidence and inference are different objects. A count is evidence: pulled from the drawing’s own content, drawable as a box where it came from. A finding like “this connection detail is atypical” is inference: it names the rule that produced it, cites its inputs, and carries its own confidence. The interface draws them as visibly different things, because conflating them is how a guess becomes a fact.

Identity is never minted by a model. Search only finds candidates. What proves a citation is a typed pointer into the stored source. Delete every embedding and every citation still resolves.

Confidence has three axes: how well the retrieval matched, how authoritative the source is, and how cleanly the text was extracted. Each is computed, not guessed by a model. The overlay shows the weakest axis next to the average, and shows nothing at all rather than inventing a number when an axis is missing. The floor isn’t yet load-bearing though: the gate is written and unit-tested and nothing calls it. What refuses an answer today is a citation that won’t resolve, or a source you don’t have access to.

The system can’t mint an expert verdict. A domain pack declares its own closed set of finding kinds, and the registry rejects any pack that tries to declare capacity, compliance or diagnosis. That’s a hard error, thrown before the pack is loaded, and every finding carries a note naming the engineer of record as the authority. Adding a domain can’t quietly turn triage into a sign-off, because the refusal lives at the extension point instead of in the copy.

Access fails closed. Permissions are enforced on every read path, and if any source in scope is unavailable the answer is refused instead of quietly partial.

The demo

An early live run over the same eleven-page set returned about 374 instances where the ground truth is 294. Both causes were in the parser. It was fuzzy-matching two distinct steel shapes into the one being counted, and it was throwing two callouts on page 9 out of the region they belonged to, so those went uncounted. The test now requires the count to match the ground truth exactly and every instance to have a drawable box, so an over-match reads high and the page-9 bug reads low.

One caveat the demo says out loud: the number counts callouts, not beams. Structural drawings use a “typical” convention where one label can stand for a run of identical members, so 294 labels isn’t a claim that there are 294 beams. The fixture records the label count and leaves the beam count blank until someone hand-counts it. Page one is done, at seven. Closing that gap needs beam-line detection, which isn’t built.

The overlay serves the drawing with every counted callout as a clickable box. Clicking one opens the evidence trail: the extraction record, the three confidence axes, and the stored source it resolves to. Counts scope per sheet or per set, findings ride alongside, and the same evidence exports as an annotated PDF.

The golden case is a good one precisely because it’s checkable by hand. A miss or a phantom callout is obvious to anyone who can read the drawing.

Where the Rust boundary came from

The first overlay renderer lived in Go. The parser returned a picture of the page plus a mapping into it, and the parent drew boxes in pixel space.

Large-format sheets exposed the cost. Resolution capped how legible the result could be, and the geometry that should drive the marks was being worked out in the parser, serialized, then interpreted a second time in Go. That second interpretation is exactly where the defects appeared.

So rendering moved into pdfParse, which now returns an annotated copy of the PDF as vectors. Go kept scope enforcement, delivery and the audit record. The source is never rewritten, and a checksum before and after proves it.

That reversal is the reason the Rust boundary exists. It wasn’t a language preference. It was about which side owns the geometry.

Structure

Qurite is a family of repositories instead of one program: a Go core with per-format parsers alongside it, and pdfParse, the Rust document toolkit, handling PDFs. The shared piece is the evidence contract. The pointer type has to cover table rows, record cells, messages, streams and video frames without making PDF page geometry the only durable shape.