Files
stork/scripts/backfill_name_meta.py
ilia 669568aada
CI / secret-scan (pull_request) Successful in 39s
CI / python-ci (pull_request) Successful in 57s
Add generator, nicknames, ideas mail; drop logo gallery
Ship name corpus lookup/spin, nickname suggestions, significance UX,
site-ideas SMTP, and remove the Logo concepts page and assets.
2026-08-11 20:07:58 -04:00

109 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Backfill empty locale fields + optional nicknames from name_meta.
Usage (on stork host / in container):
python scripts/backfill_name_meta.py --board b_UDKbqtB8cXw
python scripts/backfill_name_meta.py --board b_UDKbqtB8cXw --dry-run
"""
from __future__ import annotations
import argparse
import os
import sqlite3
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from stork.name_meta import lookup_name_meta # noqa: E402
from stork.nicknames import nicknames_for_new_name # noqa: E402
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--board", required=True)
ap.add_argument("--db", default=os.environ.get("STORK_DATA", "/data") + "/stork.sqlite3")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--force-empty-only", action="store_true", default=True)
args = ap.parse_args()
db = Path(args.db)
if not db.is_file():
print(f"missing db {db}", file=sys.stderr)
return 1
con = sqlite3.connect(db)
con.row_factory = sqlite3.Row
names = con.execute(
"SELECT id, spelling, nicknames FROM names WHERE board_id = ? ORDER BY id",
(args.board,),
).fetchall()
changed = 0
for n in names:
meta = lookup_name_meta(n["spelling"])
locs = con.execute(
"SELECT lang, pronunciation, origin, meaning FROM locales WHERE name_id = ?",
(n["id"],),
).fetchall()
by_lang = {r["lang"]: dict(r) for r in locs}
for lang in ("en", "ru", "he"):
cur = by_lang.get(lang) or {"pronunciation": "", "origin": "", "meaning": ""}
hint = meta["locales"].get(lang) or {}
new_p = (cur["pronunciation"] or "").strip() or (hint.get("pronunciation") or "").strip()
new_o = (cur["origin"] or "").strip() or (hint.get("origin") or "").strip()
new_m = (cur["meaning"] or "").strip() or (hint.get("meaning") or "").strip()
if (new_p, new_o, new_m) == (
(cur["pronunciation"] or "").strip(),
(cur["origin"] or "").strip(),
(cur["meaning"] or "").strip(),
):
continue
print(f" {n['spelling']} [{lang}]: fill origin/meaning/pron")
changed += 1
if not args.dry_run:
con.execute(
"""
INSERT INTO locales (name_id, lang, pronunciation, origin, meaning)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(name_id, lang) DO UPDATE SET
pronunciation = excluded.pronunciation,
origin = excluded.origin,
meaning = excluded.meaning
""",
(n["id"], lang, new_p[:200], new_o[:200], new_m[:500]),
)
# nicknames if empty
if not (n["nicknames"] or "").strip():
filled = nicknames_for_new_name(n["spelling"], "")
# For slash spellings try first token
if not filled and "/" in n["spelling"]:
filled = nicknames_for_new_name(n["spelling"].split("/")[0].strip(), "")
if filled:
print(f" {n['spelling']}: nicknames -> {filled}")
changed += 1
if not args.dry_run:
con.execute("UPDATE names SET nicknames = ? WHERE id = ?", (filled, n["id"]))
# strip nickname equal to full spelling when present
elif n["spelling"].casefold() in {
x.strip().casefold() for x in (n["nicknames"] or "").replace(";", ",").split(",")
}:
from stork.nicknames import nicknames_excluding_spelling
cleaned = nicknames_excluding_spelling(n["spelling"], n["nicknames"])
# also try primary token for Rivka-style
primary = n["spelling"].split("/")[0].strip()
cleaned = nicknames_excluding_spelling(primary, cleaned or n["nicknames"])
if cleaned != (n["nicknames"] or "").strip():
print(f" {n['spelling']}: nicknames clean {n['nicknames']!r} -> {cleaned!r}")
changed += 1
if not args.dry_run:
con.execute("UPDATE names SET nicknames = ? WHERE id = ?", (cleaned, n["id"]))
if not args.dry_run:
con.commit()
print(f"{'dry-run ' if args.dry_run else ''}updates touching {changed} fields/rows")
return 0
if __name__ == "__main__":
raise SystemExit(main())