USEPA / ElectricityLCI

Creative Commons Zero v1.0 Universal
24 stars 10 forks source link

What impact assessment method? #220

Open dt-woods opened 6 months ago

dt-woods commented 6 months ago

The 2016 electricity baseline includes two impact assessment methods:

Questions

  1. Are we on board with these two impact methods?
  2. Do you happen to have any code for creating the ImpactMethod, ImpactCategory, and ImpactFactor elements in openLCA? For example, I downloaded Federal Elementary Flow List from FedCommons to get the FlowProperties and UnitGroups because I couldn't find full references of them from GreenDelta. I see that Impact Categories have UUIDs and I don't know if these should be standardized across methods or I can just create them with olca_schema's helper methods (e.g., new_impact_method, new_impact_category, and new_impact_factor).
def get_lcia_traci(netl_mod=False):
    """Returns a dataframe of TRACI 2.1 characterization factors.

    This method uses the USEPA's Federal LCA Commons Elementary Flow List
    Python package (`fedelemflowlist`) to read the data file 'TRACI2.1.csv'.

    These flows are mapped using the USEPA's LCIAFormatter (`lciafmt`).
    Columns with empty strings ('') are dropped from the data frame.
    To find the method and system names, use the following:

        >>> for sm in lciafmt.supported_methods():
        ...   if sm['id'] == 'TRACI':
        ...     print("The 'get_method' is '%s'" % sm['name'])
        ...     print("The 'system' is '%s'" % sm['mapping'])
        The 'get_method' is 'TRACI 2.1'
        The 'system' is 'TRACI2.1'

    Args:
        netl_mod (bool, optional):
            Whether to retrieve NETL-modified TRACI characterization factors.
            Default value is False.

    Returns:
        pandas.DataFrame: TRACI 2.1 characterization factors for emission
        flows paired with TRACI indicators.

        The dataset includes the columns described in the table that follows.

        +--------------+-----------------------------------------------------+
        | Column       | Description                                         |
        +==============+=====================================================+
        | Method       | 'TRACI 2.1' or 'TRACI 2.1 (NETL mod)'               |
        +--------------+-----------------------------------------------------+
        | Indicator    | The nine impact categories                          |
        |              | (e.g., 'Ozone depletion')                           |
        +--------------+-----------------------------------------------------+
        | Indicator    | Units associated with impact categories             |
        | unit         | (e.g., kg N eq)                                     |
        +--------------+-----------------------------------------------------+
        | Flowable     | Element/compound names (e.g., Phosphorus)           |
        +--------------+-----------------------------------------------------+
        | Flow UUID    | 36-character long universally unique flow IDs       |
        +--------------+-----------------------------------------------------+
        | Context      | Emission paths (e.g., 'emission/air')               |
        +--------------+-----------------------------------------------------+
        | Unit         | Emission substance unit (i.e., 'kg' for all)        |
        +--------------+-----------------------------------------------------+
        | CAS No       | The Chemical Abstracts Service number               |
        |              | (e.g., xx-xx-x)                                     |
        +--------------+-----------------------------------------------------+
        | Characteri\  | Numeric, indicator units per emission unit          |
        | zationFactor |                                                     |
        +--------------+-----------------------------------------------------+

    """
    # Import necessary modules
    from zipfile import BadZipFile
    import lciafmt
    from lciafmt.util import get_modification
    # Use LCIAformatter to map flows
    method = lciafmt.Method.TRACI
    mapper = method.get_metadata()['mapping']  # 'TRACI2.1'
    # HOTFIX: bypass BadZipFile by clearing cache [2023-12-20; TWD]
    try:
        traci = lciafmt.get_method(method)
    except BadZipFile:
        lciafmt.clear_cache()
        traci = lciafmt.get_method(method)

    # Update w/ modified values, if requested; updates Method appropriately
    if netl_mod:
        mod = "NETL"
        mod_cfs = get_modification(mod, mapper) # does not include FlowUUID
        traci = traci.merge(
            mod_cfs, how="left", on=["Flowable", 'Context', 'Indicator'])
        traci.loc[traci['Updated CF'].notnull(),
                 'Characterization Factor'] = traci['Updated CF']
        traci.drop(columns=['Updated CF', 'Note'], inplace=True)
        traci['Method'] += " (" + mod + " mod)"

    traci = lciafmt.map_flows(traci, system=mapper)

    # Drop empty columns
    empty_cols = []
    for tcol in traci.columns:
        unique_vals = traci[tcol].unique()
        num_unique = len(unique_vals)
        if num_unique == 1:
            uval = unique_vals[0]
            if uval == '':
                empty_cols.append(tcol)
    traci.drop(empty_cols, axis=1, inplace=True)
    return traci

Rough draft of implementing the above data into electricityLCI for openLCA:

import olca_schema as o
import olca_schema.units as o_units

df = get_lcia_traci(True)
m_obj = o.new_impact_method("TRACI 2.1 (NETL modification)")
i_list = list(df['Indicator'].unique())
cat_list = []
for i_name in i_list:
    factor_list = []
    i_cat = o.new_impact_category(i_name)
    tmp_df = df.query("Indicator == '%s'" % i_name)
    for row in tmp_df.iterrows():
        f_id = row['Flow UUID']
        f_val = row['Characterization Factor']
        f_unit = row['Unit']
        u_obj = o_units.unit_ref(f_unit)
        # TODO: pull Flow object from JSON-LD as f_obj
        f_obj = get_flow_from_jsonld(f_id)
        i_factor = o.new_impact_factor(i_cat, f_obj, f_val, u_obj)
        factor_list.append(i_factor)
    i_cat.impact_factors = factor_list
    cat_list.append(i_cat)
    m_obj.impact_categories.append(i_cat.to_ref())
# TODO: Write out ImpactMethods and ImpactCategories to JSON-LD
save_to_jsonld(jsonld_file, m_obj)
save_to_jsonld(jsonld_file, cat_list)
bl-young commented 6 months ago

I would think the elci should be agnostic to LCIA method? My guess is that for the one on the LCA Commons currently, the impact assessment methods were added after the eLCI data were in openLCA. I think in most cases moving forward we would not want the repositories on the commons to contain impact methods.

Relatedly, the old "NETL_mod" was to enable the use of AR5-100 yr instead of TRACI's AR4-100 year. We now have explicit IPCC methods available through the LCIA formatter (AR4 through AR6)

bl-young commented 6 months ago

If for some reason you did want to add an impact method to the jsonld file in elci, I woudl think you could simplify it greatly through something like:

import lciafmt
df = lciafmt.get_mapped_method(method_id='TRACI2.1',
                               download_from_remote=True)
lciafmt.to_jsonld(df, zip_file = 'my.zip')

using get_mapped_method() with download_from_remote=True will use already processed versions from data commons that have been validated

dt-woods commented 6 months ago

Seems reasonable, Ben. Unless I hear otherwise, I'll refrain from including the impact methods from the inventory. One less thing to have to worry about.

m-jamieson commented 6 months ago

Agreed on not including an LCIA method in the elci json-ld output. It also appears to be inconsistent with respect to the commons - USLCI for example does not include any LCIA methods.