# How to Use Temporal Knowledge Graphs with `valid_from`/`valid_until` and Allen Interval Algebra in Semantica

> Learn to leverage temporal knowledge graphs with valid_from/valid_until and Allen interval algebra using Semantica's TemporalModel and TemporalReasoningEngine for advanced temporal querying.

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

---

**Use the `TemporalModel` class in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) to store temporal facts with `valid_from` and `valid_until` fields, then query them with the `TemporalReasoningEngine` in [`semantica/kg/temporal_reasoning.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_reasoning.py) which implements full Allen interval algebra for point-in-time, range, and path-based temporal reasoning.**

Temporal knowledge graphs extend traditional KGs by tracking *when* facts are true rather than treating them as eternal. In the **Semantica** open-source framework, this is achieved through explicit temporal bounds and a complete implementation of Allen's thirteen interval relations. This guide covers how to model, store, and query time-varying knowledge using the framework's core temporal components.

## Modeling Temporal Facts with `valid_from` and `valid_until`

Semantica represents temporal information through two mandatory fields on entities and relationships:

- **`valid_from`**: The instant a fact becomes true
- **`valid_until`**: The instant it ceases to be true (or `TemporalBound.OPEN` for ongoing facts)

The **Temporal Model** ([`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py)) normalizes these to UTC `datetime` objects. Key classes include:

| Class | Purpose |
|-------|---------|
| `BiTemporalFact` | Stores both assertion time and valid time (bitemporal modeling) |
| `TemporalBound.OPEN` | Sentinel value representing unbounded future validity |
| `TemporalInterval` | Internal representation of [start, end) periods |

When constructing facts, always provide ISO 8601 strings or datetime objects. The normalizer handles timezone conversion internally.

## Understanding Allen Interval Algebra in `TemporalReasoningEngine`

The **Allen interval algebra** implementation resides in `TemporalReasoningEngine` ([`semantica/kg/temporal_reasoning.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_reasoning.py)). Each temporal fact becomes a `TemporalInterval` with start, end, and optional label. The engine exposes these core methods:

| Method | Returns | Use Case |
|--------|---------|----------|
| `relation(a, b)` | `IntervalRelation` enum | Determine which of 13 Allen relations holds |
| `overlaps(a, b)` | `bool` | Check for any temporal intersection |
| `contains(outer, inner)` | `bool` | Verify interval containment |
| `active_at(interval, ts, granularity=None)` | `bool` | Point-in-time membership test |
| `merge_intervals(intervals)` | `List[TemporalInterval]` | Build non-overlapping union |
| `gap_analysis(intervals, domain_start, domain_end)` | `List[TemporalInterval]` | Find uncovered periods |
| `coverage_percentage(intervals, domain)` | `float` | Calculate temporal completeness |

The `IntervalRelation` enum implements all thirteen Allen relations: `BEFORE`, `MEETS`, `OVERLAPS`, `STARTS`, `DURING`, `FINISHES`, `EQUALS`, and their inverses (`AFTER`, `MET_BY`, `OVERLAPPED_BY`, `STARTED_BY`, `CONTAINS`, `FINISHED_BY`).

## Querying Temporal Knowledge Graphs

Higher-level operations are provided by `TemporalGraphQuery` in [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py) (lines 44-66). All queries delegate to the reasoning engine.

### Point-in-Time Queries

Use `query_at_time` to reconstruct the graph as it existed at a specific instant:

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

engine = TemporalGraphQuery()
result = engine.query_at_time(
    graph=my_kg,               # dict with "entities" and "relationships"

    query="*",
    at_time="2023-07-15T00:00:00Z"
)
print("Valid relationships at that date:", len(result["relationships"]))

```

Internally, this calls `_relationship_active_at_time` → `active_at()` to filter relationships whose valid interval contains the timestamp.

### Time-Range Queries

For interval-based retrieval, use `query_time_range` with `normalize_interval` for granularity alignment:

```python

# Relationships overlapping a range

overlapping = engine._relationship_overlaps_range(graph, start, end)

# Relationships fully covering a range

covering = engine._relationship_covers_range(graph, start, end)

```

### Determining Allen Relations Between Facts

Compare two temporal intervals explicitly:

```python
from semantica.kg.temporal_reasoning import TemporalReasoningEngine, TemporalInterval

engine = TemporalReasoningEngine()
a = TemporalInterval(
    start=engine._parse_time("2022-01-01"),
    end=engine._parse_time("2022-06-30")
)
b = TemporalInterval(
    start=engine._parse_time("2022-04-01"),
    end=engine._parse_time("2022-12-31")
)

relation = engine.relation(a, b)
print("Allen relation:", relation.value)   # → "overlaps"

```

## Advanced Temporal Path Finding and Pattern Detection

### Temporal Path Queries

The `find_temporal_paths` method respects validity intervals during graph traversal:

```python
paths = engine.find_temporal_paths(
    graph=my_kg,
    source="A",
    target="B",
    start_time="2021-01-01",
    end_time="2023-01-01",
    ordering_strategy="strict"  # "strict", "overlap", or "loose"

)
print("Found", paths["num_paths"], "temporal paths")

```

**Ordering strategies:**
- **`strict`**: Edges must follow chronological order (end time ≤ next start time)
- **`overlap`**: Adjacent edges must share at least one common instant
- **`loose`**: Only requires edges to exist within the query window

### Gap Analysis and Coverage

Analyze temporal completeness of your data:

```python
intervals = [
    TemporalInterval(start=engine._parse_time("2020-01-01"), end=engine._parse_time("2020-03-01")),
    TemporalInterval(start=engine._parse_time("2020-04-15"), end=engine._parse_time("2020-06-01")),
]
gaps = engine.gap_analysis(intervals, domain_start="2020-01-01", domain_end="2020-12-31")
for gap in gaps:
    print("Gap from", gap.start, "to", gap.end)

```

The `TemporalPatternDetector` ([`semantica/kg/temporal_pattern_detector.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_pattern_detector.py)) extends these primitives to discover sequential and cyclic patterns automatically.

## Key Source Files Reference

| File | Role |
|------|------|
| [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) | Normalizes `valid_from`/`valid_until`, defines `BiTemporalFact`, `TemporalBound` |
| [`semantica/kg/temporal_reasoning.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_reasoning.py) | `TemporalReasoningEngine` with full Allen algebra implementation |
| [`semantica/kg/temporal_query.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_query.py) | High-level `TemporalGraphQuery` API for point-in-time, range, and path queries |
| [`semantica/kg/temporal_pattern_detector.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_pattern_detector.py) | Pattern detection using interval relations |
| [`semantica/visualization/temporal_visualizer.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/visualization/temporal_visualizer.py) | Timeline and dashboard visualization |

## Summary

- **Store temporal facts** using `valid_from` and `valid_until` fields normalized by `TemporalModel`
- **Reason about intervals** with `TemporalReasoningEngine` implementing all thirteen Allen relations
- **Query at instants** with `query_at_time` using `active_at()` filtering
- **Query over ranges** with `query_time_range` and overlap/coverage predicates
- **Find valid paths** with `find_temporal_paths` and configurable ordering strategies
- **Analyze coverage** using `gap_analysis` and `merge_intervals`

According to the Semantica source code, these components operate on UTC-normalized `datetime` objects with optional granularity parameters for flexible temporal matching.

## Frequently Asked Questions

### What is the difference between `valid_from`/`valid_until` and bitemporal modeling?

`valid_from` and `valid_until` represent **valid time** (when a fact is true in reality). **Bitemporal modeling** adds **assertion time** (when the database recorded the fact). Use `BiTemporalFact` in [`semantica/kg/temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/kg/temporal_model.py) when you need to track both—for example, to correct historical records while preserving the original ingestion timestamp.

### How does Semantica handle open-ended intervals?

The `TemporalBound.OPEN` sentinel in [`temporal_model.py`](https://github.com/semantica-agi/semantica/blob/main/temporal_model.py) represents unbounded future validity. In the reasoning engine, this compares as greater than any concrete timestamp, enabling queries against facts that remain current.

### Which Allen relation should I use for "happened during the same time"?

Use `relation()` and check for `IntervalRelation.OVERLAPS`, `OVERLAPPED_BY`, `STARTS`, `STARTED_BY`, `FINISHES`, `FINISHED_BY`, `DURING`, `CONTAINS`, or `EQUALS`. For a simple boolean check, call `overlaps(a, b)` which returns true for any non-disjoint relation (excluding only `BEFORE`, `AFTER`, `MEETS`, `MET_BY`).

### Can I query with day-level granularity instead of exact timestamps?

Yes. Pass the `granularity` parameter to `active_at()` or use `normalize_interval()` in [`temporal_reasoning.py`](https://github.com/semantica-agi/semantica/blob/main/temporal_reasoning.py) before comparisons. This rounds timestamps to the specified unit (day, hour, etc.) for fuzzy temporal matching.