← back to Ventura Corridor

README.md

93 lines

# Ventura Corridor

Local-only directory of every business on Ventura Blvd (Calabasas → North Hollywood). Inventory + front-page audit + SEO scoring + ownership classification + three view surfaces.

**Loopback only** — bound to `127.0.0.1:9780`, never exposed to LAN/tailnet/internet.

## Surfaces

| URL | Purpose |
|---|---|
| `http://127.0.0.1:9780/` | 2D Leaflet map · noir-themed · grid-search · live build-status strip |
| `http://127.0.0.1:9780/3d.html` | Three.js "signal corridor" · screenshot billboards on real Ventura Blvd polyline · drive-cam |
| `http://127.0.0.1:9780/wall.html` | Pinterest-grid of every captured front-page screenshot |

## Pipeline

```
ingest (OSM Overpass, free, no key) → 224 businesses
  ↓
crawl (Playwright, conc 20) → 113 front-page audits + screenshots
  ↓
score (12-signal SEO model) → tier A/B/C/D + per-category rank
  ↓
enrich:classify (heuristic ownership) → 180 indep / 23 corp / 20 franchise
  ↓
viewers (2D / 3D / wall)
```

## Layout

```
~/Projects/ventura-corridor
├── src/
│   ├── server/index.ts        # Express on 127.0.0.1:9780
│   ├── ingest/
│   │   ├── osm_overpass.ts    # primary (free, no key)
│   │   ├── here_discover.ts   # secondary (HERE_API_KEY required)
│   │   └── foursquare.ts      # secondary (FOURSQUARE_API_KEY required)
│   ├── crawl/front_page.ts    # Playwright crawler
│   ├── score/seo_score.ts     # 12-signal scorer
│   └── enrich/
│       ├── classify_ownership.ts
│       └── restaurant_owners.ts  # DDG-search; rate-limited, low yield
├── public/                    # 2D, 3D, wall
├── data/
│   ├── ventura-blvd-polyline.json   # 214-pt corridor centerline
│   ├── screenshots/                  # firm-NNN.jpg × 113
│   ├── raw/                          # firm-NNN.html × 113
│   └── last-verified.json
├── db/migrations/             # 001_init.sql · 002_enrichment.sql
├── ecosystem.config.cjs       # pm2
└── .env                       # local config
```

## Run

```bash
npm install
npm run migrate                 # apply schema
npm run ingest:overpass         # pull businesses
npm run crawl                   # capture front-page audits
npm run score                   # compute SEO score per business
npm run enrich:classify         # tag ownership class
pm2 start ecosystem.config.cjs  # start the read API + viewers
pm2 save
```

Health: `curl http://127.0.0.1:9780/api/health`

## Schema

- `businesses` (anchor) — 1 row per business · `(source, source_id)` UNIQUE
- `corridor_segments` — 26 polyline-segment rows for the boulevard
- `front_page_audits` — 1 row per crawl pass (HTTPS, viewport, schema.org count, alt-coverage, etc.)
- `seo_scores` — 1 row per scoring pass (12 weighted signals → total / tier / category_rank)
- `business_enrichment` — 1 row per business · ownership_class · parent_brand · best_contact_path
- `business_contacts` — many per business · LinkedIn / website-about / GMB sources
- `ingest_runs` — provenance log

## Standing rules in this codebase

- **Local-only.** Never bind to `0.0.0.0`. Never deploy to Kamatera without explicit deploy-intent confirmation.
- **No fabricated stats next to real entity names.** Same compliance discipline as the lawyer/doctor projects.
- **No customer-routing language anywhere.** This is a directory, not a referral service.
- **Grid-search is mandatory** on every surface (per MEMORY.md `feedback_every_site_grid_search.md`).
- **Color-dot indicator pattern is DW-micro-site only.** Not used here.

## Known gaps

- OSM coverage west of Tarzana is sparse (1 row in Woodland Hills, 0 in Calabasas/Tarzana). Layer HERE or Foursquare ingest when the keys are available.
- 26 sites failed the front-page crawl (DNS errors on chain-locator URLs, anti-bot walls). Re-crawl with longer timeouts is wired (`npm run crawl -- --rescore`).
- LinkedIn enrichment via DDG hit DDG's rate limit at request 40 of 89; only 1 contact captured. Apollo.io free tier (50 credits) would close this if Steve wants.