← back to Rentv Adintel

README.md

95 lines

# RENTV Advertiser Intelligence Viewer

One easy web viewer that identifies and documents the **real companies advertising,
sponsoring, exhibiting, and speaking** in commercial-real-estate media — so Steve Bloom
and other non-technical RENTV users can search, understand, and download the intelligence
without training. **California first, Arizona second**, then the western U.S.

The tool distinguishes **facts from guesses**: it never labels a company an advertiser
just because it operates in CRE, sat on a panel, or was mentioned in an article. Every
displayed field is traceable to evidence, and a one-click **Download Everything** export
produces CSV, XLSX, JSON, a ZIP, and a self-contained offline HTML viewer.

Stack: **Express + PostgreSQL + vanilla front-end** (the house convention), Node 20+,
served on `http://localhost:9814`.

## Quick start

```bash
createdb rentv_advertisers      # PostgreSQL 16+ must be running
npm install
npm run db:migrate              # create the tables
npm run db:seed                 # load RENTV seed data
npm start                       # -> http://localhost:9814
```

Open **http://localhost:9814** and go to `/advertisers`.
Run the tests with `npm test` (`node --test test/*.test.js`).

## Setup wizard — plain-English steps (§35)

The first-run wizard walks a non-technical user through setup. Each step reports
**Success**, **Needs Attention**, or **Skipped** — never a technical stack trace.

1. **Choose Existing RENTV App or Standalone.** This build defaults to Standalone,
   exposed at `/advertisers`.
2. **Test the database.** Confirms Postgres is reachable (`createdb rentv_advertisers`
   done, connection OK).
3. **Load RENTV seed data.** `npm run db:seed` — the 5 verified advertisers, the CoStar
   content-partner, panelist-only participants, dated rate + audience snapshots.
4. **Upload the two flyer images** if not auto-detected (Property Spotlight Flyer REV
   April 2025 + Corp Flyer Apr 2026 V3). If absent, an admin upload card appears and the
   build continues with placeholder records.
5. **Connect GA4 or choose CSV import.** Add a Google service-account JSON to go live, or
   import a GA4 CSV export. Runs on DEMO fixtures until connected.
6. **Connect Search Console or choose CSV import.** Same pattern for organic search data.
7. **Choose a search provider or Manual Search.** Default is Manual (one-click search
   links); a permitted search API key can be added later.
8. **Choose California markets** (Greater LA → OC → Inland Empire → San Diego → Ventura →
   Bay Area → Sacramento → Central Valley → Statewide).
9. **Choose Arizona markets** (Phoenix metro → Scottsdale → Tempe → Mesa → Chandler →
   Gilbert → Glendale → Tucson → Statewide).
10. **Run a safe demo research job** — uses fixtures and manual-review links only; no
    automated public-web fetching (that stays off by default).
11. **Open the advertiser table** at `/advertisers` and search a verified advertiser.
12. **Download a sample export** — the Download-Everything ZIP with the offline
    `executive-viewer.html`.

## Where exports land

The **Download Everything** button generates a ZIP named
`RENTV-Advertiser-Intelligence-YYYY-MM-DD.zip` containing the CSV/XLSX/JSON datasets, a
`thumbnails/` and export-allowed `evidence/` folder, `source-manifest.json`,
`methodology.json`, `export-audit.json`, and a self-contained `executive-viewer.html` you
can double-click to open with no server. Full manifest + rights filtering:
[`docs/EXPORT_FORMAT.md`](docs/EXPORT_FORMAT.md).

## How to add credentials

Copy `.env.example` to `.env`. The app runs on DEMO fixtures with nothing configured.

- **GA4 / Search Console (DEMO → LIVE):** set `GOOGLE_SERVICE_ACCOUNT_JSON_BASE64`,
  `GA4_PROPERTY_ID`, `GSC_SITE_URL`, then `npm run analytics:ga4` and
  `npm run analytics:gsc`. Details in
  [`docs/ANALYTICS_CONNECTORS.md`](docs/ANALYTICS_CONNECTORS.md).
- **Optional Google Ads:** `GOOGLE_ADS_ENABLED="true"` + the six `GOOGLE_ADS_*` values.
- **Optional Gmail import:** `GMAIL_IMPORT_ENABLED="true"` + the `GMAIL_*` values
  (admin-only, least-privilege OAuth).
- **Search provider:** set `SEARCH_PROVIDER` and the matching key; default is `manual`.

Secrets are server-only and redacted from logs. A **live/production deploy is
Steve-gated** — local run is unrestricted.

## Documentation

| Doc | What it covers |
|-----|----------------|
| [`docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md`](docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md) | Honest built-vs-blocked status; §36 criteria matrix |
| [`docs/ADVERTISER_DATA_POLICY.md`](docs/ADVERTISER_DATA_POLICY.md) | Privacy/source rules as operational policy |
| [`docs/ADVERTISER_SOURCE_REGISTER.md`](docs/ADVERTISER_SOURCE_REGISTER.md) | Seeded source policies + the review-before-automation rule |
| [`docs/ADVERTISER_RESEARCH_METHOD.md`](docs/ADVERTISER_RESEARCH_METHOD.md) | Classification, entity resolution, LinkedIn/contact workflow |
| [`docs/ANALYTICS_CONNECTORS.md`](docs/ANALYTICS_CONNECTORS.md) | GA4 / GSC / Google Ads / CSV; DEMO → LIVE; formulas |
| [`docs/EXPORT_FORMAT.md`](docs/EXPORT_FORMAT.md) | ZIP manifest, offline viewer, rights filtering |
| [`docs/DEPLOYMENT_ADVERTISER_VIEWER.md`](docs/DEPLOYMENT_ADVERTISER_VIEWER.md) | Local run, env vars, Steve-gated deploy |
| [`docs/SECURITY_ADVERTISER_VIEWER.md`](docs/SECURITY_ADVERTISER_VIEWER.md) | CSP, SSRF guard, LinkedIn block, EXIF, audit, secrets |