RiC-O — In-Memory Demo (NetworkX)

This notebook demonstrates how to model a RiC-O (Records in Contexts — Ontology) graph using the DRM tools with the NetworkX in-memory backend.

Key concepts:

  • NetworkXGraph stores everything in memory with optional pickle persistence

  • Same DRM entity classes (Thing, Agent, RecordResource, etc.) as Neo4j

  • Same load_ric_o_naf() loader downloads real RiC-O data from GitHub

  • get_subdocuments() traverses _propagate edges (BFS)

  • Cypher queries are supported via an in-memory executor

  • Persistence file is created fresh each run in /tmp/

Steps:

  1. Create an in-memory NetworkX graph with unique persistence path

  2. Load real RiC-O data from the National Archives of France (NAF) example

  3. Initialize propagation properties

  4. Query subdocuments

  5. Inspect the hierarchy

  6. Summary statistics

Why NetworkX?

NetworkX is useful for:

  • Prototyping — no external database needed

  • Testing — fast, deterministic, easy to clean up

  • Learning — see the graph structure without Neo4j overhead

  • Persistence — pickle file survives notebook restarts (optional)

[1]:
import importlib.util

# Check if drm is installed
package_to_check = 'drm'
spec = importlib.util.find_spec(package_to_check)

if spec is None:
    print(f'⚠️ {package_to_check} not installed. Installing...')
    %pip install -q --upgrade drm-tools
    print("✅ Installation complete.")
else:
    print(f'✅ {package_to_check} already installed. Skipping.')
✅ drm already installed. Skipping.
[2]:
import os
import sys
import uuid
from pathlib import Path

# Read .env file manually (no python-dotenv dependency)
def read_env(env_path):
    """Read a .env file and return a dict of key=value pairs."""
    env_vars = {}
    with open(env_path, 'r') as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            if '=' in line:
                key, value = line.split('=', 1)
                env_vars[key.strip()] = value.strip()
    return env_vars

# Find .env in repo root
_env_path = Path.cwd() / ".env"
if not _env_path.exists():
    for parent in Path.cwd().parents:
        candidate = parent / ".env"
        if candidate.exists():
            _env_path = candidate
            break

if _env_path.exists():
    _env_vars = read_env(_env_path)
    for key, value in _env_vars.items():
        os.environ.setdefault(key, value)
    print(f"✅ Loaded .env from {_env_path}")
    print(f"   Found {len(_env_vars)} variables")
else:
    print("⚠️ .env file not found. NetworkX backend doesn't need credentials.")

# Add drm package to path for development
_repo_root = Path.cwd()
if not (_repo_root / "drm").exists():
    for parent in Path.cwd().parents:
        if (parent / "drm").exists():
            _repo_root = parent
            break
# Add repo root to sys.path so local drm loads before PyPI version
if str(_repo_root) not in sys.path:
    sys.path.insert(0, str(_repo_root))
    print(f"✅ Added {_repo_root} to sys.path")

from drm.networkx_graph import NetworkXGraph
from drm.rico_entities import (
    Thing,
    Agent, Person, CorporateBody, AgentName, Appellation,
    Date, Place, PlaceName, Event, Activity,
    RecordResource, Instantiation, Title,
    Group, Name,
)

def section(title):
    """Print a section header."""
    print(f"\n{'=' * 60}")
    print(f"  {title}")
    print(f"{'=' * 60}")

def sub_section(title):
    """Print a subsection header."""
    print(f"\n--- {title} ---")

print("✅ All imports successful.")
✅ Loaded .env from /Users/oriol/Desenvolupament/drm-tools/.env
   Found 5 variables
✅ Added /Users/oriol/Desenvolupament/drm-tools to sys.path
✅ All imports successful.

Step 1: Create in-memory graph with unique persistence path

The NetworkX backend stores data in memory. A pickle file is used for persistence across notebook restarts. Each run creates a unique file using a UUID, so previous runs never interfere.

[3]:
import os
import tempfile

# Generate a unique persistence file for this run
_run_id = uuid.uuid4().hex[:8]
_persistence_file = os.path.join(
    tempfile.gettempdir(),
    f"ric_o_networkx_{_run_id}.pkl"
)

print(f"📁 Persistence file: {_persistence_file}")
print(f"   (unique per run — safe to delete after)")

# Create the in-memory graph
graph = NetworkXGraph(persistence_path=_persistence_file)

print(f"\n✅ NetworkXGraph created.")
print(f"   Nodes in memory: {len(graph._node_attrs)}")
print(f"   Edges in memory: {len(graph._graph.edges())}")
print(f"   Persistence enabled: {graph._persistence_path is not None}")
📁 Persistence file: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_e073bd7a.pkl
   (unique per run — safe to delete after)

✅ NetworkXGraph created.
   Nodes in memory: 0
   Edges in memory: 0
   Persistence enabled: True

Step 2: Load real RiC-O data from the National Archives of France

The DRM tools include a loader that downloads and parses RiC-O RDF/XML data from the ICA-EGAD/RiC-O repository.

What gets loaded:

RDF Type

DRM Entity

Parent

rico:Record

Thing

— (strong node)

rico:Agent

Agent

Thing

rico:CorporateBody

CorporateBody

Agent

rico:Individual

Person

Agent

rico:AgentName

AgentName

Agent/Person

rico:RecordResource

RecordResource

Thing

rico:Instantiation

Instantiation

Thing/RecordResource

rico:Activity

Activity

Thing

Hierarchy example:

Thing (strong)
├── Agent (WeakNode)
│   ├── AgentName (WeakNode of Agent)
│   └── Instantiation (WeakNode of Agent)
└── RecordResource (WeakNode)
    └── Instantiation (WeakNode of RecordResource)

The loader downloads files from GitHub, parses the RDF/XML, and inserts entities into the graph with proper parent-child relationships.

[4]:
from drm.exemples import load_ric_o_naf

section("Loading RiC-O data from National Archives of France")

# Load real RiC-O data
# limit: total entities to load (soft limit)
# max_agents: number of agent files to load
# max_records: number of record resource files to load
stats = load_ric_o_naf(
    graph,
    limit=50,
    max_agents=10,
    max_records=3,
)

print(f"\n📊 Load statistics:")
print(f"  Agents loaded:   {stats['agents']}")
print(f"  Records loaded:  {stats['records']}")
print(f"  Things created:  {stats['things']}")
print(f"  Child entities:  {stats.get('children', '?')}")
print(f"  Total entities:  {stats['total']}")

============================================================
  Loading RiC-O data from National Archives of France
============================================================

📊 Load statistics:
  Agents loaded:   10
  Records loaded:  27
  Things created:  40
  Child entities:  75
  Total entities:  152

Step 3: Initialize propagation properties

Scan the graph and initialize _propagate flags on edges between parent and child nodes. This enables cascade delete behavior.

In NetworkX, this is much faster than Neo4j since everything is in memory.

[5]:
# Check state before init (using internal attributes)
sub_section("Before init_propagation()")
total_before = len(graph._node_attrs)
weak_before = sum(1 for a in graph._node_attrs.values() if a.get('is_weak'))
done_before = sum(1 for a in graph._node_attrs.values() if a.get('_weak_init_done'))
print(f"  Total nodes:          {total_before}")
print(f"  Nodes with is_weak:   {weak_before}")
print(f"  Nodes with _weak_init_done: {done_before}")

# Run initialization
print("\nRunning init_propagation()...")
init_result = graph.init_propagation()
print(f"Result: {init_result}")

# Check state after init
sub_section("After init_propagation()")
total_after = len(graph._node_attrs)
weak_after = sum(1 for a in graph._node_attrs.values() if a.get('is_weak'))
done_after = sum(1 for a in graph._node_attrs.values() if a.get('_weak_init_done'))
print(f"  Total nodes:          {total_after}")
print(f"  Nodes with is_weak:   {weak_after}")
print(f"  Nodes with _weak_init_done: {done_after}")

# Count propagation edges
prop_count = sum(
    1 for u, v, k, d in graph._graph.edges(data=True, keys=True)
    if graph.get_edge_attrs(u, v, d.get('rel_type', k)).get('_propagate') is True
)
print(f"  Edges with _propagate: {prop_count}")

--- Before init_propagation() ---
  Total nodes:          152
  Nodes with is_weak:   0
  Nodes with _weak_init_done: 0

Running init_propagation()...
Result: True

--- After init_propagation() ---
  Total nodes:          152
  Nodes with is_weak:   112
  Nodes with _weak_init_done: 0
  Edges with _propagate: 112

Step 4: Inspect the loaded graph structure

Query the graph to see what entities were loaded and how they are organized. We use the internal _node_attrs and _edge_attrs dicts for inspection, which works regardless of Cypher executor limitations.

[6]:
# Show all Things (strong nodes)
sub_section("Nodes by label")
from collections import Counter
label_counts = Counter(a.get('main_label', 'Node') for a in graph._node_attrs.values())
for label, count in label_counts.most_common():
    print(f"  {label}: {count}")

# Show relations by type
sub_section("Relations by type")
rel_counts = Counter()
for u, v, k, d in graph._graph.edges(data=True, keys=True):
    edge_attrs = graph.get_edge_attrs(u, v, d.get('rel_type', k))
    rel_type = edge_attrs.get('_propagate', False) and 'HAS_*' or d.get('rel_type', k)
    rel_counts[rel_type] += 1
for rel_type, count in rel_counts.most_common():
    print(f"  {rel_type}: {count}")


--- Nodes by label ---
  AgentName: 48
  Thing: 40
  RecordResource: 27
  Instantiation: 27
  Agent: 10

--- Relations by type ---
  HAS_*: 112
[7]:
# Show all Things (strong nodes)
sub_section("All Things (strong nodes)")
things = [
    (nid, a.get('identifier', '?'), a.get('title', a.get('identifier', '?')))
    for nid, a in graph._node_attrs.items()
    if a.get('main_label') == 'Thing'
]
things.sort(key=lambda x: x[1])
for nid, identifier, name in things:
    print(f"  {identifier}: {name}")

--- All Things (strong nodes) ---
  record-000005: record-000005
  record-000016: record-000016
  record-000051: record-000051
  record-003500: record-003500
  record-003529: record-003529
  record-003530: record-003530
  record-003531: record-003531
  record-003532: record-003532
  record-003549: record-003549
  record-003550: record-003550
  record-003551: record-003551
  record-007375: record-007375
  record-009555: record-009555
  thing-rr-recordResource-recordResource-003500-d_1: thing-rr-recordResource-recordResource-003500-d_1
  thing-rr-recordResource-recordResource-003500-d_2: thing-rr-recordResource-recordResource-003500-d_2
  thing-rr-recordResource-recordResource-003500-d_3: thing-rr-recordResource-recordResource-003500-d_3
  thing-rr-recordResource-recordResource-003500-d_4: thing-rr-recordResource-recordResource-003500-d_4
  thing-rr-recordResource-recordResource-007375-d_1: thing-rr-recordResource-recordResource-007375-d_1
  thing-rr-recordResource-recordResource-007375-d_2: thing-rr-recordResource-recordResource-007375-d_2
  thing-rr-recordResource-recordResource-009555-d_1: thing-rr-recordResource-recordResource-009555-d_1
  thing-rr-recordResource-recordResource-009555-d_10: thing-rr-recordResource-recordResource-009555-d_10
  thing-rr-recordResource-recordResource-009555-d_11: thing-rr-recordResource-recordResource-009555-d_11
  thing-rr-recordResource-recordResource-009555-d_12: thing-rr-recordResource-recordResource-009555-d_12
  thing-rr-recordResource-recordResource-009555-d_13: thing-rr-recordResource-recordResource-009555-d_13
  thing-rr-recordResource-recordResource-009555-d_14: thing-rr-recordResource-recordResource-009555-d_14
  thing-rr-recordResource-recordResource-009555-d_15: thing-rr-recordResource-recordResource-009555-d_15
  thing-rr-recordResource-recordResource-009555-d_16: thing-rr-recordResource-recordResource-009555-d_16
  thing-rr-recordResource-recordResource-009555-d_17: thing-rr-recordResource-recordResource-009555-d_17
  thing-rr-recordResource-recordResource-009555-d_18: thing-rr-recordResource-recordResource-009555-d_18
  thing-rr-recordResource-recordResource-009555-d_2: thing-rr-recordResource-recordResource-009555-d_2
  thing-rr-recordResource-recordResource-009555-d_3: thing-rr-recordResource-recordResource-009555-d_3
  thing-rr-recordResource-recordResource-009555-d_4: thing-rr-recordResource-recordResource-009555-d_4
  thing-rr-recordResource-recordResource-009555-d_5: thing-rr-recordResource-recordResource-009555-d_5
  thing-rr-recordResource-recordResource-009555-d_6: thing-rr-recordResource-recordResource-009555-d_6
  thing-rr-recordResource-recordResource-009555-d_7: thing-rr-recordResource-recordResource-009555-d_7
  thing-rr-recordResource-recordResource-009555-d_8: thing-rr-recordResource-recordResource-009555-d_8
  thing-rr-recordResource-recordResource-009555-d_9: thing-rr-recordResource-recordResource-009555-d_9
  thing-rr-recordResource-recordResource-top-003500: thing-rr-recordResource-recordResource-top-003500
  thing-rr-recordResource-recordResource-top-007375: thing-rr-recordResource-recordResource-top-007375
  thing-rr-recordResource-recordResource-top-009555: thing-rr-recordResource-recordResource-top-009555

Step 5: Get subdocuments

Use get_subdocuments() to retrieve all weak descendants of each Thing. This traverses only edges with _propagate=True using BFS.

[8]:
# Show subdocuments for each Thing
for nid, identifier, name in things:
    thing_node = Thing(pk={"identifier": identifier})
    subdocs = graph.get_subdocuments(thing_node)

    sub_section(f"Subdocuments of Thing({identifier})")
    print(f"  Found {len(subdocs)} subdocuments:")

    # Group by label
    by_label = {}
    for sd in subdocs:
        label = sd['label']
        if label not in by_label:
            by_label[label] = []
        by_label[label].append(sd)

    for label in sorted(by_label.keys()):
        items = by_label[label]
        for item in items[:5]:  # Show max 5 per label
            name = item['pk'].get('fullName', item['pk'].get('name', item['pk'].get('agentId', '?')))
            print(f"    - {label}: {name}")
        if len(items) > 5:
            print(f"    ... and {len(items) - 5} more")

--- Subdocuments of Thing(record-000005) ---
  Found 13 subdocuments:
    - Agent: agent-agent-000005
    - AgentName: France. Ministère de la Culture et de la Communication (1959-....)
    - AgentName: Ministère de la Culture
    - AgentName: Ministère de la Culture et de la Communication
    - AgentName: Ministère de la Culture, de la Communication, des Grands Travaux et du Bicentenaire
    - AgentName: Ministère de la Culture et de la Communication
    ... and 7 more

--- Subdocuments of Thing(record-000016) ---
  Found 11 subdocuments:
    - Agent: agent-agent-000016
    - AgentName: France. Ministère de l'Éducation nationale (1828-....)
    - AgentName: Ministère de l'Éducation nationale, de la Jeunesse et de la Vie associative
    - AgentName: Ministère de l'Éducation nationale
    - AgentName: Ministère de l'Éducation nationale, de l'Enseignement supérieur et de la Recherche
    - AgentName: Ministère de la Jeunesse, de l'Éducation nationale et de la Recherche
    ... and 5 more

--- Subdocuments of Thing(record-000051) ---
  Found 4 subdocuments:
    - Agent: agent-agent-000051
    - AgentName: France. Ministère des affaires culturelles. Direction de l'architecture (1959-1978)
    - AgentName: France. Ministère de la culture et de l'environnement. Direction de l'architecture
    - AgentName: France. Secrétariat d'État à la culture. Direction de l'architecture

--- Subdocuments of Thing(record-003500) ---
  Found 0 subdocuments:

--- Subdocuments of Thing(record-003529) ---
  Found 8 subdocuments:
    - Agent: agent-agent-003529
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Bureau de la gestion et du contrôle financiers (1945-1975)
    - AgentName: Bureau de la gestion et du contrôle financier
    - AgentName: Deuxième bureau (Gestion et contrôle financier, Affaires générales-Documentation-Matériel)
    - AgentName: Bureau de la gestion et du contrôle financiers
    - AgentName: Bureau de la comptabilité
    ... and 2 more

--- Subdocuments of Thing(record-003530) ---
  Found 4 subdocuments:
    - Agent: agent-agent-003530
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Division des services administratifs. Bureau des affaires générales (1965-1975)
    - AgentName: DB 3
    - AgentName: BL 3

--- Subdocuments of Thing(record-003531) ---
  Found 7 subdocuments:
    - Agent: agent-agent-003531
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Bureau du personnel (1945-1975)
    - AgentName: Bureau du personnel
    - AgentName: Bureau du personnel et des affaires générales
    - AgentName: Bureau du personnel
    - AgentName: BL 1
    ... and 1 more

--- Subdocuments of Thing(record-003532) ---
  Found 4 subdocuments:
    - Agent: agent-agent-003532
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Division des affaires administratives (1965-1975)
    - AgentName: Services administratifs
    - AgentName: Division des affaires administratives

--- Subdocuments of Thing(record-003549) ---
  Found 2 subdocuments:
    - Agent: agent-agent-003549
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section des bibliothèques d'étude et de recherche (1971-1975)

--- Subdocuments of Thing(record-003550) ---
  Found 2 subdocuments:
    - Agent: agent-agent-003550
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section des affaires communes (1971-1975)

--- Subdocuments of Thing(record-003551) ---
  Found 3 subdocuments:
    - Agent: agent-agent-003551
    - AgentName: France. Direction des bibliothèques et de la lecture publique. Services techniques. Section de la lecture publique (1968-1975)
    - AgentName: Service de la lecture publique

--- Subdocuments of Thing(record-007375) ---
  Found 0 subdocuments:

--- Subdocuments of Thing(record-009555) ---
  Found 0 subdocuments:

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_1) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_2) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_3) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-003500-d_4) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-007375-d_1) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-007375-d_2) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_1) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_10) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_11) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_12) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_13) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_14) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_15) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_16) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_17) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_18) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_2) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_3) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_4) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_5) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_6) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_7) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_8) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-009555-d_9) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-003500) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-007375) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

--- Subdocuments of Thing(thing-rr-recordResource-recordResource-top-009555) ---
  Found 2 subdocuments:
    - Instantiation: ?
    - RecordResource: ?

Step 6: Query the hierarchy paths

Visualize the full hierarchy for a sample Thing by walking the NetworkX graph directly. This avoids Cypher executor limitations for path traversal.

[9]:
def _walk_hierarchy(graph, start_id, max_depth=6):
    """Walk the graph from start_id and return all reachable nodes."""
    paths = []
    queue = [(start_id, [start_id], [])]

    while queue:
        current, path_nodes, path_edges = queue.pop(0)

        if len(path_nodes) > 1:
            paths.append((path_nodes, path_edges))

        if len(path_nodes) >= max_depth:
            continue

        # MultiDiGraph.edges(u, data=True, keys=True) yields (u, v, key, data)
        for u, v, edge_key, edge_data in graph._graph.edges(current, data=True, keys=True):
            edge_attrs = graph.get_edge_attrs(u, v, edge_data.get('rel_type', edge_key))
            rel_type = edge_attrs.get('_propagate', False) and 'parent' or edge_data.get('rel_type', 'unknown')
            paths.append((path_nodes + [v], path_edges + [(u, v, rel_type)]))
            if len(path_nodes) < max_depth - 1:
                queue.append((v, path_nodes + [v], path_edges + [(u, v, rel_type)]))

    return paths

# Walk hierarchy for first Thing
if things:
    first_nid, first_id, first_name = things[0]

    sub_section(f"Hierarchy: Thing({first_id})")

    paths = _walk_hierarchy(graph, first_nid)
    for path_nodes, path_edges in paths:
        parts = []
        for node_id in path_nodes:
            attrs = graph._node_attrs.get(node_id, {})
            label = attrs.get('main_label', 'Node')
            name = attrs.get('fullName', attrs.get('name', attrs.get('agentId', attrs.get('recordResourceId', str(node_id)))))
            parts.append(f"{label}({name})")
        for src, dst, rel_type in path_edges:
            parts.append(f"-[{rel_type}]->")
        print(f"  {' → '.join(parts)}")


--- Hierarchy: Thing(record-000005) ---
  Thing(string) → Agent(agent-agent-000005) → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(France. Ministère de la Culture et de la Communication (1959-....)) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture, de la Communication, des Grands Travaux et du Bicentenaire) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de l'Environnement) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Secrétariat d'État à la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère des Affaires culturelles) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère des Affaires culturelles et de l'Environnement) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Francophonie) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(France. Ministère de la Culture et de la Communication (1959-....)) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture, de la Communication, des Grands Travaux et du Bicentenaire) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Communication) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de l'Environnement) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Secrétariat d'État à la Culture) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère des Affaires culturelles) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère des Affaires culturelles et de l'Environnement) → -[parent]-> → -[parent]->
  Thing(string) → Agent(agent-agent-000005) → AgentName(Ministère de la Culture et de la Francophonie) → -[parent]-> → -[parent]->

Step 7: Inspect specific entities

Look at the properties of loaded entities to understand the data model.

[10]:
# Show sample Agent entities
sub_section("Sample Agent entities")
agents = [
    (nid, a.get('agentId', '?'), a.get('label', 'No label'),
     a.get('beginningDate', ''), a.get('endDate', ''))
    for nid, a in graph._node_attrs.items()
    if a.get('main_label') in ('Agent', 'CorporateBody', 'Person', 'Family')
]
agents.sort(key=lambda x: x[1])
for nid, agent_id, label, birth, death in agents[:5]:
    print(f"  {agent_id}: {label}")
    print(f"    Born: {birth or 'N/A'}, Died: {death or 'N/A'}")

# Show sample RecordResource entities
sub_section("Sample RecordResource entities")
record_resources = [
    (nid, a.get('recordResourceId', '?'), a.get('title', ''),
     a.get('beginningDate', ''), a.get('endDate', ''))
    for nid, a in graph._node_attrs.items()
    if a.get('main_label') == 'RecordResource'
]
record_resources.sort(key=lambda x: x[1])
for nid, rr_id, title, from_date, to_date in record_resources[:5]:
    print(f"  {rr_id}: {title or 'No title'}")
    print(f"    Date range: {from_date or 'N/A'}{to_date or 'N/A'}")

--- Sample Agent entities ---
  agent-agent-000005: No label
    Born: 1959-01-08, Died: N/A
  agent-agent-000016: No label
    Born: 1828-02-10, Died: N/A
  agent-agent-000051: No label
    Born: 1959-01-01, Died: 1978-12-31
  agent-agent-003529: No label
    Born: 1945-08-18, Died: 1975-12-31
  agent-agent-003530: No label
    Born: 1965-01-01, Died: 1975-12-31

--- Sample RecordResource entities ---
  recordResource-recordResource-003500-d_1: LUDOVIC VITET (1802-1873)
    Date range: N/A — N/A
  recordResource-recordResource-003500-d_2: EUGENE AUBRY-VITET (1845-1930) 1
    Date range: N/A — N/A
  recordResource-recordResource-003500-d_3: FAMILLE COSTA DE BEAUREGARD 1.
    Date range: N/A — N/A
  recordResource-recordResource-003500-d_4: COLLECTION DE PHOTOGRAPHIES.
    Date range: N/A — N/A
  recordResource-recordResource-007375-d_1: FRANCE
    Date range: N/A — N/A

Step 8: Summary statistics

Show the final state of the RiC-O graph.

[11]:
section("Final summary")

# Nodes by label
sub_section("Nodes by label")
label_counts = Counter(a.get('main_label', 'Node') for a in graph._node_attrs.values())
for label, count in label_counts.most_common():
    print(f"  {label}: {count}")

# Propagation edges
sub_section("Propagation edges by type")
prop_rel_counts = Counter()
for u, v, k, d in graph._graph.edges(data=True, keys=True):
    edge_attrs = graph.get_edge_attrs(u, v, d.get('rel_type', k))
    if edge_attrs.get('_propagate') is True:
        rel_type = d.get('rel_type', k) or edge_attrs.get('_propagate', False) and 'parent' or 'unknown'
        prop_rel_counts[rel_type] += 1
for rel_type, count in prop_rel_counts.most_common():
    print(f"  {rel_type}: {count}")

# All Things
sub_section("All Things")
for nid, identifier, name in things:
    print(f"  {identifier}: {name}")

# Subdocument counts
sub_section("Subdocuments per Thing")
for nid, identifier, name in things:
    thing_node = Thing(pk={"identifier": identifier})
    subdocs = graph.get_subdocuments(thing_node)
    print(f"  {identifier}: {len(subdocs)} subdocuments")

# Persistence file info
sub_section("Persistence file")
print(f"  Path: {_persistence_file}")
if os.path.exists(_persistence_file):
    size = os.path.getsize(_persistence_file)
    print(f"  Size: {size:,} bytes ({size / 1024:.1f} KB)")
else:
    print("  File not found (data only in memory)")

print("\n" + "=" * 60)
print("  ✅ RiC-O NetworkX demo completed successfully!")
print("=" * 60)

============================================================
  Final summary
============================================================

--- Nodes by label ---
  AgentName: 48
  Thing: 40
  RecordResource: 27
  Instantiation: 27
  Agent: 10

--- Propagation edges by type ---
  HAS_AGENTNAME: 48
  HAS_RECORDRESOURCE: 27
  HAS_INSTANTIATION: 27
  HAS_AGENT: 10

--- All Things ---
  record-000005: record-000005
  record-000016: record-000016
  record-000051: record-000051
  record-003500: record-003500
  record-003529: record-003529
  record-003530: record-003530
  record-003531: record-003531
  record-003532: record-003532
  record-003549: record-003549
  record-003550: record-003550
  record-003551: record-003551
  record-007375: record-007375
  record-009555: record-009555
  thing-rr-recordResource-recordResource-003500-d_1: thing-rr-recordResource-recordResource-003500-d_1
  thing-rr-recordResource-recordResource-003500-d_2: thing-rr-recordResource-recordResource-003500-d_2
  thing-rr-recordResource-recordResource-003500-d_3: thing-rr-recordResource-recordResource-003500-d_3
  thing-rr-recordResource-recordResource-003500-d_4: thing-rr-recordResource-recordResource-003500-d_4
  thing-rr-recordResource-recordResource-007375-d_1: thing-rr-recordResource-recordResource-007375-d_1
  thing-rr-recordResource-recordResource-007375-d_2: thing-rr-recordResource-recordResource-007375-d_2
  thing-rr-recordResource-recordResource-009555-d_1: thing-rr-recordResource-recordResource-009555-d_1
  thing-rr-recordResource-recordResource-009555-d_10: thing-rr-recordResource-recordResource-009555-d_10
  thing-rr-recordResource-recordResource-009555-d_11: thing-rr-recordResource-recordResource-009555-d_11
  thing-rr-recordResource-recordResource-009555-d_12: thing-rr-recordResource-recordResource-009555-d_12
  thing-rr-recordResource-recordResource-009555-d_13: thing-rr-recordResource-recordResource-009555-d_13
  thing-rr-recordResource-recordResource-009555-d_14: thing-rr-recordResource-recordResource-009555-d_14
  thing-rr-recordResource-recordResource-009555-d_15: thing-rr-recordResource-recordResource-009555-d_15
  thing-rr-recordResource-recordResource-009555-d_16: thing-rr-recordResource-recordResource-009555-d_16
  thing-rr-recordResource-recordResource-009555-d_17: thing-rr-recordResource-recordResource-009555-d_17
  thing-rr-recordResource-recordResource-009555-d_18: thing-rr-recordResource-recordResource-009555-d_18
  thing-rr-recordResource-recordResource-009555-d_2: thing-rr-recordResource-recordResource-009555-d_2
  thing-rr-recordResource-recordResource-009555-d_3: thing-rr-recordResource-recordResource-009555-d_3
  thing-rr-recordResource-recordResource-009555-d_4: thing-rr-recordResource-recordResource-009555-d_4
  thing-rr-recordResource-recordResource-009555-d_5: thing-rr-recordResource-recordResource-009555-d_5
  thing-rr-recordResource-recordResource-009555-d_6: thing-rr-recordResource-recordResource-009555-d_6
  thing-rr-recordResource-recordResource-009555-d_7: thing-rr-recordResource-recordResource-009555-d_7
  thing-rr-recordResource-recordResource-009555-d_8: thing-rr-recordResource-recordResource-009555-d_8
  thing-rr-recordResource-recordResource-009555-d_9: thing-rr-recordResource-recordResource-009555-d_9
  thing-rr-recordResource-recordResource-top-003500: thing-rr-recordResource-recordResource-top-003500
  thing-rr-recordResource-recordResource-top-007375: thing-rr-recordResource-recordResource-top-007375
  thing-rr-recordResource-recordResource-top-009555: thing-rr-recordResource-recordResource-top-009555

--- Subdocuments per Thing ---
  record-000005: 13 subdocuments
  record-000016: 11 subdocuments
  record-000051: 4 subdocuments
  record-003500: 0 subdocuments
  record-003529: 8 subdocuments
  record-003530: 4 subdocuments
  record-003531: 7 subdocuments
  record-003532: 4 subdocuments
  record-003549: 2 subdocuments
  record-003550: 2 subdocuments
  record-003551: 3 subdocuments
  record-007375: 0 subdocuments
  record-009555: 0 subdocuments
  thing-rr-recordResource-recordResource-003500-d_1: 2 subdocuments
  thing-rr-recordResource-recordResource-003500-d_2: 2 subdocuments
  thing-rr-recordResource-recordResource-003500-d_3: 2 subdocuments
  thing-rr-recordResource-recordResource-003500-d_4: 2 subdocuments
  thing-rr-recordResource-recordResource-007375-d_1: 2 subdocuments
  thing-rr-recordResource-recordResource-007375-d_2: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_1: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_10: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_11: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_12: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_13: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_14: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_15: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_16: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_17: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_18: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_2: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_3: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_4: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_5: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_6: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_7: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_8: 2 subdocuments
  thing-rr-recordResource-recordResource-009555-d_9: 2 subdocuments
  thing-rr-recordResource-recordResource-top-003500: 2 subdocuments
  thing-rr-recordResource-recordResource-top-007375: 2 subdocuments
  thing-rr-recordResource-recordResource-top-009555: 2 subdocuments

--- Persistence file ---
  Path: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_e073bd7a.pkl
  Size: 60,780 bytes (59.4 KB)

============================================================
  ✅ RiC-O NetworkX demo completed successfully!
============================================================

Cleanup

Close the graph and optionally delete the persistence file.

[12]:
graph.close()
print("✅ NetworkXGraph closed.")

# Clean up persistence file
if os.path.exists(_persistence_file):
    os.remove(_persistence_file)
    print(f"✅ Persistence file deleted: {_persistence_file}")
else:
    print(f"ℹ️  No persistence file to clean up")
✅ NetworkXGraph closed.
✅ Persistence file deleted: /var/folders/rm/5_s35r9j3198mxxpvlpfx8700000gn/T/ric_o_networkx_e073bd7a.pkl