From 6d68db0906a695f9eec5fffc01b8ec4458c8d28b Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 30 May 2025 18:20:55 -0400 Subject: [PATCH] feat(charts): add series validation --- README.md | 3 ++ scripts/lagged_oil_unrate_chart_styled.py | 45 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c0a33b..1cb6c0e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,9 @@ Downloads UNRATE and WTI series (from 1948 to today) into `data/fred.db`. ./startup.sh --offset 6 --start 1980-01-01 --end 2025-05-31 --extend-years 2 ``` +The plotting script validates that both series contain data for the +requested period before rendering the chart. + | Option | Description | | ---------------- | ----------------------------------------------- | | `--offset` | Lag in months (pos = oil leads; neg = oil lags) | diff --git a/scripts/lagged_oil_unrate_chart_styled.py b/scripts/lagged_oil_unrate_chart_styled.py index 1050046..1369480 100644 --- a/scripts/lagged_oil_unrate_chart_styled.py +++ b/scripts/lagged_oil_unrate_chart_styled.py @@ -2,8 +2,10 @@ """ lagged_oil_unrate_chart_styled.py --------------------------------- -Offline version: reads UNRATE & WTI series from data/fred.db (populated -by scripts/refresh_data.py) and plots them with a configurable lag. +Offline version: reads UNRATE & WTI series from ``data/fred.db`` +(populated by ``scripts/refresh_data.py``), validates that both series +contain data for the requested period, and plots them with a configurable +lag. Usage ----- @@ -73,6 +75,44 @@ def fetch_series( return unrate, oil_monthly +def validate_series(unrate: pd.DataFrame, oil: pd.DataFrame) -> None: + """Sanity-check the fetched series before plotting. + + Both dataframes must contain a ``value`` column with no missing values and + share a non-empty ``DatetimeIndex`` covering the same start and end dates. + + Parameters + ---------- + unrate, oil: + DataFrames returned by :func:`fetch_series`. + + Raises + ------ + ValueError + If either dataframe is empty, missing the ``value`` column, contains + nulls, or the date indexes do not span the same range. + """ + + if unrate.empty: + raise ValueError("UNRATE dataframe is empty") + if oil.empty: + raise ValueError("DCOILWTICO dataframe is empty") + + for name, df in [("UNRATE", unrate), ("DCOILWTICO", oil)]: + if "value" not in df.columns: + raise ValueError(f"{name} missing 'value' column") + if df["value"].isna().any(): + raise ValueError(f"{name} contains null values") + + if not isinstance(unrate.index, pd.DatetimeIndex) or not isinstance( + oil.index, pd.DatetimeIndex + ): + raise ValueError("Indexes must be DatetimeIndex") + + if unrate.index.min() != oil.index.min() or unrate.index.max() != oil.index.max(): + raise ValueError("Series indexes do not span the same date range") + + # ────────────────────────────────────────────────────────────────────────── # Plotting # ────────────────────────────────────────────────────────────────────────── @@ -212,6 +252,7 @@ def main() -> None: end_dt = datetime.fromisoformat(args.end) unrate, oil = fetch_series(start_dt, end_dt, args.db) + validate_series(unrate, oil) plot_lagged(unrate, oil, args.offset, start_dt, end_dt, args.extend_years)