← back to La Dscr Lists

pull.py

67 lines

#!/usr/bin/env python3
"""Pull all 2-4 unit residential-income parcels in LA County (2025 roll) from the
public LA County Assessor FeatureServer. Splits into duplex/triplex/fourplex lists
plus an investor-owned subset (no homeowners exemption). Free, keyless, read-only."""
import csv, json, time, urllib.parse, urllib.request, os

BASE = ("https://services.arcgis.com/RmCCgQtiZLDCtblq/arcgis/rest/services/"
        "Parcel_Data_2021_Table/FeatureServer/0/query")
WHERE = ("RollYear='2025' AND (UseCode LIKE '020%' OR UseCode LIKE '030%' "
         "OR UseCode LIKE '040%')")
FIELDS = ("AIN,AssessorID,PropertyLocation,SitusHouseNo,SitusDirection,SitusStreet,"
          "SitusUnit,SitusCity,SitusZIP5,TaxRateArea_CITY,UseCode,UseCodeDescChar2,"
          "Units,YearBuilt,SQFTmain,Bedrooms,Bathrooms,Roll_LandValue,Roll_ImpValue,"
          "Roll_TotalValue,Roll_HomeOwnersExemp,RecordingDate,CENTER_LAT,CENTER_LON")
OUT = os.path.join(os.path.dirname(__file__), "lists")

def fetch(offset):
    q = {"where": WHERE, "outFields": FIELDS, "returnGeometry": "false",
         "resultRecordCount": 2000, "resultOffset": offset, "f": "json"}
    url = BASE + "?" + urllib.parse.urlencode(q)
    with urllib.request.urlopen(url, timeout=60) as r:
        return json.load(r)

rows, offset = [], 0
while True:
    data = fetch(offset)
    feats = data.get("features", [])
    if not feats:
        break
    rows += [f["attributes"] for f in feats]
    print(f"  fetched {len(rows)} ...", flush=True)
    if len(feats) < 2000:
        break
    offset += 2000
    time.sleep(0.4)

cols = FIELDS.split(",")
def tier(uc):
    if uc.startswith("02"): return "2-unit"
    if uc.startswith("03"): return "3-unit"
    return "4-unit"

buckets = {"2-unit": [], "3-unit": [], "4-unit": []}
for r in rows:
    buckets[tier(r.get("UseCode", ""))].append(r)

def write(path, recs):
    with open(path, "w", newline="") as f:
        w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
        w.writeheader()
        for r in recs:
            w.writerow(r)

os.makedirs(OUT, exist_ok=True)
total = len(rows)
for k, recs in buckets.items():
    write(os.path.join(OUT, f"la-county-{k}.csv"), recs)
# investor-owned subset (no homeowners exemption claimed)
inv = [r for r in rows if not r.get("Roll_HomeOwnersExemp")]
write(os.path.join(OUT, "la-county-2to4-investor-owned.csv"), inv)
write(os.path.join(OUT, "la-county-2to4-all.csv"), rows)

print(f"\nDONE total={total}")
for k in ("2-unit", "3-unit", "4-unit"):
    print(f"  {k}: {len(buckets[k])}")
print(f"  investor-owned (no HOX): {len(inv)}")