Navigation API
Read-only, engine-abstracted traversal of the knowledge graph. For tutorials and recipes see the Graph Navigation user guide.
The graph store is pluggable, exactly like the storage layer: an
engine-agnostic GraphNavigator ABC plus a concrete Neo4jGraphNavigator,
selected by the graph_backend config field (env GRAPH_BACKEND, default
"neo4j").
Factory
scinr.newton.navigation.factory
navigation/factory.py — Graph-navigator factory.
Mirrors storage/factory.py: the concrete backend is chosen from
ScinrConfig.graph_backend (env GRAPH_BACKEND, default "neo4j"), so
call sites never name an engine.
get_graph_navigator
async
Return a connected :class:GraphNavigator for the configured backend.
The connection is verified eagerly (connect() → ping()).
Returns:
| Type | Description |
|---|---|
GraphNavigator
|
A ready-to-use navigator. The caller is responsible for |
GraphNavigator
|
meth: |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If |
GraphConnectionError
|
If the engine is unreachable. |
Base Interface
scinr.newton.navigation.base
navigation/base.py — GraphNavigator, the engine-agnostic navigation interface.
A :class:GraphNavigator is a read-only, fully async view over the
knowledge graph produced by scinr.newton. Concrete backends translate each
call to their native query language; the only backend today is
navigation.neo4j.Neo4jGraphNavigator.
Nothing on this interface mutates the graph. The single non-portable seam is
:meth:GraphNavigator.execute_raw (and its one-row sibling): an optional escape
hatch whose query string is written in the backend's own dialect.
Conventions
- Every method is
async. - Return types come from
navigation.modelsand are engine-neutral. - A single-get method returns
SomeRef | Noneonly when its arguments form a full unique key. Any looser selector returns a list. - Every method that walks a variable-length path takes
depth: int | None.depth=None= "no explicit limit" → the backend applies :data:DEFAULT_MAX_DEPTHas a runaway-traversal guard (not a hard ceiling).depth=1= direct only. An explicitnis used verbatim. - Filter values passed via
where=/ key selectors are used verbatim — the caller is responsible for any normalisation (instance-key values, for instance, are stored lower-cased and accent-stripped by ingestion). - List methods take
limit: int | None = Noneandskip: int = 0and order deterministically. Dynamic filters are only applied when supplied.
Scope: tenant, user, job
Every method except the global catalogue ones (list_catalog_models,
get_catalog_graph, list_themes, list_relationship_types,
list_node_labels) and execute_raw takes four keyword-only filters::
tenant_id: str | None = None # None = all tenants
include_public: bool = False # add public documents
created_by_user_id: str | Sequence[str] | None = None # IN
job_id: str | Sequence[str] | None = None # IN
tenant_id=None means no tenant filter (every tenant, public and legacy
data included); "__public__" means only public documents; "acme" only
that tenant, or acme plus public with include_public=True. The user /
job filters take one value or several (any-of), combine by AND with the tenant,
and an empty list is an error. The stored tenant is exposed as is — a public
document reports tenant_id == "__public__". See :mod:navigation.scope.
Because the default is "all", an API layer that forgets to pass the tenant
exposes everything: prefer :meth:GraphNavigator.scoped, which fixes the scope
once and rejects calls that try to widen it.
How the filters apply depends on the method:
- Methods that return a list return every match in scope (a path that exists in two tenants yields both documents, each ref carrying its tenant).
- Methods that return one document or tree (
get_one_document,get_latest_version,get_document_tree,get_document_parent,get_document_ancestors,get_document_stats,get_document_model_profile,get_annotation_coverage) raiseNavigationErrorwhen the path exists in several tenants of the scope; withinclude_public=Truea tenant's document shadows the public one. - By-id lookups (
get_structure_node,get_model_instance, …) only resolve inside the scope: another tenant's id behaves as if it did not exist. - In nested results (trees, spines) the user/job filters apply to the anchor only, so no node is orphaned; flat lists filter every element.
- A
DocumentRefpassed as a selector carries its own tenant; an explicit, different tenant_id raisesNavigationError.
GraphNavigator
Bases: ABC
Engine-agnostic, read-only navigation over the scinr.newton graph.
Obtain an instance with
:func:scinr.newton.navigation.get_graph_navigator or the
:func:scinr.newton.navigation.graph_navigator async context manager rather
than constructing a backend directly.
close
abstractmethod
async
Release any resources this navigator owns.
Never closes a connection handed in from outside (e.g. a shared driver).
connect
abstractmethod
async
Acquire the backend connection and verify it is reachable.
Raises:
| Type | Description |
|---|---|
GraphConnectionError
|
If the engine cannot be reached. |
count_document_model_instances
abstractmethod
async
count_document_model_instances(
document: _Selector,
*,
version: int | None = None,
model_class: str | None = None,
where: _Where = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> int
Count the instances :meth:get_document_model_instances would return.
Accepts the same where= property filter (see
:mod:scinr.newton.navigation.filters) and the same scope filters.
count_info_units
abstractmethod
async
count_info_units(
document: _Selector,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> int
Count the info units of document.
count_model_instances_by_class
abstractmethod
async
count_model_instances_by_class(
model_class: str,
*,
where: _Where = None,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> int
Count the instances :meth:get_model_instances_by_class would return.
Accepts the same where= property filter (see
:mod:scinr.newton.navigation.filters) and the same scope filters.
count_root_documents
abstractmethod
async
count_root_documents(
*,
latest_only: bool = True,
only_folders: bool = False,
only_leaves: bool = False,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> int
Count the documents :meth:list_root_documents would return.
count_structure_nodes
abstractmethod
async
count_structure_nodes(
document: _Selector,
*,
version: int | None = None,
roles: Sequence[str] | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> int
Count the structure nodes :meth:get_structure_nodes would return.
describe_node
abstractmethod
async
describe_node(
node_id: str,
*,
include_source_text: bool = False,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> NodeDescription | None
Return an aggregate view of node_id: info units, decision, extraction, ….
document_exists
abstractmethod
async
document_exists(
path: str,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> bool
Return whether a document exists at path (any version, or a specific one).
execute_raw
async
execute_raw(
query: str,
params: Mapping[str, Any] | None = None,
*,
dialect: str | None = None,
) -> list[dict[str, Any]]
Run a raw, engine-native read query and return raw records.
NON-PORTABLE: query is written in :attr:dialect. Prefer a typed
method; reach for this only for one-off queries the typed API cannot
express.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
An engine-native read query. |
required |
params
|
Mapping[str, Any] | None
|
Query parameters (values are always sent parameterised). |
None
|
dialect
|
str | None
|
If given and it does not equal :attr: |
None
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
A list of plain dict records (never |
Raises:
| Type | Description |
|---|---|
UnsupportedOperationError
|
If this backend has no raw-query path. |
NavigationError
|
If query contains a write clause, or dialect does not match. |
execute_raw_one
async
execute_raw_one(
query: str,
params: Mapping[str, Any] | None = None,
*,
dialect: str | None = None,
) -> dict[str, Any] | None
Like :meth:execute_raw but return the first record or None.
find_shell_model_instances
abstractmethod
async
find_shell_model_instances(
*,
model_class: str | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return likely "shell" instances (only key properties populated).
find_structure_nodes
abstractmethod
async
find_structure_nodes(
*,
title_contains: str | None = None,
node_id: str | None = None,
role: str | None = None,
theme: str | None = None,
document: _Selector | None = None,
where: _Where = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Search structure nodes across all documents.
where= takes {property_name: value | Op} filters on the node —
e.g. where={"role": In(["section", "subsection"])}. See
:mod:scinr.newton.navigation.filters.
get_annotation_coverage
abstractmethod
async
get_annotation_coverage(
document: _Selector,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> AnnotationCoverage | None
Return annotated / unannotated / matched / proposed counts and ratio.
get_catalog_graph
abstractmethod
async
get_catalog_graph(
*,
include_fields: bool = True,
include_relationships: bool = True,
) -> CatalogGraph
Return the whole model catalogue — :CatalogModel / :EntityLabel
nodes plus the declared relationships between them (AGGREGATES and the
domain relationship declarations with their join_via / via_field
metadata).
get_child_documents
abstractmethod
async
get_child_documents(
path: str,
*,
depth: int | None = 1,
version: int | None = None,
is_folder: bool | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Walk IS_COMPOSED_OF downward from path — child documents only.
Flat, deduplicated. depth=1 returns the immediate children.
get_child_nodes
abstractmethod
async
get_child_nodes(
node_id: str,
*,
depth: int | None = 1,
roles: Sequence[str] | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Walk HAS_CHILD downward from node_id. Flat list.
get_document_ancestors
abstractmethod
async
get_document_ancestors(
path: str,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentTree | None
Return the ancestor lineage of path as a single-spine tree.
The result is the root folder-parent, its children a chain leading
down to (but not including) path. Flatten it for a plain list; keep it
to render the hierarchy. None when path is itself a root.
get_document_entities
abstractmethod
async
get_document_entities(
document: _Selector,
*,
label: str | None = None,
version: int | None = None,
depth: int | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[LabeledEntityRef]
Return labeled entities referenced anywhere in document.
created_by_user_id / job_id further narrow by the entity's own
accumulated provenance arrays (an entity reached from this document can
still have been touched by other jobs/users within the same tenant — see
:meth:get_labeled_entities).
get_document_extraction_results
abstractmethod
async
get_document_extraction_results(
document: _Selector,
*,
version: int | None = None,
model_class: str | None = None,
depth: int | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ExtractionResultWithNode]
Return the extraction results of document, each carrying its node.
get_document_leaves
abstractmethod
async
get_document_leaves(
path: str,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Return descendants of path with no outgoing IS_COMPOSED_OF.
get_document_model_decisions
abstractmethod
async
get_document_model_decisions(
document: _Selector,
*,
version: int | None = None,
matched_only: bool | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelDecisionWithNode]
Return the model decisions of document, each carrying its node.
get_document_model_instances
abstractmethod
async
get_document_model_instances(
document: _Selector,
*,
version: int | None = None,
model_class: str | None = None,
where: _Where = None,
depth: int | None = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return every :ModelInstance extracted anywhere in document (deduped).
where= filters the instances by property
({property_name: value | Op}, values matched verbatim) — e.g.
where={"status": "active"}. See
:mod:scinr.newton.navigation.filters.
The scope filters act on the instance's own provenance — see
:meth:get_node_model_instances.
get_document_model_profile
abstractmethod
async
get_document_model_profile(
document: _Selector,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentModelProfile | None
Return how document was semantically catalogued — a roll-up of the
matched and complementary model classes across all its
decisions, with per-class counts, without the individual decisions.
get_document_of_node
abstractmethod
async
get_document_of_node(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentRef | None
Return the document that owns node_id (by traversal, not id-parsing).
get_document_original
async
get_document_original(
document: _Selector,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> OriginalFile | None
Return the original uploaded file of document (latest, or version):
its RawFileRecord plus a single-use stream of the binary.
None when the document is not found in the scope, has no stored
original, or its original does not belong to the document's tenant.
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured, or the binary is missing from the backend. |
get_document_parent
abstractmethod
async
get_document_parent(
path: str,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentRef | None
Return the immediate folder-parent of path, or None for a root.
get_document_source_text
async
get_document_source_text(
document: _Selector,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[PageText]
Return every converted page of document (latest, or version), ordered by page index.
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured. |
get_document_stats
abstractmethod
async
get_document_stats(
path: str,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentStats | None
Return aggregate counts (nodes by role, instances by class, …) for a document.
get_document_tree
abstractmethod
async
get_document_tree(
path: str,
*,
depth: int | None = None,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentTree | None
Return the nested IS_COMPOSED_OF subtree rooted at path.
get_documents
abstractmethod
async
get_documents(
*,
path: str | None = None,
name_contains: str | None = None,
version: int | None = None,
latest_only: bool = True,
is_folder: bool | None = None,
path_prefix: str | None = None,
where: _Where = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Find documents by any combination of filters. Always a list.
Only the filters you pass are applied. Anything looser than the full
(path, version) key can match more than one node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
where
|
_Where
|
Property filters on the |
None
|
Examples:
A bare value is an equality test::
await nav.get_documents(where={"tenant_id": "acme"})
Operator objects for anything else::
from scinr.newton.navigation import In, IsNotNull, StartsWith
await nav.get_documents(where={
"job_id": In(["job-1", "job-2"]),
"created_by_user_id": IsNotNull(),
"raw_file_id": StartsWith("s3://bucket/"),
})
See Also
:mod:scinr.newton.navigation.filters — the full operator set and
the where= contract (property-name rules, verbatim values).
get_documents_for_model_instance
abstractmethod
async
get_documents_for_model_instance(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Return the document(s) that contain instance uid. Always a list.
get_entity_relationships
abstractmethod
async
get_entity_relationships(
uid: str,
*,
direction: Literal["out", "in", "both"] = "both",
rel_type: str | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[EntityRelation]
Return Level-2 field_relationships edges of labeled entity uid.
Discriminated by both endpoints being :LabeledEntity — some of these
types happen to start with HAS_.
get_entity_triples
abstractmethod
async
get_entity_triples(
value_or_uid: str,
*,
direction: Literal["out", "in", "both"] = "both",
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[Triple]
Return triples touching the :Entity identified by value or uid.
get_extraction_result
abstractmethod
async
get_extraction_result(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> ExtractionResultRef | None
Return the extraction result for node_id, or None.
get_extraction_results_for_model_instance
abstractmethod
async
get_extraction_results_for_model_instance(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ExtractionResultRef]
Return the extraction result(s) that reach instance uid. Always a list.
get_graph_summary
abstractmethod
async
get_graph_summary(
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> GraphSummary
Return whole-graph counts by node type and (structural) relationship type.
get_incoming_model_instances
abstractmethod
async
get_incoming_model_instances(
uid: str,
*,
rel_type: str | None = None,
depth: int | None = 1,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return :ModelInstance nodes with an edge into uid (any rel type).
Each carries via_rel and direction="in".
get_info_unit
abstractmethod
async
get_info_unit(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> InfoUnitRef | None
Return the info unit with this uid, or None.
get_info_unit_source_text
async
get_info_unit_source_text(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[PageText]
Return the source pages behind the structure node owning info unit uid.
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured. |
get_info_units
abstractmethod
async
get_info_units(
node_id: str,
*,
order_by: str = "order",
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[InfoUnitRef]
Return the info units of one structure node.
get_labeled_entities
abstractmethod
async
get_labeled_entities(
*,
label: str | None = None,
value: str | None = None,
normalized_value: str | None = None,
where: _Where = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[LabeledEntityRef]
Find labeled entities by label / value / normalised value / where=.
where= takes {property_name: value | Op} filters on the
:LabeledEntity node — e.g.
where={"label": In(["Country", "ProcedureType"])}. See
:mod:scinr.newton.navigation.filters.
The scope filters act on the entity's own denormalized provenance —
same semantics as :meth:get_model_instances_by_class; no traversal
to :Document.
get_labeled_entity
abstractmethod
async
get_labeled_entity(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> LabeledEntityRef | None
Return the labeled entity with this uid, or None.
get_latest_version
abstractmethod
async
get_latest_version(
path: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentRef | None
Return the latest=true document at path, or None.
get_model_decision
abstractmethod
async
get_model_decision(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> ModelDecisionRef | None
Return the model decision for node_id, or None if unannotated.
get_model_instance
abstractmethod
async
get_model_instance(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> ModelInstanceRef | None
Return the :ModelInstance with this uid, or None.
get_model_instance_by_key
abstractmethod
async
get_model_instance_by_key(
model_class: str,
key_fields: Mapping[str, str],
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> ModelInstanceRef | None
Return the instance whose deterministic instance_key uid matches.
key_fields maps each instance_key field name to its value. Values
are normalised (NFKD, accent-stripped, lower-cased, whitespace-collapsed)
before the uid is rebuilt — same as ingestion. The uid embeds the tenant
(see :func:scinr.newton.utils.uid.make_instance_uid), so the lookup
cannot span "all tenants": pass a concrete tenant_id (or
"__public__"); tenant_id=None raises NavigationError. With
include_public=True the tenant's instance is tried first, then the
public one.
get_model_instance_entities
abstractmethod
async
get_model_instance_entities(
uid: str,
*,
label: str | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[LabeledEntityRef]
Return labeled entities that instance uid REFERENCES.
get_model_instance_relationships
abstractmethod
async
get_model_instance_relationships(
uid: str,
*,
direction: Literal["out", "in", "both"] = "both",
rel_type: str | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRelation]
Return every edge between uid and another :ModelInstance.
No relationship-type filtering by default — containment (HAS_*) and
typed cross-references alike, each with its rel_type and
direction.
get_model_instance_subtree
abstractmethod
async
get_model_instance_subtree(
uid: str,
*,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> ModelInstanceTree | None
Return the outgoing-edge subtree rooted at instance uid.
get_model_instances_by_class
abstractmethod
async
get_model_instances_by_class(
model_class: str,
*,
where: _Where = None,
document: _Selector | None = None,
order_by: str | None = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return :ModelInstance nodes of model_class, filtered by where=.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
where
|
_Where
|
Property filters on the instance, as
|
None
|
tenant_id
|
str | None
|
Tenant scope (see the module docstring). It is a scalar
per instance — folded into the |
None
|
include_public
|
bool
|
Add the public instances to tenant_id's. |
False
|
created_by_user_id
|
str | Sequence[str] | None
|
One user or several; matched against the
instance's accumulated |
None
|
job_id
|
str | Sequence[str] | None
|
One job or several; matched against the accumulated
|
None
|
order_by
|
str | None
|
Instance property to sort by (validated as an identifier);
defaults to |
None
|
Examples:
::
from scinr.newton.navigation import In, Gte
await nav.get_model_instances_by_class(
"VariationCodeModel",
where={"procedure_type": In(["ia", "ib"]), "confidence": Gte(0.8)},
order_by="procedure_type",
limit=50,
)
Use :meth:get_model_properties to discover which property names a
class actually carries.
See Also
:mod:scinr.newton.navigation.filters — the full operator set and
the where= contract.
get_model_instances_referencing_entity
abstractmethod
async
get_model_instances_referencing_entity(
uid: str,
*,
model_class: str | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return instances that REFERENCES labeled entity uid (reverse lookup).
get_model_properties
abstractmethod
async
get_model_properties(
model_class: str,
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> dict[str, list[str]]
Return {"declared": [...], "observed": [...]} property names for
model_class — the catalog-declared :ModelField names and the
names actually seen on a sample of its instances.
get_node_ancestors
abstractmethod
async
get_node_ancestors(
node_id: str,
*,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return the ancestors of node_id, ordered root → immediate parent.
get_node_entities
abstractmethod
async
get_node_entities(
node_id: str,
*,
label: str | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[LabeledEntityRef]
Return labeled entities referenced by any model instance under node_id.
REFERENCES always originates from a :ModelInstance; the walk is
node → HAS_EXTRACTION → (HAS_* )* → ModelInstance → REFERENCES.
get_node_for_info_unit
abstractmethod
async
get_node_for_info_unit(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureNodeRef | None
Return the structure node that owns info unit uid.
get_node_model_instances
abstractmethod
async
get_node_model_instances(
node_id: str,
*,
model_class: str | None = None,
where: _Where = None,
depth: int | None = None,
direct_only: bool = False,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return the :ModelInstance nodes extracted at node_id.
Reached via HAS_EXTRACTION then HAS_* containment edges — this is
the "belongs to this node" set, not arbitrary cross-references.
where= filters the instances by property
({property_name: value | Op}, values matched verbatim); see
:mod:scinr.newton.navigation.filters.
The scope filters act on the instance's own denormalized provenance
(tenant_id scalar; user / job match its accumulated
created_by_user_ids / job_ids arrays) — no traversal back up to
:Document is performed, so this stays cheap regardless of corpus
size. See entity_extraction/graph_mapper.py for how these are
written at ingestion time.
get_node_path
abstractmethod
async
get_node_path(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> NodePath | None
Return the document plus the node chain from its root down to node_id.
get_nodes_by_annotated_model
abstractmethod
async
get_nodes_by_annotated_model(
model_class: str,
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return structure nodes whose decision matched model_class.
get_nodes_by_theme
abstractmethod
async
get_nodes_by_theme(
theme: str,
*,
document: _Selector | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return structure nodes carrying theme.
get_nodes_referencing_entity
abstractmethod
async
get_nodes_referencing_entity(
uid: str,
*,
depth: int | None = None,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return structure nodes whose model instances reference entity uid.
Walk: LabeledEntity ← REFERENCES ← ModelInstance ← (HAS_*)* ←
ExtractionResult ← HAS_EXTRACTION ← StructureNode.
get_one_document
abstractmethod
async
get_one_document(
path: str,
version: int,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> DocumentRef | None
Return the document with exactly this (path, version) composite key.
Both arguments are mandatory — this is the unique key. No "latest" resolution happens here.
Returns:
| Type | Description |
|---|---|
DocumentRef | None
|
The document, or |
get_outgoing_model_instances
abstractmethod
async
get_outgoing_model_instances(
uid: str,
*,
rel_type: str | None = None,
depth: int | None = 1,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return :ModelInstance nodes reached by an edge out of uid (any rel type).
Each carries via_rel and direction="out".
get_parent_node
abstractmethod
async
get_parent_node(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureNodeRef | None
Return the HAS_CHILD parent of node_id, or None for a root node.
get_proposed_models
abstractmethod
async
get_proposed_models(
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ProposedModelRef]
Return proposed (new) models with their fields and source node.
get_related_entities
abstractmethod
async
get_related_entities(
uid: str,
rel_type: str,
*,
direction: Literal["out", "in"] = "out",
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[LabeledEntityRef]
Return labeled entities linked to uid by rel_type.
get_related_model_instances
abstractmethod
async
get_related_model_instances(
uid: str,
rel_type: str,
*,
direction: Literal["out", "in"] = "out",
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelInstanceRef]
Return instances linked to uid by rel_type in direction.
get_root_structure_nodes
abstractmethod
async
get_root_structure_nodes(
document: _Selector,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return only the HAS_STRUCTURE (top-level) nodes of document.
get_sibling_nodes
abstractmethod
async
get_sibling_nodes(
node_id: str,
*,
include_self: bool = False,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return the nodes sharing a parent with node_id.
get_structure_node
abstractmethod
async
get_structure_node(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureNodeRef | None
Return the structure node with this composite id, or None.
get_structure_nodes
abstractmethod
async
get_structure_nodes(
document: _Selector,
*,
version: int | None = None,
roles: Sequence[str] | None = None,
title_contains: str | None = None,
theme: str | None = None,
where: _Where = None,
depth: int | None = None,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return structure nodes of document, flat, ordered by appearance.
depth bounds how deep into the HAS_CHILD tree to descend
(None = whole tree). title_contains is a convenience substring
filter on title; where= covers the rest — a
{property_name: value | Op} mapping on the node, e.g.
where={"appearance_order": Gte(3)} (see
:mod:scinr.newton.navigation.filters).
get_structure_nodes_by_ids
abstractmethod
async
get_structure_nodes_by_ids(
node_ids: Sequence[str],
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return the structure nodes with these composite id s, in one lookup.
Only the nodes found in the scope are returned, without duplicates and
in request order (an empty list does not query). Use
StructureNodeRef.source_page_ids for a node's page ids without
reading storage.
get_structure_nodes_for_model_instance
abstractmethod
async
get_structure_nodes_for_model_instance(
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return the structure node(s) that own instance uid via containment.
Always a list: a deduplicated instance_key instance can belong to
several nodes; a shell instance belongs to none ([]).
get_structure_nodes_source_pages
async
get_structure_nodes_source_pages(
node_ids: Sequence[str],
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureNodesSourcePages
Return the verbatim converted markdown pages behind structure nodes
node_ids (StructureNodeRef.id, not the short local node_id).
One graph lookup for all nodes and one storage read per stored tenant
of the nodes; a page shared by several nodes is returned once, in
pages. Missing / out-of-scope nodes, nodes without pages and
pages not found (or of another tenant) are reported in the envelope's
status groups instead of raising. See
:func:scinr.newton.navigation.pages.get_structure_nodes_source_pages.
Raises:
| Type | Description |
|---|---|
StorageError
|
If there are pages to read and no persistent storage backend is configured. |
get_structure_subtree
abstractmethod
async
get_structure_subtree(
node_id: str,
*,
depth: int | None = None,
include_info_units: bool = False,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureTree | None
Return the nested HAS_CHILD subtree rooted at node_id.
get_triples
abstractmethod
async
get_triples(
node_id: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[Triple]
Return subject–predicate–object triples extracted from node_id.
The predicate edge is optional: a subject entity with no predicate edge
to an object of the same extraction result yields a partial Triple
(predicate/object None).
get_unannotated_nodes
abstractmethod
async
get_unannotated_nodes(
document: _Selector,
*,
version: int | None = None,
depth: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[StructureNodeRef]
Return structure nodes of document with no HAS_MODEL_DECISION.
get_version_chain
abstractmethod
async
get_version_chain(
path: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Return the versions at path ordered by the HAS_NEWER_VERSION chain.
list_catalog_models
abstractmethod
async
Return the registered :CatalogModel nodes.
list_document_versions
abstractmethod
async
list_document_versions(
path: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
Return every version at path, ascending by version.
list_entity_labels
abstractmethod
async
list_entity_labels(
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[EntityLabelStat]
Return each distinct :LabeledEntity label with its node count.
list_model_classes_in_use
abstractmethod
async
list_model_classes_in_use(
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ModelClassStat]
Return each distinct ModelInstance.model_class with its count.
list_model_instance_relationship_types
abstractmethod
async
list_model_instance_relationship_types(
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[RelTypeStat]
Return distinct (source model, rel_type, target model, count) triples
for non-containment edges between model instances.
list_node_labels
abstractmethod
async
Return the engine-native node-type names in the graph.
list_node_roles
abstractmethod
async
list_node_roles(
*,
document: _Selector | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[RoleStat]
Return each distinct StructureNode.role with its count.
list_relationship_types
abstractmethod
async
Return relationship-type names.
structural_only=True (default) returns the curated set the pipeline
writes structurally; False returns every type in the graph (can be
thousands — mostly unique normalised Triple predicates).
list_root_documents
abstractmethod
async
list_root_documents(
*,
latest_only: bool = True,
only_folders: bool = False,
only_leaves: bool = False,
limit: int | None = None,
skip: int = 0,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[DocumentRef]
List "parent" documents — those with no incoming IS_COMPOSED_OF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
latest_only
|
bool
|
Keep only |
True
|
only_folders
|
bool
|
Restrict to folder-parents ( |
False
|
only_leaves
|
bool
|
Restrict to leaf documents ( |
False
|
list_themes
abstractmethod
async
Return the themes present in the graph.
neighbors
abstractmethod
async
neighbors(
selector: NodeSelector,
*,
edge_types: Sequence[str] | None = None,
direction: Literal["out", "in", "both"] = "both",
target_types: Sequence[str] | None = None,
depth: int | None = 1,
limit: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[GraphNode]
Return nodes adjacent to selector along the given edges.
ping
abstractmethod
async
Return True if the backend answers a trivial read.
Raises:
| Type | Description |
|---|---|
GraphConnectionError
|
If the engine cannot be reached. |
scoped
scoped(
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> Any
Return a view of this navigator with the scope fixed once.
Every scope-aware method of the returned object gets the given filters
filled in; a call that passes a different tenant_id, asks for
include_public when the view does not, or names user / job values
outside the view's raises NavigationError (it can narrow, never
widen). execute_raw is refused. Lifecycle (connect / close)
is shared with this navigator.
Recommended for any multi-user API layer: the unscoped default is "all tenants", so forgetting a filter would expose everything.
search_info_units
abstractmethod
async
search_info_units(
text: str,
*,
field: Literal["title", "description", "both"] = "both",
document: _Selector | None = None,
limit: int = 25,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[ScoredInfoUnit]
Relevance-search info units by title / description.
shortest_path
abstractmethod
async
shortest_path(
from_selector: NodeSelector,
to_selector: NodeSelector,
*,
max_hops: int = 6,
edge_types: Sequence[str] | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> PathResult | None
Return a shortest path between two nodes, or None.
subgraph
abstractmethod
async
subgraph(
selector: NodeSelector,
*,
depth: int = 2,
edge_types: Sequence[str] | None = None,
max_nodes: int = 500,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> Subgraph
Return a bounded neighbourhood of selector as {nodes, edges}.
Scope
scinr.newton.navigation.scope
navigation/scope.py — The read-side scope of the navigation API.
Every non-catalog navigation method takes four keyword-only arguments::
tenant_id: str | None = None # None = all tenants
include_public: bool = False # add the public documents
created_by_user_id: str | Sequence[str] | None = None # IN
job_id: str | Sequence[str] | None = None # IN
The scope itself (:class:Scope, :func:make_scope, the semantics table and
the errors) lives in :mod:scinr.newton.utils.scope, shared with the storage
repositories so both layers filter identically; this module re-exports it and
adds :func:resolve_selector for document selectors.
Scope
dataclass
Resolved read scope. None in any field means "no filter".
clauses
WHERE fragments for alias; parameters come from :meth:params.
params
Query parameters for :meth:clauses (only the ones in use).
make_scope
make_scope(
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> Scope
Validate the four public filters and resolve them into a :class:Scope.
Raises:
| Type | Description |
|---|---|
ScopeError
|
tenant_id is empty / not a string, or a list filter is empty. |
resolve_selector
Return (path, scope) for a document selector (a path or a DocumentRef).
A DocumentRef carries its own tenant, which is authoritative: the scope
is pinned to it, and an explicit tenant_id that excludes it is an error.
scinr.newton.navigation.scoped
navigation/scoped.py — A navigator view with the tenant / user / job scope fixed.
Reads default to "all tenants" (see :mod:scinr.newton.navigation.scope), so an
API layer that forgets to pass tenant_id would expose everything.
:meth:GraphNavigator.scoped returns a :class:ScopedNavigator that fills the
scope into every call once, and refuses any call that would widen it::
async with graph_navigator() as nav:
acme = nav.scoped(tenant_id="acme", include_public=True)
docs = await acme.get_documents() # acme + public
await acme.get_documents(tenant_id="globex") # NavigationError
A call may narrow the scope (a job_id subset, tenant_id="__public__"
inside an include_public view) but never widen it.
ScopedNavigator
Duck-typed :class:~scinr.newton.navigation.GraphNavigator with a fixed scope.
Scope-aware methods get the view's filters filled in; catalogue methods
(which take no scope) are passed through. execute_raw /
execute_raw_one are refused: raw queries cannot be scoped.
Return Types
scinr.newton.navigation.models
navigation/models.py — Engine-neutral return types for the navigation API.
Every navigator method returns one of these light Pydantic models (or a list of
them). They carry no engine-specific types. Each model exposes an opaque
raw dict holding the backend-native record it was built from — useful for
debugging, not something to depend on across engines.
All models are frozen: navigation is read-only and its results are snapshots.
AnnotationCoverage
Bases: _Base
How much of a document was annotated.
CatalogFieldRef
Bases: _Base
One :ModelField of a :CatalogModel.
CatalogGraph
Bases: _Base
The whole model catalogue: nodes plus the relationships between them.
CatalogModelRef
Bases: _Base
A registered :CatalogModel, optionally with its fields.
CatalogRelation
Bases: _Base
A declared relationship between two catalog entries.
Covers AGGREGATES (model → model containment) and the domain
relationship declarations between :CatalogModel / :EntityLabel nodes
(SPECIFIED_IN, REQUIREMENT_APPLIES_TO, …) carrying join_via /
via_field / from_field / to_field metadata.
DocumentModelProfile
Bases: _Base
How a whole document was semantically catalogued by annotation.
A compact roll-up of the matched and complementary model classes
across every :ModelDecision of the document, with per-class node counts —
without listing the individual decisions.
DocumentRef
Bases: _Base
A :Document node — a leaf document or a folder-parent.
DocumentStats
Bases: _Base
Aggregate counts for one document version.
DocumentTree
Bases: DocumentRef
A DocumentRef plus nested IS_COMPOSED_OF children.
Also used as a single-spine lineage (root → … → target) by
get_document_ancestors: there every node has exactly zero or one child.
EntityLabelStat
Bases: _Base
Count of :LabeledEntity nodes for one label.
EntityRelation
Bases: _Base
A Level-2 field_relationships edge between labeled entities.
ExtractionResultRef
Bases: _Base
A :ExtractionResult — Stage 4 output for a node.
is_triple is derived (model_class == "Triple"), not a stored prop.
ExtractionResultWithNode
GraphNode
Bases: _Base
A type-tagged node returned by the generic power tools.
GraphSummary
Bases: _Base
Whole-graph counts by node type and (structural) relationship type.
InfoUnitRef
Bases: _Base
A :InfoUnit — the smallest citable summary unit.
InfoUnitWithNode
LabeledEntityRef
Bases: _Base
A :LabeledEntity — a deduplicated, labelled entity value.
ModelClassStat
Bases: _Base
Count of :ModelInstance / decision nodes for one model_class.
ModelDecisionRef
Bases: _Base
A :ModelDecision — Stage 3 annotation outcome for a node.
confidence is a free word the annotation LLM emits ("high" /
"medium" / "low" in practice), not a number. coverage_gaps
is a list of strings (often empty).
ModelDecisionWithNode
ModelInstanceRef
Bases: _Base
A :ModelInstance — one extracted record; field values in properties.
ModelInstanceRelation
Bases: _Base
A relationship between two :ModelInstance nodes, with direction.
ModelInstanceTree
NodeDescription
Bases: _Base
An aggregate, human-oriented view of one structure node.
NodePath
Bases: _Base
The chain of structure nodes from a document root down to one node.
NodeSelector
Bases: BaseModel
Identifies a node by type + a unique key/value pair.
Example: NodeSelector(type="ModelInstance", key="uid", value="abc123").
OriginalFile
dataclass
The original uploaded file behind a document (see
navigation.pages.get_document_original).
Not a Pydantic snapshot like the other models: it holds an open,
single-use stream of the binary. Consume it with async for chunk in
original.stream (constant memory) or :meth:read (whole file).
PageText
Bases: _Base
One converted source page, verbatim markdown.
PathResult
Bases: _Base
A path between two nodes (shortest_path).
ProposedFieldRef
Bases: _Base
A single field of a :ProposedModel (or a :SupplementaryField).
ProposedModelRef
RelTypeStat
Bases: _Base
Count of a distinct (source model, rel_type, target model) triple.
RoleStat
Bases: _Base
Count of :StructureNode nodes for one role.
ScoredInfoUnit
StructureNodeRef
Bases: _Base
A :StructureNode (section, subsection, table, row, …).
document_path / document_version are populated only when the query
that produced this ref already carried the owning :Document — they are
never derived by parsing the composite id (which is not safely
parseable).
StructureNodeSourcePages
Bases: _Base
The source pages of one structure node inside a
:class:StructureNodesSourcePages (ids only; the pages themselves are in
the envelope's pages).
StructureNodesSourcePages
Bases: _Base
Source pages of several structure nodes (see
navigation.pages.get_structure_nodes_source_pages).
A page shared by several nodes appears once in pages.
StructureTree
Subgraph
Bases: _Base
A bounded neighbourhood of nodes and edges (subgraph).
ThemeRef
Bases: _Base
A :Theme node, or a distinct theme value in use.
Triple
Bases: _Base
A subject–predicate–object statement (Triple fallback extraction).
predicate / predicate_raw / object are None when the subject
entity has no predicate edge to an object entity of the same extraction
result (a partial / dangling statement).
Filter Operators
scinr.newton.navigation.filters
navigation/filters.py — Engine-neutral property filter operators for where=.
Every list-returning navigation method that filters on node properties accepts a
where argument shaped as dict[str, Any | Op]:
where={"status": "active", "confidence": Gte(0.8), "code": In(["A", "B"])}
A bare value is sugar for :class:Eq. Each :class:Op is a frozen Pydantic
model carrying only its operands — it holds no engine syntax. A concrete backend
(navigation/neo4j/_translate.py for Neo4j) turns each operator into its
native predicate with fully parameterised values.
Property keys are validated against ^[A-Za-z_][A-Za-z0-9_]*$ so they can be
safely interpolated as an identifier; values are never interpolated.
Contains
EndsWith
Eq
Gt
Gte
In
IsNotNull
IsNull
Lt
Lte
Ne
NotIn
Op
Regex
StartsWith
normalize_where
Normalise a raw where= mapping into {validated_key: Op}.
A value that is not already an :class:Op is wrapped in :class:Eq. Keys
are validated with :func:validate_key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
where
|
Mapping[str, Any] | None
|
The user-supplied mapping, or |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Op]
|
A new dict mapping each validated key to an :class: |
dict[str, Op]
|
where is |
Raises:
| Type | Description |
|---|---|
NavigationError
|
On an invalid key or a non-mapping where. |
validate_key
Return key unchanged if it is a safe property identifier, else raise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
A candidate node-property name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The same string. |
Raises:
| Type | Description |
|---|---|
NavigationError
|
If key does not match |
Neo4j Backend
scinr.newton.navigation.neo4j.navigator
navigation/neo4j/navigator.py — Neo4jGraphNavigator.
The default (and, today, only) :class:~scinr.newton.navigation.GraphNavigator
backend. It is assembled from one mixin per method group (_documents,
_structure, …) over the shared _Neo4jRuntime (driver lifecycle + read
helpers). All Cypher lives in the mixins; this module only wires them together.
Neo4jGraphNavigator
Bases: _DocumentsMixin, _StructureMixin, _InfoUnitsMixin, _AnnotationMixin, _ModelInstancesMixin, _EntitiesMixin, _IntrospectionMixin, _PowerMixin, GraphNavigator
Cypher implementation of :class:GraphNavigator.
Construct via :func:scinr.newton.navigation.get_graph_navigator /
:func:scinr.newton.navigation.graph_navigator rather than directly. When
driver is not supplied it reuses the shared async driver from
ingest.config.get_async_driver() and never closes it; pass an explicit
driver to own the connection lifecycle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
driver
|
Any
|
An open |
None
|
database
|
str | None
|
Neo4j database name. Defaults to |
None
|
Source-Text Bridge
Converted pages and the original uploaded file behind structure nodes / an
info unit / a document. Exposed as GraphNavigator methods
(nav.get_structure_nodes_source_pages(), nav.get_document_original(), … —
see the base interface, section I) and as the functions below, which take the
navigator as first argument. The graph lookup takes the scope filters (or the
nav.scoped(tenant_id=...) view); the storage read is then filtered by the
stored tenant of the resolved node / :Document, so a page id or raw_file_id
pointing at another tenant's upload returns nothing.
Structure nodes are read in batches only: get_structure_nodes_source_pages()
returns a StructureNodesSourcePages envelope — each page once in pages, each
node with its page_ids, and the status groups not_found_structure_nodes
(missing or outside the scope), structure_nodes_without_pages and, per node,
not_found_page_ids (missing or of another tenant). get_document_original() returns
an OriginalFile (the RawFileRecord plus a single-use byte stream).
scinr.newton.navigation.pages
navigation/pages.py — Source-text bridge (Group I).
Resolves the verbatim converted source pages — and the original uploaded file —
behind structure nodes / an info unit / a document. Uses the already-abstract
storage layer (storage.factory.get_storage), so it stays engine-agnostic on
the graph side: it only calls the public :class:GraphNavigator methods plus
the storage repositories.
Every function needs a configured, non-none storage backend — a graph
ingested with storage_backend="none" has no page content to return. To get
a structure node's page ids without storage, read source_page_ids from
:meth:GraphNavigator.get_structure_nodes_by_ids.
Structure nodes are read in batches only
(:func:get_structure_nodes_source_pages, one id for a single node): one graph
query for all nodes, one storage read per tenant, and each shared page once.
The same operations are available as :class:GraphNavigator methods
(nav.get_structure_nodes_source_pages(node_ids), nav.get_document_original(path), …),
which delegate here; on a ScopedNavigator they get the view's scope.
Multi-tenancy
The graph lookup takes the usual four scope filters (tenant_id,
include_public, created_by_user_id, job_id) and follows the
navigator's own scope: in a multi-tenant API layer pass them, or use a
:class:~scinr.newton.navigation.ScopedNavigator (nav.scoped(tenant_id=...)),
so another tenant's node or document is never resolved.
The storage read is then also filtered by the stored tenant of the resolved
graph node (defense in depth). Even a page id or raw_file_id pointing at
another tenant's upload returns nothing:
- A structure node / info unit reads only its own
source_page_ids, by id, in the node's tenant (a page of another tenant counts as not found). The:Documentis not resolved and no other page of the document is loaded. - A document reads its upload's pages / original in the
:Document's tenant.
A public node or document only reads public pages; a legacy one without a tenant reads unfiltered. Only the tenant is checked on the storage side: the user / job filters of the scope already selected the node or document in the graph, and the upload's provenance may legitimately differ from it.
get_document_original
async
get_document_original(
nav: GraphNavigator,
document: str | DocumentRef,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> OriginalFile | None
Return the original uploaded file of document (latest version, or
version) as an :class:OriginalFile — its metadata plus a stream of
the binary.
Returns None when the document is not found (in the navigator's
scope), has no stored original (raw_file_id empty), or its original
does not belong to the document's tenant.
Example::
original = await nav.get_document_original(
"folder/report", tenant_id="acme", include_public=True
)
if original is not None:
async for chunk in original.stream:
response.write(chunk)
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured, or the binary is missing from the backend. |
get_document_source_text
async
get_document_source_text(
nav: GraphNavigator,
document: str | DocumentRef,
*,
version: int | None = None,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[PageText]
Return every converted page of document, ordered by page index.
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured. |
get_info_unit_source_text
async
get_info_unit_source_text(
nav: GraphNavigator,
uid: str,
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> list[PageText]
Return the source pages behind the structure node that owns info unit uid.
Raises:
| Type | Description |
|---|---|
StorageError
|
If no persistent storage backend is configured. |
get_structure_nodes_source_pages
async
get_structure_nodes_source_pages(
nav: GraphNavigator,
node_ids: Sequence[str],
*,
tenant_id: str | None = None,
include_public: bool = False,
created_by_user_id: str | Sequence[str] | None = None,
job_id: str | Sequence[str] | None = None,
) -> StructureNodesSourcePages
Return the verbatim converted markdown pages behind structure nodes node_ids.
node_ids are :StructureNode unique id s (StructureNodeRef.id),
not their short local node_id. Duplicates are ignored. The nodes are
looked up in one graph query and their pages read with one storage read
per tenant; a page shared by several nodes is read and returned once, in
pages, and each node lists its page_ids.
Individual ids never raise; they are reported in the envelope's status
groups: not_found_structure_nodes (missing or outside the scope),
structure_nodes_without_pages and, per node, not_found_page_ids.
Example::
result = await nav.get_structure_nodes_source_pages(
["acme::report::v1::sec-1", "acme::report::v1::tbl-3"], tenant_id="acme"
)
for entry in result.nodes:
text = "\n\n".join(result.pages[p].markdown for p in entry.page_ids)
Raises:
| Type | Description |
|---|---|
StorageError
|
If there are pages to read and no persistent storage backend is configured. |