Appearance
Citations (R4)
Grounding rule (R4): never invent bounding boxes. Prefer SoR citation ids; degrade gracefully to page/document. A page-only citation is valid; a fake rectangle is not.
Where citations live
| Store | Fields |
|---|---|
Citation (Postgres) | documentId, page, x0,y0,x1,y1, ExtractedText, human-verified flag |
ExtractedPage | OCR Text, WordsJson (per-word text, bbox, confidence), OcrConfidence |
| Cells / alerts / contradictions / findings | Reference SourceCitationId / citation rows (or null bbox) |
| Search hits | Snippet + page; bbox only when usable |
Persisted by POST /api/documents/{id}/ocr-result (pages, citations and search-index rows written together; see Dossier → OCR → analysis).
BoundingBox contract
| Context | Shape | Note |
|---|---|---|
| Wire / SoR / OCR words | x0, y0, x1, y1 (JSON array order [x0,y0,x1,y1] in bbox strings) | Origin top-left, page pixel/point space of the OCR render |
Web BoundingBox type | { x0, top, x1, bottom } | top = y0, bottom = y1 |
isUsableBoundingBox(bbox) | false if missing, any non-finite number, all-zero, or x1 ≤ x0 / bottom ≤ top | single guard shared by viewer and Copilot |
How citations are produced
| Citation | Id pattern | Bbox | Where |
|---|---|---|---|
| Page citation | cite_{docId}_p{n} | first OCR word of the page, or [] when none | OCR persist |
| Line citation | cite_{docId}_p{page}_{lineCode} | words of the matched OCR line | MatchLineCitations (SYSCOHADA cells) |
| Regulatory evidence | cite_reg_{docId}_{page} | deal-room key_pages[].bbox (no invention) | analyzeDeal |
| Copilot RAG | cite_rag_{hitId}_{i} | parseBbox(h.bbox) or undefined | hitsToCitations (max 4) |
MatchLineCitations rules:
- OCR words of a page are grouped into lines.
- For each extracted statement line, label patterns + the numeric value (±0.5) must both match.
- Unmatched cells get no
SourceCitationId(no fallback guess).
Contradiction alerts hydrate doc_a / doc_b from Citation rows; when the bbox is unusable it is null — never {0,0,0,0} (Contradictions).
Verify API
POST /api/locus/documents/{id}/verify-citation → C# POST /api/documents/{id}/verify-citation (dealroom:read_approved).
Request (BFF VerifyCitationSchema):
json
{ "pageNumber": 1, "x0": 0, "y0": 0, "x1": 0, "y1": 0 }(pageNumber positive integer; box coordinates are numbers.)
| Check (in order) | Result |
|---|---|
| Document not found for tenant | 404 { "error": "MISSING_DOCUMENT", "message": … } |
pageNumber < 1 or > PageCount | 200 isValid:false, confidence:0, error: "Page number … out of document range" |
x1 ≤ x0 or y1 ≤ y0 | 200 isValid:false, error: "Invalid bounding box geometry …" |
No ExtractedPage for that page | 200 isValid:false, error: "OCR page not available for citation verification." |
| OK | 200 isValid:true, snippetText, confidence, echoed boundingBox |
Tolerance ±5 px is applied as a containment margin: an OCR word matches when its box lies inside the requested box expanded by 5 px on every side (x0 ≥ X0−5, y0 ≥ Y0−5, x1 ≤ X1+5, y1 ≤ Y1+5). On success:
snippetText= matching words joined by spaces; if none match, the first 120 chars of the page text.confidence= average word confidence of the matches; if none match, the pageOcrConfidence.
Used by Deal Room / PDF viewer / override flows.
UI contract
- Click citation →
InteractivePdfViewerswitchesdocumentId+ page (activeCitation.pageNumber). - Highlight candidate = the active citation's bbox on that page, otherwise the page's
key_pagesbbox. - If
isUsableBoundingBox→ overlay{ left: x0, top: y0, width: x1−x0, height: y1−y0 }; zoom range 70–150 % (default 100). - Missing / all-zero / degenerate → no highlight; show page + snippet only.
- Copilot retrieval (
parseBbox) returnsundefinedwhen the stored bbox is not a usable 4-number array — no default rectangle; covered by R4 tests.
Analyst override
Low OCR confidence or Actif≠Passif → cell override + citation review before committee. Full SOP: OCR override.
Thresholds (from SOP): confidence floor 0.85 (below → low-confidence); UNRECONCILED when the balance check fails; override body { acceptedValue, reason }. The balance formula and the SoR tolerance by layer are in SYSCOHADA ratios.
Failure modes
| Symptom | Cause | Expected behaviour |
|---|---|---|
| Citation without highlight | scanned page with no word box, or page-level cite | page + snippet, no overlay |
| Cell without citation | label/value not matched on any OCR line | shown without link; analyst may override |
verify-citation → isValid:false | page out of range, bad geometry, or OCR missing | surface error; do not render a box |
404 MISSING_DOCUMENT | document row absent in tenant | explicit error, never blank success |