← back to Ventura Claw
server/connectors/lacity.js
56 lines
// LA City Open Data (Socrata). Read-only, no auth required for ≤1000 req/hr.
// Catalog: https://data.lacity.org/browse
// Useful datasets:
// - Active Business Tax Registration: 6rrh-rzua
// - Crime Data 2020-Present: 2nrs-mtv8
// - Building & Safety Permits: pi9x-tg5x
// - Code Violations: 855j-wf32
const BASE = "https://data.lacity.org/resource";
async function call(dataset, qs) {
const url = `${BASE}/${dataset}.json` + (qs ? `?${qs}` : "");
const r = await fetch(url, {
headers: { Accept: "application/json", "User-Agent": "VenturaClaw/1.0", "X-App-Token": "" },
signal: AbortSignal.timeout(20_000),
});
if (!r.ok) throw new Error(`lacity ${r.status}`);
return r.json();
}
const KNOWN = {
business_tax_registry: "6rrh-rzua",
building_permits: "pi9x-tg5x",
crime_data: "2nrs-mtv8",
code_violations: "855j-wf32",
};
module.exports = {
meta: { id: "lacity", name: "LA City Open Data", category: "data", docsUrl: "https://data.lacity.org/browse", auth: "none", realImpl: true },
fields: [],
configured() { return true; },
async health() {
try {
const d = await call(KNOWN.business_tax_registry, "$limit=1");
return { ok: true, label: `live · ${Object.keys(KNOWN).length} curated datasets (Socrata SoQL)` };
} catch (e) { return { ok: false, reason: e.message }; }
},
actions: {
async "datasets"() { return { datasets: KNOWN }; },
async "query"({ dataset, where, select, order, limit, q }) {
const id = KNOWN[dataset] || dataset;
if (!id || !/^[a-z0-9-]+$/i.test(id)) throw new Error("dataset id required");
const qs = new URLSearchParams();
if (where) qs.set("$where", where);
if (select) qs.set("$select", select);
if (order) qs.set("$order", order);
if (q) qs.set("$q", q);
qs.set("$limit", String(Math.min(limit || 50, 1000)));
return { rows: await call(id, qs.toString()) };
},
async "business_search"({ name, address, limit }) {
const where = [];
if (name) where.push(`upper(business_name) like upper('%${String(name).replace(/'/g, "''")}%')`);
if (address) where.push(`upper(street_address) like upper('%${String(address).replace(/'/g, "''")}%')`);
const qs = new URLSearchParams({ $where: where.join(" AND ") || "1=1", $limit: String(Math.min(limit || 25, 200)) });
return { rows: await call(KNOWN.business_tax_registry, qs.toString()) };
},
},
};