TimeDB stores overlapping forecast revisions, auditable corrections, and "time-of-knowledge" history as plain rows in ClickHouse. Every value carries a three-dimensional timestamp so you can replay exactly what was known at any past instant.
Traditional time-series stores assume one immutable value per timestamp. TimeDB is built for the messy reality of forecasts that get revised, observations that get corrected, and backtests that need strict point-in-time audits.
🧊 The 3D Temporal Data Model
At the heart of TimeDB is its three-dimensional approach to time. We track not just when data is valid, but when it became known and when it was altered.
| Dimension | Description | Real-World Example |
|---|---|---|
📅 valid_time |
The time the value represents a fact for. | "Wind speed forecast for Wednesday 12:00" |
⏰ knowledge_time |
The time when the value was predicted/known. | "Generated on Monday 18:00" |
✏️ change_time |
The time when the value was written or changed. | "Manually overridden on Tuesday 09:00" |
Audit & Metadata: Every row also carries
changed_byandannotationtext fields so corrections leave a readable trail instead of silent overwrites.
✨ Why Choose TimeDB?
- 📊 Forecast Revisions: Store overlapping forecasts side-by-side — every
knowledge_timeis preserved. - 🔄 Auditable Updates: Corrections are new rows with a fresh
change_time; reads collapse them into the latest state, with full history available on demand. - ⏪ True Backtesting: Query historical data as of any point in time ("What did our model know last Monday?"), or use
read_relative()for per-window day-ahead cutoffs. - 🗂️ Retention Tiers: Pick
short/medium/long/foreverper series; ClickHouse drops whole partitions when TTLs expire. - 🧹 Idempotent Writes: Opt into
write(..., skip_unchanged=True)and rows that only duplicate the latest stored value are dropped before insert — writing the same window repeatedly stops bloating storage. Off by default; full provenance kept unless you ask.unchanged_scope="auto"applies a different comparison key per series in one call, so a frame mixing plain actuals with versioned publications dedupes each correctly. - 🪶 Stateless & Minimal: One class, two tables, no catalog. Series identity (
series_id) is owned by the caller — no naming, units, or labels to keep in sync.
🚀 Quick Start
1. Installation
pip install timedb
Requires Python 3.12+ and a reachable ClickHouse instance. TimeDB reads its connection string from TIMEDB_CH_URL (also picked up from a .env file).
2. Usage Example
import polars as pl
from datetime import datetime, timezone
from timedb import TimeDBClient
td = TimeDBClient() # reads TIMEDB_CH_URL
td.create() # creates series_values + run_series tables
# 1. Write a forecast run for series_id=42, issued at 06:00.
kt = datetime(2025, 1, 1, 6, tzinfo=timezone.utc)
df = pl.DataFrame({
"series_id": [42] * 24,
"valid_time": [datetime(2025, 1, 1, h, tzinfo=timezone.utc) for h in range(24)],
"value": [100.0 + i * 2 for i in range(24)],
})
td.write(df, retention="medium", knowledge_time=kt)
# 2. A later forecast revision — same valid_time window, higher knowledge_time.
kt2 = datetime(2025, 1, 1, 12, tzinfo=timezone.utc)
td.write(df.with_columns(pl.col("value") + 5), retention="medium", knowledge_time=kt2)
# 3. Latest value per valid_time (the second run wins).
latest = td.read(series_ids=[42])
# 4. Full forecast history — one row per (knowledge_time, valid_time).
history = td.read(series_ids=[42], include_knowledge_time=True)
Required write columns are series_id, valid_time, value. Everything else (change_time, run_id, changed_by, annotation, valid_time_end) is optional and stamped with safe defaults per batch. All timestamp columns must be timezone-aware.
🤝 The Full Stack — TimeDB + EnergyDB
TimeDB stores rows keyed by integer series_id and nothing else. That's enough when you're managing identity in your own application — but for the energy domain, we ship the full stack.
EnergyDB adds, on top of the same ClickHouse store (plus a thin PostgreSQL catalog):
- 🌳 Typed asset trees:
Portfolio→Site→WindTurbine/PVArray/Battery, etc. — every asset class from EnergyDataModel. - 🔗 Grid edges:
Line, transformer, pipe — connect any two nodes, attach their own time series. - 🧭 Fluent path scopes:
client.get_node("my-portfolio", "Offshore-1", "T01").read(name="power", data_type="actual")resolves to one indexed SQL query. - ⚖️ Per-series canonical units: declare
MWonce;pintconverts on every read and write via aunit=kwarg. - 🧬 Run/workflow provenance:
workflow_id,model_name,run_start_time, etc. attached at write time. - 📋 Diffable structural changes:
dry_run=Truepreviews every rename, move, delete, or insert as aTreeDiffbefore you commit.
Use TimeDB for the raw storage primitive. Use EnergyDB for an asset-aware catalog of energy portfolios.
🧪 Try it in Google Colab
Want to try TimeDB without a local setup? Open our Quickstart in Colab — the first cell automatically installs ClickHouse inside the VM.
Note: Data persists only within the active Colab session. Additional notebooks are available in the
examples/directory.
📚 Documentation & Resources
🤝 Contributing
Contributions are welcome! If you're interested in improving TimeDB, please see our Development Guide for local setup instructions.
Licensed under the Apache-2.0 License.
Find a bug or have a feature request? Open an Issue.
Metadata
Release files for timedb 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| timedb-0.12.0.tar.gz | 38.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| timedb-0.12.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.5 kB
Release files / timedb-0.12.0.tar.gz
| Download URL | timedb-0.12.0.tar.gz |
|---|---|
| Size | 38.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
734ab73ee0f2588e8ec3b5adc2128f0ed786e6c86ca23366d0fdc6526385076d
|
|
BLAKE2b-256 checksum How to use checksums |
6038068c5c216eeb5d0ed65b1c1f3b88233c97828b8253ed07efd86ca21f4066
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.
Transparency logRelease files / timedb-0.12.0-py3-none-any.whl
| Download URL | timedb-0.12.0-py3-none-any.whl |
|---|---|
| Size | 28.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a8f9db52e3eec9a4d9bd3f46cf4e0a49337e81cf56e4503d9b8e2951a992baf
|
|
BLAKE2b-256 checksum How to use checksums |
221e8a07876253d90a2de3d2f35ad1efa411a91e15689e9d2abd896035aea0d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.
Transparency log