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.

For site-specific rigor you can also override those flows directly (Edit flowson any inventory row): substitute a measured value, add a flow the template doesn't model, change a unit/phase/compartment, or attach a per-flow source citation. Each referenced substance must exist in the dictionary so it characterizes — never silently dropped. (Re-running Edit drivers on a template item recomputes from the formulas and replaces manual flow edits.)

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.