[object Object]

← back to Rentv 2026

PR intel: add national CRE media + association-media + PR agencies

875aaec0fdb59f66aef10275d9ffeb5efacfcf78 · 2026-08-05 13:00:24 -0700 · Steve

- migration 004: submission_page_url / editorial_calendar_url / awards_url /
  events_url / media_kit_url columns on pr_organizations
- national-media-seed.js (npm run pr:seed:national): 54 curated candidates —
  national CRE publications, vertical trade media, regional business journals
  (ACBJ + independents), industry-association media, national CRE PR agencies
- taxonomy: editorial + media-business role rules (editor/reporter/publisher/
  assignment+newsletter editor/sponsored-content/ad-partnerships) -> editorial
  & media_business departments, weighted for outreach priority
- website adapter: auto-detect submission/editorial-calendar/awards/events/
  media-kit pages during verify-organization; thread new fields through
  create()/update() and the verify handler
- honest-by-construction: orgs seeded as 'discovered'; verify pipeline fills
  press pages, emails, editorial contacts from live sites (no fabricated data)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 875aaec0fdb59f66aef10275d9ffeb5efacfcf78
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 5 13:00:24 2026 -0700

    PR intel: add national CRE media + association-media + PR agencies
    
    - migration 004: submission_page_url / editorial_calendar_url / awards_url /
      events_url / media_kit_url columns on pr_organizations
    - national-media-seed.js (npm run pr:seed:national): 54 curated candidates —
      national CRE publications, vertical trade media, regional business journals
      (ACBJ + independents), industry-association media, national CRE PR agencies
    - taxonomy: editorial + media-business role rules (editor/reporter/publisher/
      assignment+newsletter editor/sponsored-content/ad-partnerships) -> editorial
      & media_business departments, weighted for outreach priority
    - website adapter: auto-detect submission/editorial-calendar/awards/events/
      media-kit pages during verify-organization; thread new fields through
      create()/update() and the verify handler
    - honest-by-construction: orgs seeded as 'discovered'; verify pipeline fills
      press pages, emails, editorial contacts from live sites (no fabricated data)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 package.json                              |   3 +-
 src/pr/adapters/website.js                |  15 ++-
 src/pr/jobs/index.js                      |   6 +
 src/pr/lib/taxonomy.js                    |  26 +++-
 src/pr/migrations/004_media_pr_fields.sql |  16 +++
 src/pr/seed/national-media-seed.js        | 196 ++++++++++++++++++++++++++++++
 src/pr/services/organizations.js          |  13 +-
 7 files changed, 268 insertions(+), 7 deletions(-)

diff --git a/package.json b/package.json
index fb1bc3f1..fc7361d3 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,8 @@
     "pr:worker": "node src/pr/worker.js",
     "pr:seed:ca": "node src/pr/seed/ca-seed.js",
     "test": "node --test test/pr/*.test.js",
-    "pr:seed:az": "node src/pr/seed/az-seed.js"
+    "pr:seed:az": "node src/pr/seed/az-seed.js",
+    "pr:seed:national": "node src/pr/seed/national-media-seed.js"
   },
   "dependencies": {
     "express": "^4.21.2",
diff --git a/src/pr/adapters/website.js b/src/pr/adapters/website.js
index 8c860ddb..cf5db05e 100644
--- a/src/pr/adapters/website.js
+++ b/src/pr/adapters/website.js
@@ -9,6 +9,12 @@ const PAGE_HINTS = {
   press: /news(room)?|press|media(-|\s)?(center|centre|room|contact|kit)|announcements/i,
   team: /team|people|leadership|our-people|professionals|staff|about-us\/(team|people|leadership)|management/i,
   contact: /contact/i,
+  // Media/PR outreach pages (publications, trade media, association media):
+  submission: /submit|press[\s-]?release|news[\s-]?tip|share[\s-]?(your[\s-]?)?news|send[\s-]?(us[\s-]?)?news|contribute|editorial[\s-]?submission|for[\s-]?(the[\s-]?)?press|story[\s-]?ideas?/i,
+  editorial_calendar: /editorial[\s-]?calendar|media[\s-]?(planner|guide|pack)|content[\s-]?calendar/i,
+  awards: /awards?|best[\s-]?of|honou?rs|rankings?|power[\s-]?(list|100|players)/i,
+  events: /events?|conferences?|webinars?|summit|forum/i,
+  media_kit: /media[\s-]?kit|advertise|advertising|sponsorship|partnerships?/i,
 };
 
 const EMAIL_RE = /\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/gi;
@@ -200,7 +206,7 @@ const adapter = register({
    */
   async profileOrganization(websiteUrl) {
     const errors = [];
-    const item = { website_ok: false, press_page_url: null, newsroom_url: null, contact_form_url: null, general_press_email: null, general_press_phone: null, press_emails: [], team_pages: [], people: [], evidence: [] };
+    const item = { website_ok: false, press_page_url: null, newsroom_url: null, contact_form_url: null, general_press_email: null, general_press_phone: null, press_emails: [], team_pages: [], people: [], evidence: [], submission_page_url: null, editorial_calendar_url: null, awards_url: null, events_url: null, media_kit_url: null };
     let home;
     try {
       home = await politeFetch(websiteUrl, { provider: 'website' });
@@ -223,6 +229,13 @@ const adapter = register({
     item.contact_form_url = firstMatch(PAGE_HINTS.contact);
     const teamUrl = firstMatch(PAGE_HINTS.team);
     if (teamUrl) item.team_pages.push(teamUrl);
+    // Media/PR outreach pages — link-level discovery from the homepage nav/footer.
+    // editorial_calendar checked before events so "editorial calendar" ≠ a plain event calendar.
+    item.submission_page_url = firstMatch(PAGE_HINTS.submission);
+    item.editorial_calendar_url = firstMatch(PAGE_HINTS.editorial_calendar);
+    item.awards_url = firstMatch(PAGE_HINTS.awards);
+    item.events_url = firstMatch(PAGE_HINTS.events);
+    item.media_kit_url = firstMatch(PAGE_HINTS.media_kit);
 
     // Harvest emails/phones from home + press + contact pages (max 3 fetches beyond home).
     const pagesToScan = [{ url: base, body: home.body }];
diff --git a/src/pr/jobs/index.js b/src/pr/jobs/index.js
index 94f68f54..af5132d6 100644
--- a/src/pr/jobs/index.js
+++ b/src/pr/jobs/index.js
@@ -230,6 +230,12 @@ const handlers = {
     if (item.contact_form_url) patch.contact_form_url = item.contact_form_url;
     if (item.general_press_email) patch.general_press_email = item.general_press_email;
     if (item.general_press_phone) patch.general_press_phone = item.general_press_phone;
+    // Media/PR outreach pages (submission / editorial calendar / awards / events / media kit)
+    if (item.submission_page_url) patch.submission_page_url = item.submission_page_url;
+    if (item.editorial_calendar_url) patch.editorial_calendar_url = item.editorial_calendar_url;
+    if (item.awards_url) patch.awards_url = item.awards_url;
+    if (item.events_url) patch.events_url = item.events_url;
+    if (item.media_kit_url) patch.media_kit_url = item.media_kit_url;
     patch.internal_pr_status = item.press_emails.length || item.press_page_url ? 'has_internal_pr' : org.internal_pr_status;
     await organizations.update(orgId, patch, 'system:verify-organization');
     for (const ev of item.evidence || []) {
diff --git a/src/pr/lib/taxonomy.js b/src/pr/lib/taxonomy.js
index ace20600..a46021ca 100644
--- a/src/pr/lib/taxonomy.js
+++ b/src/pr/lib/taxonomy.js
@@ -76,6 +76,27 @@ const ASSET_CLASSES = [
 // ── Contact roles: exact-title patterns → normalized role + department. ──────
 // Order matters: first match wins; more specific patterns first.
 const ROLE_RULES = [
+  // Editorial & newsroom (publications, trade media, association media) — checked first
+  // so a publication's own staff classify as editorial, not fall through to leadership.
+  [/editor[\s-]?in[\s-]?chief|editor in chief/i,             'editor_in_chief',              'editorial'],
+  [/executive editor/i,                                      'executive_editor',             'editorial'],
+  [/managing editor/i,                                       'managing_editor',              'editorial'],
+  [/editorial director|director[^a-z]*(of )?editorial/i,     'editorial_director',           'editorial'],
+  [/assignment editor|news editor/i,                         'news_editor',                  'editorial'],
+  [/newsletter editor/i,                                     'newsletter_editor',            'editorial'],
+  [/(web|digital|online) editor/i,                           'digital_editor',               'editorial'],
+  [/(deputy|senior|associate|assistant) editor/i,            'senior_editor',                'editorial'],
+  [/contributing editor|columnist|contributor/i,             'contributing_editor',          'editorial'],
+  [/(senior|staff)[^a-z]*(reporter|writer)/i,                'staff_writer',                 'editorial'],
+  [/reporter|journalist|correspondent/i,                     'reporter',                     'editorial'],
+  [/copy editor/i,                                           'copy_editor',                  'editorial'],
+  [/(^|[^a-z])editor(\b|s\b)/i,                              'editor',                       'editorial'],
+  // Media business side (publications) — publisher, events, awards, sponsored/branded content, ad partnerships
+  [/associate publisher|publisher/i,                         'publisher',                    'media_business'],
+  [/(conference|events?)[^a-z]*producer|event producer/i,    'event_producer',               'media_business'],
+  [/awards?[^a-z]*(program|manager|coordinator|director|lead)/i, 'awards_program',            'media_business'],
+  [/(sponsored|branded|custom)[^a-z]*content|content studio/i, 'sponsored_content',          'media_business'],
+  [/(advertising|ad sales|sponsorship)[^a-z]*(director|manager|sales|lead)?|media (sales|partnerships?)/i, 'advertising_partnerships', 'media_business'],
   // Communications & PR
   [/chief communications officer|cco\b/i,                    'chief_communications_officer', 'communications'],
   [/head of communications/i,                                'head_of_communications',       'communications'],
@@ -170,10 +191,13 @@ function classifyTitle(exactTitle) {
 
 // Role relevance for outreach-priority scoring (0..100): comms/PR first, then marketing,
 // then BD/agency, then leadership, then lending/title contacts.
-const DEPT_RELEVANCE = { communications: 100, agency: 92, marketing: 85, bd: 72, leadership: 65, lending: 60, title_escrow: 60, other: 30 };
+// editorial = the story-pitch target at a publication (editor/reporter) — highest value.
+// media_business = publisher/events/awards/sponsored-content/ad-partnerships outreach.
+const DEPT_RELEVANCE = { editorial: 100, communications: 100, agency: 92, marketing: 85, media_business: 82, bd: 72, leadership: 65, lending: 60, title_escrow: 60, other: 30 };
 function roleRelevance(department, normalized_role) {
   let base = DEPT_RELEVANCE[department] != null ? DEPT_RELEVANCE[department] : 30;
   if (/press_officer|media_relations|public_relations|director_communications|head_of_communications|chief_communications/.test(normalized_role || '')) base = 100;
+  if (/editor_in_chief|managing_editor|executive_editor|editorial_director|news_editor|reporter|staff_writer/.test(normalized_role || '')) base = 100;
   return base;
 }
 
diff --git a/src/pr/migrations/004_media_pr_fields.sql b/src/pr/migrations/004_media_pr_fields.sql
new file mode 100644
index 00000000..52e7d590
--- /dev/null
+++ b/src/pr/migrations/004_media_pr_fields.sql
@@ -0,0 +1,16 @@
+-- CRE PR Intelligence — media/PR-outreach fields.
+-- Additive only. Adds the publication/association-media outreach URLs that the media
+-- expansion (national CRE publications, trade media, association media) needs so the
+-- data model matches the required field list: submission page (press-release
+-- instructions), editorial calendar (planned topics), awards (nomination
+-- opportunities), events (conferences), and media kit. All nullable; populated by the
+-- verify-organization job from each org's own live site, and shown in the admin UI.
+
+ALTER TABLE pr_organizations ADD COLUMN IF NOT EXISTS submission_page_url    text; -- press-release / news-tip / contribute page
+ALTER TABLE pr_organizations ADD COLUMN IF NOT EXISTS editorial_calendar_url text; -- planned topics / media planner
+ALTER TABLE pr_organizations ADD COLUMN IF NOT EXISTS awards_url             text; -- awards / rankings / nomination page
+ALTER TABLE pr_organizations ADD COLUMN IF NOT EXISTS events_url             text; -- conferences / webinars / event calendar
+ALTER TABLE pr_organizations ADD COLUMN IF NOT EXISTS media_kit_url          text; -- media kit / advertising & partnerships
+
+-- 'editorial' is a first-class department for people at publications/association media
+-- (editors, reporters, publishers). No enum to alter — department is free text.
diff --git a/src/pr/seed/national-media-seed.js b/src/pr/seed/national-media-seed.js
new file mode 100644
index 00000000..ea68bacb
--- /dev/null
+++ b/src/pr/seed/national-media-seed.js
@@ -0,0 +1,196 @@
+'use strict';
+// National CRE MEDIA + ASSOCIATION-MEDIA + PR-agency seed — HONEST BY CONSTRUCTION.
+//
+// Adds the commercial-real-estate news publications, trade/vertical media, regional
+// business journals, industry-association media, and national CRE PR agencies to the
+// PR-intelligence database as `discovered` candidates. Each then gets a
+// verify-organization job that fetches the LIVE site to confirm it and extract the
+// real per-org fields the request asks for:
+//   contact name/title/email/LinkedIn  → discover-people (org's own team/press pages)
+//   website / press page / newsroom     → verify-organization
+//   submission page / editorial calendar / awards / events / media kit → verify-organization
+//                                          (PAGE_HINTS in adapters/website.js)
+//   market / property sectors            → state_presence + metros + asset_classes (below)
+//   last verified                        → last_verified_at (set by verify)
+//
+// We seed ORGANIZATIONS only. Editors, reporters, publishers, assignment/newsletter
+// editors, sponsored-content and ad-partnership contacts are PEOPLE — they are extracted
+// from each org's own published pages by the pipeline (never hand-fabricated here).
+// In-house communications departments surface as internal_pr_status + those people.
+//
+// Idempotent: exact domain matches union geography instead of duplicating; same-apex
+// network brands (ACBJ business journals, France Media regional editions) are modeled as
+// one org with the editions documented in notes rather than colliding needs_review rows.
+//
+// Run: npm run pr:seed:national   (then: npm run pr:worker)
+const db = require('../db');
+const organizations = require('../services/organizations');
+const jobs = require('../jobs');
+
+const US = ['US'];
+const NAT = ['national'];
+
+// Defaults: states=US, metros=national, counties=[], assets=[], tags=[].
+// { name, domain, type, subtype?, states?, metros?, counties?, assets?, internal_pr?, tags?, notes? }
+const ORGS = [
+  // ── National CRE trade publications ────────────────────────────────────────
+  { name: 'RENTV', domain: 'rentv.com', type: 'trade_publication', subtype: 'cre_broadcast_publication',
+    tags: ['first_party'], notes: 'RENTV network (first-party; included for completeness of the media map).' },
+  { name: 'Commercial Property Executive', domain: 'cpexecutive.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['office', 'industrial', 'retail', 'multifamily'], notes: 'CPE (Yardi/CommercialEdge). Editorial now at commercialsearch.com/news.' },
+  { name: 'CoStar News', domain: 'costar.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'CoStar News = costar.com/news (paywalled data + news).' },
+  { name: 'Commercial Observer', domain: 'commercialobserver.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['office', 'multifamily'], notes: 'NYC-based; national CRE + finance coverage.' },
+  { name: 'Bisnow', domain: 'bisnow.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'National event + newsletter network; many metro editions (LA, OC, SoCal, etc.).' },
+  { name: 'GlobeSt.com', domain: 'globest.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'ALM; national CRE news + events (RealShare).' },
+  { name: 'The Real Deal', domain: 'therealdeal.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'National real estate news (CRE + resi development).' },
+  { name: 'Connect CRE', domain: 'connectcre.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'National + regional CRE news (California, etc.).' },
+  { name: 'REBusinessOnline', domain: 'rebusinessonline.com', type: 'trade_publication', subtype: 'cre_publication',
+    notes: 'France Media Inc. Regional print editions: Western, Heartland, Northeast, Southeast, Texas Real Estate Business + California Centers. Distinct-domain siblings: Shopping Center Business, Student Housing Business, Seniors Housing Business.' },
+  { name: 'Multi-Housing News', domain: 'multihousingnews.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['multifamily', 'affordable_housing'], notes: 'Yardi/CommercialEdge; multifamily.' },
+  { name: 'National Real Estate Investor', domain: 'nreionline.com', type: 'trade_publication', subtype: 'cre_publication',
+    tags: ['archived_brand'], notes: 'Legacy NREI brand; content merged into WealthManagement.com (Informa). May redirect.' },
+  { name: 'Shopping Center Business', domain: 'shoppingcenterbusiness.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['retail'], notes: 'France Media; retail / shopping-center CRE. Regional retail sibling: California Centers.' },
+  { name: 'Student Housing Business', domain: 'studenthousingbusiness.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['student_housing'], notes: 'France Media; student housing.' },
+  { name: 'Seniors Housing Business', domain: 'seniorshousingbusiness.com', type: 'trade_publication', subtype: 'cre_publication',
+    assets: ['senior_housing'], notes: 'France Media; seniors housing.' },
+  { name: 'Propmodo', domain: 'propmodo.com', type: 'trade_publication', subtype: 'proptech_media',
+    notes: 'Proptech + built-world media and research.' },
+  { name: 'CREtech', domain: 'cretech.com', type: 'trade_publication', subtype: 'proptech_media',
+    notes: 'CRE technology news + events.' },
+  { name: 'Blueprint', domain: 'blueprintvegas.com', type: 'trade_publication', subtype: 'proptech_events',
+    tags: ['domain_unverified'], notes: 'Proptech / real-estate innovation conference & media (Blueprint).' },
+  { name: 'The Registry', domain: 'theregistrysf.com', type: 'trade_publication', subtype: 'cre_publication',
+    states: US.concat('CA'), metros: ['bay-area'], notes: 'NorCal (SF Bay) + Pacific NW CRE.' },
+
+  // ── Vertical / sector trade media ──────────────────────────────────────────
+  { name: 'Urban Land', domain: 'urbanland.uli.org', type: 'trade_publication', subtype: 'association_magazine',
+    notes: 'ULI’s magazine (Urban Land). Parent: Urban Land Institute (uli.org).' },
+  { name: 'Building Design+Construction', domain: 'bdcnetwork.com', type: 'trade_publication', subtype: 'aec_media',
+    assets: ['office', 'mixed_use'], notes: 'AEC — building design + construction.' },
+  { name: 'Engineering News-Record', domain: 'enr.com', type: 'trade_publication', subtype: 'construction_media',
+    notes: 'ENR — construction industry news + rankings (Top 400 Contractors).' },
+  { name: 'Construction Dive', domain: 'constructiondive.com', type: 'trade_publication', subtype: 'construction_media',
+    notes: 'Industry Dive; construction news.' },
+  { name: 'Hotel Business', domain: 'hotelbusiness.com', type: 'trade_publication', subtype: 'hospitality_media',
+    assets: ['hospitality'], notes: 'ICD Publications; hotel industry.' },
+  { name: 'Hotel Management', domain: 'hotelmanagement.net', type: 'trade_publication', subtype: 'hospitality_media',
+    assets: ['hospitality'], notes: 'Questex; hotel operations/development.' },
+  { name: 'Hospitality Design', domain: 'hospitalitydesign.com', type: 'trade_publication', subtype: 'hospitality_media',
+    assets: ['hospitality'], notes: 'Emerald; hospitality design (HD).' },
+  { name: 'Senior Housing News', domain: 'seniorhousingnews.com', type: 'trade_publication', subtype: 'seniors_media',
+    assets: ['senior_housing'], notes: 'Aging Media Network.' },
+  { name: 'Affordable Housing Finance', domain: 'housingfinance.com', type: 'trade_publication', subtype: 'finance_media',
+    assets: ['affordable_housing'], notes: 'Zonda; AHF.' },
+  { name: 'Mortgage Professional America', domain: 'mpamag.com', type: 'trade_publication', subtype: 'finance_media',
+    notes: 'KM Business Information; mortgage industry (MPA).' },
+  { name: 'Scotsman Guide', domain: 'scotsmanguide.com', type: 'trade_publication', subtype: 'finance_media',
+    notes: 'Commercial + residential mortgage lending media (Commercial edition).' },
+  { name: 'National Mortgage Professional', domain: 'nationalmortgageprofessional.com', type: 'trade_publication', subtype: 'finance_media',
+    notes: 'NMP Media; mortgage industry.' },
+
+  // ── Regional business journals (real-estate sections) ──────────────────────
+  { name: 'American City Business Journals', domain: 'bizjournals.com', type: 'trade_publication', subtype: 'regional_business_journal_network',
+    states: US.concat(['CA', 'AZ', 'CO', 'TX', 'FL']), metros: ['bay-area', 'sacramento'],
+    notes: 'ACBJ network — CRE sections at bizjournals.com/<city>/news/commercial-real-estate. Editions requested: San Francisco Business Times, Phoenix, Sacramento, Denver, Dallas, Houston, South Florida Business Journals. Per-market CRE reporters are the outreach targets.' },
+  { name: 'Orange County Business Journal', domain: 'ocbj.com', type: 'trade_publication', subtype: 'regional_business_journal',
+    states: US.concat('CA'), metros: ['oc'], counties: ['Orange'], notes: 'Independent (not ACBJ). Real estate section.' },
+  // (Los Angeles Business Journal + San Diego Business Journal already seeded in ca-seed.)
+
+  // ── Industry & association media ───────────────────────────────────────────
+  { name: 'Urban Land Institute', domain: 'uli.org', type: 'association', subtype: 'industry_association',
+    assets: ['mixed_use', 'office', 'multifamily'], notes: 'ULI. Media: Urban Land magazine (urbanland.uli.org). Awards + fall/spring meetings.' },
+  { name: 'NAIOP', domain: 'naiop.org', type: 'association', subtype: 'industry_association',
+    assets: ['office', 'industrial', 'logistics'], notes: 'NAIOP (Commercial Real Estate Development Assn). Media: Development magazine. Awards + CRE.Converge.' },
+  { name: 'ICSC', domain: 'icsc.com', type: 'association', subtype: 'industry_association',
+    assets: ['retail'], notes: 'ICSC. Media: Commerce + Communities Today (CCT). Events: ICSC LAS VEGAS + regional.' },
+  { name: 'BOMA International', domain: 'boma.org', type: 'association', subtype: 'industry_association',
+    assets: ['office'], notes: 'BOMA. Media: BOMA Magazine. TOBY Awards + annual conference.' },
+  { name: 'CCIM Institute', domain: 'ccim.com', type: 'association', subtype: 'industry_association',
+    notes: 'CCIM. Media: Commercial Investment Real Estate (CIRE) magazine.' },
+  { name: 'SIOR', domain: 'sior.com', type: 'association', subtype: 'industry_association',
+    assets: ['industrial', 'office'], notes: 'Society of Industrial and Office Realtors. SIOR Report magazine.' },
+  { name: 'NMHC', domain: 'nmhc.org', type: 'association', subtype: 'industry_association',
+    assets: ['multifamily'], notes: 'National Multifamily Housing Council. Annual meeting, OpTech.' },
+  { name: 'National Apartment Association', domain: 'naahq.org', type: 'association', subtype: 'industry_association',
+    assets: ['multifamily'], notes: 'NAA. units magazine. Apartmentalize event.' },
+  { name: 'Mortgage Bankers Association', domain: 'mba.org', type: 'association', subtype: 'finance_association',
+    notes: 'MBA. CREF/Multifamily Convention. MBA NewsLink.' },
+  { name: 'CRE Finance Council', domain: 'crefc.org', type: 'association', subtype: 'finance_association',
+    notes: 'CREFC. Conferences (January, June, Miami).' },
+  { name: 'CoreNet Global', domain: 'corenetglobal.org', type: 'association', subtype: 'industry_association',
+    assets: ['office'], notes: 'Corporate real estate. LEADER magazine. Global Summit.' },
+  { name: 'American Institute of Architects', domain: 'aia.org', type: 'association', subtype: 'design_association',
+    notes: 'AIA. Architect magazine + AIA Conference on Architecture.' },
+  { name: 'American Society of Landscape Architects', domain: 'asla.org', type: 'association', subtype: 'design_association',
+    notes: 'ASLA. Landscape Architecture Magazine (LAM).' },
+  { name: 'U.S. Green Building Council', domain: 'usgbc.org', type: 'association', subtype: 'sustainability_association',
+    notes: 'USGBC (LEED). Greenbuild conference + media.' },
+  { name: 'Construction Management Association of America', domain: 'cmaanet.org', type: 'association', subtype: 'construction_association',
+    notes: 'CMAA.' },
+
+  // ── National CRE PR / communications agencies ──────────────────────────────
+  { name: 'Great Ink Communications', domain: 'greatink.com', type: 'pr_agency', notes: 'NYC CRE-specialist PR.' },
+  { name: 'Marino', domain: 'marinopr.com', type: 'pr_agency', notes: 'CRE + real estate PR (NY).' },
+  { name: 'BerlinRosen', domain: 'berlinrosen.com', type: 'pr_agency', notes: 'National PR; real estate practice.' },
+  { name: 'Rubenstein', domain: 'rubenstein.com', type: 'pr_agency', notes: 'Real estate PR (NY).' },
+  { name: 'Beckerman PR', domain: 'beckermanpr.com', type: 'pr_agency', notes: 'CRE / real estate PR (NJ/NY).' },
+  { name: 'Lou Hammond Group', domain: 'louhammond.com', type: 'pr_agency', notes: 'Hospitality + real estate PR.' },
+  { name: 'Gregory FCA', domain: 'gregoryfca.com', type: 'pr_agency', notes: 'Financial + real estate PR.' },
+];
+
+async function main() {
+  const h = await db.health();
+  if (!h.ok) { console.error('DB unavailable: ' + h.reason); process.exit(1); }
+  await db.runMigrations({ log: (m) => console.log('[migrate] ' + m) });
+  let created = 0, matched = 0, verifyJobs = 0;
+  for (const o of ORGS) {
+    const r = await organizations.create({
+      display_name: o.name,
+      website_url: 'https://www.' + o.domain,
+      organization_type: o.type,
+      organization_subtype: o.subtype || null,
+      asset_classes: o.assets || [],
+      state_presence: o.states || US,
+      metros: o.metros || NAT,
+      counties: o.counties || [],
+      internal_pr_status: o.internal_pr || 'unknown',
+      tags: (o.tags || []).concat('media-map'),
+      notes: o.notes || null,
+      lifecycle_status: 'discovered',
+    }, {
+      evidence: {
+        source: {
+          source_type: 'manual',
+          source_name: 'National CRE media + PR curated seed list',
+          url: 'https://www.' + o.domain,
+          short_excerpt: 'Curated candidate entry — real CRE media/association/PR organization pending live-website verification by the pipeline. Not treated as verified until verify-organization confirms against the live site.',
+          is_primary_source: false,
+          license_or_usage_note: 'Candidate list only; all facts must be re-verified against primary sources before outreach.',
+          adapter: 'seed',
+        },
+        fields: { organization: 45 },
+      },
+      actor: 'system:national-media-seed',
+    });
+    if (r.created) created++; else matched++;
+    const j = await jobs.enqueue('verify-organization', { organization_id: r.org.id }, { priority: 5, actor: 'system:national-media-seed' });
+    if (!j.deduped) verifyJobs++;
+  }
+  await jobs.enqueue('detect-duplicates', {}, { priority: 8, actor: 'system:national-media-seed' });
+  await jobs.enqueue('calculate-scores', {}, { priority: 9, actor: 'system:national-media-seed' });
+  console.log(`[national-media-seed] curated candidates: ${ORGS.length} (created ${created}, matched-existing ${matched})`);
+  console.log(`[national-media-seed] verify-organization jobs queued: ${verifyJobs}`);
+  console.log('[national-media-seed] run the worker to verify + extract editorial contacts: npm run pr:worker');
+  process.exit(0);
+}
+
+main().catch((e) => { console.error(e); process.exit(1); });
diff --git a/src/pr/services/organizations.js b/src/pr/services/organizations.js
index c6d834d4..8934d426 100644
--- a/src/pr/services/organizations.js
+++ b/src/pr/services/organizations.js
@@ -72,8 +72,9 @@ async function create(data, { evidence, actor } = {}) {
           organization_type, organization_subtype, asset_classes, headquarters, state_presence,
           metros, counties, office_addresses, parent_organization_id, internal_pr_status,
           press_page_url, newsroom_url, general_press_email, general_press_phone, contact_form_url,
-          verification_status, lifecycle_status, notes, tags, import_batch_id)
-       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26)
+          verification_status, lifecycle_status, notes, tags, import_batch_id,
+          submission_page_url, editorial_calendar_url, awards_url, events_url, media_kit_url)
+       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31)
        RETURNING *`,
       [clean(data.legal_name, 300) || null, display_name, normalized_name, domain,
        clean(data.website_url, 500) || null, clean(data.linkedin_company_url, 500) || null,
@@ -87,7 +88,10 @@ async function create(data, { evidence, actor } = {}) {
        clean(data.contact_form_url, 500) || null,
        data.verification_status || 'unverified',
        uncertain.length ? 'needs_review' : (data.lifecycle_status || 'discovered'),
-       clean(data.notes, 4000) || null, data.tags || [], data.import_batch_id || null]);
+       clean(data.notes, 4000) || null, data.tags || [], data.import_batch_id || null,
+       clean(data.submission_page_url, 500) || null, clean(data.editorial_calendar_url, 500) || null,
+       clean(data.awards_url, 500) || null, clean(data.events_url, 500) || null,
+       clean(data.media_kit_url, 500) || null]);
     const org = r.rows[0];
     if (evidence && evidence.source) {
       const src = await sources.findOrCreateSource(evidence.source, client);
@@ -188,7 +192,8 @@ async function update(id, patch, actor) {
     'organization_subtype', 'asset_classes', 'headquarters', 'state_presence', 'metros', 'counties',
     'office_addresses', 'parent_organization_id', 'internal_pr_status', 'press_page_url', 'newsroom_url',
     'general_press_email', 'general_press_phone', 'contact_form_url', 'verification_status',
-    'lifecycle_status', 'notes', 'tags', 'last_verified_at'];
+    'lifecycle_status', 'notes', 'tags', 'last_verified_at',
+    'submission_page_url', 'editorial_calendar_url', 'awards_url', 'events_url', 'media_kit_url'];
   const sets = []; const params = [];
   for (const k of allowed) {
     if (patch[k] === undefined) continue;

← 955211ec RENTV: deals+registry ride the 20-min news cron (auto-accumu  ·  back to Rentv 2026  ·  deploy: exclude data/deals-registry.json so deploys never cl 06fe8285 →