Minimal stateless ClickHouse library for 3-dimensional time series
Project description
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. - 🪶 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file timedb-0.7.0.tar.gz.
File metadata
- Download URL: timedb-0.7.0.tar.gz
- Upload date:
- Size: 25.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8697e7ec4548a1cfe01ec5155d32d38d7b1c38800c6c3674753a989ea6a31894
|
|
| MD5 |
de789dac69332a938b3dc0372ee32608
|
|
| BLAKE2b-256 |
7afd4a54f13dc9173604761b0a92da07e832940b32d98fa9696f6c434be34500
|
Provenance
The following attestation bundles were made for timedb-0.7.0.tar.gz:
Publisher:
publish.yml on rebase-energy/timedb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timedb-0.7.0.tar.gz -
Subject digest:
8697e7ec4548a1cfe01ec5155d32d38d7b1c38800c6c3674753a989ea6a31894 - Sigstore transparency entry: 1641182343
- Sigstore integration time:
-
Permalink:
rebase-energy/timedb@c0b6751edba4a8f8d8e7fb63081970ec9b94a8b1 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/rebase-energy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c0b6751edba4a8f8d8e7fb63081970ec9b94a8b1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file timedb-0.7.0-py3-none-any.whl.
File metadata
- Download URL: timedb-0.7.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70e7145a3d75d3fcdd7c8326a81a90dd69f00865519651fb70f60d7aee602f73
|
|
| MD5 |
15b587a8620b0de1df8d5e329f2064d6
|
|
| BLAKE2b-256 |
236701db7fa6c59d9cba90be76acdd3f4a7790a2451cc81b494a3ce3f6c8b24b
|
Provenance
The following attestation bundles were made for timedb-0.7.0-py3-none-any.whl:
Publisher:
publish.yml on rebase-energy/timedb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timedb-0.7.0-py3-none-any.whl -
Subject digest:
70e7145a3d75d3fcdd7c8326a81a90dd69f00865519651fb70f60d7aee602f73 - Sigstore transparency entry: 1641182412
- Sigstore integration time:
-
Permalink:
rebase-energy/timedb@c0b6751edba4a8f8d8e7fb63081970ec9b94a8b1 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/rebase-energy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c0b6751edba4a8f8d8e7fb63081970ec9b94a8b1 -
Trigger Event:
release
-
Statement type: