school-name-resolver / alias_store.py
sharanyaswarup's picture
Fix pandas sorting bug in alias_store
5a1766a
Raw
History Blame Contribute Delete
6.94 kB
"""
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()
# Handle empty values gracefully
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:
# File might not exist yet
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
# Check if alias already exists (case-insensitive) for this UDISE
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."
# Force string type on UDISE_Code to prevent pandas from breaking when sorting mixed types!
df["UDISE_Code"] = df["UDISE_Code"].astype(str)
df = pd.concat([df, pd.DataFrame(new_rows)], ignore_index=True)
# Sort by UDISE so all aliases for the same school are grouped together in Excel
df = df.sort_values(by=["UDISE_Code", "Source"]).reset_index(drop=True)
# Upload to HF
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}."
# Keep everything EXCEPT the matching row
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}"