Skip to content

Commit 17c504e

Browse files
TheNightFox-1claude
andcommitted
Fix Stage 5 GWP calculation and update LCA pipeline docs
- Rewrote calculate_gwp() to use process-based LCA via openLCA IPC: get_providers() → create_product_system() → calculate() with EF 3.0 Method (adapted), instead of a direct characterisation-factor lookup which cannot work for product flows - Added EF30_METHOD_ID and CLIMATE_CHANGE_CAT_ID constants - Updated CLAUDE.md with LCA pipeline section and run instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0428bb0 commit 17c504e

File tree

2 files changed

+80
-45
lines changed

2 files changed

+80
-45
lines changed

CLAUDE.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ This is a **knowledge repository and MBSE model workspace**, not a traditional s
2929

3030
- **`docs/`** — Documentation source (MkDocs). Editing these files updates the published site at [thenightfox-1.github.io/SustainableTogether](https://thenightfox-1.github.io/SustainableTogether/).
3131
- **`System Model/SolarX/`** — MBSE/SysML model of the SolarX PV system (current/AS-IS state). The system architecture is: `PVArray → SolarInverter → BatteryStorage` and `SolarInverter → GridConnection`, all orchestrated by `SystemController`.
32-
- **`System Model/SolarX/LCA Analysis SolarX/`**Planned location for Life Cycle Assessment (LCA) integration with the SolarX model (currently empty, in active development).
32+
- **`System Model/SolarX/LCA Analysis SolarX/`**LCA integration work. Contains a PoC SysML v2 ↔ LCA pipeline in `SimpleLCAIntegration/`. See the `CLAUDE.md` files in each subfolder for detailed guidance.
3333
- **`Our Presentations/`** and **`SustainabilityWebinarSeries/`** — Static assets (PDFs, slides); not built or processed.
3434

3535
## Project Context
@@ -38,6 +38,26 @@ The project models a transformation from **SolarX** (conventional PV company, cu
3838

3939
The near-term roadmap prioritises: completing the SolarX RFLP (Requirements, Functional, Logical, Physical) model layers; integrating LCA to automate environmental impact assessment; and beginning the SustainaSun model.
4040

41+
## LCA Integration Pipeline
42+
43+
The `SimpleLCAIntegration/` PoC demonstrates a four-layer pipeline:
44+
45+
```
46+
motor.sysml → motor_instance.ttl → motor_lca_ontology.ttl → semantic_matching.sparql
47+
```
48+
49+
Run the end-to-end pipeline (requires openLCA 2.x running locally with IPC server on port 8080):
50+
51+
```bash
52+
cd "System Model/SolarX/LCA Analysis SolarX/SimpleLCAIntegration"
53+
pip install rdflib olca-ipc
54+
python stage4_integration.py
55+
```
56+
57+
The script connects to openLCA via IPC, fetches ELCD flows, performs a SPARQL semantic match against the SysML material names, and prints the matched flow's GWP characterisation factor × mass.
58+
59+
To extend to the full SolarX system, create one `<Component>_instance.ttl` file per component (reusing the same ontology and SPARQL query). Component names: `PVArray`, `SolarInverter`, `BatteryStorage`, `SystemController`, `GridConnection`.
60+
4161
## Contribution Workflow
4262

4363
Commit message convention: `Add: ...`, `Fix: ...`, `Update: ...`

System Model/SolarX/LCA Analysis SolarX/SimpleLCAIntegration/stage4_integration.py

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646

4747
OLCA_PORT = 8080
4848

49+
# ── LCIA constants (EF 3.0 Method (adapted), confirmed from active database) ──
50+
EF30_METHOD_ID = "b4571628-4b7b-3e4f-81b1-9a8cca6cb3f8"
51+
CLIMATE_CHANGE_CAT_ID = "3bc1c67f-d3e3-3891-9fea-4512107d88ef"
52+
4953

5054
# ─────────────────────────────────────────────────────────────────────────────
5155
# STAGE 1 — Load RDF files
@@ -144,44 +148,57 @@ def run_sparql_match(g: Graph, sparql_query: str) -> list:
144148
def calculate_gwp(client: ipc.Client, flow_uuid: str, mass_kg: float,
145149
part_name: str, flow_name: str) -> None:
146150
"""
147-
Walk through impact methods on the active database, find the first
148-
climate-change / GWP category that has a characterization factor for
149-
flow_uuid, then compute total GWP = factor * mass_kg.
150-
151-
openLCA stores factors on ImpactCategory objects (not on ImpactMethod
152-
directly), so we fetch each category individually once we find a
153-
candidate method.
151+
Calculate GWP using EF 3.0 Method (adapted):
152+
1. Find the provider process for the matched ELCD product flow.
153+
2. Auto-build a product system from that process via IPC.
154+
3. Run a full LCA calculation (EF 3.0).
155+
4. Extract the 'Climate change' total impact and scale by mass_kg.
156+
157+
Note: EF 3.0 characterises elementary flows, not product flows directly,
158+
so a characterisation-factor lookup on the product flow UUID would never
159+
yield a result — a full LCA calculation is required.
154160
"""
155-
print("STAGE 5 — Calculating GWP via openLCA IPC...")
156-
157-
gwp_factor : float | None = None
158-
gwp_unit : str = "kg CO₂-eq"
159-
method_name : str = ""
160-
cat_name : str = ""
161-
162-
for method in client.get_all(schema.ImpactMethod):
163-
for cat_ref in (method.impact_categories or []):
164-
label = (cat_ref.name or "").lower()
165-
if not ("climate" in label or "gwp" in label or "global warming" in label):
166-
continue
167-
168-
full_cat = client.get(schema.ImpactCategory, cat_ref.id)
169-
if full_cat is None:
170-
continue
171-
172-
for factor in (full_cat.impact_factors or []):
173-
if factor.flow and factor.flow.id == flow_uuid:
174-
gwp_factor = factor.value
175-
gwp_unit = factor.unit.name if factor.unit else gwp_unit
176-
method_name = method.name or ""
177-
cat_name = cat_ref.name or ""
178-
break
179-
180-
if gwp_factor is not None:
181-
break
182-
if gwp_factor is not None:
161+
print("STAGE 5 — Calculating GWP via openLCA IPC (EF 3.0 Method)...")
162+
163+
# -- Find the provider process for the matched ELCD flow ------------------
164+
flow_ref = schema.Ref(id=flow_uuid, ref_type=schema.RefType.Flow)
165+
providers = client.get_providers(flow_ref)
166+
if not providers:
167+
print(f" ERROR: no provider process found for flow UUID {flow_uuid}.")
168+
return
169+
process_ref = providers[0].provider
170+
print(f" Provider process : {process_ref.name}")
171+
172+
# -- Create a product system (auto-link upstream processes) ---------------
173+
print(" Creating product system (this may take a moment)...")
174+
ps_ref = client.create_product_system(process_ref)
175+
if ps_ref is None:
176+
print(" ERROR: create_product_system returned None.")
177+
return
178+
print(f" Product system : {ps_ref.id}")
179+
180+
# -- Run LCA calculation for 1 kg reference flow --------------------------
181+
print(" Running LCA calculation...")
182+
setup = schema.CalculationSetup(
183+
target=ps_ref,
184+
impact_method=schema.Ref(
185+
id=EF30_METHOD_ID,
186+
ref_type=schema.RefType.ImpactMethod,
187+
),
188+
amount=1.0,
189+
)
190+
result = client.calculate(setup)
191+
result.wait_until_ready()
192+
193+
# -- Extract Climate change total -----------------------------------------
194+
gwp_per_kg: float | None = None
195+
for impact in result.get_total_impacts():
196+
if impact.impact_category.id == CLIMATE_CHANGE_CAT_ID:
197+
gwp_per_kg = impact.amount
183198
break
199+
result.dispose()
184200

201+
# -- Report ---------------------------------------------------------------
185202
print()
186203
print("=" * 62)
187204
print(" RESULT")
@@ -190,19 +207,17 @@ def calculate_gwp(client: ipc.Client, flow_uuid: str, mass_kg: float,
190207
print(f" Matched ELCD flow : {flow_name}")
191208
print(f" Flow UUID : {flow_uuid}")
192209
print(f" Mass : {mass_kg} kg")
210+
print(f" LCIA method : EF 3.0 Method (adapted)")
211+
print(f" Impact category : Climate change")
193212

194-
if gwp_factor is None:
213+
if gwp_per_kg is None:
195214
print()
196215
print(" GWP : not calculated")
197-
print(" Reason: no characterization factor found for this flow.")
198-
print(" Hint: ensure an impact method with a GWP / climate-change")
199-
print(" category is imported into the active openLCA database.")
216+
print(" Reason: Climate change category missing from result.")
200217
else:
201-
gwp_total = gwp_factor * mass_kg
202-
print(f" Impact method : {method_name}")
203-
print(f" Impact category : {cat_name}")
204-
print(f" GWP factor : {gwp_factor:.6f} {gwp_unit} / kg")
205-
print(f" Total GWP : {gwp_total:.4f} {gwp_unit}")
218+
gwp_total = gwp_per_kg * mass_kg
219+
print(f" GWP per kg : {gwp_per_kg:.6f} kg CO2-eq / kg")
220+
print(f" Total GWP : {gwp_total:.4f} kg CO2-eq")
206221

207222
print("=" * 62)
208223

0 commit comments

Comments
 (0)