Programming

6. add

import re
from functools import lru_cache


def _file_range(path):
    """
    Rough period range a file covers, from its name. Used only to skip files
    we definitely don't need — rows are filtered exactly after reading, so a
    range that is too WIDE is harmless and too NARROW loses data. When in
    doubt, return None and the file gets read.

        history_2023.parquet     -> whole of 2023
        history_10.2026.parquet  -> October 2026
    """
    s = path.stem
    years = re.findall(r"20\d{2}", s)
    if not years:
        return None
    year = int(years[-1])
    months = [int(m) for m in re.findall(r"\d{1,2}", re.sub(r"20\d{2}", " ", s))
              if 1 <= int(m) <= 12]
    if months:
        p = year * 100 + months[0]
        return p - 2, p + 2          # pad: the filename may be the stamp, not the period
    return (year - 1) * 100 + 11, (year + 1) * 100 + 1


def read_months(data_dir, period_from=None, period_to=None):
    files = sorted(Path(data_dir).glob("*.parquet"))
    if not files:
        raise FileNotFoundError(f"нет parquet-файлов в {data_dir}")

    chosen = []
    for f in files:
        rng = _file_range(f)
        if rng is None:
            chosen.append(f); continue
        lo, hi = rng
        if period_from and hi < int(period_from):
            continue
        if period_to and lo > int(period_to):
            continue
        chosen.append(f)
    if not chosen:
        raise ValueError(f"нет файлов за период {period_from}–{period_to}")

    df = normalise(pd.concat([pd.read_parquet(f) for f in chosen], ignore_index=True))

    # Exact row-level filter — a yearly file carries months outside the window.
    per = ((df["report_date"] - pd.Timedelta(days=1)).dt.year * 100
           + (df["report_date"] - pd.Timedelta(days=1)).dt.month)
    if period_from:
        df = df[per >= int(period_from)]
    if period_to:
        df = df[per <= int(period_to)]
    if df.empty:
        raise ValueError(f"нет данных за период {period_from}–{period_to}")
    return df


@lru_cache(maxsize=8)
def available_periods(data_dir):
    """
    For the dropdown. Reads only the report_date column, so a yearly file
    costs almost nothing. Cached — clear it after uploading a new file.
    """
    out = set()
    for f in sorted(Path(str(data_dir)).glob("*.parquet")):
        try:
            s = pd.read_parquet(f, columns=["report_date"])["report_date"]
        except Exception:
            continue
        a = pd.to_datetime(s, errors="coerce").dropna() - pd.Timedelta(days=1)
        out |= set((a.dt.year * 100 + a.dt.month).unique().tolist())
    return sorted(int(p) for p in out)
Foydali bo'ldimi? Yoqmadi 0 Baholash uchun kiring