Public Access
1
0
Files
leads4less/db/scripts/import-postal-areas.ts
T
pguerrerox cc00a439bf feat: add deep research planning and postal batch runs
Add a dedicated Deep Research view with postal-area preview overlays, batch execution, and bundled map results.

Also add postal dataset import tooling and fix local API networking and research insert issues needed to support the new workflow.
2026-04-05 18:05:04 +00:00

122 lines
3.8 KiB
TypeScript

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getDbPool } from '../../server/src/db/pool.js';
import { getFeatureGeometry, getStringProperty, normalizePostalCode, readFeatureCollection, type PostalDatasetConfig } from './postal-import-utils.js';
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const datasetsRoot = path.resolve(currentDir, '../datasets/postal');
const datasetConfigs: PostalDatasetConfig[] = [
{
countryCode: 'US',
label: 'US ZIP/ZCTA',
filePath: process.env.POSTAL_US_DATASET_PATH || path.join(datasetsRoot, 'us_zcta.geojson'),
postalCodeKeys: ['postal_code', 'zip', 'zcta', 'GEOID20', 'ZCTA5CE20', 'ZCTA5CE10'],
displayNameKeys: ['display_name', 'name', 'NAMELSAD20', 'GEOID20', 'postal_code'],
},
{
countryCode: 'CA',
label: 'Canada FSA',
filePath: process.env.POSTAL_CA_DATASET_PATH || path.join(datasetsRoot, 'ca_fsa.geojson'),
postalCodeKeys: ['postal_code', 'fsa', 'CFSAUID', 'CFSAUID24'],
displayNameKeys: ['display_name', 'name', 'CFSAUID', 'postal_code'],
},
];
async function importDataset(config: PostalDatasetConfig) {
const pool = getDbPool();
const client = await pool.connect();
try {
const collection = await readFeatureCollection(config.filePath);
let insertedCount = 0;
let skippedCount = 0;
await client.query('begin');
for (const [index, feature] of collection.features.entries()) {
const rawPostalCode = getStringProperty(feature.properties, config.postalCodeKeys);
if (!rawPostalCode) {
skippedCount += 1;
continue;
}
const normalizedPostalCode = normalizePostalCode(config.countryCode, rawPostalCode);
if (!normalizedPostalCode) {
skippedCount += 1;
continue;
}
const displayName = getStringProperty(feature.properties, config.displayNameKeys) || normalizedPostalCode;
const geometry = getFeatureGeometry(feature, config.filePath, index);
await client.query(
`
insert into public.postal_areas (
country_code,
postal_code,
display_name,
normalized_postal_code,
geom,
centroid,
search_radius_m,
metadata_json,
created_at,
updated_at
)
values (
$1,
$2,
$3,
$4,
ST_Multi(ST_SetSRID(ST_GeomFromGeoJSON($5), 4326)),
ST_Centroid(ST_SetSRID(ST_GeomFromGeoJSON($5), 4326))::geography,
greatest(1000, round(sqrt(ST_Area(ST_SetSRID(ST_GeomFromGeoJSON($5), 4326)::geography) / pi()))::integer),
$6::jsonb,
now(),
now()
)
on conflict (country_code, normalized_postal_code)
do update set
postal_code = excluded.postal_code,
display_name = excluded.display_name,
geom = excluded.geom,
centroid = excluded.centroid,
search_radius_m = excluded.search_radius_m,
metadata_json = excluded.metadata_json,
updated_at = now()
`,
[
config.countryCode,
rawPostalCode.trim(),
displayName,
normalizedPostalCode,
JSON.stringify(geometry),
JSON.stringify(feature.properties ?? {}),
],
);
insertedCount += 1;
}
await client.query('commit');
console.log(`Imported ${insertedCount} ${config.label} areas from ${config.filePath}. Skipped ${skippedCount}.`);
} catch (error) {
await client.query('rollback');
throw error;
} finally {
client.release();
}
}
async function run() {
for (const config of datasetConfigs) {
await importDataset(config);
}
await getDbPool().end();
}
await run();