"""Wykrywanie wiersza nagłówka w arkuszu. Pliki Excela w tej domenie nie mają nagłówka zawsze w pierwszym wierszu — bywają puste wiersze, tytuły, metadane. Ta heurystyka skanuje pierwsze N wierszy i wybiera ten, który "wygląda jak nagłówek": dużo niepustych, tekstowych, unikalnych komórek, po którym następują wiersze danych o podobnym wypełnieniu. Wynik (indeks wiersza nagłówka) jest CACHE'OWANY per plik (schema_cache), więc ten kosztowny skan robimy raz na wersję pliku. """ from __future__ import annotations import pandas as pd def _row_score(raw: pd.DataFrame, i: int) -> float: row = raw.iloc[i] non_null = row.notna() filled = float(non_null.mean()) if len(row) else 0.0 if filled == 0: return -1.0 values = [str(v).strip() for v in row[non_null].tolist()] text_like = sum(1 for v in values if v and not _looks_numeric(v)) text_ratio = text_like / max(len(values), 1) uniqueness = len(set(values)) / max(len(values), 1) # Wiersze danych pod spodem powinny mieć podobną liczbę wypełnionych kolumn. follow_bonus = 0.0 if i + 1 < len(raw): below = raw.iloc[i + 1].notna().mean() follow_bonus = 1.0 - abs(filled - float(below)) return filled * 0.4 + text_ratio * 0.3 + uniqueness * 0.2 + follow_bonus * 0.1 def _looks_numeric(v: str) -> bool: try: float(v.replace(",", ".")) return True except ValueError: return False def detect_header_row(raw: pd.DataFrame, max_scan: int = 15) -> int: """Zwraca indeks (0-based) wiersza, który najprawdopodobniej jest nagłówkiem.""" best_idx, best_score = 0, float("-inf") for i in range(min(max_scan, len(raw))): score = _row_score(raw, i) if score > best_score: best_idx, best_score = i, score return best_idx