"""Archive old (single-model degenerate) SAGA data antes de submeter SAGA-real. Move/copia as rows com `attack == "SAGA"` de: - results/raw/sweep_main_tcc//results.csv + attention_maps.parquet - results/raw/sweep_ablation_tcc//results.csv + attention_maps.parquet - results/raw/probe_eps_curve_tcc//results.csv + attention_maps.parquet - results/raw/probe_eps_curve_tcc_saga_only//results.csv + attention_maps.parquet para: results/raw/_archive_saga_single_model///{results.csv,attention_maps.parquet} Por DEFAULT é DRY-RUN (não modifica nada — só lista o que seria feito). Pra executar passa `--commit`. Após executar, os CSV/parquets dos sweeps ficam SEM rows SAGA — SAGA-real sweeps depois preenchem com dados novos. NÃO É REVERSÍVEL com `--commit`. Recomendado: 1. Rodar DRY (sem --commit) e confirmar contagens 2. Rodar com `--commit` SÓ após smoke test do SAGA-real ter passado 3. Manter `_archive_saga_single_model/` no disco (pequeno) pra forensics Usage: # Dry-run (default) python scripts/archive_old_saga_data.py # Commit python scripts/archive_old_saga_data.py --commit """ from __future__ import annotations import argparse import shutil import sys from pathlib import Path import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parent.parent RAW_DIR = PROJECT_ROOT / "results" / "raw" ARCHIVE_DIR = RAW_DIR / "_archive_saga_single_model" SWEEPS = [ "sweep_main_tcc", "sweep_ablation_tcc", "probe_eps_curve_tcc", "probe_eps_curve_tcc_saga_only", ] def split_csv(src_csv: Path, archive_csv: Path, commit: bool) -> tuple[int, int]: """Move SAGA rows from src_csv to archive_csv. Returns (n_saga, n_kept).""" df = pd.read_csv(src_csv) if "attack" not in df.columns: return 0, len(df) saga_df = df[df["attack"] == "SAGA"].copy() kept_df = df[df["attack"] != "SAGA"].copy() if len(saga_df) == 0: return 0, len(kept_df) if commit: archive_csv.parent.mkdir(parents=True, exist_ok=True) saga_df.to_csv(archive_csv, index=False) kept_df.to_csv(src_csv, index=False) return len(saga_df), len(kept_df) def split_parquet(src_pq: Path, archive_pq: Path, commit: bool) -> tuple[int, int]: """Move SAGA rows from src_pq to archive_pq. Returns (n_saga, n_kept).""" if not src_pq.exists(): return 0, 0 df = pd.read_parquet(src_pq) if "attack" not in df.columns: return 0, len(df) saga_df = df[df["attack"] == "SAGA"].copy() kept_df = df[df["attack"] != "SAGA"].copy() if len(saga_df) == 0: return 0, len(kept_df) if commit: archive_pq.parent.mkdir(parents=True, exist_ok=True) saga_df.to_parquet(archive_pq, index=False) kept_df.to_parquet(src_pq, index=False) return len(saga_df), len(kept_df) def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--commit", action="store_true", help="Apply changes (default: dry-run)") args = ap.parse_args() mode = "COMMIT" if args.commit else "DRY-RUN" print(f"=" * 70) print(f"Archive old SAGA data [{mode}]") print(f"Source: {RAW_DIR}") print(f"Archive: {ARCHIVE_DIR}") print(f"=" * 70) total_saga = 0 total_kept = 0 for sweep in SWEEPS: sweep_dir = RAW_DIR / sweep if not sweep_dir.exists(): print(f"\n [SKIP] {sweep}/ — directory not found") continue print(f"\n {sweep}/") for model_dir in sorted(sweep_dir.glob("vit-*_imagenet-1k_seed42")): csv_src = model_dir / "results.csv" pq_src = model_dir / "attention_maps.parquet" if not csv_src.exists(): continue csv_arch = ARCHIVE_DIR / sweep / model_dir.name / "results.csv" pq_arch = ARCHIVE_DIR / sweep / model_dir.name / "attention_maps.parquet" n_saga_csv, n_kept_csv = split_csv(csv_src, csv_arch, args.commit) n_saga_pq, n_kept_pq = split_parquet(pq_src, pq_arch, args.commit) print(f" {model_dir.name}: csv {n_saga_csv} SAGA / {n_kept_csv} kept | " f"parquet {n_saga_pq} SAGA / {n_kept_pq} kept") total_saga += n_saga_csv total_kept += n_kept_csv print(f"\n{'=' * 70}") print(f"Totals: {total_saga:,} SAGA rows {'moved to archive' if args.commit else 'WOULD BE moved'}") print(f" {total_kept:,} non-SAGA rows kept in source") print(f"{'=' * 70}") if not args.commit: print("Dry-run only — pass --commit to apply.") return 0 if __name__ == "__main__": sys.exit(main())