From 34bd007f12958651520c71ce2c8bbbd6e659a848 Mon Sep 17 00:00:00 2001 From: Nitin Magima Date: Tue, 20 Jan 2026 12:57:55 -0500 Subject: [PATCH 1/3] Added Start Year Control --- fbfmaproom/fbflayout.py | 8 ++++++ fbfmaproom/fbfmaproom.py | 57 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/fbfmaproom/fbflayout.py b/fbfmaproom/fbflayout.py index 1ef38c64..997c4730 100644 --- a/fbfmaproom/fbflayout.py +++ b/fbfmaproom/fbflayout.py @@ -345,6 +345,14 @@ def table_layout(): ), width='100px', ), + control( + "Start year", + "The starting year for the table data. Only years from this year onwards will be displayed.", + dcc.Dropdown( + id="start_year", + clearable=False, + ), + ), ], id="table_controls", style={"font-family": "Arial, Helvetica, sans-serif"}, diff --git a/fbfmaproom/fbfmaproom.py b/fbfmaproom/fbfmaproom.py index 5e35b6dc..ee9d6746 100644 --- a/fbfmaproom/fbfmaproom.py +++ b/fbfmaproom/fbfmaproom.py @@ -383,11 +383,12 @@ def generate_tables( mode, geom_key, final_season, + start_year, ): basic_ds = fundamental_table_data( country_key, table_columns, season_config, issue_month0, - freq, mode, geom_key + freq, mode, geom_key, start_year ) if "pct" in basic_ds.coords: basic_ds = basic_ds.drop_vars("pct") @@ -507,8 +508,8 @@ def select_obs(country_key, obs_keys, target_month0, target_year=None): def fundamental_table_data(country_key, table_columns, season_config, issue_month0, freq, mode, - geom_key): - year_min = season_config["start_year"] + geom_key, start_year): + year_min = start_year season_length = season_config["length"] target_month0 = season_config["target_month"] @@ -1017,6 +1018,47 @@ def forecast_selectors(season, col_name, pathname, qstring): ) +@APP.callback( + Output("start_year", "options"), + Output("start_year", "value"), + Input("season", "value"), + Input("location", "pathname"), + State("location", "search"), +) +def start_year_selector(season, pathname, qstring): + try: + if season is None: + raise ValueError("Season is None") + country_key = country(pathname) + country_conf = CONFIG["countries"][country_key] + season_conf = country_conf["seasons"][season] + + year_min = season_conf["start_year"] + year_max = datetime.datetime.now().year + year_range = range(year_min, year_max + 1) + + start_year_options = [ + dict(label=str(year), value=year) + for year in year_range + ] + + start_year_value = parse_arg( + "start_year", + conversion=int, + default=year_min, + qstring=qstring + ) + except Exception: + traceback.print_exc() + start_year_options = dash.no_update + start_year_value = dash.no_update + + return ( + start_year_options, + start_year_value, + ) + + @APP.callback( Output("marker", "position"), Input("location", "pathname"), @@ -1115,9 +1157,10 @@ def update_popup(pathname, position, mode): Input("predictand", "value"), Input("predictors", "value"), Input("include_upcoming", "value"), + Input("start_year", "value"), State("season", "value"), ) -def table_cb(issue_month_abbrev, freq, mode, geom_key, pathname, severity, predictand_key, predictor_keys, include_upcoming, season_id): +def table_cb(issue_month_abbrev, freq, mode, geom_key, pathname, severity, predictand_key, predictor_keys, include_upcoming, start_year, season_id): country_key = country(pathname) config = CONFIG["countries"][country_key] season_config = config["seasons"][season_id] @@ -1158,6 +1201,7 @@ def table_cb(issue_month_abbrev, freq, mode, geom_key, pathname, severity, predi mode, geom_key, final_season, + start_year, ) summary_presentation_df = format_summary_table( summary_df, tcs, thresholds, @@ -1569,6 +1613,7 @@ def export_endpoint(country_key): include_upcoming = parse_arg( "include_upcoming", pingrid.boolean, default=False ) + start_year = parse_arg("start_year", int, default=None) if issue_month is None: if issue_month0 is None: @@ -1602,6 +1647,9 @@ def export_endpoint(country_key): seasons = ' '.join(config["seasons"].keys()) raise InvalidRequestError(f"Unknown season {season_id}. Valid values are: {seasons}") + if start_year is None: + start_year = season_config["start_year"] + target_month0 = season_config["target_month"] cols = table_columns( @@ -1635,6 +1683,7 @@ def export_endpoint(country_key): mode, geom_key, final_season, + start_year, ) main_df['year'] = main_df['time'].apply(lambda x: x.year) From 8b366dba036dcd239ee95e50e89f304d32c37640 Mon Sep 17 00:00:00 2001 From: Nitin Magima Date: Tue, 20 Jan 2026 13:10:03 -0500 Subject: [PATCH 2/3] fbfmaproom: validate season/col_name and issue_month in callbacks - forecast_selectors: raise ValueError if season or col_name is None - tile_url_callback: also check issue_month_abbrev before PreventUpdate --- fbfmaproom/fbfmaproom.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fbfmaproom/fbfmaproom.py b/fbfmaproom/fbfmaproom.py index ee9d6746..4e55181e 100644 --- a/fbfmaproom/fbfmaproom.py +++ b/fbfmaproom/fbfmaproom.py @@ -960,6 +960,8 @@ def custom_static(relpath): ) def forecast_selectors(season, col_name, pathname, qstring): try: + if season is None or col_name is None: + raise ValueError("Season or column name is None") country_key = country(pathname) country_conf = CONFIG["countries"][country_key] season_conf = country_conf["seasons"][season] @@ -1245,7 +1247,7 @@ def update_severity_color(value): ) def tile_url_callback(target_year, issue_month_abbrev, freq, pathname, map_col_key, season_id): colorscale = None # default value in case an exception is raised - if season_id is None: + if season_id is None or issue_month_abbrev is None: raise PreventUpdate try: country_key = country(pathname) From 79630c727e56b564bf18b00ce2c168c1a807f225 Mon Sep 17 00:00:00 2001 From: Nitin Magima Date: Tue, 20 Jan 2026 17:36:46 -0500 Subject: [PATCH 3/3] remove callback --- fbfmaproom/fbfmaproom.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fbfmaproom/fbfmaproom.py b/fbfmaproom/fbfmaproom.py index 4e55181e..b57370fd 100644 --- a/fbfmaproom/fbfmaproom.py +++ b/fbfmaproom/fbfmaproom.py @@ -960,8 +960,6 @@ def custom_static(relpath): ) def forecast_selectors(season, col_name, pathname, qstring): try: - if season is None or col_name is None: - raise ValueError("Season or column name is None") country_key = country(pathname) country_conf = CONFIG["countries"][country_key] season_conf = country_conf["seasons"][season] @@ -1029,8 +1027,6 @@ def forecast_selectors(season, col_name, pathname, qstring): ) def start_year_selector(season, pathname, qstring): try: - if season is None: - raise ValueError("Season is None") country_key = country(pathname) country_conf = CONFIG["countries"][country_key] season_conf = country_conf["seasons"][season] @@ -1247,7 +1243,7 @@ def update_severity_color(value): ) def tile_url_callback(target_year, issue_month_abbrev, freq, pathname, map_col_key, season_id): colorscale = None # default value in case an exception is raised - if season_id is None or issue_month_abbrev is None: + if season_id is None: raise PreventUpdate try: country_key = country(pathname) @@ -1304,10 +1300,12 @@ def borders(pathname, mode): """ function ( mode, map_column, season, predictors, predictand, year, issue_month, + start_year, freq, severity, include_upcoming, position, show_modal ) { args = { mode, map_column, season, predictors, predictand, year, issue_month, + start_year, freq, severity, include_upcoming, position, show_modal } // Don't include undefined values in the querystring, otherwise @@ -1332,6 +1330,7 @@ def borders(pathname, mode): Input("predictand", "value"), Input("year", "value"), Input("issue_month", "value"), + Input("start_year", "value"), Input("freq", "value"), Input("severity", "value"), Input("include_upcoming", "value"),