← back to Ventura Claw
server/connectors/cadata.js
64 lines
// California Open Data (Socrata, hosted on data.ca.gov). No auth needed.
// Catalog: https://data.ca.gov/dataset
// Useful datasets:
// - CA SOS Business Filings: 8ek2-eknp (SOS active business search)
// - CalEnviroScreen 4.0: <varies>
// - State Contracts Inventory: ng9z-2spp
// - Active Civil Cases: c9b6-2v9a
const BASE = "https://data.ca.gov/api/3/action/datastore_search";
async function call(qs) {
const r = await fetch(`${BASE}?${qs}`, {
headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
signal: AbortSignal.timeout(20_000),
});
if (!r.ok) throw new Error(`cadata ${r.status}`);
return r.json();
}
async function packageList() {
const r = await fetch("https://data.ca.gov/api/3/action/package_list?limit=10", {
headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
signal: AbortSignal.timeout(15_000),
});
if (!r.ok) throw new Error(`cadata package_list ${r.status}`);
return r.json();
}
module.exports = {
meta: { id: "cadata", name: "California Open Data", category: "data", docsUrl: "https://data.ca.gov/dataset", auth: "none", realImpl: true },
fields: [],
configured() { return true; },
async health() {
try {
const d = await packageList();
return { ok: true, label: `live · CKAN datastore · ${(d.result || []).length}+ packages sampled` };
} catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "search_packages"({ q, rows }) {
if (!q) throw new Error("q required");
const qs = new URLSearchParams({ q, rows: String(Math.min(rows || 20, 100)) });
const r = await fetch(`https://data.ca.gov/api/3/action/package_search?${qs}`, {
headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0" },
signal: AbortSignal.timeout(15_000),
});
if (!r.ok) throw new Error(`cadata search ${r.status}`);
const d = await r.json();
return {
count: d.result?.count || 0,
packages: (d.result?.results || []).map(p => ({
id: p.id, name: p.name, title: p.title, organization: p.organization?.title,
notes: p.notes?.slice(0, 240),
resources: (p.resources || []).map(r => ({ id: r.id, name: r.name, format: r.format, url: r.url })).slice(0, 5),
})),
};
},
async "datastore_query"({ resource_id, q, limit, offset }) {
if (!resource_id) throw new Error("resource_id required");
const qs = new URLSearchParams({ resource_id, limit: String(Math.min(limit || 50, 500)) });
if (offset) qs.set("offset", String(offset));
if (q) qs.set("q", q);
const d = await call(qs.toString());
return { total: d.result?.total, records: d.result?.records || [] };
},
},
};