"""Run the pipeline across multiple OULAD presentations.""" from __future__ import annotations import argparse import json from pathlib import Path import pandas as pd from . import pipeline from .config import DEMO_CACHE, TABLES, ensure_dirs, raw_data_dir DEFAULT_PRESENTATIONS = ["AAA_2013J", "AAA_2014J", "BBB_2014J", "CCC_2014J"] def available_presentations() -> list[str]: info = pd.read_csv(raw_data_dir() / "studentInfo.csv", usecols=["code_module", "code_presentation"]) pairs = info.drop_duplicates().sort_values(["code_module", "code_presentation"]) return [f"{row.code_module}_{row.code_presentation}" for row in pairs.itertuples(index=False)] def _load_meta() -> dict: path = DEMO_CACHE / "meta.json" if not path.exists(): return {} return json.loads(path.read_text(encoding="utf-8")) def run_many( presentations: list[str], bootstrap_b: int, sample_n: int | None, n_jobs: int, random_baselines: int, ) -> pd.DataFrame: ensure_dirs() rows = [] for presentation in presentations: print(f"\n=== {presentation} ===") result = pipeline.run( presentation=presentation, bootstrap_b=bootstrap_b, sample_n=sample_n, n_jobs=n_jobs, random_baselines=random_baselines, ) winner = result["winner"] group_metrics = result["group_metrics"].set_index("strategy") meta = _load_meta() mode_b = group_metrics.loc["mode_b"].to_dict() if "mode_b" in group_metrics.index else {} rows.append( { "presentation": presentation, "n_learners": meta.get("n_learners"), "n_features": meta.get("n_features"), "winner_config": winner.get("config_id"), "winner_reducer": winner.get("reducer"), "winner_clusterer": winner.get("clusterer"), "winner_k": winner.get("k"), "winner_bootstrap_ari": winner.get("bootstrap_ari_mean"), "mode_b_complementarity": mode_b.get("complementarity"), "mode_b_cluster_coverage": mode_b.get("cluster_coverage"), "mode_b_outcome_diversity": mode_b.get("outcome_diversity"), "mode_b_high_risk_group_rate": mode_b.get("high_risk_group_rate"), } ) summary = pd.DataFrame(rows) summary.to_csv(TABLES / "multi_presentation_summary.csv", index=False) summary.to_parquet(TABLES / "multi_presentation_summary.parquet", index=False) return summary def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Run pipeline across multiple OULAD presentations") parser.add_argument( "--presentations", nargs="*", default=None, help="Presentations such as AAA_2014J BBB_2014J. Defaults to a small representative set.", ) parser.add_argument("--list", action="store_true", help="List available presentations and exit") parser.add_argument("--bootstrap-b", type=int, default=5) parser.add_argument("--sample-n", type=int, default=300) parser.add_argument("--n-jobs", type=int, default=1) parser.add_argument("--random-baselines", type=int, default=20) return parser def main() -> None: parser = build_parser() args = parser.parse_args() available = available_presentations() if args.list: print("\n".join(available)) return presentations = args.presentations or [p for p in DEFAULT_PRESENTATIONS if p in available] if not presentations: presentations = available[:4] summary = run_many( presentations, bootstrap_b=args.bootstrap_b, sample_n=args.sample_n, n_jobs=args.n_jobs, random_baselines=args.random_baselines, ) print("\nSummary") print(summary.to_string(index=False)) if __name__ == "__main__": main()