สมอง SML

คลังความรู้ / Process Engine

#process#workflow#ai#copilot#report#bank-reconcile#audit#reorder#auto-gl#rule-based

ผู้ช่วย AI แบบกฎคำนวณ 6 ตัว (AI Copilot ai-*)

ไฟล์ ai_copilot.go (1,168 บรรทัด) รวม "AI Engine" 6 ตัวที่เรียกผ่าน POST /process/workflow/ai-... จากการอ่านโค้ดทั้งไฟล์ ไม่พบการเรียกบริการ LLM หรือ HTTP ภายนอกเลย (ค้นคำ http, openai, anthropic, llm, gemini ไม่พบ) ทุกตัวเป็นตรรกะตามกฎ: จับคำสำคัญ, SQL คงที่ และคำนวณด้วย big.Rat ส่วนฟิลด์ผู้ใช้/ฐานข้อมูลมาจาก token (json:"-") ใช้ transaction แบบอ่านอย่างเดียว (pgx.ReadOnly) ทุกตัวที่แตะฐานข้อมูล

ทำงานอย่างไร

ภาพรวม 6 action (คอมเมนต์หัวไฟล์ ai_copilot.go:1-14)

action ฟังก์ชัน แตะ DB
ai-report-sql AIReportSQL :259 ใช่ (รันเพื่อนับแถว)
ai-report-analyze AIReportAnalyze :341 ไม่ (วิเคราะห์ข้อมูลที่ client ส่งมา)
ai-bank-reconcile AIBankReconcile :494 ใช่
ai-audit-copilot AIAuditCopilot :764 ใช่
ai-smart-reorder AISmartReorder :945 ใช่
ai-auto-gl AIAutoGL :1089 ใช่ (ค้นผังบัญชี)

สิทธิ์: ไม่มีรายการ workflow/ai-* ใน routeRights จึงตกเป็น Admin ตามค่าตั้งต้นของ RequiredRight (permission.go:130-133; อนุมานจากการอ่านโค้ด ไม่ได้ทดสอบ runtime) ดู การตรวจสิทธิ์เมนูของ route process (Process route rights) ผู้เรียกจากหน้าจอ: SystemProcessWorkbenchScreen.tsx (4 แท็บ: กระทบยอด, ตรวจก่อนตรวจสอบบัญชี, จุดสั่งซื้อ, ผังบัญชี) และ GlobalReportPrintModal.tsx (ai-report-sql)

1. ai-report-sql -- ข้อความไทย -> SELECT (AIReportSQL)

  • Input prompt (บังคับ), menuKey, dateFrom, dateTo (สองตัวหลังไม่ถูกใช้ในโค้ด)
  • compileThaiPromptToSQL (:91) ย่อ prompt เป็นตัวพิมพ์เล็กแล้วเลือก SQL สำเร็จรูปด้วย strings.Contains ตามลำดับ switch (ตัวแรกที่ตรงชนะ):
    1. ขนาน / parallel / เส้น / 2 หน่วย / สองหน่วย (:95) -- ถ้ามีคำว่าค้าง/สั่งซื้อ/สั่งขาย/เคลื่อนไหว ได้รายงานเอกสารหน่วยนับขนาน มิฉะนั้นได้ยอดคงเหลือหน่วยนับขนาน (ic_inventory.unit_type IN (2,3))
    2. กำไร / ขายดี / ยอดขาย / sales / margin (:135)
    3. สั่งซื้อ / ค้างรับ / po / จัดซื้อ (:156)
    4. ลูกหนี้ / ar / ลูกค้า (:177)
    5. เจ้าหนี้ / ap / ซัพพลายเออร์ (:193)
    6. บัญชี / เดบิต / เครดิต / งบทดลอง / gl / ค่าใช้จ่าย (:209)
    7. เช็ค / ธนาคาร / เงินฝาก / bank / cheque (:225)
    8. ไม่ตรงอะไร (default :240) -- สรุปยอดคงเหลือและมูลค่าสินค้าคงคลัง
  • ทุก SQL เป็นค่าคงที่ใน source (LIMIT 500 ตามที่เห็นในตัวอย่าง) ไม่ได้ประกอบจากข้อความผู้ใช้ จากนั้น validateReadOnlySelectSQL (:70) ตรวจซ้ำ: ไม่ว่าง, ไม่มี ; -- /*, ต้องขึ้นต้น select หรือ with, ไม่มีคำต้องห้ามตาม forbiddenSQLKeywordsRe (report_template.go:499, เช่น INSERT UPDATE DELETE DROP ALTER TRUNCATE GRANT ...)
  • รัน SQL ใน transaction อ่านอย่างเดียวเพื่อนับแถวและอ่านชื่อคอลัมน์เท่านั้น แล้วคืน {prompt, title, explanation, sql, suggestedGroupBy, previewRowCount, columns} (ไม่คืนข้อมูลแถว)
  • ข้อสังเกตจากโค้ด: คำสั้นอย่าง po, ar, ap, gl เป็น substring ของข้อความทั้งหมด จึงอาจตรงกับตัวอักษรในคำอื่นได้ (ยังไม่ได้ทดสอบ)

2. ai-report-analyze -- สรุปผู้บริหารจากแถวรายงาน (AIReportAnalyze)

  • Input menuKey, title, rows[], totals{}, summaryCards[] ทั้งหมดมาจาก client ไม่มีการอ่าน DB
  • ผลลัพธ์ {headline, highlights[], anomalies[], metrics[]} (การ์ดมี tone = emerald/amber/rose/brand)
  • คำนวณ: ยอดรวมแต่ละตัวใน totals ที่ไม่เป็นศูนย์เป็นการ์ด; ถ้ามีทั้งยอดที่ชื่อมีคำว่า เดบิต/debit และ เครดิต/credit เทียบผลต่างด้วย big.Rat (เท่ากัน = highlight "ดุลบัญชีถูกต้อง" ไม่เท่า = anomaly); สแกนแถวหาค่าติดลบและคอลัมน์ที่ชื่อมีคำว่า ขนาน / qty_2 / balance_qty_2 / unit_code_2 ที่มีค่า
  • ไม่มีแถว = คืน headline กับคำแนะนำให้ปรับเงื่อนไข

3. ai-bank-reconcile -- จับคู่รายการเดินบัญชี (AIBankReconcile)

  • Input statementLines[] {txDate, description, refNo, amount} (ฟิลด์ passBookCode, dateFrom, dateTo ประกาศไว้แต่ไม่ถูกใช้ในโค้ด)
  • ผู้สมัครจับคู่ (candidates) ของระบบ: cb_chq_list (เช็ครับ/จ่าย ยอด > 0, 100 แถวล่าสุดตาม chq_due_date) และ ic_trans flag 44 (ขาย) กับ 12 (ซื้อ) ที่ last_status = 0, total_amount > 0 (100 แถวล่าสุด) -- ถ้า query พังจะข้ามเงียบ ๆ (if err == nil)
  • ถ้าไม่ส่ง statementLines ระบบสร้างรายการทดสอบจากผู้สมัคร 6 รายการแรก (ตัวที่ 2 หักภาษี 3% ตัวที่ 3 หักค่าธรรมเนียม 15 บาท) เพื่อให้กดครั้งเดียวได้ผล (คอมเมนต์ :556-557) จึงไม่ควรใช้ผลของโหมดนี้เป็นข้อมูลจริง
  • จับคู่ 3 รอบต่อรายการ (ผู้สมัครแต่ละรายใช้ได้ครั้งเดียว):
    1. EXACT ยอดเท่ากันทุกสตางค์ (ความมั่นใจ 100)
    2. WHT_DEDUCTED ยอดสุทธิ = ยอดเอกสาร - หัก ณ ที่จ่าย 1%, 2%, 3% หรือ 5% (ปัด HALF_UP 2 ตำแหน่ง) (ความมั่นใจ 95)
    3. BANK_FEE ยอดเอกสารมากกว่า statement ไม่เกิน 50 บาท (ความมั่นใจ 92)
    • ไม่เข้าสักรอบ = UNMATCHED
  • ผลลัพธ์ {summary{totalLines, matchedExact, matchedWht, matchedFee, unmatched, totalStatementAmount, totalSystemAmount, totalDiffAmount}, matches[]}; ตัวเลขทั้งหมดเป็นข้อความทศนิยม 2 ตำแหน่ง

4. ai-audit-copilot -- ตรวจก่อนตรวจสอบบัญชี (AIAuditCopilot)

  • Input dateFrom, dateTo ประกาศไว้ แต่ไม่ถูกใช้ในโค้ด
  • ตรวจ 3 ข้อ (แต่ละข้อได้ finding PASS เมื่อผ่าน):
    1. สินค้าหน่วยนับขนาน (unit_type IN (2,3), 50 รายการแรก) -- WARNING ถ้าไม่มีหน่วยที่สองใน ic_opposite_unit หรือยอดคงเหลือหลักกับขนานมีเครื่องหมายไม่สอดคล้อง
    2. ใบรายวันบัญชีที่ผลรวมเดบิต != เครดิต ใน gl_journal_detail (20 ใบแรก) -- CRITICAL ต่อใบ
    3. สินค้า balance_qty < 0 ใน ic_inventory -- WARNING หนึ่ง finding ต่อทั้งหมด
  • healthScore = 100 - 20 x critical - 5 x warning ต่ำสุด 0; คืน {scannedAt, healthScore, criticalCount, warningCount, passCount, findings[]} (category มี PARALLEL_UNIT, STOCK_COST, GL_BALANCE, TAX_VAT)
  • ข้อสังเกต: คอมเมนต์หัวไฟล์ระบุ "duplicate tax invoices" และประเภท TAX_VAT ถูกประกาศ แต่ในโค้ดไม่มีขั้นตรวจใบกำกับภาษีซ้ำ (มีเพียง 3 ข้อด้านบน)

5. ai-smart-reorder -- แนะนำจุดสั่งซื้อ 2 หน่วย (AISmartReorder)

  • Input onlyParallel (แสดงเฉพาะสินค้าหน่วยนับขนาน)
  • อ่าน ic_inventory สูงสุด 40 รายการ (เรียงสินค้าขนานก่อน แล้วตามรหัส) พร้อมยอดค้างรับ/ค้างส่งจาก ic_trans_detail: ใบสั่งซื้อ = trans_flag IN (6, 10), ใบสั่งขาย = trans_flag IN (32, 34, 36) เฉพาะ last_status = 0 ทั้งหัวและรายการ (SQL ไม่กรองว่าใบถูกปิดหรือรับครบแล้ว)
  • net = balance + PO - SO ต่อหน่วยหลักและหน่วยขนาน; ค่าความปลอดภัยตายตัว หน่วยหลัก 100 และหน่วยขนาน 20 (ในโค้ด safetyTarget1/2); ถ้า net ต่ำกว่าเป้า ยอดแนะนำสั่ง = เป้า - net; สถานะ "สต็อกเพียงพอ" หรือ "แนะนำสั่งซื้อเพิ่ม (Reorder)"
  • คืน {totalItemsAnalyzed, parallelItemsCount, needReorderCount, items[]} เรียงสินค้าขนานก่อน

6. ai-auto-gl -- เสนอรายการบัญชีคู่ (AIAutoGL)

  • Input description (ว่าง = "ซื้อสินค้าและวัสดุสำนักงาน"), amount (<= 0 = 1,070.00), includeVat
  • เลือกบัญชีเดบิตจากคำในคำอธิบาย (ไฟ/น้ำ/สาธารณูปโภค, ขนส่ง/เดินทาง, เงินเดือน/ค่าจ้าง, ไม่ตรง = "ซื้อ") ค้น gl_chart_of_account ด้วย name_1 ILIKE หรือ code ILIKE ถ้าไม่พบใช้รหัสตั้งต้นที่ฝังในโค้ด; ภาษีซื้อค้น "%ภาษีซื้อ%"; เครดิตค้น "%เงินฝาก%"
  • includeVat: ฐาน = RoundHalfUp(amount x 100 / 107, 2), ภาษี = amount - ฐาน (3 บรรทัด เดบิตฐาน + เดบิตภาษีซื้อ + เครดิตเต็ม); ไม่ใช้ VAT: 2 บรรทัด เดบิต=เครดิต=amount
  • คืน confidence ค่าคงที่ 96 และ reason -- ไม่ได้คำนวณความมั่นใจจากข้อมูลจริง (ตัวเลขฝังในโค้ด) เป็นข้อเสนอให้ผู้ใช้ตรวจ ไม่บันทึกอะไรลงฐานข้อมูล

ที่มาในโค้ด

  • backend/internal/application/process/ai_copilot.go:1-14 - คำอธิบาย 6 engine
  • backend/internal/application/process/ai_copilot.go:70-89 - validateReadOnlySelectSQL
  • backend/internal/application/process/ai_copilot.go:91-257 - compileThaiPromptToSQL
  • backend/internal/application/process/ai_copilot.go:259-312 - AIReportSQL
  • backend/internal/application/process/ai_copilot.go:314-428 - AIReportAnalyze
  • backend/internal/application/process/ai_copilot.go:430-733 - bank reconcile (systemBankCandidate :484)
  • backend/internal/application/process/ai_copilot.go:735-907 - AIAuditCopilot
  • backend/internal/application/process/ai_copilot.go:909-1056 - AISmartReorder
  • backend/internal/application/process/ai_copilot.go:1060-1168 - AIAutoGL
  • backend/internal/application/process/report_template.go:499 - forbiddenSQLKeywordsRe
  • backend/internal/adapter/inbound/http/process/workflow_handler.go:124-164 - case ai-* (ส่ง Username แทน UserCode)
  • frontend/account/process/SystemProcessWorkbenchScreen.tsx:305-355 - ผู้เรียก ai-bank-reconcile, ai-audit-copilot, ai-smart-reorder, ai-auto-gl

ดูเพิ่ม