Files
stef-openclaw-skills/skills/property-assessor/src/photo-review.ts

80 lines
2.6 KiB
TypeScript

import { extractHarPhotos } from "../../web-automation/scripts/har-photos.js";
import { extractZillowPhotos } from "../../web-automation/scripts/zillow-photos.js";
import { withTimeout } from "./async-timeout.js";
export type PhotoSource = "zillow" | "har";
export interface PhotoExtractionResult {
source: PhotoSource;
requestedUrl: string;
finalUrl?: string;
expectedPhotoCount?: number | null;
complete?: boolean;
photoCount: number;
imageUrls: string[];
notes: string[];
}
export interface PhotoReviewResolution {
review: Record<string, unknown>;
discoveredListingUrls: Array<{ label: string; url: string }>;
}
interface PhotoReviewDeps {
timeoutMs?: number;
extractZillowPhotosFn?: typeof extractZillowPhotos;
extractHarPhotosFn?: typeof extractHarPhotos;
}
const DEFAULT_PHOTO_EXTRACTION_TIMEOUT_MS = Number(
process.env.PROPERTY_ASSESSOR_PHOTO_TIMEOUT_MS || 25_000
);
export async function extractPhotoData(
source: PhotoSource,
url: string,
deps: PhotoReviewDeps = {}
): Promise<PhotoExtractionResult> {
const timeoutMs = deps.timeoutMs ?? DEFAULT_PHOTO_EXTRACTION_TIMEOUT_MS;
const extractZillowPhotosFn = deps.extractZillowPhotosFn || extractZillowPhotos;
const extractHarPhotosFn = deps.extractHarPhotosFn || extractHarPhotos;
if (source === "zillow") {
const payload = await withTimeout(
() => extractZillowPhotosFn(url),
{
operationName: "Zillow photo extraction",
timeoutMs
}
);
return {
source,
requestedUrl: String(payload.requestedUrl || url),
finalUrl: typeof payload.finalUrl === "string" ? payload.finalUrl : undefined,
expectedPhotoCount: typeof payload.expectedPhotoCount === "number" ? payload.expectedPhotoCount : null,
complete: Boolean(payload.complete),
photoCount: Number(payload.photoCount || 0),
imageUrls: Array.isArray(payload.imageUrls) ? payload.imageUrls.map(String) : [],
notes: Array.isArray(payload.notes) ? payload.notes.map(String) : []
};
}
const payload = await withTimeout(
() => extractHarPhotosFn(url),
{
operationName: "HAR photo extraction",
timeoutMs
}
);
return {
source,
requestedUrl: String(payload.requestedUrl || url),
finalUrl: typeof payload.finalUrl === "string" ? payload.finalUrl : undefined,
expectedPhotoCount: typeof payload.expectedPhotoCount === "number" ? payload.expectedPhotoCount : null,
complete: Boolean(payload.complete),
photoCount: Number(payload.photoCount || 0),
imageUrls: Array.isArray(payload.imageUrls) ? payload.imageUrls.map(String) : [],
notes: Array.isArray(payload.notes) ? payload.notes.map(String) : []
};
}