← back to La Socrata Ingester
src/cli.js
91 lines
#!/usr/bin/env node
import { SOURCES, GROUPS } from './sources.js';
import { ingestSource, closePool } from './ingest.js';
import { classifyRunResults } from './run-policy.js';
const argv = process.argv.slice(2);
const flags = new Set(argv.filter((a) => a.startsWith('--') && !a.includes('=')));
const kv = Object.fromEntries(
argv.filter((a) => a.startsWith('--') && a.includes('=')).map((a) => a.slice(2).split('='))
);
const positional = argv.filter((a) => !a.startsWith('--'));
function printList() {
console.log('\nLA Socrata/ArcGIS ingester — sources:\n');
for (const [group, names] of Object.entries(GROUPS)) {
console.log(` ${group}`);
for (const n of names) {
const s = SOURCES[n];
const id = s.datasetId || (s.endpoint ? 'arcgis' : '');
console.log(` • ${n.padEnd(30)} ${s.platform.padEnd(8)} ${id}${s.static ? ' [static]' : ''}`);
}
}
console.log(`\nUsage:
node src/cli.js --list
node src/cli.js <source|group|all> [--full] [--max=N] [--page=N]
Examples:
node src/cli.js building_permits --max=25 # smoke test: 25 rows
node src/cli.js code_enforcement_open # incremental refresh
node src/cli.js "building permits" # whole group
node src/cli.js assessor_parcels --full # 12.1M-row backfill (all roll years)
node src/cli.js all # every non-static source, incremental
`);
}
function resolveTargets(token) {
if (!token || token === 'all') {
// everything except static historical bulk (run those explicitly)
return Object.keys(SOURCES).filter((n) => !SOURCES[n].static);
}
if (GROUPS[token]) return GROUPS[token];
if (SOURCES[token]) return [token];
return null;
}
async function main() {
if (flags.has('--list') || argv.length === 0) return printList();
const token = positional[0];
const targets = resolveTargets(token);
if (!targets) {
console.error(`Unknown source/group: "${token}". Run --list to see options.`);
process.exitCode = 1;
return;
}
const opts = {
full: flags.has('--full'),
maxRows: kv.max ? Number(kv.max) : undefined,
pageSize: kv.page ? Number(kv.page) : undefined,
year: kv.year || undefined,
};
const results = [];
for (const name of targets) {
try {
results.push(await ingestSource(name, opts));
} catch (err) {
results.push({ name, error: err.message });
}
}
const policy = classifyRunResults(results, SOURCES, token);
const toleratedNames = new Set(policy.tolerated.map((r) => r.name));
console.log('\n=== summary ===');
for (const r of results) {
if (!r.error) console.log(` ✔ ${r.name}: ${r.total} rows`);
else if (toleratedNames.has(r.name)) {
const meta = SOURCES[r.name].allowFailure;
console.log(` ⚠ ${r.name}: known degraded (${meta.since}; ${meta.reason}): ${r.error}`);
} else console.log(` ✖ ${r.name}: ${r.error}`);
}
if (policy.tolerated.length) {
console.log(` ⚠ tolerated ${policy.tolerated.length} audited failure(s) in aggregate all run`);
}
if (policy.exitCode) process.exitCode = policy.exitCode;
}
main().finally(closePool);