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
+99
View File
@@ -0,0 +1,99 @@
import type { Pool } from 'pg';
import type { CreateDeepResearchBatchRequest, DeepResearchBatchDetail, DeepResearchBatchSummary, DeepResearchPreviewRequest, JobStatus } from '../../../shared/types.js';
import { listPostalAreasByPropagation, findPostalAreaContainingPoint } from '../postal/repository.js';
import { previewDeepResearchForPoint } from '../postal/service.js';
import { runSearchForPostalArea } from '../search/run-search.js';
import {
createDeepResearchBatch,
failDeepResearchBatch,
finalizeDeepResearchBatch,
getDeepResearchBatchDetailForUser,
listDeepResearchBatchesForUser,
} from './repository.js';
function toRadiusKm(searchRadiusMeters: number | null) {
return Math.min(50, Math.max(1, Math.ceil((searchRadiusMeters ?? 1000) / 1000)));
}
export async function listDeepResearchBatches(db: Pool, userId: string): Promise<DeepResearchBatchSummary[]> {
return listDeepResearchBatchesForUser(db, userId);
}
export async function getDeepResearchBatchDetail(db: Pool, userId: string, batchId: string): Promise<DeepResearchBatchDetail | null> {
return getDeepResearchBatchDetailForUser(db, userId, batchId);
}
export async function createDeepResearchBatchForUser(
db: Pool,
userId: string,
input: CreateDeepResearchBatchRequest,
): Promise<DeepResearchBatchDetail> {
const preview = await previewDeepResearchForPoint(db, input as DeepResearchPreviewRequest);
const baseArea = await findPostalAreaContainingPoint(db, input.lat, input.lng);
if (!baseArea) {
throw new Error('No supported postal area was found for the selected pin.');
}
const areaRows = await listPostalAreasByPropagation(db, baseArea.id, input.propagation);
const batchId = await createDeepResearchBatch(db, userId, {
pinLat: input.lat,
pinLng: input.lng,
basePostalCode: preview.baseArea.postalCode,
countryCode: preview.countryCode,
propagation: input.propagation,
businessType: input.businessType,
keywords: input.keywords,
totalPostalAreas: preview.totalAreas,
});
let totalResults = 0;
let hadFailures = false;
try {
for (const area of areaRows) {
if (typeof area.centroid_lat !== 'number' || typeof area.centroid_lng !== 'number') {
hadFailures = true;
continue;
}
try {
const result = await runSearchForPostalArea(db, userId, {
name: `${input.businessType} in ${area.display_name || area.postal_code}`,
city: area.display_name || area.postal_code,
address: area.display_name || area.postal_code,
postalCode: area.postal_code,
countryCode: area.country_code,
radiusKm: toRadiusKm(area.search_radius_m),
businessType: input.businessType,
keywords: input.keywords,
lat: area.centroid_lat,
lng: area.centroid_lng,
deepResearchBatchId: batchId,
postalAreaId: area.id,
queryContextTerms: [area.postal_code, area.country_code],
});
totalResults += result.totalResults;
} catch {
hadFailures = true;
}
}
const finalStatus: JobStatus = hadFailures ? 'failed' : 'completed';
await finalizeDeepResearchBatch(db, batchId, {
status: finalStatus,
totalResults,
});
} catch (error) {
await failDeepResearchBatch(db, batchId);
throw error;
}
const detail = await getDeepResearchBatchDetailForUser(db, userId, batchId);
if (!detail) {
throw new Error('Failed to load the created deep research batch.');
}
return detail;
}