← 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
M PLAN.mdM data/flyer-index.jsonA public/flyers/usre-1111-brickell-ave-miami.htmlA scripts/flyer-from-deal.mjsM scripts/index-flyers.mjs
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 & 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 & 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) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[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 & 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 & 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)