Files
flyer-crawler.projectium.com/src/services/flyerDataTransformer.ts

84 lines
3.6 KiB
TypeScript

// src/services/flyerDataTransformer.ts
import path from 'path';
import type { z } from 'zod';
import type { Logger } from 'pino';
import type { FlyerInsert, FlyerItemInsert } from '../types';
import type { AiFlyerDataSchema } from './flyerProcessingService.server';
import { generateFlyerIcon } from '../utils/imageProcessor';
/**
* This class is responsible for transforming the validated data from the AI service
* into the structured format required for database insertion (FlyerInsert and FlyerItemInsert).
*/
export class FlyerDataTransformer {
/**
* Transforms AI-extracted data into database-ready flyer and item records.
* @param extractedData The validated data from the AI.
* @param imagePaths The paths to the flyer images.
* @param originalFileName The original name of the uploaded file.
* @param checksum The checksum of the file.
* @param userId The ID of the user who uploaded the file, if any.
* @param logger The request-scoped or job-scoped logger instance.
* @returns A promise that resolves to an object containing the prepared flyer and item data.
*/
async transform(
extractedData: z.infer<typeof AiFlyerDataSchema>,
imagePaths: { path: string; mimetype: string }[],
originalFileName: string,
checksum: string,
userId: string | undefined,
logger: Logger,
): Promise<{ flyerData: FlyerInsert; itemsForDb: FlyerItemInsert[] }> {
logger.info('Starting data transformation from AI output to database format.');
const firstImage = imagePaths[0].path;
const iconFileName = await generateFlyerIcon(
firstImage,
path.join(path.dirname(firstImage), 'icons'),
logger,
);
const itemsForDb: FlyerItemInsert[] = extractedData.items.map((item) => ({
...item,
// Ensure 'item' is always a string, defaulting to 'Unknown Item' if null/undefined/empty.
item:
item.item === null || item.item === undefined || String(item.item).trim() === ''
? 'Unknown Item'
: String(item.item),
// Ensure 'price_display' is always a string, defaulting to empty if null/undefined.
price_display:
item.price_display === null || item.price_display === undefined
? ''
: String(item.price_display),
// Ensure 'quantity' is always a string, defaulting to empty if null/undefined.
quantity: item.quantity === null || item.quantity === undefined ? '' : String(item.quantity),
// Ensure 'category_name' is always a string, defaulting to 'Other/Miscellaneous' if null/undefined.
category_name: item.category_name === null || item.category_name === undefined ? 'Other/Miscellaneous' : String(item.category_name),
master_item_id: item.master_item_id === null ? undefined : item.master_item_id, // Convert null to undefined
view_count: 0,
click_count: 0,
updated_at: new Date().toISOString(),
}));
const flyerData: FlyerInsert = {
file_name: originalFileName,
image_url: `/flyer-images/${path.basename(firstImage)}`,
icon_url: `/flyer-images/icons/${iconFileName}`,
checksum,
store_name: extractedData.store_name || 'Unknown Store (auto)',
valid_from: extractedData.valid_from,
valid_to: extractedData.valid_to,
store_address: extractedData.store_address, // The number of items is now calculated directly from the transformed data.
item_count: itemsForDb.length,
uploaded_by: userId,
};
logger.info(
{ itemCount: itemsForDb.length, storeName: flyerData.store_name },
'Data transformation complete.',
);
return { flyerData, itemsForDb };
}
}