[object Object]

← back to Rentv 2026

auto-save: 2026-08-05T12:42:02 (6 files) — data/markets.json data/news.json data/ticker.json src/pr/index.js src/pr/lib/geo.js

eb29d4a12a8dcc970f8027279ecb17b665aa2179 · 2026-08-05 12:42:03 -0700 · Steve Abrams

Files touched

Diff

commit eb29d4a12a8dcc970f8027279ecb17b665aa2179
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 5 12:42:03 2026 -0700

    auto-save: 2026-08-05T12:42:02 (6 files) — data/markets.json data/news.json data/ticker.json src/pr/index.js src/pr/lib/geo.js
---
 data/markets.json      | 16 ++++++++--------
 data/news.json         |  2 +-
 data/ticker.json       |  8 ++++----
 src/pr/index.js        | 22 +++++++++++++++-------
 src/pr/lib/geo.js      |  6 +++++-
 src/pr/lib/taxonomy.js | 26 +++++++++++++++++++++++++-
 6 files changed, 58 insertions(+), 22 deletions(-)

diff --git a/data/markets.json b/data/markets.json
index 423893b0..74d064a2 100644
--- a/data/markets.json
+++ b/data/markets.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-08-05T18:54:15.031Z",
+  "fetched_at": "2026-08-05T19:34:16.689Z",
   "source": "Yahoo Finance — live (CBOE Treasury yields, Dow Jones/MSCI REIT indices)",
   "curve": [
     {
@@ -16,7 +16,7 @@
     },
     {
       "label": "10-Year",
-      "value": "4.61%",
+      "value": "4.62%",
       "change_bps": -1,
       "dir": "down"
     },
@@ -30,20 +30,20 @@
   "spreads": [
     {
       "label": "30Y – 10Y (curve)",
-      "value": "+55 bps",
+      "value": "+56 bps",
       "dir": "up"
     },
     {
       "label": "10Y – 3M (curve)",
-      "value": "+88 bps",
+      "value": "+89 bps",
       "dir": "up"
     }
   ],
   "reits": [
     {
       "label": "DJ U.S. Real Estate",
-      "value": "394.61",
-      "change_pct": -0.37,
+      "value": "395.14",
+      "change_pct": -0.24,
       "dir": "down"
     },
     {
@@ -54,8 +54,8 @@
     },
     {
       "label": "Vanguard REIT ETF (VNQ)",
-      "value": "$98.55",
-      "change_pct": -0.52,
+      "value": "$98.67",
+      "change_pct": -0.4,
       "dir": "down"
     }
   ],
diff --git a/data/news.json b/data/news.json
index e99f878e..52db4e7b 100644
--- a/data/news.json
+++ b/data/news.json
@@ -1,6 +1,6 @@
 {
  "source": "rentv.com — live",
- "fetched_at": "2026-08-05T18:54:14.461Z",
+ "fetched_at": "2026-08-05T19:34:16.461Z",
  "count": 30,
  "items": [
   {
diff --git a/data/ticker.json b/data/ticker.json
index d8d7ba5d..65af0ab9 100644
--- a/data/ticker.json
+++ b/data/ticker.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-08-05T18:54:15.001Z",
+  "fetched_at": "2026-08-05T19:34:16.662Z",
   "la_office_vac": {
     "label": "LA OFFICE VAC",
     "value": "23.1%",
@@ -14,14 +14,14 @@
   },
   "cre_index": {
     "label": "CRE INDEX",
-    "value": "4,147",
-    "change_pct": -0.37,
+    "value": "4,153",
+    "change_pct": -0.24,
     "dir": "down",
     "source": "Dow Jones U.S. Real Estate"
   },
   "ten_yr": {
     "label": "10-YR",
-    "value": "4.61%",
+    "value": "4.62%",
     "change_bps": -1,
     "dir": "down",
     "source": "CBOE 10-Yr (^TNX)"
diff --git a/src/pr/index.js b/src/pr/index.js
index d66547bf..1fab610f 100644
--- a/src/pr/index.js
+++ b/src/pr/index.js
@@ -24,7 +24,7 @@ const providers = require('./services/email-providers');
 const jobs = require('./jobs');
 const adapters = require('./adapters');
 const geo = require('./lib/geo');
-const { ORG_TYPES, ASSET_CLASSES } = require('./lib/taxonomy');
+const { ORG_TYPES, ASSET_CLASSES, UNIVERSAL_TYPES, MAJOR_ONLY_TYPES } = require('./lib/taxonomy');
 
 const PAGES = ['index', 'research', 'organizations', 'people', 'review', 'campaigns', 'letters', 'inbox', 'tasks', 'sources', 'runs', 'settings'];
 
@@ -125,13 +125,21 @@ module.exports = function mountPR(app, { adminOnly, sendPage }) {
     const gate = await settings.get('california_gate', { passed: false });
     const azUnlocked = await settings.arizonaUnlocked();
     const coverage = await settings.get('coverage_report_CA', null);
-    // CA completion: verified CA orgs vs all CA orgs + metro×category cells with any coverage
+    // CA completion: metro×category cells with coverage, over only REALISTICALLY-FILLABLE cells.
+    // A cell counts iff its category is 'universal' (any metro) or 'major' in a MAJOR metro — this
+    // is the tier model (taxonomy.ORG_TYPE_TIER) that stops the denominator counting specialist
+    // firms in secondary metros where they don't exist (Steve 8/5: "fix the denominator").
     const caCells = await db.one(
-      `SELECT count(DISTINCT (m, organization_type))::int AS covered
-         FROM pr_organizations, unnest(metros) m
-        WHERE 'CA'=ANY(state_presence) AND lifecycle_status NOT IN ('duplicate','archived')
-          AND organization_type <> ALL($1::text[])`, [excludeTypes]);
-    const totalCells = geo.CA_METROS.length * (ORG_TYPES.length - excludeTypes.length);
+      `SELECT count(*)::int AS covered FROM (
+         SELECT DISTINCT m, organization_type
+           FROM pr_organizations, unnest(metros) m
+          WHERE 'CA'=ANY(state_presence) AND lifecycle_status NOT IN ('duplicate','archived')
+            AND ( organization_type = ANY($1::text[])
+                  OR (organization_type = ANY($2::text[]) AND m = ANY($3::text[])) )
+       ) fillable_covered`,
+      [UNIVERSAL_TYPES, MAJOR_ONLY_TYPES, geo.MAJOR_METRO_KEYS]);
+    const totalCells = UNIVERSAL_TYPES.length * geo.CA_METROS.length
+      + MAJOR_ONLY_TYPES.length * geo.MAJOR_METRO_KEYS.length;
     res.json({
       california: {
         organizations: orgTotals.ca,
diff --git a/src/pr/lib/geo.js b/src/pr/lib/geo.js
index 281e3a4c..c722af3e 100644
--- a/src/pr/lib/geo.js
+++ b/src/pr/lib/geo.js
@@ -29,6 +29,10 @@ const AZ_METROS = [
 
 const STATES = { CA: { label: 'California', metros: CA_METROS }, AZ: { label: 'Arizona', metros: AZ_METROS } };
 
+// Major CA metros — the coverage tier model expects 'major'-tier categories (finance/PR/
+// proptech) only here, not in secondary metros (Sacramento, Inland Empire, Central Coast/Valley).
+const MAJOR_METRO_KEYS = ['la', 'oc', 'san-diego', 'bay-area'];
+
 function metrosFor(state) { return (STATES[state] || {}).metros || []; }
 function metroLabel(state, key) {
   const m = metrosFor(state).find((x) => x.key === key);
@@ -47,4 +51,4 @@ function geoPriority(state, metroKey) {
   return Math.max(30, 100 - Math.round((i / Math.max(list.length - 1, 1)) * 60));
 }
 
-module.exports = { CA_METROS, AZ_METROS, STATES, metrosFor, metroLabel, metroForCounty, geoPriority };
+module.exports = { CA_METROS, AZ_METROS, MAJOR_METRO_KEYS, STATES, metrosFor, metroLabel, metroForCounty, geoPriority };
diff --git a/src/pr/lib/taxonomy.js b/src/pr/lib/taxonomy.js
index 00401556..ace20600 100644
--- a/src/pr/lib/taxonomy.js
+++ b/src/pr/lib/taxonomy.js
@@ -42,6 +42,30 @@ const ORG_TYPES = [
 const ORG_TYPE_KEYS = ORG_TYPES.map(([k]) => k);
 const ORG_TYPE_LABEL = Object.fromEntries(ORG_TYPES);
 
+// ── Coverage tier model (Steve 8/5: "fix the denominator") ──────────────────
+// The CA completion metric counts (metro × category) cells. Counting EVERY category in
+// EVERY metro over-inflates the denominator, because specialist finance/PR/proptech firms
+// don't realistically exist in every secondary metro. So each category is tiered:
+//   'universal' — realistically present in every CA metro (counts against all 8 metros)
+//   'major'     — concentrates in the big metros (counts only against MAJOR_METRO_KEYS)
+//   'excluded'  — banks/credit-unions: out of the PR-outreach denominator entirely
+// Denominator = universal×(all metros) + major×(major metros). Edit here to re-tune.
+const ORG_TYPE_TIER = {
+  brokerage: 'universal', developer_owner: 'universal', asset_property_mgmt: 'universal',
+  title_insurer: 'universal', title_agency: 'universal', escrow_company: 'universal',
+  commercial_escrow_dept: 'universal', law_firm: 'universal', accounting_consulting: 'universal',
+  appraiser: 'universal', insurance_risk: 'universal', architecture_firm: 'universal',
+  engineering_firm: 'universal', general_contractor: 'universal', construction_mgmt: 'universal',
+  economic_development: 'universal', municipal_re: 'universal', association: 'universal',
+  reit: 'major', facility_mgmt: 'major', commercial_mortgage_bank: 'major', debt_equity_broker: 'major',
+  construction_lender: 'major', bridge_lender: 'major', private_lender: 'major', cmbs_structured: 'major',
+  life_co_lender: 'major', exchange_1031: 'major', proptech: 'major', pr_agency: 'major',
+  marketing_agency: 'major', public_affairs: 'major', trade_publication: 'major',
+  bank: 'excluded', credit_union: 'excluded',
+};
+const UNIVERSAL_TYPES = ORG_TYPE_KEYS.filter((k) => ORG_TYPE_TIER[k] === 'universal');
+const MAJOR_ONLY_TYPES = ORG_TYPE_KEYS.filter((k) => ORG_TYPE_TIER[k] === 'major');
+
 const ASSET_CLASSES = [
   'office', 'industrial', 'logistics', 'retail', 'multifamily', 'hospitality',
   'medical_office', 'life_science', 'senior_housing', 'student_housing',
@@ -153,4 +177,4 @@ function roleRelevance(department, normalized_role) {
   return base;
 }
 
-module.exports = { ORG_TYPES, ORG_TYPE_KEYS, ORG_TYPE_LABEL, ASSET_CLASSES, classifyTitle, roleRelevance };
+module.exports = { ORG_TYPES, ORG_TYPE_KEYS, ORG_TYPE_LABEL, ORG_TYPE_TIER, UNIVERSAL_TYPES, MAJOR_ONLY_TYPES, ASSET_CLASSES, classifyTitle, roleRelevance };

← d1b78915 Content Studio: Boomer voice + video buttons — narrate the g  ·  back to Rentv 2026  ·  Boomer voice fix: re-clone from verified 7/31 VO (VOICE_ID c 9ea3fd53 →