sharanyaswarup commited on
Commit
959775e
Β·
1 Parent(s): 9eec76a

Add download aliases button to UI

Browse files
Files changed (2) hide show
  1. alias_store.py +88 -77
  2. app.py +41 -0
alias_store.py CHANGED
@@ -1,42 +1,21 @@
1
  """
2
- alias_store.py β€” Manages the school_aliases.json file stored on HuggingFace.
3
-
4
- Schema:
5
- {
6
- "18050406004": {
7
- "names": [
8
- {"name": "SARUPETA GIRLS HE SCHOOL", "year_month": "2025-07", "source": "Old Master"}
9
- ],
10
- "last_updated": "2026-07-07"
11
- }
12
- }
13
  """
14
 
15
  import os
16
- import json
17
  import tempfile
 
18
  from datetime import datetime, timezone
19
  from huggingface_hub import HfApi, hf_hub_download
20
 
21
  HF_TOKEN = os.getenv("HF_TOKEN", "")
22
  HF_SCRAPER_REPO = os.getenv("HF_SCRAPER_REPO", "")
23
- ALIAS_FILE = "school_aliases.json"
24
-
25
-
26
- def _migrate_names(names: list) -> list[dict]:
27
- migrated = []
28
- for item in names:
29
- if isinstance(item, str):
30
- migrated.append({"name": item, "year_month": "", "source": "Unknown (Migrated)"})
31
- elif isinstance(item, dict):
32
- migrated.append(item)
33
- return migrated
34
-
35
 
36
  def load_aliases() -> dict:
37
  """
38
- Download and return the alias dictionary from HF as a Python dict.
39
- Returns an empty dict {} if the file doesn't exist yet.
40
  """
41
  if not HF_SCRAPER_REPO:
42
  return {}
@@ -48,25 +27,39 @@ def load_aliases() -> dict:
48
  token=HF_TOKEN or None,
49
  force_download=True,
50
  )
51
- with open(path, "r", encoding="utf-8") as f:
52
- data = json.load(f)
53
- # Migrate old string arrays to object arrays on load
54
- for udise, info in data.items():
55
- if "names" in info:
56
- info["names"] = _migrate_names(info["names"])
57
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  except Exception as e:
59
  if "404" in str(e) or "not found" in str(e).lower() or "Entry Not Found" in str(e):
60
  return {}
61
- print(f"[alias_store] Error loading aliases: {e}")
62
  return {}
63
 
64
-
65
  def save_aliases(new_entries: list[dict]) -> str:
66
  """
67
- Upsert new alias entries into the cloud JSON.
68
-
69
- Each entry should have: udise_code, alias_name, source_label
70
  """
71
  if not HF_SCRAPER_REPO:
72
  return "⚠️ HF_SCRAPER_REPO is not configured β€” cannot save aliases."
@@ -74,45 +67,59 @@ def save_aliases(new_entries: list[dict]) -> str:
74
  return "⚠️ No entries provided."
75
 
76
  today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
77
- data = load_aliases()
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  added_count = 0
80
  skipped_count = 0
 
81
 
82
  for entry in new_entries:
83
  code = str(entry.get("udise_code", "")).strip()
84
  name = str(entry.get("alias_name", "")).strip()
85
  source = str(entry.get("source_label", "")).strip()
86
- year_month = str(entry.get("year_month", "")).strip()
87
 
88
  if not code or not name:
89
  continue
90
 
91
- if code not in data:
92
- data[code] = {"names": [], "last_updated": today}
93
-
94
- existing_upper = {n["name"].upper() for n in data[code]["names"]}
95
-
96
- if name.upper() in existing_upper:
97
- skipped_count += 1
98
- continue
99
-
100
- data[code]["names"].append({
101
- "name": name,
102
- "source": source,
103
- "year_month": year_month
104
  })
105
- data[code]["last_updated"] = today
106
  added_count += 1
107
 
108
  if added_count == 0:
109
- return f"ℹ️ All {skipped_count} name(s) already in dictionary β€” nothing new to save."
 
 
110
 
111
  # Upload to HF
112
  try:
113
  api = HfApi()
114
- with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as f:
115
- json.dump(data, f, indent=2, ensure_ascii=False)
116
  tmp_path = f.name
117
 
118
  udise_sample = new_entries[0].get("udise_code", "unknown")
@@ -139,8 +146,9 @@ def get_names_for_udise(udise_code: str) -> list[dict]:
139
  data = load_aliases()
140
  return data.get(str(udise_code).strip(), {}).get("names", [])
141
 
 
142
  def delete_alias(udise_code: str, alias_name: str) -> str:
143
- """Delete a specific alias name for a UDISE code from the cloud JSON."""
144
  if not HF_SCRAPER_REPO:
145
  return "⚠️ HF_SCRAPER_REPO is not configured β€” cannot delete aliases."
146
 
@@ -149,29 +157,33 @@ def delete_alias(udise_code: str, alias_name: str) -> str:
149
 
150
  if not code or not name_to_delete:
151
  return "⚠️ Invalid UDISE code or name."
152
-
153
- data = load_aliases()
154
- if code not in data:
155
- return "⚠️ UDISE code not found in saved aliases."
156
-
157
- original_count = len(data[code].get("names", []))
158
- data[code]["names"] = [
159
- n for n in data[code].get("names", [])
160
- if n.get("name", "").strip().upper() != name_to_delete
161
- ]
 
 
 
 
162
 
163
- if len(data[code]["names"]) == original_count:
164
  return f"⚠️ Alias '{alias_name}' not found for UDISE {code}."
165
-
166
- data[code]["last_updated"] = datetime.now(timezone.utc).strftime("%Y-%m-%d")
167
-
168
- # Upload to HF
169
  try:
170
  api = HfApi()
171
- with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as f:
172
- json.dump(data, f, indent=2, ensure_ascii=False)
173
  tmp_path = f.name
174
-
175
  api.upload_file(
176
  path_or_fileobj=tmp_path,
177
  path_in_repo=ALIAS_FILE,
@@ -180,7 +192,6 @@ def delete_alias(udise_code: str, alias_name: str) -> str:
180
  token=HF_TOKEN or None,
181
  commit_message=f"Delete alias '{alias_name}' for UDISE {code}",
182
  )
183
-
184
  return f"πŸ—‘οΈ Deleted '{alias_name}' successfully!"
185
  except Exception as e:
186
  return f"❌ Failed to delete alias: {e}"
 
1
  """
2
+ alias_store.py β€” Manages the school_aliases.csv file stored on HuggingFace.
 
 
 
 
 
 
 
 
 
 
3
  """
4
 
5
  import os
 
6
  import tempfile
7
+ import pandas as pd
8
  from datetime import datetime, timezone
9
  from huggingface_hub import HfApi, hf_hub_download
10
 
11
  HF_TOKEN = os.getenv("HF_TOKEN", "")
12
  HF_SCRAPER_REPO = os.getenv("HF_SCRAPER_REPO", "")
13
+ ALIAS_FILE = "school_aliases.csv"
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  def load_aliases() -> dict:
16
  """
17
+ Download the CSV from HF, parse it, and return a dictionary identical to
18
+ the old JSON format so that app.py doesn't have to change at all!
19
  """
20
  if not HF_SCRAPER_REPO:
21
  return {}
 
27
  token=HF_TOKEN or None,
28
  force_download=True,
29
  )
30
+ df = pd.read_csv(path)
31
+
32
+ data = {}
33
+ for _, row in df.iterrows():
34
+ code = str(row["UDISE_Code"]).strip()
35
+ name = str(row["Alias_Name"]).strip()
36
+ source = str(row["Source"]).strip()
37
+
38
+ # Handle empty values gracefully
39
+ ym = str(row["Year_Month"]) if pd.notna(row["Year_Month"]) else ""
40
+ if ym == "nan": ym = ""
41
+
42
+ lu = str(row["Last_Updated"]) if pd.notna(row["Last_Updated"]) else ""
43
+ if lu == "nan": lu = ""
44
+
45
+ if code not in data:
46
+ data[code] = {"names": [], "last_updated": lu}
47
+
48
+ data[code]["names"].append({
49
+ "name": name,
50
+ "source": source,
51
+ "year_month": ym
52
+ })
53
+ return data
54
  except Exception as e:
55
  if "404" in str(e) or "not found" in str(e).lower() or "Entry Not Found" in str(e):
56
  return {}
57
+ print(f"[alias_store] Error loading CSV aliases: {e}")
58
  return {}
59
 
 
60
  def save_aliases(new_entries: list[dict]) -> str:
61
  """
62
+ Upsert new alias entries into the cloud CSV.
 
 
63
  """
64
  if not HF_SCRAPER_REPO:
65
  return "⚠️ HF_SCRAPER_REPO is not configured β€” cannot save aliases."
 
67
  return "⚠️ No entries provided."
68
 
69
  today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
70
+
71
+ try:
72
+ path = hf_hub_download(
73
+ repo_id=HF_SCRAPER_REPO,
74
+ filename=ALIAS_FILE,
75
+ repo_type="dataset",
76
+ token=HF_TOKEN or None,
77
+ force_download=True,
78
+ )
79
+ df = pd.read_csv(path)
80
+ except Exception:
81
+ # File might not exist yet
82
+ df = pd.DataFrame(columns=["UDISE_Code", "Alias_Name", "Source", "Year_Month", "Last_Updated"])
83
 
84
  added_count = 0
85
  skipped_count = 0
86
+ new_rows = []
87
 
88
  for entry in new_entries:
89
  code = str(entry.get("udise_code", "")).strip()
90
  name = str(entry.get("alias_name", "")).strip()
91
  source = str(entry.get("source_label", "")).strip()
92
+ ym = str(entry.get("year_month", "")).strip()
93
 
94
  if not code or not name:
95
  continue
96
 
97
+ # Check if alias already exists (case-insensitive) for this UDISE
98
+ if not df.empty:
99
+ mask = (df["UDISE_Code"].astype(str).str.strip() == code) & (df["Alias_Name"].astype(str).str.strip().str.upper() == name.upper())
100
+ if mask.any():
101
+ skipped_count += 1
102
+ continue
103
+
104
+ new_rows.append({
105
+ "UDISE_Code": code,
106
+ "Alias_Name": name,
107
+ "Source": source,
108
+ "Year_Month": ym,
109
+ "Last_Updated": today
110
  })
 
111
  added_count += 1
112
 
113
  if added_count == 0:
114
+ return f"ℹ️ All {skipped_count} name(s) already in CSV β€” nothing new to save."
115
+
116
+ df = pd.concat([df, pd.DataFrame(new_rows)], ignore_index=True)
117
 
118
  # Upload to HF
119
  try:
120
  api = HfApi()
121
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding="utf-8") as f:
122
+ df.to_csv(f.name, index=False)
123
  tmp_path = f.name
124
 
125
  udise_sample = new_entries[0].get("udise_code", "unknown")
 
146
  data = load_aliases()
147
  return data.get(str(udise_code).strip(), {}).get("names", [])
148
 
149
+
150
  def delete_alias(udise_code: str, alias_name: str) -> str:
151
+ """Delete a specific alias name for a UDISE code from the cloud CSV."""
152
  if not HF_SCRAPER_REPO:
153
  return "⚠️ HF_SCRAPER_REPO is not configured β€” cannot delete aliases."
154
 
 
157
 
158
  if not code or not name_to_delete:
159
  return "⚠️ Invalid UDISE code or name."
160
+
161
+ try:
162
+ path = hf_hub_download(
163
+ repo_id=HF_SCRAPER_REPO,
164
+ filename=ALIAS_FILE,
165
+ repo_type="dataset",
166
+ token=HF_TOKEN or None,
167
+ force_download=True,
168
+ )
169
+ df = pd.read_csv(path)
170
+ except Exception:
171
+ return "⚠️ CSV file not found on HuggingFace."
172
+
173
+ mask = (df["UDISE_Code"].astype(str).str.strip() == code) & (df["Alias_Name"].astype(str).str.strip().str.upper() == name_to_delete)
174
 
175
+ if not mask.any():
176
  return f"⚠️ Alias '{alias_name}' not found for UDISE {code}."
177
+
178
+ # Keep everything EXCEPT the matching row
179
+ df = df[~mask]
180
+
181
  try:
182
  api = HfApi()
183
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False, encoding="utf-8") as f:
184
+ df.to_csv(f.name, index=False)
185
  tmp_path = f.name
186
+
187
  api.upload_file(
188
  path_or_fileobj=tmp_path,
189
  path_in_repo=ALIAS_FILE,
 
192
  token=HF_TOKEN or None,
193
  commit_message=f"Delete alias '{alias_name}' for UDISE {code}",
194
  )
 
195
  return f"πŸ—‘οΈ Deleted '{alias_name}' successfully!"
196
  except Exception as e:
197
  return f"❌ Failed to delete alias: {e}"
app.py CHANGED
@@ -473,6 +473,47 @@ with gr.Blocks(title="School Name Resolver") as app:
473
  outputs=[status_text, render_trigger]
474
  )
475
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  # ── Settings ──
477
  with gr.Accordion("βš™οΈ Settings & Cache", open=False):
478
  refresh_cache_btn = gr.Button("πŸ”„ Refresh Data Cache", elem_classes="btn-secondary")
 
473
  outputs=[status_text, render_trigger]
474
  )
475
 
476
+ # ── Download Master Sheet ──
477
+ gr.HTML('<div style="margin-top: 20px;"></div>')
478
+ download_aliases_btn = gr.DownloadButton("πŸ“₯ Download Aliases Master Sheet (Excel)", elem_classes="btn-secondary", size="lg")
479
+
480
+ def on_download_aliases():
481
+ from huggingface_hub import hf_hub_download
482
+ import os
483
+ import pandas as pd
484
+ import tempfile
485
+
486
+ token = os.getenv("HF_TOKEN")
487
+ repo = os.getenv("HF_SCRAPER_REPO")
488
+ if not token or not repo: return None
489
+ try:
490
+ path = hf_hub_download(
491
+ repo_id=repo,
492
+ filename="school_aliases.csv",
493
+ repo_type="dataset",
494
+ token=token,
495
+ force_download=True,
496
+ )
497
+
498
+ # Convert the CSV into a true Excel (.xlsx) file on the fly!
499
+ df = pd.read_csv(path)
500
+
501
+ import datetime
502
+ date_str = datetime.datetime.now().strftime("%b_%Y")
503
+ download_name = f"school_aliases_{date_str}.xlsx"
504
+
505
+ temp_dir = tempfile.mkdtemp()
506
+ out_path = os.path.join(temp_dir, download_name)
507
+ df.to_excel(out_path, index=False)
508
+
509
+ return out_path
510
+
511
+ except Exception as e:
512
+ print(f"Error downloading for UI: {e}")
513
+ return None
514
+
515
+ download_aliases_btn.click(fn=on_download_aliases, outputs=[download_aliases_btn])
516
+
517
  # ── Settings ──
518
  with gr.Accordion("βš™οΈ Settings & Cache", open=False):
519
  refresh_cache_btn = gr.Button("πŸ”„ Refresh Data Cache", elem_classes="btn-secondary")