HeritageGraph Knowledge-Graph Pipeline — Architecture, SOTA Review & Roadmap
Scope: the full path from ingestion → knowledge graph → display, an honest state-of-the-art assessment, what is implemented today, and the concrete roadmap to a fully research-grade KG. Companion to RDF_ENGINE.md, ../ontology/ONTOLOGY.md, ../../ARCHITECTURE.md.
1. Executive summary
HeritageGraph is a standards-based RDF knowledge graph, not a property-graph app:
CIDOC-CRM (ISO 21127) + a LinkML ontology registry, projected into Oxigraph (SPARQL 1.1)
with reified, provenance-bearing assertions (crminf:I2_Belief / prov:Entity), identity
clustering (owl:sameAs), and named-graph partitions. PostgreSQL is the system of record;
Oxigraph is the runtime KG.
Verdict: a genuine research-grade foundation, currently ~70% of the way to SOTA. The remaining gaps are specific and closable (statement-level provenance in the graph, SHACL enforcement, external-authority reconciliation, LOD publishing, evaluation).
2. The full pipeline (ingestion → display)
Ontology (LinkML) ──gen──► registry + SHACL + TS/Py config [Stage 0]
│
Contribution form (OntologyForm) / optional agent suggestions [Stage 1]
│ contribution_entity_resolution on create (exact → cluster link)
▼
PostgreSQL (CIDOC MetaData · CulturalEntity/Revision/Activity · [Stage 2]
HeritageAssertion · EntityCluster) ← system of record
│
Review & reconciliation (queue · identity candidates · [Stage 3]
│ suggest-duplicates · merge/split · RelationshipProposal)
▼
rdf_signals → projector (registry-aligned triples, SHACL gate, [Stage 4]
│ owl:sameAs, outbox retry)
▼
Oxigraph (named graphs: PUBLIC · SCHEMA · DOCUMENT · PROV) [Stage 5]
│
Read API /kg/graph · /kg/neighborhood · /kg/query · /kg/stats [Stage 6]
│ (scope=all|reviewed · per-edge provenance)
▼
Heritage Museum (ontology-typed nodes + real edges + expand) [Stage 7]
Stage 0 — Ontology as single source of truth
ontology/HeritageGraph.yaml(LinkML) defines classes/slots/enums +class_uri/slot_uri.- Generators fan it out so every layer stays aligned:
tools/gen_heritage_viz_config.py→heritage-viz-config.ts(incl.NODE_TYPE_CONFIG,RDF_CLASS_URI_TO_NODE_TYPE),apps/graph/ontology_config.py(RDF prefixes), SHACL shapes, and the registry payload (apps/cidoc_data/linkml_loader.py).
Stage 1 — Ingestion
- Human:
OntologyForm→ DRFContributionFlowMixin.perform_create(apps/cidoc_data/views.py) creates a CIDOCMetaDatarow +CulturalEntitywrapper + firstRevision+ reviewer notifications. - Identity on submit:
contribution_entity_resolution.resolve_contribution_identity()runs on commit — exact label+type → link to existingEntityCluster; similar label → singleton +IdentityResolutionCandidate; new label → new singleton cluster. - Duplicate UX:
DuplicateContributionAlerton forms callsGET /api/v1/cidoc/entity-clusters/suggest-duplicates/and steers contributors to edit the richer existing record when appropriate. - Agentic / OCR (suspended): document OCR and multi-agent extraction are infrastructure-only;
OCR_ENABLEDdefaults false andocr-workeris not in active compose. See../pipelines/OCR.md.
Stage 2 — System of record (PostgreSQL)
- CIDOC
MetaDatasubclasses — 26 navigable registry types intools/ui-classmap.yaml(Person, Location, ArchitecturalStructure, Production, Consecration, Enshrinement, TransferOfCustody, Deity, Festival, Monument, IconographicObject, EntityCluster, …). Full table: ../ontology/ONTOLOGY.md §5. HeritageAssertion= first-class reified statement (subject, predicate, object/value) with provenance:source/source_citation,confidence,confidence_score,temporal_scope_edtf,contributed_by,attributed_to_agent,reconciliation_status.CulturalEntity/Revision/Activity= versioned edit history;EntityCluster= identity.
Stage 3 — Review & reconciliation
- Moderation via
reconciliation_status(pending→accepted). - Relationships:
RelationshipProposal→ moderator approval →materialize_relationship_proposal(apps/heritage_data/views.py) → an acceptedrelationship.*HeritageAssertion. - Identity:
EntityCluster+IdentityResolutionCandidatequeue at/curation/identity; deploy bootstrap runsbootstrap_identity_clustersandrefresh_identity_candidates --auto-merge(entrypoint).canonical_record_selectionranks cluster members for museum hub display.external_identifiers→owl:sameAswhen unambiguous.
Stage 4 — Projection to RDF
apps/cidoc_data/rdf_signals.pyconnectspost_saveon everyMetaDatamodel and onHeritageAssertion. On save (whenRDF_SYNC_ENABLED, default on):- Entity →
rdf:type(classUri),rdfs:label, slot literals, FKrelationslots as IRI→IRI edges (build_entity_projection), andowl:sameAs. - Accepted
relationship.*assertion → one edge triple (queue_relationship_assertion_projection). - Canonical IRI policy:
resource_uri_for_instance={base}/{registry_key}/{pk}(apps/graph/kg_engine/uris.py). Assertion subjects/objects resolve to the same IRIs. - Optional SHACL gate (
RDF_SHACL_VALIDATE_ON_WRITE); failed writes retried via the outbox.
Stage 5 — KG store (Oxigraph)
- SPARQL 1.1; partitions PUBLIC (published), SCHEMA (TBox from
HeritageGraph.ttl), DOCUMENT (per-upload ingest), PROV (reserved). rdf_rebuildre-projects every entity idempotently (per-subject managed-triple replace — it does not wipe assertion edges).
Stage 6 — Read API
GET /api/v1/cidoc/kg/graph/— whole public graph as render-ready JSON: ontology-typed nodes + real edges. Supports?scope=all|reviewedand now per-edgeprovenance(source/confidence/asserter/temporal) joined from accepted assertions.GET /kg/neighborhood/?uri=— inbound/outbound edges (+ neighbourrdf:type) for click-to-expand.POST /kg/query/— read-only SPARQL.GET /kg/stats/— counts/histogram.
Stage 7 — Display (Museum, Atlas, Graphview)
- Heritage Museum (
/heritage-museum):src/lib/museum-graph.ts+kg-graph.tsfetch/api/v1/cidoc/kg/graph/; cluster dedup + canonical member preference; live corpus default. - Graphview (
/graphview): Cytoscape instance graph viasrc/lib/instance-graph.ts. - Atlas (
/atlas): Cesium globe + live CIDOC hydration (src/lib/atlas-api-hydrate.ts). - Nodes typed via
RDF_CLASS_URI_TO_NODE_TYPE; edges are real triples (not name-matching heuristics).
3. SOTA scorecard
| Dimension | Status | Industry benchmark | Notes |
|---|---|---|---|
| Ontology & standards | 🟢 Strong | Getty/CIDOC LOD | CIDOC-CRM + LinkML single source → SHACL/TS/Py |
| Reified statements + uncertainty | 🟢 Strong | Nanopub / CRMinf | crminf:I2_Belief, confidence, EDTF temporal |
| Identity & external linking | 🟡 Partial | Wikidata 5-star LOD | skos:exactMatch machinery exists; needs real links |
| Provenance in the graph | 🟢 Good | PROV-O / nanopubs | Per-assertion prov graphs + read-time edge provenance |
| Validation (SHACL) | 🟡 Partial | SHACL mandatory at publish | Shapes exist; RDF_SHACL_VALIDATE_ON_WRITE=false by default |
| Review-gated publication | 🟢 Strong | Museum LOD norm | Write + read gate: only accepted/merged/published → PUBLIC |
| Linkset vs bulk merge | 🟢 Strong | VoID linksets | External datasets stay separate; curated namespace only in PUBLIC |
| LOD publishing / FAIR | 🔴 Gap | Zenodo DOI + dereferenceable URIs | VoID/DCAT plumbing exists; no versioned public release yet |
| Evaluation / quality metrics | 🟡 Partial | Gold-standard F1 | kg_quality_report + kg_verify; no expert benchmark yet |
| Dataset density | 🔴 Gap (data) | Connected graph | ~112 curated entities; curation throughput is the bottleneck |
Maturity level: Level 3–4 architecture (research/industry-grade stack) on a curated global corpus (Nepal-rich fixtures + review workflow). Dataset density grows with curation throughput and external linking — not bulk third-party imports.
4. Recently implemented (toward SOTA)
- Museum reads the real KG via SPARQL (
/kg/graph/), replacing the prior client-side heuristic edge reconstruction (name-matching/NLP/co-location — all removed). - Ontology-faithful typing: generated
RDF_CLASS_URI_TO_NODE_TYPE. - Review-gating:
scope=all|reviewedon the read API. - Provenance-bearing edges: every assertion-backed edge now carries source, confidence,
asserter, and temporal scope (
KnowledgeGraphGraphView+_assertion_provenance_map). - Click-to-expand via
/kg/neighborhood/(neighbours typed byrdf:type). - Diagnostics/verification:
python manage.py kg_verify(store consistency, edge counts, dangling edges, accepted-vs-pending),kg_e2e_demo(create→project→read round-trip). - Deterministic SVG node icons; reproducible, attributed demo corpus.
5. Roadmap to full SOTA
P1 — research credibility (highest leverage)
- Statement-level provenance in RDF — project each accepted assertion as a
named graph per assertion (or RDF-star), described in the PROV partition with
prov:wasGeneratedBy,prov:wasAttributedTo, confidence,prov:generatedAtTime, and EDTF validity (nanopublication pattern). Promotes today's read-time provenance into the graph itself. Spec: mint{base}/assertion/{uuid}; write the asserted triple to its own graph; describe that graph in PROV; extend/kg/graph/to read provenance from PROV instead of Postgres. - Enable SHACL on write (
RDF_SHACL_VALIDATE_ON_WRITE=true) + a conformance report; surface violations in review. - External reconciliation — populate
EntityCluster.external_identifierswith Wikidata/Getty AAT+TGN/GeoNames/VIAF IRIs (reconciliation service + reviewer UI); theowl:sameAsemission already exists.
P2 — FAIR / Linked Open Data publishing
- Dereferenceable resource URIs with content negotiation (Turtle / JSON-LD / RDF-XML).
- A read-only public SPARQL endpoint; VoID + DCAT dataset description; Zenodo DOI per versioned release; per-resource license.
- SKOS thesauri for controlled vocabularies, aligned to Getty AAT.
- Linkset model (not bulk merge): external datasets (e.g. Yale LUX) live in separate
named graphs (
imported/*,alignment/*). Curated entities link viaskos:exactMatchonly. The public graph (graph/public) accepts IRIs underRDF_RESOURCE_BASE_URIonly; runpython manage.py kg_purge_public_imports --applyif bulk imports were merged by mistake. Batch link suggestions:python manage.py kg_suggest_external_links.
P3 — evaluation & quality
- Gold-standard evaluation of the agent extraction (precision/recall/F1 vs expert annotations) — required to publish the pipeline.
- KG-quality dashboard: SHACL conformance %, link density, type/coverage completeness,
dangling-edge count (extend
kg_verify).
6. Verify the pipeline end to end
make test-e2e # 40 tests: health, contribute, identity, RDF, museum (see tests/)
cd heritage_graph
python manage.py kg_verify # store consistency · edges by predicate · accepted vs pending
python manage.py kg_e2e_demo # create → project → read round-trip
make rdf-rebuild # re-project entities (FK relations included)
curl -s 'http://localhost:8000/api/v1/cidoc/kg/graph/?scope=all' | jq '.counts'
Then open Heritage Museum (/heritage-museum) or Graphview (/graphview) in Live mode;
assertion-backed edges should carry provenance when scope=reviewed or provenance map is populated.
7. Key files
- Ontology/gen:
ontology/HeritageGraph.yaml,make generate,tools/gen_heritage_viz_config.py - Contribution + identity:
apps/cidoc_data/contribution_entity_resolution.py,apps/cidoc_data/canonical_record_selection.py,heritage_graph_ui/src/components/contribute/duplicate-contribution-alert.tsx - Projection:
apps/cidoc_data/rdf_signals.py,apps/cidoc_data/rdf_entity_projection.py,apps/graph/kg_engine/{projector,engine,store,queries,uris,partitions}.py - Read API:
apps/graph/views.py,apps/cidoc_data/urls.py(/kg/graph/,/kg/stats/, …) - Ops:
heritage_graph/entrypoint.sh,make rdf-rebuild,make identity-candidates - Diagnostics:
apps/graph/management/commands/{kg_verify,kg_e2e_demo,rdf_rebuild}.py - Display:
heritage_graph_ui/src/lib/{kg-graph,museum-graph,instance-graph}.ts,heritage_graph_ui/src/app/(dashboard)/heritage-museum/,heritage_graph_ui/src/app/(dashboard)/graphview/,heritage_graph_ui/src/app/(dashboard)/atlas/