/** * Conservative subject-block strip: stop on body actions / Dear / 新增* */ import fs from "fs"; const STRIP_FN = ` function stripSubjectLines(text: string): string { const lines = text.split(/\\n/); const out: string[] = []; let inSubject = false; let cont = 0; for (const line of lines) { // "\\u4e3b\\u9898 Re:..." often has NO colon if (/(?:\u4e3b\u9898|\u4e3b\u65e8|Subject)\\s*[\uFF1A:]?/i.test(line)) { inSubject = true; cont = 0; continue; } if (inSubject) { const t = line.trim(); if (!t) { inSubject = false; continue; } // real body starts if ( BODY_ACTION_RE.test(line) || /Dear[,,]/.test(t) || /(?:\u65b0\u589e\u8f6c\u4ed3|\u65b0\u589e\u9884\u62a5|\u8bf7\u67e5\u6536|\u66f4\u65b0\u6d3e\u9001\u5355)/.test( t, ) || /^\\d+[\\.\\u3001\\uff1a:]/.test(t) ) { inSubject = false; out.push(line); continue; } if ( /^(?:\u5bc4\u4ef6\u4eba|\u53d1\u4ef6\u4eba|\u6536\u4ef6\u4eba|\u6284\u9001|\u65e5\u671f|\u53d1\u9001\u65f6\u95f4|From|To|Cc|Date|Sent|----)/i.test( t, ) || /^date@/i.test(t) || t === "date" ) { inSubject = false; out.push(line); continue; } // wrapped subject: ETA / container / voyage fragments only (max 4 lines) if ( cont < 4 && /Fw:|\u8f6c\u53d1|ETA|ETD|\u8239\u540d|\u822a\u6b21|\u67dc\u53f7|[A-Z]{4}\\d{7}|40HQ|20GP|\u4ef6\\+|\\+\\s*$/i.test( line, ) ) { cont += 1; continue; } inSubject = false; out.push(line); continue; } out.push(line); } return out.join("\\n"); } `.trimStart(); function replaceStripInLexicon() { const path = "src/services/parse/instruction-lexicon.ts"; let src = fs.readFileSync(path, "utf8"); const a = src.indexOf("function stripSubjectLines"); const b = src.indexOf("export function isForwardShellSegment"); if (a < 0 || b < 0) throw new Error("lexicon markers"); let start = src.lastIndexOf("/**", a); if (start < 0 || a - start > 200) start = a; src = src.slice(0, start) + "/** Drop subject block; stop early on real body actions */\n" + STRIP_FN + "\n" + src.slice(b); fs.writeFileSync(path, src); console.log("lexicon ok"); } function replaceBodyDetect() { const path = "src/services/parse/split-instructions.ts"; let src = fs.readFileSync(path, "utf8"); // Import BODY_ACTION already exists from lexicon const neu = `function bodyForKindDetect(segBody: string): string { const lines = segBody.split(/\\n/); const out: string[] = []; let inSubject = false; let cont = 0; for (const line of lines) { if (/(?:\u4e3b\u9898|\u4e3b\u65e8|Subject)\\s*[\uFF1A:]?/i.test(line)) { inSubject = true; cont = 0; continue; } if (inSubject) { const t = line.trim(); if (!t) { inSubject = false; continue; } if ( BODY_ACTION_RE.test(line) || /Dear[,,]/.test(t) || /(?:\u65b0\u589e\u8f6c\u4ed3|\u65b0\u589e\u9884\u62a5|\u8bf7\u67e5\u6536|\u66f4\u65b0\u6d3e\u9001\u5355)/.test( t, ) || /^\\d+[\\.\\u3001\\uff1a:]/.test(t) ) { inSubject = false; out.push(line); continue; } if ( /^(?:\u5bc4\u4ef6\u4eba|\u53d1\u4ef6\u4eba|\u6536\u4ef6\u4eba|\u6284\u9001|\u65e5\u671f|\u53d1\u9001\u65f6\u95f4|From|To|Cc|Date|Sent|----)/i.test( t, ) || /^date@/i.test(t) || t === "date" ) { inSubject = false; out.push(line); continue; } if ( cont < 4 && /Fw:|\u8f6c\u53d1|ETA|ETD|\u8239\u540d|\u822a\u6b21|\u67dc\u53f7|[A-Z]{4}\\d{7}|40HQ|20GP|\u4ef6\\+|\\+\\s*$/i.test( line, ) ) { cont += 1; continue; } inSubject = false; out.push(line); continue; } out.push(line); } return out.join("\\n"); } `; const a = src.indexOf("function bodyForKindDetect"); const b = src.indexOf("export function detectKindsForSegment"); if (a < 0 || b < 0) throw new Error("split markers"); src = src.slice(0, a) + neu + "\n" + src.slice(b); fs.writeFileSync(path, src); console.log("split ok"); } replaceStripInLexicon(); replaceBodyDetect();