Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 41s
97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
// src/services/flyerDataTransformer.ts
|
|
import path from 'path';
|
|
import type { z } from 'zod';
|
|
import type { Logger } from 'pino';
|
|
import type { FlyerInsert, FlyerItemInsert, FlyerStatus } from '../types';
|
|
import type { AiFlyerDataSchema, AiProcessorResult } from './flyerAiProcessor.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 {
|
|
/**
|
|
* Normalizes a single raw item from the AI, providing default values for nullable fields.
|
|
* @param item The raw item object from the AI.
|
|
* @returns A normalized item object ready for database insertion.
|
|
*/
|
|
private _normalizeItem(
|
|
item: z.infer<typeof AiFlyerDataSchema>['items'][number],
|
|
): FlyerItemInsert {
|
|
return {
|
|
...item,
|
|
// Use logical OR to default falsy values (null, undefined, '') to a fallback.
|
|
// The trim is important for cases where the AI returns only whitespace.
|
|
item: String(item.item || '').trim() || 'Unknown Item',
|
|
// Use nullish coalescing to default only null/undefined to an empty string.
|
|
price_display: String(item.price_display ?? ''),
|
|
quantity: String(item.quantity ?? ''),
|
|
// Use logical OR to default falsy category names (null, undefined, '') to a fallback.
|
|
category_name: String(item.category_name || 'Other/Miscellaneous'),
|
|
// Use nullish coalescing to convert null to undefined for the database.
|
|
master_item_id: item.master_item_id ?? undefined,
|
|
view_count: 0,
|
|
click_count: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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(
|
|
aiResult: AiProcessorResult,
|
|
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 { data: extractedData, needsReview } = aiResult;
|
|
|
|
const firstImage = imagePaths[0].path;
|
|
const iconFileName = await generateFlyerIcon(
|
|
firstImage,
|
|
path.join(path.dirname(firstImage), 'icons'),
|
|
logger,
|
|
);
|
|
|
|
const itemsForDb: FlyerItemInsert[] = extractedData.items.map((item) => this._normalizeItem(item));
|
|
|
|
const storeName = extractedData.store_name || 'Unknown Store (auto)';
|
|
if (!extractedData.store_name) {
|
|
logger.warn('AI did not return a store name. Using fallback "Unknown Store (auto)".');
|
|
}
|
|
|
|
const flyerData: FlyerInsert = {
|
|
file_name: originalFileName,
|
|
image_url: `/flyer-images/${path.basename(firstImage)}`,
|
|
icon_url: `/flyer-images/icons/${iconFileName}`,
|
|
checksum,
|
|
store_name: storeName,
|
|
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,
|
|
status: needsReview ? 'needs_review' : 'processed',
|
|
};
|
|
|
|
logger.info(
|
|
{ itemCount: itemsForDb.length, storeName: flyerData.store_name },
|
|
'Data transformation complete.',
|
|
);
|
|
|
|
return { flyerData, itemsForDb };
|
|
}
|
|
}
|