#!/usr/bin/env python3 from __future__ import annotations import json import math import os import sys from collections import OrderedDict from pathlib import Path from typing import Any import joblib import pandas as pd from huggingface_hub import HfApi, hf_hub_download REPO_ID = "gui-sparim/repositorio_mesa" OUTPUT_XLSX = "repositorio_mesa_colunas_dai.xlsx" SUMMARY_JSON = "repositorio_mesa_colunas_dai_summary.json" def normalize_value(value: Any) -> str | None: if value is None: return None if isinstance(value, float) and math.isnan(value): return None try: if pd.isna(value): return None except Exception: # noqa: BLE001 pass if isinstance(value, bytes): value = value.decode("utf-8", errors="replace") if isinstance(value, (dict, list, tuple, set)): value = json.dumps(value, ensure_ascii=False) text = str(value).strip() return text or None def first_nonblank_samples(frame: pd.DataFrame, column: str, limit: int = 5) -> list[str]: samples: list[str] = [] for value in frame[column].tolist(): normalized = normalize_value(value) if normalized is not None: samples.append(normalized) if len(samples) >= limit: break return samples def walk_dataframes(node: Any, path: str = "root", seen: set[int] | None = None) -> list[tuple[str, pd.DataFrame]]: if seen is None: seen = set() node_id = id(node) if node_id in seen: return [] seen.add(node_id) results: list[tuple[str, pd.DataFrame]] = [] if isinstance(node, pd.DataFrame): return [(path, node)] if isinstance(node, dict): for key, value in node.items(): results.extend(walk_dataframes(value, f"{path}.{key}", seen)) return results if isinstance(node, (list, tuple)): for index, value in enumerate(node): results.extend(walk_dataframes(value, f"{path}[{index}]", seen)) return results attrs = getattr(node, "__dict__", None) if isinstance(attrs, dict): for key, value in attrs.items(): results.extend(walk_dataframes(value, f"{path}.{key}", seen)) return results def main() -> None: token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN") if not token: raise SystemExit("HF_TOKEN or HUGGINGFACE_HUB_TOKEN is required.") os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1") api = HfApi(token=token) tree = list(api.list_repo_tree(REPO_ID, repo_type="dataset", recursive=True, expand=False)) dai_files = sorted( item.path for item in tree if getattr(item, "type", "file") == "file" and item.path.lower().endswith(".dai") ) ordered_columns: OrderedDict[str, list[str]] = OrderedDict() sources: OrderedDict[str, dict[str, str]] = OrderedDict() dataframe_rows: list[dict[str, Any]] = [] scanned_files: list[str] = [] skipped_files: list[dict[str, str]] = [] for dai_path in dai_files: print(f"Scanning {dai_path}...", file=sys.stderr) try: local_path = hf_hub_download( repo_id=REPO_ID, repo_type="dataset", filename=dai_path, token=token, ) obj = joblib.load(local_path) dataframes = walk_dataframes(obj) scanned_files.append(dai_path) for dataframe_path, frame in dataframes: dataframe_rows.append( { "dai_file": dai_path, "dataframe_path": dataframe_path, "row_count": int(frame.shape[0]), "column_count": int(frame.shape[1]), } ) for column in frame.columns: if column in ordered_columns: continue samples = first_nonblank_samples(frame, column) ordered_columns[column] = samples sources[column] = { "dai_file": dai_path, "dataframe_path": dataframe_path, } except Exception as exc: # noqa: BLE001 skipped_files.append({"dai_file": dai_path, "error": str(exc)}) print(f"Skipped {dai_path}: {exc}", file=sys.stderr) if not ordered_columns: raise SystemExit("No dataframe columns were extracted from the .dai files.") workbook_data = { column: ordered_columns[column] + [""] * (5 - len(ordered_columns[column])) for column in ordered_columns } columns_df = pd.DataFrame(workbook_data) origins_df = pd.DataFrame( [ { "column_name": column, "first_seen_dai_file": source["dai_file"], "first_seen_dataframe_path": source["dataframe_path"], "sample_count": len(ordered_columns[column]), } for column, source in sources.items() ] ) dataframes_df = pd.DataFrame(dataframe_rows) skipped_df = pd.DataFrame(skipped_files or [{"dai_file": "", "error": ""}]) output_path = Path(OUTPUT_XLSX).resolve() summary_path = Path(SUMMARY_JSON).resolve() with pd.ExcelWriter(output_path, engine="openpyxl") as writer: columns_df.to_excel(writer, sheet_name="colunas", index=False) origins_df.to_excel(writer, sheet_name="origem_colunas", index=False) dataframes_df.to_excel(writer, sheet_name="dataframes_lidos", index=False) skipped_df.to_excel(writer, sheet_name="arquivos_pulados", index=False) summary = { "repo_id": REPO_ID, "output_xlsx": str(output_path), "summary_json": str(summary_path), "dai_files_found": len(dai_files), "dai_files_scanned": len(scanned_files), "dai_files_skipped": len(skipped_files), "dataframes_found": len(dataframe_rows), "unique_columns": len(ordered_columns), "columns": list(ordered_columns.keys()), "sources": sources, "skipped_files": skipped_files, } summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8") print(json.dumps(summary, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()