From 12d61b92dfc28f4f809929dff2b6f56fbc956984 Mon Sep 17 00:00:00 2001 From: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:37:24 +0530 Subject: [PATCH] docs: add Temporal & Distance Intelligence reference pages with accurate API (#650) - Add docs/reference/temporal.md: full Temporal Intelligence reference covering bi-temporal model (TemporalBound.OPEN sentinel, BiTemporalFact.from_relationship() factory), TemporalGraphQuery (query_at_time, reconstruct_at_time, query_time_range, find_temporal_paths, analyze_evolution, validate_temporal_consistency), TemporalPatternDetector, TemporalReasoningEngine with all 13 Allen interval relations over TemporalInterval objects, TemporalNormalizer (returns Optional[Tuple[datetime, datetime]]), TemporalQueryRewriter.rewrite() returning TemporalQueryResult, and TemporalVersionManager with SQLite storage and correct method names (list_versions, compare_versions, get_version, apply_revision, validate_snapshot, verify_checksum) - Add docs/reference/distance.md: Distance Intelligence reference with corrected SimilarityCalculator API (pairwise_similarity, batch_similarity, find_most_similar) and semantic neighborhood / proximity-blended retrieval patterns - Update docs/reference/kg.md: expand Exported Classes table to include all TemporalPatternDetector, TemporalInterval, IntervalRelation, TemporalQueryResult, AlgorithmTrackerWithProvenance, AlgorithmRegistry, ProvenanceTracker, SeedManager, KGConfig; fix all temporal code examples to use correct constructors and method names - Update docs/reference/context.md: add Distance Intelligence section - Update docs/index.md: add v0.3.0 release accordion with feature highlights - Update docs/docs.json: wire temporal and distance pages into Modules navigation --- docs/docs.json | 2 + docs/index.md | 40 +- docs/reference/context.md | 76 +++- docs/reference/distance.md | 615 ++++++++++++++++++++++++++ docs/reference/kg.md | 219 +++++++-- docs/reference/temporal.md | 883 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1798 insertions(+), 37 deletions(-) create mode 100644 docs/reference/distance.md create mode 100644 docs/reference/temporal.md diff --git a/docs/docs.json b/docs/docs.json index 8cd1f3f4..4c47a653 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -120,6 +120,8 @@ "pages": [ "reference/context", "reference/kg", + "reference/temporal", + "reference/distance", "reference/semantic_extract", "reference/reasoning", "reference/ontology" diff --git a/docs/index.md b/docs/index.md index 4de8e1a3..738c1ce7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,10 +15,12 @@ If your stack can't answer those questions with a traceable record, you have a g **Semantica closes that gap.** It's the context and accountability layer that sits beneath your existing agent framework: not a replacement for LangChain or LlamaIndex, but the infrastructure that makes their outputs trustworthy. -
+
1,000+
passing tests
-
25+
modules
-
12
LLM providers
+
29
modules
+
13
LLM providers
+
6,000×
search speedup
+
109
REST endpoints
MIT
open source
@@ -300,11 +302,37 @@ pip install semantica==0.5.0 -- **Temporal Intelligence** — 6-PR system: temporal data model, point-in-time queries, Allen interval algebra (all 13 relations), OWL-Time export -- **Knowledge Explorer API** — Full FastAPI backend: 99 tests, 12 export formats, WebSocket progress, thread-safe sessions, audit trail -- **Ontology Foundations** — SHACL generation/validation, SKOS vocabulary, ontology alignment API, diff & migration tooling +Released **April 8, 2026** + +- **Temporal Intelligence** — Bi-temporal data model (`BiTemporalFact`, `TemporalBound`), point-in-time query engine, Allen interval algebra (all 13 relations), `TemporalNormalizer` (zero LLM calls), `TemporalQueryRewriter`, OWL-Time export +- **Knowledge Explorer API** — Full FastAPI backend: 99 integration tests, 12 export formats, WebSocket progress, thread-safe `RLock` sessions, audit trail, rollback protection +- **Ontology Foundations** — SHACL generation/validation (basic/standard/strict tiers), SKOS vocabulary, ontology alignment API, diff & migration with change impact analysis - **Datalog Reasoning** — Pure-Python bottom-up semi-naive fixpoint, recursive Horn clause rules, guaranteed termination - **Agno Integration** — 5 components: graph-backed memory, multi-hop GraphRAG, decision toolkit, KG toolkit, shared team context; 110 tests +- **Novita AI provider** — OpenAI-compatible provider for 200+ open-weight models + +```bash +pip install semantica==0.4.0 +``` + + + + + +Released **March 10, 2026** + +- **Context Graph completeness** — `valid_from`/`valid_until` on every node, `find_active_nodes()` temporal filtering, `get_neighbors(min_weight)` confidence-filtered traversal +- **Cross-graph navigation** — `link_graph()`, `navigate_to()`, `resolve_links()`, `cross_graph_path()` for multi-graph agent architectures +- **Decision tracking system** — Full lifecycle: `record_decision()` → `add_causal_relationship()` → `find_similar_decisions()` → `trace_decision_chain()` → `analyze_decision_impact()` → `check_decision_rules()` → `get_decision_insights()` +- **Advanced KG algorithms** — Node2Vec embeddings, betweenness / closeness / eigenvector centrality, Louvain community detection +- **Policy management** — Versioned policy storage with policy exception tracking and approver audit trail +- **Vector store** — Hybrid search (dense + sparse), PgVector HNSW/IVFFlat indexing with JSONB metadata filtering +- **Apache AGE backend** — Full graph store support for PostgreSQL-native AGE graphs +- **ResourceScheduler fix** — `RLock` deadlock fix for concurrent agent pipelines + +```bash +pip install semantica==0.3.0 +``` diff --git a/docs/reference/context.md b/docs/reference/context.md index e013667e..4768edaf 100644 --- a/docs/reference/context.md +++ b/docs/reference/context.md @@ -443,7 +443,81 @@ print("Nodes: {}, Edges: {}".format(stats["node_count"], stats["edge_count"])) | `cross_graph_path(source_node_id, target_graph, target_node_id, max_hops)` | `Dict` | Shortest path across linked graphs | | `clear()` | `None` | Reset graph state and all indexes | -### Cross-Graph Navigation +### Distance Intelligence (v0.5.0) + +`ContextGraph` exposes a full Distance Intelligence API for exploring semantic neighborhoods and blending proximity into retrieval. + + + Full Distance Intelligence reference — distance matrices, API endpoints, embedding cache, Explorer UI — is covered in the dedicated [Distance Intelligence](distance) page. This section documents the context-layer API. + + +### Neighbors with Distance Metadata + +Pass `include_distance_metadata=True` to `get_neighbors()` to receive distance band, confidence decay, and path information alongside every neighbor: + +```python +graph = ContextGraph(advanced_analytics=True) + +# ... populate graph ... + +neighbors = graph.get_neighbors( + "python", + hops=3, + include_distance_metadata=True, + min_weight=0.3, # exclude low-confidence edges +) + +for n in neighbors: + print( + f"{n['node_id']:15s} " + f"band={n['distance_band']:10s} " + f"decay={n['confidence_decay']:.3f} " + f"hops={n['hop_count']}" + ) +``` + +| Added field | Type | Description | +| :---------- | :---- | :----------- | +| `distance_band` | `str` | `"direct"` (1 hop) / `"near"` (2) / `"mid-range"` (3–4) / `"distant"` (5+) | +| `confidence_decay` | `float` | `edge_weight ^ hop_count` — decays with each hop | +| `path_to_anchor` | `List[str]` | Shortest path from anchor node to this neighbor | +| `hop_count` | `int` | BFS depth from anchor | + +### Proximity-Blended Retrieval + +Set `proximity_weight` on `AgentContext` to blend graph proximity into every `retrieve()` and `find_precedents()` call: + +```python +context = AgentContext( + vector_store=VectorStore(backend="faiss", dimension=768), + knowledge_graph=ContextGraph(advanced_analytics=True), + proximity_weight=0.3, # 0.7×semantic + 0.3×proximity +) + +# combined_score is returned alongside semantic_score and proximity_score +results = context.retrieve("web API frameworks", max_results=10) +for r in results: + print( + f"[{r['combined_score']:.3f}] " + f"semantic={r['semantic_score']:.3f} " + f"proximity={r['proximity_score']:.3f} " + f"{r['content'][:60]}" + ) + +# Override weight per-call +precedents = context.find_precedents( + "infrastructure scaling decisions", + proximity_weight=0.5, + limit=5, +) +``` + + + `proximity_weight=0.0` disables proximity blending entirely (pure semantic). `proximity_weight=1.0` returns results ranked purely by graph proximity to the query anchor. Values between `0.2`–`0.4` work well for most production use cases. + + + +## Cross-Graph Navigation Link multiple independent `ContextGraph` instances so agents can traverse across problem spaces: diff --git a/docs/reference/distance.md b/docs/reference/distance.md new file mode 100644 index 00000000..376bda3e --- /dev/null +++ b/docs/reference/distance.md @@ -0,0 +1,615 @@ +--- +title: "Distance Intelligence" +description: "Semantic neighborhoods, N×N distance matrices, ego-mode exploration, proximity-blended retrieval, and embedding cache optimization." +icon: "radar" +--- + +Distance Intelligence gives every node in your knowledge graph a **semantic neighborhood** — making it possible to answer not just "is A connected to B?" but "how semantically close is A to B, and what lies in between?" + +Introduced in **v0.5.0**, Distance Intelligence operates across three layers: + +
+
+
Distance Matrices
+
N×N upper-triangle semantic distance between any node set
+
+
+
Semantic Neighborhoods
+
BFS ego-graphs with confidence decay and distance band classification
+
+
+
Proximity Blending
+
Combine semantic similarity with graph proximity in retrieval
+
+
+
10× Cache
+
Graph revision–based embedding cache avoids redundant re-computation
+
+
+ + +## Distance Bands + +Every neighbor result is classified into one of four distance bands based on hop count and semantic similarity: + +| Band | Hop count | Meaning | Explorer color | +| :---- | :-------- | :------- | :------------- | +| `direct` | 1 | Immediate neighbor — strong semantic overlap | Green | +| `near` | 2 | One-hop away — closely related concept | Teal | +| `mid-range` | 3–4 | Conceptually related but some separation | Yellow | +| `distant` | 5+ | Weak structural connection | Red | + +Distance bands flow through the entire system: retrieval results, path responses, API endpoints, and the Explorer Ego Mode visualization all use the same four-tier classification. + + +## Quick Start + + + + The simplest entry point: call `get_neighbors()` with `include_distance_metadata=True`: + + ```python + from semantica.context import ContextGraph + + graph = ContextGraph(advanced_analytics=True) + + graph.add_node("python", "language", properties={"paradigm": "multi"}) + graph.add_node("fastapi", "framework", properties={"language": "Python"}) + graph.add_node("django", "framework", properties={"language": "Python"}) + graph.add_node("sqlmodel", "library", properties={"orm": True}) + + graph.add_edge("python", "fastapi", "enables") + graph.add_edge("python", "django", "enables") + graph.add_edge("fastapi", "sqlmodel", "uses") + + neighbors = graph.get_neighbors( + "python", + hops=3, + include_distance_metadata=True, + ) + + for n in neighbors: + print(f"{n['node_id']:12s} band={n['distance_band']:10s} " + f"decay={n['confidence_decay']:.3f} " + f"path={n['path_to_anchor']}") + ``` + ``` + fastapi band=direct decay=1.000 path=['python', 'fastapi'] + django band=direct decay=1.000 path=['python', 'django'] + sqlmodel band=near decay=0.750 path=['python', 'fastapi', 'sqlmodel'] + ``` + + + ```python + from semantica.kg import SimilarityCalculator, NodeEmbedder + + # Generate structural embeddings first + embedder = NodeEmbedder(method="node2vec", embedding_dimension=128) + embeddings = embedder.compute_embeddings(kg, ["language", "framework", "library"], ["enables", "uses"]) + + # N×N upper-triangle distance matrix + calc = SimilarityCalculator() + matrix = calc.compute_distance_matrix(embeddings) + + # matrix["distances"] is an upper-triangle dict: {(node_a, node_b): distance} + for (a, b), dist in sorted(matrix["distances"].items(), key=lambda x: x[1]): + print(f"{a:15s} ↔ {b:15s} distance={dist:.4f}") + ``` + + + Set `proximity_weight` on `AgentContext` to blend graph proximity into every semantic retrieval call: + + ```python + from semantica.context import AgentContext, ContextGraph + from semantica.vector_store import VectorStore + + context = AgentContext( + vector_store=VectorStore(backend="faiss", dimension=768), + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + proximity_weight=0.3, # combined = 0.7×semantic + 0.3×proximity + ) + + # retrieve() and find_precedents() both use the blended score + results = context.retrieve("web API frameworks", max_results=10) + for r in results: + print(f"[{r['combined_score']:.3f}] semantic={r['semantic_score']:.3f} " + f"proximity={r['proximity_score']:.3f} {r['content'][:60]}") + ``` + + + + +## ContextGraph Distance API + +### `get_neighbors()` + +Returns BFS neighbors enriched with distance metadata when `include_distance_metadata=True`: + +```python +neighbors = graph.get_neighbors( + node_id="python", + hops=4, + include_distance_metadata=True, + min_weight=0.3, # exclude low-confidence edges +) +``` + +| Field | Type | Description | +| :---- | :---- | :----------- | +| `node_id` | `str` | Node identifier | +| `node_type` | `str` | Node type label | +| `properties` | `Dict` | Node property dict | +| `hop_count` | `int` | BFS hops from anchor | +| `distance_band` | `str` | `"direct"` / `"near"` / `"mid-range"` / `"distant"` | +| `confidence_decay` | `float` | Confidence score after hop-based decay: `weight^hop_count` | +| `path_to_anchor` | `List[str]` | Shortest path from anchor to this node | +| `edge_weight` | `float` | Weight of the direct edge (if hop=1) | + +### `get_neighbor_distances()` + +Returns a sorted list of neighbors ranked by combined confidence-decay distance score: + +```python +distances = graph.get_neighbor_distances("fastapi", hops=3) + +for d in distances: + print(f"{d['node_id']:15s} score={d['combined_distance_score']:.4f} " + f"band={d['distance_band']}") +``` + + +## SimilarityCalculator — Pairwise Similarity + +`SimilarityCalculator` computes similarity between node embeddings using four metrics. + +```python +from semantica.kg import SimilarityCalculator + +calc = SimilarityCalculator(method="cosine", normalize=True) +# method: "cosine" | "euclidean" | "manhattan" | "correlation" +``` + +### Constructor + +| Parameter | Type | Default | Description | +| :--------- | :---- | :------- | :----------- | +| `method` | `str` | `"cosine"` | Default metric: `"cosine"`, `"euclidean"`, `"manhattan"`, `"correlation"` | +| `normalize` | `bool` | `True` | Normalize vectors before calculation | + +### Methods + +| Method | Returns | Description | +| :------ | :------- | :----------- | +| `cosine_similarity(vector1, vector2)` | `float` | Cosine similarity `[-1, 1]` between two vectors | +| `euclidean_distance(embedding1, embedding2)` | `float` | L2 distance (non-negative) between two vectors | +| `manhattan_distance(embedding1, embedding2)` | `float` | L1 distance (non-negative) between two vectors | +| `correlation_similarity(embedding1, embedding2)` | `float` | Pearson correlation `[-1, 1]` between two vectors | +| `batch_similarity(embeddings, query_embedding, method=None, top_k=None, chunk_size=1000)` | `Dict[str, float]` | Similarity of all nodes against a query vector. Returns `{node_id: score}` | +| `pairwise_similarity(embeddings, method=None)` | `Dict[Tuple[str,str], float]` | Upper-triangle N×N pairwise similarity matrix for all node pairs | +| `find_most_similar(embeddings, query_embedding, top_k=10, method=None)` | `List[Tuple[str, float]]` | Top-k `(node_id, score)` pairs sorted by similarity | + +### Pairwise Similarity Matrix + +`pairwise_similarity()` returns the upper triangle of the N×N matrix — each key is a `(node_id_a, node_id_b)` tuple: + +```python +from semantica.kg import NodeEmbedder, SimilarityCalculator + +embedder = NodeEmbedder(method="node2vec", embedding_dimension=128) +embeddings = embedder.compute_embeddings(kg, ["language", "framework"], ["enables", "uses"]) + +calc = SimilarityCalculator(method="cosine") + +# N×N upper-triangle: Dict[(node_a, node_b), similarity_score] +matrix = calc.pairwise_similarity(embeddings) + +# Sort by similarity (most similar first) +for (a, b), score in sorted(matrix.items(), key=lambda x: x[1], reverse=True)[:5]: + print(f"{a:15s} ↔ {b:15s} similarity={score:.4f}") + +# Find most similar pair +best_pair = max(matrix.items(), key=lambda x: x[1]) +print(f"Most similar: {best_pair[0]} score={best_pair[1]:.4f}") + +# Find most dissimilar pair +worst_pair = min(matrix.items(), key=lambda x: x[1]) +print(f"Most distant: {worst_pair[0]} score={worst_pair[1]:.4f}") +``` + + + The matrix is upper-triangle only — `(a, b)` is stored but `(b, a)` is not. To look up either direction: `matrix.get((a, b)) or matrix.get((b, a))`. + + +### Batch Similarity + +Efficiently compare a query vector against all nodes using chunked vectorized ops: + +```python +# Query vector against all nodes +scores = calc.batch_similarity( + embeddings, + query_embedding=my_query_vec, + method="cosine", # override default + top_k=10, # return only top 10 (None = all) + chunk_size=1000, # chunk size for memory efficiency +) + +for node_id, score in sorted(scores.items(), key=lambda x: x[1], reverse=True): + print(f"{node_id:15s} {score:.4f}") +``` + +### Find Most Similar + +```python +# Top-k (node_id, score) tuples sorted descending +similar = calc.find_most_similar( + embeddings, + query_embedding=embeddings["python"], + top_k=5, + method="cosine", +) + +for node_id, score in similar: + print(f"{node_id:15s} similarity={score:.4f}") +``` + +### Individual Metrics + +```python +vec_a = embeddings["fastapi"] +vec_b = embeddings["django"] + +cosine = calc.cosine_similarity(vec_a, vec_b) +l2 = calc.euclidean_distance(vec_a, vec_b) +l1 = calc.manhattan_distance(vec_a, vec_b) +pearson = calc.correlation_similarity(vec_a, vec_b) + +print(f"Cosine: {cosine:.4f}") +print(f"Euclidean: {l2:.4f}") +print(f"Manhattan: {l1:.4f}") +print(f"Correlation: {pearson:.4f}") +``` + + +## Proximity-Blended Retrieval + +`AgentContext.retrieve()` and `find_precedents()` both support a `proximity_weight` parameter that blends graph proximity into the semantic similarity score: + +``` +combined_score = (1 − proximity_weight) × semantic_score + + proximity_weight × proximity_score +``` + +Where `proximity_score` is derived from hop count and edge weights from the query anchor node. + +```python +from semantica.context import AgentContext, ContextGraph +from semantica.vector_store import VectorStore + +context = AgentContext( + vector_store=VectorStore(backend="faiss", dimension=768), + knowledge_graph=ContextGraph(advanced_analytics=True), + proximity_weight=0.3, +) + +# Standard retrieval — proximity blended automatically +results = context.retrieve("model deployment strategies", max_results=10) + +# Override weight per-call +results = context.retrieve( + "model deployment strategies", + max_results=10, + proximity_weight=0.5, # stronger proximity weight for this query +) + +# find_precedents also blends proximity +precedents = context.find_precedents( + "infrastructure scaling decisions", + proximity_weight=0.4, + limit=5, +) + +for p in precedents: + print(f"[{p.combined_score:.3f}] {p.outcome} (confidence: {p.confidence:.2f})") +``` + + +## Embedding Cache + +The embedding cache avoids re-computing embeddings for nodes that haven't changed since the last call — delivering up to **10× throughput improvement** on large graphs. + +### How It Works + +Each `GraphSession` tracks a **graph revision hash** derived from the current node and edge state. When a distance matrix or neighborhood request arrives: + +1. The revision hash is compared to the cached hash +2. If unchanged: the cached embeddings are returned directly +3. If changed (nodes/edges added or modified): the cache is invalidated and embeddings are recomputed + +```python +from semantica.explorer import GraphSession + +session = GraphSession(graph=kg) + +# First call: computes embeddings, stores in cache +embeddings = session.get_cached_embeddings() + +# Second call (graph unchanged): returns cache instantly +embeddings = session.get_cached_embeddings() + +# After graph modification: cache is automatically invalidated +session.graph.add_node("new_node", "concept", properties={}) +embeddings = session.get_cached_embeddings() # recomputes +``` + +| Parameter | Type | Default | Description | +| :--------- | :---- | :------- | :----------- | +| `force_refresh` | `bool` | `False` | Force cache invalidation even if the graph is unchanged | +| Cache invalidation | Automatic | — | Triggered by `add_nodes()`, `add_edges()`, or any mutation | +| Cache scope | Per-session | — | Each `GraphSession` maintains its own independent cache | + + + The cache is most effective in Explorer deployments where the same graph is queried repeatedly for distance matrices and ego-mode neighborhoods. In batch pipeline contexts, set `force_refresh=True` to ensure the latest graph state is always used. + + + +## REST API Endpoints + +Five new endpoints were added in v0.5.0 for programmatic distance intelligence access: + +### `POST /api/graph/distance-matrix` + +Compute N×N semantic distance matrix for a set of node IDs: + +```bash +curl -X POST http://localhost:8000/api/graph/distance-matrix \ + -H "Content-Type: application/json" \ + -d '{ + "node_ids": ["alice", "bob", "acme_corp", "beta_ltd"], + "embedding_model": "all-MiniLM-L6-v2", + "include_band_classification": true + }' +``` + +```json +{ + "matrix": { + "alice,bob": 0.312, + "alice,acme_corp": 0.087, + "alice,beta_ltd": 0.154, + "bob,acme_corp": 0.401, + "bob,beta_ltd": 0.233, + "acme_corp,beta_ltd": 0.198 + }, + "most_similar": ["alice", "acme_corp"], + "most_distant": ["bob", "acme_corp"], + "mean_distance": 0.231 +} +``` + +### `GET /api/graph/node/{id}/semantic-neighborhood` + +Retrieve the ego-graph (BFS neighborhood) of a node with distance metadata: + +```bash +curl "http://localhost:8000/api/graph/node/alice/semantic-neighborhood?depth=3&include_distance_metadata=true" +``` + +```json +{ + "anchor_node": "alice", + "neighbors": [ + {"node_id": "acme_corp", "distance_band": "direct", "confidence_decay": 1.0, "hop_count": 1}, + {"node_id": "ceo_role", "distance_band": "direct", "confidence_decay": 1.0, "hop_count": 1}, + {"node_id": "beta_ltd", "distance_band": "near", "confidence_decay": 0.75, "hop_count": 2}, + {"node_id": "london_hq", "distance_band": "mid-range","confidence_decay": 0.56, "hop_count": 3} + ], + "total_neighbors": 4, + "depth": 3 +} +``` + +### `GET /api/decisions/causal-distance` + +Return causal distance (hop count through causal edges) between two decision nodes: + +```bash +curl "http://localhost:8000/api/decisions/causal-distance?source=dec_001&target=dec_005" +``` + +```json +{ + "source": "dec_001", + "target": "dec_005", + "causal_hops": 3, + "causal_path": ["dec_001", "dec_002", "dec_004", "dec_005"], + "distance_band": "near" +} +``` + +### `GET /api/temporal/distance-history` + +Track how the semantic distance between two nodes has evolved over time: + +```bash +curl "http://localhost:8000/api/temporal/distance-history?node_a=alice&node_b=acme_corp&snapshots=2021-01-01,2022-01-01,2023-01-01" +``` + +```json +{ + "node_a": "alice", + "node_b": "acme_corp", + "history": [ + {"timestamp": "2021-01-01", "distance": 0.08, "band": "direct"}, + {"timestamp": "2022-01-01", "distance": 0.09, "band": "direct"}, + {"timestamp": "2023-01-01", "distance": 0.54, "band": "mid-range"} + ] +} +``` + +### `POST /api/export/distance-enriched` + +Export graph data enriched with distance metadata (CSV or JSONL, capped at 200 nodes): + +```bash +curl -X POST http://localhost:8000/api/export/distance-enriched \ + -H "Content-Type: application/json" \ + -d '{"anchor_node": "alice", "depth": 4, "format": "csv"}' +``` + + +## Explorer Distance Intelligence UI + +The Knowledge Explorer embeds Distance Intelligence directly in the browser dashboard: + + + + + Ego Mode centers the visualization on a selected node and renders its semantic neighborhood with **BFS depth-of-field fading** — nodes further from the anchor become progressively dimmer, revealing the "shape" of conceptual proximity. + + - **Depth slider (1–8)**: controls the BFS radius of the neighborhood + - **Confidence decay visualization**: edge opacity maps to `confidence_decay` score + - **Distance band color coding**: green (direct) → teal (near) → yellow (mid-range) → red (distant) + - **Bottleneck highlighting**: bridge nodes that connect otherwise separate clusters are highlighted in the path inspector + + Activate via the Explorer toolbar: **View → Ego Mode**, then click any node to set it as anchor. + + + + The heatmap renders an N×N distance matrix as a color-coded grid — instantly revealing which clusters of nodes are semantically cohesive and which are isolated. + + - **Color scale**: green (near, distance → 0) through yellow to red (distant, distance → 1) + - **Hover**: shows exact distance value and distance band for each cell + - **Sort options**: sort rows/columns by node type, community membership, or alphabetical + + Access via **View → Distance Heatmap** in the Explorer sidebar. + + + + Overlay semantic similarity on the standard force-directed graph layout without switching modes: + + - **Semantic overlay**: edge thickness scaled by semantic similarity score + - **Structural overlay**: edge thickness scaled by graph centrality + - Both overlays can be toggled independently + + Access via the **Overlay** toggle in the Explorer toolbar. + + + + Click any two nodes to inspect the shortest path between them. The Path Inspector shows: + + - **Distance band chip**: classifies the overall path as direct / near / mid-range / distant + - **Metric cards**: hop count, mean edge weight, path confidence decay + - **Bottleneck node highlight**: the single node whose removal would disconnect the path + - **Distance history**: timeline of how the distance between the two nodes has changed across graph snapshots + + Access via **right-click → Inspect Path** on any two selected nodes. + + + + + +## Real-World Patterns + + + + Find semantically cohesive topic clusters in a large knowledge graph without running community detection: + + ```python + from semantica.kg import NodeEmbedder, SimilarityCalculator + + embedder = NodeEmbedder(method="node2vec", embedding_dimension=128) + embeddings = embedder.compute_embeddings(kg, node_types=["Concept", "Topic"]) + + calc = SimilarityCalculator() + + # Cluster nodes where pairwise distance < 0.2 + clusters = calc.cluster_by_distance(embeddings, threshold=0.2) + + for i, cluster in enumerate(clusters): + print(f"Cluster {i+1} ({len(cluster)} nodes): {cluster[:5]}") + ``` + + + Flag nodes that are unexpectedly distant from their structural neighbors — potential data quality issues or genuine anomalies: + + ```python + from semantica.context import ContextGraph + from semantica.kg import NodeEmbedder, SimilarityCalculator + + graph = ContextGraph(advanced_analytics=True) + # ... build graph ... + + embedder = NodeEmbedder(method="node2vec", embedding_dimension=128) + embeddings = embedder.compute_embeddings(graph._graph, ["entity"], ["RELATED_TO"]) + + calc = SimilarityCalculator() + + for node_id in graph._graph.nodes(): + neighbors = graph.get_neighbors(node_id, hops=1, include_distance_metadata=True) + for n in neighbors: + # Node connected by edge but semantically very distant → anomaly candidate + structural_dist = 1.0 - n["edge_weight"] + semantic_dist = calc.euclidean_distance( + embeddings[node_id], embeddings[n["node_id"]] + ) + if semantic_dist > 0.7 and structural_dist < 0.3: + print(f"Anomaly: {node_id} → {n['node_id']} " + f"(structural={structural_dist:.2f}, semantic={semantic_dist:.2f})") + ``` + + + Verify that similar decisions (low semantic distance) reached similar outcomes — flag inconsistencies for review: + + ```python + from semantica.context import AgentContext, ContextGraph + from semantica.vector_store import VectorStore + + context = AgentContext( + vector_store=VectorStore(backend="faiss", dimension=768), + knowledge_graph=ContextGraph(advanced_analytics=True), + decision_tracking=True, + proximity_weight=0.4, + ) + + # ... populate with historical decisions ... + + # Find pairs of semantically close decisions with different outcomes + all_decisions = context.query_decisions("", max_hops=0) + for i, d1 in enumerate(all_decisions): + for d2 in all_decisions[i+1:]: + precedents = context.find_precedents( + d1.scenario, limit=5, proximity_weight=0.4 + ) + for p in precedents: + if p.source_decision_id == d2.decision_id: + if p.similarity_score > 0.85 and d1.outcome != d2.outcome: + print(f"INCONSISTENCY: {d1.scenario}") + print(f" Decision A: {d1.outcome} (confidence {d1.confidence:.2f})") + print(f" Decision B: {d2.outcome} (confidence {d2.confidence:.2f})") + print(f" Similarity: {p.similarity_score:.3f}") + ``` + + + + +## Performance + +| Operation | Without cache | With cache | Improvement | +| :--------- | :------------ | :--------- | :---------- | +| Distance matrix (118k nodes) | ~48s | ~4.8s | **10×** | +| Semantic neighborhood (depth 4) | ~2.1s | ~0.21s | **10×** | +| Node search (indexed) | 24 ms | 0.004 ms | **6,000×** | +| Semantic deduplication | baseline | — | **6.98×** (v2 algorithms) | + + + The 10× cache improvement applies when the graph is unchanged between requests. In write-heavy pipelines where nodes are added continuously, cache hit rates will be lower. Use `force_refresh=False` (default) for read-heavy Explorer usage and `force_refresh=True` for batch pipeline contexts. + + +- [Context Module](context) — `ContextGraph.get_neighbors()` and proximity-blended retrieval. +- [Knowledge Graph Module](kg) — `NodeEmbedder`, `SimilarityCalculator`, and graph analytics. +- [Visualization](visualization) — Programmatic distance heatmaps and ego-mode graph renders. +- [Explorer](explorer) — Knowledge Explorer with built-in Distance Intelligence dashboard. + +- [Distance Intelligence](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/12_Distance_Intelligence.ipynb) — Semantic neighborhoods and distance matrices · Advanced diff --git a/docs/reference/kg.md b/docs/reference/kg.md index 2fb7e66c..59bf42cb 100644 --- a/docs/reference/kg.md +++ b/docs/reference/kg.md @@ -23,7 +23,17 @@ icon: "diagram-project" | `EntityResolver` | Entity deduplication and merging during graph construction | | `GraphAnalyzer` | Unified analytics wrapper: runs centrality, community detection, and connectivity in one call | | `ConnectivityAnalyzer` | Connected component detection, bridge identification, density, and degree statistics | -| `TemporalGraphQuery` | Point-in-time snapshots, temporal diffs, and all 13 Allen interval queries | +| `TemporalGraphQuery` | Point-in-time snapshots, range queries, evolution analysis, temporal path finding | +| `TemporalPatternDetector` | Sequence and cycle pattern detection over temporal edges | +| `TemporalReasoningEngine` | All 13 Allen interval algebra relations over `TemporalInterval` objects | +| `TemporalInterval` | Frozen dataclass `(start: datetime, end: datetime \| TemporalBound, label?)` | +| `IntervalRelation` | Enum of all 13 Allen relation labels (`BEFORE`, `AFTER`, `MEETS`, …) | +| `BiTemporalFact` | Dataclass wrapping `valid_from`, `valid_until`, `recorded_at`, `superseded_at`. Factory: `BiTemporalFact.from_relationship(rel_dict)` | +| `TemporalBound` | Sentinel enum for open-ended intervals — single value: `TemporalBound.OPEN` | +| `TemporalNormalizer` | Parse NL temporal expressions to `(datetime, datetime)` tuples — zero LLM calls | +| `TemporalQueryRewriter` | Extract temporal intent from free-text queries; returns `TemporalQueryResult` | +| `TemporalQueryResult` | Dataclass output of `TemporalQueryRewriter.rewrite()` | +| `TemporalVersionManager` | Versioned snapshots with SHA-256 integrity, SQLite-backed persistent storage | | `CentralityCalculator` | PageRank, degree, betweenness, closeness, eigenvector centrality | | `CommunityDetector` | Louvain, Leiden, Label Propagation, and K-Clique community detection | | `PathFinder` | Dijkstra, A*, BFS, and K-Shortest path algorithms | @@ -31,6 +41,11 @@ icon: "diagram-project" | `NodeEmbedder` | Node2Vec structural embeddings for downstream ML | | `SimilarityCalculator` | Cosine, Euclidean, Manhattan, and correlation similarity scoring | | `GraphValidator` | Schema and constraint validation before persistence | +| `AlgorithmTrackerWithProvenance` | Algorithm execution tracking with provenance metadata | +| `AlgorithmRegistry` / `algorithm_registry` | Registry for registered algorithms; `algorithm_registry` is the shared singleton | +| `ProvenanceTracker` | W3C PROV-O provenance tracking for graph operations | +| `SeedManager` | Reproducible random seed management across algorithms | +| `KGConfig` / `kg_config` | Module-level configuration; `kg_config` is the shared singleton | @@ -57,56 +72,200 @@ kg = builder.build({"entities": entities, "relationships": relationships}) | `build_single_source(data)` | `dict` | Build graph from a single data source dict | -## Temporal Knowledge Graphs (v0.4.0) +## Temporal Knowledge Graphs (v0.4.0+) -Use **`TemporalGraphQuery`** to attach `valid_from`/`valid_until` windows and query **point-in-time snapshots** of any graph: + + Full temporal reference including `BiTemporalFact`, `TemporalReasoningEngine`, Allen interval algebra, and `TemporalNormalizer` is covered in the dedicated [Temporal Intelligence](temporal) page. This section documents the KG-layer temporal API. + + +The temporal stack — see the [Temporal Intelligence](temporal) page for the full reference. + +### Building a Temporal Graph ```python from semantica.kg import GraphBuilder, TemporalGraphQuery, TemporalVersionManager -from datetime import datetime -# Build a time-aware graph builder = GraphBuilder() kg = builder.build(sources=[ { "entities": [ {"id": "alice", "type": "Person"}, {"id": "acme_corp", "type": "Organization"}, + {"id": "beta_ltd", "type": "Organization"}, ], "relationships": [ { "source": "alice", "target": "acme_corp", "type": "ceo_of", - "valid_from": "2020-01-01", - "valid_until": "2023-06-01", - } - ] + "valid_from": "2018-01-01", + "valid_until": "2022-06-01", + }, + { + "source": "alice", "target": "beta_ltd", "type": "ceo_of", + "valid_from": "2022-06-01", + # No valid_until → open-ended (TemporalBound.OPEN) + }, + ], } ]) - -# Point-in-time snapshot: TemporalGraphQuery takes no positional graph arg; -# pass the graph into each query method instead. -query = TemporalGraphQuery() -snapshot_2021 = query.reconstruct_at_time(kg, "2021-06-15") -snapshot_2023 = query.reconstruct_at_time(kg, "2023-01-01") - -# Relationships active within a date range -range_result = query.query_time_range(kg, "", "2020-01-01", "2023-01-01") -print(f"Relationships in range: {range_result['num_relationships']}") - -# Versioned snapshots: author and description are required -versioner = TemporalVersionManager() -versioner.create_snapshot(kg, version_label="2024-Q1", - author="user@example.com", - description="Q1 2024 snapshot") ``` -Supports all 13 Allen interval algebra relations: +### Point-in-Time Queries -- before, after, meets, met_by -- overlaps, overlapped_by -- during, contains, starts, started_by, finishes, finished_by, equals +`TemporalGraphQuery` accepts optional constructor args; pass the graph into each query call: -OWL-Time export available. +```python +from semantica.kg import TemporalGraphQuery + +query = TemporalGraphQuery( + temporal_granularity="day", # second|minute|hour|day|week|month|year + enable_temporal_reasoning=True, +) + +# Primary API: query_at_time returns counts + filtered data +result_2020 = query.query_at_time(kg, "", at_time="2020-06-15") +result_2023 = query.query_at_time(kg, "", at_time="2023-01-01") +print(f"Rels in 2020: {result_2020['num_relationships']}") + +# Low-level: reconstruct_at_time returns a deep-copied subgraph dict +snapshot = query.reconstruct_at_time(kg, "2020-06-15") + +# Range query: all relationships active during any part of 2021 +range_result = query.query_time_range(kg, "", "2021-01-01", "2021-12-31") + +# Compare two snapshots: use TemporalVersionManager.compare_versions() +# (temporal_diff() does not exist — see TemporalVersionManager below) +``` + +### Bi-Temporal Facts + +`BiTemporalFact` is a **dataclass** — use the `from_relationship()` factory, not a positional constructor: + +```python +from semantica.kg import BiTemporalFact, TemporalBound + +rel = { + "source": "alice", "target": "acme_corp", "type": "ceo_of", + "valid_from": "2018-01-01", + "valid_until": "2022-06-01", + "recorded_at": "2018-01-05T09:32:00Z", + "superseded_at": None, # None → TemporalBound.OPEN (still current) +} +fact = BiTemporalFact.from_relationship(rel) + +print(fact.valid_from) # datetime(2018, 1, 1, tzinfo=utc) +print(fact.valid_until) # datetime(2022, 6, 1, tzinfo=utc) +print(fact.superseded_at) # TemporalBound.OPEN + +# Open-ended fact (no valid_until → TemporalBound.OPEN) +open_rel = {"source": "alice", "target": "beta_ltd", "type": "ceo_of", + "valid_from": "2022-06-01"} +open_fact = BiTemporalFact.from_relationship(open_rel) +print(open_fact.valid_until) # TemporalBound.OPEN + +# Serialize back to dict fields for storage +fields = fact.to_relationship_fields() +``` + +### Allen Interval Algebra + +`TemporalReasoningEngine` implements **all 13 Allen relations** deterministically — no LLM, no probability. It operates on `TemporalInterval` objects (not plain dicts): + +```python +from semantica.kg import ( + TemporalReasoningEngine, TemporalInterval, IntervalRelation +) +from datetime import datetime, timezone + +def dt(y, m, d): return datetime(y, m, d, tzinfo=timezone.utc) + +engine = TemporalReasoningEngine() + +h1_2020 = TemporalInterval(start=dt(2020, 1, 1), end=dt(2020, 6, 30)) +q2_q4 = TemporalInterval(start=dt(2020, 4, 1), end=dt(2020, 12, 31)) + +relation = engine.relation(h1_2020, q2_q4) # primary method +print(relation) # IntervalRelation.OVERLAPS +print(relation.value) # "overlaps" + +print(engine.overlaps(h1_2020, q2_q4)) # True +print(engine.contains(q2_q4, h1_2020)) # False +print(engine.active_at(h1_2020, dt(2020, 3, 15))) # True +``` + +| `IntervalRelation` | `.value` | Description | +| :--- | :--- | :--- | +| `BEFORE` | `"before"` | A ends strictly before B starts | +| `MEETS` | `"meets"` | A ends exactly when B starts | +| `OVERLAPS` | `"overlaps"` | A and B share a period; A starts and ends first | +| `STARTS` | `"starts"` | Same start; A ends before B | +| `DURING` | `"during"` | A is entirely within B | +| `FINISHES` | `"finishes"` | Same end; B started earlier | +| `EQUALS` | `"equals"` | Identical interval | +| `AFTER`, `MET_BY`, `OVERLAPPED_BY`, `STARTED_BY`, `CONTAINS`, `FINISHED_BY` | *(inverses)* | Mirror relations | + +### Natural Language Temporal Parsing + +```python +from semantica.kg import TemporalNormalizer, TemporalQueryRewriter +from datetime import datetime, timezone + +# reference_date set at construction time (required for relative phrases) +norm = TemporalNormalizer(reference_date=datetime(2024, 6, 15, tzinfo=timezone.utc)) + +# Returns Optional[Tuple[datetime, datetime]] — not a dict +result = norm.normalize("last quarter") +start, end = result +print(start) # datetime(2024, 1, 1, tzinfo=utc) +print(end) # datetime(2024, 3, 31, tzinfo=utc) + +result = norm.normalize("2022") +# (datetime(2022, 1, 1, tzinfo=utc), datetime(2022, 12, 31, tzinfo=utc)) + +result = norm.normalize("unparseable phrase") +print(result) # None + +# TemporalQueryRewriter: primary method is rewrite(), returns TemporalQueryResult +rewriter = TemporalQueryRewriter() +result = rewriter.rewrite("Who was CEO before the 2022 restructuring?") +print(result.temporal_intent) # "before" +print(result.at_time.year) # 2022 +print(result.rewritten_query) # "Who was CEO" +print(result.confidence) # 0.85 +print(result.has_temporal_context()) # True +``` + +### Versioned Snapshots + +```python +from semantica.kg import TemporalVersionManager + +# In-memory (default); pass storage_path="versions.db" for SQLite persistence +versioner = TemporalVersionManager() + +# author and description are required for create_snapshot +versioner.create_snapshot(kg, version_label="2024-Q1", + author="user@example.com", + description="Q1 2024 baseline") + +# List versions (not list_snapshots) +for v in versioner.list_versions(): + print(f"{v['label']:12s} {v['author']}") + +# Compare two versions (not diff_versions) +diff = versioner.compare_versions("2023-Q4", "2024-Q1") +print(f"Entities added: {diff['summary']['entities_added']}") +print(f"Relationships added: {diff['summary']['relationships_added']}") + +# Retrieve a version (not restore_snapshot) +past_kg = versioner.get_version("2023-Q4") + +# SHA-256 integrity check +versioner.verify_checksum(past_kg) +``` + + + See the [Temporal Intelligence](temporal) reference for the full class API, domain examples (personnel changes, policy evolution, financial timelines), and configuration options. + ## Similarity Scoring diff --git a/docs/reference/temporal.md b/docs/reference/temporal.md new file mode 100644 index 00000000..d4bd4fdd --- /dev/null +++ b/docs/reference/temporal.md @@ -0,0 +1,883 @@ +--- +title: "Temporal Intelligence" +description: "Bi-temporal facts, point-in-time snapshots, Allen interval algebra, temporal pattern detection, and natural-language temporal parsing for time-aware knowledge graphs." +icon: "clock" +--- + +Temporal Intelligence gives your knowledge graph a complete understanding of *when* — not just what is true, but when it was true in the real world, when it was recorded, and how facts have evolved over time. + +Shipped across **v0.3.0** (context temporal validity) and **v0.4.0** (full temporal stack), the system covers five layers: + +
+
+
Bi-temporal model
+
Valid time + transaction time on every fact
+
+
+
Point-in-time queries
+
Reconstruct any historical graph state in one call
+
+
+
Allen interval algebra
+
All 13 temporal relations, deterministic reasoning
+
+
+
NL temporal parsing
+
Zero LLM calls — pure regex + dateutil
+
+
+ + +## Exported Classes + +| Class | Role | +| :---- | :---- | +| `BiTemporalFact` | Dataclass wrapping `valid_from`, `valid_until`, `recorded_at`, `superseded_at`. Factory: `BiTemporalFact.from_relationship(rel_dict)` | +| `TemporalBound` | Enum sentinel for open-ended intervals. Single value: `TemporalBound.OPEN` | +| `TemporalInterval` | Frozen dataclass `(start: datetime, end: datetime \| TemporalBound, label?)` used by `TemporalReasoningEngine` | +| `IntervalRelation` | Enum of all 13 Allen relation labels (`BEFORE`, `AFTER`, `MEETS`, etc.) | +| `TemporalGraphQuery` | Point-in-time snapshots, range queries, pattern detection, evolution analysis, temporal path finding | +| `TemporalPatternDetector` | Sequence and cycle pattern detection over temporal edges | +| `TemporalReasoningEngine` | Allen interval algebra over `TemporalInterval` objects — pure Python, deterministic | +| `TemporalNormalizer` | Parse NL temporal expressions to `(datetime, datetime)` tuples — zero LLM calls | +| `TemporalQueryRewriter` | Extract temporal intent from free-text queries; returns `TemporalQueryResult` | +| `TemporalQueryResult` | Dataclass output of `TemporalQueryRewriter.rewrite()` | +| `TemporalVersionManager` | Create, list, compare, and apply revisions to versioned graph snapshots | + + +## Quick Start + + + + Attach `valid_from` / `valid_until` to any relationship at construction time: + + ```python + from semantica.kg import GraphBuilder + + builder = GraphBuilder() + kg = builder.build(sources=[{ + "entities": [ + {"id": "alice", "type": "Person"}, + {"id": "acme_corp", "type": "Organization"}, + {"id": "beta_ltd", "type": "Organization"}, + ], + "relationships": [ + { + "source": "alice", "target": "acme_corp", "type": "ceo_of", + "valid_from": "2018-01-01", + "valid_until": "2022-06-01", + }, + { + "source": "alice", "target": "beta_ltd", "type": "ceo_of", + "valid_from": "2022-06-01", + # No valid_until → open-ended (TemporalBound.OPEN) + }, + ], + }]) + ``` + + + `TemporalGraphQuery` takes constructor args; pass the graph into each query call: + + ```python + from semantica.kg import TemporalGraphQuery + + query = TemporalGraphQuery(temporal_granularity="day") + + # query_at_time is the primary public API + result_2020 = query.query_at_time(kg, query="", at_time="2020-06-15") + result_2023 = query.query_at_time(kg, query="", at_time="2023-01-01") + + print(f"Rels active in 2020: {result_2020['num_relationships']}") + print(f"Rels active in 2023: {result_2023['num_relationships']}") + ``` + + + `reconstruct_at_time()` is the low-level primitive — returns a full graph dict + with only nodes and edges that were valid at the given moment: + + ```python + snapshot = query.reconstruct_at_time(kg, "2021-06-15") + # snapshot has "entities" and "relationships" keys + # usable with all GraphAnalyzer, PathFinder, CommunityDetector calls + ``` + + + ```python + from semantica.kg import TemporalVersionManager + + versioner = TemporalVersionManager() # in-memory storage + # versioner = TemporalVersionManager(storage_path="versions.db") # SQLite + + versioner.create_snapshot( + kg, + version_label="2024-Q1", + author="user@example.com", + description="Q1 2024 snapshot after board restructure", + ) + + for v in versioner.list_versions(): + print(f"{v['label']:12s} {v['author']} {v['timestamp']}") + ``` + + + + +## The Bi-Temporal Model + +Most systems track only one timeline: when something is currently true. Bi-temporal graphs track **two independent timelines** simultaneously: + + + + *When was the fact true in the real world?* + + - `valid_from` — date the fact became true + - `valid_until` — date the fact ceased to be true. Omit (or use `TemporalBound.OPEN`) for currently-active facts + + ```python + from semantica.kg import BiTemporalFact, TemporalBound + + # Create from an existing relationship dict + rel = { + "source": "alice", "target": "acme_corp", "type": "ceo_of", + "valid_from": "2018-01-01", + "valid_until": "2022-06-01", + } + fact = BiTemporalFact.from_relationship(rel) + + print(fact.valid_from) # datetime(2018, 1, 1, tzinfo=utc) + print(fact.valid_until) # datetime(2022, 6, 1, tzinfo=utc) + + # Serialize back to dict fields + fields = fact.to_relationship_fields() + print(fields["valid_from"]) # "2018-01-01T00:00:00Z" + print(fields["valid_until"]) # "2022-06-01T00:00:00Z" + ``` + + + *When did we record this fact in the system?* + + - `recorded_at` — auto-stamped at ingestion time (defaults to `datetime.now(utc)`) + - `superseded_at` — set when a later version replaces this record. `TemporalBound.OPEN` means still current + + ```python + rel = { + "source": "alice", "target": "acme_corp", "type": "ceo_of", + "valid_from": "2018-01-01", + "valid_until": "2022-06-01", + "recorded_at": "2018-01-05T09:32:00Z", + "superseded_at": None, # still the current record + } + fact = BiTemporalFact.from_relationship(rel) + + print(fact.recorded_at) # datetime(2018, 1, 5, 9, 32, tzinfo=utc) + print(fact.superseded_at) # TemporalBound.OPEN + ``` + + + `TemporalBound.OPEN` is the single sentinel that represents an open-ended interval — a fact with no defined end date: + + ```python + from semantica.kg import TemporalBound + + print(TemporalBound.OPEN) # TemporalBound.OPEN + print(TemporalBound.OPEN.value) # "OPEN" + + # A relationship with no valid_until gets TemporalBound.OPEN automatically + rel = {"source": "alice", "target": "beta_ltd", "type": "ceo_of", + "valid_from": "2022-06-01"} + fact = BiTemporalFact.from_relationship(rel) + print(fact.valid_until) # TemporalBound.OPEN + ``` + + + `TemporalBound.OPEN` replaces both the start and end sentinels — there is only one value. The reasoning engine treats `OPEN` as `datetime.max` (far future) when comparing end bounds, and as `datetime.min` (far past) when used for `superseded_at`. + + + + + +## TemporalGraphQuery — Reference + +Constructed once; the graph is passed into each method call: + +```python +from semantica.kg import TemporalGraphQuery + +query = TemporalGraphQuery( + enable_temporal_reasoning=True, # default + temporal_granularity="day", # second|minute|hour|day|week|month|year + max_temporal_depth=None, # optional max depth +) +``` + +### Core Methods + +| Method | Returns | Description | +| :------ | :------- | :----------- | +| `query_at_time(graph, query, at_time, include_history=False, time_axis="valid")` | `Dict` | Primary API — filter graph to facts valid at `at_time`. Returns `entities`, `relationships`, `num_entities`, `num_relationships` | +| `reconstruct_at_time(graph, at_time, *, time_axis="valid")` | `Dict` | Low-level — returns a deep-copied subgraph valid at `at_time`. Usable with all analytics tools | +| `query_time_range(graph, query, start_time, end_time, temporal_aggregation="union", include_intervals=True, time_axis="valid")` | `Dict` | All relationships active during `[start, end]`. `temporal_aggregation`: `"union"` / `"intersection"` / `"evolution"` | +| `validate_temporal_consistency(graph)` | `TemporalConsistencyReport` | Detect inverted intervals, overlapping same-edge facts, and entity lifetime violations | +| `query_temporal_pattern(graph, pattern, time_window=None, min_support=1)` | `Dict` | Detect `"sequence"` or `"cycle"` patterns. Delegates to `TemporalPatternDetector` | +| `analyze_evolution(graph, entity=None, relationship=None, start_time=None, end_time=None, metrics=None)` | `Dict` | Track evolution metrics (`"count"`, `"diversity"`, `"stability"`) over time | +| `find_temporal_paths(graph, source, target, start_time=None, end_time=None, max_path_length=None, enforce_causal_ordering=True, ordering_strategy="strict")` | `Dict` | BFS paths respecting temporal validity. `ordering_strategy`: `"strict"` / `"overlap"` / `"loose"` | + +### `time_axis` Parameter + +All query methods accept a `time_axis` parameter controlling which timestamps are used for filtering: + +| Value | Effect | +| :---- | :----- | +| `"valid"` (default) | Filter by `valid_from` / `valid_until` — when the fact was true | +| `"transaction"` | Filter by `recorded_at` / `superseded_at` — when we recorded it | +| `"both"` | Fact must be active on both axes simultaneously | + +### Range Query Example + +```python +# All relationships active at any point in 2021 +result = query.query_time_range(kg, "", "2021-01-01", "2021-12-31") +for rel in result["relationships"]: + print(f" {rel['source']} --[{rel['type']}]--> {rel['target']}") + +# Only relationships valid throughout the entire range (stricter) +result = query.query_time_range( + kg, "", "2021-01-01", "2021-12-31", + temporal_aggregation="intersection", +) + +# Grouped by calendar period +result = query.query_time_range( + kg, "", "2021-01-01", "2021-12-31", + temporal_aggregation="evolution", +) +for period, rels in result["relationship_buckets"].items(): + print(f" {period}: {len(rels)} relationships active") +``` + +### Evolution Analysis + +```python +evolution = query.analyze_evolution( + kg, + entity="alice", # track a specific entity (None = whole graph) + relationship="ceo_of", # track a specific edge type (None = all) + start_time="2018-01-01", + end_time="2024-12-31", + metrics=["count", "diversity", "stability"], +) +print(f"Relationship count: {evolution['count']}") +print(f"Relationship types: {evolution['diversity']}") +``` + +### Temporal Path Finding + +```python +paths = query.find_temporal_paths( + kg, + source="alice", + target="beta_ltd", + start_time="2022-01-01", + end_time="2024-12-31", + max_path_length=5, + enforce_causal_ordering=True, + ordering_strategy="strict", # strict|overlap|loose +) +for p in paths["paths"]: + print(f" {' → '.join(p['path'])} (length={p['length']})") +``` + +### Consistency Validation + +```python +from semantica.kg import TemporalGraphQuery + +report = TemporalGraphQuery().validate_temporal_consistency(kg) + +print(f"Errors: {len(report.errors)}") +print(f"Warnings: {len(report.warnings)}") + +for err in report.errors: + print(f" [{err['issue_type']}] fact_id={err['fact_id']}: {err['message']}") +``` + +Error types reported: `inverted_interval`, `invalid_temporal_fields`, `missing_source_entity`, `missing_target_entity`, `source_lifetime_mismatch`, `target_lifetime_mismatch`. +Warning types: `overlapping_same_edge`, `gap_after_restart`. + + +## TemporalPatternDetector + +Detect recurring temporal patterns across graph edges. Accessed directly or via `TemporalGraphQuery.query_temporal_pattern()`: + +```python +from semantica.kg import TemporalPatternDetector + +detector = TemporalPatternDetector() + +# Find sequential edge patterns (A→B→C where edges are back-to-back) +sequences = detector.detect_temporal_patterns( + kg, + pattern_type="sequence", + min_frequency=2, + time_window=None, +) + +for seq in sequences: + print(f"Sequence: {seq['signature']} (occurs {seq['frequency']} times)") + for occ in seq["occurrences"]: + print(f" nodes={occ['nodes']} {occ['start_time']} → {occ['end_time']}") + +# Find cyclic patterns (A→B→C→A) +cycles = detector.detect_temporal_patterns( + kg, + pattern_type="cycle", + min_frequency=1, +) +``` + +| Parameter | Type | Default | Description | +| :--------- | :---- | :------- | :----------- | +| `pattern_type` | `str` | `"sequence"` | `"sequence"` or `"cycle"` | +| `min_frequency` | `int` | `2` | Minimum occurrences for a pattern to be returned | +| `time_window` | `Any` | `None` | Optional time constraint on pattern window | + +Each pattern dict has: `pattern_type`, `signature` (tuple of node IDs), `frequency`, `occurrences` (list with `nodes`, `edges`, `start_time`, `end_time`). + + +## Allen Interval Algebra + +`TemporalReasoningEngine` operates on `TemporalInterval` objects — a frozen dataclass with `start: datetime` and `end: datetime | TemporalBound`: + +```python +from semantica.kg import ( + TemporalReasoningEngine, TemporalInterval, IntervalRelation, TemporalBound +) +from datetime import datetime, timezone + +def dt(year, month, day): + return datetime(year, month, day, tzinfo=timezone.utc) + +engine = TemporalReasoningEngine() + +h1_2020 = TemporalInterval(start=dt(2020, 1, 1), end=dt(2020, 6, 30)) +q2_q4 = TemporalInterval(start=dt(2020, 4, 1), end=dt(2020, 12, 31)) + +relation = engine.relation(h1_2020, q2_q4) +print(relation) # IntervalRelation.OVERLAPS +print(relation.value) # "overlaps" + +print(engine.overlaps(h1_2020, q2_q4)) # True +print(engine.contains(q2_q4, h1_2020)) # False +``` + +### All 13 Relations + +| `IntervalRelation` | `.value` | Inverse | Description | +| :--- | :--- | :--- | :--- | +| `BEFORE` | `"before"` | `AFTER` | A ends strictly before B starts | +| `AFTER` | `"after"` | `BEFORE` | A starts strictly after B ends | +| `MEETS` | `"meets"` | `MET_BY` | A ends exactly when B starts | +| `MET_BY` | `"met_by"` | `MEETS` | A starts exactly when B ends | +| `OVERLAPS` | `"overlaps"` | `OVERLAPPED_BY` | A and B share a period; A starts and ends first | +| `OVERLAPPED_BY` | `"overlapped_by"` | `OVERLAPS` | B starts and ends before A, they share a period | +| `STARTS` | `"starts"` | `STARTED_BY` | Same start time; A ends before B | +| `STARTED_BY` | `"started_by"` | `STARTS` | Same start time; B ends before A | +| `DURING` | `"during"` | `CONTAINS` | A is entirely inside B | +| `CONTAINS` | `"contains"` | `DURING` | B is entirely inside A | +| `FINISHES` | `"finishes"` | `FINISHED_BY` | Same end time; A started after B | +| `FINISHED_BY` | `"finished_by"` | `FINISHES` | Same end time; B started after A | +| `EQUALS` | `"equals"` | *(self-inverse)* | Identical interval | + +### Additional Engine Methods + +| Method | Returns | Description | +| :------ | :------- | :----------- | +| `active_at(interval, timestamp, granularity=None)` | `bool` | Is `timestamp` within `interval`? | +| `merge_intervals(intervals)` | `List[TemporalInterval]` | Merge overlapping/touching intervals | +| `gap_analysis(intervals, domain_start, domain_end)` | `List[TemporalInterval]` | Find uncovered gaps within a domain | +| `coverage_percentage(intervals, domain_start, domain_end)` | `float` | Fraction of domain covered by intervals | +| `timeline_of(entity_id, graph)` | `List[Dict]` | Sorted event timeline for an entity | +| `retroactive_coverage(revision, original_facts)` | `Dict` | Classify facts as `affected`, `partial`, or `unaffected` by a revision | +| `normalize_timestamp(timestamp, granularity)` | `datetime` | Truncate timestamp to granularity | +| `normalize_interval(start, end, granularity)` | `TemporalInterval` | Parse and expand interval to granularity boundaries | + +### Advanced: Interval Operations + +```python +from datetime import datetime, timezone + +def dt(y, m, d): return datetime(y, m, d, tzinfo=timezone.utc) + +intervals = [ + TemporalInterval(start=dt(2020, 1, 1), end=dt(2020, 6, 30)), + TemporalInterval(start=dt(2020, 4, 1), end=dt(2020, 12, 31)), + TemporalInterval(start=dt(2021, 3, 1), end=TemporalBound.OPEN), +] + +# Merge overlapping intervals +merged = engine.merge_intervals(intervals) +print(f"Merged into {len(merged)} intervals") + +# Find gaps in coverage across 2020 +gaps = engine.gap_analysis(intervals, dt(2020, 1, 1), dt(2020, 12, 31)) +print(f"Uncovered gaps: {len(gaps)}") + +# Coverage fraction +pct = engine.coverage_percentage(intervals, dt(2020, 1, 1), dt(2021, 12, 31)) +print(f"Coverage: {pct:.1%}") + +# Entity timeline (all add/modify/remove events sorted by time) +timeline = engine.timeline_of("alice", kg) +for event in timeline: + print(f" {event['timestamp'].date()} {event['change_type']}") +``` + + +## TemporalNormalizer — NL Temporal Parsing + +Converts natural-language temporal phrases into `(valid_from, valid_until)` datetime tuples. **Zero LLM calls.** Pure regex + `dateutil.relativedelta`. + +```python +from semantica.kg import TemporalNormalizer +from datetime import datetime, timezone + +norm = TemporalNormalizer( + reference_date=datetime(2024, 6, 15, tzinfo=timezone.utc) +) +``` + +### `normalize(value)` → `Optional[Tuple[datetime, datetime]]` + +```python +# ISO 8601 → point interval +result = norm.normalize("2022-03-15") +print(result) +# (datetime(2022, 3, 15, tzinfo=utc), datetime(2022, 3, 15, tzinfo=utc)) + +# Year → full year span +result = norm.normalize("2022") +print(result) +# (datetime(2022, 1, 1, tzinfo=utc), datetime(2022, 12, 31, tzinfo=utc)) + +# Quarter → quarter span +result = norm.normalize("Q2 2021") +print(result) +# (datetime(2021, 4, 1, tzinfo=utc), datetime(2021, 6, 30, tzinfo=utc)) + +# Month + year +result = norm.normalize("January 2022") +print(result) +# (datetime(2022, 1, 1, tzinfo=utc), datetime(2022, 1, 31, tzinfo=utc)) + +# YYYY-MM (ISO partial) +result = norm.normalize("2022-03") +print(result) +# (datetime(2022, 3, 1, tzinfo=utc), datetime(2022, 3, 31, tzinfo=utc)) + +# Relative phrases (requires reference_date) +result = norm.normalize("last quarter") +print(result) +# (datetime(2024, 1, 1, tzinfo=utc), datetime(2024, 3, 31, tzinfo=utc)) + +result = norm.normalize("last year") +# (datetime(2023, 1, 1, tzinfo=utc), datetime(2023, 12, 31, tzinfo=utc)) + +# Unparseable → None (never raises, logs debug) +result = norm.normalize("recently") +print(result) # None +``` + + + `normalize()` returns `None` for unparseable input — it **never raises** an exception. For relative phrases (`"last quarter"`, `"this year"`, etc.), `reference_date` **must** be set at construction time, otherwise `ValueError` is raised at call time. + + +### `normalize_phrase(phrase)` → `Optional[Dict]` + +Look up a domain-specific temporal phrase in the phrase map: + +```python +meta = norm.normalize_phrase("expiry date") +print(meta) +# {"maps_to": "valid_until", "type": "end", "domain": ["Healthcare", "Supply Chain"]} + +meta = norm.normalize_phrase("retroactive to") +print(meta) +# {"maps_to": "valid_from", "type": "start", "retroactive": True, "domain": ["Regulatory", "Finance"]} + +meta = norm.normalize_phrase("unknown phrase") +print(meta) # None +``` + +Built-in domain phrases cover: General/Policy, Healthcare, Cybersecurity, Supply Chain, Finance, and Energy. + +### Custom Phrase Map + +```python +from datetime import datetime, timezone + +def my_grant_window(ref: datetime): + return ( + datetime(ref.year, 10, 1, tzinfo=timezone.utc), + datetime(ref.year, 10, 31, tzinfo=timezone.utc), + ) + +norm = TemporalNormalizer( + reference_date=datetime(2024, 1, 1, tzinfo=timezone.utc), + phrase_map={"grant application window": my_grant_window}, +) +start, end = norm.normalize("grant application window") +``` + +### Supported Expressions + +| Pattern | Example | Return type | +| :------- | :------- | :---------- | +| ISO 8601 full date/datetime | `"2022-03-15"`, `"2022-03-15T10:00:00Z"` | Point interval | +| Year only | `"2022"` | Full year span | +| Month + year (word) | `"January 2022"`, `"Jan 2022"` | Full month span | +| YYYY-MM (ISO partial) | `"2022-03"` | Full month span | +| Quarter + year | `"Q2 2021"` | Quarter span | +| Relative (built-in) | `"last year"`, `"last quarter"`, `"this month"`, `"three months ago"`, `"six months ago"`, `"two years ago"` | Computed span | +| Ambiguous slash date | `"03/04/2022"` | `None` + `TemporalAmbiguityWarning` | +| Domain phrase | `"expiry date"`, `"retroactive to"` | Only via `normalize_phrase()` | + + +## TemporalQueryRewriter + +Extract temporal intent from a natural-language query so downstream retrieval can apply deterministic temporal filtering. + +**Two modes:** regex-only (no LLM) or LLM-assisted for free-form phrasing. + +```python +from semantica.kg import TemporalQueryRewriter + +# Regex-only (default — no dependencies beyond standard library) +rewriter = TemporalQueryRewriter() + +# LLM-assisted for more complex phrasings +from semantica.llms import Groq +rewriter = TemporalQueryRewriter( + llm_provider=Groq(model="llama-3.1-8b-instant"), + reference_date=datetime.now(timezone.utc), +) +``` + +### `rewrite(query, context=None)` → `TemporalQueryResult` + +```python +# "before" intent +r = rewriter.rewrite("which suppliers were certified before 2021?") +print(r.temporal_intent) # "before" +print(r.at_time.year) # 2021 +print(r.rewritten_query) # "which suppliers were certified?" +print(r.confidence) # 0.85 + +# "between" intent +r = rewriter.rewrite("revenue between Q1 2022 and Q3 2022") +print(r.temporal_intent) # "between" +print(r.start_time) # datetime(2022, 1, 1, tzinfo=utc) +print(r.end_time) # datetime(2022, 9, 30, tzinfo=utc) + +# "during" intent +r = rewriter.rewrite("what decisions were made during Q2 2023?") +print(r.temporal_intent) # "during" +print(r.at_time) # datetime(2023, 4, 1, tzinfo=utc) + +# No temporal phrase +r = rewriter.rewrite("list all active suppliers") +print(r.temporal_intent) # None +print(r.rewritten_query) # "list all active suppliers" +print(r.has_temporal_context()) # False +``` + +### `TemporalQueryResult` Fields + +| Field | Type | Description | +| :---- | :---- | :----------- | +| `rewritten_query` | `str` | Original query with the temporal phrase stripped and whitespace normalised | +| `at_time` | `Optional[datetime]` | Point-in-time bound for `before`, `after`, `at`, `during` intents | +| `start_time` | `Optional[datetime]` | Lower bound for `between` queries | +| `end_time` | `Optional[datetime]` | Upper bound for `between` queries | +| `temporal_intent` | `Optional[str]` | One of `"before"`, `"after"`, `"at"`, `"during"`, `"between"`, or `None` | +| `confidence` | `float` | `0.85` for regex extraction; LLM-propagated confidence or `0.75` fallback | + +| Method | Returns | Description | +| :------ | :------- | :----------- | +| `has_temporal_context()` | `bool` | `True` if any temporal parameter was extracted | + +Supported intent keywords: `before` / `prior to` / `until` / `up to`, `after` / `since` / `following`, `during` / `in` / `within`, `as of` / `at` / `on`, `between … and …`. + + +## TemporalVersionManager + +Create and manage versioned graph snapshots with SHA-256 integrity checking. Supports both **in-memory** (default) and **SQLite persistent** storage. + +```python +from semantica.kg import TemporalVersionManager + +# In-memory (default) +versioner = TemporalVersionManager() + +# SQLite-backed (persists across process restarts) +versioner = TemporalVersionManager( + storage_path="graph_versions.db", + version_strategy="timestamp", # timestamp | incremental | semantic +) +``` + +### Methods + +| Method | Returns | Description | +| :------ | :------- | :----------- | +| `create_snapshot(graph, version_label, author, description)` | `Dict` | Create snapshot with SHA-256 checksum. `author` and `description` are required | +| `create_version(graph, version_label=None, timestamp=None, metadata=None)` | `Dict` | Lightweight version without checksum or mandatory author | +| `list_versions()` | `List[Dict]` | List all stored snapshots | +| `get_version(label)` | `Optional[Dict]` | Retrieve snapshot by label | +| `compare_versions(v1, v2, comparison_metrics=None)` | `Dict` | Detailed entity + relationship diff between two versions or labels | +| `apply_revision(snapshot, revision)` | `Dict` | Temporal revision: supersede matching facts without deleting originals | +| `validate_snapshot(snapshot)` | `bool` | Validate against v1.0 schema (required fields + types) | +| `migrate_snapshot(snapshot)` | `Dict` | Upgrade old-format snapshot to v1.0 | +| `verify_checksum(snapshot)` | `bool` | Integrity check via SHA-256 | + +### Snapshot & Diff Example + +```python +# Create a snapshot (author and description are required) +snap = versioner.create_snapshot( + kg, + version_label="v1.0", + author="analyst@example.com", + description="Initial baseline", +) +print(snap["checksum"]) # SHA-256 hex string + +# List versions +for v in versioner.list_versions(): + print(f"{v['label']:12s} {v['author']} {v['timestamp']}") + +# Get a specific version +past = versioner.get_version("v1.0") + +# Diff: compare two versions (pass labels or snapshot dicts) +diff = versioner.compare_versions("v1.0", "v2.0") +print(f"Entities added: {diff['summary']['entities_added']}") +print(f"Entities removed: {diff['summary']['entities_removed']}") +print(f"Relationships added: {diff['summary']['relationships_added']}") +print(f"Relationships removed: {diff['summary']['relationships_removed']}") + +# Field-level changes on each modified entity +for change in diff["entities_modified"]: + print(f" {change['id']}: {change['changes']}") +``` + +### Temporal Revision + +Apply a revision to specific fact IDs — the originals are **superseded** (not deleted), preserving full audit history: + +```python +revision = { + "fact_ids": ["alice|ceo_of|acme_corp"], # relationship key: src|type|target + "new_valid_from": "2018-03-01", + "new_valid_until": None, # None = TemporalBound.OPEN + "revision_type": "correction", # correction | retroactive + "author": "analyst@example.com", + "reason": "Original start date was incorrect", +} + +revised_snapshot = versioner.apply_revision(snap, revision) +# original fact is preserved with superseded_at set +# replacement fact has new_valid_from, superseded_at = OPEN +``` + +### Integrity & Migration + +```python +# Validate snapshot schema +is_valid = versioner.validate_snapshot(snap) + +# Verify checksum integrity +is_intact = versioner.verify_checksum(snap) + +# Upgrade old-format snapshot (no format_version field) +upgraded = versioner.migrate_snapshot(old_snap) +``` + + +## Context Graph Temporal Features (v0.3.0) + +The `ContextGraph` exposes temporal awareness directly on graph nodes and decisions, available since v0.3.0: + +```python +from semantica.context import ContextGraph +from datetime import datetime, timezone + +graph = ContextGraph(advanced_analytics=True) + +# Add time-bounded nodes +graph.add_node("policy_v1", "policy", + properties={"text": "All transactions require dual approval"}, + valid_from="2021-01-01", + valid_until="2023-06-30") + +graph.add_node("policy_v2", "policy", + properties={"text": "Transactions > $50k require dual approval"}, + valid_from="2023-07-01") + +# Find nodes active at a specific timestamp +current_policies = graph.find_active_nodes( + node_type="policy", + at_time=datetime.now(timezone.utc), +) +for p in current_policies: + print(p["properties"]["text"]) +# → "Transactions > $50k require dual approval" + +# Historical query +past_policies = graph.find_active_nodes( + node_type="policy", + at_time=datetime(2022, 6, 1, tzinfo=timezone.utc), +) +for p in past_policies: + print(p["properties"]["text"]) +# → "All transactions require dual approval" +``` + +### Temporal Decision Windows + +```python +from semantica.context import AgentContext, ContextGraph +from semantica.vector_store import VectorStore + +context = AgentContext( + vector_store=VectorStore(backend="faiss", dimension=768), + knowledge_graph=ContextGraph(), + decision_tracking=True, +) + +# Decision superseded after policy change +old_id = context.record_decision( + category="data_retention", scenario="Set retention window for user PII", + reasoning="GDPR Article 5(1)(e) limits storage", + outcome="retain_90_days", confidence=0.98, + valid_from="2023-01-01", valid_until="2023-06-30", +) + +new_id = context.record_decision( + category="data_retention", scenario="Set retention window for user PII", + reasoning="Legal confirmed 60-day window after new DPA amendment", + outcome="retain_60_days", confidence=0.99, + valid_from="2023-07-01", +) + +# Temporal precedent search +old_prec = context.find_precedents("data retention PII", as_of="2023-03-01", limit=3) +new_prec = context.find_precedents("data retention PII", as_of="2024-01-01", limit=3) +``` + + +## Real-World Patterns + + + + ```python + from semantica.kg import GraphBuilder, TemporalGraphQuery + + builder = GraphBuilder() + kg = builder.build(sources=[{ + "entities": [ + {"id": "alice", "type": "Person"}, + {"id": "finteam", "type": "Team"}, + ], + "relationships": [ + {"source": "alice", "target": "finteam", "type": "leads", + "valid_from": "2020-01-01", "valid_until": "2022-12-31"}, + ], + }]) + + query = TemporalGraphQuery() + + # Incident in Nov 2022 → who was responsible? + result = query.query_at_time(kg, "", "2022-11-15") + leads = [r for r in result["relationships"] if r["type"] == "leads"] + print(f"Team lead at incident: {leads[0]['source']}") + ``` + + + ```python + from semantica.kg import TemporalVersionManager, TemporalGraphQuery + + versioner = TemporalVersionManager(storage_path="policy_history.db") + versioner.create_snapshot(kg_before, version_label="2023-H1", + author="compliance@org.com", + description="Pre-July policy baseline") + versioner.create_snapshot(kg_after, version_label="2023-H2", + author="compliance@org.com", + description="Post-July amendment") + + diff = versioner.compare_versions("2023-H1", "2023-H2") + print(f"Policy changes: {diff['summary']['relationships_modified']}") + ``` + + + ```python + from semantica.kg import TemporalGraphQuery + + report = TemporalGraphQuery().validate_temporal_consistency(kg) + + if report.errors: + print("ERRORS (must fix):") + for e in report.errors: + print(f" [{e['issue_type']}] {e['message']} (fact: {e['fact_id']})") + + if report.warnings: + print("WARNINGS (review):") + for w in report.warnings: + print(f" [{w['issue_type']}] {w['message']} (fact: {w['fact_id']})") + ``` + + + ```python + from semantica.kg import TemporalQueryRewriter, TemporalGraphQuery + + rewriter = TemporalQueryRewriter() + query = TemporalGraphQuery() + + user_query = "Who was responsible for compliance before the 2022 audit?" + result = rewriter.rewrite(user_query) + + if result.has_temporal_context(): + # Use point-in-time filtering + snapshot = query.reconstruct_at_time(kg, result.at_time) + else: + snapshot = kg + + # Now run your retrieval over snapshot with result.rewritten_query + print(f"Intent: {result.temporal_intent}") + print(f"Query: {result.rewritten_query}") + ``` + + + + +## Configuration + +```yaml +kg: + temporal: + enabled: true + default_validity: infinite # OPEN when valid_until is omitted + recorded_at_auto_stamp: true # auto-fill recorded_at on every ingested fact + reasoning: + enabled: true + granularity: day # second|minute|hour|day|week|month|year + engine: allen # allen | point_in_time_only +``` + +- [Knowledge Graph Module](kg) — Core graph construction, `GraphBuilder`, analytics. +- [Context Module](context) — Decision temporal windows and `find_active_nodes()`. +- [Provenance](provenance) — W3C PROV-O lineage stamped alongside temporal metadata. +- [Export](export) — OWL, Turtle, JSON-LD, and Parquet export with temporal annotations. + +- [Temporal Knowledge Graphs](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb) — Temporal reasoning and Allen algebra · Advanced +- [Context Module](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/19_Context_Module.ipynb) — Including temporal decision windows · Intermediate