Data model

How the pieces fit together.

Organization
  └── Project (pins reference data version + eGRID subregion)
        └── Scenario (alternative)
              ├── Functional unit, design flow, service life, CAPEX/OPEX
              └── Inventory item × N
                    ├── Template (parametric blueprint)
                    ├── Drivers {key: value, ...}
                    └── Elementary flows {substance, amount, unit, compartment}

Reference data versioning

Every project pins a reference data version — a coherent bundle of eGRID year + TRACI version + IPCC report. When new factor data is published, new projects use it automatically; existing projects keep their pinned version so their results stay reproducible. You can upgrade explicitly and see the delta.

Multi-tenancy

Every row in tenant-scoped tables carries an org_id. Row-level security on Postgres ensures users only ever see their org's data. Reference data tables (TRACI CFs, eGRID, IPCC, substances) are global and read-only to all authenticated users.

Computed flows

When you add a template-backed inventory item, the template's flow expressions are evaluated against your driver values (using a sandboxed expression evaluator — noeval()), producing concrete elementary flows. Those flows are stored on the inventory item as computed_flows JSONB so they're queryable and cacheable.

Caching

Impact results are cached on the scenario keyed by a SHA-256 hash of (scenario id + sorted inventory updated_at values + reference data version id). Any inventory edit flips a results_dirty flag and clears the cached results; the next Calculate run recomputes from scratch.