← back to La Permits School

README.md

120 lines

# LA Permits — free data puller (school project)

A $0, no-API-key tool that pulls **City of Los Angeles building permits** from the
public LA Open Data Portal and writes a clean CSV — including **contractor,
applicant, and principal names**, project **valuation**, address, **APN**, and a
ready-made **California Secretary of State** lookup URL per contractor.

## ⚡ LIVE vs. historical (read this first)
The City **stopped publishing contractor/applicant NAMES in the open feed after
2023-05-19** (privacy/policy change). So you pick one:

- **Want CURRENT permits (updated ~daily)?** → `pull_permits_live.py` (feed `pi9x-tg5x`,
  refreshed to within ~2 days). Has valuation / type / address / **APN** / council
  district / status — but **no contractor names**. Enrich the **owner/developer** live
  via the LA County Assessor (by APN) and the contractor via an LADBS permit lookup.
  ```bash
  python3 pull_permits_live.py --min-valuation 5000000 --days 120   # -> permits_live.csv
  ```
- **Want contractor/applicant/principal NAMES?** → `pull_permits.py` (feed `d9aa-v8bm`),
  but that data is a **frozen 2013–2023 snapshot** — the last free named permit data.

**UPDATE — live names ARE recoverable per-permit.** LADBS's public "Permit & Inspection
Report" detail page is a plain GET keyed by the permit's three segments
(`.../PermitReport/PcisPermitDetail?id1=&id2=&id3=`) and renders
`Contractor <name>; Lic. No.: <license>`. So `enrich_live_contractor.py` recovers the
live contractor + CSLB license for each current permit — free, no browser, no login,
cached + rate-limited. The bulk open feed is still nameless; you just enrich per permit.

### Self-running loop
`run_pipeline.sh` = one full cycle (refresh live permits → recover contractors → merge
into the accumulating `master_live.csv` → regenerate `LIVE-BOARD.md` → commit). It has an
overlap lockfile and the LADBS fetch is cached, so cycles after the first are near-instant.
```bash
./run_pipeline.sh          # run one cycle by hand anytime
```
A launchd job (`~/Library/LaunchAgents/com.steve.la-permits-live-refresh.plist`, every 6h)
is provided but must be loaded by the user (the agent's harness blocks installing a
standing daemon):
```bash
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.steve.la-permits-live-refresh.plist
```

Two free public data sources, no signup, no cost:

| Source | What it gives | Access |
|---|---|---|
| **LADBS building permits** (`data.lacity.org`, dataset `d9aa-v8bm`) | Permit type, valuation, address, APN, **contractor / applicant / principal names**, license, stories, dwelling units | Socrata API — free, no key |
| **CA Secretary of State** (`bizfileonline.sos.ca.gov`) | Legal entity + agent for service of process behind a business name | Free web lookup (the tool generates the URL per contractor) |

## Quick start
No install needed — Python 3 standard library only.

```bash
python3 pull_permits.py                          # new builds + additions > $1M -> permits.csv
python3 pull_permits.py --min-valuation 5000000  # only projects over $5M
python3 pull_permits.py --permit-types "Bldg-New" --since 2020-01-01
python3 pull_permits.py --limit 500 --out sample.csv
```

### Options
- `--min-valuation N` — minimum project $ (default `1000000`; `0` = no filter)
- `--permit-types "A,B"` — e.g. `Bldg-New`, `Bldg-Addition`, `Bldg-Alter/Repair`,
  `Bldg-Demolition`, `Sign` (`""` = all types)
- `--since YYYY-MM-DD` — only permits issued on/after this date
- `--limit N` — cap rows written (`0` = all matches)
- `--out FILE.csv` — output path
- `--app-token TOKEN` — optional free Socrata token (only if you hit rate limits)

## The pipeline
1. **`pull_permits.py`** → bulk CSV of significant permits with the party names.
2. **`enrich_contractors.py`** → dedupe those permits to **unique contractors**,
   ranked by total project value, each with ready-to-click **CA SOS** (by name) and
   **CSLB** (by license #) lookup links:
   ```bash
   python3 enrich_contractors.py                 # permits.csv -> contractors.csv
   python3 enrich_contractors.py --min-permits 2 # firms with 2+ permits only
   ```
   Example: 1,000 permits → ~290 unique firms; top row = Hensel Phelps ($1.6B / 10
   permits). This deduped list IS your "top LA contractors" ranking.
3. **`summary.py`** → class-ready descriptive stats printed to the terminal
   (headline totals, permits & $ by year, top contractors, build $ by council
   district, permit-type mix, biggest single projects):
   ```bash
   python3 summary.py --top 8
   ```
4. **`make_chart.py`** → a slide-ready "value by year" bar chart (pure-stdlib SVG,
   2023 hatched as partial). Rasterize to PNG with a local renderer — no pip:
   ```bash
   python3 make_chart.py                         # -> value_by_year.svg
   # then PNG (either works, both free/local):
   "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new \
     --screenshot="$PWD/value_by_year.png" --window-size=960,540 "file://$PWD/value_by_year.svg"
   # or: qlmanage -t -s 1600 -o . value_by_year.svg   (square crop; Chrome keeps 16:9)
   ```
5. **CA SOS + CSLB** (free lookups) → open the URLs on the firms you care about to
   resolve the legal entity + agent (SOS) and license status/classification (CSLB).

### Why the enrichment is a worklist, not an auto-scrape
There is no reliable *free* HTTP endpoint for entity data: CA SOS bizfile sits behind
Imperva/Incapsula bot protection, OpenCorporates now requires a paid token, and CSLB
session-gates its license page. Rather than fight three anti-bot walls (or drive a real
browser, a ToS gray area), the tool **dedupes to the handful of unique firms** so a
guided manual lookup is trivial — and ranks them so you check the important ones first.

## Data notes
- `d9aa-v8bm` is a **stable historical snapshot: 2013-01 → 2023-05, ~317k permits**.
  Great for a reproducible course project. (The *live* daily permit feed exists but
  the name-bearing fields are login-gated; the open live feed `pi9x-tg5x` omits
  contractor/applicant names.)
- `apn` is the full LA County parcel id (book-page-parcel), e.g. `4129-027-902`.
- Real examples in a default run: the **$554M LAX addition** (Hensel Phelps) and
  the **$476M tower at 100 S Grand Ave** (Tishman).

## Compliance
Both sources are **public / open data**, free to reuse. This project uses only
public records — it does **not** touch paid or license-restricted databases
(e.g. Data Axle / Reference Solutions library edition, which is academic-use-only
and must not be redistributed). If this work is ever used beyond coursework,
re-check terms before republishing compiled lists.