feat: Implement supplier export functionality with workbook generation

- Add `writeSupplierWorkbook` function to create Excel workbooks for supplier analysis results.
- Introduce `SupplierExportSummary` type for summarizing export data.
- Create tests for `writeSupplierWorkbook` to ensure correct sheet creation and data population.
- Implement supplier scoring logic in `supplier-scoring.ts` to evaluate product profitability and demand.
- Add tests for supplier scoring to validate scoring logic and verdict determination.
- Enhance UPC file analysis to integrate supplier scoring and export results to Excel.
- Update database writing logic to accommodate new supplier analysis results.
- Refactor types to include supplier-specific data structures and scoring metrics.
- Ensure proper cleanup of temporary files after tests.
This commit is contained in:
Victor Noguera
2026-05-19 01:19:48 -04:00
parent 41ef57a7bc
commit f3e4d3ac52
25 changed files with 1320 additions and 155 deletions

View File

@@ -26,23 +26,24 @@ export interface ProductRecord {
[key: string]: unknown;
}
export interface KeepaData {
currentPrice: number | null;
avgPrice90: number | null;
minPrice90: number | null;
maxPrice90: number | null;
export interface KeepaData {
currentPrice: number | null;
avgPrice90: number | null;
minPrice90: number | null;
maxPrice90: number | null;
salesRank: number | null;
salesRankAvg90: number | null;
salesRankDrops30: number | null;
salesRankDrops90: number | null;
sellerCount: number | null;
amazonIsSeller: boolean | null;
amazonBuyboxSharePct90d: number | null;
buyBoxSeller: string | null;
buyBoxPrice: number | null;
monthlySold: number | null;
categoryTree: string[];
}
amazonBuyboxSharePct90d: number | null;
buyBoxSeller: string | null;
buyBoxPrice: number | null;
buyBoxAvg90?: number | null;
monthlySold: number | null;
categoryTree: string[];
}
export type KeepaUpcLookupStatus =
| "found"
@@ -51,15 +52,17 @@ export type KeepaUpcLookupStatus =
| "multiple_asins"
| "request_failed";
export interface KeepaUpcLookupDetail {
requestedUpc: string;
normalizedUpc: string;
status: KeepaUpcLookupStatus;
export interface KeepaUpcLookupDetail {
requestedUpc: string;
normalizedUpc: string;
status: KeepaUpcLookupStatus;
asin: string | null;
candidateAsins: string[];
keepaData: KeepaData | null;
reason?: string;
}
reason?: string;
}
export type UpcLookupDetail = KeepaUpcLookupDetail;
export type SellabilityInfo = {
canSell: boolean | null;
@@ -88,10 +91,36 @@ export interface LlmVerdict {
reasoning: string;
}
export interface AnalysisResult {
product: EnrichedProduct;
verdict: LlmVerdict;
}
export interface AnalysisResult {
product: EnrichedProduct;
verdict: LlmVerdict;
}
export type SupplierVerdict = "BUY" | "WATCH" | "SKIP";
export interface SupplierScore {
salePrice: number | null;
fbaFee: number | null;
profit: number | null;
margin: number | null;
roi: number | null;
demandScore: number;
competitionPenalty: number;
score: number;
verdict: SupplierVerdict;
reason: string;
}
export interface SupplierAnalysisResult {
upc: string;
rowNumber?: number;
record: ProductRecord;
lookup: UpcLookupDetail;
keepa: KeepaData | null;
spApi: SpApiData | null;
score: SupplierScore;
fetchedAt: string;
}
export interface CategoryRunSummaryDb {
categoryId: number;