← back to Unclaimed Property Platform
scripts/gen_records_requests.py
110 lines
#!/usr/bin/env python3
"""
gen_records_requests.py — stamp the master public-records-request template with each
jurisdiction's correct public-records statute, producing one ready-to-review letter per
state (+ DC) under outreach/generated/.
Stdlib only. $0, local, no network. Sends nothing — this only WRITES letter files.
The actual send (via George/Gmail) is a separate, Steve-gated step.
Usage:
python3 scripts/gen_records_requests.py # all jurisdictions
python3 scripts/gen_records_requests.py CA # one (or several) by code
python3 scripts/gen_records_requests.py CA TX NY
"""
from __future__ import annotations
import datetime as _dt
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUTREACH = ROOT / "outreach"
REGISTRY = OUTREACH / "states-records-law.json"
TEMPLATE = OUTREACH / "TEMPLATE-public-records-request.md"
OUT_DIR = OUTREACH / "generated"
# Requester identity — steve-office. Update here if the requesting entity changes.
REQUESTER = {
"name": "Steve Abrams",
"entity": "Designer Wallcoverings",
"address": "18406 Bessemer Street, Tarzana, CA 91335",
"email": "steve@designerwallcoverings.com",
}
def _address_block(j: dict) -> str:
addr = j.get("mailing_address")
if addr:
# split a single-line comma address into readable lines
return "\n".join(part.strip() for part in addr.split(", "))
return "[VERIFY: confirm the current records-request mailing address / online PRA portal for this office]"
def _verify_note(j: dict) -> str:
if j.get("submission_channel") and j.get("mailing_address"):
return ""
return (
"----\n"
"BEFORE SENDING — verify the current submission channel for this office "
"(many states now require an online public-records portal or a specific "
"records-coordinator email). Do not guess the address. Registry field "
"`submission_channel` / `mailing_address` is null for this jurisdiction."
)
def render(j: dict, template: str, today: str) -> str:
return (
template
.replace("{{DATE}}", today)
.replace("{{STATE}}", j["name"])
.replace("{{OFFICE}}", j["office"])
.replace("{{ADDRESS_BLOCK}}", _address_block(j))
.replace("{{LAW_NAME}}", j["law_name"])
.replace("{{LAW_CITE}}", j["law_cite"])
.replace("{{REQUESTER_NAME}}", REQUESTER["name"])
.replace("{{REQUESTER_ENTITY}}", REQUESTER["entity"])
.replace("{{REQUESTER_ADDRESS}}", REQUESTER["address"])
.replace("{{REQUESTER_EMAIL}}", REQUESTER["email"])
.replace("{{VERIFY_NOTE}}", _verify_note(j))
)
def main(argv: list[str]) -> int:
registry = json.loads(REGISTRY.read_text())["jurisdictions"]
template = TEMPLATE.read_text()
today = _dt.date.today().strftime("%B %-d, %Y")
wanted = {c.upper() for c in argv}
if wanted:
registry = [j for j in registry if j["code"] in wanted]
if not registry:
print(f"No jurisdiction matched {sorted(wanted)}", file=sys.stderr)
return 1
OUT_DIR.mkdir(parents=True, exist_ok=True)
manifest = []
for j in registry:
letter = render(j, template, today)
out = OUT_DIR / f"{j['code']}-records-request.txt"
out.write_text(letter)
ready = bool(j.get("submission_channel") and j.get("mailing_address"))
manifest.append({
"code": j["code"], "state": j["name"], "office": j["office"],
"law": j["law_name"], "file": str(out.relative_to(ROOT)),
"contact_verified": ready,
})
flag = "ready" if ready else "VERIFY-CONTACT"
print(f" [{j['code']}] {j['name']:<22} {j['law_name']:<45} -> {out.name} ({flag})")
(OUT_DIR / "MANIFEST.json").write_text(json.dumps(manifest, indent=2))
verified = sum(1 for m in manifest if m["contact_verified"])
print(f"\nWrote {len(manifest)} letter(s). Contact verified: {verified}/{len(manifest)}.")
print("Sending is a separate, Steve-gated step — nothing was transmitted.")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))