From e03d2bd02cd013aa4fd96e0aadc38e8a4c7330bc Mon Sep 17 00:00:00 2001 From: Hugh Runyan Date: Thu, 11 Jun 2026 07:38:42 -0700 Subject: [PATCH 1/2] changes associated with schema formalization --- SWEET_python/city_params.py | 211 +++++++++++++++++++----------------- SWEET_python/constants.py | 12 ++ SWEET_python/landfill.py | 3 +- SWEET_python/model_v2.py | 9 +- requirements.txt | 1 - 5 files changed, 129 insertions(+), 107 deletions(-) create mode 100644 SWEET_python/constants.py diff --git a/SWEET_python/city_params.py b/SWEET_python/city_params.py index 29745fa..73cf061 100644 --- a/SWEET_python/city_params.py +++ b/SWEET_python/city_params.py @@ -28,6 +28,7 @@ from sqlalchemy.exc import OperationalError as SQLAlchemyOperationalError from datetime import datetime import time +from SWEET_python.constants import MODEL_START_YEAR, MODEL_END_YEAR # The way this model is set up is based on the unit of a City, corresponding to the City class. @@ -246,7 +247,7 @@ def __init__(self, city_name: str): } self.latitude = None self.longitude = None - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) def load_from_csv(self, db: pd.DataFrame, scenario: int = 0) -> None: """ @@ -509,7 +510,7 @@ def load_csv_new(self, db: pd.DataFrame, scenario: int = 0, dst: bool = False) - self.country = city_data["Country ISO3"].values[0] # Define the range of years - years = range(1990, 2051) + years = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) waste_fractions = WasteFractions( food=city_data["Waste Components: Food (%)"].values[0] / 100, @@ -746,7 +747,7 @@ def load_andre_params(self, row, backfill=False): """ if backfill: - years = range(2010, 2051) + years = range(2010, MODEL_END_YEAR + 1) current_row = row[row['Year Emissions'] == 2025] data_source = current_row["Data Source (Population)"].iloc[0] country = current_row["Country"].iloc[0] @@ -831,7 +832,7 @@ def load_andre_params(self, row, backfill=False): year_of_data_msw = int(year_of_data_msw) # Define the range of years - years = range(1990, 2051) + years = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Hardcode missing population values population = float(row["population_count"]) @@ -1411,8 +1412,8 @@ def calculate_component_fractions( waste_masses_df = waste_fractions.multiply(waste_mass, axis=0) waste_generated_df = WasteGeneratedDF.create( waste_masses_df, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, year_of_data_pop, growth_rate_historic, growth_rate_future, @@ -1506,7 +1507,7 @@ def finish_sites_prep(self): open_date=lifespans[i][0], close_date=lifespans[i][1], site_type=site_types[i], - mcf=pd.Series(mcfs[i], index=range(lifespans[i][0], 2051)), + mcf=pd.Series(mcfs[i], index=range(lifespans[i][0], MODEL_END_YEAR + 1)), city_params_dict=city_params_dict, city_instance_attrs=baseline.city_instance_attrs, landfill_index=i, @@ -1515,7 +1516,7 @@ def finish_sites_prep(self): scenario=0, new_baseline=True, gas_capture_efficiency=pd.Series( - gas_capture_efficiencies[i], index=range(lifespans[i][0], 2051) + gas_capture_efficiencies[i], index=range(lifespans[i][0], MODEL_END_YEAR + 1) ), # flaring=pd.Series(flaring, index=year_range), # leachate_circulate=leachate_circulate[i], @@ -1526,7 +1527,7 @@ def finish_sites_prep(self): latlon=latitudes_longitudes[i], ks=baseline.ks, oxidation_factor=pd.Series( - oxidation_values[i], index=range(lifespans[i][0], 2051) + oxidation_values[i], index=range(lifespans[i][0], MODEL_END_YEAR + 1) ), rmi_id=rmi_ids[i], ) @@ -1537,8 +1538,8 @@ def finish_sites_prep(self): ) baseline.waste_generated_df = WasteGeneratedDF.create( waste_masses_df, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, baseline.year_of_data_pop, baseline.growth_rate_historic, baseline.growth_rate_future, @@ -1589,7 +1590,7 @@ def sinar_city_and_site(self, row, linker, for_trace=False): # Basic information # idx = row[0] row = row[1] - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Import basic information basics_dict = self.import_basics(row) @@ -1747,7 +1748,7 @@ def sinar_city_and_site(self, row, linker, for_trace=False): ].unique() earliest_mention_in_data = int(site_data["ctf_year"].iat[-1]) if len(opens) == 1 and np.isnan(opens[0]): - open = 1990 + open = MODEL_START_YEAR else: open = int(sorted(opens)[0]) closes = linker.loc[ @@ -1755,7 +1756,7 @@ def sinar_city_and_site(self, row, linker, for_trace=False): ].unique() last_mention_in_data = int(site_data["ctf_year"].iat[0]) if len(closes) == 1 and np.isnan(closes[0]): - close = 2050 + close = MODEL_END_YEAR else: close = int(sorted(closes, reverse=True)[0]) if last_mention_in_data > close: @@ -1830,7 +1831,7 @@ def sinar_city_and_site(self, row, linker, for_trace=False): earliest_mention_in_data = int(site_data["ctf_year"].iat[-1]) if len(opens) == 1 and np.isnan(opens[0]): if earliest_landfill: - open = 1990 + open = MODEL_START_YEAR else: open = earliest_mention_in_data else: @@ -1841,7 +1842,7 @@ def sinar_city_and_site(self, row, linker, for_trace=False): last_mention_in_data = int(site_data["ctf_year"].iat[0]) if len(closes) == 1 and np.isnan(closes[0]): if last_landfill: - close = 2050 + close = MODEL_END_YEAR else: close = last_mention_in_data else: @@ -1924,8 +1925,8 @@ def check_for_landfill_gaps( ).fillna(0) #.infer_objects(copy=False) # Make a fake landfill if no data for old landfills - if earliest_landfill_year > 1990: - open = 1990 + if earliest_landfill_year > MODEL_START_YEAR: + open = MODEL_START_YEAR close = earliest_landfill_year - 1 lifespans["fake_landfill_early"] = (open, close) site_types["fake_landfill_early"] = ( @@ -1970,7 +1971,7 @@ def check_for_landfill_gaps( close_date=lifespans[landfill][1], site_type=site_types[landfill], mcf=pd.Series( - mcfs[landfill], index=range(lifespans[landfill][0], 2051) + mcfs[landfill], index=range(lifespans[landfill][0], MODEL_END_YEAR + 1) ), city_params_dict=city_params_dict, city_instance_attrs=baseline.city_instance_attrs, @@ -1981,7 +1982,7 @@ def check_for_landfill_gaps( new_baseline=True, gas_capture_efficiency=pd.Series( gas_capture_efficiencies[landfill], - index=range(lifespans[landfill][0], 2051), + index=range(lifespans[landfill][0], MODEL_END_YEAR + 1), ), # flaring=pd.Series(flaring, index=year_range), # leachate_circulate=leachate_circulate[i], @@ -2033,7 +2034,7 @@ def site_only_estimate(self, row=None, pop_data=None): None """ # Basic information - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Import basic information basics_dict = self.import_basics_site(row, pop_data) @@ -2187,14 +2188,15 @@ def site_only_estimate(self, row=None, pop_data=None): mcf = 0.8 else: mcf = mcf_options[site_type] - open_date = row['Site Open Year'].fillna(1990).values[0] - if open_date < 1990: - open_date = 1990 - close_date = row['Site Close Year'].fillna(2051).values[0] + open_date = row['Site Open Year'].fillna(MODEL_START_YEAR).values[0] + if open_date < MODEL_START_YEAR: + open_date = MODEL_START_YEAR + close_date = row['Site Close Year'].fillna(MODEL_END_YEAR + 1).values[0] fration_of_waste_vector = pd.Series( 0.0, index=self.years_range ) - fration_of_waste_vector.loc[open_date:close_date] = 1.0 + # Sites accept waste in open years but not the closure year. + fration_of_waste_vector.loc[open_date:close_date-1] = 1.0 new_landfill = Landfill( open_date=open_date, close_date=close_date, @@ -2251,7 +2253,7 @@ def site_only_estimate_trace(self, canonical_row=None, time_series_rows=None, po None """ # Basic information - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Import basic information basics_dict = self.import_basics_site(canonical_row, pop_data, usecase="trace", time_series_rows=time_series_rows) @@ -2462,16 +2464,16 @@ def site_only_estimate_trace(self, canonical_row=None, time_series_rows=None, po if open_date[-2:] == '.0': open_date = int(open_date[:-2]) elif open_date in ['NS', 'NSNSNSNS', 'NSNS', 'NO SABE', 'NO SUPO']: - open_date = 1990 + open_date = MODEL_START_YEAR else: open_date = int(open_date) else: if pd.isna(open_date) or open_date is None: - open_date = 1990 + open_date = MODEL_START_YEAR else: open_date = int(open_date) - if open_date < 1990: - open_date = 1990 + if open_date < MODEL_START_YEAR: + open_date = MODEL_START_YEAR if open_date == 20007: open_date = 2007 close_date = canonical_row['site_close_year'] @@ -2482,7 +2484,7 @@ def site_only_estimate_trace(self, canonical_row=None, time_series_rows=None, po close_date = int(close_date) else: if pd.isna(close_date) or close_date is None: - close_date = 2050 + close_date = MODEL_END_YEAR else: close_date = int(close_date) fraction_of_waste_vector = pd.Series( @@ -2546,7 +2548,7 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit None """ # Basic information - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Import basic information basics_dict = self.import_basics_site(canonical_row, pop_data, usecase="trace", time_series_rows=time_series_rows) @@ -2734,16 +2736,16 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit if open_date[-2:] == '.0': open_date = int(open_date[:-2]) elif open_date in ['NS', 'NSNSNSNS', 'NSNS', 'NO SABE', 'NO SUPO']: - open_date = 1990 + open_date = MODEL_START_YEAR else: open_date = int(open_date) else: if pd.isna(open_date) or open_date is None: - open_date = 1990 + open_date = MODEL_START_YEAR else: open_date = int(open_date) - if open_date < 1990: - open_date = 1990 + if open_date < MODEL_START_YEAR: + open_date = MODEL_START_YEAR if open_date == 20007: open_date = 2007 close_date = canonical_row['site_close_year'] @@ -2754,7 +2756,7 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit close_date = int(close_date) else: if pd.isna(close_date) or close_date is None: - close_date = 2050 + close_date = MODEL_END_YEAR else: close_date = int(close_date) @@ -2764,7 +2766,8 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit fraction_of_waste_vector = pd.Series( 0.0, index=self.years_range ) - fraction_of_waste_vector.loc[open_date:close_date] = 1.0 + # Sites accept waste in open years but not the closure year. + fraction_of_waste_vector.loc[open_date:close_date-1] = 1.0 new_landfill = Landfill( open_date=open_date, close_date=close_date, @@ -2859,6 +2862,12 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit # # Assign the same weights across the problematic rows # W.loc[zero_or_nan_rows, :] = pop_w_aligned fraction_of_waste_df = W.div(W.sum(axis=1), axis=0).fillna(0.0) + # Sites accept waste in open years but not the closure year. These + # per-city fractions are the only deposition gate for the virtual + # landfills below, so zero them outside the operating window. + operating_years = pd.Series(False, index=self.years_range) + operating_years.loc[open_date:close_date-1] = True + fraction_of_waste_df.loc[~operating_years] = 0.0 baseline.fraction_of_waste_df = fraction_of_waste_df for city_id in cities_to_model: @@ -3059,7 +3068,7 @@ def import_basics(self, row) -> None: # Normalize waste fractions to sum to 1 s = sum([x for x in waste_fractions.values()]) waste_fractions = {x: waste_fractions[x] / s for x in waste_fractions.keys()} - self.years_range = range(1990, 2051) + self.years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) waste_fractions = pd.DataFrame(waste_fractions, index=self.years_range) waste_masses = waste_mass * waste_fractions @@ -3120,8 +3129,8 @@ def import_basics(self, row) -> None: # Calculate waste generated, which is like waste masses but adjusts for population growth waste_generated_df = WasteGeneratedDF.create( waste_masses, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, year_of_data_pop, growth_rate_historic, growth_rate_future, @@ -3231,12 +3240,12 @@ def import_basics_site(self, canonical_row, pop_data, usecase='wastemap', time_s growth_rate_future = pop_data.at[self.iso3, 'growth_rate_future'] # Create time series from 1990 to 2050 - years = np.arange(1990, 2051) + years = np.arange(MODEL_START_YEAR, MODEL_END_YEAR + 1) waste_series = np.zeros(len(years)) # Set 2020 as the baseline year baseline_year = 2020 - baseline_idx = baseline_year - 1990 + baseline_idx = baseline_year - MODEL_START_YEAR # Project backward and forwards for i, year in enumerate(years): @@ -3252,7 +3261,7 @@ def import_basics_site(self, canonical_row, pop_data, usecase='wastemap', time_s # Overwrite with actual data where available for _, data_row in time_series_rows.iterrows(): if not pd.isna(data_row['incoming_waste_year']): - year_idx = int(data_row['incoming_waste_year']) - 1990 + year_idx = int(data_row['incoming_waste_year']) - MODEL_START_YEAR if 0 <= year_idx < len(waste_series): val = data_row['annual_incoming_waste'] if not np.isnan(val): @@ -3477,7 +3486,7 @@ def _is_transient_db_error(err: Exception) -> bool: index=self.years_range, columns=wf_norm.index ) - waste_fractions = waste_fractions.loc[1990:2050] + waste_fractions = waste_fractions.loc[MODEL_START_YEAR:MODEL_END_YEAR] waste_masses = waste_fractions.multiply(waste_mass, axis=0) try: @@ -3536,9 +3545,9 @@ def _is_transient_db_error(err: Exception) -> bool: # Calculate waste generated, which is like waste masses but adjusts for population growth waste_generated_df = WasteGeneratedDF.create( - waste_masses.loc[1990:2050, :], - 1990, - 2050, + waste_masses.loc[MODEL_START_YEAR:MODEL_END_YEAR, :], + MODEL_START_YEAR, + MODEL_END_YEAR, year_of_data_pop, growth_rate_historic, growth_rate_future, @@ -3815,8 +3824,8 @@ def _is_transient_db_error(err: Exception) -> bool: # Calculate waste generated, which is like waste masses but adjusts for population growth waste_generated_df = WasteGeneratedDF.create( waste_masses, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, year_of_data_pop, growth_rate_historic, growth_rate_future, @@ -4104,8 +4113,8 @@ def _calculate_divs(self, advanced_baseline=False, advanced_dst=False) -> None: city_parameters._singapore_k() # Create city-level dataframes - start_year = 1990 - end_year = 2050 + start_year = MODEL_START_YEAR + end_year = MODEL_END_YEAR years = range(start_year, end_year + 1) waste_masses_df = city_parameters.waste_fractions.multiply( @@ -4142,8 +4151,8 @@ def _calculate_divs(self, advanced_baseline=False, advanced_dst=False) -> None: if not advanced_baseline and not advanced_dst: landfill_w_capture = Landfill( - open_date=1990, - close_date=2050, + open_date=MODEL_START_YEAR, + close_date=MODEL_END_YEAR, site_type="landfill", mcf=pd.Series(1, index=years), city_params_dict=city_params_dict, @@ -4154,8 +4163,8 @@ def _calculate_divs(self, advanced_baseline=False, advanced_dst=False) -> None: fraction_of_waste_vector=pd.Series(city_parameters.split_fractions.landfill_w_capture, index=years), ) landfill_wo_capture = Landfill( - open_date=1990, - close_date=2050, + open_date=MODEL_START_YEAR, + close_date=MODEL_END_YEAR, site_type="landfill", mcf=pd.Series(1, index=years), city_params_dict=city_params_dict, @@ -4167,8 +4176,8 @@ def _calculate_divs(self, advanced_baseline=False, advanced_dst=False) -> None: fraction_of_waste_vector=pd.Series(city_parameters.split_fractions.landfill_wo_capture, index=years), ) dumpsite = Landfill( - open_date=1990, - close_date=2050, + open_date=MODEL_START_YEAR, + close_date=MODEL_END_YEAR, site_type="dumpsite", mcf=pd.Series(0.4, index=years), city_params_dict=city_params_dict, @@ -4287,7 +4296,7 @@ def _calculate_diverted_masses(self, scenario: int = 0) -> None: # Unsure if this is the right place for this... if isinstance(parameters.div_component_fractions.combustion, WasteFractions): div_component_fractions = parameters.div_component_fractions - years = range(1990, 2051) + years = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) compost_dict = div_component_fractions.compost.model_dump() compost = pd.DataFrame(compost_dict, index=years)[ list(self.div_components["compost"]) @@ -4425,7 +4434,7 @@ def dst_baseline_blank( raise ValueError(f"Region for ISO3 code '{iso3}' not found.") precip_zone = defaults_2019.get_precipitation_zone(precipitation) - years = range(1990, 2051) + years = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) # Calculate growth rates population_1950 = 751_000_000 @@ -4521,7 +4530,7 @@ def dst_baseline_blank( ) # Instantiate landfill objects - years_range = range(1990, 2051) + years_range = range(MODEL_START_YEAR, MODEL_END_YEAR + 1) city_instance_attrs = { "city_name": self.city_name, "country": country, @@ -4622,8 +4631,8 @@ def calculate_component_fractions( waste_masses_df = waste_fractions_df * waste_mass waste_generated_df = WasteGeneratedDF.create( waste_masses_df, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, year_of_data_pop, growth_rate_historic, growth_rate_future, @@ -6498,11 +6507,11 @@ def _divs_from_component_fractions( ] ) parameters.non_compostable_not_targeted_total = pd.Series( - non_compostable_not_targeted_total, np.arange(1990, 2051) + non_compostable_not_targeted_total, np.arange(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) if parameters.non_compostable_not_targeted_total.isna().all(): parameters.non_compostable_not_targeted_total = pd.Series( - 0, index=np.arange(1990, 2051) + 0, index=np.arange(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) # Deal with waste mass that changes at implement_date first. @@ -6761,7 +6770,7 @@ def _calculate_net_masses( if not parameters.waste_masses: waste_mass_dict = {} for col in self.waste_types: - fraction = parameters.waste_fractions.at[1990, col] + fraction = parameters.waste_fractions.at[MODEL_START_YEAR, col] waste_mass_dict[col] = parameters.waste_mass.iloc[0] * fraction parameters.waste_masses = WasteMasses(**waste_mass_dict) @@ -6857,7 +6866,7 @@ def implement_dst_changes_simple( self.scenario_parameters[scenario - 1] = scenario_parameters scenario_parameters.div_fractions = new_div_fractions - food_fraction = scenario_parameters.waste_fractions.at[1990, "food"] + food_fraction = scenario_parameters.waste_fractions.at[MODEL_START_YEAR, "food"] food_waste_prevented = ( food_waste_prevention * food_fraction * scenario_parameters.waste_mass ) @@ -6866,7 +6875,7 @@ def implement_dst_changes_simple( new_total_waste_fracs = 1 - food_waste_prevention * food_fraction if food_waste_prevention > 0: for frac in scenario_parameters.waste_fractions.columns: - old_val = scenario_parameters.waste_fractions.at[1990, frac] + old_val = scenario_parameters.waste_fractions.at[MODEL_START_YEAR, frac] new_val = old_val / new_total_waste_fracs scenario_parameters.waste_fractions.loc[:, frac] = new_val @@ -7035,8 +7044,8 @@ def fill_missing_fields(d: dict) -> dict: scenario_parameters.divs_df = DivsDF.create_simple( baseline_divs=baseline_divs, scenario_divs=scenario_parameters.divs, - start_year=1990, - end_year=2050, + start_year=MODEL_START_YEAR, + end_year=MODEL_END_YEAR, implement_year=implement_year, year_of_data_pop=yr_pop, growth_rate_historic=scenario_parameters.growth_rate_historic, @@ -7143,45 +7152,45 @@ def implement_dst_changes_simple_v1_5( skip_ox = False if add_gas: scenario_parameters.landfills[0].gas_capture_efficiency = pd.Series( - 0.6, index=range(1990, 2051) + 0.6, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[0].oxidation_factor = pd.Series( - 0.22, index=range(1990, 2051) + 0.22, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[0].mcf = pd.Series( - 1, index=range(1990, 2051) + 1, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[1].gas_capture_efficiency = pd.Series( - 0.6, index=range(1990, 2051) + 0.6, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[1].gas_capture_efficiency.loc[ :implement_year ] = 0.0 scenario_parameters.landfills[1].oxidation_factor = pd.Series( - 0.22, index=range(1990, 2051) + 0.22, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[1].oxidation_factor.loc[ :implement_year ] = 0.1 scenario_parameters.landfills[1].mcf = pd.Series( - 1, index=range(1990, 2051) + 1, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) # Here we convert the dumpsite to a controlled dumpsite w gas capture scenario_parameters.landfills[2].gas_capture_efficiency = pd.Series( - 0.3, index=range(1990, 2051) + 0.3, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[2].gas_capture_efficiency.loc[ :implement_year ] = 0.0 scenario_parameters.landfills[2].oxidation_factor = pd.Series( - 0.1, index=range(1990, 2051) + 0.1, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[2].oxidation_factor.loc[ :implement_year ] = 0.0 scenario_parameters.landfills[2].mcf = pd.Series( - 0.7, index=range(1990, 2051) + 0.7, index=range(MODEL_START_YEAR, MODEL_END_YEAR + 1) ) scenario_parameters.landfills[2].mcf.loc[:implement_year] = 0.4 skip_ox = True @@ -7300,9 +7309,9 @@ def implement_dst_changes_simple_v1_5( fraction_of_waste_vector.loc[implement_year:] = new_gas_pct new_landfill = Landfill( open_date=implement_year, - close_date=2050, + close_date=MODEL_END_YEAR, site_type="Sanitary Landfill", - mcf=pd.Series(1, index=range(implement_year, 2051)), + mcf=pd.Series(1, index=range(implement_year, MODEL_END_YEAR + 1)), city_params_dict=scenario_parameters.landfills[i].city_params_dict, city_instance_attrs=scenario_parameters.landfills[ i @@ -7313,7 +7322,7 @@ def implement_dst_changes_simple_v1_5( scenario=0, new_baseline=False, gas_capture_efficiency=pd.Series( - 0.6, index=range(implement_year, 2051) + 0.6, index=range(implement_year, MODEL_END_YEAR + 1) ), # flaring=pd.Series(flaring, index=year_range), # leachate_circulate=leachate_circulate[i], @@ -7321,7 +7330,7 @@ def implement_dst_changes_simple_v1_5( advanced=True, latlon=lf.latlon, ks=scenario_parameters.ks, - oxidation_factor=pd.Series(0.22, index=range(implement_year, 2051)), + oxidation_factor=pd.Series(0.22, index=range(implement_year, MODEL_END_YEAR + 1)), rmi_id=999999999, ) scenario_parameters.landfills.append(new_landfill) @@ -7462,8 +7471,8 @@ def fill_missing_fields(d: dict) -> dict: scenario_parameters.divs_df = DivsDF.create_simple( baseline_divs=baseline_divs, scenario_divs=scenario_parameters.divs, - start_year=1990, - end_year=2050, + start_year=MODEL_START_YEAR, + end_year=MODEL_END_YEAR, implement_year=implement_year, year_of_data_pop=yr_pop, growth_rate_historic=scenario_parameters.growth_rate_historic, @@ -7486,8 +7495,8 @@ def fill_missing_fields(d: dict) -> dict: scenario_parameters.waste_generated_df = WasteGeneratedDF.create( waste_masses_df, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, scenario_parameters.year_of_data_pop, scenario_parameters.growth_rate_historic, scenario_parameters.growth_rate_future, @@ -7590,7 +7599,7 @@ def implement_dst_changes_advanced( new_waste_mass["scenario"] = scenario_parameters.waste_mass.iat[0] scenario_parameters.waste_mass = new_waste_mass - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) waste_mass_series = pd.Series(index=years) waste_mass_series.loc[: waste_mass_year - 1] = new_waste_mass["baseline"] waste_mass_series.loc[waste_mass_year:] = new_waste_mass["scenario"] @@ -7691,8 +7700,8 @@ def implement_dst_changes_advanced( # Update waste generated scenario_parameters.waste_generated_df = WasteGeneratedDF.create_advanced( waste_masses_df=waste_masses_df, - start_year=1990, - end_year=2050, + start_year=MODEL_START_YEAR, + end_year=MODEL_END_YEAR, year_of_data_pop=scenario_parameters.year_of_data_pop["baseline"], growth_rate_historic=scenario_parameters.growth_rate_historic, growth_rate_future=scenario_parameters.growth_rate_future, @@ -7720,7 +7729,7 @@ def implement_dst_changes_advanced( scenario_parameters.landfills = [] for i, lf_type in enumerate(new_landfill_types["scenario"]): # Make the MCF, oxidation, and efficiency vectors - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) mcf = {} ox_value = {} gas_eff = {} @@ -8114,7 +8123,7 @@ def sdst_v1_5( scenario_parameters = copy.deepcopy(self.baseline_parameters) baseline_parameters = copy.deepcopy(self.baseline_parameters) model_year_min = 1950 - model_year_max = 2050 + model_year_max = MODEL_END_YEAR def _variant_value(value, label): try: @@ -8727,7 +8736,7 @@ def advanced_baseline( self.scenario_parameters[scenario - 1] = scenario_parameters scenario_parameters.div_fractions = new_div_fractions - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) waste_fractions_dict = new_waste_fractions.model_dump() new_waste_fractions = pd.DataFrame(waste_fractions_dict, index=years) scenario_parameters.waste_fractions = new_waste_fractions @@ -8776,8 +8785,8 @@ def advanced_baseline( # Update waste generated scenario_parameters.waste_generated_df = WasteGeneratedDF.create( waste_masses, - 1990, - 2050, + MODEL_START_YEAR, + MODEL_END_YEAR, scenario_parameters.year_of_data_pop["baseline"], scenario_parameters.growth_rate_historic, scenario_parameters.growth_rate_future, @@ -8810,7 +8819,7 @@ def advanced_baseline( scenario_parameters.landfills = [] for i, lf_type in enumerate(new_landfill_types): # Make the MCF, oxidation, and efficiency vectors - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) mcf = mcf_options[lf_type] if (depth > 5) and (lf_type in (1, 2)): mcf = 0.8 @@ -9130,7 +9139,7 @@ async def sdst_prepopulate( # Normalize the waste fractions so that they sum to 1. waste_fractions = waste_fractions / waste_fractions.sum() - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) waste_fractions_df = pd.DataFrame( np.tile(waste_fractions.values, (len(years), 1)), index=years, @@ -9165,8 +9174,8 @@ async def sdst_prepopulate( else: waste_mass = 10_000 # Default value if not provided waste_mass_year = 2025 - site_open_year = 1990 - site_close_year = 2050 + site_open_year = MODEL_START_YEAR + site_close_year = MODEL_END_YEAR return { "iso3": iso3, @@ -9181,7 +9190,7 @@ async def sdst_prepopulate( "waste_mass": float(waste_mass), "waste_mass_year": int(waste_mass_year), 'site_open_year': int(site_open_year) if pd.notna(site_open_year) else int(2010), - 'site_close_year': int(site_close_year) if pd.notna(site_close_year) else 2050 + 'site_close_year': int(site_close_year) if pd.notna(site_close_year) else MODEL_END_YEAR } def create_geolocator( diff --git a/SWEET_python/constants.py b/SWEET_python/constants.py new file mode 100644 index 0000000..d851780 --- /dev/null +++ b/SWEET_python/constants.py @@ -0,0 +1,12 @@ +"""Canonical modeling window shared across SWEET_python, the Climate TRACE +waste methane pipeline, and the WasteMAP backend. + +Waste deposited before MODEL_START_YEAR is assumed to be zero everywhere: +sites with earlier reported opening years keep their true opening year in +source data, but models treat deposition as starting in MODEL_START_YEAR. +Export and display windows (Climate TRACE submissions, WasteMAP charts) are +filters over this window, never separate modeling horizons. +""" + +MODEL_START_YEAR: int = 1990 +MODEL_END_YEAR: int = 2050 diff --git a/SWEET_python/landfill.py b/SWEET_python/landfill.py index 55f52de..acd68c0 100644 --- a/SWEET_python/landfill.py +++ b/SWEET_python/landfill.py @@ -6,6 +6,7 @@ import SWEET_python.defaults_2019 as defaults_2019 from SWEET_python.class_defs import * from SWEET_python.model_v2 import SWEET +from SWEET_python.constants import MODEL_START_YEAR, MODEL_END_YEAR # from SWEET_python.calmim_ox import Site, WeatherModel, WeatherProfile, Cover, CoverMaterial, materials, attach_thread @@ -392,7 +393,7 @@ def estimate_emissions(self, skip_ox=False, trace_monthly=False) -> tuple: if isinstance(self.oxidation_factor, pd.Series): self.oxidation_factor.loc[self.implementation_year :] = oxidation_factor else: - years = pd.Index(range(1990, 2051)) + years = pd.Index(range(MODEL_START_YEAR, MODEL_END_YEAR + 1)) ox_series = pd.Series(self.oxidation_factor, index=years) ox_series.loc[self.implementation_year :] = oxidation_factor self.oxidation_factor = ox_series diff --git a/SWEET_python/model_v2.py b/SWEET_python/model_v2.py index 9a0c744..e8bf8ec 100644 --- a/SWEET_python/model_v2.py +++ b/SWEET_python/model_v2.py @@ -30,6 +30,7 @@ import time import calendar import SWEET_python.defaults_2019 as defaults_2019 +from SWEET_python.constants import MODEL_START_YEAR, MODEL_END_YEAR pd.set_option("display.max_rows", None) # Particulate emission factors (kg PM / m^3 CH4 destroyed by flaring) @@ -90,7 +91,7 @@ def estimate_emissions2(self): # Precompute factors outside of the loop for all modeled years. # Callers are responsible for providing aligned parameter vectors from # the actual landfill open year through 2050. - year_range = np.arange(int(open_date), 2051) + year_range = np.arange(int(open_date), MODEL_END_YEAR + 1) if flare_efficiency is None: flare_efficiency = pd.Series([1 for x in year_range], index=year_range) elif isinstance(flare_efficiency, dict): @@ -191,7 +192,7 @@ def estimate_emissions2(self): def estimate_emissions_monthly(self): - open_date = self.landfill_instance_attrs["open_date"] or 1990 + open_date = self.landfill_instance_attrs["open_date"] or MODEL_START_YEAR close_date = self.landfill_instance_attrs["close_date"] ks = self.landfill_instance_attrs["ks"] waste_mass_df = self.landfill_instance_attrs["waste_mass_df"] @@ -203,11 +204,11 @@ def estimate_emissions_monthly(self): flare_efficiency = self.landfill_instance_attrs["flaring"] for k, df in ks.items(): - ks[k] = df.loc[1990:2050] + ks[k] = df.loc[MODEL_START_YEAR:MODEL_END_YEAR] # Monthly date range start_date = pd.Timestamp(f'{int(open_date)}-01-01') - end_date = pd.Timestamp('2050-12-31') + end_date = pd.Timestamp(f'{MODEL_END_YEAR}-12-31') monthly_dates = pd.date_range(start=start_date, end=end_date, freq='MS') n_months = len(monthly_dates) years = monthly_dates.year diff --git a/requirements.txt b/requirements.txt index 37a0390..0a2e0e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,4 @@ numpy pycountry asyncpg fastapi -psycopg2 sqlalchemy \ No newline at end of file From dfbe23f9fdf471bf8500a46740da00ffa6fc6d77 Mon Sep 17 00:00:00 2001 From: Hugh Runyan Date: Thu, 25 Jun 2026 17:00:59 -0700 Subject: [PATCH 2/2] Add .gitignore entries for local-only working files (CLAUDE.md, pr-drafts) --- .gitignore | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 51da93f..6e6c74e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,13 @@ __pycache__ SWEET_python.egg-info/ build/ SWEET_python/unused -SWEET_python.egg-info \ No newline at end of file +SWEET_python.egg-info + +# Claude Code personal/local settings — machine-specific, never shared +.claude/settings.local.json +.claude/settings.local.json.bak +.claude/*.bak +# Local-only repo guidance for Claude Code (not shared) +/CLAUDE.md +# Local PR-body drafts (edit in VS Code, sync to GitHub via `gh pr edit`) +/pr-drafts/ \ No newline at end of file