# How to Query Historical Graph States with Point-in-Time Queries in Semantica

> Query historical graph states in Semantica using TemporalGraphQuery and query_at_time(). Reconstruct any past knowledge graph state by filtering timestamps on entities and relationships.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: how-to-guide
- Published: 2026-08-16

---

**Use `TemporalGraphQuery` with `query_at_time()` to reconstruct any past state of a Semantica knowledge graph by filtering entities and relationships against their `valid_from`/`valid_until` timestamps.**

Semantica provides native support for **temporal knowledge graphs**, storing validity periods directly on nodes and edges. The `TemporalGraphQuery` engine in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py) enables precise **point-in-time queries** to retrieve exact graph snapshots as they existed at any historical moment.

## How TemporalGraphQuery Works

The query engine operates in four stages when processing a point-in-time request:

1. **Initialize** — Configure logging, progress tracking, and optional temporal reasoning
2. **Parse timestamp** — Normalize ISO strings, Unix timestamps, or `datetime` objects
3. **Reconstruct subgraph** — Deep-copy the graph and filter inactive elements via `reconstruct_at_time()`
4. **Return snapshot** — Package the result with metadata, active entities, and valid relationships

### Core Methods in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py)

| Method | Location | Purpose |
|--------|----------|---------|
| `__init__()` | Lines 45-72 | Sets up the engine with optional temporal reasoning and progress tracking |
| `query_at_time()` | Lines 107-144 | Public entry point: parses time, calls reconstruction, builds result dictionary |
| `reconstruct_at_time()` | Lines 170-209 | Deep-copies graph and applies temporal filters using `_entity_active_at_time()` and `_relationship_active_at_time()` |

## Basic Point-in-Time Query Example

```python
from semantica.kg.temporal_query import TemporalGraphQuery

# Define a temporal knowledge graph

graph = {
    "entities": [
        {"id": "e1", "name": "Alice"},
        {"id": "e2", "name": "Bob"},
    ],
    "relationships": [
        {
            "id": "r1",
            "source": "e1",
            "target": "e2",
            "type": "knows",
            "valid_from": "2022-01-01T00:00:00Z",
            "valid_until": "2023-06-30T23:59:59Z",
        },
        {
            "id": "r2",
            "source": "e2",
            "target": "e1",
            "type": "likes",
            "valid_from": "2023-07-01T00:00:00Z",
            "valid_until": None,  # still active

        },
    ],
}

# Initialize query engine

tgq = TemporalGraphQuery(enable_temporal_reasoning=False)

# Query graph state as of March 15, 2023

snapshot = tgq.query_at_time(
    graph=graph,
    query="snapshot-2023-03-15",
    at_time="2023-03-15T12:00:00Z",
)

print(f"Timestamp: {snapshot['at_time']}")
print(f"Active relationships: {snapshot['num_relationships']}")
print(snapshot["relationships"])

```

**Output:**

```

Timestamp: 2023-03-15 12:00:00+00:00
Active relationships: 1
[{'id': 'r1', 'source': 'e1', 'target': 'e2', 'type': 'knows', ...}]

```

Only `r1` appears because `r2` had not yet become valid. The `reconstruct_at_time()` method filters edges by checking `valid_from <= query_time < valid_until` (or `valid_until is None` for unbounded validity).

## Configuring Temporal Axes and Granularity

### Time Axis Selection

By default, `query_at_time()` uses the **valid time axis** (`valid_from`/`valid_until`). Switch to **transaction time** for audit-style queries:

```python

# Use transaction timestamps instead of validity periods

snapshot = tgq.query_at_time(
    graph=graph,
    query="audit-snapshot",
    at_time="2023-03-15T12:00:00Z",
    time_axis="transaction",  # default: "valid"

)

```

### Temporal Granularity

The engine respects granularity settings when normalizing timestamps. Available levels include `second`, `minute`, `hour`, `day`, `week`, `month`, `year`. Configure via the graph's temporal model or pass explicitly to parsing utilities in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py).

## Advanced Query Options

### Include Historical Metadata

To retrieve inactive relationships alongside active ones for audit purposes:

```python
snapshot = tgq.query_at_time(
    graph=graph,
    query="full-history",
    at_time="2023-03-15T12:00:00Z",
    include_history=True,  # includes edges valid before/after timestamp

)

```

### Batch Historical Snapshots

Generate time-series graph states for analysis or visualization:

```python
from datetime import datetime, timedelta

timestamps = [datetime(2023, 1, 1) + timedelta(days=30*i) for i in range(12)]
snapshots = [
    tgq.query_at_time(graph, f"month-{i}", ts)
    for i, ts in enumerate(timestamps)
]

```

### Combine with Pattern Detection

Feed point-in-time results into temporal pattern analysis:

```python

# First obtain a historical snapshot

snapshot = tgq.query_at_time(graph, "pattern-base", "2023-06-01")

# Then detect patterns existing at that moment

patterns = tgq.query_temporal_pattern(
    graph=snapshot,
    pattern_type="causal_chain",
    time_window=timedelta(days=30),
)

```

## Key Supporting Files

| File | Role |
|------|------|
| [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py) | Core query engine with `TemporalGraphQuery` class |
| [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) | Temporal value parsing (`parse_temporal_value`), `TemporalBound` dataclass |
| [`semantica/utils/progress_tracker.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/utils/progress_tracker.py) | Progress monitoring for long-running reconstructions |
| [`semantica/visualization/temporal_visualizer.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/visualization/temporal_visualizer.py) | Rendering point-in-time snapshots and evolution timelines |

## Summary

- **Point-in-time queries** use `TemporalGraphQuery.query_at_time()` in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py)
- The engine filters entities and relationships by their `valid_from`/`valid_until` timestamps via `reconstruct_at_time()`
- Support multiple **time axes** (`valid`, `transaction`) and **granularity levels**
- Enable `include_history=True` to retrieve inactive elements for auditing
- Combine with **pattern detection** and **batch processing** for advanced temporal analytics

## Frequently Asked Questions

### What timestamp formats does Semantica accept for point-in-time queries?

Semantica accepts **ISO 8601 strings** (e.g., `"2023-03-15T12:00:00Z"`), **Unix timestamps** (integer or float seconds since epoch), and **Python `datetime` objects**. The `parse_temporal_value()` function in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) normalizes all inputs to timezone-aware datetimes before filtering.

### How does Semantica handle unbounded or currently valid relationships?

Relationships with `valid_until=None` are treated as **permanently valid** from their `valid_from` timestamp forward. The `_relationship_active_at_time()` helper checks `valid_until is None or query_time < valid_until`, so open-ended edges remain active in all future point-in-time queries until explicitly closed.

### Can I query historical states using transaction time instead of validity time?

Yes. Pass `time_axis="transaction"` to `query_at_time()` or `reconstruct_at_time()`. This switches the filtering logic to use `transaction_start` and `transaction_end` fields instead of `valid_from`/`valid_until`, enabling audit trails of when records were physically added or modified in the system.

### Is progress tracking available for large graph reconstructions?

Yes. `TemporalGraphQuery` automatically initializes a progress tracker from [`semantica/utils/progress_tracker.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/utils/progress_tracker.py). For long-running operations on large graphs, monitor the tracker to observe filtering progress through entities and relationships during the reconstruction phase.