← back to Ca Donations
scripts/ingest-propublica-ca-orgs.mjs
55 lines
// Ingest California nonprofits from the ProPublica Nonprofit Explorer API (free, no key).
// Proof-of-pipeline: paginates CA orgs into charitable_orgs. MAX_PAGES bounds it for v1;
// the full CA org universe (~150k) is reached by the org-index backfill node.
// Docs: https://projects.propublica.org/nonprofits/api
import { q, startRun, upsertSource } from '../lib/db.js';
const BASE = 'https://projects.propublica.org/nonprofits/api/v2/search.json';
const MAX_PAGES = parseInt(process.env.MAX_PAGES || '5', 10); // 100 orgs/page
const SLUG = 'propublica';
async function main() {
await upsertSource({
slug: SLUG, name: 'ProPublica Nonprofit Explorer', url: BASE,
jurisdiction: 'federal', family: 'charitable', granularity: 'org', access: 'api',
notes: 'CA org index + EINs; free, no key. Full corpus via IRS 990 XML join.',
});
const run = await startRun(SLUG);
let seen = 0, upserted = 0;
try {
for (let page = 0; page < MAX_PAGES; page++) {
const url = `${BASE}?state%5Bid%5D=CA&page=${page}`;
const res = await fetch(url, { headers: { 'User-Agent': 'ca-donations/0.1 (public-records)' } });
if (!res.ok) throw new Error(`ProPublica HTTP ${res.status} on page ${page}`);
const json = await res.json();
const orgs = json.organizations || [];
if (!orgs.length) break;
for (const o of orgs) {
seen++;
const ein = o.strein || String(o.ein);
const sub = o.subseccd ? `501(c)(${o.subseccd})` : null;
const r = await q(
`INSERT INTO charitable_orgs (ein,name,city,state,ntee_code,subsection,source_slug)
VALUES ($1,$2,$3,$4,$5,$6,$7)
ON CONFLICT (ein) DO UPDATE SET name=EXCLUDED.name, city=EXCLUDED.city,
state=EXCLUDED.state, ntee_code=EXCLUDED.ntee_code, subsection=EXCLUDED.subsection
RETURNING id`,
[ein, o.name, o.city, o.state, o.ntee_code || o.raw_ntee_code || null, sub, SLUG],
);
if (r.length) upserted++;
}
process.stdout.write(` page ${page}: ${orgs.length} orgs (running total ${seen})\n`);
await new Promise((r) => setTimeout(r, 500)); // be polite
}
await run.done(seen, upserted, 'ok');
console.log(`ProPublica CA orgs: ${seen} seen, ${upserted} upserted. $0 (free API).`);
} catch (e) {
await run.done(seen, upserted, 'error', String(e.message));
console.error('FAILED:', e.message);
process.exit(1);
} finally {
await (await import('../lib/db.js')).pool.end();
}
}
main();