Skip to content

Add premise temporal-distribution annotation - #197

Open
TimoDiepers wants to merge 14 commits into
mainfrom
feat/premise-temporal
Open

Add premise temporal-distribution annotation#197
TimoDiepers wants to merge 14 commits into
mainfrom
feat/premise-temporal

Conversation

@TimoDiepers

@TimoDiepers TimoDiepers commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

Adds bw_timex.add_premise_temporal_distributions(...) to annotate existing premise-generated, year-specific bw2 databases with bw_temporalis.TemporalDistributions, so a premise background can be used for time-explicit LCA without hand-defining temporal data.

The temporal parameters and placement rules are sourced from premise's own temporal_distributions.csv (the TrailsDataPackage work, premise ≥ 2.5.0): biomass-growth params go on the dataset's "Carbon dioxide, in air" biosphere exchange; stock-asset / maintenance / end-of-life params go on supplier-keyed technosphere exchanges (maintenance = uniform over [0, lifetime], end-of-life = pulse at lifetime). Ambiguous tags, missing supplier products, missing lifetimes, and malformed rows are recorded as faults and skipped — annotation never aborts mid-database.

No materialization / unfold: this operates on databases the user already has.

Design / API

  • premise_params_to_td(params) — pure converter from premise distribution codes (1 discrete, 3 normal, 4 uniform, 5 triangular, 6 discrete-empirical) to a years-resolution TemporalDistribution (normal scale normalized to the year range; degenerate min==max → single pulse).
  • annotate_database(db_name, specs, *, overwrite=False) — applies premise's placement rules to one bw2 database using an injected TemporalSpecs (premise-free, fully unit-tested).
  • load_temporal_specs() — reuses premise's CSV loader; premise is imported lazily and feature-detected (not version-gated), so core bw_timex imports without premise.
  • add_premise_temporal_distributions(databases, *, overwrite=False) — public entry point; returns an aggregated AnnotationReport.

premise is a lazily-imported, feature-detected optional runtime dependency. It is not declared as a packaging extra because premise pins scipy<1.14, which has no Python 3.13 wheels; install premise separately (e.g. pip install premise>=2.5.0) in an environment that satisfies that constraint.

Tests

New tests/test_premise_temporal.py: converter shapes for every code, placement rules on synthetic bw2 fixtures (biomass-growth, maintenance, end-of-life, ambiguity, idempotency/overwrite, unknown DB, malformed-row fault), the premise-loader adapter (premise-gated / importorskip), and a guard that core import + the converter work with premise blocked. Full suite: 235 passed, 1 skipped.

Add bw_timex/premise_temporal.py with TemporalSpecs and AnnotationReport
dataclasses and premise_params_to_td() converter for distribution codes 1/3/4/5/6.
All 7 unit tests pass; full suite (222 tests) remains green.
…ise_params_to_td

Issue 1: easy_timedelta_distribution evaluates norm.pdf on a normalized [-0.5, 0.5]
axis (not in years), so passing temporal_scale in years made all normal TDs nearly
flat. Fix: convert to normalized axis via scale / (end - start) before the call.

Issue 2: when temporal_min == temporal_max, start == end after _bounds(), causing
easy_timedelta_distribution to raise "Start value is later than end". Fix: early
return _single_pulse(start) for the degenerate case, covering codes 3/4/5.

Tests added: test_normal_peak_near_loc (verifies mass concentration and narrow>wide
spread ratio), test_degenerate_min_equals_max_codes_345 (verifies single pulse for
each code). Also added missing yrs.min()==0 assertion to test_triangular_code_5.

All 224 tests pass, no regressions.
Appends annotate_database() (+ _clean/_supplier_key helpers) to
bw_timex/premise_temporal.py; mirrors premise's biomass-growth,
stock-asset, maintenance (uniform[0,lifetime]), and end-of-life
(pulse@lifetime) placement rules on an existing bw2 database via an
injected TemporalSpecs — no premise import required. Adds 5 bw2-fixture
tests (RED→GREEN verified, 229 total passing).
Add _import_premise_trails (lazy, feature-detects TrailsDataPackage +
FILEPATH_TEMPORAL_PARAMETERS), _DummySelf stand-in, and
load_temporal_specs(path=None) -> TemporalSpecs that delegates CSV
parsing to premise's own loader. premise is kept as an optional dep;
core bw_timex imports are unaffected. Two new tests: monkeypatched
ImportError test passes; importorskip test skips (premise not installed).
Add `add_premise_temporal_distributions(databases, *, overwrite=False)`
as the public entry point for premise temporal annotation; export it
from `bw_timex.__init__`; declare `[project.optional-dependencies] premise`
in pyproject.toml; add the two Task-4 tests (17 passed, 1 skipped).
premise 2.5.0 is not yet on PyPI, making the pinned requirement unresolvable.
The runtime feature-detect guard in _import_premise_trails already gates
on temporal-support, so the version floor is unnecessary.
Append test_core_import_does_not_require_premise to tests/test_premise_temporal.py.
Validates that bw_timex.premise_temporal can be imported and used (premise_params_to_td converter) without premise installed, proving the core is premise-free.
Test monkeypatches builtins.__import__ to block premise, reloads the module, and confirms the converter still works.
Replace unguarded `_apply(exc, premise_params_to_td(...))` calls with a
`_convert_and_apply(ds, exc, params)` helper that catches any Exception,
routes it to `_fault`, and continues — so a single bad exchange never
aborts the whole database pass. Remove the now-unused `_apply`.

Add `test_malformed_stock_asset_params_is_faulted_not_raised`: proves
code-99 stock_asset_params produces a fault, not a raised exception.
Add `test_end_of_life_pulse_at_lifetime`: proves the end_of_life path
emits a single-date pulse at the correct year.

All 235 tests pass; 1 premise-gated skip.
premise pins scipy<1.14 (incompatible with bw2calc>2.0.1), but scipy<1.14
has no Python 3.13 wheels. Declaring a `premise` optional extra forced the
unified uv lock to scipy 1.13.1 across the board, so `uv sync` failed to
build scipy on the 3.13 CI matrix jobs.

Remove the extra and restore pyproject.toml/uv.lock to main (scipy 1.17.1).
premise stays a lazily-imported, feature-detected optional runtime dependency;
the import-error message now tells users to install premise themselves.
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.56%. Comparing base (f2e6d2b) to head (e017645).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
bw_timex/premise_temporal.py 81.72% 34 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
+ Coverage   76.10%   76.56%   +0.46%     
==========================================
  Files          10       11       +1     
  Lines        2113     2300     +187     
==========================================
+ Hits         1608     1761     +153     
- Misses        505      539      +34     

☔ 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.

@TimoDiepers
TimoDiepers force-pushed the feat/premise-temporal branch from 25c3c07 to 8c6acb7 Compare June 24, 2026 15:30
create_biosphere_datapackage hardcoded flip_array to length 1, so any
descended/temporalized background producer with more than one biosphere
flow raised ShapeMismatch under traverse_background=True. Build a per-flow
all-False flip_array (biosphere flows are never flipped).

Add a multibio fixture (descended bg_B carrying CO2 + CH4) and a
parametrized test (priority & bfs) that reproduced the crash.
…riangular)

- load_temporal_specs: strip a UTF-8 BOM before handing the path to premise's
  CSV loader. premise's bundled temporal_distributions.csv ships a BOM and its
  loader opens it without utf-8-sig, corrupting the 'name' column.
- Correct install guidance in docstring/errors: premise's trails work is
  unreleased (git branch), and its scipy<1.14 pin means Python <=3.12.
- premise_params_to_td: add code 2 (lognormal of fleet age; median = -loc,
  fixed log-space sigma 0.55, negative-loc convention, truncated to [min,max]),
  and degrade a code-5 triangular whose integer span is too narrow for >=3
  steps to uniform. All curated stock_asset/biomass rows now convert.
Showcases add_premise_temporal_distributions on the REMIND/ecoinvent
variants: annotates every background variant (required for
traverse_background to shift dates), inspects the written TemporalDistributions,
and runs a time-explicit TimexLCA.
Superseded by example_premise_temporal_distributions.ipynb, which does the
same background temporalization automatically from premise's curated trails
data instead of by hand.
Simplify the install note (install bw_timex + premise trails branch together
on Python 3.12), drop the removed sibling-notebook reference, and add dynamic
LCIA + characterized-inventory waterfall steps.
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.

1 participant