@lineai/municipal-intel
Version:
AI-first municipal data API providing natural language descriptions of building permits and planning applications from major US cities
328 lines • 25.9 kB
JavaScript
/**
* Socrata API client for municipal data
* Used by San Francisco, NYC, Oakland, Sacramento
*/
import axios from 'axios';
import { BaseMunicipalClient, MunicipalDataError, RateLimitError } from '../base-client';
/**
* Socrata API client
*/
export class SocrataClient extends BaseMunicipalClient {
api;
appToken;
resetTime = Date.now() + 60000; // 1 minute from now
datasetConfig; // Current dataset configuration
params;
constructor(config, params) {
super(config);
if (!this.source.api || this.source.api.type !== 'socrata') {
throw new Error('SocrataClient requires a source with api.type = "socrata"');
}
this.appToken = config.appToken;
this.params = params;
// const apiSource = this.source.api as ApiSource;
this.datasetConfig = this.source.api.datasets[params.datasetId || this.source.api.defaultDataset];
// Create axios instance
this.api = axios.create({
baseURL: this.source.api.baseUrl,
timeout: this.timeout,
headers: {
'User-Agent': config.userAgent || 'municipal-intel/0.1.0',
...(this.appToken && { 'X-App-Token': this.appToken })
}
});
// Add response interceptor for error handling
this.api.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error.response?.status === 429) {
const resetTime = this.resetTime;
throw new RateLimitError(this.source.id, new Date(resetTime));
}
throw new MunicipalDataError(error.message, this.source.id, error.response?.status, error.response?.data);
});
}
/**
* Execute a SoQL query against a dataset
*/
async query(sq = {}) {
const response = await this.api.get(this.datasetConfig.endpoint, {
params: this.cleanParams(sq)
});
this.log(`Retrieved ${response.data.length} records`);
return response.data;
}
/**
* Search for municipal projects
*/
async search() {
const adjustments = [];
const soqlQuery = this.buildSoQLQuery(adjustments);
const data = await this.query(soqlQuery);
const projects = data.map(item => this.normalizeProject(item));
// Get total count if needed
let total = projects.length;
if (this.params.limit && projects.length === this.params.limit) {
const countQuery = { ...soqlQuery, $select: 'count(*) as total', $limit: 1, $order: undefined };
const countResult = await this.query(countQuery);
total = parseInt(countResult[0]?.total || '0');
}
return {
projects,
total,
page: Math.floor((this.params.offset || 0) / (this.params.limit || 100)) + 1,
pageSize: this.params.limit || 100,
hasMore: total > (this.params.offset || 0) + projects.length,
adjustments
};
}
/**
* Get a project by its URL
*/
async getByUrl(url) {
try {
// Extract the ID from the URL
const id = this.extractIdFromUrl(url);
if (!id) {
return null;
}
return this.getProject(id);
}
catch (error) {
console.warn(`Error getting project by URL ${url}: ${error}`);
return null;
}
}
/**
* Extract project ID from a municipal-intel URL
*/
extractIdFromUrl(url) {
try {
const urlObj = new URL(url);
// Expected format: /projects/{sourceId}/{datasetId}/{projectId}
const pathParts = urlObj.pathname.split('/');
if (pathParts.length >= 5 && pathParts[1] === 'projects') {
return pathParts[4]; // Return the project ID part
}
return null;
}
catch (error) {
return null;
}
}
/**
* Get a specific project by ID
*/
async getProject(id) {
const idField = this.getIdField();
if (!idField) {
throw new MunicipalDataError(`Missing field mapping for 'id' in source ${this.source.id}. Please add to fieldMappings in registry.`, this.source.id);
}
const query = {
$where: `${idField} = '${id.replace(`${this.source.id}-`, '')}'`,
$limit: 1
};
const data = await this.query(query);
return data.length > 0 ? this.normalizeProject(data[0]) : null;
}
/**
* Get available project types
*/
async getAvailableTypes() {
const typeField = this.getTypeField();
if (!typeField)
return [];
const query = {
$select: `distinct ${typeField}`,
$limit: 1000
};
const data = await this.query(query);
return data.map(item => item[typeField]).filter(Boolean);
}
/**
* Check if the data source is healthy
*/
async healthCheck() {
const startTime = Date.now();
try {
// Simple health check - try to fetch one record
const query = { $limit: 1 };
await this.query(query);
const latency = Date.now() - startTime;
return {
status: 'healthy',
latency,
lastChecked: new Date()
};
}
catch (error) {
const latency = Date.now() - startTime;
return {
status: 'unhealthy',
latency,
error: error.message,
lastChecked: new Date()
};
}
}
/**
* Build SoQL query from search parameters
*/
buildSoQLQuery(adjustments = []) {
const params = this.params;
const query = {
$limit: params.limit || 100,
$offset: this.params.offset || 0,
$order: this.buildOrderClause(params)
};
const whereConditions = [];
// Date filters
if (params.submitDateFrom) {
const field = this.getDateField('submit');
if (field) {
this.validateDateParameter(params.submitDateFrom, 'submitDateFrom');
// Socrata doesn't like Z timezone indicator, remove it
const dateString = params.submitDateFrom.toISOString().replace('Z', '');
whereConditions.push(`${field} >= '${dateString}'`);
}
}
if (params.submitDateTo) {
const field = this.getDateField('submit');
if (field) {
this.validateDateParameter(params.submitDateTo, 'submitDateTo');
// Socrata doesn't like Z timezone indicator, remove it
const dateString = params.submitDateTo.toISOString().replace('Z', '');
whereConditions.push(`${field} <= '${dateString}'`);
}
}
// Value filters
if (params.minValue) {
const field = this.getValueField();
if (field) {
// All Socrata sources store numeric values as text strings, requires casting
whereConditions.push(`${field}::number >= ${params.minValue}`);
}
else {
// No value field available - skip filter and record adjustment
adjustments.push(`${this.source.id.toUpperCase()}: Skipped minValue filter - no value field available in dataset`);
}
}
// Status filters
if (params.statuses && params.statuses.length > 0) {
const field = this.getStatusField();
if (field) {
const statusList = params.statuses.map(s => `'${s}'`).join(',');
whereConditions.push(`${field} in (${statusList})`);
}
}
// Address filters
if (params.addresses && params.addresses.length > 0) {
const field = this.getAddressField();
if (field) {
const addressConditions = params.addresses.map(addr => `upper(${field}) like upper('%${addr}%')`);
whereConditions.push(`(${addressConditions.join(' OR ')})`);
}
}
// Keywords
if (params.keywords && params.keywords.length > 0) {
const searchText = params.keywords.join(' ');
query.$q = searchText;
}
if (whereConditions.length > 0) {
query.$where = whereConditions.join(' AND ');
}
return query;
}
/**
* Clean query parameters (remove undefined values)
*/
cleanParams(sq) {
const cleaned = {};
for (const [key, value] of Object.entries(sq)) {
if (value !== undefined && value !== null) {
cleaned[key] = value;
}
}
return cleaned;
}
/**
* Build ORDER BY clause
*/
buildOrderClause(params) {
const sortBy = params.sortBy || 'submitDate';
const order = params.sortOrder || 'desc';
const field = this.getFieldMapping(sortBy);
if (!field) {
// If field mapping not available, try to use a default field or skip ordering
return `:created_at ${order}`; // Most Socrata datasets have this system field
}
return `${field} ${order}`;
}
/**
* Get field mapping for this source (returns null if missing)
*/
getFieldMapping(logicalField) {
const mappings = this.datasetConfig?.fieldMappings;
return mappings?.[logicalField] || null;
}
// Placeholder methods - would be implemented per source
getIdField() { return this.getFieldMapping('id'); }
getTypeField() { return this.getFieldMapping('title'); }
getDateField(type) {
return type === 'submit'
? this.getFieldMapping('submitDate')
: this.getFieldMapping('approvalDate');
}
getValueField() {
return this.getFieldMapping('value');
}
getStatusField() { return this.getFieldMapping('status'); }
getAddressField() { return this.getFieldMapping('address'); }
/**
* Normalize raw data to MunicipalProject using dataset-specific description
*/
normalizeProject(data) {
// Get ID field for unique identifier
const idField = this.getFieldMapping('id');
const id = idField ? data[idField] || 'unknown' : 'unknown';
// Use dataset-specific description method
let description = 'Municipal Project';
if (this.datasetConfig?.getDescription) {
try {
description = this.datasetConfig.getDescription(data);
}
catch (error) {
console.warn(`Error generating description for ${this.source.id}: ${error}`);
description = `${this.source.name} Record`;
}
}
return {
id: `${this.source.id}-${id}`,
source: this.source.id,
description,
url: this.generateProjectUrl(id),
rawData: data,
lastUpdated: new Date()
};
}
/**
* Generate a project URL for accessing full details
*/
generateProjectUrl(id) {
const datasetId = this.params.datasetId || this.source.api?.defaultDataset || 'default';
return `https://municipal-intel.lineai.com/projects/${this.source.id}/${datasetId}/${id}`;
}
/**
* Validate that date parameter is a proper Date object
*/
validateDateParameter(dateParam, paramName) {
if (!(dateParam instanceof Date)) {
const actualType = Array.isArray(dateParam) ? 'array' : typeof dateParam;
throw new MunicipalDataError(`Invalid ${paramName}: expected Date object, got ${actualType}. Use: new Date('2024-01-01') or new Date()`, this.source.id);
}
if (isNaN(dateParam.getTime())) {
throw new MunicipalDataError(`Invalid ${paramName}: Date object contains invalid date. Check your date values.`, this.source.id);
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2NsaWVudHMvc29jcmF0YS9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxLQUF1QyxNQUFNLE9BQU8sQ0FBQztBQUk1RCxPQUFPLEVBQW9CLG1CQUFtQixFQUFlLGtCQUFrQixFQUFFLGNBQWMsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBc0J4SDs7R0FFRztBQUNILE1BQU0sT0FBTyxhQUFjLFNBQVEsbUJBQW1CO0lBQ25DLEdBQUcsQ0FBZ0I7SUFDbkIsUUFBUSxDQUFVO0lBQ2xCLFNBQVMsR0FBVyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsS0FBSyxDQUFDLENBQUMsb0JBQW9CO0lBQzVELGFBQWEsQ0FBaUIsQ0FBQyxnQ0FBZ0M7SUFDL0QsTUFBTSxDQUF3QjtJQUUvQyxZQUFZLE1BQTJCLEVBQUUsTUFBNkI7UUFDcEUsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRWQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksS0FBSyxTQUFTLEVBQUU7WUFDMUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyREFBMkQsQ0FBQyxDQUFDO1NBQzlFO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDO1FBQ2hDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLGtEQUFrRDtRQUNsRCxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBR2xHLHdCQUF3QjtRQUN4QixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssQ0FB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