UNPKG

@lineai/gov-deals

Version:

Explore Federal contracts for government building renovations, city hall renovations, courthouse updates, library modernizations, federal building improvement contracts, base housing and facilities upgrades.

130 lines 9.08 kB
/** * CSV-based data source for opportunities * Provides same interface as SAM.gov API but reads from local CSV file */ import { promises as fs } from 'fs'; import { parse } from 'csv-parse/sync'; import { mapCsvToSamOpportunity, filterCsvRow } from './field-mapping'; import { SamOpportunitySearchResponseSchema, } from '../../types/opportunities'; import { ValidationError } from '../../core/errors'; /** * CSV-based client that mimics SAM.gov API */ export class CsvClient { csvPath; cache = null; cacheResults; encoding; constructor(options) { if (!options.csvPath) { throw new Error('CSV file path is required'); } this.csvPath = options.csvPath; this.cacheResults = options.cacheResults ?? true; this.encoding = options.encoding ?? 'utf-8'; } /** * Load and parse CSV data */ async loadData() { // Return cached data if available if (this.cache && this.cacheResults) { return this.cache; } try { // Read CSV file const csvContent = await fs.readFile(this.csvPath, this.encoding); // Parse CSV with proper options const records = parse(csvContent, { columns: true, skip_empty_lines: true, relax_quotes: true, relax_column_count: true, skip_records_with_error: true, // Skip problematic rows }); // Cache if enabled if (this.cacheResults) { this.cache = records; } return records; } catch (error) { throw new Error(`Failed to load CSV file: ${error instanceof Error ? error.message : String(error)}`); } } /** * Search opportunities with filtering and pagination */ async search(filters, pagination) { // Load all data const allRecords = await this.loadData(); // Apply filters let filteredRecords = allRecords; if (filters) { filteredRecords = allRecords.filter(row => filterCsvRow(row, filters)); } // Calculate pagination const limit = pagination?.limit || 100; const page = pagination?.page || 1; const offset = (page - 1) * limit; // Get paginated results const paginatedRecords = filteredRecords.slice(offset, offset + limit); // Transform to SAM format const opportunities = paginatedRecords.map(mapCsvToSamOpportunity); // Build response const response = { totalRecords: filteredRecords.length, limit, offset, opportunitiesData: opportunities, links: [], // CSV doesn't have links }; // Validate response format const result = SamOpportunitySearchResponseSchema.safeParse(response); if (!result.success) { console.error('CSV mapping validation errors:', result.error.errors); // Continue anyway - the data is still useful even if not perfectly typed } return response; } /** * Get opportunity by ID */ async getById(noticeId) { const allRecords = await this.loadData(); const record = allRecords.find(row => row.NoticeId === noticeId); if (!record) { throw new ValidationError(`Opportunity with ID ${noticeId} not found`); } return mapCsvToSamOpportunity(record); } /** * Get opportunity description (from CSV Description field) */ async getDescription(noticeId) { const allRecords = await this.loadData(); const record = allRecords.find(row => row.NoticeId === noticeId); if (!record) { throw new ValidationError(`Opportunity with ID ${noticeId} not found`); } return record.Description || 'No description available'; } /** * Search for construction opportunities */ async searchConstruction(additionalFilters, pagination) { const constructionFilters = { naicsCodes: ['236', '238'], keywords: 'renovation construction building modernization', ...additionalFilters, }; return this.search(constructionFilters, pagination); } /** * Clear cache if needed */ clearCache() { this.cache = null; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2RhdGFzb3VyY2VzL2Nzdi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxFQUFFLFFBQVEsSUFBSSxFQUFFLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDcEMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3ZDLE9BQU8sRUFFTCxzQkFBc0IsRUFDdEIsWUFBWSxFQUNiLE1BQU0saUJBQWlCLENBQUM7QUFDekIsT0FBTyxFQUlMLGtDQUFrQyxHQUNuQyxNQUFNLDJCQUEyQixDQUFDO0FBRW5DLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQVFwRDs7R0FFRztBQUNILE1BQU0sT0FBTyxTQUFTO0lBQ1osT0FBTyxDQUFTO0lBQ2hCLEtBQUssR0FBK0IsSUFBSSxDQUFDO0lBQ3pDLFlBQVksQ0FBVTtJQUN0QixRQUFRLENBQWlCO0lBRWpDLFlBQVksT0FBeUI7UUFDbkMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUU7WUFDcEIsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO1NBQzlDO1FBRUQsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDO1FBQy9CLElBQUksQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUM7UUFDakQsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQztJQUM5QyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsUUFBUTtRQUNwQixrQ0FBa0M7UUFDbEMsSUFBSSxJQUFJLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxZQUFZLEVBQUU7WUFDbkMsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDO1NBQ25CO1FBRUQsSUFBSTtZQUNG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