#!/usr/bin/env node /** * Reonomy JSON → CSV converter * Flattens leads into one row per owner (property info repeated) * Usage: node reonomy-to-csv.js [input.json] [output.csv] */ const fs = require('fs'); const path = require('path'); const inputPath = process.argv[2] || path.join(process.env.HOME, '.clawdbot/workspace/reonomy-leads-v13.json'); const outputPath = process.argv[3] || path.join(process.env.HOME, '.clawdbot/workspace/reonomy-leads.csv'); function escapeCsv(val) { if (val == null) return ''; const str = String(val); if (str.includes(',') || str.includes('"') || str.includes('\n')) { return `"${str.replace(/"/g, '""')}"`; } return str; } try { const data = JSON.parse(fs.readFileSync(inputPath, 'utf8')); const leads = data.leads || []; if (leads.length === 0) { console.error('No leads found in', inputPath); process.exit(1); } // Figure out max phones/emails across all owners let maxPhones = 0; let maxEmails = 0; for (const lead of leads) { for (const owner of (lead.owners || [])) { maxPhones = Math.max(maxPhones, (owner.phones || []).length); maxEmails = Math.max(maxEmails, (owner.emails || []).length); } } // Cap at reasonable numbers maxPhones = Math.min(maxPhones, 5); maxEmails = Math.min(maxEmails, 5); // Build header const headers = [ 'Property Address', 'City', 'State', 'ZIP', 'Property Type', 'Square Footage', 'Year Built', 'Units', 'Lot Size', 'Property ID', 'Scrape Date', 'Owner Name', ]; for (let i = 1; i <= maxPhones; i++) { headers.push(`Phone ${i}`); headers.push(`Phone ${i} Type`); } for (let i = 1; i <= maxEmails; i++) { headers.push(`Email ${i}`); } const rows = [headers.map(escapeCsv).join(',')]; for (const lead of leads) { const propBase = [ lead.propertyAddress || '', lead.city || '', lead.state || '', lead.zip || '', lead.propertyType || '', lead.squareFootage || '', lead.yearBuilt || '', lead.units || '', lead.lotSize || '', lead.propertyId || '', lead.scrapeDate ? new Date(lead.scrapeDate).toLocaleDateString() : '', ]; const owners = lead.owners || []; if (owners.length === 0) { // Property with no owners — still include it rows.push([...propBase, ''].map(escapeCsv).join(',')); continue; } for (const owner of owners) { const row = [...propBase, owner.name || '']; // Phones const phones = (owner.phones || []).slice(0, maxPhones); for (let i = 0; i < maxPhones; i++) { if (i < phones.length) { row.push(phones[i].number || ''); row.push(phones[i].type || ''); } else { row.push(''); row.push(''); } } // Emails const emails = (owner.emails || []).slice(0, maxEmails); for (let i = 0; i < maxEmails; i++) { row.push(i < emails.length ? emails[i] : ''); } rows.push(row.map(escapeCsv).join(',')); } } fs.writeFileSync(outputPath, rows.join('\n')); console.log(`CSV written: ${outputPath}`); console.log(`${leads.length} properties, ${rows.length - 1} owner rows`); console.log(`Columns: ${headers.length} (${maxPhones} phone cols, ${maxEmails} email cols)`); } catch (err) { console.error('Error:', err.message); process.exit(1); }