Skip to content

update capacity input database to AEO-NEMS 2026 - #13

Open
atpham88 wants to merge 52 commits into
mainfrom
ap_update_NEMS
Open

update capacity input database to AEO-NEMS 2026#13
atpham88 wants to merge 52 commits into
mainfrom
ap_update_NEMS

Conversation

@atpham88

@atpham88 atpham88 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR updates ReEDS capacity input database with AEO-NEMS 2026 and EIA860M version June 2026, and also overhauls the NEMS processing script to make it more clear and easier to use as well as remove redundant steps.

Technical details

Implementation notes

Changes made to to this PR include:

  • Remove a_inheritance.py: This script is no longer necessary. We now directly merge AEO-NEMS to EIA860M using the final output file structure established in Clean up input plant database ReEDS#164.
  • b_aeo_cleaning.py --> a_data_cleaning.py: Improve the process of cleaning AEO and EIA860M data to resolve issues saw in prior version of capacity database used in ReEDS (such as applying repower year as new start year).
    • Units with statuses (OA) Out of service but expected to return to service in next calendar year , (OS) Out of service and NOT expected to return to service in next calendar year and (SB) Standby/Backup: available for service but not normally used are considered operating and available for dispatch.
  • c_geospatial_mapping.py --> b_geospatial_mapping.py.
  • d_hydro_classification.py --> c_hydro_classification.py: Remove any mention of state and resource regions (we only use FIPS from now on to capacity input database).
  • e_additional_inputs.py --> d_additional_inputs.py:
    • Remove several manual retirement data adjustment for nuclear power plants (For example Palisades nuclear restarting in 2026 is now captured in new version of EIA860M and no longer need manual adjustment here).
    • Plant max ages are now read directly from main ReEDS repo from inputs/plant_characteristics/maxage.csv and only include one value for each plant type regardless of its size. lfill-gas, pumped-hydro, csp-ns, pvb_pv and pvb_battery are missing from maxage.csv in the ReEDs repo and are assigned values of 100, 100, 30, 30, 30 years respectively.
    • Operating plants with current year > start year + max age have their retire years adjusted by increments of 10 years until the new retire years are at least 10 years extended from current year.
    • Plants with repower year data have their start years and retire years updated to reflect repowering statuses.

Additional changes

  • Rename all subfolders to lower case for consistency.
  • Add intermediate_outputs folder within the outputs folder to store outputs from each step of the process.

Issues resolved

  • Several units in current capacity input database have wrong start years (and thus wrong retirement years) due to not having their start years updated to their repowered years. This issue is fixed in this PR.
  • Units with out of service and standby statuses previously are not considered operating and not available for dispatch but now they are. This results in some extra 16GW of capacity available for dispatch compared to last version.
  • One solar unit was matched to the wrong FIPS and thus was located outside of CONUS borders. This is fixed here.
  • A few coal units that were incorrectly labeled ogs especially in historical years are fixed here.

Relevant sources or documentation

Output verification:

  • Plot national- and FIPS-level changes in online and retire capacity by tech for each year between 2010 and 2050.
    • Planned online:
planned_oneline_current_conus planned_oneline_new_conus planned_oneline_diff_conus In previous NEMS-EIA versions, a few units techs were labeled incorrectly, resulting in differences in historical years here. The majority of such instances are `coaloldscr` with online year after 1995 NOT being labeled as `coal-new`. But there are also a few coal units being mislabeled as `ogs`. For example: Unit 56068 is a `coal-new` unit but was labeled as `ogs` in previous version. Such examples have been fixed in this version.
  • Planned retire:
planned_retire_current_conus planned_retire_new_conus planned_retire_diff_conus
  • Existing capacity map:
    • Current fleet
existing-capacity_main - Updated fleet existing-capacity
  • Documentation: did you update the README for a given a folder?
    Yes
  • ReEDS Documentation: do any parts of the ReEDS documentation need to be updated because of the changes in this pull request?
    No. Updated README for this input processing only.

Comment thread nems_database_processing/d2_fix_upgrades.py Outdated
Comment thread nems_database_processing/inputs/aeo_nems/AEO2023PF_final.csv Outdated
Comment thread nems_database_processing/inputs/aeo_nems/PLTF860_RDB.xlsx
Comment thread nems_database_processing/inputs/eia860M/october_generator2025.xlsx Outdated
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Comment thread nems_database_processing/run.sh Outdated
Comment thread nems_database_processing/a_data_cleaning.py Outdated
Comment thread nems_database_processing/d2_fix_upgrades.py Outdated
Comment thread nems_database_processing/d2_fix_upgrades.py Outdated

@bsergi bsergi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts, mostly on processing structure that aren't necessarily directly related to the update itself.

For the plots, could you structure so that the old and new capacity plots are side-by-side and use the same y-axis? I think that would facilitate comparsion. You already include the different plot which covers similar information here, but just a thought for going forward.

* `d_additional_inputs.py`, which includes `d1_set_retire_years.py`, `d2_fix_upgrades.py`, and `d3_merge_psh_dbs.py`: This script handles updated retirement years that are outdated or missing in AEO-NEMS and EIA860M, fix upgrades, and handles other additional adjustments. Any manually adjusted retirement years should be included in `d1_set_retire_years.py`.
* `e_comparison_plotting.py`: This script generates comparison figures between previous version of NEMS and the newly updated version for validation.

# Input files and params to run run.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add some details to this section on where you get the input files? If we download them from somewhere it would be good to include links.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added input links here now. The input links are also provided in the scripts when the inputs first appear.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add a bit more to the overview about the general process? My rough understanding is that we start from AEO file, clean it, and then supplement with the 860M.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a bit more details in overview and the steps

Comment thread nems_database_processing/run.sh Outdated
coal_plant_retirement='EIA860_2025ER_CoalRetirements.csv'

# Average lithium battery duration:
# Assumed average duration for planned storage units:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this based on on? would be good to document where this assumption comes from.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now removed and storage_duration is calculated based on battery units that came online within 5 years of current year.

Comment thread nems_database_processing/a_data_cleaning.py Outdated
# Main directory
dir = os.getcwd()

# Key parameters:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now if the script is launched using run.sh it will ignore the command line arguments and use the testing ones below. Before merging you should uncomment these and recomment the ones below.

Alternatively, you could set up the main function to take arguments and then either call directly using testing argument or pass command-line arguments to it from a if __name__ == '__main__': (this is the approach we've been using for a number of other python modules).

# Specify scrubber or unscrubber coal units
aeo_data.loc[aeo_data['reeds_tech'].notna(), 'tech'] = aeo_data['reeds_tech']
aeo_data.loc[(aeo_data['tech'].str.contains("coal", na=False)) &
(aeo_data['T_SYR']<=1969),'tech'] = 'coalolduns'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be overkill but could you put this in variable with a descriptive name? I think that's usually more informative than a number with no context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added two variables coaluns and coalcrs with more descriptions for both now.

# Then, collapse on T_PID and T_UID
aeo_data_mult_g = aeo_data_mult_g.groupby(['T_PID','T_UID','TVIN'],
as_index=False).agg(
{'ctt':'first','wst':'first','THRATE':'mean','TC_SUM':'sum',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might suggest storing this as a global dictionary or input file and then reading in here. if you do keep in the code it might be cleaner to read with one assignment per line even though it's more lines. alternatively, there might be a way to use 'first' as the default and then only specify the ones that are different.

{
'ctt':'first',
'wst':'first',
'THRATE':'mean',
'TC_SUM':'sum',
'TC_NP':'sum',
'TC_WIN':'sum',
'battery_duration':'mean',
'T_SYR':'min',
'T_RYR':'first',
'tech':'first',
'EFDcd':'first',
'ECPcd':'first',
'T_PNM':'first',
'T_PCA':'first',
'TRFURB':'first',
'T_VOM':'mean',
'T_FOM':'mean',
'T_SMO':'first',
'T_RMO':'first',
'T_CCSROV':'first',
'T_CCSF':'first',
'T_CCSV':'first',
'T_CCSHR':'first',
'T_CAPAD':'first',
'T_CCSCAPA':'first',
'T_CCSLOC':'first',
'sector':'first',
'TCOUNT':'sum',
'T_LONG':'first',
'T_LAT':'first',
'status':'first',
'nems':'first'
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cleaned this up a little bit. first is now used as default and columns that are 'sum' and 'mean' are specified.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReEDS has switched to using EPSG:5070 instead of ESRI:102008 for the crs, so you might consider updating that here. It shouldn't change the actual lat/lon values, but it might affect how things get placed into counties.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching. I updated everything to 5070 now. Previously I had some weird issue where I got point(inf,inf) with 5070 so had to use the ESRI one (but I stilled used the 5070 for nearest distance calculation). But that did not seem to be an issue with projections and can be fixed by preloading projection data by conda install -c conda-forge proj-data, which is a note I added to README.

@wesleyjcole wesleyjcole left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the updates. I think there are a few minor-ish things to address, and then I think it's ready to merge in.

Comment thread nems_database_processing/d_additional_inputs.py
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Comment on lines +185 to +192
df_temp['T_SYR'] = 2028
df_temp['T_RYR'] = 2108

nems_cleaned.loc[(nems_cleaned['T_RYR'] > 2021) &
(nems_cleaned['T_UID'].str.contains('5')) &
(nems_cleaned['T_PNM'].str.contains('Edgewater')) &
(nems_cleaned['EFDcd']=='CSC'),
'T_RYR'] = 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The year above is 2028, but this is 2025. Is that mismatch intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. the 2025 row is the original unit which is set to retire yearly, the new row (df_temp with T_RYR = 2108) is then appended so we can keep both rows. I added more description to make it more clear.

Comment thread nems_database_processing/a_data_cleaning.py Outdated
Comment thread nems_database_processing/a_data_cleaning.py Outdated
Comment thread nems_database_processing/b_geospatial_mapping.py Outdated
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Comment thread nems_database_processing/d1_set_retire_years.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants