From 859d87dbe10e258e6807532ca009c6613c39c7d0 Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Mon, 16 Mar 2026 19:53:33 -0800 Subject: [PATCH 1/7] Add all vs. mid (rcp60) outputs for hydrograph and monthly flow charts, and min/mid/max for table stats. --- routes/conus_hydrology.py | 249 ++++++++++++++++++++++++-------------- 1 file changed, 157 insertions(+), 92 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index 07b62703..97a42ebe 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1002,14 +1002,12 @@ def run_get_conus_hydrology_gauge_info(): return render_template("500/server_error.html"), 500 -@routes.route("/conus_hydrology/hydroviz//") -def fetch_all_hydroviz_route(stream_id, model): +@routes.route("/conus_hydrology/hydroviz/") +def fetch_all_hydroviz_route(stream_id): """ - Function to fetch all data for the hydrology visualization for a given stream ID and model. - Maurer historical baseline data is included in every return. + Function to fetch all data for the hydrology visualization for a given stream ID. Args: stream_id (str): Stream ID for the hydrology data - model (str): Model for projected data, only affects stats for tables. Returns: JSON response with the following top-level keys, which correspond to different sections of the hydroviz webapp and contain only the data necessary to populate those sections. @@ -1021,87 +1019,107 @@ def fetch_all_hydroviz_route(stream_id, model): "stats": ..., "summary": ... } - """ - # Use this to validate the model provided in the URL. - projected_models = [ - "ACCESS1-0", - "BCC-CSM1-1", - "BNU-ESM", - "CCSM4", - "GFDL-ESM2G", - "GFDL-ESM2M", - "IPSL-CM5A-LR", - "IPSL-CM5A-MR", - "MIROC-ESM", - "MIROC-ESM-CHEM", - "MIROC5", - "MRI-CGCM3", - "NorESM1-M", - ] - - if model not in projected_models: - return render_template("400/bad_request.html"), 400 stats_response = run_get_conus_hydrology_stats_data(stream_id) - modeled_response = run_get_conus_hydrology_modeled_climatology(stream_id) + climatology_response = run_get_conus_hydrology_modeled_climatology(stream_id) + + # Projected era to use for the hydrograph and monthly flow charts. + chart_era = "2046-2075" # If either response is an error page, return it. - for response in [stats_response, modeled_response]: + for response in [stats_response, climatology_response]: if isinstance(response, tuple): return response try: + # Fetch all needed data. stats = stats_response.get_json() - modeled = modeled_response.get_json() + climatology = climatology_response.get_json() - # The hydrograph and monthly mean flow charts use this scenario and era. - scenario = "rcp85" - era = "2046-2075" + ########## Populate arrays for hydrograph. ########## - hydrograph_min = [] - hydrograph_mean = [] - hydrograph_max = [] - - doy_min = None - doy_max = None - doy_mean = None - - hydrograph_historical = {} - historical = modeled["data"]["static"]["Maurer"]["historical"]["1976-2005"] - - hydrograph_historical["doy_min"] = list( - map(lambda x: round(x["doy_min"]), historical) - ) - hydrograph_historical["doy_mean"] = list( - map(lambda x: x["doy_mean"], historical) - ) - hydrograph_historical["doy_max"] = list( - map(lambda x: round(x["doy_max"]), historical) - ) + hydrograph = { + "historical": {}, + "projected": { + "all": { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + }, + "mid": { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + }, + }, + } - del modeled["data"]["static"]["Maurer"] + historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ + "1976-2005" + ] + hydrograph_historical = { + "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), + "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), + "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), + } + hydrograph["historical"] = hydrograph_historical # Get min/mean/max values for each DOY across all projected models. for i in range(366): - unset = True - for model_dict in modeled["data"]["static"].values(): - if unset: - doy_min = model_dict[scenario][era][i]["doy_min"] - doy_max = model_dict[scenario][era][i]["doy_max"] - doy_mean = model_dict[scenario][era][i]["doy_mean"] - unset = False - continue - else: - if model_dict[scenario][era][i]["doy_min"] < doy_min: - doy_min = model_dict[scenario][era][i]["doy_min"] - if model_dict[scenario][era][i]["doy_max"] > doy_max: - doy_max = model_dict[scenario][era][i]["doy_max"] - doy_mean += model_dict[scenario][era][i]["doy_mean"] - - hydrograph_min.append(round(doy_min)) - hydrograph_mean.append(round(doy_mean / len(modeled["data"]["static"]), 3)) - hydrograph_max.append(round(doy_max)) + doy_mins_all = [] + doy_means_all = [] + doy_maxes_all = [] + doy_mins_mid = [] + doy_means_mid = [] + doy_maxes_mid = [] + for model_dict in climatology["data"]["static"].values(): + for scenario in model_dict.keys(): + if scenario in ["historical", "rcp26"]: + continue + doy_mins_all.append(model_dict[scenario][chart_era][i]["doy_min"]) + doy_means_all.append(model_dict[scenario][chart_era][i]["doy_mean"]) + doy_maxes_all.append(model_dict[scenario][chart_era][i]["doy_max"]) + if scenario == "rcp60": + doy_mins_mid.append( + model_dict[scenario][chart_era][i]["doy_min"] + ) + doy_means_mid.append( + model_dict[scenario][chart_era][i]["doy_mean"] + ) + doy_maxes_mid.append( + model_dict[scenario][chart_era][i]["doy_max"] + ) + + hydrograph["projected"]["all"]["doy_min"].append( + round(min(doy_mins_all), 3) + ) + hydrograph["projected"]["all"]["doy_mean"].append( + round(sum(doy_means_all) / len(doy_means_all), 3) + ) + hydrograph["projected"]["all"]["doy_max"].append( + round(max(doy_maxes_all), 3) + ) + + hydrograph["projected"]["mid"]["doy_min"].append( + round(min(doy_mins_mid), 3) + ) + hydrograph["projected"]["mid"]["doy_mean"].append( + round(sum(doy_means_mid) / len(doy_means_mid), 3) + ) + hydrograph["projected"]["mid"]["doy_max"].append( + round(max(doy_maxes_mid), 3) + ) + + ########## Populate arrays for monthly modeled flow rate chart. ########## + + monthly_flow = { + "historical": {}, + "projected": { + "all": {}, + "mid": {}, + }, + } monthly_flow_keys = [ "ma12", @@ -1118,8 +1136,6 @@ def fetch_all_hydroviz_route(stream_id, model): "ma23", ] - monthly_flow = {"historical": {}, "projected": {}} - # Get historical data for each month. for key in monthly_flow_keys: monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ @@ -1128,30 +1144,79 @@ def fetch_all_hydroviz_route(stream_id, model): # Populate lists of values for each month for each projected model. # These values will be used to generate box plots in the webapp. - for model_key, model_stats in stats["data"]["static"].items(): - # Skip Maurer, which is used only for historical baselines. - if model_key == "Maurer": - continue - for key in monthly_flow_keys: - if key not in monthly_flow["projected"]: - monthly_flow["projected"][key] = [] - monthly_flow["projected"][key].append(model_stats[scenario][era][key]) - - # Stats for the tables in the webapp, only for the requested model. + for model_stats in stats["data"]["static"].values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + for key in monthly_flow_keys: + if key not in monthly_flow["projected"]["all"]: + monthly_flow["projected"]["all"][key] = [] + if ( + scenario not in model_stats + or chart_era not in model_stats[scenario] + ): + continue + monthly_flow["projected"]["all"][key].append( + model_stats[scenario][chart_era][key] + ) + if scenario == "rcp60": + monthly_flow["projected"]["mid"][key] = model_stats["rcp60"][ + chart_era + ][key] + + ########## Calculate stats for the stats table. ########## + table_stats = { - "historical": stats["data"]["static"]["Maurer"]["historical"], - "projected": stats["data"]["static"][model][scenario], + "historical": {}, + "projected": { + "max": {}, + "mid": {}, + "min": {}, + }, } + table_stats["historical"] = model_stats["historical"] + + all_stats = {} + mid_stats = {} + for model_stats in stats["data"]["static"].values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + for era in model_stats[scenario].keys(): + if era not in model_stats[scenario]: + continue + for stat in model_stats[scenario][era].keys(): + if era not in all_stats: + all_stats[era] = {} + if stat not in all_stats[era]: + all_stats[era][stat] = [] + all_stats[era][stat].append(model_stats[scenario][era][stat]) + if scenario == "rcp60": + if era not in mid_stats: + mid_stats[era] = {} + if stat not in mid_stats[era]: + mid_stats[era][stat] = [] + mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) + + for era in all_stats.keys(): + for stat in all_stats[era].keys(): + all_values = all_stats[era][stat] + mid_values = mid_stats[era][stat] + if era not in table_stats["projected"]["min"]: + table_stats["projected"]["min"][era] = {} + if era not in table_stats["projected"]["max"]: + table_stats["projected"]["max"][era] = {} + if era not in table_stats["projected"]["mid"]: + table_stats["projected"]["mid"][era] = {} + table_stats["projected"]["min"][era][stat] = round(min(all_values), 3) + table_stats["projected"]["max"][era][stat] = round(max(all_values), 3) + table_stats["projected"]["mid"][era][stat] = round( + sum(mid_values) / len(mid_values), 3 + ) + response = { - "hydrograph": { - "historical": hydrograph_historical, - "projected": { - "doy_min": hydrograph_min, - "doy_mean": hydrograph_mean, - "doy_max": hydrograph_max, - }, - }, + "hydrograph": hydrograph, "id": stats["id"], "name": stats["name"], "monthly_flow": monthly_flow, From 5b651be8bfe49d8dca5d6986b0abc44e3254e6f8 Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Thu, 19 Mar 2026 14:54:18 -0800 Subject: [PATCH 2/7] Change hydrograph and monthly_flow "all" key to "extremes" to match webapp. --- routes/conus_hydrology.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index 97a42ebe..fffc1c9c 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1042,7 +1042,7 @@ def fetch_all_hydroviz_route(stream_id): hydrograph = { "historical": {}, "projected": { - "all": { + "extremes": { "doy_min": [], "doy_mean": [], "doy_max": [], @@ -1091,13 +1091,13 @@ def fetch_all_hydroviz_route(stream_id): model_dict[scenario][chart_era][i]["doy_max"] ) - hydrograph["projected"]["all"]["doy_min"].append( + hydrograph["projected"]["extremes"]["doy_min"].append( round(min(doy_mins_all), 3) ) - hydrograph["projected"]["all"]["doy_mean"].append( + hydrograph["projected"]["extremes"]["doy_mean"].append( round(sum(doy_means_all) / len(doy_means_all), 3) ) - hydrograph["projected"]["all"]["doy_max"].append( + hydrograph["projected"]["extremes"]["doy_max"].append( round(max(doy_maxes_all), 3) ) @@ -1116,7 +1116,7 @@ def fetch_all_hydroviz_route(stream_id): monthly_flow = { "historical": {}, "projected": { - "all": {}, + "extremes": {}, "mid": {}, }, } @@ -1149,14 +1149,14 @@ def fetch_all_hydroviz_route(stream_id): if scenario in ["historical", "rcp26"]: continue for key in monthly_flow_keys: - if key not in monthly_flow["projected"]["all"]: - monthly_flow["projected"]["all"][key] = [] + if key not in monthly_flow["projected"]["extremes"]: + monthly_flow["projected"]["extremes"][key] = [] if ( scenario not in model_stats or chart_era not in model_stats[scenario] ): continue - monthly_flow["projected"]["all"][key].append( + monthly_flow["projected"]["extremes"][key].append( model_stats[scenario][chart_era][key] ) if scenario == "rcp60": From a30db87279c7e1fe1d3099f19da9891fb52cfabe Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Tue, 24 Mar 2026 20:57:16 -0800 Subject: [PATCH 3/7] Add min/max annual flow data to hydroviz endpoint for polarscatter chart. --- routes/conus_hydrology.py | 451 +++++++++++++++++++++++--------------- 1 file changed, 271 insertions(+), 180 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index fffc1c9c..0bc93d37 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -29,6 +29,7 @@ from csv_functions import create_csv from config import RAS_BASE_URL from . import routes +import statistics coverages = { "stats": ["conus_hydro_segments_stats"], @@ -1015,6 +1016,7 @@ def fetch_all_hydroviz_route(stream_id): "hydrograph": ..., "id": ..., "monthly_flow": ..., + "min_max_flow_dates": ..., "name": ..., "stats": ..., "summary": ... @@ -1032,198 +1034,287 @@ def fetch_all_hydroviz_route(stream_id): if isinstance(response, tuple): return response - try: - # Fetch all needed data. - stats = stats_response.get_json() - climatology = climatology_response.get_json() - - ########## Populate arrays for hydrograph. ########## - - hydrograph = { - "historical": {}, - "projected": { - "extremes": { - "doy_min": [], - "doy_mean": [], - "doy_max": [], + # try: + # Fetch all needed data. + stats = stats_response.get_json() + climatology = climatology_response.get_json() + + ########## Populate arrays for hydrograph. ########## + + hydrograph = { + "historical": {}, + "projected": { + "extremes": { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + }, + "mid": { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + }, + }, + } + + historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ + "1976-2005" + ] + hydrograph_historical = { + "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), + "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), + "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), + } + hydrograph["historical"] = hydrograph_historical + + # Get min/mean/max values for each DOY across all projected models. + for i in range(366): + doy_mins_all = [] + doy_means_all = [] + doy_maxes_all = [] + doy_mins_mid = [] + doy_means_mid = [] + doy_maxes_mid = [] + for model_dict in climatology["data"]["static"].values(): + for scenario in model_dict.keys(): + if scenario in ["historical", "rcp26"]: + continue + doy_mins_all.append(model_dict[scenario][chart_era][i]["doy_min"]) + doy_means_all.append(model_dict[scenario][chart_era][i]["doy_mean"]) + doy_maxes_all.append(model_dict[scenario][chart_era][i]["doy_max"]) + if scenario == "rcp60": + doy_mins_mid.append(model_dict[scenario][chart_era][i]["doy_min"]) + doy_means_mid.append(model_dict[scenario][chart_era][i]["doy_mean"]) + doy_maxes_mid.append(model_dict[scenario][chart_era][i]["doy_max"]) + + hydrograph["projected"]["extremes"]["doy_min"].append( + round(min(doy_mins_all), 3) + ) + hydrograph["projected"]["extremes"]["doy_mean"].append( + round(sum(doy_means_all) / len(doy_means_all), 3) + ) + hydrograph["projected"]["extremes"]["doy_max"].append( + round(max(doy_maxes_all), 3) + ) + + hydrograph["projected"]["mid"]["doy_min"].append(round(min(doy_mins_mid), 3)) + hydrograph["projected"]["mid"]["doy_mean"].append( + round(sum(doy_means_mid) / len(doy_means_mid), 3) + ) + hydrograph["projected"]["mid"]["doy_max"].append(round(max(doy_maxes_mid), 3)) + + ########## Populate arrays for monthly modeled flow rate chart. ########## + + monthly_flow = { + "historical": {}, + "projected": { + "extremes": {}, + "mid": {}, + }, + } + + monthly_flow_keys = [ + "ma12", + "ma13", + "ma14", + "ma15", + "ma16", + "ma17", + "ma18", + "ma19", + "ma20", + "ma21", + "ma22", + "ma23", + ] + + # Get historical data for each month. + for key in monthly_flow_keys: + monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"][key] + + # Populate lists of values for each month for each projected model. + # These values will be used to generate box plots in the webapp. + for model_stats in stats["data"]["static"].values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + for key in monthly_flow_keys: + if key not in monthly_flow["projected"]["extremes"]: + monthly_flow["projected"]["extremes"][key] = [] + if ( + scenario not in model_stats + or chart_era not in model_stats[scenario] + ): + continue + monthly_flow["projected"]["extremes"][key].append( + model_stats[scenario][chart_era][key] + ) + if scenario == "rcp60": + monthly_flow["projected"]["mid"][key] = model_stats["rcp60"][ + chart_era + ][key] + + ########## Populate arrays for max flow date chart. ########## + + min_max_flow_dates = { + "historical": { + "min": { + "flow": None, + "date": None, + }, + "max": { + "flow": None, + "date": None, + }, + }, + "projected": { + "extremes": { + "min": { + "flow": [], + "date": [], }, - "mid": { - "doy_min": [], - "doy_mean": [], - "doy_max": [], + "max": { + "flow": [], + "date": [], }, }, - } + "mid": { + "min": { + "flow": None, + "date": None, + }, + "max": { + "flow": None, + "date": None, + }, + }, + }, + } - historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ - "1976-2005" - ] - hydrograph_historical = { - "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), - "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), - "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), - } - hydrograph["historical"] = hydrograph_historical - - # Get min/mean/max values for each DOY across all projected models. - for i in range(366): - doy_mins_all = [] - doy_means_all = [] - doy_maxes_all = [] - doy_mins_mid = [] - doy_means_mid = [] - doy_maxes_mid = [] - for model_dict in climatology["data"]["static"].values(): - for scenario in model_dict.keys(): - if scenario in ["historical", "rcp26"]: - continue - doy_mins_all.append(model_dict[scenario][chart_era][i]["doy_min"]) - doy_means_all.append(model_dict[scenario][chart_era][i]["doy_mean"]) - doy_maxes_all.append(model_dict[scenario][chart_era][i]["doy_max"]) - if scenario == "rcp60": - doy_mins_mid.append( - model_dict[scenario][chart_era][i]["doy_min"] - ) - doy_means_mid.append( - model_dict[scenario][chart_era][i]["doy_mean"] - ) - doy_maxes_mid.append( - model_dict[scenario][chart_era][i]["doy_max"] - ) - - hydrograph["projected"]["extremes"]["doy_min"].append( - round(min(doy_mins_all), 3) - ) - hydrograph["projected"]["extremes"]["doy_mean"].append( - round(sum(doy_means_all) / len(doy_means_all), 3) + # Get historical data for min and max flow date. + min_max_flow_dates["historical"]["min"]["flow"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["dl1"] + min_max_flow_dates["historical"]["min"]["date"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["tl1"] + min_max_flow_dates["historical"]["max"]["flow"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["dh1"] + min_max_flow_dates["historical"]["max"]["date"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["th1"] + + max_flow_date_array = [] + max_flow_value_array = [] + min_flow_date_array = [] + min_flow_value_array = [] + + # Populate lists of values for max flow date for each projected model. + for model_stats in stats["data"]["static"].values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in model_stats or chart_era not in model_stats[scenario]: + continue + + min_max_flow_dates["projected"]["extremes"]["min"]["flow"].append( + model_stats[scenario][chart_era]["dl1"] ) - hydrograph["projected"]["extremes"]["doy_max"].append( - round(max(doy_maxes_all), 3) + min_max_flow_dates["projected"]["extremes"]["min"]["date"].append( + model_stats[scenario][chart_era]["tl1"] ) - - hydrograph["projected"]["mid"]["doy_min"].append( - round(min(doy_mins_mid), 3) + min_max_flow_dates["projected"]["extremes"]["max"]["flow"].append( + model_stats[scenario][chart_era]["dh1"] ) - hydrograph["projected"]["mid"]["doy_mean"].append( - round(sum(doy_means_mid) / len(doy_means_mid), 3) + min_max_flow_dates["projected"]["extremes"]["max"]["date"].append( + model_stats[scenario][chart_era]["th1"] ) - hydrograph["projected"]["mid"]["doy_max"].append( - round(max(doy_maxes_mid), 3) - ) - - ########## Populate arrays for monthly modeled flow rate chart. ########## + if scenario == "rcp60": + min_flow_date_array.append(model_stats["rcp60"][chart_era]["tl1"]) + min_flow_value_array.append(model_stats["rcp60"][chart_era]["dl1"]) + max_flow_date_array.append(model_stats["rcp60"][chart_era]["th1"]) + max_flow_value_array.append(model_stats["rcp60"][chart_era]["dh1"]) + + # Take the median, not mean, of dates. To understand why, consider the mean + # of January 1st (doy: 1) and December 31st (doy: 366). The mean would be + # July 3rd (doy: 184) which is not representative of either date. + min_max_flow_dates["projected"]["mid"]["min"]["date"] = round( + statistics.median(min_flow_date_array), 1 + ) + min_max_flow_dates["projected"]["mid"]["min"]["flow"] = round( + statistics.mean(min_flow_value_array), 1 + ) + min_max_flow_dates["projected"]["mid"]["max"]["date"] = round( + statistics.median(max_flow_date_array), 1 + ) + min_max_flow_dates["projected"]["mid"]["max"]["flow"] = round( + statistics.mean(max_flow_value_array), 1 + ) - monthly_flow = { - "historical": {}, - "projected": { - "extremes": {}, - "mid": {}, - }, - } + ########## Calculate stats for the stats table. ########## - monthly_flow_keys = [ - "ma12", - "ma13", - "ma14", - "ma15", - "ma16", - "ma17", - "ma18", - "ma19", - "ma20", - "ma21", - "ma22", - "ma23", - ] - - # Get historical data for each month. - for key in monthly_flow_keys: - monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"][key] - - # Populate lists of values for each month for each projected model. - # These values will be used to generate box plots in the webapp. - for model_stats in stats["data"]["static"].values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - for key in monthly_flow_keys: - if key not in monthly_flow["projected"]["extremes"]: - monthly_flow["projected"]["extremes"][key] = [] - if ( - scenario not in model_stats - or chart_era not in model_stats[scenario] - ): - continue - monthly_flow["projected"]["extremes"][key].append( - model_stats[scenario][chart_era][key] - ) - if scenario == "rcp60": - monthly_flow["projected"]["mid"][key] = model_stats["rcp60"][ - chart_era - ][key] - - ########## Calculate stats for the stats table. ########## - - table_stats = { - "historical": {}, - "projected": { - "max": {}, - "mid": {}, - "min": {}, - }, - } + table_stats = { + "historical": {}, + "projected": { + "max": {}, + "mid": {}, + "min": {}, + }, + } - table_stats["historical"] = model_stats["historical"] + table_stats["historical"] = model_stats["historical"] - all_stats = {} - mid_stats = {} - for model_stats in stats["data"]["static"].values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: + all_stats = {} + mid_stats = {} + for model_stats in stats["data"]["static"].values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + for era in model_stats[scenario].keys(): + if era not in model_stats[scenario]: continue - for era in model_stats[scenario].keys(): - if era not in model_stats[scenario]: - continue - for stat in model_stats[scenario][era].keys(): - if era not in all_stats: - all_stats[era] = {} - if stat not in all_stats[era]: - all_stats[era][stat] = [] - all_stats[era][stat].append(model_stats[scenario][era][stat]) - if scenario == "rcp60": - if era not in mid_stats: - mid_stats[era] = {} - if stat not in mid_stats[era]: - mid_stats[era][stat] = [] - mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) - - for era in all_stats.keys(): - for stat in all_stats[era].keys(): - all_values = all_stats[era][stat] - mid_values = mid_stats[era][stat] - if era not in table_stats["projected"]["min"]: - table_stats["projected"]["min"][era] = {} - if era not in table_stats["projected"]["max"]: - table_stats["projected"]["max"][era] = {} - if era not in table_stats["projected"]["mid"]: - table_stats["projected"]["mid"][era] = {} - table_stats["projected"]["min"][era][stat] = round(min(all_values), 3) - table_stats["projected"]["max"][era][stat] = round(max(all_values), 3) - table_stats["projected"]["mid"][era][stat] = round( - sum(mid_values) / len(mid_values), 3 - ) + for stat in model_stats[scenario][era].keys(): + if era not in all_stats: + all_stats[era] = {} + if stat not in all_stats[era]: + all_stats[era][stat] = [] + all_stats[era][stat].append(model_stats[scenario][era][stat]) + if scenario == "rcp60": + if era not in mid_stats: + mid_stats[era] = {} + if stat not in mid_stats[era]: + mid_stats[era][stat] = [] + mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) + + for era in all_stats.keys(): + for stat in all_stats[era].keys(): + all_values = all_stats[era][stat] + mid_values = mid_stats[era][stat] + if era not in table_stats["projected"]["min"]: + table_stats["projected"]["min"][era] = {} + if era not in table_stats["projected"]["max"]: + table_stats["projected"]["max"][era] = {} + if era not in table_stats["projected"]["mid"]: + table_stats["projected"]["mid"][era] = {} + table_stats["projected"]["min"][era][stat] = round(min(all_values), 3) + table_stats["projected"]["max"][era][stat] = round(max(all_values), 3) + table_stats["projected"]["mid"][era][stat] = round( + statistics.mean(mid_values), 3 + ) - response = { - "hydrograph": hydrograph, - "id": stats["id"], - "name": stats["name"], - "monthly_flow": monthly_flow, - "stats": table_stats, - "summary": stats["summary"], - } - return jsonify(response) + response = { + "hydrograph": hydrograph, + "id": stats["id"], + "name": stats["name"], + "monthly_flow": monthly_flow, + "min_max_flow_dates": min_max_flow_dates, + "stats": table_stats, + "summary": stats["summary"], + } + return jsonify(response) - except Exception as exc: - return render_template("500/server_error.html"), 500 + # except Exception as exc: + # return render_template("500/server_error.html"), 500 From 7e318b5e84bb581d98fb589cd0931dab95ff31db Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Thu, 26 Mar 2026 17:43:24 -0800 Subject: [PATCH 4/7] Add gauge_id, h8_outlet, and huc8 fields to hydroviz endpoint. --- routes/conus_hydrology.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index 0bc93d37..b257f464 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1305,7 +1305,22 @@ def fetch_all_hydroviz_route(stream_id): statistics.mean(mid_values), 3 ) + gauge_id = None + h8_outlet = False + huc8 = None + + gdf = asyncio.run(get_features(stream_id)) + if not isinstance(gdf, tuple): + if gdf.loc[0].h8_outlet == 1: + h8_outlet = True + if gdf.loc[0].GAUGE_ID != "NA": + gauge_id = gdf.loc[0].GAUGE_ID + huc8 = gdf.loc[0].huc8 + response = { + "gauge_id": gauge_id, + "h8_outlet": h8_outlet, + "huc8": huc8, "hydrograph": hydrograph, "id": stats["id"], "name": stats["name"], @@ -1314,6 +1329,7 @@ def fetch_all_hydroviz_route(stream_id): "stats": table_stats, "summary": stats["summary"], } + return jsonify(response) # except Exception as exc: From 6985ca0aa1f56bbc1b2c3f0aaaf8cbac4e9687cd Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Fri, 27 Mar 2026 13:34:32 -0800 Subject: [PATCH 5/7] Implement "Maurer plus projected delta" conversion with multiple eras. --- routes/conus_hydrology.py | 647 +++++++++++++++++++++----------------- 1 file changed, 367 insertions(+), 280 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index b257f464..31c42aac 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1026,161 +1026,222 @@ def fetch_all_hydroviz_route(stream_id): stats_response = run_get_conus_hydrology_stats_data(stream_id) climatology_response = run_get_conus_hydrology_modeled_climatology(stream_id) - # Projected era to use for the hydrograph and monthly flow charts. - chart_era = "2046-2075" + # Projected eras to use for the hydrograph and monthly flow charts. + chart_eras = ["2016-2045", "2046-2075", "2071-2100"] # If either response is an error page, return it. for response in [stats_response, climatology_response]: if isinstance(response, tuple): return response - # try: - # Fetch all needed data. - stats = stats_response.get_json() - climatology = climatology_response.get_json() - - ########## Populate arrays for hydrograph. ########## - - hydrograph = { - "historical": {}, - "projected": { - "extremes": { - "doy_min": [], - "doy_mean": [], - "doy_max": [], - }, - "mid": { - "doy_min": [], - "doy_mean": [], - "doy_max": [], - }, - }, - } - - historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ - "1976-2005" - ] - hydrograph_historical = { - "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), - "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), - "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), - } - hydrograph["historical"] = hydrograph_historical - - # Get min/mean/max values for each DOY across all projected models. - for i in range(366): - doy_mins_all = [] - doy_means_all = [] - doy_maxes_all = [] - doy_mins_mid = [] - doy_means_mid = [] - doy_maxes_mid = [] - for model_dict in climatology["data"]["static"].values(): - for scenario in model_dict.keys(): + try: + # Fetch all needed data. + stats = stats_response.get_json() + climatology = climatology_response.get_json() + + # Convert static stats to "Maurer plus projected delta". + static_stats = stats["data"]["static"] + maurer_plus_delta_stats = {} + for model in static_stats.keys(): + if model not in maurer_plus_delta_stats: + maurer_plus_delta_stats[model] = {} + for scenario in static_stats[model].keys(): if scenario in ["historical", "rcp26"]: continue - doy_mins_all.append(model_dict[scenario][chart_era][i]["doy_min"]) - doy_means_all.append(model_dict[scenario][chart_era][i]["doy_mean"]) - doy_maxes_all.append(model_dict[scenario][chart_era][i]["doy_max"]) - if scenario == "rcp60": - doy_mins_mid.append(model_dict[scenario][chart_era][i]["doy_min"]) - doy_means_mid.append(model_dict[scenario][chart_era][i]["doy_mean"]) - doy_maxes_mid.append(model_dict[scenario][chart_era][i]["doy_max"]) - - hydrograph["projected"]["extremes"]["doy_min"].append( - round(min(doy_mins_all), 3) - ) - hydrograph["projected"]["extremes"]["doy_mean"].append( - round(sum(doy_means_all) / len(doy_means_all), 3) - ) - hydrograph["projected"]["extremes"]["doy_max"].append( - round(max(doy_maxes_all), 3) - ) + if scenario not in maurer_plus_delta_stats[model]: + maurer_plus_delta_stats[model][scenario] = {} + for era in static_stats[model][scenario].keys(): + if era not in maurer_plus_delta_stats[model][scenario]: + maurer_plus_delta_stats[model][scenario][era] = {} + for stat in static_stats[model][scenario][era].keys(): + maurer_historical = static_stats["Maurer"]["historical"][ + "1976-2005" + ][stat] + modeled_historical = static_stats[model]["historical"][ + "1976-2005" + ][stat] + modeled_projected = static_stats[model][scenario][era][stat] + projected_delta = modeled_projected - modeled_historical + maurer_plus_delta = round( + maurer_historical + projected_delta, 3 + ) + maurer_plus_delta_stats[model][scenario][era][ + stat + ] = maurer_plus_delta + + # Convert static climatology to "Maurer plus projected delta". + static_climatology = climatology["data"]["static"] + maurer_plus_delta_climatology = {} + for model in static_climatology.keys(): + if model not in maurer_plus_delta_climatology: + maurer_plus_delta_climatology[model] = {} + for scenario in static_climatology[model].keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in maurer_plus_delta_climatology[model]: + maurer_plus_delta_climatology[model][scenario] = {} + for era in static_climatology[model][scenario].keys(): + if era not in maurer_plus_delta_climatology[model][scenario]: + maurer_plus_delta_climatology[model][scenario][era] = [] + for i in range(len(static_climatology[model][scenario][era])): + doy_stats = {} + for stat in static_climatology[model][scenario][era][i].keys(): + maurer_historical = static_climatology["Maurer"][ + "historical" + ]["1976-2005"][i][stat] + modeled_historical = static_climatology[model][ + "historical" + ]["1976-2005"][i][stat] + modeled_projected = static_climatology[model][scenario][ + era + ][i][stat] + projected_delta = modeled_projected - modeled_historical + maurer_plus_delta = round( + maurer_historical + projected_delta, 3 + ) + doy_stats[stat] = maurer_plus_delta + maurer_plus_delta_climatology[model][scenario][era].append( + doy_stats + ) + + ########## Populate arrays for hydrograph. ########## + + hydrograph = { + "historical": {}, + "projected": { + "extremes": {}, + "mid": {}, + }, + } - hydrograph["projected"]["mid"]["doy_min"].append(round(min(doy_mins_mid), 3)) - hydrograph["projected"]["mid"]["doy_mean"].append( - round(sum(doy_means_mid) / len(doy_means_mid), 3) - ) - hydrograph["projected"]["mid"]["doy_max"].append(round(max(doy_maxes_mid), 3)) + historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ + "1976-2005" + ] + hydrograph_historical = { + "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), + "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), + "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), + } + hydrograph["historical"] = hydrograph_historical + + # Get min/mean/max values for each DOY across all projected models. + for era in chart_eras: + for i in range(366): + doy_mins_all = [] + doy_means_all = [] + doy_maxes_all = [] + doy_mins_mid = [] + doy_means_mid = [] + doy_maxes_mid = [] + for model_dict in maurer_plus_delta_climatology.values(): + for scenario in model_dict.keys(): + if scenario in ["historical", "rcp26"]: + continue + doy_mins_all.append(model_dict[scenario][era][i]["doy_min"]) + doy_means_all.append(model_dict[scenario][era][i]["doy_mean"]) + doy_maxes_all.append(model_dict[scenario][era][i]["doy_max"]) + if scenario == "rcp60": + doy_mins_mid.append(model_dict[scenario][era][i]["doy_min"]) + doy_means_mid.append( + model_dict[scenario][era][i]["doy_mean"] + ) + doy_maxes_mid.append( + model_dict[scenario][era][i]["doy_max"] + ) + + if era not in hydrograph["projected"]["extremes"]: + hydrograph["projected"]["extremes"][era] = { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + } + hydrograph["projected"]["extremes"][era]["doy_min"].append( + round(min(doy_mins_all), 3) + ) + hydrograph["projected"]["extremes"][era]["doy_mean"].append( + round(sum(doy_means_all) / len(doy_means_all), 3) + ) + hydrograph["projected"]["extremes"][era]["doy_max"].append( + round(max(doy_maxes_all), 3) + ) - ########## Populate arrays for monthly modeled flow rate chart. ########## + if era not in hydrograph["projected"]["mid"]: + hydrograph["projected"]["mid"][era] = { + "doy_min": [], + "doy_mean": [], + "doy_max": [], + } + hydrograph["projected"]["mid"][era]["doy_min"].append( + round(min(doy_mins_mid), 3) + ) + hydrograph["projected"]["mid"][era]["doy_mean"].append( + round(sum(doy_means_mid) / len(doy_means_mid), 3) + ) + hydrograph["projected"]["mid"][era]["doy_max"].append( + round(max(doy_maxes_mid), 3) + ) - monthly_flow = { - "historical": {}, - "projected": { - "extremes": {}, - "mid": {}, - }, - } + ########## Populate arrays for monthly modeled flow rate chart. ########## - monthly_flow_keys = [ - "ma12", - "ma13", - "ma14", - "ma15", - "ma16", - "ma17", - "ma18", - "ma19", - "ma20", - "ma21", - "ma22", - "ma23", - ] - - # Get historical data for each month. - for key in monthly_flow_keys: - monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"][key] - - # Populate lists of values for each month for each projected model. - # These values will be used to generate box plots in the webapp. - for model_stats in stats["data"]["static"].values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - for key in monthly_flow_keys: - if key not in monthly_flow["projected"]["extremes"]: - monthly_flow["projected"]["extremes"][key] = [] - if ( - scenario not in model_stats - or chart_era not in model_stats[scenario] - ): - continue - monthly_flow["projected"]["extremes"][key].append( - model_stats[scenario][chart_era][key] - ) - if scenario == "rcp60": - monthly_flow["projected"]["mid"][key] = model_stats["rcp60"][ - chart_era - ][key] - - ########## Populate arrays for max flow date chart. ########## - - min_max_flow_dates = { - "historical": { - "min": { - "flow": None, - "date": None, + monthly_flow = { + "historical": {}, + "projected": { + "extremes": {}, + "mid": {}, }, - "max": { - "flow": None, - "date": None, - }, - }, - "projected": { - "extremes": { - "min": { - "flow": [], - "date": [], - }, - "max": { - "flow": [], - "date": [], - }, - }, - "mid": { + } + + monthly_flow_keys = [ + "ma12", + "ma13", + "ma14", + "ma15", + "ma16", + "ma17", + "ma18", + "ma19", + "ma20", + "ma21", + "ma22", + "ma23", + ] + + # Get historical data for each month. + for key in monthly_flow_keys: + monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"][key] + + # Populate lists of values for each month for each projected model. + # These values will be used to generate box plots in the webapp. + for era in chart_eras: + if era not in monthly_flow["projected"]["mid"]: + monthly_flow["projected"]["mid"][era] = {} + if era not in monthly_flow["projected"]["extremes"]: + monthly_flow["projected"]["extremes"][era] = {} + for model_stats in maurer_plus_delta_stats.values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in model_stats or era not in model_stats[scenario]: + continue + for key in monthly_flow_keys: + if key not in monthly_flow["projected"]["extremes"][era]: + monthly_flow["projected"]["extremes"][era][key] = [] + monthly_flow["projected"]["extremes"][era][key].append( + model_stats[scenario][era][key] + ) + if scenario == "rcp60": + if era not in monthly_flow["projected"]["mid"]: + monthly_flow["projected"]["mid"][era] = {} + monthly_flow["projected"]["mid"][era][key] = model_stats[ + "rcp60" + ][era][key] + + ########## Populate arrays for max flow date chart. ########## + + min_max_flow_dates = { + "historical": { "min": { "flow": None, "date": None, @@ -1190,147 +1251,173 @@ def fetch_all_hydroviz_route(stream_id): "date": None, }, }, - }, - } - - # Get historical data for min and max flow date. - min_max_flow_dates["historical"]["min"]["flow"] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"]["dl1"] - min_max_flow_dates["historical"]["min"]["date"] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"]["tl1"] - min_max_flow_dates["historical"]["max"]["flow"] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"]["dh1"] - min_max_flow_dates["historical"]["max"]["date"] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"]["th1"] - - max_flow_date_array = [] - max_flow_value_array = [] - min_flow_date_array = [] - min_flow_value_array = [] - - # Populate lists of values for max flow date for each projected model. - for model_stats in stats["data"]["static"].values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - if scenario not in model_stats or chart_era not in model_stats[scenario]: - continue - - min_max_flow_dates["projected"]["extremes"]["min"]["flow"].append( - model_stats[scenario][chart_era]["dl1"] - ) - min_max_flow_dates["projected"]["extremes"]["min"]["date"].append( - model_stats[scenario][chart_era]["tl1"] - ) - min_max_flow_dates["projected"]["extremes"]["max"]["flow"].append( - model_stats[scenario][chart_era]["dh1"] - ) - min_max_flow_dates["projected"]["extremes"]["max"]["date"].append( - model_stats[scenario][chart_era]["th1"] - ) - if scenario == "rcp60": - min_flow_date_array.append(model_stats["rcp60"][chart_era]["tl1"]) - min_flow_value_array.append(model_stats["rcp60"][chart_era]["dl1"]) - max_flow_date_array.append(model_stats["rcp60"][chart_era]["th1"]) - max_flow_value_array.append(model_stats["rcp60"][chart_era]["dh1"]) - - # Take the median, not mean, of dates. To understand why, consider the mean - # of January 1st (doy: 1) and December 31st (doy: 366). The mean would be - # July 3rd (doy: 184) which is not representative of either date. - min_max_flow_dates["projected"]["mid"]["min"]["date"] = round( - statistics.median(min_flow_date_array), 1 - ) - min_max_flow_dates["projected"]["mid"]["min"]["flow"] = round( - statistics.mean(min_flow_value_array), 1 - ) - min_max_flow_dates["projected"]["mid"]["max"]["date"] = round( - statistics.median(max_flow_date_array), 1 - ) - min_max_flow_dates["projected"]["mid"]["max"]["flow"] = round( - statistics.mean(max_flow_value_array), 1 - ) - - ########## Calculate stats for the stats table. ########## + "projected": { + "extremes": {}, + "mid": {}, + }, + } - table_stats = { - "historical": {}, - "projected": { - "max": {}, - "mid": {}, - "min": {}, - }, - } + # Get historical data for min and max flow date. + min_max_flow_dates["historical"]["min"]["flow"] = stats["data"]["static"][ + "Maurer" + ]["historical"]["1976-2005"]["dl1"] + min_max_flow_dates["historical"]["min"]["date"] = stats["data"]["static"][ + "Maurer" + ]["historical"]["1976-2005"]["tl1"] + min_max_flow_dates["historical"]["max"]["flow"] = stats["data"]["static"][ + "Maurer" + ]["historical"]["1976-2005"]["dh1"] + min_max_flow_dates["historical"]["max"]["date"] = stats["data"]["static"][ + "Maurer" + ]["historical"]["1976-2005"]["th1"] + + max_flow_date_array = [] + max_flow_value_array = [] + min_flow_date_array = [] + min_flow_value_array = [] + + # Populate lists of values for max flow date for each projected model. + for era in chart_eras: + for model_stats in maurer_plus_delta_stats.values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in model_stats or era not in model_stats[scenario]: + continue + if era not in min_max_flow_dates["projected"]["extremes"]: + min_max_flow_dates["projected"]["extremes"][era] = { + "min": { + "flow": [], + "date": [], + }, + "max": { + "flow": [], + "date": [], + }, + } + + min_max_flow_dates["projected"]["extremes"][era]["min"][ + "flow" + ].append(model_stats[scenario][era]["dl1"]) + min_max_flow_dates["projected"]["extremes"][era]["min"][ + "date" + ].append(model_stats[scenario][era]["tl1"]) + min_max_flow_dates["projected"]["extremes"][era]["max"][ + "flow" + ].append(model_stats[scenario][era]["dh1"]) + min_max_flow_dates["projected"]["extremes"][era]["max"][ + "date" + ].append(model_stats[scenario][era]["th1"]) + if scenario == "rcp60": + min_flow_date_array.append(model_stats["rcp60"][era]["tl1"]) + min_flow_value_array.append(model_stats["rcp60"][era]["dl1"]) + max_flow_date_array.append(model_stats["rcp60"][era]["th1"]) + max_flow_value_array.append(model_stats["rcp60"][era]["dh1"]) + + # Take the median, not mean, of dates. To understand why, consider the mean + # of January 1st (doy: 1) and December 31st (doy: 366). The mean would be + # July 3rd (doy: 184) which is not representative of either date. + min_max_flow_dates["projected"]["mid"][era] = { + "min": { + "flow": None, + "date": None, + }, + "max": { + "flow": None, + "date": None, + }, + } + + if len(min_flow_date_array) > 0: + min_max_flow_dates["projected"]["mid"][era]["min"]["date"] = round( + statistics.median(min_flow_date_array), 1 + ) + min_max_flow_dates["projected"]["mid"][era]["min"]["flow"] = round( + statistics.mean(min_flow_value_array), 1 + ) + min_max_flow_dates["projected"]["mid"][era]["max"] = {} + min_max_flow_dates["projected"]["mid"][era]["max"]["date"] = round( + statistics.median(max_flow_date_array), 1 + ) + min_max_flow_dates["projected"]["mid"][era]["max"]["flow"] = round( + statistics.mean(max_flow_value_array), 1 + ) + + ########## Calculate stats for the stats table. ########## + + table_stats = { + "historical": {}, + "projected": {}, + } - table_stats["historical"] = model_stats["historical"] + table_stats["historical"] = stats["data"]["static"]["Maurer"]["historical"] - all_stats = {} - mid_stats = {} - for model_stats in stats["data"]["static"].values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - for era in model_stats[scenario].keys(): - if era not in model_stats[scenario]: - continue - for stat in model_stats[scenario][era].keys(): - if era not in all_stats: - all_stats[era] = {} - if stat not in all_stats[era]: - all_stats[era][stat] = [] - all_stats[era][stat].append(model_stats[scenario][era][stat]) - if scenario == "rcp60": - if era not in mid_stats: - mid_stats[era] = {} - if stat not in mid_stats[era]: - mid_stats[era][stat] = [] - mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) - - for era in all_stats.keys(): - for stat in all_stats[era].keys(): - all_values = all_stats[era][stat] - mid_values = mid_stats[era][stat] - if era not in table_stats["projected"]["min"]: - table_stats["projected"]["min"][era] = {} - if era not in table_stats["projected"]["max"]: - table_stats["projected"]["max"][era] = {} - if era not in table_stats["projected"]["mid"]: - table_stats["projected"]["mid"][era] = {} - table_stats["projected"]["min"][era][stat] = round(min(all_values), 3) - table_stats["projected"]["max"][era][stat] = round(max(all_values), 3) - table_stats["projected"]["mid"][era][stat] = round( - statistics.mean(mid_values), 3 - ) - - gauge_id = None - h8_outlet = False - huc8 = None + all_stats = {} + mid_stats = {} + for era in chart_eras: + for model_stats in maurer_plus_delta_stats.values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + if era not in model_stats[scenario]: + continue + for stat in model_stats[scenario][era].keys(): + if era not in all_stats: + all_stats[era] = {} + if stat not in all_stats[era]: + all_stats[era][stat] = [] + all_stats[era][stat].append(model_stats[scenario][era][stat]) + if scenario == "rcp60": + if era not in mid_stats: + mid_stats[era] = {} + if stat not in mid_stats[era]: + mid_stats[era][stat] = [] + mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) + + for era in all_stats.keys(): + if era not in table_stats["projected"]: + table_stats["projected"][era] = {} + for stat in all_stats[era].keys(): + all_values = all_stats[era][stat] + mid_values = mid_stats[era][stat] + if "min" not in table_stats["projected"][era]: + table_stats["projected"][era]["min"] = {} + if "max" not in table_stats["projected"][era]: + table_stats["projected"][era]["max"] = {} + if "mid" not in table_stats["projected"][era]: + table_stats["projected"][era]["mid"] = {} + table_stats["projected"][era]["min"][stat] = round(min(all_values), 3) + table_stats["projected"][era]["max"][stat] = round(max(all_values), 3) + table_stats["projected"][era]["mid"][stat] = round( + statistics.mean(mid_values), 3 + ) - gdf = asyncio.run(get_features(stream_id)) - if not isinstance(gdf, tuple): - if gdf.loc[0].h8_outlet == 1: - h8_outlet = True - if gdf.loc[0].GAUGE_ID != "NA": - gauge_id = gdf.loc[0].GAUGE_ID - huc8 = gdf.loc[0].huc8 - - response = { - "gauge_id": gauge_id, - "h8_outlet": h8_outlet, - "huc8": huc8, - "hydrograph": hydrograph, - "id": stats["id"], - "name": stats["name"], - "monthly_flow": monthly_flow, - "min_max_flow_dates": min_max_flow_dates, - "stats": table_stats, - "summary": stats["summary"], - } + gauge_id = None + h8_outlet = False + huc8 = None + + gdf = asyncio.run(get_features(stream_id)) + if not isinstance(gdf, tuple): + if gdf.loc[0].h8_outlet == 1: + h8_outlet = True + if gdf.loc[0].GAUGE_ID != "NA": + gauge_id = gdf.loc[0].GAUGE_ID + huc8 = gdf.loc[0].huc8 + + response = { + "gauge_id": gauge_id, + "h8_outlet": h8_outlet, + "huc8": huc8, + "hydrograph": hydrograph, + "id": stats["id"], + "name": stats["name"], + "monthly_flow": monthly_flow, + "min_max_flow_dates": min_max_flow_dates, + "stats": table_stats, + "summary": stats["summary"], + } - return jsonify(response) + return jsonify(response) - # except Exception as exc: - # return render_template("500/server_error.html"), 500 + except Exception as exc: + return render_template("500/server_error.html"), 500 From df03c1d9dfa9d66877a4b74f07ae25d45822ae1f Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Mon, 30 Mar 2026 18:10:41 -0800 Subject: [PATCH 6/7] Set hard limits on Maurer + delta stats, and other fixes. --- routes/conus_hydrology.py | 660 ++++++++++++++++++-------------------- 1 file changed, 309 insertions(+), 351 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index 31c42aac..0b6ab462 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1,5 +1,6 @@ import asyncio import io +from pprint import pprint import numpy as np import xarray as xr import ast @@ -1034,390 +1035,347 @@ def fetch_all_hydroviz_route(stream_id): if isinstance(response, tuple): return response - try: - # Fetch all needed data. - stats = stats_response.get_json() - climatology = climatology_response.get_json() - - # Convert static stats to "Maurer plus projected delta". - static_stats = stats["data"]["static"] - maurer_plus_delta_stats = {} - for model in static_stats.keys(): - if model not in maurer_plus_delta_stats: - maurer_plus_delta_stats[model] = {} - for scenario in static_stats[model].keys(): - if scenario in ["historical", "rcp26"]: - continue - if scenario not in maurer_plus_delta_stats[model]: - maurer_plus_delta_stats[model][scenario] = {} - for era in static_stats[model][scenario].keys(): - if era not in maurer_plus_delta_stats[model][scenario]: - maurer_plus_delta_stats[model][scenario][era] = {} - for stat in static_stats[model][scenario][era].keys(): - maurer_historical = static_stats["Maurer"]["historical"][ + # try: + # Fetch all needed data. + stats = stats_response.get_json() + climatology = climatology_response.get_json() + + # Convert static stats to "Maurer plus projected delta". + static_stats = stats["data"]["static"] + maurer_plus_delta_stats = {} + for model in static_stats.keys(): + if model not in maurer_plus_delta_stats: + maurer_plus_delta_stats[model] = {} + for scenario in static_stats[model].keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in maurer_plus_delta_stats[model]: + maurer_plus_delta_stats[model][scenario] = {} + for era in static_stats[model][scenario].keys(): + if era not in maurer_plus_delta_stats[model][scenario]: + maurer_plus_delta_stats[model][scenario][era] = {} + # print("##### KEYS #######") + # pprint(static_stats[model][scenario][era].keys()) + for stat in static_stats[model][scenario][era].keys(): + maurer_historical = static_stats["Maurer"]["historical"][ + "1976-2005" + ][stat] + modeled_historical = static_stats[model]["historical"]["1976-2005"][ + stat + ] + modeled_projected = static_stats[model][scenario][era][stat] + projected_delta = modeled_projected - modeled_historical + maurer_plus_delta = round(maurer_historical + projected_delta, 3) + + # These stats represent days per year, so cannot exceed 366. + if ( + stat + in [ + "dh15", + "dl16", + "lf1", + "ra8", + "spr_ord", + "sum_ord", + "th1", + "tl1", + ] + and maurer_plus_delta > 366 + ): + maurer_plus_delta = 366 + + # No stat can be negative except for ra3. + if stat != "ra3" and maurer_plus_delta < 0: + maurer_plus_delta = 0 + elif stat == "ra3" and maurer_plus_delta > 0: + maurer_plus_delta = 0 + + maurer_plus_delta_stats[model][scenario][era][ + stat + ] = maurer_plus_delta + + # pprint(maurer_plus_delta_stats) + + # Convert static climatology to "Maurer plus projected delta". + static_climatology = climatology["data"]["static"] + maurer_plus_delta_climatology = {} + for model in static_climatology.keys(): + if model not in maurer_plus_delta_climatology: + maurer_plus_delta_climatology[model] = {} + for scenario in static_climatology[model].keys(): + if scenario in ["historical", "rcp26"]: + continue + if scenario not in maurer_plus_delta_climatology[model]: + maurer_plus_delta_climatology[model][scenario] = {} + for era in static_climatology[model][scenario].keys(): + if era not in maurer_plus_delta_climatology[model][scenario]: + maurer_plus_delta_climatology[model][scenario][era] = [] + for i in range(len(static_climatology[model][scenario][era])): + doy_stats = {} + for stat in static_climatology[model][scenario][era][i].keys(): + maurer_historical = static_climatology["Maurer"]["historical"][ "1976-2005" - ][stat] - modeled_historical = static_stats[model]["historical"][ + ][i][stat] + modeled_historical = static_climatology[model]["historical"][ "1976-2005" - ][stat] - modeled_projected = static_stats[model][scenario][era][stat] + ][i][stat] + modeled_projected = static_climatology[model][scenario][era][i][ + stat + ] projected_delta = modeled_projected - modeled_historical maurer_plus_delta = round( maurer_historical + projected_delta, 3 ) - maurer_plus_delta_stats[model][scenario][era][ - stat - ] = maurer_plus_delta - - # Convert static climatology to "Maurer plus projected delta". - static_climatology = climatology["data"]["static"] - maurer_plus_delta_climatology = {} - for model in static_climatology.keys(): - if model not in maurer_plus_delta_climatology: - maurer_plus_delta_climatology[model] = {} - for scenario in static_climatology[model].keys(): - if scenario in ["historical", "rcp26"]: - continue - if scenario not in maurer_plus_delta_climatology[model]: - maurer_plus_delta_climatology[model][scenario] = {} - for era in static_climatology[model][scenario].keys(): - if era not in maurer_plus_delta_climatology[model][scenario]: - maurer_plus_delta_climatology[model][scenario][era] = [] - for i in range(len(static_climatology[model][scenario][era])): - doy_stats = {} - for stat in static_climatology[model][scenario][era][i].keys(): - maurer_historical = static_climatology["Maurer"][ - "historical" - ]["1976-2005"][i][stat] - modeled_historical = static_climatology[model][ - "historical" - ]["1976-2005"][i][stat] - modeled_projected = static_climatology[model][scenario][ - era - ][i][stat] - projected_delta = modeled_projected - modeled_historical - maurer_plus_delta = round( - maurer_historical + projected_delta, 3 - ) - doy_stats[stat] = maurer_plus_delta - maurer_plus_delta_climatology[model][scenario][era].append( - doy_stats - ) - - ########## Populate arrays for hydrograph. ########## + doy_stats[stat] = maurer_plus_delta + maurer_plus_delta_climatology[model][scenario][era].append( + doy_stats + ) - hydrograph = { - "historical": {}, - "projected": { - "extremes": {}, - "mid": {}, - }, - } + ########## Populate arrays for hydrograph. ########## - historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ - "1976-2005" - ] - hydrograph_historical = { - "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), - "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), - "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), - } - hydrograph["historical"] = hydrograph_historical + hydrograph = { + "historical": {}, + "projected": {}, + } - # Get min/mean/max values for each DOY across all projected models. - for era in chart_eras: + historical_climatology = climatology["data"]["static"]["Maurer"]["historical"][ + "1976-2005" + ] + hydrograph_historical = { + "doy_min": list(map(lambda x: x["doy_min"], historical_climatology)), + "doy_mean": list(map(lambda x: x["doy_mean"], historical_climatology)), + "doy_max": list(map(lambda x: x["doy_max"], historical_climatology)), + } + hydrograph["historical"] = hydrograph_historical + + # Get min/mean/max values for each DOY across all projected models. + for era in chart_eras: + if era not in hydrograph["projected"]: + hydrograph["projected"][era] = {} + for scenario in ["rcp45", "rcp60", "rcp85"]: + if scenario not in hydrograph["projected"][era]: + hydrograph["projected"][era][scenario] = { + "doy_min_min": [], + "doy_mean_min": [], + "doy_mean_mean": [], + "doy_mean_max": [], + "doy_max_max": [], + } for i in range(366): - doy_mins_all = [] - doy_means_all = [] - doy_maxes_all = [] - doy_mins_mid = [] - doy_means_mid = [] - doy_maxes_mid = [] + doy_mins = [] + doy_means = [] + doy_maxes = [] for model_dict in maurer_plus_delta_climatology.values(): - for scenario in model_dict.keys(): - if scenario in ["historical", "rcp26"]: - continue - doy_mins_all.append(model_dict[scenario][era][i]["doy_min"]) - doy_means_all.append(model_dict[scenario][era][i]["doy_mean"]) - doy_maxes_all.append(model_dict[scenario][era][i]["doy_max"]) - if scenario == "rcp60": - doy_mins_mid.append(model_dict[scenario][era][i]["doy_min"]) - doy_means_mid.append( - model_dict[scenario][era][i]["doy_mean"] - ) - doy_maxes_mid.append( - model_dict[scenario][era][i]["doy_max"] - ) - - if era not in hydrograph["projected"]["extremes"]: - hydrograph["projected"]["extremes"][era] = { - "doy_min": [], - "doy_mean": [], - "doy_max": [], - } - hydrograph["projected"]["extremes"][era]["doy_min"].append( - round(min(doy_mins_all), 3) - ) - hydrograph["projected"]["extremes"][era]["doy_mean"].append( - round(sum(doy_means_all) / len(doy_means_all), 3) + if scenario not in model_dict: + continue + doy_mins.append(model_dict[scenario][era][i]["doy_min"]) + doy_means.append(model_dict[scenario][era][i]["doy_mean"]) + doy_maxes.append(model_dict[scenario][era][i]["doy_max"]) + + hydrograph["projected"][era][scenario]["doy_min_min"].append( + round(min(doy_mins), 3) ) - hydrograph["projected"]["extremes"][era]["doy_max"].append( - round(max(doy_maxes_all), 3) + hydrograph["projected"][era][scenario]["doy_mean_min"].append( + round(min(doy_means), 3) ) - - if era not in hydrograph["projected"]["mid"]: - hydrograph["projected"]["mid"][era] = { - "doy_min": [], - "doy_mean": [], - "doy_max": [], - } - hydrograph["projected"]["mid"][era]["doy_min"].append( - round(min(doy_mins_mid), 3) + hydrograph["projected"][era][scenario]["doy_mean_mean"].append( + round(statistics.mean(doy_means), 3) ) - hydrograph["projected"]["mid"][era]["doy_mean"].append( - round(sum(doy_means_mid) / len(doy_means_mid), 3) + hydrograph["projected"][era][scenario]["doy_mean_max"].append( + round(max(doy_means), 3) ) - hydrograph["projected"]["mid"][era]["doy_max"].append( - round(max(doy_maxes_mid), 3) + hydrograph["projected"][era][scenario]["doy_max_max"].append( + round(max(doy_maxes), 3) ) - ########## Populate arrays for monthly modeled flow rate chart. ########## + ########## Populate arrays for monthly modeled flow rate chart. ########## - monthly_flow = { - "historical": {}, - "projected": { - "extremes": {}, - "mid": {}, - }, - } + monthly_flow = { + "historical": {}, + "projected": {}, + } + + monthly_flow_keys = [ + "ma12", + "ma13", + "ma14", + "ma15", + "ma16", + "ma17", + "ma18", + "ma19", + "ma20", + "ma21", + "ma22", + "ma23", + ] - monthly_flow_keys = [ - "ma12", - "ma13", - "ma14", - "ma15", - "ma16", - "ma17", - "ma18", - "ma19", - "ma20", - "ma21", - "ma22", - "ma23", - ] - - # Get historical data for each month. - for key in monthly_flow_keys: - monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ - "historical" - ]["1976-2005"][key] - - # Populate lists of values for each month for each projected model. - # These values will be used to generate box plots in the webapp. - for era in chart_eras: - if era not in monthly_flow["projected"]["mid"]: - monthly_flow["projected"]["mid"][era] = {} - if era not in monthly_flow["projected"]["extremes"]: - monthly_flow["projected"]["extremes"][era] = {} + # Get historical data for each month. + for key in monthly_flow_keys: + monthly_flow["historical"][key] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"][key] + + # Populate lists of values for each month for each projected model. + # These values will be used to generate box plots in the webapp. + for era in chart_eras: + if era not in monthly_flow["projected"]: + monthly_flow["projected"][era] = {} + for scenario in ["rcp45", "rcp60", "rcp85"]: + if scenario not in monthly_flow["projected"][era]: + monthly_flow["projected"][era][scenario] = {} for model_stats in maurer_plus_delta_stats.values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - if scenario not in model_stats or era not in model_stats[scenario]: - continue - for key in monthly_flow_keys: - if key not in monthly_flow["projected"]["extremes"][era]: - monthly_flow["projected"]["extremes"][era][key] = [] - monthly_flow["projected"]["extremes"][era][key].append( - model_stats[scenario][era][key] - ) - if scenario == "rcp60": - if era not in monthly_flow["projected"]["mid"]: - monthly_flow["projected"]["mid"][era] = {} - monthly_flow["projected"]["mid"][era][key] = model_stats[ - "rcp60" - ][era][key] - - ########## Populate arrays for max flow date chart. ########## - - min_max_flow_dates = { - "historical": { - "min": { - "flow": None, - "date": None, - }, - "max": { - "flow": None, - "date": None, - }, + # print("########### model_stats ############") + # pprint(model_stats) + if scenario not in model_stats: + continue + # print("##############################") + # pprint(model_stats[scenario][era]) + for key in monthly_flow_keys: + if key not in monthly_flow["projected"][era][scenario]: + monthly_flow["projected"][era][scenario][key] = [] + monthly_flow["projected"][era][scenario][key].append( + model_stats[scenario][era][key] + ) + + ########## Populate arrays for max flow date chart. ########## + + min_max_flow_dates = { + "historical": { + "min": { + "flow": None, + "date": None, }, - "projected": { - "extremes": {}, - "mid": {}, + "max": { + "flow": None, + "date": None, }, - } + }, + "projected": {}, + } - # Get historical data for min and max flow date. - min_max_flow_dates["historical"]["min"]["flow"] = stats["data"]["static"][ - "Maurer" - ]["historical"]["1976-2005"]["dl1"] - min_max_flow_dates["historical"]["min"]["date"] = stats["data"]["static"][ - "Maurer" - ]["historical"]["1976-2005"]["tl1"] - min_max_flow_dates["historical"]["max"]["flow"] = stats["data"]["static"][ - "Maurer" - ]["historical"]["1976-2005"]["dh1"] - min_max_flow_dates["historical"]["max"]["date"] = stats["data"]["static"][ - "Maurer" - ]["historical"]["1976-2005"]["th1"] - - max_flow_date_array = [] - max_flow_value_array = [] - min_flow_date_array = [] - min_flow_value_array = [] - - # Populate lists of values for max flow date for each projected model. - for era in chart_eras: - for model_stats in maurer_plus_delta_stats.values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - if scenario not in model_stats or era not in model_stats[scenario]: - continue - if era not in min_max_flow_dates["projected"]["extremes"]: - min_max_flow_dates["projected"]["extremes"][era] = { - "min": { - "flow": [], - "date": [], - }, - "max": { - "flow": [], - "date": [], - }, - } - - min_max_flow_dates["projected"]["extremes"][era]["min"][ - "flow" - ].append(model_stats[scenario][era]["dl1"]) - min_max_flow_dates["projected"]["extremes"][era]["min"][ - "date" - ].append(model_stats[scenario][era]["tl1"]) - min_max_flow_dates["projected"]["extremes"][era]["max"][ - "flow" - ].append(model_stats[scenario][era]["dh1"]) - min_max_flow_dates["projected"]["extremes"][era]["max"][ - "date" - ].append(model_stats[scenario][era]["th1"]) - if scenario == "rcp60": - min_flow_date_array.append(model_stats["rcp60"][era]["tl1"]) - min_flow_value_array.append(model_stats["rcp60"][era]["dl1"]) - max_flow_date_array.append(model_stats["rcp60"][era]["th1"]) - max_flow_value_array.append(model_stats["rcp60"][era]["dh1"]) - - # Take the median, not mean, of dates. To understand why, consider the mean - # of January 1st (doy: 1) and December 31st (doy: 366). The mean would be - # July 3rd (doy: 184) which is not representative of either date. - min_max_flow_dates["projected"]["mid"][era] = { + # Get historical data for min and max flow date. + min_max_flow_dates["historical"]["min"]["flow"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["dl1"] + min_max_flow_dates["historical"]["min"]["date"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["tl1"] + min_max_flow_dates["historical"]["max"]["flow"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["dh1"] + min_max_flow_dates["historical"]["max"]["date"] = stats["data"]["static"]["Maurer"][ + "historical" + ]["1976-2005"]["th1"] + + # Populate lists of values for max flow date for each projected model. + for era in chart_eras: + if era not in min_max_flow_dates["projected"]: + min_max_flow_dates["projected"][era] = {} + for scenario in ["rcp45", "rcp60", "rcp85"]: + if scenario not in min_max_flow_dates["projected"][era]: + min_max_flow_dates["projected"][era][scenario] = { "min": { - "flow": None, - "date": None, + "flow": [], + "date": [], }, "max": { - "flow": None, - "date": None, + "flow": [], + "date": [], }, } + for model_stats in maurer_plus_delta_stats.values(): + if scenario not in model_stats: + continue - if len(min_flow_date_array) > 0: - min_max_flow_dates["projected"]["mid"][era]["min"]["date"] = round( - statistics.median(min_flow_date_array), 1 - ) - min_max_flow_dates["projected"]["mid"][era]["min"]["flow"] = round( - statistics.mean(min_flow_value_array), 1 - ) - min_max_flow_dates["projected"]["mid"][era]["max"] = {} - min_max_flow_dates["projected"]["mid"][era]["max"]["date"] = round( - statistics.median(max_flow_date_array), 1 - ) - min_max_flow_dates["projected"]["mid"][era]["max"]["flow"] = round( - statistics.mean(max_flow_value_array), 1 - ) + min_max_flow_dates["projected"][era][scenario]["min"]["flow"].append( + model_stats[scenario][era]["dl1"] + ) + min_max_flow_dates["projected"][era][scenario]["min"]["date"].append( + model_stats[scenario][era]["tl1"] + ) + min_max_flow_dates["projected"][era][scenario]["max"]["flow"].append( + model_stats[scenario][era]["dh1"] + ) + min_max_flow_dates["projected"][era][scenario]["max"]["date"].append( + model_stats[scenario][era]["th1"] + ) - ########## Calculate stats for the stats table. ########## + ########## Calculate stats for the stats table. ########## - table_stats = { - "historical": {}, - "projected": {}, - } + table_stats = { + "historical": {}, + "projected": {}, + } - table_stats["historical"] = stats["data"]["static"]["Maurer"]["historical"] + table_stats["historical"] = stats["data"]["static"]["Maurer"]["historical"] - all_stats = {} - mid_stats = {} - for era in chart_eras: - for model_stats in maurer_plus_delta_stats.values(): - for scenario in model_stats.keys(): - if scenario in ["historical", "rcp26"]: - continue - if era not in model_stats[scenario]: - continue - for stat in model_stats[scenario][era].keys(): - if era not in all_stats: - all_stats[era] = {} - if stat not in all_stats[era]: - all_stats[era][stat] = [] - all_stats[era][stat].append(model_stats[scenario][era][stat]) - if scenario == "rcp60": - if era not in mid_stats: - mid_stats[era] = {} - if stat not in mid_stats[era]: - mid_stats[era][stat] = [] - mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) - - for era in all_stats.keys(): - if era not in table_stats["projected"]: - table_stats["projected"][era] = {} - for stat in all_stats[era].keys(): - all_values = all_stats[era][stat] - mid_values = mid_stats[era][stat] - if "min" not in table_stats["projected"][era]: - table_stats["projected"][era]["min"] = {} - if "max" not in table_stats["projected"][era]: - table_stats["projected"][era]["max"] = {} - if "mid" not in table_stats["projected"][era]: - table_stats["projected"][era]["mid"] = {} - table_stats["projected"][era]["min"][stat] = round(min(all_values), 3) - table_stats["projected"][era]["max"][stat] = round(max(all_values), 3) - table_stats["projected"][era]["mid"][stat] = round( - statistics.mean(mid_values), 3 - ) + all_stats = {} + mid_stats = {} + for era in chart_eras: + for model_stats in maurer_plus_delta_stats.values(): + for scenario in model_stats.keys(): + if scenario in ["historical", "rcp26"]: + continue + if era not in model_stats[scenario]: + continue + for stat in model_stats[scenario][era].keys(): + if era not in all_stats: + all_stats[era] = {} + if stat not in all_stats[era]: + all_stats[era][stat] = [] + all_stats[era][stat].append(model_stats[scenario][era][stat]) + if scenario == "rcp60": + if era not in mid_stats: + mid_stats[era] = {} + if stat not in mid_stats[era]: + mid_stats[era][stat] = [] + mid_stats[era][stat].append(model_stats["rcp60"][era][stat]) + + for era in all_stats.keys(): + if era not in table_stats["projected"]: + table_stats["projected"][era] = {} + for stat in all_stats[era].keys(): + all_values = all_stats[era][stat] + mid_values = mid_stats[era][stat] + if "min" not in table_stats["projected"][era]: + table_stats["projected"][era]["min"] = {} + if "max" not in table_stats["projected"][era]: + table_stats["projected"][era]["max"] = {} + if "mid" not in table_stats["projected"][era]: + table_stats["projected"][era]["mid"] = {} + table_stats["projected"][era]["min"][stat] = round(min(all_values), 3) + table_stats["projected"][era]["max"][stat] = round(max(all_values), 3) + table_stats["projected"][era]["mid"][stat] = round( + statistics.mean(mid_values), 3 + ) + + gauge_id = None + h8_outlet = False + huc8 = None - gauge_id = None - h8_outlet = False - huc8 = None - - gdf = asyncio.run(get_features(stream_id)) - if not isinstance(gdf, tuple): - if gdf.loc[0].h8_outlet == 1: - h8_outlet = True - if gdf.loc[0].GAUGE_ID != "NA": - gauge_id = gdf.loc[0].GAUGE_ID - huc8 = gdf.loc[0].huc8 - - response = { - "gauge_id": gauge_id, - "h8_outlet": h8_outlet, - "huc8": huc8, - "hydrograph": hydrograph, - "id": stats["id"], - "name": stats["name"], - "monthly_flow": monthly_flow, - "min_max_flow_dates": min_max_flow_dates, - "stats": table_stats, - "summary": stats["summary"], - } + gdf = asyncio.run(get_features(stream_id)) + if not isinstance(gdf, tuple): + if gdf.loc[0].h8_outlet == 1: + h8_outlet = True + if gdf.loc[0].GAUGE_ID != "NA": + gauge_id = gdf.loc[0].GAUGE_ID + huc8 = gdf.loc[0].huc8 + + response = { + "gauge_id": gauge_id, + "h8_outlet": h8_outlet, + "huc8": huc8, + "hydrograph": hydrograph, + "id": stats["id"], + "name": stats["name"], + "monthly_flow": monthly_flow, + "min_max_flow_dates": min_max_flow_dates, + "stats": table_stats, + "summary": stats["summary"], + } - return jsonify(response) + return jsonify(response) - except Exception as exc: - return render_template("500/server_error.html"), 500 + # except Exception as exc: + # return render_template("500/server_error.html"), 500 From 899ede56a4302fc48b30ee8d2b8d42c709ee68e4 Mon Sep 17 00:00:00 2001 From: Craig Stephenson Date: Thu, 2 Apr 2026 10:58:47 -0800 Subject: [PATCH 7/7] Revert back to raw CMIP5 data, not Maurer + delta method. --- routes/conus_hydrology.py | 59 ++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/routes/conus_hydrology.py b/routes/conus_hydrology.py index 0b6ab462..d245b60b 100644 --- a/routes/conus_hydrology.py +++ b/routes/conus_hydrology.py @@ -1067,32 +1067,37 @@ def fetch_all_hydroviz_route(stream_id): projected_delta = modeled_projected - modeled_historical maurer_plus_delta = round(maurer_historical + projected_delta, 3) - # These stats represent days per year, so cannot exceed 366. - if ( - stat - in [ - "dh15", - "dl16", - "lf1", - "ra8", - "spr_ord", - "sum_ord", - "th1", - "tl1", - ] - and maurer_plus_delta > 366 - ): - maurer_plus_delta = 366 - - # No stat can be negative except for ra3. - if stat != "ra3" and maurer_plus_delta < 0: - maurer_plus_delta = 0 - elif stat == "ra3" and maurer_plus_delta > 0: - maurer_plus_delta = 0 - + ### Temporary fallback to raw projected data, not Maurer + delta method. + ### TODO: Figure out how to make the Maurer + delta method work without unrealistic values. + # # These stats represent days per year, so cannot exceed 366. + # if ( + # stat + # in [ + # "dh15", + # "dl16", + # "lf1", + # "ra8", + # "spr_ord", + # "sum_ord", + # "th1", + # "tl1", + # ] + # and maurer_plus_delta > 366 + # ): + # maurer_plus_delta = 366 + + # # No stat can be negative except for ra3. + # if stat != "ra3" and maurer_plus_delta < 0: + # maurer_plus_delta = 0 + # elif stat == "ra3" and maurer_plus_delta > 0: + # maurer_plus_delta = 0 + + # maurer_plus_delta_stats[model][scenario][era][ + # stat + # ] = maurer_plus_delta maurer_plus_delta_stats[model][scenario][era][ stat - ] = maurer_plus_delta + ] = modeled_projected # pprint(maurer_plus_delta_stats) @@ -1126,7 +1131,11 @@ def fetch_all_hydroviz_route(stream_id): maurer_plus_delta = round( maurer_historical + projected_delta, 3 ) - doy_stats[stat] = maurer_plus_delta + + ### Temporary fallback to raw projected data, not Maurer + delta method. + ### TODO: Figure out how to make the Maurer + delta method work without unrealistic values. + # doy_stats[stat] = maurer_plus_delta + doy_stats[stat] = modeled_projected maurer_plus_delta_climatology[model][scenario][era].append( doy_stats )