← back to La Permits School

live_board.py

78 lines

#!/usr/bin/env python3
"""
live_board.py — ranked LIVE board from master_live.csv (the loop's analytical product). $0.

Turns the accumulating live permit+contractor dataset into the "who is building in LA
right now" board: top contractors by ACTIVE pipeline value, newest big permits, and a
council-district cut. Writes LIVE-BOARD.md (committed each cycle so the repo shows a
current, dated snapshot).
"""
import csv, datetime, os

OWNER = {"owner-builder", "owner builder"}


def money(v):
    try:
        return float(v)
    except (TypeError, ValueError):
        return 0.0


def main():
    if not os.path.exists("master_live.csv"):
        print("no master_live.csv yet"); return
    rows = list(csv.DictReader(open("master_live.csv", newline="", encoding="utf-8")))
    if not rows:
        print("master empty"); return

    firms = {}
    for r in rows:
        name = (r.get("contractor_name") or "").strip()
        if not name or name.lower() in OWNER:
            continue
        f = firms.setdefault(name, {"n": 0, "val": 0.0, "lic": r.get("contractor_license", "")})
        f["n"] += 1
        f["val"] += money(r.get("valuation"))

    # active build value by council district (the "where is LA building now" cut)
    cd = {}
    for r in rows:
        k = (r.get("council_district") or "?").strip() or "?"
        c = cd.setdefault(k, {"n": 0, "val": 0.0})
        c["n"] += 1
        c["val"] += money(r.get("valuation"))
    top_cd = sorted(cd.items(), key=lambda kv: kv[1]["val"], reverse=True)[:10]

    top = sorted(firms.items(), key=lambda kv: kv[1]["val"], reverse=True)[:15]
    newest = sorted(rows, key=lambda r: (r.get("issue_date") or ""), reverse=True)[:12]
    total = sum(money(r.get("valuation")) for r in rows)

    out = []
    out.append("# LA LIVE Construction Board")
    out.append(f"\n_Auto-generated from live LADBS data. Master: **{len(rows)} active permits**, "
               f"**${total:,.0f}** total declared value._\n")
    out.append("## Top contractors by active pipeline value")
    out.append("\n| # | Contractor | Active $ | Permits | CSLB Lic |")
    out.append("|---|---|---|---|---|")
    for i, (name, f) in enumerate(top, 1):
        out.append(f"| {i} | {name} | ${int(f['val']):,} | {f['n']} | {f['lic'] or '—'} |")
    out.append("\n## Active build value by City Council district")
    out.append("\n| District | Active $ | Permits |")
    out.append("|---|---|---|")
    for k, c in top_cd:
        out.append(f"| CD {k} | ${int(c['val']):,} | {c['n']} |")

    out.append("\n## Newest major permits")
    out.append("\n| Issued | Value | Contractor | Address |")
    out.append("|---|---|---|---|")
    for r in newest:
        out.append(f"| {(r.get('issue_date') or '')[:10]} | ${int(money(r.get('valuation'))):,} | "
                   f"{(r.get('contractor_name') or '—')[:30]} | {(r.get('address') or '')[:34]} |")
    open("LIVE-BOARD.md", "w").write("\n".join(out) + "\n")
    print(f"LIVE-BOARD.md written: {len(rows)} permits, {len(firms)} contractors  (cost: $0)")


if __name__ == "__main__":
    main()