Skip to content

Read background database timing from database metadata - #219

Merged
TimoDiepers merged 24 commits into
brightway-lca:mainfrom
TimoDiepers:feat/representative-time-metadata
Aug 24, 2026
Merged

Read background database timing from database metadata#219
TimoDiepers merged 24 commits into
brightway-lca:mainfrom
TimoDiepers:feat/representative-time-metadata

Conversation

@TimoDiepers

Copy link
Copy Markdown
Member

Closes #217. Supersedes #218.

TimexLCA used to learn what a background database represents in one way only: the database_dates mapping, hand-written at every call site. The information already exists in the database — a premise export knows the year it was built for — but it lived only in the database name, so every study re-typed it, and a typo either raised or, worse, mapped a vintage to the wrong year.

premise PR #303 (on master, released after 2.4.9.2) closes the gap on the producing side: exported databases now carry what they represent in their Brightway metadata. This PR makes bw_timex read it.

tlca = TimexLCA(demand={("foreground", "A"): 1}, method=("GWP", "example"))

What changed

Timing is read from database metadata by default. Every registered database carrying representative_time is mapped to its point in time. ISO strings, datetimes and "dynamic" are all accepted; databases holding the functional unit default to "dynamic".

set_database_metadata records it for databases that don't bring it themselves:

from bw_timex import set_database_metadata

set_database_metadata("db_2030", representative_time=datetime(2030, 1, 1))
set_database_metadata("my_2050_variant", representative_time="2050-01-01",
                      iam_model="remind", pathway="SSP2-PkBudg500")

Dates are stored as ISO strings, since Brightway serializes database metadata to JSON.

TimexLCA(scenario={...}) selects one scenario when a project holds several. Any metadata key works — iam_model, pathway, system_model, ecoinvent_version, premise_version, or one you set yourself. Databases that don't declare the filtered key (your foreground, a hand-built vintage) are never filtered out.

A project holding several scenarios stays unambiguous — bw_timex refuses to guess, and names only the keys that actually differ:

Several background scenarios found in this project:
  pathway=SSP2-PkBudg500: ei310_remind_SSP2-PkBudg500_2030, ..._2040, ..._2050
  pathway=SSP2-Base: ei310_remind_SSP2-Base_2030, ..._2040, ..._2050
Select one, e.g. scenario={'pathway': '...'}, or map the databases explicitly with `database_dates`.

A filter key no database declares, and a filter that matches nothing, are errors too — never a silent non-time-explicit run.

database_dates is unchanged and exclusive. When passed, it is the whole mapping and metadata is not read at all, so existing scripts behave exactly as before — a legacy call in a project that also holds ten premise vintages must not silently pull those ten in.

Multi-scenario databases (premise superstructure and scenario-array exports) are recognised and skipped: there is no single technosphere per point in time to pick. They remain usable by naming them in database_dates.

Also in here

Three user-facing error messages named database_dates when the timing may now have come from metadata (validation.py, timeline_builder.py, edge_extractor.py); they now name both sources.

Docs and notebooks

New page What a database represents covering the metadata, the setter, scenario selection, and database_dates as the explicit override — including the note that metadata resolution pulls in every registered database carrying representative_time, which costs setup time and lets those databases take part in temporal-market interpolation. The quickstart and walkthrough steps 1–2 teach the new default. All twelve notebooks are updated; paper_case_study.ipynb is deliberately untouched.

Testing

338 passed, 0 failed. tests/test_database_metadata.py covers resolution, ISO/datetime/"dynamic" values, database_dates exclusivity, scenario filtering, the ambiguity and empty-filter errors, the multi-scenario skip, and set_database_metadata round-tripping through bd.databases.flush(), plus an end-to-end check that the metadata path and the database_dates path give the same score.

Note for reviewers

The design and implementation plan are committed under docs/superpowers/. One deliberate compatibility wrinkle: scenario sits at positional slot 4, ahead of use_global_lci_cache, so TimexLCA(demand, method, dates, False) now raises rather than disabling the cache. It fails loudly, never silently.

…ime-explicit

`_resolve_database_dates` now raises a ValueError naming the scenario filter
and the values actually declared for its keys when the filter matches no
database, instead of quietly falling back to a plain (non-time-explicit)
LCA. The "no representative_time metadata anywhere" log is now accurate for
that case only. Reworded the three "database is not marked as dynamic" /
"remove one of the two databases from database_dates" error messages to
also credit representative_time metadata as a source of timing, since
database_dates may never have been passed. Documented the ValueError cases
in _resolve_database_dates's docstring, and added tests covering the new
typo error and TimexLCAInputs.validate_scenario, which had no direct test
coverage.
The tutorials taught the old, mandatory database_dates mapping. They now
record each background database's representative_time once with
set_database_metadata and let TimexLCA read it, matching the new default
behaviour. Notebook 4 (import_model_from_excel) could not be re-executed
in this environment: bw2io is not installed in .venv, unrelated to this
change; its stored outputs are left as-is and its source cells are
updated.
The prior update to notebooks/tutorials/4_import_model_from_excel.ipynb
was round-tripped through notebook tooling that re-serialized the
whole file with different indentation and no trailing newline,
turning a four-cell content change into a ~2100-line diff. Restore
the file to the original's formatting (4-space indent, alphabetical
keys, trailing newline) while keeping only the intended edits to
cells 21, 22, 23, and 27.
The eight remaining notebooks (two self-contained advanced examples plus
six premise/ecoinvent notebooks) taught the old, mandatory database_dates
mapping. They now rely on the representative_time metadata bw_timex reads
automatically, matching the new default behaviour from Tasks 1-6.

- background_temporal_distributions.ipynb and uncertainty_with_datapackages.ipynb
  were re-executed against their own self-contained databases.
- The six premise/ecoinvent notebooks had only their source cells edited;
  stored outputs are unchanged except where they printed a now-obsolete
  database_dates dict.
- The electric-vehicle notebooks now call set_database_metadata on their
  own "without EOL" background copies, using the same representative_time
  as the premise vintage they were copied from.
- exercise_ev_vs_petrol_solutions.ipynb's project holds two IAM pathways,
  so its markdown now shows the scenario argument needed to pick one.
Make the premise_version example in database_metadata.py's docstring and
background_database_metadata.md's callout self-consistent: both showed
2.4.9.1 alongside representative_time, but the callout says premise only
writes that key from the version after 2.4.9.2 onwards. Bumped the example
version to 2.4.9.3.

Added a CHANGES.md bullet for the reworded database_dates-specific error
messages and the new ValueError on a scenario filter that matches no
database, which Task 5's changelog entries had missed.
A database that declares no scenario keys survives every `scenario`
filter, so the resolved mapping stayed non-empty and masked a filter
that matched none of the actual scenario databases (e.g. a typo'd
pathway value with an unrelated hand-built vintage also in the
project). Raise based on whether any surviving database positively
declares one of the filtered keys, not on whether the resolved
mapping is empty.

Also treat `database_dates={}` as an explicit (invalid) mapping
rather than falling through to metadata resolution, and hoist the
`DatabaseMetadataInputs` import in database_metadata.py to module
scope now that there's no cycle to avoid.
The quickstart cheat-sheet listed scenario before database_dates;
the real signature order is (demand, method, database_dates,
scenario, use_global_lci_cache).

Reword the "every database with representative_time is pulled in"
note: it's not only a setup-time cost, it's a results concern too,
since everything in database_dates_static becomes a temporal-market
interpolation candidate.
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.70115% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.78%. Comparing base (f67e3ef) to head (107b047).

Files with missing lines Patch % Lines
bw_timex/database_metadata.py 98.05% 2 Missing ⚠️
bw_timex/validation.py 92.59% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #219      +/-   ##
==========================================
+ Coverage   78.51%   79.78%   +1.26%     
==========================================
  Files          10       12       +2     
  Lines        2472     2642     +170     
==========================================
+ Hits         1941     2108     +167     
- Misses        531      534       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…books

premise only writes representative_time metadata from 2.4.9.2 onwards
(current release is 2.4.9.1), so notebooks claiming premise databases
already know their point in time now say so and point to
set_database_metadata for earlier versions.
…notebooks

The scenario argument filters on the same premise >= 2.4.9.2 metadata as
representative_time, so it needs the same version qualifier. Several
notebooks also only mentioned set_database_metadata as the escape hatch
for pre-2.4.9.2 premise, without pointing out that database_dates works
as a full mapping/override too.
@TimoDiepers
TimoDiepers merged commit 29a7538 into brightway-lca:main Aug 24, 2026
11 checks passed
@TimoDiepers
TimoDiepers deleted the feat/representative-time-metadata branch August 24, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make representative time a database attribute

1 participant