← back to Rentv Adintel
docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md
99 lines
# RENTV Advertiser Intelligence — Build Status
_Honest status of what is built vs. what is genuinely blocked. Updated at the docs/tests pass._
This is the RENTV Advertiser Intelligence Viewer — a California-first (Arizona-second)
tool 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. The stack is the house convention: **Express + PostgreSQL + vanilla
front-end** (not the Next.js/Prisma default in the master prompt — the prompt says to
use the repository's existing sound stack), Node 20+, CommonJS, served on
`http://localhost:9814`.
## Built this pass
- **Database schema + migrations** — normalized PostgreSQL model (organizations,
people, contact_points, markets, publications, placements, campaigns, ad_sightings,
creative_assets, events, event_relationships, the source/provenance tables, the
analytics tables, and the sales workspace) at `db/schema.sql`, applied by
`db/migrate.js`.
- **Seed data** — the five verified RENTV advertiser/sponsor examples (Hanley
Investment Group, Chase Partners, Fidelity Mortgage Lenders, Rockefeller Group,
Provident Savings Bank), the CoStar Group **content-partner** record (NOT a paid
sponsor), the panelist-only / prospect conference participants, dated RENTV
rate-card and audience snapshots, and the seeded source policies —
`db/seed/`.
- **Compliance guards (enforced in code, not just docs)** —
- `lib/compliance/fetch-guard.js` — SSRF guard + hard LinkedIn host block on every
outbound fetch; blocks private / loopback / link-local / metadata IP ranges.
- `lib/compliance/no-inferred-email.js` — refuses to store a contact without
explicit public evidence; detects pattern-generated emails so they can be rejected.
- `lib/compliance/source-policy.js` — rejects any policy that enables automation
against CoStar / LoopNet / ZoomInfo / Apollo / MLS / LinkedIn.
- **Shared contract** — `lib/types.js` (statuses, categories, role priority, LinkedIn
blocked hosts, `normalizeName`) and `lib/scoring.js` (the §13 opportunity score with
transparent, admin-overridable weights and `explainScore`).
- **UI viewer** — Simple-View executive UX served by Express with a persistent search,
California / Arizona / All switch, Verified-Only switch, and a "Show Me Why" evidence
drawer. (Front-end pages are mounted optionally so the app boots before every module
lands.)
- **Fixture-backed GA4 / GSC** — deterministic demo fixtures under `fixtures/` drive the
analytics dashboards so the app is fully demonstrable with **no live Google
credentials**. Everything derived from them is labeled `DEMO DATA`.
- **Exports + offline viewer** — the one-click Download-Everything ZIP (CSV / XLSX /
JSON + `executive-viewer.html`) with rights-aware filtering.
- **Tests** — `node --test` suite (built-in runner, no jest/vitest) covering scoring,
the shared vocabulary, the compliance guards, the classification guard, and
rights-aware export filtering.
## Genuine external blockers (not our code — real access limits)
1. **No live Google service-account credentials.** GA4 (§17) and Search Console (§18)
run on **DEMO fixtures**. Adding `GOOGLE_SERVICE_ACCOUNT_JSON_BASE64` +
`GA4_PROPERTY_ID` + `GSC_SITE_URL` (see `docs/ANALYTICS_CONNECTORS.md`) switches
them from demo to live with no code change.
2. **The two flyer JPGs were not found on disk.** `/mnt/data/Property Spotlight Flyer
REV April 2025.jpg` and `/mnt/data/Corp Flyer Apr 2026 V3.jpg` are absent, so the
build created **placeholder creative_assets records + an admin upload card** (§22)
instead of real thumbnails. Upload them via the admin card to complete the RENTV
products/evidence page.
3. **Playwright screenshot capture is NOT implemented this pass.** The screenshot /
thumbnail service (§15) is specified but not wired; ad evidence currently uses
link-only preview state + neutral placeholders. This is deferred, not faked.
4. **Redis / BullMQ durable queues deferred.** Research jobs (§23) run as **synchronous
stubs**; the durable-queue path is designed but not required for the demo and is off
by default.
5. **Live public-web research disabled by default, per policy (§6, §30).**
`ALLOW_AUTOMATED_PUBLIC_WEB_RESEARCH="false"` — the app ships with search set to
`manual` (one-click human search links). No source runs automation until its policy
is reviewed and enabled.
## §36 completion criteria — met / partial / deferred
| # | Criterion | Status |
|---|-----------|--------|
| 1 | App runs locally | **Met** — `npm start` → `http://localhost:9814` |
| 2 | Migrations and seed succeed | **Met** — `npm run db:migrate && npm run db:seed` |
| 3 | Five verified advertiser/sponsor examples appear with evidence status | **Met** — seeded |
| 4 | Panelists not mislabeled as sponsors | **Met** — CoStar seeded as content-partner (event_relationships, never an ad_sighting); 22 panelists seeded SPEAKER_OR_PANELIST_ONLY / LIKELY_PROSPECT with zero sponsor status; `assertNotPanelistMislabeledAsSponsor` is wired LIVE into `POST /api/v1/review/:id/verify` (returns 409 + audit `verify_blocked` on an evidence-less promotion), plus seed-time + unit test |
| 5 | Two flyer assets appear when available | **Partial** — placeholder + admin upload card (JPGs absent) |
| 6 | California / Arizona filters work | **Met** |
| 7 | Public contact + LinkedIn discovery workflows work | **Met** — search-URL-only, Open/Search LinkedIn buttons, never fetched |
| 8 | LinkedIn automated fetching blocked and tested | **Met** — `fetch-guard` + `test/compliance.test.js` |
| 9 | GA4 + Search Console connectors or deterministic fixtures work | **Partial** — deterministic fixtures work; live connectors pending creds |
| 10 | Screenshots / thumbnails work for an allowed fixture page | **Deferred** — Playwright capture not implemented this pass |
| 11 | Conference sponsor viewer works | **Met** |
| 12 | One-click export → CSV, XLSX, JSON, ZIP, offline HTML | **Met** (SQLite snapshot optional / deferred) |
| 13 | All tests, typecheck, lint, production build pass | **Partial** — `node --test` suite passes; no TS/build step in this stack |
| 14 | Docs explain exactly how to add credentials and sources | **Met** — `docs/` (this file + 8 others) |
| 15 | This build-status file lists completed work + blockers | **Met** — this file |
## Tests — current result
`node --test test/*.test.js`: **33 pass, 11 skip, 0 fail** (44 total).
The 11 skips are the classification + export-filter suites, which
guard-skip while `lib/classification.js` and `src/export/rights.js` are still being
written by teammates; they light up automatically once those modules land. The scoring,
types, and compliance suites all pass.