cc00a439bf
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.
77 lines
2.0 KiB
TypeScript
77 lines
2.0 KiB
TypeScript
import { readFile } from 'node:fs/promises';
|
|
|
|
export type FeatureGeometry = {
|
|
type: 'Polygon' | 'MultiPolygon';
|
|
coordinates: unknown;
|
|
};
|
|
|
|
export type GeoJsonFeature = {
|
|
type: 'Feature';
|
|
properties?: Record<string, unknown> | null;
|
|
geometry?: FeatureGeometry | null;
|
|
};
|
|
|
|
export type GeoJsonFeatureCollection = {
|
|
type: 'FeatureCollection';
|
|
features: GeoJsonFeature[];
|
|
};
|
|
|
|
export type PostalDatasetConfig = {
|
|
countryCode: 'US' | 'CA';
|
|
label: string;
|
|
filePath: string;
|
|
postalCodeKeys: string[];
|
|
displayNameKeys: string[];
|
|
};
|
|
|
|
export async function readFeatureCollection(filePath: string) {
|
|
const raw = await readFile(filePath, 'utf8');
|
|
const parsed = JSON.parse(raw) as GeoJsonFeatureCollection;
|
|
|
|
if (parsed.type !== 'FeatureCollection' || !Array.isArray(parsed.features)) {
|
|
throw new Error(`Dataset at ${filePath} is not a valid GeoJSON FeatureCollection.`);
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
export function normalizePostalCode(countryCode: 'US' | 'CA', rawPostalCode: string) {
|
|
const trimmed = rawPostalCode.trim();
|
|
|
|
if (countryCode === 'US') {
|
|
const digits = trimmed.replace(/\D/g, '').slice(0, 5);
|
|
return digits.length === 5 ? digits : null;
|
|
}
|
|
|
|
const compact = trimmed.replace(/\s+/g, '').toUpperCase();
|
|
return compact.length >= 3 ? compact.slice(0, 3) : null;
|
|
}
|
|
|
|
export function getStringProperty(properties: Record<string, unknown> | null | undefined, keys: string[]) {
|
|
if (!properties) {
|
|
return null;
|
|
}
|
|
|
|
for (const key of keys) {
|
|
const value = properties[key];
|
|
|
|
if (typeof value === 'string' && value.trim().length > 0) {
|
|
return value.trim();
|
|
}
|
|
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
return String(value);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function getFeatureGeometry(feature: GeoJsonFeature, filePath: string, index: number) {
|
|
if (!feature.geometry || (feature.geometry.type !== 'Polygon' && feature.geometry.type !== 'MultiPolygon')) {
|
|
throw new Error(`Feature ${index} in ${filePath} is missing a Polygon or MultiPolygon geometry.`);
|
|
}
|
|
|
|
return feature.geometry;
|
|
}
|