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", "日期 2026年07月09日 15:32", "收件人fancy、receiving、hui", "、luo", "主题 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", "日期 2026年07月09日 15:32", "收件人fancy、receiving、hui", "、luo、date、op10、cs1", "主题 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|请查收新增预报/); }); });