diff --git a/CLAUDE.md b/CLAUDE.md index 5a4960b..281e1da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,8 +12,8 @@ Default to using Bun instead of Node.js. ## APIs -- `bun:sqlite` for SQLite. Don't use `better-sqlite3`. - `Bun.redis` for Redis. Don't use `ioredis`. +- Use Drizzle ORM with `postgres` driver for Postgres. Connection is in `src/db/index.ts`. - Prefer `Bun.file` over `node:fs`'s readFile/writeFile. - `Bun.$\`cmd\`` instead of execa. @@ -24,7 +24,7 @@ Default to using Bun instead of Node.js. bun test # Run a single test file -bun test src/supplier-scoring.test.ts +bun test src/supplier/supplier-scoring.test.ts # Type-check (no emit) ./node_modules/.bin/tsc --noEmit @@ -40,6 +40,9 @@ bun run bestsellers bun run monthly-sold bun run mid-range +# Stalker pipeline +bun run stalker --input input/asins.xlsx + # Web API server bun run start:web # http://localhost:3000 @@ -47,29 +50,37 @@ bun run start:web # http://localhost:3000 bun run src/sp-test.ts bun run src/sp-test.ts B07SN9BHVV bun run src/sp-test.ts --sellability B07SN9BHVV + +# Database migrations (Drizzle) +bun run db:generate +bun run db:migrate ``` ## Architecture -Two distinct analysis pipelines share infrastructure (Keepa, SP-API, Redis, SQLite) but diverge in how they produce verdicts. +Two distinct analysis pipelines share infrastructure (Keepa, SP-API, Redis, Postgres) but diverge in how they produce verdicts. ### ASIN Lead-list Pipeline (`src/index.ts` → `src/analysis-pipeline.ts`) For spreadsheets containing known ASINs. Verdict is LLM-based (FBA/FBM/SKIP via LM Studio). -Flow: `reader.ts` parse → Redis cache check → `sp-api.ts` sellability gate (5 concurrent workers) → `keepa.ts` batch enrichment → `sp-api.ts` pricing + FBA fees (5 concurrent workers) → `llm.ts` batched analysis (5 products/batch) → `writer.ts` XLSX + SQLite. +Flow: `reader.ts` parse → Redis cache check → `integrations/sp-api.ts` sellability gate (5 concurrent workers) → `integrations/keepa.ts` batch enrichment → `integrations/sp-api.ts` pricing + FBA fees (5 concurrent workers) → `integrations/llm.ts` batched analysis (5 products/batch) → `writer.ts` XLSX + Postgres. -### Supplier UPC Pipeline (`src/upc-file-analysis.ts`) +### Supplier UPC Pipeline (`src/supplier/upc-file-analysis.ts`) For supplier price lists containing UPC/EAN values. Verdict is deterministic (BUY/WATCH/SKIP); never calls LM Studio. -Flow: `upc-file-reader.ts` streaming parse (`.xlsx`) or row-window parse (`.xls`) → SP-API catalog UPC lookup first, Keepa UPC lookup as fallback → `keepa.ts` demand enrichment → `sp-api.ts` sellability + FBA fees → `supplier-scoring.ts` deterministic score → `supplier-export.ts` Excel workbook (`Ranked Leads`, `Skipped`, `Summary` sheets) + SQLite. +Flow: `supplier/upc-file-reader.ts` streaming parse (`.xlsx`) or row-window parse (`.xls`) → SP-API catalog UPC lookup first, Keepa UPC lookup as fallback → `integrations/keepa.ts` demand enrichment → `integrations/sp-api.ts` sellability + FBA fees → `supplier/supplier-scoring.ts` deterministic score → `supplier/supplier-export.ts` Excel workbook (`Ranked Leads`, `Skipped`, `Summary` sheets) + Postgres. UPC resolution priority: SP-API catalog lookup → Keepa fallback (for no-match or request failure only). ### Category Pipelines -`bestsellers-by-category.ts`, `top-monthly-sold-by-category.ts`, `mid-range-sellers-by-category.ts` — Keepa category browsing → SP-API sellability gate → LLM verdict. Each saves results to SQLite. Mid-range applies configurable filters (monthly sold, price, seller count, Amazon buy box share). +`src/categories/` — Keepa category browsing → SP-API sellability gate → LLM verdict. Each saves results to Postgres. Mid-range applies configurable filters (monthly sold, price, seller count, Amazon buy box share). + +### Stalker Pipeline (`src/stalker/stalker.ts`) + +Tracks competitor sellers across ASINs. Fetches storefronts, checks sellability of inventory items, and persists matched seller data to Postgres. ### Shared Infrastructure @@ -77,18 +88,23 @@ UPC resolution priority: SP-API catalog lookup → Keepa fallback (for no-match |--------|------| | `src/types.ts` | All shared interfaces (`ProductRecord`, `KeepaData`, `SpApiData`, `SupplierScore`, etc.) | | `src/config.ts` | Env var loading via `Bun.env` | -| `src/keepa.ts` | Keepa API: batch ASIN fetch, UPC lookup, auto rate-limiting on token exhaustion | -| `src/sp-api.ts` | SP-API: sellability (`getListingsRestrictions`), pricing+fees, UPC catalog lookup | -| `src/cache.ts` | Redis caching (24h TTL for lead-list; 12h for mid-range) | -| `src/database.ts` | SQLite `runs` + `results` tables; auto-creates `db/results.db` | +| `src/db/index.ts` | Drizzle Postgres connection (shared pool) | +| `src/db/schema.ts` | Drizzle schema for all tables | +| `src/integrations/keepa.ts` | Keepa API: batch ASIN fetch, UPC lookup, auto rate-limiting | +| `src/integrations/sp-api.ts` | SP-API: sellability, pricing+fees, UPC catalog lookup | +| `src/integrations/cache.ts` | Redis caching (24h TTL for lead-list; 12h for mid-range) | +| `src/integrations/llm.ts` | LLM integration (LM Studio / Claude) | | `src/server.ts` | Bun HTTP server exposing REST endpoints for both pipelines | ### File Layout +- `src/integrations/` — external API clients (Keepa, SP-API, Redis cache, LLM, SearXNG) +- `src/categories/` — category discovery pipelines +- `src/stalker/` — competitor seller tracking pipeline +- `src/supplier/` — supplier UPC analysis pipeline +- `src/db/` — Drizzle schema and connection - `input/` — source spreadsheets (git-ignored) - `output/` — generated workbooks (git-ignored) -- `db/` — SQLite files (git-ignored) -- `src/` — all source and test files ## Project Rules diff --git a/drizzle/0000_gorgeous_william_stryker.sql b/drizzle/0000_gorgeous_william_stryker.sql new file mode 100644 index 0000000..2949d2c --- /dev/null +++ b/drizzle/0000_gorgeous_william_stryker.sql @@ -0,0 +1,217 @@ +CREATE TYPE "public"."run_status" AS ENUM('running', 'ok', 'empty', 'failed', 'completed');--> statement-breakpoint +CREATE TYPE "public"."run_type" AS ENUM('lead_analysis', 'category_analysis', 'supplier_upc', 'stalker');--> statement-breakpoint +CREATE TABLE "analysis_results" ( + "id" serial PRIMARY KEY NOT NULL, + "run_id" integer NOT NULL, + "asin" text NOT NULL, + "product_name" text, + "brand" text, + "category" text, + "upc" text, + "unit_cost" real, + "avg_price_90d_sheet" real, + "selling_price_sheet" real, + "fba_net_sheet" real, + "gross_profit_dollar" real, + "gross_profit_pct" real, + "net_profit_sheet" real, + "roi_sheet" real, + "moq" integer, + "moq_cost" real, + "qty_available" integer, + "supplier" text, + "source_url" text, + "asin_link" text, + "promo_coupon_code" text, + "notes" text, + "lead_date" text, + "current_price" real, + "avg_price_90d" real, + "sales_rank" integer, + "rank_avg_90d" integer, + "monthly_sold" integer, + "rank_drops_30d" integer, + "rank_drops_90d" integer, + "seller_count" integer, + "amazon_is_seller" boolean, + "amazon_buybox_share_pct_90d" real, + "fba_fee" real, + "fbm_fee" real, + "referral_percent" real, + "can_sell" text, + "sellability_status" text, + "sellability_reason" text, + "supplier_score" real, + "supplier_profit" real, + "supplier_margin" real, + "supplier_roi" real, + "supplier_reason" text, + "upc_lookup_status" text, + "upc_lookup_reason" text, + "candidate_asins" text, + "verdict" text NOT NULL, + "confidence" real, + "reasoning" text, + "fetched_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +CREATE TABLE "category_product_results" ( + "id" serial PRIMARY KEY NOT NULL, + "asin" text NOT NULL, + "run_id" integer NOT NULL, + "name" text NOT NULL, + "brand" text, + "category" text, + "unit_cost" real, + "current_price" real, + "avg_price_90d" real, + "avg_price_90d_sheet" real, + "selling_price_sheet" real, + "sales_rank" integer, + "sales_rank_avg_90d" integer, + "seller_count" integer, + "amazon_is_seller" boolean, + "amazon_buybox_share_pct_90d" real, + "monthly_sold" integer, + "rank_drops_30d" integer, + "rank_drops_90d" integer, + "fba_fee" real, + "fbm_fee" real, + "referral_percent" real, + "can_sell" text, + "sellability_status" text, + "sellability_reason" text, + "verdict" text NOT NULL, + "confidence" real NOT NULL, + "reasoning" text, + "fetched_at" timestamp with time zone NOT NULL, + CONSTRAINT "category_product_results_asin_unique" UNIQUE("asin") +); +--> statement-breakpoint +CREATE TABLE "runs" ( + "id" serial PRIMARY KEY NOT NULL, + "type" "run_type" NOT NULL, + "input_file" text, + "output_file" text, + "status" "run_status" DEFAULT 'running' NOT NULL, + "error_message" text, + "total_products" integer, + "fba_count" integer, + "fbm_count" integer, + "skip_count" integer, + "category_id" integer, + "category_label" text, + "top_asins_checked" integer, + "available_asins" integer, + "started_at" timestamp with time zone DEFAULT now() NOT NULL, + "completed_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "sellers" ( + "seller_id" text PRIMARY KEY NOT NULL, + "seller_name" text, + "rating" real, + "rating_count" integer, + "storefront_asin_total" integer, + "persisted_inventory_sample_count" integer, + "last_updated_at" timestamp with time zone NOT NULL, + "raw_seller_json" text +); +--> statement-breakpoint +CREATE TABLE "stalker_asin_scans" ( + "id" serial PRIMARY KEY NOT NULL, + "run_id" integer NOT NULL, + "source_asin" text NOT NULL, + "title" text, + "offer_count" integer DEFAULT 0 NOT NULL, + "candidate_seller_count" integer DEFAULT 0 NOT NULL, + "matched_seller_count" integer DEFAULT 0 NOT NULL, + "fetched_at" timestamp with time zone NOT NULL, + "raw_product_json" text, + CONSTRAINT "uq_stalker_scans_run_asin" UNIQUE("run_id","source_asin") +); +--> statement-breakpoint +CREATE TABLE "stalker_asin_sellers" ( + "id" serial PRIMARY KEY NOT NULL, + "scan_id" integer NOT NULL, + "seller_id" text NOT NULL, + "offer_price" real, + "condition" text, + "is_fba" boolean, + "stock" integer, + "seller_rating" real, + "seller_rating_count" integer, + "raw_offer_json" text, + CONSTRAINT "uq_stalker_asin_sellers_scan_seller" UNIQUE("scan_id","seller_id") +); +--> statement-breakpoint +CREATE TABLE "stalker_runs" ( + "id" serial PRIMARY KEY NOT NULL, + "input_file" text NOT NULL, + "started_at" timestamp with time zone NOT NULL, + "completed_at" timestamp with time zone, + "requested_asins" integer DEFAULT 0 NOT NULL, + "skipped_asins" integer DEFAULT 0 NOT NULL, + "scanned_asins" integer DEFAULT 0 NOT NULL, + "source_asins_with_matches" integer DEFAULT 0 NOT NULL, + "candidate_sellers" integer DEFAULT 0 NOT NULL, + "qualifying_sellers" integer DEFAULT 0 NOT NULL, + "matched_sellers" integer DEFAULT 0 NOT NULL, + "seller_metadata_requests" integer DEFAULT 0 NOT NULL, + "seller_storefront_requests" integer DEFAULT 0 NOT NULL, + "inventory_sellability_checked_asins" integer DEFAULT 0 NOT NULL, + "inventory_sellability_available_asins" integer DEFAULT 0 NOT NULL, + "inventory_sellability_excluded_asins" integer DEFAULT 0 NOT NULL, + "persisted_inventory_asins" integer DEFAULT 0 NOT NULL, + "status" text NOT NULL, + "error_message" text +); +--> statement-breakpoint +CREATE TABLE "stalker_seller_inventory" ( + "id" serial PRIMARY KEY NOT NULL, + "run_id" integer NOT NULL, + "seller_id" text NOT NULL, + "asin" text NOT NULL, + "can_sell" boolean, + "sellability_status" text, + "sellability_reason" text, + "product_title" text, + "brand" text, + "category_tree" text, + "current_price" real, + "avg_price_90d" real, + "sales_rank" integer, + "monthly_sold" integer, + "seller_count" integer, + "amazon_is_seller" boolean, + "raw_product_json" text, + "last_seen_at" timestamp with time zone NOT NULL, + "raw_inventory_json" text, + CONSTRAINT "uq_stalker_inventory_run_seller_asin" UNIQUE("run_id","seller_id","asin") +); +--> statement-breakpoint +ALTER TABLE "analysis_results" ADD CONSTRAINT "analysis_results_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "category_product_results" ADD CONSTRAINT "category_product_results_run_id_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "stalker_asin_scans" ADD CONSTRAINT "stalker_asin_scans_run_id_stalker_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."stalker_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "stalker_asin_sellers" ADD CONSTRAINT "stalker_asin_sellers_scan_id_stalker_asin_scans_id_fk" FOREIGN KEY ("scan_id") REFERENCES "public"."stalker_asin_scans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "stalker_asin_sellers" ADD CONSTRAINT "stalker_asin_sellers_seller_id_sellers_seller_id_fk" FOREIGN KEY ("seller_id") REFERENCES "public"."sellers"("seller_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "stalker_seller_inventory" ADD CONSTRAINT "stalker_seller_inventory_run_id_stalker_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."stalker_runs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "stalker_seller_inventory" ADD CONSTRAINT "stalker_seller_inventory_seller_id_sellers_seller_id_fk" FOREIGN KEY ("seller_id") REFERENCES "public"."sellers"("seller_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "idx_analysis_results_run_id" ON "analysis_results" USING btree ("run_id");--> statement-breakpoint +CREATE INDEX "idx_analysis_results_asin" ON "analysis_results" USING btree ("asin");--> statement-breakpoint +CREATE INDEX "idx_analysis_results_verdict" ON "analysis_results" USING btree ("verdict");--> statement-breakpoint +CREATE INDEX "idx_analysis_results_sellability_status" ON "analysis_results" USING btree ("sellability_status");--> statement-breakpoint +CREATE INDEX "idx_analysis_results_fetched_at" ON "analysis_results" USING btree ("fetched_at");--> statement-breakpoint +CREATE INDEX "idx_category_results_run_id" ON "category_product_results" USING btree ("run_id");--> statement-breakpoint +CREATE INDEX "idx_category_results_verdict" ON "category_product_results" USING btree ("verdict");--> statement-breakpoint +CREATE INDEX "idx_category_results_sellability_status" ON "category_product_results" USING btree ("sellability_status");--> statement-breakpoint +CREATE INDEX "idx_category_results_fetched_at" ON "category_product_results" USING btree ("fetched_at");--> statement-breakpoint +CREATE INDEX "idx_runs_started_at" ON "runs" USING btree ("started_at");--> statement-breakpoint +CREATE INDEX "idx_runs_type" ON "runs" USING btree ("type");--> statement-breakpoint +CREATE INDEX "idx_runs_status" ON "runs" USING btree ("status");--> statement-breakpoint +CREATE INDEX "idx_stalker_scans_run_id" ON "stalker_asin_scans" USING btree ("run_id");--> statement-breakpoint +CREATE INDEX "idx_stalker_scans_source_asin" ON "stalker_asin_scans" USING btree ("source_asin");--> statement-breakpoint +CREATE INDEX "idx_stalker_runs_started_at" ON "stalker_runs" USING btree ("started_at");--> statement-breakpoint +CREATE INDEX "idx_stalker_inventory_seller_id" ON "stalker_seller_inventory" USING btree ("seller_id");--> statement-breakpoint +CREATE INDEX "idx_stalker_inventory_asin" ON "stalker_seller_inventory" USING btree ("asin");--> statement-breakpoint +CREATE INDEX "idx_stalker_inventory_product_title" ON "stalker_seller_inventory" USING btree ("product_title"); \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..09b0018 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,1545 @@ +{ + "id": "5b8f629e-e65a-40a4-b261-4a460b4c23fb", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.analysis_results": { + "name": "analysis_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "asin": { + "name": "asin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "product_name": { + "name": "product_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upc": { + "name": "upc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_cost": { + "name": "unit_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "avg_price_90d_sheet": { + "name": "avg_price_90d_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "selling_price_sheet": { + "name": "selling_price_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fba_net_sheet": { + "name": "fba_net_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "gross_profit_dollar": { + "name": "gross_profit_dollar", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "gross_profit_pct": { + "name": "gross_profit_pct", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "net_profit_sheet": { + "name": "net_profit_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "roi_sheet": { + "name": "roi_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moq": { + "name": "moq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "moq_cost": { + "name": "moq_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "qty_available": { + "name": "qty_available", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "supplier": { + "name": "supplier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asin_link": { + "name": "asin_link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_coupon_code": { + "name": "promo_coupon_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lead_date": { + "name": "lead_date", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_price": { + "name": "current_price", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "avg_price_90d": { + "name": "avg_price_90d", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "sales_rank": { + "name": "sales_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rank_avg_90d": { + "name": "rank_avg_90d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "monthly_sold": { + "name": "monthly_sold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rank_drops_30d": { + "name": "rank_drops_30d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rank_drops_90d": { + "name": "rank_drops_90d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seller_count": { + "name": "seller_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "amazon_is_seller": { + "name": "amazon_is_seller", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "amazon_buybox_share_pct_90d": { + "name": "amazon_buybox_share_pct_90d", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fba_fee": { + "name": "fba_fee", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fbm_fee": { + "name": "fbm_fee", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "referral_percent": { + "name": "referral_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "can_sell": { + "name": "can_sell", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sellability_status": { + "name": "sellability_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sellability_reason": { + "name": "sellability_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "supplier_score": { + "name": "supplier_score", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "supplier_profit": { + "name": "supplier_profit", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "supplier_margin": { + "name": "supplier_margin", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "supplier_roi": { + "name": "supplier_roi", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "supplier_reason": { + "name": "supplier_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upc_lookup_status": { + "name": "upc_lookup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upc_lookup_reason": { + "name": "upc_lookup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "candidate_asins": { + "name": "candidate_asins", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verdict": { + "name": "verdict", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_analysis_results_run_id": { + "name": "idx_analysis_results_run_id", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analysis_results_asin": { + "name": "idx_analysis_results_asin", + "columns": [ + { + "expression": "asin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analysis_results_verdict": { + "name": "idx_analysis_results_verdict", + "columns": [ + { + "expression": "verdict", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analysis_results_sellability_status": { + "name": "idx_analysis_results_sellability_status", + "columns": [ + { + "expression": "sellability_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_analysis_results_fetched_at": { + "name": "idx_analysis_results_fetched_at", + "columns": [ + { + "expression": "fetched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "analysis_results_run_id_runs_id_fk": { + "name": "analysis_results_run_id_runs_id_fk", + "tableFrom": "analysis_results", + "tableTo": "runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.category_product_results": { + "name": "category_product_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "asin": { + "name": "asin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_cost": { + "name": "unit_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "current_price": { + "name": "current_price", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "avg_price_90d": { + "name": "avg_price_90d", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "avg_price_90d_sheet": { + "name": "avg_price_90d_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "selling_price_sheet": { + "name": "selling_price_sheet", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "sales_rank": { + "name": "sales_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sales_rank_avg_90d": { + "name": "sales_rank_avg_90d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seller_count": { + "name": "seller_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "amazon_is_seller": { + "name": "amazon_is_seller", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "amazon_buybox_share_pct_90d": { + "name": "amazon_buybox_share_pct_90d", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "monthly_sold": { + "name": "monthly_sold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rank_drops_30d": { + "name": "rank_drops_30d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rank_drops_90d": { + "name": "rank_drops_90d", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fba_fee": { + "name": "fba_fee", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fbm_fee": { + "name": "fbm_fee", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "referral_percent": { + "name": "referral_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "can_sell": { + "name": "can_sell", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sellability_status": { + "name": "sellability_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sellability_reason": { + "name": "sellability_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verdict": { + "name": "verdict", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_category_results_run_id": { + "name": "idx_category_results_run_id", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category_results_verdict": { + "name": "idx_category_results_verdict", + "columns": [ + { + "expression": "verdict", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category_results_sellability_status": { + "name": "idx_category_results_sellability_status", + "columns": [ + { + "expression": "sellability_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category_results_fetched_at": { + "name": "idx_category_results_fetched_at", + "columns": [ + { + "expression": "fetched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "category_product_results_run_id_runs_id_fk": { + "name": "category_product_results_run_id_runs_id_fk", + "tableFrom": "category_product_results", + "tableTo": "runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "category_product_results_asin_unique": { + "name": "category_product_results_asin_unique", + "nullsNotDistinct": false, + "columns": [ + "asin" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.runs": { + "name": "runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "run_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "input_file": { + "name": "input_file", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_file": { + "name": "output_file", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_products": { + "name": "total_products", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fba_count": { + "name": "fba_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fbm_count": { + "name": "fbm_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "skip_count": { + "name": "skip_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category_id": { + "name": "category_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category_label": { + "name": "category_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "top_asins_checked": { + "name": "top_asins_checked", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "available_asins": { + "name": "available_asins", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_runs_started_at": { + "name": "idx_runs_started_at", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_runs_type": { + "name": "idx_runs_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_runs_status": { + "name": "idx_runs_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sellers": { + "name": "sellers", + "schema": "", + "columns": { + "seller_id": { + "name": "seller_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "seller_name": { + "name": "seller_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "rating_count": { + "name": "rating_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "storefront_asin_total": { + "name": "storefront_asin_total", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "persisted_inventory_sample_count": { + "name": "persisted_inventory_sample_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_updated_at": { + "name": "last_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "raw_seller_json": { + "name": "raw_seller_json", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stalker_asin_scans": { + "name": "stalker_asin_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_asin": { + "name": "source_asin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "offer_count": { + "name": "offer_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "candidate_seller_count": { + "name": "candidate_seller_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "matched_seller_count": { + "name": "matched_seller_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "raw_product_json": { + "name": "raw_product_json", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_stalker_scans_run_id": { + "name": "idx_stalker_scans_run_id", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stalker_scans_source_asin": { + "name": "idx_stalker_scans_source_asin", + "columns": [ + { + "expression": "source_asin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stalker_asin_scans_run_id_stalker_runs_id_fk": { + "name": "stalker_asin_scans_run_id_stalker_runs_id_fk", + "tableFrom": "stalker_asin_scans", + "tableTo": "stalker_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_stalker_scans_run_asin": { + "name": "uq_stalker_scans_run_asin", + "nullsNotDistinct": false, + "columns": [ + "run_id", + "source_asin" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stalker_asin_sellers": { + "name": "stalker_asin_sellers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seller_id": { + "name": "seller_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "offer_price": { + "name": "offer_price", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_fba": { + "name": "is_fba", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seller_rating": { + "name": "seller_rating", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "seller_rating_count": { + "name": "seller_rating_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "raw_offer_json": { + "name": "raw_offer_json", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "stalker_asin_sellers_scan_id_stalker_asin_scans_id_fk": { + "name": "stalker_asin_sellers_scan_id_stalker_asin_scans_id_fk", + "tableFrom": "stalker_asin_sellers", + "tableTo": "stalker_asin_scans", + "columnsFrom": [ + "scan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "stalker_asin_sellers_seller_id_sellers_seller_id_fk": { + "name": "stalker_asin_sellers_seller_id_sellers_seller_id_fk", + "tableFrom": "stalker_asin_sellers", + "tableTo": "sellers", + "columnsFrom": [ + "seller_id" + ], + "columnsTo": [ + "seller_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_stalker_asin_sellers_scan_seller": { + "name": "uq_stalker_asin_sellers_scan_seller", + "nullsNotDistinct": false, + "columns": [ + "scan_id", + "seller_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stalker_runs": { + "name": "stalker_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "input_file": { + "name": "input_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "requested_asins": { + "name": "requested_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_asins": { + "name": "skipped_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scanned_asins": { + "name": "scanned_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_asins_with_matches": { + "name": "source_asins_with_matches", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "candidate_sellers": { + "name": "candidate_sellers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_sellers": { + "name": "qualifying_sellers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "matched_sellers": { + "name": "matched_sellers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seller_metadata_requests": { + "name": "seller_metadata_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seller_storefront_requests": { + "name": "seller_storefront_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "inventory_sellability_checked_asins": { + "name": "inventory_sellability_checked_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "inventory_sellability_available_asins": { + "name": "inventory_sellability_available_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "inventory_sellability_excluded_asins": { + "name": "inventory_sellability_excluded_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "persisted_inventory_asins": { + "name": "persisted_inventory_asins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_stalker_runs_started_at": { + "name": "idx_stalker_runs_started_at", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stalker_seller_inventory": { + "name": "stalker_seller_inventory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seller_id": { + "name": "seller_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "asin": { + "name": "asin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "can_sell": { + "name": "can_sell", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "sellability_status": { + "name": "sellability_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sellability_reason": { + "name": "sellability_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_title": { + "name": "product_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category_tree": { + "name": "category_tree", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_price": { + "name": "current_price", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "avg_price_90d": { + "name": "avg_price_90d", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "sales_rank": { + "name": "sales_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "monthly_sold": { + "name": "monthly_sold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seller_count": { + "name": "seller_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "amazon_is_seller": { + "name": "amazon_is_seller", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "raw_product_json": { + "name": "raw_product_json", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "raw_inventory_json": { + "name": "raw_inventory_json", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_stalker_inventory_seller_id": { + "name": "idx_stalker_inventory_seller_id", + "columns": [ + { + "expression": "seller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stalker_inventory_asin": { + "name": "idx_stalker_inventory_asin", + "columns": [ + { + "expression": "asin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stalker_inventory_product_title": { + "name": "idx_stalker_inventory_product_title", + "columns": [ + { + "expression": "product_title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stalker_seller_inventory_run_id_stalker_runs_id_fk": { + "name": "stalker_seller_inventory_run_id_stalker_runs_id_fk", + "tableFrom": "stalker_seller_inventory", + "tableTo": "stalker_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "stalker_seller_inventory_seller_id_sellers_seller_id_fk": { + "name": "stalker_seller_inventory_seller_id_sellers_seller_id_fk", + "tableFrom": "stalker_seller_inventory", + "tableTo": "sellers", + "columnsFrom": [ + "seller_id" + ], + "columnsTo": [ + "seller_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_stalker_inventory_run_seller_asin": { + "name": "uq_stalker_inventory_run_seller_asin", + "nullsNotDistinct": false, + "columns": [ + "run_id", + "seller_id", + "asin" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.run_status": { + "name": "run_status", + "schema": "public", + "values": [ + "running", + "ok", + "empty", + "failed", + "completed" + ] + }, + "public.run_type": { + "name": "run_type", + "schema": "public", + "values": [ + "lead_analysis", + "category_analysis", + "supplier_upc", + "stalker" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..c1afd38 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1779683900467, + "tag": "0000_gorgeous_william_stryker", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index a433e51..e10d4bc 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,13 @@ "type": "module", "private": true, "scripts": { - "bestsellers": "bun run src/bestsellers-by-category.ts", - "monthly-sold": "bun run src/top-monthly-sold-by-category.ts", - "mid-range": "bun run src/mid-range-sellers-by-category.ts", - "stalker": "bun run src/stalker.ts", + "bestsellers": "bun run src/categories/bestsellers-by-category.ts", + "monthly-sold": "bun run src/categories/top-monthly-sold-by-category.ts", + "mid-range": "bun run src/categories/mid-range-sellers-by-category.ts", + "stalker": "bun run src/stalker/stalker.ts", "search-offers": "bun run src/asin-offer-search.ts", - "upc": "bun run src/upc-lookup.ts", - "upc-file": "bun run src/upc-file-analysis.ts", + "upc": "bun run src/supplier/upc-lookup.ts", + "upc-file": "bun run src/supplier/upc-file-analysis.ts", "start": "bun run src/index.ts", "start:web": "bun --hot src/server.ts", "build:web": "bun build src/web/index.html --outdir dist", diff --git a/src/analysis-pipeline.ts b/src/analysis-pipeline.ts index b78f5c9..fdb182a 100644 --- a/src/analysis-pipeline.ts +++ b/src/analysis-pipeline.ts @@ -1,7 +1,7 @@ -import { fetchKeepaDataBatch } from "./keepa.ts"; -import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./sp-api.ts"; -import { getCache, setCache } from "./cache.ts"; -import { analyzeProducts } from "./llm.ts"; +import { fetchKeepaDataBatch } from "./integrations/keepa.ts"; +import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./integrations/sp-api.ts"; +import { getCache, setCache } from "./integrations/cache.ts"; +import { analyzeProducts } from "./integrations/llm.ts"; import type { AnalysisResult, EnrichedProduct, diff --git a/src/asin-offer-search.ts b/src/asin-offer-search.ts index cea369b..fb1626a 100644 --- a/src/asin-offer-search.ts +++ b/src/asin-offer-search.ts @@ -1,4 +1,4 @@ -import { searchProductOffers, type SearxngOfferSearchResult } from "./searxng.ts"; +import { searchProductOffers, type SearxngOfferSearchResult } from "./integrations/searxng.ts"; type CliArgs = { query: string; diff --git a/src/bestsellers-by-category.test.ts b/src/categories/bestsellers-by-category.test.ts similarity index 96% rename from src/bestsellers-by-category.test.ts rename to src/categories/bestsellers-by-category.test.ts index 41ed507..f94ee13 100644 --- a/src/bestsellers-by-category.test.ts +++ b/src/categories/bestsellers-by-category.test.ts @@ -35,7 +35,7 @@ const makeMockDb = (): any => ({ transaction: async (fn: (tx: any) => Promise) => fn(makeMockDb()), }); -mock.module("./db/index.ts", () => ({ db: makeMockDb(), client: {} })); +mock.module("../db/index.ts", () => ({ db: makeMockDb(), client: {} })); const fetchSellabilityBatchMock = mock(async (asins: string[]) => { return new Map( @@ -69,12 +69,12 @@ const analyzeProductsMock = mock(async (products: any[]) => { })); }); -mock.module("./sp-api.ts", () => ({ +mock.module("../integrations/sp-api.ts", () => ({ fetchSellabilityBatch: fetchSellabilityBatchMock, fetchSpApiPricingAndFees: fetchSpApiPricingAndFeesMock, })); -mock.module("./llm.ts", () => ({ +mock.module("../integrations/llm.ts", () => ({ analyzeProducts: analyzeProductsMock, })); diff --git a/src/bestsellers-by-category.ts b/src/categories/bestsellers-by-category.ts similarity index 96% rename from src/bestsellers-by-category.ts rename to src/categories/bestsellers-by-category.ts index fbad857..78ef912 100644 --- a/src/bestsellers-by-category.ts +++ b/src/categories/bestsellers-by-category.ts @@ -1,11 +1,11 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import path from "node:path"; -import { db } from "./db/index.ts"; -import { runs, categoryProductResults } from "./db/schema.ts"; +import { db } from "../db/index.ts"; +import { runs, categoryProductResults } from "../db/schema.ts"; import { eq, sql } from "drizzle-orm"; -import { config } from "./config.ts"; -import { analyzeProducts } from "./llm.ts"; -import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./sp-api.ts"; +import { config } from "../config.ts"; +import { analyzeProducts } from "../integrations/llm.ts"; +import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "../integrations/sp-api.ts"; import type { AnalysisResult, EnrichedProduct, @@ -14,7 +14,7 @@ import type { ProductRecord, SellabilityInfo, SpApiData, -} from "./types.ts"; +} from "../types.ts"; type CategoryInfo = { id: number; diff --git a/src/mid-range-sellers-by-category.test.ts b/src/categories/mid-range-sellers-by-category.test.ts similarity index 98% rename from src/mid-range-sellers-by-category.test.ts rename to src/categories/mid-range-sellers-by-category.test.ts index 56181d4..71668dc 100644 --- a/src/mid-range-sellers-by-category.test.ts +++ b/src/categories/mid-range-sellers-by-category.test.ts @@ -35,7 +35,7 @@ const makeMockDb = (): any => ({ transaction: async (fn: (tx: any) => Promise) => fn(makeMockDb()), }); -mock.module("./db/index.ts", () => ({ db: makeMockDb(), client: {} })); +mock.module("../db/index.ts", () => ({ db: makeMockDb(), client: {} })); const fetchSellabilityBatchMock = mock(async (asins: string[]) => { return new Map( @@ -84,12 +84,12 @@ const analyzeProductsMock = mock(async (products: any[]) => { })); }); -mock.module("./sp-api.ts", () => ({ +mock.module("../integrations/sp-api.ts", () => ({ fetchSellabilityBatch: fetchSellabilityBatchMock, fetchSpApiPricingAndFees: fetchSpApiPricingAndFeesMock, })); -mock.module("./llm.ts", () => ({ +mock.module("../integrations/llm.ts", () => ({ analyzeProducts: analyzeProductsMock, })); diff --git a/src/mid-range-sellers-by-category.ts b/src/categories/mid-range-sellers-by-category.ts similarity index 96% rename from src/mid-range-sellers-by-category.ts rename to src/categories/mid-range-sellers-by-category.ts index 5df63b1..f1ed93e 100644 --- a/src/mid-range-sellers-by-category.ts +++ b/src/categories/mid-range-sellers-by-category.ts @@ -2,18 +2,18 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import path from "node:path"; import { createInterface } from "node:readline/promises"; import { stdin as input, stdout as output } from "node:process"; -import { db } from "./db/index.ts"; -import { runs, categoryProductResults } from "./db/schema.ts"; +import { db } from "../db/index.ts"; +import { runs, categoryProductResults } from "../db/schema.ts"; import { eq, sql } from "drizzle-orm"; -import { config } from "./config.ts"; +import { config } from "../config.ts"; import { connectCache, disconnectCache, getApiCache, setApiCache, -} from "./cache.ts"; -import { analyzeProducts } from "./llm.ts"; -import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./sp-api.ts"; +} from "../integrations/cache.ts"; +import { analyzeProducts } from "../integrations/llm.ts"; +import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "../integrations/sp-api.ts"; import type { AnalysisResult, EnrichedProduct, @@ -22,7 +22,7 @@ import type { ProductRecord, SellabilityInfo, SpApiData, -} from "./types.ts"; +} from "../types.ts"; type CategoryInfo = { id: number; diff --git a/src/top-monthly-sold-by-category.test.ts b/src/categories/top-monthly-sold-by-category.test.ts similarity index 97% rename from src/top-monthly-sold-by-category.test.ts rename to src/categories/top-monthly-sold-by-category.test.ts index b832b6c..4a324e9 100644 --- a/src/top-monthly-sold-by-category.test.ts +++ b/src/categories/top-monthly-sold-by-category.test.ts @@ -35,7 +35,7 @@ const makeMockDb = (): any => ({ transaction: async (fn: (tx: any) => Promise) => fn(makeMockDb()), }); -mock.module("./db/index.ts", () => ({ db: makeMockDb(), client: {} })); +mock.module("../db/index.ts", () => ({ db: makeMockDb(), client: {} })); const fetchSellabilityBatchMock = mock(async (asins: string[]) => { return new Map( @@ -82,12 +82,12 @@ const analyzeProductsMock = mock(async (products: any[]) => { })); }); -mock.module("./sp-api.ts", () => ({ +mock.module("../integrations/sp-api.ts", () => ({ fetchSellabilityBatch: fetchSellabilityBatchMock, fetchSpApiPricingAndFees: fetchSpApiPricingAndFeesMock, })); -mock.module("./llm.ts", () => ({ +mock.module("../integrations/llm.ts", () => ({ analyzeProducts: analyzeProductsMock, })); diff --git a/src/top-monthly-sold-by-category.ts b/src/categories/top-monthly-sold-by-category.ts similarity index 96% rename from src/top-monthly-sold-by-category.ts rename to src/categories/top-monthly-sold-by-category.ts index 4ce10c8..9ec8d10 100644 --- a/src/top-monthly-sold-by-category.ts +++ b/src/categories/top-monthly-sold-by-category.ts @@ -1,11 +1,11 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import path from "node:path"; -import { db } from "./db/index.ts"; -import { runs, categoryProductResults } from "./db/schema.ts"; +import { db } from "../db/index.ts"; +import { runs, categoryProductResults } from "../db/schema.ts"; import { eq, sql } from "drizzle-orm"; -import { config } from "./config.ts"; -import { analyzeProducts } from "./llm.ts"; -import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./sp-api.ts"; +import { config } from "../config.ts"; +import { analyzeProducts } from "../integrations/llm.ts"; +import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "../integrations/sp-api.ts"; import type { AnalysisResult, EnrichedProduct, @@ -14,7 +14,7 @@ import type { ProductRecord, SellabilityInfo, SpApiData, -} from "./types.ts"; +} from "../types.ts"; type CategoryInfo = { id: number; diff --git a/src/database.ts b/src/database.ts deleted file mode 100644 index 8b0b708..0000000 --- a/src/database.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Central re-export so existing `import { db } from "./database.ts"` keeps working. -export { db, type Db } from "./db/index.ts"; -export * as schema from "./db/schema.ts"; diff --git a/src/index.ts b/src/index.ts index 306fc0c..ee2efe0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { readProducts } from "./reader.ts"; -import { connectCache, disconnectCache } from "./cache.ts"; +import { connectCache, disconnectCache } from "./integrations/cache.ts"; import { printResults, writeResultsToDb, diff --git a/src/cache.ts b/src/integrations/cache.ts similarity index 91% rename from src/cache.ts rename to src/integrations/cache.ts index 9b2f908..cec53d1 100644 --- a/src/cache.ts +++ b/src/integrations/cache.ts @@ -1,6 +1,6 @@ import Redis from "ioredis"; -import { config } from "./config.ts"; -import type { EnrichedProduct, KeepaData, SpApiData } from "./types.ts"; +import { config } from "../config.ts"; +import type { EnrichedProduct, KeepaData, SpApiData } from "../types.ts"; let redis: Redis | null = null; let disabled = false; diff --git a/src/keepa.test.ts b/src/integrations/keepa.test.ts similarity index 100% rename from src/keepa.test.ts rename to src/integrations/keepa.test.ts diff --git a/src/keepa.ts b/src/integrations/keepa.ts similarity index 96% rename from src/keepa.ts rename to src/integrations/keepa.ts index 81d4632..21bc445 100644 --- a/src/keepa.ts +++ b/src/integrations/keepa.ts @@ -1,5 +1,5 @@ -import { config } from "./config.ts"; -import type { KeepaData, KeepaUpcLookupDetail } from "./types.ts"; +import { config } from "../config.ts"; +import type { KeepaData, KeepaUpcLookupDetail } from "../types.ts"; const KEEPA_BASE = "https://api.keepa.com"; const MAX_ASINS_PER_REQUEST = 100; diff --git a/src/llm.ts b/src/integrations/llm.ts similarity index 96% rename from src/llm.ts rename to src/integrations/llm.ts index e96c5ff..1908902 100644 --- a/src/llm.ts +++ b/src/integrations/llm.ts @@ -1,5 +1,5 @@ -import { config } from "./config.ts"; -import type { EnrichedProduct, LlmVerdict } from "./types.ts"; +import { config } from "../config.ts"; +import type { EnrichedProduct, LlmVerdict } from "../types.ts"; const SYSTEM_PROMPT_STRICT = `You are an expert Amazon product analyst specializing in FBA and FBM fulfillment strategy. diff --git a/src/searxng.test.ts b/src/integrations/searxng.test.ts similarity index 100% rename from src/searxng.test.ts rename to src/integrations/searxng.test.ts diff --git a/src/searxng.ts b/src/integrations/searxng.ts similarity index 100% rename from src/searxng.ts rename to src/integrations/searxng.ts diff --git a/src/sp-api.test.ts b/src/integrations/sp-api.test.ts similarity index 100% rename from src/sp-api.test.ts rename to src/integrations/sp-api.test.ts diff --git a/src/sp-api.ts b/src/integrations/sp-api.ts similarity index 97% rename from src/sp-api.ts rename to src/integrations/sp-api.ts index eb2b9a8..8230258 100644 --- a/src/sp-api.ts +++ b/src/integrations/sp-api.ts @@ -1,11 +1,11 @@ import { SellingPartner } from "amazon-sp-api"; -import { config } from "./config.ts"; +import { config } from "../config.ts"; import type { KeepaUpcLookupStatus, SpApiData, SellabilityInfo, UpcLookupDetail, -} from "./types.ts"; +} from "../types.ts"; type RegionCode = "na" | "eu" | "fe"; diff --git a/src/server.ts b/src/server.ts index 03fb47f..9916a46 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,10 +5,10 @@ import { fetchKeepaDataBatch, lookupKeepaUpcs, mapUpcsToAsins, -} from "./keepa.ts"; -import { runUpcFileAnalysis } from "./upc-file-analysis.ts"; -import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./sp-api.ts"; -import { analyzeProducts } from "./llm.ts"; +} from "./integrations/keepa.ts"; +import { runUpcFileAnalysis } from "./supplier/upc-file-analysis.ts"; +import { fetchSellabilityBatch, fetchSpApiPricingAndFees } from "./integrations/sp-api.ts"; +import { analyzeProducts } from "./integrations/llm.ts"; import type { EnrichedProduct, KeepaUpcLookupDetail, diff --git a/src/sp-test.ts b/src/sp-test.ts index 9d901f8..242402f 100644 --- a/src/sp-test.ts +++ b/src/sp-test.ts @@ -1,4 +1,4 @@ -import { testSpApiConnectivity, testSpApiSellability } from "./sp-api.ts"; +import { testSpApiConnectivity, testSpApiSellability } from "./integrations/sp-api.ts"; function parseArgs(): { asin?: string; sellabilityMode: boolean } { const args = process.argv.slice(2); diff --git a/src/stalker-analyze.ts b/src/stalker/stalker-analyze.ts similarity index 97% rename from src/stalker-analyze.ts rename to src/stalker/stalker-analyze.ts index d0664a5..18bfa5d 100644 --- a/src/stalker-analyze.ts +++ b/src/stalker/stalker-analyze.ts @@ -1,15 +1,15 @@ -import { db } from "./db/index.ts"; -import { categoryProductResults, runs } from "./db/schema.ts"; +import { db } from "../db/index.ts"; +import { categoryProductResults, runs } from "../db/schema.ts"; import { eq, sql } from "drizzle-orm"; -import { analyzeProducts } from "./llm.ts"; -import { fetchSpApiPricingAndFees } from "./sp-api.ts"; +import { analyzeProducts } from "../integrations/llm.ts"; +import { fetchSpApiPricingAndFees } from "../integrations/sp-api.ts"; import type { AnalysisResult, EnrichedProduct, KeepaData, ProductRecord, SellabilityInfo, -} from "./types.ts"; +} from "../types.ts"; const LLM_BATCH_SIZE = 5; const LLM_BATCH_DELAY_MS = 5_000; diff --git a/src/stalker-sellability.test.ts b/src/stalker/stalker-sellability.test.ts similarity index 91% rename from src/stalker-sellability.test.ts rename to src/stalker/stalker-sellability.test.ts index 322e3f8..b5e5b48 100644 --- a/src/stalker-sellability.test.ts +++ b/src/stalker/stalker-sellability.test.ts @@ -62,7 +62,7 @@ const makeMockDb = (): any => ({ transaction: async (fn: (tx: any) => Promise) => fn(makeMockTx()), }); -mock.module("./db/index.ts", () => ({ db: makeMockDb(), client: {} })); +mock.module("../db/index.ts", () => ({ db: makeMockDb(), client: {} })); const TEST_DIR = path.join(process.cwd(), "test_output", "stalker-sellability"); const originalFetch = globalThis.fetch; @@ -87,10 +87,6 @@ const fetchSellabilityBatchMock = mock(async (asins: string[]) => { ); }); -mock.module("./sp-api.ts", () => ({ - fetchSellabilityBatch: fetchSellabilityBatchMock, -})); - const modulePromise = import("./stalker.ts"); beforeEach(() => { @@ -194,22 +190,25 @@ test("sellability checks matched seller inventory, not the source ASIN", async ( return new Response("not found", { status: 404 }); }) as unknown as typeof globalThis.fetch; - const stats = await runStalker({ - input: inputPath, - maxAsins: null, - storefrontUpdateHours: 168, - offerLimit: 20, - sellerLimit: 30, - inventoryLimit: 200, - sellerCacheHours: 168, - includeStock: false, - dryRun: false, - resume: true, - maxSellerRequests: null, - sellability: true, - analyzeSellable: false, - useClaude: false, - }); + const stats = await runStalker( + { + input: inputPath, + maxAsins: null, + storefrontUpdateHours: 168, + offerLimit: 20, + sellerLimit: 30, + inventoryLimit: 200, + sellerCacheHours: 168, + includeStock: false, + dryRun: false, + resume: true, + maxSellerRequests: null, + sellability: true, + analyzeSellable: false, + useClaude: false, + }, + { fetchSellabilityBatch: fetchSellabilityBatchMock }, + ); expect(fetchSellabilityBatchMock.mock.calls.length).toBe(1); expect(fetchSellabilityBatchMock.mock.calls[0]?.[0]).toEqual([ diff --git a/src/stalker.test.ts b/src/stalker/stalker.test.ts similarity index 99% rename from src/stalker.test.ts rename to src/stalker/stalker.test.ts index d324441..86eaf67 100644 --- a/src/stalker.test.ts +++ b/src/stalker/stalker.test.ts @@ -69,7 +69,7 @@ const makeMockDb = (): any => ({ transaction: async (fn: (tx: any) => Promise) => fn(makeMockTx()), }); -mock.module("./db/index.ts", () => ({ db: makeMockDb(), client: {} })); +mock.module("../db/index.ts", () => ({ db: makeMockDb(), client: {} })); const TEST_DIR = path.join(process.cwd(), "test_output", "stalker"); const originalFetch = globalThis.fetch; diff --git a/src/stalker.ts b/src/stalker/stalker.ts similarity index 98% rename from src/stalker.ts rename to src/stalker/stalker.ts index 629b961..4770589 100644 --- a/src/stalker.ts +++ b/src/stalker/stalker.ts @@ -1,6 +1,6 @@ import * as XLSX from "xlsx"; import path from "node:path"; -import { db } from "./db/index.ts"; +import { db } from "../db/index.ts"; import { runs, stalkerRuns, @@ -8,10 +8,10 @@ import { sellers, stalkerAsinSellers, stalkerSellerInventory, -} from "./db/schema.ts"; +} from "../db/schema.ts"; import { eq, sql } from "drizzle-orm"; -import { fetchSellabilityBatch } from "./sp-api.ts"; -import type { SellabilityInfo } from "./types.ts"; +import { fetchSellabilityBatch } from "../integrations/sp-api.ts"; +import type { SellabilityInfo } from "../types.ts"; const KEEPA_BASE = "https://api.keepa.com"; const DOMAIN_US = "1"; @@ -310,7 +310,11 @@ export function extractLiveOfferSellerCandidates( return Array.from(bySeller.values()); } -export async function runStalker(args: StalkerArgs): Promise { +export type StalkerDeps = { + fetchSellabilityBatch?: (asins: string[]) => Promise>; +}; + +export async function runStalker(args: StalkerArgs, deps: StalkerDeps = {}): Promise { const apiKey = Bun.env.KEEPA_API_KEY; if (!apiKey) throw new Error("Missing required env var: KEEPA_API_KEY"); @@ -383,7 +387,7 @@ export async function runStalker(args: StalkerArgs): Promise { ); if (args.sellability && !args.dryRun) { - await enrichInventorySellability(result, stats); + await enrichInventorySellability(result, stats, deps.fetchSellabilityBatch ?? fetchSellabilityBatch); } applyInventoryPersistencePolicy(result, args.sellability && !args.dryRun); if (args.sellability && !args.dryRun) { @@ -545,6 +549,7 @@ function applyInventoryPersistencePolicy( async function enrichInventorySellability( result: StalkerAsinResult, stats: StalkerRunStats, + sellabilityFn: (asins: string[]) => Promise>, ): Promise { const sellers = result.matchedSellers.map(({ seller }) => seller); const items = sellers.flatMap((seller) => seller.storefrontItems); @@ -554,7 +559,7 @@ async function enrichInventorySellability( console.log( `Stalker inventory sellability: checking ${uniqueAsins.length} ASIN(s) from matched seller storefronts...`, ); - const sellabilityMap = await fetchSellabilityBatch(uniqueAsins); + const sellabilityMap = await sellabilityFn(uniqueAsins); stats.inventorySellabilityCheckedAsins += uniqueAsins.length; for (const asin of uniqueAsins) { diff --git a/src/supplier-export.test.ts b/src/supplier/supplier-export.test.ts similarity index 98% rename from src/supplier-export.test.ts rename to src/supplier/supplier-export.test.ts index c4d163f..e5041d3 100644 --- a/src/supplier-export.test.ts +++ b/src/supplier/supplier-export.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { rmSync } from "node:fs"; import ExcelJS from "exceljs"; import { writeSupplierWorkbook } from "./supplier-export.ts"; -import type { SupplierAnalysisResult } from "./types.ts"; +import type { SupplierAnalysisResult } from "../types.ts"; const OUTPUT_FILE = path.join("/private/tmp", "asin-check-supplier-export-test.xlsx"); diff --git a/src/supplier-export.ts b/src/supplier/supplier-export.ts similarity index 99% rename from src/supplier-export.ts rename to src/supplier/supplier-export.ts index 61be0d9..4bcb9d7 100644 --- a/src/supplier-export.ts +++ b/src/supplier/supplier-export.ts @@ -5,7 +5,7 @@ import type { KeepaUpcLookupStatus, SupplierAnalysisResult, SupplierVerdict, -} from "./types.ts"; +} from "../types.ts"; export type SupplierExportSummary = { processedRows: number; diff --git a/src/supplier-scoring.test.ts b/src/supplier/supplier-scoring.test.ts similarity index 96% rename from src/supplier-scoring.test.ts rename to src/supplier/supplier-scoring.test.ts index 297ae70..17a1c86 100644 --- a/src/supplier-scoring.test.ts +++ b/src/supplier/supplier-scoring.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; import { scoreSupplierProduct } from "./supplier-scoring.ts"; -import type { KeepaData, ProductRecord, SpApiData } from "./types.ts"; +import type { KeepaData, ProductRecord, SpApiData } from "../types.ts"; function record(overrides: Partial = {}): ProductRecord { return { diff --git a/src/supplier-scoring.ts b/src/supplier/supplier-scoring.ts similarity index 99% rename from src/supplier-scoring.ts rename to src/supplier/supplier-scoring.ts index 01275e3..47e397b 100644 --- a/src/supplier-scoring.ts +++ b/src/supplier/supplier-scoring.ts @@ -3,7 +3,7 @@ import type { ProductRecord, SpApiData, SupplierScore, -} from "./types.ts"; +} from "../types.ts"; function round2(value: number): number { return Math.round(value * 100) / 100; diff --git a/src/upc-file-analysis.ts b/src/supplier/upc-file-analysis.ts similarity index 98% rename from src/upc-file-analysis.ts rename to src/supplier/upc-file-analysis.ts index dd5e3fd..fb7929e 100644 --- a/src/upc-file-analysis.ts +++ b/src/supplier/upc-file-analysis.ts @@ -1,10 +1,10 @@ import path from "node:path"; -import { fetchKeepaDataBatch, lookupKeepaUpcs } from "./keepa.ts"; +import { fetchKeepaDataBatch, lookupKeepaUpcs } from "../integrations/keepa.ts"; import { fetchSellabilityBatch, fetchSpApiPricingAndFees, lookupSpApiUpcs, -} from "./sp-api.ts"; +} from "../integrations/sp-api.ts"; import { processUpcFileInBatches, type UpcInputRow, @@ -14,8 +14,8 @@ import { refreshRunCountsInDb, startRunInDb, type RunCounts, -} from "./writer.ts"; -import { connectCache, disconnectCache } from "./cache.ts"; +} from "../writer.ts"; +import { connectCache, disconnectCache } from "../integrations/cache.ts"; import { scoreSupplierProduct, resolveSupplierSalePrice } from "./supplier-scoring.ts"; import { writeSupplierWorkbook, @@ -28,7 +28,7 @@ import type { SupplierAnalysisResult, SupplierScore, UpcLookupDetail, -} from "./types.ts"; +} from "../types.ts"; const DEFAULT_INPUT_BATCH_SIZE = 200; const DEFAULT_UPC_LOOKUP_BATCH_SIZE = 100; diff --git a/src/upc-file-reader.ts b/src/supplier/upc-file-reader.ts similarity index 100% rename from src/upc-file-reader.ts rename to src/supplier/upc-file-reader.ts diff --git a/src/upc-lookup.ts b/src/supplier/upc-lookup.ts similarity index 97% rename from src/upc-lookup.ts rename to src/supplier/upc-lookup.ts index afeaa60..3346d86 100644 --- a/src/upc-lookup.ts +++ b/src/supplier/upc-lookup.ts @@ -1,4 +1,4 @@ -import { lookupKeepaUpcs, mapUpcsToAsins } from "./keepa.ts"; +import { lookupKeepaUpcs, mapUpcsToAsins } from "../integrations/keepa.ts"; function printUsage(): void { console.log("Usage:");