126 lines
3.3 KiB
JavaScript
126 lines
3.3 KiB
JavaScript
#!/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);
|
|
}
|