import { getDb } from "./database.ts"; import type { AnalysisResult } from "./types.ts"; function buildRow(r: AnalysisResult) { const price = r.product.keepa?.currentPrice ?? r.product.record.sellingPriceFromSheet ?? r.product.spApi.estimatedSalePrice; const rank = r.product.keepa?.salesRank ?? r.product.record.amazonRank; const canSellStatus = r.product.spApi.canSell == null ? "unknown" : r.product.spApi.canSell ? "yes" : "no"; return { ASIN: r.product.record.asin, Name: r.product.record.name, Brand: r.product.record.brand ?? "", Category: r.product.record.category ?? r.product.keepa?.categoryTree?.join(" > ") ?? "", "Unit Cost": r.product.record.unitCost, "Current Price": price ?? "", "Avg Price 90d": r.product.keepa?.avgPrice90 ?? "", "Avg Price 90d (sheet)": r.product.record.avgPrice90FromSheet ?? "", "Selling Price (sheet)": r.product.record.sellingPriceFromSheet ?? "", "Sales Rank": rank ?? "", "Rank Avg 90d": r.product.keepa?.salesRankAvg90 ?? "", Sellers: r.product.keepa?.sellerCount ?? "", "Amazon Is Seller": r.product.keepa?.amazonIsSeller ?? null, "Amazon Buy Box Share 90d %": r.product.keepa?.amazonBuyboxSharePct90d ?? "", "Monthly Sold": r.product.keepa?.monthlySold ?? "", "Rank Drops 30d": r.product.keepa?.salesRankDrops30 ?? "", "Rank Drops 90d": r.product.keepa?.salesRankDrops90 ?? "", "FBA Net (sheet)": r.product.record.fbaNet ?? "", "Gross Profit $": r.product.record.grossProfit ?? "", "Gross Profit %": r.product.record.grossProfitPct ?? "", "Net Profit (sheet)": r.product.record.netProfitFromSheet ?? "", "ROI (sheet)": r.product.record.roiFromSheet ?? "", MOQ: r.product.record.moq ?? "", "MOQ Cost": r.product.record.moqCost ?? "", "Qty Available": r.product.record.totalQtyAvail ?? "", Supplier: r.product.record.supplier ?? "", "Source URL": r.product.record.sourceUrl ?? "", "ASIN Link": r.product.record.asinLink ?? "", "Promo/Coupon Code": r.product.record.promoCouponCode ?? "", Notes: r.product.record.notes ?? "", "Lead Date": r.product.record.leadDate ?? "", "FBA Fee": r.product.spApi.fbaFee, "FBM Fee": r.product.spApi.fbmFee, "Referral %": r.product.spApi.referralFeePercent, "Can Sell": canSellStatus, Sellability: r.product.spApi.sellabilityStatus, "Sellability Reason": r.product.spApi.sellabilityReason ?? "", Verdict: r.verdict.verdict, Confidence: r.verdict.confidence, Reasoning: r.verdict.reasoning, }; } export function writeResultsToDb( results: AnalysisResult[], dbPath: string, inputFile: string, outputFile: string | undefined, ): void { const database = getDb(dbPath); const timestamp = new Date().toISOString(); const fbaCount = results.filter((r) => r.verdict.verdict === "FBA").length; const fbmCount = results.filter((r) => r.verdict.verdict === "FBM").length; const skipCount = results.filter((r) => r.verdict.verdict === "SKIP").length; const insertRun = database.prepare( `INSERT INTO runs ( timestamp, input_file, output_file, total_products, fba_count, fbm_count, skip_count ) VALUES (?, ?, ?, ?, ?, ?, ?)`, ); const runInfo = insertRun.run( timestamp, inputFile, outputFile ?? null, results.length, fbaCount, fbmCount, skipCount, ); const runId = (runInfo.changes as number) > 0 ? (runInfo.lastInsertRowid as number) : null; if (runId === null) { console.error("Failed to insert run record into SQLite."); return; } const insertResult = database.prepare( `INSERT INTO results ( run_id, asin, product_name, brand, category, unit_cost, current_price, avg_price_90d, avg_price_90d_sheet, selling_price_sheet, sales_rank, rank_avg_90d, sellers, amazon_is_seller, amazon_buybox_share_pct_90d, monthly_sold, rank_drops_30d, rank_drops_90d, fba_net_sheet, gross_profit_dollar, gross_profit_pct, net_profit_sheet, roi_sheet, moq, moq_cost, qty_available, supplier, source_url, asin_link, promo_coupon_code, notes, lead_date, fba_fee, fbm_fee, referral_percent, can_sell, sellability_status, sellability_reason, verdict, confidence, reasoning, fetched_at ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )`, ); database.transaction(() => { for (const r of results) { const row = buildRow(r); insertResult.run( runId, row.ASIN, row.Name, row.Brand, row.Category, row["Unit Cost"] ?? null, row["Current Price"] ?? null, row["Avg Price 90d"] ?? null, row["Avg Price 90d (sheet)"] ?? null, row["Selling Price (sheet)"] ?? null, row["Sales Rank"] ?? null, row["Rank Avg 90d"] ?? null, row.Sellers ?? null, row["Amazon Is Seller"] == null ? null : row["Amazon Is Seller"] ? 1 : 0, row["Amazon Buy Box Share 90d %"] ?? null, row["Monthly Sold"] ?? null, row["Rank Drops 30d"] ?? null, row["Rank Drops 90d"] ?? null, row["FBA Net (sheet)"] ?? null, row["Gross Profit $"] ?? null, row["Gross Profit %"] ?? null, row["Net Profit (sheet)"] ?? null, row["ROI (sheet)"] ?? null, row.MOQ ?? null, row["MOQ Cost"] ?? null, row["Qty Available"] ?? null, row.Supplier ?? null, row["Source URL"] ?? null, row["ASIN Link"] ?? null, row["Promo/Coupon Code"] ?? null, row.Notes ?? null, row["Lead Date"] ?? null, row["FBA Fee"] ?? null, row["FBM Fee"] ?? null, row["Referral %"] ?? null, row["Can Sell"], row.Sellability, row["Sellability Reason"] ?? null, row.Verdict, row.Confidence ?? null, row.Reasoning, r.product.fetchedAt, ); } })(); console.log(`Results written to SQLite database for run_id: ${runId}`); } export function printResults(results: AnalysisResult[]): void { const rows = results .filter((r) => r.verdict.verdict === "FBA" || r.verdict.verdict === "FBM") .map((r) => { const sellingPrice = r.product.keepa?.currentPrice ?? r.product.record.sellingPriceFromSheet ?? r.product.spApi.estimatedSalePrice; const referralFee = sellingPrice != null ? sellingPrice * (r.product.spApi.referralFeePercent / 100) : null; const fulfillmentFee = r.verdict.verdict === "FBA" ? r.product.spApi.fbaFee : r.product.spApi.fbmFee; const netProfit = sellingPrice != null ? Math.round( (sellingPrice - r.product.record.unitCost - fulfillmentFee - (referralFee ?? 0)) * 100, ) / 100 : ""; return { ASIN: r.product.record.asin, Name: r.product.record.name.slice(0, 40), Category: String( r.product.record.category ?? r.product.keepa?.categoryTree?.join(" > ") ?? "", ).slice(0, 20), "Unit Cost": r.product.record.unitCost, "Selling Price": sellingPrice ?? "", "Net Profit": netProfit, "Monthly Sold": r.product.keepa?.monthlySold ?? "", "Sold 90 Day": r.product.keepa?.salesRankDrops90 ?? "", "Can Sell": r.product.spApi.canSell == null ? "unknown" : r.product.spApi.canSell ? "yes" : "no", Sellability: r.product.spApi.sellabilityStatus, "Sellability Reason": String( r.product.spApi.sellabilityReason ?? "", ).slice(0, 60), Confidence: r.verdict.confidence, Reasoning: r.verdict.reasoning.slice(0, 60), }; }); console.log("\n=== Analysis Results ===\n"); if (rows.length === 0) { console.log("No FBA/FBM leads found."); } else { console.table(rows); } const summary = { FBA: results.filter((r) => r.verdict.verdict === "FBA").length, FBM: results.filter((r) => r.verdict.verdict === "FBM").length, SKIP: results.filter((r) => r.verdict.verdict === "SKIP").length, Available: results.filter( (r) => r.product.spApi.sellabilityStatus === "available", ).length, Restricted: results.filter( (r) => r.product.spApi.sellabilityStatus === "restricted", ).length, NotAvailable: results.filter( (r) => r.product.spApi.sellabilityStatus === "not_available", ).length, Unknown: results.filter( (r) => r.product.spApi.sellabilityStatus === "unknown", ).length, }; console.log( `\nSummary: ${summary.FBA} FBA | ${summary.FBM} FBM | ${summary.SKIP} SKIP out of ${results.length} products\n`, ); console.log( `Sellability: ${summary.Available} available | ${summary.Restricted} restricted | ${summary.NotAvailable} not_available | ${summary.Unknown} unknown\n`, ); }