สมอง BC

คลังความรู้ / ระบบงานในชุด BC

#systems#fraudwarning#risk-scoring#deepseek#audit

กฎให้คะแนนและการตรวจด้วย DeepSeek ของ 3D Warning

โน้ตนี้อธิบายว่า 3D Warning ตรวจจับความผิดปกติและทุจริต ภาพรวม (SYS-15 FraudWarning) ตัดสินความเสี่ยงอย่างไร: ด่านแรกเป็นกฎคำสำคัญฝั่งเซิร์ฟเวอร์ที่ไม่เสียโทเคน (evaluateActivityRisk) ด่านที่สองเป็นการส่งข้อความย่อให้ DeepSeek (runDeepSeekFraudInspection) ทุกอย่างอยู่ใน frontend/workmonitor/mongoAuditStore.ts และชนิดข้อมูลอยู่ใน fraudWarningData.ts

1. คะแนนจากการกระทำและช่องที่แก้

คะแนนเริ่มที่ 0 บวกตามกลุ่มที่เข้าเงื่อนไข (แต่ละกลุ่มนับครั้งเดียว) แล้วบีบให้อยู่ระหว่าง 5 ถึง 99

กลุ่ม เงื่อนไข คะแนน หมวด (fraudCategoryCode)
แก้แล้วปิดหนี การกระทำ ABANDON_WITHOUT_SAVE หรือผลลัพธ์ ABANDONED_UNSAVED +65 GHOST_EDIT_ABANDON
ยกเลิกหลังแก้ CANCEL_EDIT หรือ CANCELLED_AFTER_EDIT และมีช่องที่แก้อย่างน้อย 1 +48 PROBING_CANCEL_EDIT
ลบเอกสาร DELETE_RECORD หรือ DELETED +72 SUSPICIOUS_DELETION_VOID
เปิดส่องข้อมูล CLOSE_AFTER_PEEKING, PEEKED_NO_SAVE หรือเลือกดูตั้งแต่ 4 รายการ +52 DATA_HARVESTING_PEEK
ออกนอกระบบ TAB_OUTSIDE_SYSTEM +22 ไม่เปลี่ยนหมวด
บัญชีธนาคาร/ภาษี ชื่อช่องหรือค่ามีคำเกี่ยวกับเลขบัญชี ธนาคาร พร้อมเพย์ ภาษี VAT หัก ณ ที่จ่าย +32 VENDOR_BANK_SWAP เมื่อเป็นแผนกจัดซื้อ
ราคา/ส่วนลด/ยอดเงิน คำว่า ราคา ส่วนลด ยอดเงิน มูลค่า ต้นทุน และคำอังกฤษ price/discount/amount/cost +26 POS_PRICE_DISCOUNT_OVERRIDE เมื่อเป็นแผนก POS
สต็อก/จำนวน คำว่า จำนวน คงเหลือ สต็อก ปรับปรุง คลัง ของเสีย ชำรุด และ qty/balance +25 WMS_SHRINKAGE_COVERUP เมื่อเป็นแผนกคลัง
วงเงินเครดิต คำว่า เครดิต วงเงิน credit +28 AR_CREDIT_LIMIT_BYPASS
เงินเดือน/สิทธิ์ คำว่า เงินเดือน ค่าแรง โอที สิทธิ์ ระดับ และ salary/permission/level/password +35 HR_PRIVILEGE_SALARY_TAMPER
น้ำมัน/ไมล์/ซ่อม คำว่า น้ำมัน ไมล์ ซ่อม อะไหล่ และ fuel/mileage +26 TMS_FUEL_MAINTENANCE_SKIM
  • หมวดและหัวข้อไทยของกลุ่มช่องจะถูกตั้งเมื่อยังไม่มีหัวข้อจากกลุ่มการกระทำ (ยกเว้น VENDOR_BANK_SWAP ที่ตั้งทับเมื่อเป็นแผนกจัดซื้อ) ถ้าไม่เข้ากลุ่มใดเลยหัวข้อจะเป็นข้อความทั่วไป "พบพฤติกรรมผิดปกติใน <ชื่อหน้าจอ>"
  • ตัวจับคำทำงานกับ "ชื่อช่อง + ชื่อช่องที่แก้ + ค่าเดิม + ค่าใหม่" ที่แปลงเป็นตัวพิมพ์เล็กแล้ว ไม่รวมชื่อหน้าจอ ค่าที่ผู้ใช้พิมพ์จึงทำให้ติดธงได้ เช่น ชื่อสินค้าที่มีคำว่า "คลัง"
  • ระดับความรุนแรง: 80 ขึ้นไป CRITICAL, 60 HIGH, 40 MEDIUM, 20 LOW, ต่ำกว่านั้น SAFE
  • ตัวอย่างจากสูตร: ปิดหนีโดยไม่บันทึก 65 (HIGH); ลบเอกสาร 72 (HIGH); ยกเลิกหลังแก้ 48 (MEDIUM); บันทึกที่แก้เฉพาะช่องราคา 26 (LOW); บันทึกที่แก้ช่องธนาคารร่วมกับราคา 58; ปิดหนีที่มีช่องส่วนลดด้วย 91 (CRITICAL)

เมื่อไรถึงเกิดแจ้งเตือน

needsDeepSeekScan เป็นจริงเมื่อคะแนน 45 ขึ้นไป หรือเป็น ABANDON_WITHOUT_SAVE/DELETE_RECORD/CANCEL_EDIT ที่มีช่องแก้ หรือ SAVE_DOCUMENT ที่คะแนน 35 ขึ้นไป แต่ตอนสร้างแจ้งเตือนโค้ดเช็คทั้ง needsDeepSeekScan และคะแนน 45 ขึ้นไป ดังนั้นในทางปฏิบัติ "แจ้งเตือนเกิดเมื่อคะแนน 45 ขึ้นไปเท่านั้น" เงื่อนไขอื่นไม่ได้สร้างแจ้งเตือนเอง (บันทึกที่คะแนน 35-44 เก็บเป็นกิจกรรมอย่างเดียว) แจ้งเตือนที่ได้เป็นแบบ "ไฮบริด 0 โทเคน" ตามที่เล่าใน 3D Warning ตรวจจับความผิดปกติและทุจริต ภาพรวม (SYS-15 FraudWarning)

2. ตัวตรวจจับเหตุการณ์ฝั่งเบราว์เซอร์ มีผลต่อคะแนน

  • ตัวแทนส่ง selectedRecordsCount เท่ากับจำนวนรหัสแถว (ข้อความช่องแรกของแถว) ที่ไม่ซ้ำกันซึ่งเคยถูกคลิกบนหน้าจอนั้น และไม่ล้างค่าจนกว่าจะเปลี่ยนหน้าจอ ตามโค้ดกฎ "เลือกดู 4 รายการ" (+52) จึงติดกับ "ทุกการกระทำถัดไป" บนหน้าจอเดียวกัน รวมถึงการแก้ช่องและการบันทึก ทำให้หน้ารายการที่ผู้ใช้คลิกหลายแถวก่อนแก้ไขอาจเกิดแจ้งเตือนได้ง่าย (อ่านจากโค้ด ยังไม่ได้ทดลองรัน)
  • ตัวแทนแยก "ลบ" ด้วยปุ่มที่ข้อความเป็น ลบ, ลบรายการ, ยืนยันลบ, ยกเลิกบิล หรือ void แต่เงื่อนไข "ยกเลิก" (ประเภท CANCEL_EDIT/CLOSE_SCREEN) ถูกตรวจก่อน และคำว่า "ยกเลิก" ครอบคลุม "ยกเลิกบิล" ด้วย ตามลำดับเงื่อนไขในโค้ด ปุ่ม "ยกเลิกบิล" จึงไม่ถูกบันทึกเป็น DELETE_RECORD (ถ้าไม่มีช่องที่แก้ ผลคือ CLOSE_SCREEN ปกติ)
  • สถานะ "บันทึกแล้ว" ตั้งทันทีที่คลิกปุ่มที่ข้อความตรงกับ "บันทึก/Save/ชำระเงิน/ยืนยันสั่งซื้อ" หรือกด Ctrl+S ขณะอยู่โหมดแก้ไข
  • การทิ้งจอที่แก้ค้างโดยไม่บันทึก (ABANDON_WITHOUT_SAVE) ตรวจตอนสลับหน้าจอ/ปิดแท็บ/ออกจากหน้า ถ้ามีช่องที่แก้หรืออยู่โหมดแก้ไข และยังไม่เคยกดบันทึก

3. การจัดแผนก (7 แผนก)

resolveFraudDepartment ต่อ path, โมดูล, รหัสเมนู และชื่อหน้าจอเป็นข้อความเดียว (ตัวพิมพ์เล็ก) แล้วเลือกแผนกแรกที่มีคำตรง ตามลำดับนี้

ลำดับ รหัสแผนก ชื่อย่อบนจอ คำที่ใช้จับ
1 DEP-POS ขายหน้าร้าน POS /pos, pos, แคชเชียร์, ขายหน้าร้าน
2 DEP-PROCURE จัดซื้อ & เจ้าหนี้ /procure, จัดซื้อ, เจ้าหนี้, ap_, po
3 DEP-WMS คลังสินค้า & สต็อก /wms, คลัง, สต็อก, สินค้า, ic_
4 DEP-TMS ขนส่ง & ฟลีทรถ /tms, ขนส่ง, รถ
5 DEP-SALES ขายส่ง & ลูกหนี้ /crm, /market, ลูกค้า, ลูกหนี้, ar_, ใบเสนอราคา, ขายส่ง
6 DEP-HR-IT บุคคล & ผู้ดูแลระบบ พนักงาน, ผู้ใช้, สิทธิ์, เงินเดือน, erp_user, b102, config
7 DEP-FINANCE การเงิน & บัญชี ไม่เข้าข้างบนเลย (ค่าตั้งต้น)

ผลคือการประมาณจากข้อความ เช่น คำสั้น po ตรงกับคำอื่นที่มีตัวอักษรนี้ (เช่น report, export) หน้าจอที่ไม่เข้าลำดับใดจะตกเป็นแผนกการเงินทั้งหมด และชื่อผู้ใช้/แผนกจริงของบริษัทไม่ถูกนำมาใช้ แต่ละแผนกมีข้อความ "กฎและธงแดง" ที่แสดงบนแท็บ "ครบ 7 แผนก" ซึ่งเป็นคำอธิบายอย่างเดียว ไม่ได้ถูกนำไปคำนวณคะแนน

4. ด่านที่สอง: DeepSeek และการประหยัดโทเคน

ตัวกระตุ้น (triggeredBy): MANUAL_SCAN จากปุ่มบนจอ (สูงสุด 8 บันทึก, กรองตามแผนกที่เลือก), HIGH_RISK_TRIGGER จาก record_activity ที่ตัวแทนตั้ง autoRunDeepSeek และมีเหตุเสี่ยง (คะแนนเบื้องต้น 60 ขึ้นไป, ปิดหนี หรือลบ) สูงสุด 5 บันทึก, SIMULATION จากปุ่มจำลอง สูงสุด 4 บันทึก ค่าเริ่มต้นเมื่อไม่ระบุเลขเป็น 4

ชั้นที่ 1 (กรองฝั่งเซิร์ฟเวอร์ ไม่เสียโทเคน) อ่านบันทึกล่าสุด 80 แถว (กรองตามแผนกถ้าระบุ) เลือกเฉพาะที่คะแนนเบื้องต้น 35 ขึ้นไป หรือผลลัพธ์เป็นปิดหนี/ยกเลิกหลังแก้/ลบ/เปิดส่องแล้วไม่บันทึก หรือมีช่องที่แก้ แล้วตัดตามจำนวนสูงสุดข้างต้น ถ้าไม่มีบันทึกน่าสงสัยเลย โค้ดจะหยิบ 3 บันทึกล่าสุดของแผนกนั้นไปส่งแทน (จึงไม่ได้คัดทิ้งทั้งหมด) ตัวนับ "ตัดออก n จาก m บันทึก" บนจอคือผลต่างนี้

ชั้นที่ 2 (ข้อความย่อ) buildCompactTokenSavingDigest แปลงบันทึกเป็นหนึ่งบรรทัดต่อหนึ่งบันทึกคั่นด้วย |: รหัสบันทึก, รหัส(ชื่อ)พนักงาน, รหัสแผนก, [รหัสเมนู] พร้อมชื่อหน้าจอตัดที่ 28 ตัวอักษร, ประเภทการกระทำ, rec: รหัสเอกสาร, diff: ช่องที่แก้ไม่เกิน 3 ช่อง (ตัดรวมที่ 90 ตัวอักษร), out: ผลลัพธ์, และจำนวนวินาที ประมาณโทเคนด้วยอัตราส่วนตัวอักษร (ข้อความดิบหาร 2.6 ขั้นต่ำ 120; ข้อความย่อหาร 3.2 ขั้นต่ำ 30) ไม่ใช่ตัวนับโทเคนจริง

ชั้นที่ 3 (เรียกโมเดล) เมื่อมี env DEEPSEEK_API_KEY (ถ้าไม่มี โค้ดลองอ่านบรรทัดที่ขึ้นต้นด้วยชื่อตัวแปรนี้จากไฟล์ .env.local หรือ .env.deepseek ใน cwd หรือโฟลเดอร์แม่) จะ POST ไป https://api.deepseek.com/chat/completions ด้วยโมเดล deepseek-chat ส่งคีย์ในส่วนหัว Authorization อุณหภูมิ 0.1 max_tokens 800 ไม่สตรีม ข้อความระบบเป็นคำสั่งภาษาไทยคงที่ให้ตอบเป็นอาร์เรย์ JSON อย่างเดียว (โค้ดเรียกว่า prefix caching แต่เพียงรักษาข้อความระบบให้คงที่ ไม่ได้ตรวจว่าผู้ให้บริการใช้แคชจริง) ไม่ได้ตั้งเวลาหมดอายุให้คำขอนี้ คำตอบถูกอ่านโดยตัดข้อความตั้งแต่วงเล็บเหลี่ยมเปิดตัวแรกถึงวงเล็บเหลี่ยมปิดตัวสุดท้ายแล้วแปลงเป็น JSON

การแปลงคำตอบเป็นแจ้งเตือน: จับคู่กับบันทึกด้วยรหัส (ถ้าไม่ตรงใช้ลำดับ) ความรุนแรงรับเฉพาะ CRITICAL/HIGH/MEDIUM/LOW (ค่าอื่น ๆ ตกเป็น CRITICAL ถ้าคะแนนเบื้องต้น 80 ขึ้นไป ไม่เช่นนั้น HIGH) คะแนนบีบให้อยู่ที่ 40-99 แจ้งเตือนเดิมที่ตรงกัน (รหัสร่องรอยเดียวกัน หรือพนักงานคนเดียวกันกับรหัสเอกสารเดียวกัน ซึ่งรวมกรณีที่ทั้งคู่เป็น "-") จะถูกอัปเดตแทนการสร้างใหม่ โดยคงสถานะและหลักฐานเดิม แล้วเขียนเข้า MongoDB แบบ upsert ตั้ง fraudCategoryCode เป็น DEEPSEEK_AI_AUDIT และ detectedBy เป็น DEEPSEEK_AI

ถ้าเรียกไม่สำเร็จ (ไม่มีคีย์ คำตอบไม่ปกติ เครือข่ายล้ม หรืออ่านผลไม่ได้) โค้ดกลืนข้อผิดพลาดแล้วบันทึกรอบตรวจต่อ โดยชื่อโมเดลต่อท้าย (Hybrid Cache) ไม่มีแจ้งเตือนใหม่จาก AI แต่:

  • ตัวเลขโทเคนเป็นค่าประมาณคงที่ (ข้อความย่อที่ประมาณ + 75 สำหรับพรอมต์ และ 165 สำหรับคำตอบ)
  • warningsGeneratedCount ถูกตั้งเป็นจำนวนบันทึกที่ส่ง ทั้งที่ไม่ได้สร้างแจ้งเตือน
  • ค่าที่ route คืนเป็น "แจ้งเตือนอัปเดต" คือแจ้งเตือนล่าสุด 8 รายการในสำเนา RAM
  • แถบข้อความบนจอยังพูดว่า "ใช้เพียง n Tokens (ประหยัดไป n%) พบ/อัปเดตแจ้งเตือน n รายการ" ซึ่งอ่านแล้วคล้ายว่าเรียก AI สำเร็จ

ตัวเลข "ประหยัด Token" คำนวณจากประมาณการ: ฐานเทียบ = ค่ามากกว่าระหว่าง (โทเคนรวม × 6) กับ (ประมาณการข้อความดิบ + 950) เปอร์เซ็นต์ที่ประหยัดบีบให้อยู่ระหว่าง 85.0 ถึง 98.5 เสมอ จึงไม่ใช่ค่าที่วัดได้จริงจากผู้ให้บริการ (ค่าเฉลี่ยรวมบนจอคำนวณจากผลรวมของรอบที่เก็บไว้ ถ้ายังไม่มีรอบเลยเป็นค่าคงที่ ดู 3D Warning ตรวจจับความผิดปกติและทุจริต ภาพรวม (SYS-15 FraudWarning)) ส่วนแจ้งเตือนไฮบริดที่สร้างทันทีระบุว่าประหยัด 320 โทเคนเป็นค่าคงที่ ข้อความ "ลดขนาดพรอมต์กว่าร้อยละ 92" บนแท็บ "ประหยัด Token" เป็นคำอธิบายที่เขียนไว้ ไม่ได้คำนวณจากข้อมูลจริง

ข้อมูลที่ออกไปยังภายนอก ข้อความย่อมีรหัสและชื่อพนักงาน แผนก หน้าจอ รหัสเอกสาร และค่าก่อน/หลังของช่องที่แก้ (ตัดความยาวแล้ว) ถูกส่งไปที่บริการ DeepSeek เมื่อมีคีย์ และการสแกนซ้ำจะส่งบันทึกชุดเดิมซ้ำเพราะไม่มีการกันส่งซ้ำ (ตัวแปร seenDigestHashes ถูกประกาศไว้แต่ไม่ได้ใช้) การทำเครื่องหมาย analyzedByDeepSeek ทำบนข้อมูลในหน่วยความจำ ไม่ได้สั่งอัปเดตใน MongoDB

5. การจำลองเหตุทุจริต

simulate_fraud_scenario สร้างเหตุการณ์ 4 ขั้นต่อหนึ่งแผนก (เปิดหน้าจอ, เลือก 3 รายการ, แก้ไข 2 ช่อง, ขั้นสุดท้าย) ด้วยตัวละครและเอกสารสมมติที่กำหนดไว้ในโค้ดคนละชุดต่อแผนก ขั้นสุดท้ายของแผนก POS, ขายส่ง, คลัง และบุคคลคือปิดหนีโดยไม่บันทึก แผนกจัดซื้อคือยกเลิกหลังแก้ แผนกการเงินคือลบเอกสาร และแผนกขนส่งคือบันทึก เขียนผ่านเส้นทางเดียวกับของจริง (insertActivityLogsToMongo) แล้วเรียก DeepSeek แบบ SIMULATION ถ้าไม่ระบุแผนกใช้ POS บันทึกจำลองใช้ traceId ขึ้นต้น trace-sim-<รหัสแผนก>- เป็นเครื่องหมายเดียว

ตามสูตรในหัวข้อ 1 สถานการณ์แผนกขนส่งได้คะแนน 26 ต่อบันทึก (เฉพาะกลุ่มน้ำมัน/ซ่อม) จึงไม่ก่อแจ้งเตือนจากกฎเอง ต้องพึ่ง DeepSeek ที่มีคีย์ และเมื่อไม่มีผลจาก AI ค่าที่คืนไปให้จอ (warningsCreated) จะเป็นแจ้งเตือนล่าสุดที่มีอยู่ซึ่งอาจไม่เกี่ยวกับการจำลองนั้น (อนุมานจากโค้ด ยังไม่ได้ทดลองรัน)

6. เปลี่ยนสถานะแจ้งเตือน

update_warning_status แก้สถานะทั้งในสำเนา RAM และใน MongoDB ($set ตามรหัสแจ้งเตือน) แล้วคืนภาพรวมใหม่ route ไม่ตรวจว่าค่าสถานะอยู่ในรายการที่อนุญาต และตอบ success แม้ MongoDB ไม่ตอบ (ฟิลด์ updated จะเป็นเท็จ แต่จอไม่ได้ใช้)

ที่มาในโค้ด