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.
100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
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;
|
|
}
|