[object Object]

← back to Rentv Adintel

auto-data-snapshot: 2026-08-07T16:25:56 (10 data files) — docs/ADVERTISER_DATA_POLICY.md docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md fixtures/ga4-acquisition.json fixtures/ga4-daily.json fixtures/ga4-geo.json

a0476aba824fda2f896d2e6e9e887fab67df6c51 · 2026-08-07 16:26:48 -0700 · auto-commit-fleet

Files touched

Diff

commit a0476aba824fda2f896d2e6e9e887fab67df6c51
Author: auto-commit-fleet <steve@designerwallcoverings.com>
Date:   Fri Aug 7 16:26:48 2026 -0700

    auto-data-snapshot: 2026-08-07T16:25:56 (10 data files) — docs/ADVERTISER_DATA_POLICY.md docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md fixtures/ga4-acquisition.json fixtures/ga4-daily.json fixtures/ga4-geo.json
---
 docs/ADVERTISER_DATA_POLICY.md               | 81 +++++++++++++++++++++++
 docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md | 98 ++++++++++++++++++++++++++++
 fixtures/ga4-acquisition.json                | 23 +++++++
 fixtures/ga4-daily.json                      | 35 ++++++++++
 fixtures/ga4-geo.json                        | 37 +++++++++++
 fixtures/ga4-landing.json                    | 25 +++++++
 fixtures/gsc-pages.json                      | 25 +++++++
 fixtures/gsc-queries.json                    | 35 ++++++++++
 fixtures/sample-constant-contact.csv         | 10 +++
 fixtures/sample-gsc-export.csv               | 23 +++++++
 10 files changed, 392 insertions(+)

diff --git a/docs/ADVERTISER_DATA_POLICY.md b/docs/ADVERTISER_DATA_POLICY.md
new file mode 100644
index 0000000..bfb2929
--- /dev/null
+++ b/docs/ADVERTISER_DATA_POLICY.md
@@ -0,0 +1,81 @@
+# RENTV Advertiser Intelligence — Data & Source Policy
+
+The privacy, research, and source rules from the master prompt (§6), restated as
+**operational policy**. Every rule here is enforced in code, not merely documented —
+see `docs/SECURITY_ADVERTISER_VIEWER.md` for the enforcement points and the test suite.
+
+## 1. No LinkedIn scraping — ever
+
+- The generic fetcher hard-blocks `linkedin.com`, `www.linkedin.com`, `m.linkedin.com`,
+  and `lnkd.in` (and any subdomain). Enforced in `lib/compliance/fetch-guard.js`
+  (`assertFetchAllowed`) and tested in `test/compliance.test.js`.
+- We **never** crawl or copy LinkedIn profile or company page content.
+- LinkedIn **URLs** may be collected only from: a search-engine API result, a public
+  first-party company page, an official event/speaker page, a press release, or a manual
+  user entry. For each we store only the URL, the result title/snippet (when licensing
+  permits), the discovery query, the discovery date, and a manual-review status.
+- Every profile is opened by a human via **Open LinkedIn** / **Search LinkedIn**
+  buttons in a normal browser — the app never fetches it.
+
+## 2. No inferred / pattern-generated emails
+
+- We **never** infer or generate an email address from a naming pattern
+  (`first.last@domain`, `flast@domain`, etc.). `looksLikePatternEmail()` detects the
+  forbidden case so importers and the UI can refuse it.
+- A phone or email is stored **only** when it is explicitly published as a business
+  contact on a first-party company page, press release, media kit, event page,
+  government record, or public professional page — or manually entered by an authorized
+  user.
+
+## 3. Explicit-public-only contacts, with evidence
+
+- `assertContactEvidence(contact)` throws unless the contact carries a
+  `source_evidence_id` **and** `explicitly_public === true`. No evidence, no storage.
+- Every displayed factual field is traceable to an evidence record (source URL, owner,
+  title, observed date, retrieved date, access method, parser version, confidence,
+  rights/export status).
+- We never store home addresses, personal mobile numbers identified as private, dates of
+  birth, or sensitive/protected attributes.
+
+## 4. No invented ad spend
+
+- We do **not** estimate a company's ad spend. The opportunity score (§13) ranks RENTV
+  **sales opportunity** using activity counts, recency, fit, and evidence quality — never
+  invented dollars. The output must never be relabeled an "ad-spend estimate."
+
+## 5. Panelist ≠ sponsor; editorial ≠ advertising
+
+- A verified panelist/speaker is stored as `SPEAKER_OR_PANELIST_ONLY`. It may not be
+  promoted to `VERIFIED_CONFERENCE_SPONSOR` / `VERIFIED_ADVERTISER` without separate
+  sponsor evidence. Enforced by the classification guard and tested in
+  `test/classification.test.js`.
+- Editorial coverage is not advertising without a disclosure or a direct record. CoStar
+  Group is seeded as `VERIFIED_CONTENT_PARTNER`, not a paid sponsor.
+
+## 6. Suppression, correction, and do-not-contact are honored
+
+- Correction, suppression, and do-not-contact requests are respected in search, exports,
+  and outreach preparation. A suppressed or do-not-contact record is filtered out of
+  every export.
+
+## 7. Rights-aware exports
+
+- Exports honor suppression, do-not-contact, private notes, and source rights.
+  `applyExportRights(rows, kind)` filters out suppressed rows, do-not-contact /
+  export-disallowed contacts, private notes, and `INTERNAL_EVIDENCE_ONLY` creative
+  assets. Internal-only evidence images never leave the app.
+
+## 8. Only official APIs; no bypassing controls
+
+- Google Analytics, Search Console, Google Ads, Gmail, and search providers are accessed
+  only through their official APIs.
+- We never bypass authentication, paywalls, CAPTCHAs, robots directives, rate limits, or
+  anti-bot controls, and we never scrape proprietary login-gated databases (CoStar,
+  LoopNet, ZoomInfo, Apollo, MLS).
+- We do not auto-open email click-tracking URLs (that would create false clicks); the
+  tracking URL is stored and opened only by a deliberate human action.
+
+## 9. Reversible merges
+
+- Uncertain entities are never silently merged. Merges are reversible with a full
+  side-by-side review, evidence, conflicts, and undo (§12).
diff --git a/docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md b/docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md
new file mode 100644
index 0000000..9e20ee7
--- /dev/null
+++ b/docs/ADVERTISER_INTELLIGENCE_BUILD_STATUS.md
@@ -0,0 +1,98 @@
+# 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; classification guard + test enforce it |
+| 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.
diff --git a/fixtures/ga4-acquisition.json b/fixtures/ga4-acquisition.json
new file mode 100644
index 0000000..805ce90
--- /dev/null
+++ b/fixtures/ga4-acquisition.json
@@ -0,0 +1,23 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. Channel/source/medium breakdown with UTMs where present, 2026-07-08 to 2026-08-06.",
+  "rows": [
+    { "metric_date": "2026-08-06", "channel_group": "Organic Search", "session_source": "google", "session_medium": "organic", "session_campaign": "(not set)", "sessions": 18432, "users": 15412, "key_events": 531 },
+    { "metric_date": "2026-08-06", "channel_group": "Email", "session_source": "email", "session_medium": "email", "session_campaign": "july-2026-cre-digest", "sessions": 8741, "users": 8214, "key_events": 287 },
+    { "metric_date": "2026-08-06", "channel_group": "Direct", "session_source": "(direct)", "session_medium": "(none)", "session_campaign": "(not set)", "sessions": 7891, "users": 6598, "key_events": 241 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "bisnow.com", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 3214, "users": 2687, "key_events": 98 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "crexi.com", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 2987, "users": 2498, "key_events": 91 },
+    { "metric_date": "2026-08-06", "channel_group": "Social", "session_source": "linkedin.com", "session_medium": "social", "session_campaign": "(not set)", "sessions": 2341, "users": 2012, "key_events": 71 },
+    { "metric_date": "2026-08-06", "channel_group": "Email", "session_source": "constantcontact", "session_medium": "email", "session_campaign": "property-spotlight-aug-2026", "sessions": 2198, "users": 2071, "key_events": 68 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "costar.com", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 1876, "users": 1569, "key_events": 57 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "globest.com", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 1654, "users": 1383, "key_events": 50 },
+    { "metric_date": "2026-08-06", "channel_group": "Email", "session_source": "shared1.ccsend.com", "session_medium": "email", "session_campaign": "cre-talk-weekly", "sessions": 1432, "users": 1349, "key_events": 44 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "connect.media", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 1287, "users": 1076, "key_events": 39 },
+    { "metric_date": "2026-08-06", "channel_group": "Organic Search", "session_source": "bing", "session_medium": "organic", "session_campaign": "(not set)", "sessions": 1142, "users": 954, "key_events": 35 },
+    { "metric_date": "2026-08-06", "channel_group": "Social", "session_source": "twitter.com", "session_medium": "social", "session_campaign": "(not set)", "sessions": 987, "users": 849, "key_events": 30 },
+    { "metric_date": "2026-08-06", "channel_group": "Email", "session_source": "email", "session_medium": "email", "session_campaign": "cre-conference-preview", "sessions": 876, "users": 825, "key_events": 27 },
+    { "metric_date": "2026-08-06", "channel_group": "Paid Search", "session_source": "google", "session_medium": "cpc", "session_campaign": "rentv-brand-awareness", "sessions": 743, "users": 621, "key_events": 23 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "naiop.org", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 698, "users": 584, "key_events": 21 },
+    { "metric_date": "2026-08-06", "channel_group": "Referral", "session_source": "commercialobserver.com", "session_medium": "referral", "session_campaign": "(not set)", "sessions": 612, "users": 512, "key_events": 19 },
+    { "metric_date": "2026-08-06", "channel_group": "Unassigned", "session_source": "(other)", "session_medium": "(other)", "session_campaign": "(not set)", "sessions": 487, "users": 407, "key_events": 15 }
+  ]
+}
diff --git a/fixtures/ga4-daily.json b/fixtures/ga4-daily.json
new file mode 100644
index 0000000..4fdb982
--- /dev/null
+++ b/fixtures/ga4-daily.json
@@ -0,0 +1,35 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. 30-day window ending 2026-08-06. ~50k monthly users, CRE news site pattern.",
+  "rows": [
+    { "metric_date": "2026-07-08", "sessions": 1421, "total_users": 1187, "new_users": 631, "engaged_sessions": 892, "engagement_rate": 0.6277, "avg_engagement_time": 143.2, "views": 2104, "event_count": 9812, "key_events": 38 },
+    { "metric_date": "2026-07-09", "sessions": 1508, "total_users": 1261, "new_users": 674, "engaged_sessions": 951, "engagement_rate": 0.6306, "avg_engagement_time": 138.7, "views": 2237, "event_count": 10401, "key_events": 42 },
+    { "metric_date": "2026-07-10", "sessions": 1589, "total_users": 1332, "new_users": 703, "engaged_sessions": 1003, "engagement_rate": 0.6312, "avg_engagement_time": 147.1, "views": 2356, "event_count": 10918, "key_events": 51 },
+    { "metric_date": "2026-07-11", "sessions": 1502, "total_users": 1256, "new_users": 658, "engaged_sessions": 945, "engagement_rate": 0.6292, "avg_engagement_time": 141.3, "views": 2219, "event_count": 10234, "key_events": 45 },
+    { "metric_date": "2026-07-12", "sessions": 872, "total_users": 728, "new_users": 381, "engaged_sessions": 538, "engagement_rate": 0.6170, "avg_engagement_time": 122.4, "views": 1287, "event_count": 5921, "key_events": 19 },
+    { "metric_date": "2026-07-13", "sessions": 741, "total_users": 618, "new_users": 324, "engaged_sessions": 455, "engagement_rate": 0.6140, "avg_engagement_time": 118.6, "views": 1094, "event_count": 5034, "key_events": 14 },
+    { "metric_date": "2026-07-14", "sessions": 1614, "total_users": 1349, "new_users": 712, "engaged_sessions": 1022, "engagement_rate": 0.6333, "avg_engagement_time": 152.1, "views": 2391, "event_count": 11104, "key_events": 53 },
+    { "metric_date": "2026-07-15", "sessions": 1677, "total_users": 1403, "new_users": 741, "engaged_sessions": 1058, "engagement_rate": 0.6309, "avg_engagement_time": 149.8, "views": 2487, "event_count": 11521, "key_events": 57 },
+    { "metric_date": "2026-07-16", "sessions": 1723, "total_users": 1442, "new_users": 762, "engaged_sessions": 1089, "engagement_rate": 0.6320, "avg_engagement_time": 155.2, "views": 2554, "event_count": 11840, "key_events": 61 },
+    { "metric_date": "2026-07-17", "sessions": 1688, "total_users": 1411, "new_users": 748, "engaged_sessions": 1066, "engagement_rate": 0.6316, "avg_engagement_time": 150.9, "views": 2501, "event_count": 11612, "key_events": 58 },
+    { "metric_date": "2026-07-18", "sessions": 1635, "total_users": 1368, "new_users": 724, "engaged_sessions": 1031, "engagement_rate": 0.6307, "avg_engagement_time": 146.3, "views": 2422, "event_count": 11234, "key_events": 54 },
+    { "metric_date": "2026-07-19", "sessions": 891, "total_users": 744, "new_users": 391, "engaged_sessions": 547, "engagement_rate": 0.6139, "avg_engagement_time": 120.1, "views": 1315, "event_count": 6089, "key_events": 21 },
+    { "metric_date": "2026-07-20", "sessions": 756, "total_users": 631, "new_users": 332, "engaged_sessions": 462, "engagement_rate": 0.6111, "avg_engagement_time": 115.7, "views": 1114, "event_count": 5177, "key_events": 16 },
+    { "metric_date": "2026-07-21", "sessions": 1701, "total_users": 1422, "new_users": 752, "engaged_sessions": 1075, "engagement_rate": 0.6320, "avg_engagement_time": 153.4, "views": 2523, "event_count": 11680, "key_events": 59 },
+    { "metric_date": "2026-07-22", "sessions": 1782, "total_users": 1490, "new_users": 789, "engaged_sessions": 1126, "engagement_rate": 0.6319, "avg_engagement_time": 158.7, "views": 2641, "event_count": 12231, "key_events": 63 },
+    { "metric_date": "2026-07-23", "sessions": 1744, "total_users": 1459, "new_users": 771, "engaged_sessions": 1101, "engagement_rate": 0.6313, "avg_engagement_time": 156.2, "views": 2585, "event_count": 11982, "key_events": 60 },
+    { "metric_date": "2026-07-24", "sessions": 1698, "total_users": 1420, "new_users": 751, "engaged_sessions": 1072, "engagement_rate": 0.6313, "avg_engagement_time": 151.8, "views": 2515, "event_count": 11668, "key_events": 58 },
+    { "metric_date": "2026-07-25", "sessions": 1621, "total_users": 1356, "new_users": 717, "engaged_sessions": 1023, "engagement_rate": 0.6311, "avg_engagement_time": 147.1, "views": 2403, "event_count": 11142, "key_events": 54 },
+    { "metric_date": "2026-07-26", "sessions": 904, "total_users": 755, "new_users": 398, "engaged_sessions": 554, "engagement_rate": 0.6128, "avg_engagement_time": 121.3, "views": 1337, "event_count": 6191, "key_events": 22 },
+    { "metric_date": "2026-07-27", "sessions": 766, "total_users": 639, "new_users": 337, "engaged_sessions": 469, "engagement_rate": 0.6123, "avg_engagement_time": 117.2, "views": 1131, "event_count": 5248, "key_events": 17 },
+    { "metric_date": "2026-07-28", "sessions": 1714, "total_users": 1433, "new_users": 758, "engaged_sessions": 1083, "engagement_rate": 0.6318, "avg_engagement_time": 154.2, "views": 2540, "event_count": 11784, "key_events": 60 },
+    { "metric_date": "2026-07-29", "sessions": 1793, "total_users": 1499, "new_users": 794, "engaged_sessions": 1133, "engagement_rate": 0.6319, "avg_engagement_time": 159.8, "views": 2657, "event_count": 12312, "key_events": 64 },
+    { "metric_date": "2026-07-30", "sessions": 1754, "total_users": 1467, "new_users": 776, "engaged_sessions": 1108, "engagement_rate": 0.6318, "avg_engagement_time": 157.1, "views": 2599, "event_count": 12053, "key_events": 62 },
+    { "metric_date": "2026-07-31", "sessions": 1712, "total_users": 1432, "new_users": 757, "engaged_sessions": 1081, "engagement_rate": 0.6315, "avg_engagement_time": 153.6, "views": 2537, "event_count": 11764, "key_events": 59 },
+    { "metric_date": "2026-08-01", "sessions": 1643, "total_users": 1374, "new_users": 726, "engaged_sessions": 1037, "engagement_rate": 0.6312, "avg_engagement_time": 148.4, "views": 2434, "event_count": 11282, "key_events": 55 },
+    { "metric_date": "2026-08-02", "sessions": 917, "total_users": 766, "new_users": 404, "engaged_sessions": 562, "engagement_rate": 0.6131, "avg_engagement_time": 122.5, "views": 1356, "event_count": 6274, "key_events": 23 },
+    { "metric_date": "2026-08-03", "sessions": 776, "total_users": 648, "new_users": 341, "engaged_sessions": 476, "engagement_rate": 0.6134, "avg_engagement_time": 118.3, "views": 1147, "event_count": 5312, "key_events": 18 },
+    { "metric_date": "2026-08-04", "sessions": 1731, "total_users": 1448, "new_users": 766, "engaged_sessions": 1094, "engagement_rate": 0.6320, "avg_engagement_time": 155.8, "views": 2565, "event_count": 11894, "key_events": 61 },
+    { "metric_date": "2026-08-05", "sessions": 1808, "total_users": 1512, "new_users": 800, "engaged_sessions": 1142, "engagement_rate": 0.6318, "avg_engagement_time": 161.2, "views": 2680, "event_count": 12428, "key_events": 65 },
+    { "metric_date": "2026-08-06", "sessions": 1769, "total_users": 1479, "new_users": 782, "engaged_sessions": 1117, "engagement_rate": 0.6315, "avg_engagement_time": 158.4, "views": 2621, "event_count": 12144, "key_events": 63 }
+  ]
+}
diff --git a/fixtures/ga4-geo.json b/fixtures/ga4-geo.json
new file mode 100644
index 0000000..ab8b699
--- /dev/null
+++ b/fixtures/ga4-geo.json
@@ -0,0 +1,37 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. California-heavy + Arizona secondary audience, sampled 2026-07-08 to 2026-08-06.",
+  "rows": [
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Los Angeles", "sessions": 9871, "users": 8234 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Irvine", "sessions": 4312, "users": 3601 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "San Diego", "sessions": 3987, "users": 3324 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "San Francisco", "sessions": 3541, "users": 2954 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "El Segundo", "sessions": 2214, "users": 1847 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Burbank", "sessions": 1987, "users": 1658 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Long Beach", "sessions": 1834, "users": 1529 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Ontario", "sessions": 1712, "users": 1428 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Riverside", "sessions": 1598, "users": 1333 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Santa Ana", "sessions": 1487, "users": 1240 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Pasadena", "sessions": 1389, "users": 1158 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Oakland", "sessions": 1301, "users": 1086 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "San Jose", "sessions": 1218, "users": 1016 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Sacramento", "sessions": 1142, "users": 953 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "California", "city": "Ventura", "sessions": 1071, "users": 894 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Phoenix", "sessions": 4112, "users": 3430 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Scottsdale", "sessions": 2987, "users": 2491 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Tempe", "sessions": 1843, "users": 1538 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Mesa", "sessions": 1512, "users": 1261 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Chandler", "sessions": 1298, "users": 1082 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Gilbert", "sessions": 1087, "users": 907 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Glendale", "sessions": 921, "users": 768 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Arizona", "city": "Tucson", "sessions": 798, "users": 666 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Nevada", "city": "Las Vegas", "sessions": 1987, "users": 1659 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Texas", "city": "Dallas", "sessions": 1432, "users": 1196 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "New York", "city": "New York City", "sessions": 1287, "users": 1074 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Washington", "city": "Seattle", "sessions": 987, "users": 824 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Colorado", "city": "Denver", "sessions": 876, "users": 731 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Illinois", "city": "Chicago", "sessions": 812, "users": 678 },
+    { "metric_date": "2026-08-06", "country": "United States", "region": "Florida", "city": "Miami", "sessions": 743, "users": 620 },
+    { "metric_date": "2026-08-06", "country": "Canada", "region": "Ontario", "city": "Toronto", "sessions": 621, "users": 518 },
+    { "metric_date": "2026-08-06", "country": "United Kingdom", "region": "England", "city": "London", "sessions": 487, "users": 407 }
+  ]
+}
diff --git a/fixtures/ga4-landing.json b/fixtures/ga4-landing.json
new file mode 100644
index 0000000..cfa71c8
--- /dev/null
+++ b/fixtures/ga4-landing.json
@@ -0,0 +1,25 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. Top landing pages for a CRE news/video site, sampled over 2026-07-08 to 2026-08-06.",
+  "rows": [
+    { "metric_date": "2026-08-06", "landing_page": "/", "sessions": 14821, "users": 12344, "views": 21943, "engaged_sessions": 9332, "engagement_rate": 0.6297, "key_events": 298 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/los-angeles-office-market-q2-2026", "sessions": 3142, "users": 2801, "views": 4718, "engaged_sessions": 2187, "engagement_rate": 0.6960, "key_events": 97 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/orange-county-industrial-vacancy-drops", "sessions": 2891, "users": 2567, "views": 4337, "engaged_sessions": 2012, "engagement_rate": 0.6960, "key_events": 88 },
+    { "metric_date": "2026-08-06", "landing_page": "/cre-talk/chase-partners-commercial-lending", "sessions": 2543, "users": 2267, "views": 3821, "engaged_sessions": 1779, "engagement_rate": 0.6993, "key_events": 79 },
+    { "metric_date": "2026-08-06", "landing_page": "/property-spotlight/hanley-investment-net-lease", "sessions": 2387, "users": 2129, "views": 3584, "engaged_sessions": 1668, "engagement_rate": 0.6991, "key_events": 74 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/phoenix-multifamily-rent-growth-2026", "sessions": 2211, "users": 1972, "views": 3320, "engaged_sessions": 1545, "engagement_rate": 0.6987, "key_events": 68 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/inland-empire-industrial-leasing", "sessions": 2089, "users": 1864, "views": 3137, "engaged_sessions": 1461, "engagement_rate": 0.6994, "key_events": 64 },
+    { "metric_date": "2026-08-06", "landing_page": "/conferences/rentv-la-summit-2026", "sessions": 1934, "users": 1724, "views": 2904, "engaged_sessions": 1351, "engagement_rate": 0.6986, "key_events": 60 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/san-diego-retail-revival", "sessions": 1811, "users": 1616, "views": 2720, "engaged_sessions": 1267, "engagement_rate": 0.6997, "key_events": 56 },
+    { "metric_date": "2026-08-06", "landing_page": "/cre-talk/fidelity-mortgage-lenders-cre-financing", "sessions": 1698, "users": 1516, "views": 2550, "engaged_sessions": 1188, "engagement_rate": 0.6996, "key_events": 52 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/scottsdale-office-demand-surge", "sessions": 1589, "users": 1418, "views": 2386, "engaged_sessions": 1111, "engagement_rate": 0.6992, "key_events": 49 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/bay-area-life-sciences-leasing", "sessions": 1477, "users": 1318, "views": 2218, "engaged_sessions": 1034, "engagement_rate": 0.6999, "key_events": 45 },
+    { "metric_date": "2026-08-06", "landing_page": "/property-spotlight/rockefeller-group-la-tower", "sessions": 1388, "users": 1239, "views": 2084, "engaged_sessions": 971, "engagement_rate": 0.6994, "key_events": 43 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/capital-markets-debt-financing-california", "sessions": 1301, "users": 1161, "views": 1954, "engaged_sessions": 910, "engagement_rate": 0.6995, "key_events": 40 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/naiop-socal-awards-2026-nominees", "sessions": 1219, "users": 1089, "views": 1831, "engaged_sessions": 852, "engagement_rate": 0.6989, "key_events": 37 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/arizona-data-center-expansion", "sessions": 1142, "users": 1020, "views": 1716, "engaged_sessions": 799, "engagement_rate": 0.6997, "key_events": 35 },
+    { "metric_date": "2026-08-06", "landing_page": "/newsletter/july-2026-cre-digest", "sessions": 1071, "users": 957, "views": 1609, "engaged_sessions": 750, "engagement_rate": 0.6995, "key_events": 33 },
+    { "metric_date": "2026-08-06", "landing_page": "/cre-talk/provident-savings-bank-multifamily", "sessions": 1004, "users": 897, "views": 1508, "engaged_sessions": 702, "engagement_rate": 0.6992, "key_events": 31 },
+    { "metric_date": "2026-08-06", "landing_page": "/news/ventura-county-mixed-use-pipeline", "sessions": 942, "users": 841, "views": 1415, "engaged_sessions": 659, "engagement_rate": 0.6996, "key_events": 29 },
+    { "metric_date": "2026-08-06", "landing_page": "/advertise", "sessions": 887, "users": 792, "views": 1333, "engaged_sessions": 620, "engagement_rate": 0.6992, "key_events": 27 }
+  ]
+}
diff --git a/fixtures/gsc-pages.json b/fixtures/gsc-pages.json
new file mode 100644
index 0000000..34b03dd
--- /dev/null
+++ b/fixtures/gsc-pages.json
@@ -0,0 +1,25 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. Search Console page-level performance, 90-day window ending 2026-08-06.",
+  "rows": [
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/", "country": "usa", "device": "desktop", "clicks": 5214, "impressions": 7891, "ctr": 0.6607, "position": 1.1 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/los-angeles-office-market-q2-2026", "country": "usa", "device": "desktop", "clicks": 2987, "impressions": 41230, "ctr": 0.0725, "position": 4.2 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/orange-county-industrial-vacancy-drops", "country": "usa", "device": "desktop", "clicks": 2741, "impressions": 38870, "ctr": 0.0705, "position": 4.5 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/cre-talk/chase-partners-commercial-lending", "country": "usa", "device": "desktop", "clicks": 2512, "impressions": 8934, "ctr": 0.2813, "position": 2.1 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/property-spotlight/hanley-investment-net-lease", "country": "usa", "device": "desktop", "clicks": 2341, "impressions": 7821, "ctr": 0.2994, "position": 1.9 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/phoenix-multifamily-rent-growth-2026", "country": "usa", "device": "desktop", "clicks": 2187, "impressions": 34120, "ctr": 0.0641, "position": 5.1 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/inland-empire-industrial-leasing", "country": "usa", "device": "desktop", "clicks": 2041, "impressions": 29870, "ctr": 0.0683, "position": 4.8 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/conferences/rentv-la-summit-2026", "country": "usa", "device": "desktop", "clicks": 1897, "impressions": 12340, "ctr": 0.1537, "position": 2.8 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/san-diego-retail-revival", "country": "usa", "device": "desktop", "clicks": 1754, "impressions": 26780, "ctr": 0.0655, "position": 5.3 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/cre-talk/fidelity-mortgage-lenders-cre-financing", "country": "usa", "device": "desktop", "clicks": 1621, "impressions": 7234, "ctr": 0.2241, "position": 2.4 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/scottsdale-office-demand-surge", "country": "usa", "device": "desktop", "clicks": 1498, "impressions": 22140, "ctr": 0.0677, "position": 4.9 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/bay-area-life-sciences-leasing", "country": "usa", "device": "desktop", "clicks": 1387, "impressions": 28740, "ctr": 0.0483, "position": 6.7 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/property-spotlight/rockefeller-group-la-tower", "country": "usa", "device": "desktop", "clicks": 1284, "impressions": 6872, "ctr": 0.1868, "position": 2.6 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/capital-markets-debt-financing-california", "country": "usa", "device": "desktop", "clicks": 1187, "impressions": 31240, "ctr": 0.0380, "position": 7.8 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/advertise", "country": "usa", "device": "desktop", "clicks": 1102, "impressions": 4321, "ctr": 0.2551, "position": 2.2 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/newsletter/july-2026-cre-digest", "country": "usa", "device": "desktop", "clicks": 1023, "impressions": 5678, "ctr": 0.1801, "position": 3.1 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/cre-talk/provident-savings-bank-multifamily", "country": "usa", "device": "desktop", "clicks": 951, "impressions": 6234, "ctr": 0.1525, "position": 3.4 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/arizona-data-center-expansion", "country": "usa", "device": "desktop", "clicks": 887, "impressions": 14320, "ctr": 0.0620, "position": 5.6 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/los-angeles-office-sublease-availability-2026", "country": "usa", "device": "desktop", "clicks": 234, "impressions": 28970, "ctr": 0.0081, "position": 14.2 },
+    { "metric_date": "2026-08-06", "page": "https://www.rentv.com/news/title-company-commercial-escrow-los-angeles", "country": "usa", "device": "desktop", "clicks": 187, "impressions": 19870, "ctr": 0.0094, "position": 12.8 }
+  ]
+}
diff --git a/fixtures/gsc-queries.json b/fixtures/gsc-queries.json
new file mode 100644
index 0000000..8356560
--- /dev/null
+++ b/fixtures/gsc-queries.json
@@ -0,0 +1,35 @@
+{
+  "_note": "DEMO fixture — not real RENTV analytics. Organic search query data for a CRE news/video site, 90-day window ending 2026-08-06.",
+  "rows": [
+    { "metric_date": "2026-08-06", "query": "rentv commercial real estate news", "country": "usa", "device": "desktop", "clicks": 4312, "impressions": 8924, "ctr": 0.4832, "position": 1.2 },
+    { "metric_date": "2026-08-06", "query": "rentv cre talk", "country": "usa", "device": "desktop", "clicks": 3187, "impressions": 5412, "ctr": 0.5889, "position": 1.1 },
+    { "metric_date": "2026-08-06", "query": "rentv", "country": "usa", "device": "desktop", "clicks": 2941, "impressions": 4201, "ctr": 0.7001, "position": 1.0 },
+    { "metric_date": "2026-08-06", "query": "los angeles commercial real estate news 2026", "country": "usa", "device": "desktop", "clicks": 1876, "impressions": 28740, "ctr": 0.0653, "position": 4.7 },
+    { "metric_date": "2026-08-06", "query": "california commercial real estate market", "country": "usa", "device": "desktop", "clicks": 1654, "impressions": 31240, "ctr": 0.0530, "position": 5.2 },
+    { "metric_date": "2026-08-06", "query": "orange county industrial real estate 2026", "country": "usa", "device": "desktop", "clicks": 1543, "impressions": 19870, "ctr": 0.0777, "position": 3.8 },
+    { "metric_date": "2026-08-06", "query": "phoenix commercial real estate market 2026", "country": "usa", "device": "desktop", "clicks": 1432, "impressions": 22140, "ctr": 0.0647, "position": 4.1 },
+    { "metric_date": "2026-08-06", "query": "inland empire industrial leasing vacancy", "country": "usa", "device": "desktop", "clicks": 1321, "impressions": 17890, "ctr": 0.0739, "position": 4.3 },
+    { "metric_date": "2026-08-06", "query": "cre conference los angeles 2026", "country": "usa", "device": "desktop", "clicks": 1214, "impressions": 14320, "ctr": 0.0848, "position": 3.2 },
+    { "metric_date": "2026-08-06", "query": "commercial real estate financing california", "country": "usa", "device": "desktop", "clicks": 1109, "impressions": 19874, "ctr": 0.0558, "position": 5.9 },
+    { "metric_date": "2026-08-06", "query": "scottsdale commercial real estate office market", "country": "usa", "device": "desktop", "clicks": 1014, "impressions": 13421, "ctr": 0.0756, "position": 4.0 },
+    { "metric_date": "2026-08-06", "query": "san diego cre market report 2026", "country": "usa", "device": "desktop", "clicks": 934, "impressions": 12987, "ctr": 0.0719, "position": 4.2 },
+    { "metric_date": "2026-08-06", "query": "net lease investment california 2026", "country": "usa", "device": "desktop", "clicks": 869, "impressions": 15234, "ctr": 0.0571, "position": 5.7 },
+    { "metric_date": "2026-08-06", "query": "brokerage deal los angeles commercial property sale", "country": "usa", "device": "desktop", "clicks": 812, "impressions": 17890, "ctr": 0.0454, "position": 7.1 },
+    { "metric_date": "2026-08-06", "query": "multifamily apartment market phoenix az rent", "country": "usa", "device": "desktop", "clicks": 768, "impressions": 11234, "ctr": 0.0684, "position": 4.8 },
+    { "metric_date": "2026-08-06", "query": "naiop southern california awards 2026", "country": "usa", "device": "desktop", "clicks": 714, "impressions": 4321, "ctr": 0.1653, "position": 2.1 },
+    { "metric_date": "2026-08-06", "query": "cre lending debt fund california rates 2026", "country": "usa", "device": "desktop", "clicks": 662, "impressions": 14321, "ctr": 0.0462, "position": 6.8 },
+    { "metric_date": "2026-08-06", "query": "rentv property spotlight advertise", "country": "usa", "device": "desktop", "clicks": 621, "impressions": 1234, "ctr": 0.5031, "position": 1.4 },
+    { "metric_date": "2026-08-06", "query": "arizona data center market 2026", "country": "usa", "device": "desktop", "clicks": 578, "impressions": 8921, "ctr": 0.0648, "position": 5.1 },
+    { "metric_date": "2026-08-06", "query": "bay area life sciences lab space leasing", "country": "usa", "device": "desktop", "clicks": 534, "impressions": 10234, "ctr": 0.0522, "position": 6.2 },
+    { "metric_date": "2026-08-06", "query": "los angeles office sublease availability 2026", "country": "usa", "device": "desktop", "clicks": 492, "impressions": 21340, "ctr": 0.0230, "position": 9.4 },
+    { "metric_date": "2026-08-06", "query": "cre advertise newsletter sponsorship california", "country": "usa", "device": "desktop", "clicks": 451, "impressions": 2341, "ctr": 0.1926, "position": 2.3 },
+    { "metric_date": "2026-08-06", "query": "uli los angeles conference 2026 sponsors", "country": "usa", "device": "desktop", "clicks": 412, "impressions": 3212, "ctr": 0.1282, "position": 2.9 },
+    { "metric_date": "2026-08-06", "query": "ventura county mixed use development", "country": "usa", "device": "desktop", "clicks": 378, "impressions": 7890, "ctr": 0.0479, "position": 6.7 },
+    { "metric_date": "2026-08-06", "query": "commercial real estate video advertising", "country": "usa", "device": "desktop", "clicks": 341, "impressions": 5432, "ctr": 0.0628, "position": 5.4 },
+    { "metric_date": "2026-08-06", "query": "icsc southern california retail conference 2026", "country": "usa", "device": "desktop", "clicks": 312, "impressions": 4321, "ctr": 0.0722, "position": 4.9 },
+    { "metric_date": "2026-08-06", "query": "cre talk sponsor california commercial real estate", "country": "usa", "device": "desktop", "clicks": 287, "impressions": 1876, "ctr": 0.1530, "position": 2.7 },
+    { "metric_date": "2026-08-06", "query": "mesa gilbert chandler office space lease arizona", "country": "usa", "device": "desktop", "clicks": 263, "impressions": 6789, "ctr": 0.0387, "position": 7.8 },
+    { "metric_date": "2026-08-06", "query": "hanley investment group net lease properties", "country": "usa", "device": "desktop", "clicks": 241, "impressions": 2134, "ctr": 0.1129, "position": 3.1 },
+    { "metric_date": "2026-08-06", "query": "title company commercial escrow los angeles", "country": "usa", "device": "desktop", "clicks": 221, "impressions": 12340, "ctr": 0.0179, "position": 11.3 }
+  ]
+}
diff --git a/fixtures/sample-constant-contact.csv b/fixtures/sample-constant-contact.csv
new file mode 100644
index 0000000..107b28c
--- /dev/null
+++ b/fixtures/sample-constant-contact.csv
@@ -0,0 +1,10 @@
+# DEMO DATA — Constant Contact campaign export (not real RENTV data)
+Campaign Name,Send Date,Subject,Recipients,Emails Delivered,Times Opened,Unique Opens,Open Rate,Times Clicked,Unique Clicks,Click Rate,Bounces,Unsubscribes,Spam Reports
+July 2026 CRE Digest,2026-07-15,California CRE Market Update — July 2026,47832,47201,19824,15987,33.87%,4321,3214,6.81%,631,48,2
+Property Spotlight — Hanley Investment Net Lease Portfolio,2026-07-07,RENTV Property Spotlight: Premium Net Lease Opportunities by Hanley Investment Group,47654,47089,21342,17234,36.60%,5678,4321,9.18%,565,52,1
+CRE Talk Weekly — Chase Partners & Fidelity Mortgage,2026-06-29,This Week on CRE Talk: Chase Partners + Fidelity Mortgage Lenders,47421,46901,18234,14567,31.06%,3987,3012,6.42%,520,44,3
+RENTV LA Summit 2026 — Conference Preview,2026-06-15,Join Us: RENTV Los Angeles Summit 2026 — Sponsors & Agenda,46987,46489,22341,18012,38.75%,6234,4789,10.30%,498,61,2
+June 2026 CRE Digest,2026-06-15,California & Arizona CRE Market Update — June 2026,46812,46321,18987,15234,32.89%,4123,3089,6.67%,491,43,1
+Property Spotlight — Rockefeller Group LA Tower,2026-05-28,RENTV Property Spotlight: Rockefeller Group — Premier LA Office Tower,46541,46082,20124,16234,35.23%,5432,4098,8.89%,459,49,2
+CRE Talk Weekly — Provident Savings Bank,2026-05-14,This Week on CRE Talk: Provident Savings Bank Multifamily Financing,46234,45798,17654,14123,30.83%,3765,2876,6.28%,436,41,1
+May 2026 CRE Digest,2026-05-15,California CRE Market Update — May 2026,45987,45562,17987,14456,31.73%,3921,2987,6.56%,425,39,2
diff --git a/fixtures/sample-gsc-export.csv b/fixtures/sample-gsc-export.csv
new file mode 100644
index 0000000..429ed5d
--- /dev/null
+++ b/fixtures/sample-gsc-export.csv
@@ -0,0 +1,23 @@
+Top queries,,,
+# DEMO DATA — Google Search Console export (not real RENTV data)
+Query,Clicks,Impressions,CTR,Position
+rentv commercial real estate news,4312,8924,48.32%,1.2
+rentv cre talk,3187,5412,58.89%,1.1
+rentv,2941,4201,70.01%,1.0
+los angeles commercial real estate news 2026,1876,28740,6.53%,4.7
+california commercial real estate market,1654,31240,5.30%,5.2
+orange county industrial real estate 2026,1543,19870,7.77%,3.8
+phoenix commercial real estate market 2026,1432,22140,6.47%,4.1
+inland empire industrial leasing vacancy,1321,17890,7.39%,4.3
+cre conference los angeles 2026,1214,14320,8.48%,3.2
+commercial real estate financing california,1109,19874,5.58%,5.9
+scottsdale commercial real estate office market,1014,13421,7.56%,4.0
+san diego cre market report 2026,934,12987,7.19%,4.2
+net lease investment california 2026,869,15234,5.71%,5.7
+brokerage deal los angeles commercial property sale,812,17890,4.54%,7.1
+multifamily apartment market phoenix az rent,768,11234,6.84%,4.8
+naiop southern california awards 2026,714,4321,16.53%,2.1
+cre lending debt fund california rates 2026,662,14321,4.62%,6.8
+rentv property spotlight advertise,621,1234,50.31%,1.4
+arizona data center market 2026,578,8921,6.48%,5.1
+bay area life sciences lab space leasing,534,10234,5.22%,6.2

← fb83652 spine: scaffold RENTV Advertiser Intelligence (Express+pg ho  ·  back to Rentv Adintel  ·  auto-data-snapshot: 2026-08-07T16:56:57 (8 data files) — REA 806e0b0 →