Skip to content

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

get_graph_navigator() -> GraphNavigator

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:GraphNavigator.close — or use :func:graph_navigator instead.

Raises:

Type Description
ConfigurationError

If graph_backend is unknown, or the library is not configured.

GraphConnectionError

If the engine is unreachable.

graph_navigator async

graph_navigator() -> AsyncIterator[GraphNavigator]

Async context manager that yields a connected navigator and closes it.

Example

async with graph_navigator() as nav: ... roots = await nav.list_root_documents()

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.models and are engine-neutral.
  • A single-get method returns SomeRef | None only 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_DEPTH as a runaway-traversal guard (not a hard ceiling). depth=1 = direct only. An explicit n is 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 = None and skip: int = 0 and 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) raise NavigationError when the path exists in several tenants of the scope; with include_public=True a 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 DocumentRef passed as a selector carries its own tenant; an explicit, different tenant_id raises NavigationError.

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

close() -> None

Release any resources this navigator owns.

Never closes a connection handed in from outside (e.g. a shared driver).

connect abstractmethod async

connect() -> None

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:dialect, the call fails immediately.

None

Returns:

Type Description
list[dict[str, Any]]

A list of plain dict records (never *Ref models).

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 :Document node, as {property_name: value | Op}. A bare value means equality; the operator objects (Eq, In, Gte, Contains, IsNotNull, …) cover the rest. Values are matched verbatim and always parameterised, and are ANDed with the other arguments and with latest_only.

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 {property_name: value | Op}. A bare value means equality. Values are matched verbatim — normalise them yourself (instance-key / entity values are stored lower-cased & accent-stripped by ingestion; see :func:scinr.newton.utils.uid.normalize_key).

None
tenant_id str | None

Tenant scope (see the module docstring). It is a scalar per instance — folded into the uid at ingestion, see :func:scinr.newton.utils.uid.make_instance_uid. A plain indexed property match, not a traversal up to :Document — unlike document, its cost does not depend on corpus size.

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 created_by_user_ids array (a dedup-merged instance can carry several).

None
job_id str | Sequence[str] | None

One job or several; matched against the accumulated job_ids array, same semantics as created_by_user_id.

None
order_by str | None

Instance property to sort by (validated as an identifier); defaults to uid.

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 None if that exact pair does not exist.

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(
    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(
    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

list_catalog_models(
    *, include_fields: bool = False
) -> list[CatalogModelRef]

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

list_node_labels() -> list[str]

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

list_relationship_types(
    *, structural_only: bool = True
) -> list[str]

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 latest=true documents (default).

True
only_folders bool

Restrict to folder-parents (is_folder = true).

False
only_leaves bool

Restrict to leaf documents (is_folder = false). Mutually exclusive with only_folders.

False

list_themes abstractmethod async

list_themes() -> list[ThemeRef]

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

ping() -> bool

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

clauses(
    alias: str,
    kind: ScopeKind = "scalar",
    *,
    tenant: bool = True,
) -> list[str]

WHERE fragments for alias; parameters come from :meth:params.

params

params(
    *, tenant: bool = True, provenance: bool = True
) -> dict[str, Any]

Query parameters for :meth:clauses (only the ones in use).

with_tenant

with_tenant(tenant: str) -> Scope

Same scope pinned to a single (already stored) tenant.

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

resolve_selector(
    document: Any, scope: Scope
) -> tuple[str, Scope]

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.

scoped

scoped(**kwargs: Any) -> ScopedNavigator

Narrow this view further (never widen it).

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

Bases: ExtractionResultRef

An ExtractionResultRef annotated with its owning structure node.

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

Bases: InfoUnitRef

An InfoUnitRef annotated with its owning structure node.

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

Bases: ModelDecisionRef

A ModelDecisionRef annotated with its owning structure node.

ModelInstanceRef

Bases: _Base

A :ModelInstance — one extracted record; field values in properties.

ModelInstanceRelation

Bases: _Base

A relationship between two :ModelInstance nodes, with direction.

ModelInstanceTree

Bases: ModelInstanceRef

A ModelInstanceRef plus its nested outgoing-edge children.

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).

read async

read() -> bytes

Read the whole (remaining) stream into memory.

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

Bases: _Base

A :ProposedModel suggested during annotation when nothing matched.

name property

name: str | None

Alias for :attr:schema_name.

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

Bases: InfoUnitWithNode

An InfoUnitWithNode plus a relevance score (search results).

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

Bases: StructureNodeRef

A StructureNodeRef plus its nested HAS_CHILD children.

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

Bases: Op

Substring match: value appears somewhere in the (string) field.

EndsWith

Bases: Op

The (string) field ends with value.

Eq

Bases: Op

field == value.

Gt

Bases: Op

field > value.

Gte

Bases: Op

field >= value.

In

Bases: Op

field IN values.

IsNotNull

Bases: Op

field IS NOT NULL — the property is present and non-null.

IsNull

Bases: Op

field IS NULL — the property is absent or explicitly null.

Lt

Bases: Op

field < value.

Lte

Bases: Op

field <= value.

Ne

Bases: Op

field != value.

NotIn

Bases: Op

NOT (field IN values).

Op

Bases: BaseModel

Base class for all where= filter operators. Frozen and immutable.

describe

describe() -> str

Return a short human-readable form, e.g. ">= 0.8". For logs/errors.

Regex

Bases: Op

The (string) field fully matches the regular expression pattern.

StartsWith

Bases: Op

The (string) field starts with value.

normalize_where

normalize_where(
    where: Mapping[str, Any] | None,
) -> dict[str, Op]

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 None.

required

Returns:

Type Description
dict[str, Op]

A new dict mapping each validated key to an :class:Op. Empty when

dict[str, Op]

where is None or empty.

Raises:

Type Description
NavigationError

On an invalid key or a non-mapping where.

validate_key

validate_key(key: str) -> str

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 ^[A-Za-z_][A-Za-z0-9_]*$.

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 neo4j.AsyncDriver. Optional.

None
database str | None

Neo4j database name. Defaults to config.neo4j_database.

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 :Document is 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.