Public Access
1
0

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.
This commit is contained in:
pguerrerox
2026-04-05 18:05:04 +00:00
parent a1ba5ee093
commit cc00a439bf
29 changed files with 1860 additions and 82 deletions
+121
View File
@@ -0,0 +1,121 @@
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();