← 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:]))