UNPKG

@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
/** * 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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