Emission Computation — Data Reference Factor Retrieval Strategies Two strategies, mutually exclusive per EmissionComputation:
Strategy Trigger Returns Rows produced A — primary_factor_id primary_factor_id in data_entry.data 1 factor 1 row B — classification query FactorQuery(kind, subkind, **ctx) 1..N factors 1..N rows
Pre-computation Layer Some formula inputs are not in data_entry.data and must be derived before factor retrieval. Handlers declare a pre_compute async hook that runs first and returns an enriched ctx dict merged with data_entry.data.
DataEntryType Pre-computed key Source Pure? scientific / it / other annual_kwh = ((active_w + standby_w) / 2) * hours / 1000 Arithmetic ✅ professional_travel (plane) distance_km = LocationService.calculate_distance(origin, dest) DB — locations table ❌ building kwh_per_m2 per subcategory from archibus_rooms DB — archibus_rooms table ❌ (tech debt — will move to factors)
Full DataEntryType Matrix Headcount — member / student Field Value EmissionTypes food, waste, commuting, grey_energy Strategy B — classification query Factor query kind=emission_type.name, subkind=None, data_entry_type Factors per type N (e.g. food → food__vegetarian + food__non_vegetarian) Rows produced 1 row per factor (food → 2 rows, waste → up to 15 rows) Formula fte × kg_co2eq_per_fte Quantity key fte from data_entry.data Pre-compute None
Professional Travel — Train Field Value EmissionTypes professional_travel__train__class_1, professional_travel__train__class_2 Resolution Runtime — cabin_class in data_entry.data → leaf via resolve_emission_types Strategy B — classification query Factor query kind="train", subkind=class_name, country_code from ctx Factors per type 1 Formula distance_km × kg_co2eq_per_km Quantity key distance_km from data_entry.data Pre-compute None (distance_km stored directly for train)
Professional Travel — Plane Field Value EmissionTypes plane__first, plane__business, plane__eco_plus, plane__eco Resolution Runtime — cabin_class in data_entry.data → leaf via resolve_emission_types Strategy B — classification query Factor query kind="plane", subkind=cabin_class, distance_km from ctx Factors per type 1 Formula distance_km × kg_co2eq_per_km Quantity key distance_km Pre-compute ✅ distance_km = LocationService.calculate_distance(origin_iata, dest_iata) — DB call
Building Field Value EmissionTypes buildings__rooms__lighting, cooling, ventilation, heating_elec, heating_thermal Strategy B — classification query Factor query kind=subcategory (e.g. "heating"), subkind=energy_type ("elec"/"therm"), building_name from ctx Factors per type 1 per subcategory Formula kwh_per_m2 × surface_m2 × kg_co2eq_per_kwh Quantity key kwh_per_m2 (pre-computed), surface_m2 from data Pre-compute ✅ kwh_per_m2 per subcategory from archibus_rooms by building_name — DB call (tech debt)
Energy Combustion Field Value EmissionTypes Resolved via resolve_emission_types(data_entry_type, data) Strategy A — primary_factor_id Formula TBD per fuel type Pre-compute None
Process Emissions Field Value EmissionTypes process_emissions__ch4, co2, n2o, refrigerants — resolved via resolve_emission_types Strategy A — primary_factor_id Formula TBD Pre-compute None
Equipment — scientific / it / other Field Value EmissionTypes equipment__scientific, equipment__it, equipment__other (1-1 mapping) Strategy A — primary_factor_id Factor Embeds ef_kg_co2eq_per_kwh directly (no separate electricity factor needed) Formula annual_kwh × ef_kg_co2eq_per_kwh Quantity key annual_kwh Pre-compute ✅ annual_kwh = ((active_power_w + standby_power_w) / 2) * usage_hours / 1000 — pure arithmetic
Purchases Field Value EmissionTypes 1-1 with DataEntryTypeEnum (e.g. purchases__goods_and_services) Strategy A — primary_factor_id Formula TBD Pre-compute None
Research Facilities Field Value EmissionTypes 1-1 with DataEntryTypeEnum Strategy A — primary_factor_id Formula TBD Pre-compute None
External Clouds & AI Field Value EmissionTypes Resolved dynamically via resolve_emission_types Strategy A — primary_factor_id Formula TBD Pre-compute None
Flow Summary prepare_create(data_entry)
│
├── resolve_emission_types(data_entry_type, data) → list[EmissionType]
├── handler.pre_compute(data_entry, session) → ctx dict (may hit DB)
│ merged with data_entry.data
│
└── for each emission_type:
│
├── handler.resolve_computations(data_entry, emission_type, ctx)
│ → list[EmissionComputation] (1 per factor)
│
└── for each computation:
├── fetch factor (Strategy A: get(id) | Strategy B: get_by_classification(**query))
├── apply formula (ctx[quantity_key] × factor.values[formula_key])
└── → DataEntryEmission row
Implementation TODO Phase 1 — Core data structures Phase 2 — Base handler contract Phase 3 — FactorService extension Phase 4 — Handlers (one file each) HeadcountHandler — Strategy B, kg_co2eq_per_fte, registers food/waste/commuting/grey_energy ProfessionalTravelHandler — train (Strategy B, country_code) + plane (Strategy B, pre-computed distance_km) BuildingHandler — Strategy B, pre-computes kwh_per_m2 from archibus_rooms EquipmentHandler — Strategy A, pre-computes annual_kwh (pure arithmetic) PurchasesHandler — Strategy A, primary_factor_id ProcessEmissionsHandler — Strategy A, primary_factor_id ExternalHandler — Strategy A, primary_factor_id ResearchFacilitiesHandler — Strategy A, primary_factor_id CombustionHandler — Strategy A, primary_factor_id Phase 5 — EmissionService rewrite Phase 6 — resolve_emission_types Phase 7 — Tests