Initial release with production deploy config.

Add GitAtlas deploy assets, DATA_FILE support for isolated server data, and port documentation for sourcing.simosen.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-15 00:52:31 +08:00
commit e9939ef9dc
45 changed files with 14376 additions and 0 deletions
+303
View File
@@ -0,0 +1,303 @@
import assert from "node:assert/strict";
import { mkdir, rm } from "node:fs/promises";
import { test } from "node:test";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
buildDashboard,
buildLivePitch,
buildStyleGroups,
calculateProfit,
detectPlatform,
extractPrice,
inferJewelryTrack,
scoreCapture,
toCsv,
} from "../src/core.js";
import { JsonStore } from "../src/store.js";
test("detectPlatform recognizes target marketplaces", () => {
assert.equal(detectPlatform("https://www.taobao.com/list?q=耳环"), "taobao");
assert.equal(detectPlatform("https://detail.tmall.com/item.htm?id=1"), "tmall");
assert.equal(detectPlatform("https://www.xiaohongshu.com/search_result?keyword=藏式手链"), "xiaohongshu");
assert.equal(detectPlatform("https://detail.1688.com/offer/123.html"), "1688");
assert.equal(detectPlatform("https://example.com/item"), "other");
});
test("extractPrice parses Chinese ecommerce price strings", () => {
assert.equal(extractPrice("¥39.90 起"), 39.9);
assert.equal(extractPrice("¥ 12 - 18"), 12);
assert.equal(extractPrice("批发价 3.50 元"), 3.5);
assert.equal(extractPrice("暂无报价"), null);
});
test("inferJewelryTrack classifies jewelry categories and Tibetan style", () => {
assert.equal(inferJewelryTrack("藏式编绳手链 民族风 绿松石色"), "藏式/民族风");
assert.equal(inferJewelryTrack("法式珍珠耳环耳夹"), "耳饰");
assert.equal(inferJewelryTrack("复古发簪抓夹"), "发饰");
assert.equal(inferJewelryTrack("手机链包挂钥匙扣"), "包挂/手机链");
assert.equal(inferJewelryTrack("小众戒指开口戒"), "戒指");
});
test("scoreCapture gives actionable sourcing decisions", () => {
const strong = scoreCapture({
platform: "1688",
title: "藏式编绳手链 民族风 绿松石色 直播爆款",
price: 8,
image: "https://img.example/a.jpg",
supplier: "义乌源头饰品厂",
metrics: { sales: "1000+人付款", comments: "求链接 问材质" },
});
assert.equal(strong.track, "藏式/民族风");
assert.equal(strong.decision, "拿样");
assert.ok(strong.score >= 75);
assert.ok(strong.reasons.some((reason) => reason.includes("直播")));
const risky = scoreCapture({
platform: "taobao",
title: "天然绿松石纯银开光转运手串",
price: 188,
image: "",
metrics: {},
});
assert.equal(risky.decision, "暂不做");
assert.ok(risky.risks.length >= 2);
});
test("calculateProfit estimates live selling margin for low-ticket jewelry", () => {
const profit = calculateProfit({
costPrice: 8,
targetPrice: 39,
shippingFee: 4,
packagingFee: 1,
platformFeeRate: 0.05,
promoFee: 3,
});
assert.equal(profit.grossProfit, 21.05);
assert.equal(profit.grossMarginRate, 0.54);
assert.equal(profit.recommendation, "可跑量");
});
test("buildLivePitch creates practical livestream talking points and warnings", () => {
const item = scoreCapture({
platform: "1688",
title: "藏式绿松石色编绳手链 民族风 直播叠戴",
price: 7.8,
supplier: "义乌源头饰品厂",
metrics: { visibleText: "现货 混批" },
});
const pitch = buildLivePitch(item);
assert.equal(pitch.audience, "喜欢民族风、旅行感、叠戴感的用户");
assert.ok(pitch.talkingPoints.some((point) => point.includes("藏式")));
assert.ok(pitch.supplierQuestions.some((question) => question.includes("材质")));
});
test("buildStyleGroups groups similar captured products across platforms", () => {
const items = [
scoreCapture({
platform: "taobao",
title: "藏式绿松石编绳手链 民族风",
url: "https://item.taobao.com/item.htm?id=style-a",
price: 39,
}),
scoreCapture({
platform: "1688",
title: "民族风绿松石色编织手链 藏式",
url: "https://detail.1688.com/offer/style-b.html",
price: 6.8,
supplier: "义乌源头厂",
}),
scoreCapture({
platform: "1688",
title: "珍珠耳钉小众耳饰",
url: "https://detail.1688.com/offer/style-c.html",
price: 3.5,
}),
];
const groups = buildStyleGroups(items);
assert.equal(groups.length, 2);
assert.equal(groups[0].items.length, 2);
assert.equal(groups[0].bestSupply.title, "民族风绿松石色编织手链 藏式");
assert.ok(groups[0].suggestedRetailPrice >= 29);
});
test("buildDashboard summarizes sourcing work and next actions", () => {
const items = [
scoreCapture({ platform: "1688", title: "藏式手机链", price: 4.2, status: "ordered_sample" }),
scoreCapture({ platform: "1688", title: "民族风手链", price: 8.8, status: "asking_supplier" }),
scoreCapture({ platform: "taobao", title: "珍珠耳环", price: 59, status: "new" }),
];
const dashboard = buildDashboard(items);
assert.equal(dashboard.totals.items, 3);
assert.equal(dashboard.totals.samplePipeline, 2);
assert.equal(dashboard.priceBands.under10, 2);
assert.ok(dashboard.nextActions.length > 0);
});
test("JsonStore inserts, updates, lists, deletes, and exports captures", async () => {
const dir = join(tmpdir(), `sourcing-store-${Date.now()}`);
await mkdir(dir, { recursive: true });
const store = new JsonStore(join(dir, "items.json"));
const inserted = await store.insertMany([
{
platform: "1688",
title: "民族风手机链",
url: "https://detail.1688.com/offer/1.html",
image: "https://img.example/1.jpg",
price: 4.2,
},
]);
assert.equal(inserted.length, 1);
assert.equal(inserted[0].decision, "拿样");
const updated = await store.update(inserted[0].id, { status: "ordered_sample" });
assert.equal(updated.status, "ordered_sample");
const listed = await store.list({ platform: "1688" });
assert.equal(listed.length, 1);
const csv = toCsv(listed);
assert.match(csv, /民族风手机链/);
assert.match(csv, /ordered_sample/);
const [header, firstRow] = csv.trim().split("\n");
const targetPriceIndex = header.split(",").indexOf("targetPrice");
assert.notEqual(firstRow.split(",")[targetPriceIndex], "");
await store.delete(inserted[0].id);
assert.equal((await store.list()).length, 0);
await rm(dir, { recursive: true, force: true });
});
test("JsonStore deletes multiple selected captures at once", async () => {
const dir = join(tmpdir(), `sourcing-store-bulk-${Date.now()}`);
await mkdir(dir, { recursive: true });
const store = new JsonStore(join(dir, "items.json"));
const inserted = await store.insertMany([
{
platform: "1688",
title: "误采集款 A",
url: "https://detail.1688.com/offer/bulk-a.html",
price: 5,
},
{
platform: "1688",
title: "误采集款 B",
url: "https://detail.1688.com/offer/bulk-b.html",
price: 6,
},
{
platform: "1688",
title: "保留款 C",
url: "https://detail.1688.com/offer/bulk-c.html",
price: 7,
},
]);
const result = await store.deleteMany([inserted[0].id, inserted[1].id, "not-found"]);
assert.equal(result.deleted, 2);
assert.deepEqual(result.missing, ["not-found"]);
assert.deepEqual(
(await store.list()).map((item) => item.title),
["保留款 C"],
);
await rm(dir, { recursive: true, force: true });
});
test("JsonStore bulk-updates selected captures with status and tags", async () => {
const dir = join(tmpdir(), `sourcing-store-bulk-update-${Date.now()}`);
await mkdir(dir, { recursive: true });
const store = new JsonStore(join(dir, "items.json"));
const inserted = await store.insertMany([
{
platform: "1688",
title: "待拿样藏式手链",
url: "https://detail.1688.com/offer/bulk-update-a.html",
price: 7,
},
{
platform: "1688",
title: "待拿样手机链",
url: "https://detail.1688.com/offer/bulk-update-b.html",
price: 4,
},
]);
const result = await store.bulkUpdate({
ids: inserted.map((item) => item.id),
patch: { status: "asking_supplier" },
addTags: ["低价跑量", "直播待测"],
});
assert.equal(result.updated, 2);
const listed = await store.list({ status: "asking_supplier" });
assert.equal(listed.length, 2);
assert.deepEqual(listed[0].tags, ["低价跑量", "直播待测"]);
await rm(dir, { recursive: true, force: true });
});
test("JsonStore filters by live sourcing decision dimensions", async () => {
const dir = join(tmpdir(), `sourcing-store-live-filters-${Date.now()}`);
await mkdir(dir, { recursive: true });
const store = new JsonStore(join(dir, "items.json"));
await store.insertMany([
{
platform: "1688",
title: "藏式编绳手机链 低价跑量",
url: "https://detail.1688.com/offer/filter-a.html",
price: 4.8,
image: "https://img.example/a.jpg",
supplier: "义乌源头饰品厂",
},
{
platform: "taobao",
title: "天然绿松石纯银开光转运手串",
url: "https://item.taobao.com/item.htm?id=filter-b",
price: 188,
},
{
platform: "taobao",
title: "珍珠耳环小众耳饰",
url: "https://item.taobao.com/item.htm?id=filter-c",
price: 18,
tags: ["直播待测"],
},
]);
assert.deepEqual(
(await store.list({ priceBand: "under10", source: "only1688" })).map((item) => item.title),
["藏式编绳手机链 低价跑量"],
);
assert.deepEqual(
(await store.list({ risk: "high" })).map((item) => item.title),
["天然绿松石纯银开光转运手串"],
);
assert.deepEqual(
(await store.list({ tag: "低价" })).map((item) => item.title),
["藏式编绳手机链 低价跑量"],
);
assert.deepEqual(
(await store.list({ source: "non1688", priceBand: "10to30" })).map((item) => item.title),
["珍珠耳环小众耳饰"],
);
await rm(dir, { recursive: true, force: true });
});