|
|
from dataclasses import dataclass
|
|
|
from enum import Enum
|
|
|
|
|
|
from ecologits.impacts.modeling import Impacts, Energy, GWP, ADPe, PE, WCF, Usage, Embodied
|
|
|
|
|
|
from pint import UnitRegistry, Quantity
|
|
|
import streamlit as st
|
|
|
import plotly.graph_objects as go
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
u = UnitRegistry()
|
|
|
u.define("Wh = watt_hour")
|
|
|
u.define("mWh = milliwatt_hour")
|
|
|
u.define("kWh = kilowatt_hour")
|
|
|
u.define("MWh = megawatt_hour")
|
|
|
u.define("GWh = gigawatt_hour")
|
|
|
u.define("TWh = terawatt_hour")
|
|
|
u.define("gCO2eq = gram")
|
|
|
u.define("mgCO2eq = milligram")
|
|
|
u.define("kgCO2eq = kilogram")
|
|
|
u.define("tCO2eq = metricton")
|
|
|
u.define("kgSbeq = kilogram")
|
|
|
u.define("gSbeq = gram")
|
|
|
u.define("mgSbeq = milligram")
|
|
|
u.define("µgSbeq = microgram")
|
|
|
u.define("kJ = kilojoule")
|
|
|
u.define("MJ = megajoule")
|
|
|
u.define("L = liter")
|
|
|
u.define("mL = milliliter")
|
|
|
u.define("m = meter")
|
|
|
u.define("km = kilometer")
|
|
|
u.define("s = second")
|
|
|
u.define("min = minute")
|
|
|
u.define("h = hour")
|
|
|
q = u.Quantity
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
class QImpacts:
|
|
|
energy: Quantity
|
|
|
gwp: Quantity
|
|
|
adpe: Quantity
|
|
|
pe: Quantity
|
|
|
wcf: Quantity
|
|
|
ranges: bool = False
|
|
|
energy_min: Quantity | None = None
|
|
|
energy_max: Quantity | None = None
|
|
|
gwp_min: Quantity | None = None
|
|
|
gwp_max: Quantity | None = None
|
|
|
adpe_min: Quantity | None = None
|
|
|
adpe_max: Quantity | None = None
|
|
|
pe_min: Quantity | None = None
|
|
|
pe_max: Quantity | None = None
|
|
|
wcf_min: Quantity | None = None
|
|
|
wcf_max: Quantity | None = None
|
|
|
|
|
|
|
|
|
|
|
|
class PhysicalActivity(str, Enum):
|
|
|
RUNNING = "running"
|
|
|
WALKING = "walking"
|
|
|
|
|
|
|
|
|
class EnergyProduction(str, Enum):
|
|
|
NUCLEAR = "nuclear"
|
|
|
WIND = "wind"
|
|
|
|
|
|
|
|
|
COUNTRIES = [
|
|
|
("cook_islands", 38.81, 9_556),
|
|
|
("tonga", 51.15, 104_490),
|
|
|
("comoros", 100, 821_632),
|
|
|
("samoa", 100, 821_632),
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RUNNING_ENERGY_EQ = q("294 kJ / km")
|
|
|
WALKING_ENERGY_EQ = q("196 kJ / km")
|
|
|
|
|
|
|
|
|
|
|
|
EV_ENERGY_EQ = q("0.17 kWh / km")
|
|
|
|
|
|
|
|
|
STREAMING_GWP_EQ = q("15.6 h / kgCO2eq")
|
|
|
|
|
|
|
|
|
ONE_PERCENT_WORLD_POPULATION = 80_000_000
|
|
|
|
|
|
DAYS_IN_YEAR = 365
|
|
|
|
|
|
|
|
|
|
|
|
YEARLY_NUCLEAR_ENERGY_EQ = q("6 TWh")
|
|
|
|
|
|
|
|
|
|
|
|
YEARLY_WIND_ENERGY_EQ = q("4.2 GWh")
|
|
|
|
|
|
|
|
|
|
|
|
YEARLY_IRELAND_ELECTRICITY_CONSUMPTION = q("33 TWh")
|
|
|
IRELAND_POPULATION_MILLION = 5
|
|
|
|
|
|
|
|
|
|
|
|
AIRPLANE_PARIS_NYC_GWP_EQ = q("177000 kgCO2eq")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_energy(energy_value: float, energy_unit = Energy(value=0.).unit) -> Quantity:
|
|
|
val = q(energy_value, energy_unit)
|
|
|
if val < q("1 kWh"):
|
|
|
val = val.to("Wh")
|
|
|
if val < q("1 Wh"):
|
|
|
val = val.to("mWh")
|
|
|
return val
|
|
|
|
|
|
|
|
|
def format_gwp(gwp_value: float, gwp_unit = GWP(value=0.).unit) -> Quantity:
|
|
|
val = q(gwp_value, gwp_unit)
|
|
|
if val < q("1 kgCO2eq"):
|
|
|
val = val.to("gCO2eq")
|
|
|
if val < q("1 gCO2eq"):
|
|
|
val = val.to("mgCO2eq")
|
|
|
return val
|
|
|
|
|
|
|
|
|
def format_adpe(adpe_value: float, adpe_unit = ADPe(value=0.).unit) -> Quantity:
|
|
|
val = q(adpe_value, adpe_unit)
|
|
|
if val < q("1 kgSbeq"):
|
|
|
val = val.to("gSbeq")
|
|
|
if val < q("1 gSbeq"):
|
|
|
val = val.to("mgSbeq")
|
|
|
if val < q("1 mgSbeq"):
|
|
|
val = val.to("µgSbeq")
|
|
|
return val
|
|
|
|
|
|
|
|
|
def format_pe(pe_value: float, pe_unit = PE(value=0.).unit) -> Quantity:
|
|
|
val = q(pe_value, pe_unit)
|
|
|
if val < q("1 MJ"):
|
|
|
val = val.to("kJ")
|
|
|
return val
|
|
|
|
|
|
|
|
|
def format_wcf(wcf_value: float, wcf_unit = WCF(value=0.).unit) -> Quantity:
|
|
|
val = q(wcf_value, wcf_unit)
|
|
|
if val < q("1 L"):
|
|
|
val = val.to("mL")
|
|
|
return val
|
|
|
|
|
|
|
|
|
def format_impacts(impacts: Impacts) -> tuple[QImpacts, Usage, Embodied]:
|
|
|
if isinstance(impacts.energy.value, float):
|
|
|
return QImpacts(
|
|
|
energy=format_energy(impacts.energy.value),
|
|
|
gwp=format_gwp(impacts.gwp.value),
|
|
|
adpe=format_adpe(impacts.adpe.value),
|
|
|
pe=format_pe(impacts.pe.value),
|
|
|
wcf=format_wcf(impacts.wcf.value)
|
|
|
), impacts.usage, impacts.embodied
|
|
|
|
|
|
else:
|
|
|
energy = format_energy(impacts.energy.value.mean)
|
|
|
gwp = format_gwp(impacts.gwp.value.mean)
|
|
|
adpe = format_adpe(impacts.adpe.value.mean)
|
|
|
pe = format_pe(impacts.pe.value.mean)
|
|
|
wcf = format_wcf(impacts.wcf.value.mean)
|
|
|
|
|
|
return QImpacts(
|
|
|
energy=energy,
|
|
|
energy_min=format_energy(impacts.energy.value.min).to(energy.units),
|
|
|
energy_max=format_energy(impacts.energy.value.max).to(energy.units),
|
|
|
gwp=gwp,
|
|
|
gwp_min=format_gwp(impacts.gwp.value.min).to(gwp.units),
|
|
|
gwp_max=format_gwp(impacts.gwp.value.max).to(gwp.units),
|
|
|
adpe=adpe,
|
|
|
adpe_min=format_adpe(impacts.adpe.value.min).to(adpe.units),
|
|
|
adpe_max=format_adpe(impacts.adpe.value.max).to(adpe.units),
|
|
|
pe=pe,
|
|
|
pe_min=format_pe(impacts.pe.value.min).to(pe.units),
|
|
|
pe_max=format_pe(impacts.pe.value.max).to(pe.units),
|
|
|
wcf=wcf,
|
|
|
wcf_min=format_wcf(impacts.wcf.value.min).to(wcf.units),
|
|
|
wcf_max=format_wcf(impacts.wcf.value.max).to(wcf.units),
|
|
|
ranges=True
|
|
|
), impacts.usage, impacts.embodied
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_energy_eq_physical_activity(
|
|
|
energy: Quantity,
|
|
|
) -> tuple[PhysicalActivity, Quantity]:
|
|
|
energy = energy.to("kJ")
|
|
|
running_eq = energy / RUNNING_ENERGY_EQ
|
|
|
if running_eq > q("1 km"):
|
|
|
return PhysicalActivity.RUNNING, running_eq
|
|
|
|
|
|
walking_eq = energy / WALKING_ENERGY_EQ
|
|
|
if walking_eq < q("1 km"):
|
|
|
walking_eq = walking_eq.to("meter")
|
|
|
return PhysicalActivity.WALKING, walking_eq
|
|
|
|
|
|
|
|
|
def format_energy_eq_electric_vehicle(energy: Quantity) -> Quantity:
|
|
|
energy = energy.to("kWh")
|
|
|
ev_eq = energy / EV_ENERGY_EQ
|
|
|
if ev_eq < q("1 km"):
|
|
|
ev_eq = ev_eq.to("meter")
|
|
|
return ev_eq
|
|
|
|
|
|
|
|
|
def format_gwp_eq_streaming(gwp: Quantity) -> Quantity:
|
|
|
gwp = gwp.to("kgCO2eq")
|
|
|
streaming_eq = gwp * STREAMING_GWP_EQ
|
|
|
if streaming_eq < q("1 h"):
|
|
|
streaming_eq = streaming_eq.to("min")
|
|
|
if streaming_eq < q("1 min"):
|
|
|
streaming_eq = streaming_eq.to("s")
|
|
|
return streaming_eq
|
|
|
|
|
|
|
|
|
def format_energy_eq_electricity_production(
|
|
|
energy: Quantity,
|
|
|
) -> tuple[EnergyProduction, Quantity]:
|
|
|
electricity_eq = energy * ONE_PERCENT_WORLD_POPULATION * DAYS_IN_YEAR
|
|
|
electricity_eq = electricity_eq.to("TWh")
|
|
|
if electricity_eq > YEARLY_NUCLEAR_ENERGY_EQ:
|
|
|
return EnergyProduction.NUCLEAR, electricity_eq / YEARLY_NUCLEAR_ENERGY_EQ
|
|
|
electricity_eq = electricity_eq.to("GWh")
|
|
|
return EnergyProduction.WIND, electricity_eq / YEARLY_WIND_ENERGY_EQ
|
|
|
|
|
|
|
|
|
def format_energy_eq_electricity_consumption_ireland(energy: Quantity) -> Quantity:
|
|
|
electricity_eq = energy * ONE_PERCENT_WORLD_POPULATION * DAYS_IN_YEAR
|
|
|
electricity_eq = electricity_eq.to("TWh")
|
|
|
return electricity_eq / YEARLY_IRELAND_ELECTRICITY_CONSUMPTION
|
|
|
|
|
|
|
|
|
def format_gwp_eq_airplane_paris_nyc(gwp: Quantity) -> Quantity:
|
|
|
gwp_eq = gwp * ONE_PERCENT_WORLD_POPULATION * DAYS_IN_YEAR
|
|
|
gwp_eq = gwp_eq.to("kgCO2eq")
|
|
|
return gwp_eq / AIRPLANE_PARIS_NYC_GWP_EQ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def range_plot (mean_val, min_val, max_val, unit):
|
|
|
|
|
|
fig = go.Figure()
|
|
|
|
|
|
|
|
|
fig.add_trace(go.Bar(
|
|
|
x=[max_val],
|
|
|
y=[''],
|
|
|
orientation='h',
|
|
|
marker=dict(color="#0B3B36"),
|
|
|
showlegend=False,
|
|
|
hoverinfo='skip',
|
|
|
))
|
|
|
|
|
|
|
|
|
fig.add_shape(
|
|
|
type="line",
|
|
|
x0=mean_val, y0=-1,
|
|
|
x1=mean_val, y1=1,
|
|
|
line=dict(color='#00BF63', width=3, dash="solid"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
for val, pos, text in zip([max_val, min_val]*2,[0.85,0.85,1.6,1.6], ["Max", "Min", f'{max_val:.3g} {unit}', f'{min_val:.3g} {unit}']):
|
|
|
fig.add_annotation(
|
|
|
x=val,
|
|
|
y=-pos,
|
|
|
text=text,
|
|
|
showarrow=False,
|
|
|
font=dict(color="black", size=16)
|
|
|
)
|
|
|
|
|
|
fig.add_annotation(
|
|
|
x=mean_val,
|
|
|
y=1.65,
|
|
|
text=f'{mean_val:.3g} {unit}',
|
|
|
showarrow=False,
|
|
|
font=dict(color="black", size=35)
|
|
|
)
|
|
|
|
|
|
|
|
|
fig.update_layout(
|
|
|
height=160,
|
|
|
width = 400,
|
|
|
xaxis=dict(range=[min_val, max_val], showgrid=False, showticklabels=False),
|
|
|
yaxis=dict(showticklabels=False),
|
|
|
plot_bgcolor='white',
|
|
|
margin=dict(l=100, r=100, t=0, b=20),
|
|
|
showlegend=False
|
|
|
)
|
|
|
|
|
|
|
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|