[object Object]

← back to Realestate Flyers

TK-10708: fold Cody top fix — render one real usre deal flyer (1111 Brickell $274M) + honest dedup count

d077573d63185c39482c0bf54ec11866a053e7f4 · 2026-08-24 19:52:17 -0700 · Steve Abrams

Contrarian FIX FIRST: index alone was bookkeeping; render one property-spotlight flyer
from a real usre recent_commercial_deals row to prove the RENTV-template->deal-data path
(compliance-clean, 0 re-hosted images, link-out only, Frank loan angle). Register it back
into flyer-index.json. Fix gross-vs-dedup candidate count (usre/CRCP closed-deal overlap).

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

Files touched

Diff

commit d077573d63185c39482c0bf54ec11866a053e7f4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 24 19:52:17 2026 -0700

    TK-10708: fold Cody top fix — render one real usre deal flyer (1111 Brickell $274M) + honest dedup count
    
    Contrarian FIX FIRST: index alone was bookkeeping; render one property-spotlight flyer
    from a real usre recent_commercial_deals row to prove the RENTV-template->deal-data path
    (compliance-clean, 0 re-hosted images, link-out only, Frank loan angle). Register it back
    into flyer-index.json. Fix gross-vs-dedup candidate count (usre/CRCP closed-deal overlap).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 PLAN.md                                         |  24 +++--
 data/flyer-index.json                           |  22 ++++-
 public/flyers/usre-1111-brickell-ave-miami.html |  63 ++++++++++++
 scripts/flyer-from-deal.mjs                     | 122 ++++++++++++++++++++++++
 scripts/index-flyers.mjs                        |  51 +++++++++-
 5 files changed, 266 insertions(+), 16 deletions(-)

diff --git a/PLAN.md b/PLAN.md
index b9ceb67..2e83154 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -61,13 +61,23 @@ first because both the factory and the storefront read it.
 - **RENTV:** `public/concepts/*.html` + `public/pdf/*.pdf` (rendered artifacts, the template).
 - **HomesOnSpec:** Kamatera prod DB / admin app (later — not local).
 
-## 6. The minimal first increment ("start") — SHIPPED
-
-`scripts/index-flyers.mjs` → `data/flyer-index.json`. Scans all four builds, records 6 rendered
-flyers + 4 sources + 9,209 candidates, computes rendered-coverage %. **$0, read-only, local,
-idempotent.** This is the smallest useful thing: it makes the aggregation *addressable* — every
-later piece (generator, UI, coverage canary) reads this one file. Nothing downstream can start
-without it.
+## 6. The minimal first increment ("start") — SHIPPED (index + one proven flyer)
+
+Two pieces, both shipped, both $0 / read-only / local / idempotent:
+
+1. **`scripts/index-flyers.mjs` → `data/flyer-index.json`** — the spine. Scans all four builds,
+   records rendered flyers + 4 sources + candidate counts (GROSS 9,209 / DEDUP-EST 6,011 —
+   honest about the usre↔CRCP closed-deal overlap, per Cody hole #2), computes coverage %.
+   Every later piece (generator, UI, coverage canary) reads this one file.
+2. **`scripts/flyer-from-deal.mjs` → `public/flyers/*.html`** — *the risk-retiring proof* (Cody's
+   top fix). Renders ONE real property-spotlight flyer from the top usre deal
+   (**1111 Brickell Ave, Miami — $274.35M office**) using the RENTV brand token system.
+   Validates the whole template→data path end-to-end AND the index schema in one pass.
+   **Compliance-clean: 0 re-hosted images (`grep -c "<img"` = 0), link-OUT block only, loan-officer
+   angle for Frank.** The generated flyer is auto-registered back into the index (`status: rendered`).
+
+Why this is the start (not just the index): the index alone was bookkeeping until a real
+artifact proved the template holds variable deal data with no listing photos. Now it does.
 
 ## 7. Dependencies
 
diff --git a/data/flyer-index.json b/data/flyer-index.json
index 8fc08b7..8728099 100644
--- a/data/flyer-index.json
+++ b/data/flyer-index.json
@@ -1,5 +1,5 @@
 {
-  "generated_at": "2026-08-25T02:48:22.071Z",
+  "generated_at": "2026-08-25T02:51:55.629Z",
   "ticket": "TK-10708",
   "agent": "re-flyers",
   "builds": {
@@ -9,12 +9,26 @@
     "homesonspec": "HomesOnSpec spec homes"
   },
   "summary": {
-    "rendered_flyers": 6,
+    "rendered_flyers": 7,
     "flyer_sources": 4,
-    "flyer_candidates_across_sources": 9209,
-    "rendered_coverage_pct": 0.0651
+    "flyer_candidates_gross_pre_dedupe": 9209,
+    "flyer_candidates_dedup_estimate": 6011,
+    "candidate_count_note": "GROSS sums every source; closed-deal sources (usre + CRCP) overlap heavily, so DEDUP_ESTIMATE counts the largest closed-deal source once + non-closed sources in full. True dedupe = canon(address)+city at flyer time.",
+    "rendered_coverage_pct_vs_dedup": 0.1163
   },
   "flyers": [
+    {
+      "id": "local:usre-1111-brickell-ave-miami",
+      "build": "usre",
+      "kind": "property-spotlight",
+      "title": "Property Spotlight — 1111 BRICKELL AVE, Miami",
+      "format": "US-Letter-816x1056",
+      "html_path": "~/Projects/realestate-flyers/public/flyers/usre-1111-brickell-ave-miami.html",
+      "pdf_path": null,
+      "pdf_bytes": null,
+      "public_url": null,
+      "status": "rendered"
+    },
     {
       "id": "rentv:concept-1-editorial-media-kit",
       "build": "rentv",
diff --git a/public/flyers/usre-1111-brickell-ave-miami.html b/public/flyers/usre-1111-brickell-ave-miami.html
new file mode 100644
index 0000000..fb57c03
--- /dev/null
+++ b/public/flyers/usre-1111-brickell-ave-miami.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Property Spotlight — 1111 BRICKELL AVE, Miami</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700;900&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+  :root{--blue:#16357A;--navy:#0E2350;--red:#D0202E;--gold:#E8A81C;--ink:#141414;--paper:#fff;--gray:#6B7280;--panel:#F4F6FB;--rule:#D9DFF0;--pw:816px;--ph:1056px}
+  html,body{width:var(--pw);min-height:var(--ph);background:#CBD5E0;font-family:'Inter',Helvetica,Arial,sans-serif;-webkit-print-color-adjust:exact;print-color-adjust:exact}
+  .page{width:var(--pw);height:var(--ph);background:var(--paper);position:relative;overflow:hidden;display:flex;flex-direction:column}
+  .top-bar{height:6px;background:linear-gradient(90deg,var(--navy),var(--blue) 40%,var(--gold))}
+  .head{padding:32px 44px 20px;display:flex;justify-content:space-between;align-items:flex-start}
+  .kicker{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:700}
+  .brand{font-family:'Playfair Display',Georgia,serif;font-weight:900;font-size:22px;color:var(--blue)}
+  .brand small{display:block;font-family:Inter;font-weight:500;font-size:10px;letter-spacing:.06em;color:var(--gray);text-transform:uppercase}
+  h1{font-family:'Playfair Display',Georgia,serif;font-weight:700;font-size:40px;line-height:1.05;color:var(--ink);padding:0 44px}
+  .sub{padding:6px 44px 0;font-size:15px;color:var(--gray)}
+  .price-band{margin:26px 44px 0;background:var(--navy);color:#fff;border-radius:10px;padding:22px 28px;display:flex;justify-content:space-between;align-items:center}
+  .price-band .p{font-family:'Playfair Display',serif;font-weight:900;font-size:38px;color:var(--gold-lt,#F5C842)}
+  .price-band .lab{font-size:11px;letter-spacing:.16em;text-transform:uppercase;opacity:.85}
+  .facts{margin:26px 44px 0;display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
+  .fact{background:var(--panel);border:1px solid var(--rule);border-radius:8px;padding:14px 16px}
+  .fact .k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--gray);font-weight:600}
+  .fact .v{font-size:18px;font-weight:700;color:var(--ink);margin-top:3px}
+  .loan{margin:26px 44px 0;border-left:4px solid var(--red);background:#FCF3F3;border-radius:0 8px 8px 0;padding:16px 20px}
+  .loan .t{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--red);font-weight:700}
+  .loan .b{font-size:15px;color:var(--ink);margin-top:6px;line-height:1.5}
+  .linkout{margin:24px 44px 0;padding:16px 20px;border:1px dashed var(--rule);border-radius:8px}
+  .linkout .t{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--blue);font-weight:700;margin-bottom:8px}
+  .linkout a{color:var(--blue);font-weight:600;text-decoration:none}
+  .foot{margin-top:auto;padding:20px 44px;border-top:1px solid var(--rule);font-size:11px;color:var(--gray);display:flex;justify-content:space-between}
+  @page{size:letter;margin:0}
+  @media print{body{background:#fff}}
+</style></head>
+<body><div class="page">
+  <div class="top-bar"></div>
+  <div class="head">
+    <div><div class="kicker">Property Spotlight · Closed Deal</div></div>
+    <div class="brand">RENTV<small>Cutting-edge CRE media coverage</small></div>
+  </div>
+  <h1>1111 BRICKELL AVE</h1>
+  <div class="sub">Miami, Miami-Dade County · Office</div>
+  <div class="price-band">
+    <div><div class="lab">Recorded sale price</div><div class="p">$274,350,000</div></div>
+    <div style="text-align:right"><div class="lab">Sale date</div><div style="font-size:20px;font-weight:700">2025-09-02</div></div>
+  </div>
+  <div class="facts">
+    <div class="fact"><div class="k">Property type</div><div class="v">Office</div></div>
+    <div class="fact"><div class="k">Building sqft</div><div class="v">—</div></div>
+    <div class="fact"><div class="k">Year built</div><div class="v">—</div></div>
+    <div class="fact"><div class="k">County</div><div class="v">Miami-Dade County</div></div>
+    <div class="fact"><div class="k">Recorded doc #</div><div class="v">34974-3025</div></div>
+    <div class="fact"><div class="k">Source</div><div class="v">Public deed record</div></div>
+  </div>
+  <div class="loan"><div class="t">Loan-officer angle — for Frank</div><div class="b">Est. acquisition debt at 60% LTV ≈ <strong>$164,610,000</strong>. acquisition/perm debt likely placed at close — refi window ~5-7yr. A closed sale of this size is a live refinance/perm-debt lead.</div></div>
+  <div class="linkout">
+    <div class="t">Verify &amp; contact — link out, never re-hosted</div>
+    <div>🔗 Find the listing / broker: <a href="https://www.crexi.com/properties?searchTerm=1111%20BRICKELL%20AVE%20Miami">https://www.crexi.com/properties?searchTerm=1111%20BRICKELL%20AVE%20Miami</a></div>
+    <div style="margin-top:4px;color:var(--gray);font-size:12px">Deal facts sourced from public deed records. Broker/agent assets are linked, never copied (RENTV compliance rail).</div>
+  </div>
+  <div class="foot"><span>RENTV.com, Inc. · Real Estate News &amp; Media</span><span>Generated by realestate-flyers · TK-10708</span></div>
+</div></body></html>
\ No newline at end of file
diff --git a/scripts/flyer-from-deal.mjs b/scripts/flyer-from-deal.mjs
new file mode 100644
index 0000000..542b88e
--- /dev/null
+++ b/scripts/flyer-from-deal.mjs
@@ -0,0 +1,122 @@
+#!/usr/bin/env node
+/**
+ * flyer-from-deal.mjs — RE flyer aggregation, increment 1b (TK-10708, re-flyers)
+ *
+ * Cody-gate top fix: PROVE the template->data path. Renders ONE real property-spotlight
+ * flyer from a usre recent_commercial_deals row, using the RENTV brand token system
+ * (lifted from rentv-bloom-flyers concept-1: palette + @page letter + print CSS).
+ *
+ * Compliance rails (re-props skill): PUBLIC-record facts only; NO re-hosted third-party
+ * photos/descriptions (the flyer carries a link-OUT block, not ripped listing assets);
+ * loan-officer angle for Frank. Local HTML only — renders NOTHING to a public host.
+ *
+ * Usage: node scripts/flyer-from-deal.mjs [--top]   (default: highest-price usre deal)
+ * $0, read-only against the DB, writes one HTML file under public/flyers/. Idempotent.
+ * Undo: git revert the commit / delete the generated html.
+ */
+import { writeFileSync, mkdirSync } from 'node:fs';
+import { join, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { execSync } from 'node:child_process';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const ROOT = join(__dirname, '..');
+const OUTDIR = join(ROOT, 'public', 'flyers');
+
+// --- pull one real deal row from usre ---------------------------------------
+function topDeal() {
+  const sql = `SELECT json_build_object('sale_date',sale_date,'sale_price',sale_price,'ctype',ctype,'address',address,'city',city,'county_name',county_name,'sqft',sqft,'year_built',year_built,'doc_number',doc_number) FROM recent_commercial_deals ORDER BY sale_price DESC NULLS LAST LIMIT 1;`;
+  const raw = execSync(`psql -h /tmp usre -tAc "${sql.replace(/"/g, '\\"')}"`, { encoding: 'utf8', timeout: 8000 }).trim();
+  return JSON.parse(raw);
+}
+
+// --- loan-officer angle (Frank): rough acquisition-loan estimate ------------
+function loanAngle(price) {
+  if (!price) return null;
+  const ltv = 0.6; // conservative CRE acquisition LTV
+  const loan = Math.round(price * ltv);
+  return { ltv, loan, refi_window: 'acquisition/perm debt likely placed at close — refi window ~5-7yr' };
+}
+
+const money = (n) => (n == null ? '—' : '$' + Number(n).toLocaleString('en-US'));
+const titleCase = (s) => (s || '').replace(/\b\w/g, (c) => c.toUpperCase());
+const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
+
+function render(deal) {
+  const loan = loanAngle(deal.sale_price);
+  const addr = titleCase(deal.address);
+  const listingSearch = `https://www.crexi.com/properties?searchTerm=${encodeURIComponent(deal.address + ' ' + (deal.city || ''))}`;
+  return `<!DOCTYPE html>
+<html lang="en"><head>
+<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Property Spotlight — ${esc(addr)}, ${esc(deal.city)}</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700;900&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
+<style>
+  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+  :root{--blue:#16357A;--navy:#0E2350;--red:#D0202E;--gold:#E8A81C;--ink:#141414;--paper:#fff;--gray:#6B7280;--panel:#F4F6FB;--rule:#D9DFF0;--pw:816px;--ph:1056px}
+  html,body{width:var(--pw);min-height:var(--ph);background:#CBD5E0;font-family:'Inter',Helvetica,Arial,sans-serif;-webkit-print-color-adjust:exact;print-color-adjust:exact}
+  .page{width:var(--pw);height:var(--ph);background:var(--paper);position:relative;overflow:hidden;display:flex;flex-direction:column}
+  .top-bar{height:6px;background:linear-gradient(90deg,var(--navy),var(--blue) 40%,var(--gold))}
+  .head{padding:32px 44px 20px;display:flex;justify-content:space-between;align-items:flex-start}
+  .kicker{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);font-weight:700}
+  .brand{font-family:'Playfair Display',Georgia,serif;font-weight:900;font-size:22px;color:var(--blue)}
+  .brand small{display:block;font-family:Inter;font-weight:500;font-size:10px;letter-spacing:.06em;color:var(--gray);text-transform:uppercase}
+  h1{font-family:'Playfair Display',Georgia,serif;font-weight:700;font-size:40px;line-height:1.05;color:var(--ink);padding:0 44px}
+  .sub{padding:6px 44px 0;font-size:15px;color:var(--gray)}
+  .price-band{margin:26px 44px 0;background:var(--navy);color:#fff;border-radius:10px;padding:22px 28px;display:flex;justify-content:space-between;align-items:center}
+  .price-band .p{font-family:'Playfair Display',serif;font-weight:900;font-size:38px;color:var(--gold-lt,#F5C842)}
+  .price-band .lab{font-size:11px;letter-spacing:.16em;text-transform:uppercase;opacity:.85}
+  .facts{margin:26px 44px 0;display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
+  .fact{background:var(--panel);border:1px solid var(--rule);border-radius:8px;padding:14px 16px}
+  .fact .k{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--gray);font-weight:600}
+  .fact .v{font-size:18px;font-weight:700;color:var(--ink);margin-top:3px}
+  .loan{margin:26px 44px 0;border-left:4px solid var(--red);background:#FCF3F3;border-radius:0 8px 8px 0;padding:16px 20px}
+  .loan .t{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--red);font-weight:700}
+  .loan .b{font-size:15px;color:var(--ink);margin-top:6px;line-height:1.5}
+  .linkout{margin:24px 44px 0;padding:16px 20px;border:1px dashed var(--rule);border-radius:8px}
+  .linkout .t{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--blue);font-weight:700;margin-bottom:8px}
+  .linkout a{color:var(--blue);font-weight:600;text-decoration:none}
+  .foot{margin-top:auto;padding:20px 44px;border-top:1px solid var(--rule);font-size:11px;color:var(--gray);display:flex;justify-content:space-between}
+  @page{size:letter;margin:0}
+  @media print{body{background:#fff}}
+</style></head>
+<body><div class="page">
+  <div class="top-bar"></div>
+  <div class="head">
+    <div><div class="kicker">Property Spotlight · Closed Deal</div></div>
+    <div class="brand">RENTV<small>Cutting-edge CRE media coverage</small></div>
+  </div>
+  <h1>${esc(addr)}</h1>
+  <div class="sub">${esc(titleCase(deal.city))}, ${esc(deal.county_name)} · ${esc(titleCase(deal.ctype))}</div>
+  <div class="price-band">
+    <div><div class="lab">Recorded sale price</div><div class="p">${money(deal.sale_price)}</div></div>
+    <div style="text-align:right"><div class="lab">Sale date</div><div style="font-size:20px;font-weight:700">${esc(deal.sale_date)}</div></div>
+  </div>
+  <div class="facts">
+    <div class="fact"><div class="k">Property type</div><div class="v">${esc(titleCase(deal.ctype)) || '—'}</div></div>
+    <div class="fact"><div class="k">Building sqft</div><div class="v">${deal.sqft ? Number(deal.sqft).toLocaleString() : '—'}</div></div>
+    <div class="fact"><div class="k">Year built</div><div class="v">${esc(deal.year_built) || '—'}</div></div>
+    <div class="fact"><div class="k">County</div><div class="v">${esc(deal.county_name)}</div></div>
+    <div class="fact"><div class="k">Recorded doc #</div><div class="v">${esc(deal.doc_number) || '—'}</div></div>
+    <div class="fact"><div class="k">Source</div><div class="v">Public deed record</div></div>
+  </div>
+  ${loan ? `<div class="loan"><div class="t">Loan-officer angle — for Frank</div><div class="b">Est. acquisition debt at ${(loan.ltv * 100)}% LTV ≈ <strong>${money(loan.loan)}</strong>. ${esc(loan.refi_window)}. A closed sale of this size is a live refinance/perm-debt lead.</div></div>` : ''}
+  <div class="linkout">
+    <div class="t">Verify &amp; contact — link out, never re-hosted</div>
+    <div>🔗 Find the listing / broker: <a href="${esc(listingSearch)}">${esc(listingSearch)}</a></div>
+    <div style="margin-top:4px;color:var(--gray);font-size:12px">Deal facts sourced from public deed records. Broker/agent assets are linked, never copied (RENTV compliance rail).</div>
+  </div>
+  <div class="foot"><span>RENTV.com, Inc. · Real Estate News &amp; Media</span><span>Generated by realestate-flyers · TK-10708</span></div>
+</div></body></html>`;
+}
+
+// --- run --------------------------------------------------------------------
+const deal = topDeal();
+const slug = ('usre-' + (deal.address || 'deal') + '-' + (deal.city || '')).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60);
+mkdirSync(OUTDIR, { recursive: true });
+const outPath = join(OUTDIR, `${slug}.html`);
+writeFileSync(outPath, render(deal));
+console.log(`rendered property-spotlight flyer -> ${outPath.replace(process.env.HOME, '~')}`);
+console.log(`  deal: ${deal.address}, ${deal.city} — ${money(deal.sale_price)} (${deal.ctype})`);
+console.log(`  slug: ${slug}`);
diff --git a/scripts/index-flyers.mjs b/scripts/index-flyers.mjs
index 1aceb34..5681591 100644
--- a/scripts/index-flyers.mjs
+++ b/scripts/index-flyers.mjs
@@ -35,6 +35,32 @@ const BUILDS = {
 const flyers = [];   // rendered flyer artifacts that exist today
 const sources = [];  // datasets that are flyer-able but have no flyer yet
 
+// --- (a0) OUR OWN rendered per-deal flyers (public/flyers/*.html) -----------
+function indexOwnFlyers() {
+  const dir = join(ROOT, 'public', 'flyers');
+  if (!existsSync(dir)) return;
+  for (const f of readdirSync(dir).filter((n) => n.endsWith('.html')).sort()) {
+    const slug = f.replace(/\.html$/, '');
+    let title = slug;
+    try {
+      const m = readFileSync(join(dir, f), 'utf8').match(/<title>([^<]*)<\/title>/i);
+      if (m) title = m[1].trim();
+    } catch {}
+    flyers.push({
+      id: `local:${slug}`,
+      build: slug.startsWith('usre') ? 'usre' : slug.startsWith('crcp') ? 'crcp' : 'realestate-flyers',
+      kind: 'property-spotlight',
+      title,
+      format: 'US-Letter-816x1056',
+      html_path: join(dir, f).replace(process.env.HOME, '~'),
+      pdf_path: null,
+      pdf_bytes: null,
+      public_url: null, // deploy is gated
+      status: 'rendered',
+    });
+  }
+}
+
 // --- (a) EXISTING rendered flyers: rentv-bloom-flyers concepts --------------
 function indexBloomFlyers() {
   const b = BUILDS.rentv;
@@ -140,12 +166,24 @@ function indexHomesOnSpec() {
 }
 
 // --- run -------------------------------------------------------------------
+indexOwnFlyers();
 indexBloomFlyers();
 indexUsreDeals();
 indexCrcpListings();
 indexHomesOnSpec();
 
-const totalCandidates = sources.reduce((a, s) => a + (s.flyer_candidate_count || 0), 0);
+// Cody hole #2 fix: report the count HONESTLY. usre closed-deals and CRCP closed-sales
+// overlap heavily (both LA-adjacent closed deals), so summing pre-dedupe inflates the
+// number. We report gross AND a de-overlapped estimate keyed by kind, and label it.
+const grossCandidates = sources.reduce((a, s) => a + (s.flyer_candidate_count || 0), 0);
+// crude de-overlap: closed-deal sources overlap → count the LARGER closed-deal source once,
+// then add non-closed-deal sources in full. (True dedupe = canon(addr)+city at flyer time.)
+const closedDeal = sources.filter((s) => s.kind === 'closed-deal' && s.flyer_candidate_count);
+const nonClosed = sources.filter((s) => s.kind !== 'closed-deal');
+const dedupEstimate =
+  (closedDeal.length ? Math.max(...closedDeal.map((s) => s.flyer_candidate_count)) : 0) +
+  nonClosed.reduce((a, s) => a + (s.flyer_candidate_count || 0), 0);
+
 const out = {
   generated_at: new Date().toISOString(),
   ticket: 'TK-10708',
@@ -154,9 +192,12 @@ const out = {
   summary: {
     rendered_flyers: flyers.length,
     flyer_sources: sources.length,
-    flyer_candidates_across_sources: totalCandidates,
-    rendered_coverage_pct: totalCandidates
-      ? +((flyers.length / (flyers.length + totalCandidates)) * 100).toFixed(4)
+    flyer_candidates_gross_pre_dedupe: grossCandidates,
+    flyer_candidates_dedup_estimate: dedupEstimate,
+    candidate_count_note:
+      'GROSS sums every source; closed-deal sources (usre + CRCP) overlap heavily, so DEDUP_ESTIMATE counts the largest closed-deal source once + non-closed sources in full. True dedupe = canon(address)+city at flyer time.',
+    rendered_coverage_pct_vs_dedup: dedupEstimate
+      ? +((flyers.length / (flyers.length + dedupEstimate)) * 100).toFixed(4)
       : null,
   },
   flyers,
@@ -168,4 +209,4 @@ writeFileSync(OUT, JSON.stringify(out, null, 2) + '\n');
 console.log(`flyer-index.json written -> ${OUT.replace(process.env.HOME, '~')}`);
 console.log(`  rendered flyers: ${flyers.length}`);
 console.log(`  flyer sources:   ${sources.length}`);
-console.log(`  candidates:      ${totalCandidates.toLocaleString()} deals/listings across builds`);
+console.log(`  candidates:      ${grossCandidates.toLocaleString()} gross / ${dedupEstimate.toLocaleString()} dedup-est`);

← 5a94e7c TK-10708: RE flyer aggregation plan + increment-1 indexer (f  ·  back to Realestate Flyers  ·  (newest)