← back to Sdcc Journalists Viewer

scripts/refresh.py

55 lines

#!/usr/bin/env python3
"""Pull the SDCC media-contacts spreadsheet from Google Drive and emit data/journalists.json.

The Drive file (gdrive:SDCC/sdcc-journalists-media-contacts.xlsx) is the source of
truth — team edits in Google Sheets/Drive land here on every refresh.
"""
import json, subprocess, sys, os, datetime

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA = os.path.join(ROOT, "data")
XLSX = os.path.join(DATA, "sdcc-journalists-media-contacts.xlsx")
OUT = os.path.join(DATA, "journalists.json")

# 1. pull latest from Drive (rclone only copies if changed)
r = subprocess.run(["rclone", "copy", "gdrive:SDCC/sdcc-journalists-media-contacts.xlsx", DATA],
                   capture_output=True, text=True, timeout=120)
drive_ok = r.returncode == 0
if not drive_ok:
    sys.stderr.write(f"rclone pull failed: {r.stderr[:300]}\n")
    if not os.path.exists(XLSX):
        sys.exit(2)  # nothing cached either — hard fail

# 2. parse xlsx (headers row 1; hyperlinks preserved per cell)
from openpyxl import load_workbook
wb = load_workbook(XLSX)
ws = wb.active
headers = [str(c.value or "").strip() for c in ws[1]]

def key(h):
    return h.lower().replace(" (", "_").replace(")", "").replace(" ", "_").replace("(", "").replace("__", "_").strip("_")

rows = []
for row in ws.iter_rows(min_row=2):
    if not any(c.value for c in row):
        continue
    rec, links = {}, {}
    for h, c in zip(headers, row):
        k = key(h)
        rec[k] = "" if c.value is None else str(c.value)
        if c.hyperlink and c.hyperlink.target:
            links[k] = c.hyperlink.target
    rec["_links"] = links
    rows.append(rec)

payload = {
    "generated_at": datetime.datetime.now().astimezone().isoformat(),
    "drive_pull_ok": drive_ok,
    "source": "gdrive:SDCC/sdcc-journalists-media-contacts.xlsx",
    "count": len(rows),
    "rows": rows,
}
with open(OUT, "w") as f:
    json.dump(payload, f)
print(f"refreshed: {len(rows)} rows, drive_pull_ok={drive_ok}")