Source code for districtheatingsim.utilities.csv_schemas

"""
Column contracts for app-owned CSV files (BACKLOG D4 step 4).
=============================================================

CSV files here are user- and Excel-editable, so — unlike the JSON artifacts in
:mod:`districtheatingsim.utilities.schema` — the **column header is the contract**;
a version field would break pandas/Excel interop. Instead we centralize the
required columns per file *kind* and validate on load, turning a ``KeyError`` raised
deep inside the maths (``data["VLT_max"]`` …) into one clear message naming every
missing column up front. Generalizes the ad-hoc ``UTM_X``/``UTM_Y`` checks.

GUI-free and pandas-free at import (the DataFrame is duck-typed via ``.columns``).

:author: Dipl.-Ing. (FH) Jonas Pfeiffer
"""

#: Required (and known-optional) columns per CSV kind. ``optional`` is documentation
#: only — it lists columns the readers use when present but tolerate when absent.
CSV_SCHEMAS: dict[str, dict[str, tuple[str, ...]]] = {
    # The building / "Quartier" CSV consumed by the heat-demand profile calculation.
    "building": {
        "required": (
            "Wärmebedarf",
            "Gebäudetyp",
            "Subtyp",
            "WW_Anteil",
            "Normaußentemperatur",
            "VLT_max",
            "RLT_max",
            "Steigung_Heizkurve",
        ),
        "optional": ("Heizgrenztemperatur", "Heizexponent", "P_max"),
    },
    # Any layer that needs projected coordinates (e.g. building CSV → network layers).
    "coordinates": {
        "required": ("UTM_X", "UTM_Y"),
        "optional": (),
    },
}


[docs] def required_columns(kind: str) -> tuple[str, ...]: """Return the required column names for a CSV ``kind``.""" return CSV_SCHEMAS[kind]["required"]
[docs] def validate_csv_columns(df, kind: str) -> None: """ Raise ``KeyError`` if ``df`` is missing any required column for ``kind``. :param df: A loaded table (duck-typed: needs a ``.columns`` collection). :param kind: A key registered in :data:`CSV_SCHEMAS`. :raises KeyError: Naming every missing required column (and listing the present ones), so a renamed/missing header is a clear up-front error rather than an opaque failure deep in the calculation. """ required = CSV_SCHEMAS[kind]["required"] columns = list(df.columns) missing = [c for c in required if c not in columns] if missing: raise KeyError(f"CSV is missing required column(s) for kind '{kind}': {missing}. Present columns: {columns}")