| """ |
| alias_store.py — Manages the school_aliases.csv file stored on HuggingFace. |
| """ |
|
|
| import os |
| import tempfile |
| import pandas as pd |
| from datetime import datetime, timezone |
| from huggingface_hub import HfApi, hf_hub_download |
|
|
| HF_TOKEN = os.getenv("HF_TOKEN", "") |
| HF_SCRAPER_REPO = os.getenv("HF_SCRAPER_REPO", "") |
| ALIAS_FILE = "school_aliases.csv" |
|
|
| def load_aliases() -> dict: |
| """ |
| Download the CSV from HF, parse it, and return a dictionary identical to |
| the old JSON format so that app.py doesn't have to change at all! |
| """ |
| if not HF_SCRAPER_REPO: |
| return {} |
| try: |
| path = hf_hub_download( |
| repo_id=HF_SCRAPER_REPO, |
| filename=ALIAS_FILE, |
| repo_type="dataset", |
| token=HF_TOKEN or None, |
| force_download=True, |
| ) |
| df = pd.read_csv(path) |
| |
| data = {} |
| for _, row in df.iterrows(): |
| code = str(row["UDISE_Code"]).strip() |
| name = str(row["Alias_Name"]).strip() |
| source = str(row["Source"]).strip() |
| |
| |
| ym = str(row["Year_Month"]) if pd.notna(row["Year_Month"]) else "" |
| if ym == "nan": ym = "" |
| |
| lu = str(row["Last_Updated"]) if pd.notna(row["Last_Updated"]) else "" |
| if lu == "nan": lu = "" |
| |
| if code not in data: |
| data[code] = {"names": [], "last_updated": lu} |
| |
| data[code]["names"].append({ |
| "name": name, |
| "source": source, |
| "year_month": ym |
| }) |
| return data |
| except Exception as e: |
| if "404" in str(e) or "not found" in str(e).lower() or "Entry Not Found" in str(e): |
| return {} |
| print(f"[alias_store] Error loading CSV aliases: {e}") |
| return {} |
|
|
| def save_aliases(new_entries: list[dict]) -> str: |
| """ |
| Upsert new alias entries into the cloud CSV. |
| """ |
| if not HF_SCRAPER_REPO: |
| return "⚠️ HF_SCRAPER_REPO is not configured — cannot save aliases." |
| if not new_entries: |
| return "⚠️ No entries provided." |
|
|
| today = datetime.now(timezone.utc).strftime("%Y-%m-%d") |
| |
| try: |
| path = hf_hub_download( |
| repo_id=HF_SCRAPER_REPO, |
| filename=ALIAS_FILE, |
| repo_type="dataset", |
| token=HF_TOKEN or None, |
| force_download=True, |
| ) |
| df = pd.read_csv(path) |
| except Exception: |
| |
| df = pd.DataFrame(columns=["UDISE_Code", "Alias_Name", "Source", "Year_Month", "Last_Updated"]) |
|
|
| added_count = 0 |
| skipped_count = 0 |
| new_rows = [] |
|
|
| for entry in new_entries: |
| code = str(entry.get("udise_code", "")).strip() |
| name = str(entry.get("alias_name", "")).strip() |
| source = str(entry.get("source_label", "")).strip() |
| ym = str(entry.get("year_month", "")).strip() |
| |
| if not code or not name: |
| continue |
|
|
| |
| if not df.empty: |
| mask = (df["UDISE_Code"].astype(str).str.strip() == code) & (df["Alias_Name"].astype(str).str.strip().str.upper() == name.upper()) |
| if mask.any(): |
| skipped_count += 1 |
| continue |
|
|
| new_rows.append({ |
| "UDISE_Code": code, |
| "Alias_Name": name, |
| "Source": source, |
| "Year_Month": ym, |
| "Last_Updated": today |
| }) |
| added_count += 1 |
|
|
| if added_count == 0: |
| return f"ℹ️ All {skipped_count} name(s) already in CSV — nothing new to save." |
|
|
| |
| df["UDISE_Code"] = df["UDISE_Code"].astype(str) |
| |
| df = pd.concat([df, pd.DataFrame(new_rows)], ignore_index=True) |
| |
| |
| df = df.sort_values(by=["UDISE_Code", "Source"]).reset_index(drop=True) |
|
|
| |
| try: |
| api = HfApi() |
| with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding="utf-8") as f: |
| df.to_csv(f.name, index=False) |
| tmp_path = f.name |
|
|
| udise_sample = new_entries[0].get("udise_code", "unknown") |
| api.upload_file( |
| path_or_fileobj=tmp_path, |
| path_in_repo=ALIAS_FILE, |
| repo_id=HF_SCRAPER_REPO, |
| repo_type="dataset", |
| token=HF_TOKEN or None, |
| commit_message=f"Add aliases for UDISE {udise_sample} ({added_count} new name(s))", |
| ) |
|
|
| msg = f"✅ Saved! {added_count} new name(s) added." |
| if skipped_count: |
| msg += f" ({skipped_count} duplicate(s) skipped.)" |
| return msg |
|
|
| except Exception as e: |
| return f"❌ Failed to save aliases: {e}" |
|
|
|
|
| def get_names_for_udise(udise_code: str) -> list[dict]: |
| """Return all known names for a specific UDISE code as a list of dicts.""" |
| data = load_aliases() |
| return data.get(str(udise_code).strip(), {}).get("names", []) |
|
|
|
|
| def delete_alias(udise_code: str, alias_name: str) -> str: |
| """Delete a specific alias name for a UDISE code from the cloud CSV.""" |
| if not HF_SCRAPER_REPO: |
| return "⚠️ HF_SCRAPER_REPO is not configured — cannot delete aliases." |
| |
| code = str(udise_code).strip() |
| name_to_delete = str(alias_name).strip().upper() |
| |
| if not code or not name_to_delete: |
| return "⚠️ Invalid UDISE code or name." |
| |
| try: |
| path = hf_hub_download( |
| repo_id=HF_SCRAPER_REPO, |
| filename=ALIAS_FILE, |
| repo_type="dataset", |
| token=HF_TOKEN or None, |
| force_download=True, |
| ) |
| df = pd.read_csv(path) |
| except Exception: |
| return "⚠️ CSV file not found on HuggingFace." |
| |
| mask = (df["UDISE_Code"].astype(str).str.strip() == code) & (df["Alias_Name"].astype(str).str.strip().str.upper() == name_to_delete) |
| |
| if not mask.any(): |
| return f"⚠️ Alias '{alias_name}' not found for UDISE {code}." |
| |
| |
| df = df[~mask] |
| |
| try: |
| api = HfApi() |
| with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding="utf-8") as f: |
| df.to_csv(f.name, index=False) |
| tmp_path = f.name |
| |
| api.upload_file( |
| path_or_fileobj=tmp_path, |
| path_in_repo=ALIAS_FILE, |
| repo_id=HF_SCRAPER_REPO, |
| repo_type="dataset", |
| token=HF_TOKEN or None, |
| commit_message=f"Delete alias '{alias_name}' for UDISE {code}", |
| ) |
| return f"🗑️ Deleted '{alias_name}' successfully!" |
| except Exception as e: |
| return f"❌ Failed to delete alias: {e}" |
|
|