← back to La Socrata Ingester
scripts/deals-digest.js
95 lines
// RE-news deals digest (READ-ONLY, $0 local). Reads the latest deals-feed-*.json
// (the data layer from deals-feed.js) and renders a publishable markdown digest —
// top deals + by-council-district sections + data-quality flags + source disclaimer.
// Presentation only; the ranking logic lives in deals-feed.js (single source of truth).
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIR = path.resolve(__dirname, '../tmp/leads');
const money = v => v == null ? null : '$' + Math.round(Number(v)).toLocaleString();
const millions = v => '$' + (Number(v) / 1e6).toFixed(Number(v) >= 1e7 ? 0 : 1) + 'M';
function latestFeed() {
if (!fs.existsSync(DIR)) return null;
const f = fs.readdirSync(DIR).filter(x => /^deals-feed-.*\.json$/.test(x)).sort().pop();
return f ? path.join(DIR, f) : null;
}
function main() {
const feedPath = latestFeed();
if (!feedPath) { console.error('No deals-feed-*.json found — run: node scripts/deals-feed.js'); process.exit(1); }
const feed = JSON.parse(fs.readFileSync(feedPath, 'utf8'));
const deals = feed.deals || [];
const stamp = feed.generated;
const flagged = deals.filter(d => d.valuation_outlier).length;
const verifiedValue = deals.filter(d => !d.valuation_outlier).reduce((s, d) => s + (d.project_value || 0), 0);
const outlierValue = deals.filter(d => d.valuation_outlier).reduce((s, d) => s + (d.project_value || 0), 0);
const L = [];
L.push(`# LA Development Deals — ${stamp}`);
L.push('');
// Headline uses VERIFIED value only; flagged/unverified permits shown separately so a
// single self-reported outlier can't inflate the total (Cody c7).
const outlierNote = flagged ? ` (plus ${money(outlierValue)} across ${flagged} flagged / unverified permit${flagged > 1 ? 's' : ''})` : '';
L.push(`**${deals.length}** notable building permits issued in the last ${feed.window_days} days (project value ≥ ${money(feed.min_value)}), ranked by lead score. Verified tracked value: **${money(verifiedValue)}**${outlierNote}.`);
L.push('');
// Staleness guard (Cody c7): flag if the underlying feed isn't from today.
const today = new Date().toISOString().slice(0, 10);
if (stamp !== today) L.push(`> ⚠ Feed data is from ${stamp} (not today, ${today}). Re-run \`node scripts/deals-feed.js\` for current data.\n`);
// Top deals
L.push('## Top deals');
L.push('');
deals.slice(0, 15).forEach((d, i) => {
const bits = [];
if (d.council_member) bits.push(`CD ${d.council_district} · ${d.council_member}`);
if (d.assessed_value) bits.push(`parcel assessed ${millions(d.assessed_value)}`);
if (d.year_built) bits.push(`built ${d.year_built}`);
if (d.sqft) bits.push(`${Number(d.sqft).toLocaleString()} sqft`);
bits.push(`issued ${d.issued}`);
bits.push(`lead ${d.lead_score}`);
const mapLink = d.map ? ` · [map](${d.map})` : '';
const flag = d.valuation_outlier ? ' _(⚠ valuation unverified)_' : '';
L.push(`${i + 1}. **${millions(d.project_value)} ${d.kind}** — ${d.address}${d.zip ? ', ' + d.zip : ''}${flag}`);
L.push(` ${bits.join(' · ')}${mapLink}`);
L.push('');
});
// By council district
const byCd = {};
for (const d of deals) {
const k = d.council_district || '—';
(byCd[k] ||= { member: d.council_member, count: 0, verified: 0, unverified: 0 });
byCd[k].count++;
if (d.valuation_outlier) byCd[k].unverified += d.project_value || 0;
else byCd[k].verified += d.project_value || 0;
}
// Sort by VERIFIED value so a district can't rank #1 on a single flagged permit (Cody c7).
const cdSorted = Object.entries(byCd).sort((a, b) => b[1].verified - a[1].verified).slice(0, 8);
L.push('## By council district');
L.push('');
L.push('| District | Member | Deals | Verified value |');
L.push('|---|---|---:|---:|');
for (const [cd, v] of cdSorted) {
const unv = v.unverified ? ` (+${money(v.unverified)} unverified ⚠)` : '';
L.push(`| CD ${cd} | ${v.member || '—'}${v.unverified ? ' ⚠' : ''} | ${v.count} | ${money(v.verified)}${unv} |`);
}
L.push('');
// Notes
L.push('## Notes');
L.push('');
if (flagged) L.push(`- **${flagged}** deal(s) flagged _⚠ valuation unverified_ — LADBS permit valuations are self-reported; verify institutional-scale (>$100M) or parcel-mismatched figures before publishing.`);
L.push('- Data: City of Los Angeles building permits (public records) enriched with LA County Assessor parcel values. Provided as-is; not independently verified. Not affiliated with any government agency.');
L.push('');
const out = path.join(DIR, `deals-digest-${stamp}.md`);
fs.writeFileSync(out, L.join('\n'));
console.log(L.slice(0, 26).join('\n'));
console.log(`\n… digest (${deals.length} deals) → ${out}`);
}
main();