diff --git a/config.py b/config.py index 9edb6a6..2482292 100644 --- a/config.py +++ b/config.py @@ -414,7 +414,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.802" +SYSTEM_VERSION = "V10.803" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/routes/sales_routes.py b/routes/sales_routes.py index 62ecc5e..ecb52e5 100644 --- a/routes/sales_routes.py +++ b/routes/sales_routes.py @@ -520,6 +520,16 @@ def _filter_growth_payload(chart_data, kpi, start_month=None, end_month=None): return filtered, filtered_kpi, analysis_period, True +def _sorted_valid_month_labels(values): + """Normalize mixed pandas month values before chart-axis sorting.""" + labels = set() + for value in values: + parsed = parse_month(value) + if parsed: + labels.add(parsed.strftime('%Y-%m')) + return sorted(labels) + + def _build_growth_kpi(kpi_row): if not kpi_row: return None @@ -1889,7 +1899,7 @@ def sales_analysis(): # Y軸: 分類 (使用 top_cats_season 的索引) # 取得所有月份並排序 - all_months_sorted = sorted(target_df['_month_str'].unique()) + all_months_sorted = _sorted_valid_month_labels(target_df['_month_str'].unique()) month_map = {m: i for i, m in enumerate(all_months_sorted)} cat_map = {c: i for i, c in enumerate(top_cats_season)} diff --git a/tests/test_analysis_period_linkage.py b/tests/test_analysis_period_linkage.py index 8bf588a..8e916fa 100644 --- a/tests/test_analysis_period_linkage.py +++ b/tests/test_analysis_period_linkage.py @@ -14,7 +14,7 @@ from routes.monthly_routes import ( _resolve_monthly_period, get_monthly_summary_data, ) -from routes.sales_routes import _filter_growth_payload +from routes.sales_routes import _filter_growth_payload, _sorted_valid_month_labels from services.analysis_period_service import make_analysis_period, make_month_period @@ -250,6 +250,17 @@ def test_growth_period_slices_every_series_and_recomputes_period_kpis(): assert period["end_month"] == "2026-05" +def test_sales_seasonality_month_axis_ignores_nan_and_invalid_values(): + assert _sorted_valid_month_labels([ + "2026-04", + float("nan"), + None, + "invalid", + "2025-12", + "2026-04-30", + ]) == ["2025-12", "2026-04"] + + def test_monthly_frontend_has_replayable_period_and_no_fixed_years(): script = (ROOT / "web/static/js/page-monthly-summary.js").read_text(encoding="utf-8") route = (ROOT / "routes/monthly_routes.py").read_text(encoding="utf-8")