|
|
import { describe, expect, it } from "vitest";
|
|
|
import {
|
|
|
detectUiKindsFromText,
|
|
|
extractForecastInstructionText,
|
|
|
extractMailInstructions,
|
|
|
splitBodySegments,
|
|
|
stripMobileMailChrome,
|
|
|
extractForwardChromeMeta,
|
|
|
} from "@/services/parse/split-instructions";
|
|
|
import { matchKeywordRules } from "@/services/parse/instruction-lexicon";
|
|
|
|
|
|
const MAIL4_SUBJECT =
|
|
|
"Fw: \u8f6c\u53d1\uff1a\u65b0\u589e\u9884\u62a5136\uff1a\u65b0\u8fb0\u6cfd+WHLC027G597465+WHSU8127240+\u6d1b\u6749\u77f6+40HQ+EDT2026.04-25 ETA2026.05-15\u8239\u540d\u822a\u6b21HMM EMERALD 013E+\u63d0\u62c6\u6d3e\u7ec4\u5408\u67dc-\u4e0d\u5e26\u6258\u67b6";
|
|
|
|
|
|
const MAIL4_BODY = [
|
|
|
"Dear,",
|
|
|
"\u539f\u7bb1\u53f7YT2604021091=FBA199R49LD6-MDW2=76\u4ef6\u64cd\u4f5c\u6307\u4ee4",
|
|
|
"\u6362\u6807\u540e\u5355\u53f7\uff1aFBA19HW52S0L-2G1MCB6X-HIA1=76\u4ef6",
|
|
|
"1\uff1a\u8986\u76d6\u8d34FBA\u6807\u7b7e\uff0c\u4e00\u7bb1\u8d34\u4e24\u5f20",
|
|
|
"3\uff1a\u8d34\u597d\u62cd\u7167\u56de\u4f20",
|
|
|
].join("\n");
|
|
|
|
|
|
const MAIL4_FILES = [
|
|
|
"YT2604021091=76\u4ef6\u6362\u6807\u8d70HIA1\u5361\u6d3e\u51fa\u5e93\u64cd\u4f5c\u6307\u4ee4.xlsx",
|
|
|
"FBA19HW52S0L-2G1MCB6X-HIA1.pdf",
|
|
|
];
|
|
|
|
|
|
const MULTI_DATE_BODY = [
|
|
|
"\u65b0\u589e\u8f6c\u4ed3\uff0c\u8bf7\u7559\u610f",
|
|
|
"\u67dc\u53f7\uff1aMATU2745683",
|
|
|
"------------------ \u539f\u59cb\u7535\u5b50\u90ae\u4ef6 ------------------",
|
|
|
"\u53d1\u4ef6\u4eba\uff1aa@b.com",
|
|
|
"\u4e3b\u9898\uff1a\u65b0\u589e\u9884\u62a5136\uff1a\u65b0\u8fb0\u6cfd+WHSU8127240",
|
|
|
"\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5",
|
|
|
].join("\n");
|
|
|
|
|
|
describe("splitBodySegments", () => {
|
|
|
it("splits on quote header", () => {
|
|
|
const segs = splitBodySegments(MULTI_DATE_BODY);
|
|
|
expect(segs.length).toBeGreaterThanOrEqual(2);
|
|
|
expect(segs[0]).toContain("\u65b0\u589e\u8f6c\u4ed3");
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("matchKeywordRules", () => {
|
|
|
it("maps hard words to ui kinds", () => {
|
|
|
expect(matchKeywordRules("\u8bf7\u8d34\u6807\u5e76\u62cd\u7167", "body").map((h) => h.uiKind)).toEqual(
|
|
|
expect.arrayContaining(["work_order"]),
|
|
|
);
|
|
|
expect(matchKeywordRules("\u65b0\u589e\u9884\u62a5", "subject").map((h) => h.uiKind)).toContain(
|
|
|
"forecast",
|
|
|
);
|
|
|
expect(matchKeywordRules("\u65b0\u589e\u8f6c\u4ed3", "body").map((h) => h.uiKind)).toContain(
|
|
|
"transfer",
|
|
|
);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("detectUiKindsFromText", () => {
|
|
|
it("subject forecast", () => {
|
|
|
expect(detectUiKindsFromText(MAIL4_SUBJECT, "", [])).toContain("forecast");
|
|
|
});
|
|
|
it("body work_order", () => {
|
|
|
expect(detectUiKindsFromText("", MAIL4_BODY, [])).toContain("work_order");
|
|
|
});
|
|
|
it("DO filename", () => {
|
|
|
expect(detectUiKindsFromText("", "", ["WHSU8127240-DO.pdf"])).toContain(
|
|
|
"do_upload",
|
|
|
);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("extractMailInstructions multi-date", () => {
|
|
|
it("mail4: forecast + work_order as separate units", () => {
|
|
|
const units = extractMailInstructions({
|
|
|
subject: MAIL4_SUBJECT,
|
|
|
body: MAIL4_BODY,
|
|
|
filenames: MAIL4_FILES,
|
|
|
});
|
|
|
const kinds = units.map((u) => u.uiKind);
|
|
|
expect(kinds).toContain("forecast");
|
|
|
expect(kinds).toContain("work_order");
|
|
|
const wo = units.find((u) => u.uiKind === "work_order");
|
|
|
expect(wo?.segmentText).toMatch(/操作指令|换标/);
|
|
|
});
|
|
|
|
|
|
it("keeps transfer(current) + forecast(history) as two units", () => {
|
|
|
const units = extractMailInstructions({
|
|
|
subject: "MATU2745683",
|
|
|
body: MULTI_DATE_BODY,
|
|
|
filenames: [],
|
|
|
});
|
|
|
expect(units.some((u) => u.uiKind === "transfer" && u.isCurrent)).toBe(true);
|
|
|
expect(units.some((u) => u.uiKind === "forecast")).toBe(true);
|
|
|
expect(units.length).toBeGreaterThanOrEqual(2);
|
|
|
});
|
|
|
|
|
|
it("does not duplicate subject forecast when body already has forecast", () => {
|
|
|
const units = extractMailInstructions({
|
|
|
subject: MAIL4_SUBJECT,
|
|
|
body: "\u8bf7\u67e5\u6536\u65b0\u589e\u9884\u62a5\uff1a\u65b0\u8fb0\u6cfd+WHSU8127240+\u63d0\u62c6\u6d3e",
|
|
|
filenames: [],
|
|
|
});
|
|
|
const forecasts = units.filter((u) => u.uiKind === "forecast");
|
|
|
expect(forecasts).toHaveLength(1);
|
|
|
expect(forecasts[0].source).toBe("body_segment");
|
|
|
});
|
|
|
|
|
|
it("mail4 current unit is work_order not forecast", () => {
|
|
|
const units = extractMailInstructions({
|
|
|
subject: MAIL4_SUBJECT,
|
|
|
body: MAIL4_BODY,
|
|
|
filenames: MAIL4_FILES,
|
|
|
});
|
|
|
const current = units.filter((u) => u.isCurrent);
|
|
|
expect(current.some((u) => u.uiKind === "work_order")).toBe(true);
|
|
|
expect(current.every((u) => u.uiKind !== "forecast")).toBe(true);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("extractForecastInstructionText", () => {
|
|
|
it("isolates forecast from label body", () => {
|
|
|
const t = extractForecastInstructionText({
|
|
|
subject: MAIL4_SUBJECT,
|
|
|
body: MAIL4_BODY,
|
|
|
});
|
|
|
expect(t).not.toMatch(/\u8986\u76d6\u8d34/);
|
|
|
});
|
|
|
|
|
|
it("extracts 时间/发件人/主题 from QQ forward chrome, ignores 收件人", () => {
|
|
|
const body = [
|
|
|
"Dear, 请处理。",
|
|
|
"",
|
|
|
"---- 转发的邮件 ----",
|
|
|
"发件人cs3<cs3@xinfenginc.com>",
|
|
|
"日期 2026年07月09日 15:32",
|
|
|
"收件人fancy<fancy@link-ever.com>、receiving<receiving@usasinogroup.com>、hui",
|
|
|
"<hui@usasinogroup.com>、luo<luo@usasinogroup.com>",
|
|
|
"主题 TIIU8073522-90022",
|
|
|
"",
|
|
|
"贴标后拍照回传",
|
|
|
].join("\n");
|
|
|
const meta = extractForwardChromeMeta(body);
|
|
|
expect(meta.from).toMatch(/cs3.*cs3@xinfenginc\.com/);
|
|
|
expect(meta.at).toMatch(/2026年07月09日\s*15:32/);
|
|
|
expect(meta.subject).toBe("TIIU8073522-90022");
|
|
|
// 收件人不得进 from/subject
|
|
|
expect(meta.from).not.toMatch(/fancy|receiving|hui@/);
|
|
|
expect(meta.subject).not.toMatch(/fancy|收件人/);
|
|
|
|
|
|
const units = extractMailInstructions({
|
|
|
subject: "Fw: TIIU8073522-90022",
|
|
|
body,
|
|
|
});
|
|
|
const cur = units.find((u) => u.isCurrent) || units[0];
|
|
|
expect(cur?.segmentSubject).toBe("TIIU8073522-90022");
|
|
|
expect(cur?.roundFrom).toMatch(/cs3/);
|
|
|
expect(cur?.roundAt).toMatch(/2026年07月09日/);
|
|
|
expect(cur?.segmentText || "").not.toMatch(/收件人fancy|转发的邮件/);
|
|
|
});
|
|
|
|
|
|
it("strips QQ desktop 转发的邮件 header block", () => {
|
|
|
const body = [
|
|
|
"Dear,",
|
|
|
"请贴标后拍照回传,谢谢。",
|
|
|
"",
|
|
|
"---- 转发的邮件 ----",
|
|
|
"发件人cs3<cs3@xinfenginc.com>",
|
|
|
"日期 2026年07月09日 15:32",
|
|
|
"收件人fancy<fancy@link-ever.com>、receiving<receiving@usasinogroup.com>、hui",
|
|
|
"<hui@usasinogroup.com>、luo<luo@usasinogroup.com>、date<date@usasi",
|
|
|
"nogroup.com>、op10<op10@xinfenginc.com>、cs1<cs1@xinfenginc.com>",
|
|
|
"主题 TIIU8073522-90022",
|
|
|
"",
|
|
|
"原业务说明可在此继续",
|
|
|
].join("\n");
|
|
|
const t = stripMobileMailChrome(body);
|
|
|
expect(t).not.toMatch(/转发的邮件/);
|
|
|
expect(t).not.toMatch(/发件人cs3|收件人fancy|usasinogroup|主题 TIIU|日期 2026/);
|
|
|
expect(t).toMatch(/请贴标后拍照回传/);
|
|
|
expect(t).toMatch(/原业务说明可在此继续/);
|
|
|
});
|
|
|
|
|
|
it("strips QQ/iPhone mobile forward chrome from instruction", () => {
|
|
|
const body = [
|
|
|
"Fw: 转发:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ",
|
|
|
"船名航次HMM EMERALD 013E+提拆派组合柜-不带托架",
|
|
|
"【发件人75440098<75440098@qq.com>",
|
|
|
"收件人3457189498<3457189498@qq.com>",
|
|
|
"大小 1.4M",
|
|
|
"时间 2026年7月14日 16:00",
|
|
|
"发自我的iPhone】",
|
|
|
"",
|
|
|
"请查收新增预报资料",
|
|
|
].join("\n");
|
|
|
const t = extractForecastInstructionText({
|
|
|
subject: "Fw: 转发:新增预报136:新辰泽+WHSU8127240",
|
|
|
body,
|
|
|
});
|
|
|
expect(t).not.toMatch(/发件人75440098|收件人3457189498|1\.4M|发自我的iPhone|2026年7月14日/);
|
|
|
expect(t).toMatch(/新增预报|船名航次|WHSU8127240|请查收新增预报/);
|
|
|
});
|
|
|
});
|