ผู้ช่วย 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(ตัวแรกที่ตรงชนะ):- ขนาน / parallel / เส้น / 2 หน่วย / สองหน่วย (:95) -- ถ้ามีคำว่าค้าง/สั่งซื้อ/สั่งขาย/เคลื่อนไหว ได้รายงานเอกสารหน่วยนับขนาน มิฉะนั้นได้ยอดคงเหลือหน่วยนับขนาน (
ic_inventory.unit_type IN (2,3)) - กำไร / ขายดี / ยอดขาย / sales / margin (:135)
- สั่งซื้อ / ค้างรับ / po / จัดซื้อ (:156)
- ลูกหนี้ / ar / ลูกค้า (:177)
- เจ้าหนี้ / ap / ซัพพลายเออร์ (:193)
- บัญชี / เดบิต / เครดิต / งบทดลอง / gl / ค่าใช้จ่าย (:209)
- เช็ค / ธนาคาร / เงินฝาก / bank / cheque (:225)
- ไม่ตรงอะไร (
default:240) -- สรุปยอดคงเหลือและมูลค่าสินค้าคงคลัง
- ขนาน / parallel / เส้น / 2 หน่วย / สองหน่วย (:95) -- ถ้ามีคำว่าค้าง/สั่งซื้อ/สั่งขาย/เคลื่อนไหว ได้รายงานเอกสารหน่วยนับขนาน มิฉะนั้นได้ยอดคงเหลือหน่วยนับขนาน (
- ทุก 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_transflag 44 (ขาย) กับ 12 (ซื้อ) ที่last_status = 0,total_amount > 0(100 แถวล่าสุด) -- ถ้า query พังจะข้ามเงียบ ๆ (if err == nil) - ถ้าไม่ส่ง
statementLinesระบบสร้างรายการทดสอบจากผู้สมัคร 6 รายการแรก (ตัวที่ 2 หักภาษี 3% ตัวที่ 3 หักค่าธรรมเนียม 15 บาท) เพื่อให้กดครั้งเดียวได้ผล (คอมเมนต์ :556-557) จึงไม่ควรใช้ผลของโหมดนี้เป็นข้อมูลจริง - จับคู่ 3 รอบต่อรายการ (ผู้สมัครแต่ละรายใช้ได้ครั้งเดียว):
EXACTยอดเท่ากันทุกสตางค์ (ความมั่นใจ 100)WHT_DEDUCTEDยอดสุทธิ = ยอดเอกสาร - หัก ณ ที่จ่าย 1%, 2%, 3% หรือ 5% (ปัด HALF_UP 2 ตำแหน่ง) (ความมั่นใจ 95)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เมื่อผ่าน):- สินค้าหน่วยนับขนาน (
unit_type IN (2,3), 50 รายการแรก) -- WARNING ถ้าไม่มีหน่วยที่สองในic_opposite_unitหรือยอดคงเหลือหลักกับขนานมีเครื่องหมายไม่สอดคล้อง - ใบรายวันบัญชีที่ผลรวมเดบิต != เครดิต ใน
gl_journal_detail(20 ใบแรก) -- CRITICAL ต่อใบ - สินค้า
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 enginebackend/internal/application/process/ai_copilot.go:70-89-validateReadOnlySelectSQLbackend/internal/application/process/ai_copilot.go:91-257-compileThaiPromptToSQLbackend/internal/application/process/ai_copilot.go:259-312-AIReportSQLbackend/internal/application/process/ai_copilot.go:314-428-AIReportAnalyzebackend/internal/application/process/ai_copilot.go:430-733- bank reconcile (systemBankCandidate:484)backend/internal/application/process/ai_copilot.go:735-907-AIAuditCopilotbackend/internal/application/process/ai_copilot.go:909-1056-AISmartReorderbackend/internal/application/process/ai_copilot.go:1060-1168-AIAutoGLbackend/internal/application/process/report_template.go:499-forbiddenSQLKeywordsRebackend/internal/adapter/inbound/http/process/workflow_handler.go:124-164- caseai-*(ส่งUsernameแทนUserCode)frontend/account/process/SystemProcessWorkbenchScreen.tsx:305-355- ผู้เรียกai-bank-reconcile,ai-audit-copilot,ai-smart-reorder,ai-auto-gl