มหาวิทยาลัยมหามกุฏราชวิทยาลัย · ระบบสาธิต (Demo)
ข้ามไปยังเนื้อหา

ข้อกำหนดความต้องการซอฟต์แวร์ (SRS)

งวดที่ 1 · ออกแบบ

ดาวน์โหลด .md

ข้อกำหนดความต้องการซอฟต์แวร์ (Software Requirements Specification: SRS)

โครงการ: ระบบแชตบอตปัญญาประดิษฐ์เฉพาะทางพระพุทธศาสนาและเว็บไซต์ (MBU Buddhist AI) ผู้ว่าจ้าง: มหาวิทยาลัยมหามกุฏราชวิทยาลัย (มมร.) อ้างอิง TOR: ข้อ 4.2.2 (SRS: FR/NFR, Workflow, Architecture, Data Dictionary, User Flow/Wireframe, OpenAPI 3.0) และข้อ 7.1.2 (เอกสารส่งมอบงวดที่ 1) ฉบับ: 1.0 (ระบบสาธิต) — วันที่ 3 ตุลาคม พ.ศ. 2569

เอกสารนี้อธิบายความต้องการของระบบตามที่ มีอยู่จริงในซอร์สโค้ด ของระบบสาธิต (Next.js 15 + Prisma/SQLite) ทุกข้อกำหนดระบุไฟล์หรือ endpoint ที่ implement ไว้เพื่อให้ตรวจสอบย้อนกลับได้ ส่วนที่เป็นสถาปัตยกรรมระยะ Production (Kubernetes, PostgreSQL, Elasticsearch) ระบุแยกไว้ในหัวข้อ 5 พร้อมแผนที่การย่อส่วน


1. บทนำ

1.1 วัตถุประสงค์ของเอกสาร

  1. กำหนดความต้องการเชิงหน้าที่ (Functional Requirements: FR) และไม่ใช่เชิงหน้าที่ (Non-Functional Requirements: NFR) โดยผูกรหัส FR-xxx / NFR-xxx กับข้อ TOR (clause id) ให้ตรวจสอบย้อนกลับได้
  2. อธิบายลำดับการทำงานหลัก (Workflow) ได้แก่ การถาม-ตอบผ่าน RAG pipeline 8 โหนด การนำเข้าเอกสาร และการประเมินคุณภาพ AI
  3. อธิบายสถาปัตยกรรมระบบ (ระบบสาธิตและการแมปไปยัง Production)
  4. ให้พจนานุกรมข้อมูล (Data Dictionary) ครบทุก model/field ที่สร้างจาก prisma/schema.prisma จริง
  5. สรุปกลุ่ม API ตามเอกสาร OpenAPI 3.0 ที่ระบบให้บริการที่ /api/openapi.json

รหัส FR ในเอกสารนี้ใช้อ้างอิงต่อใน แผนการทดสอบหลัก (/documents/master-test-plan) เพื่อทำ Traceability Matrix ระหว่าง SRS กับกรณีทดสอบ (TOR 4.7.1)

1.2 ขอบเขตของระบบ

ระบบประกอบด้วย 5 ส่วนตาม TOR ข้อ 4.4:

ส่วนขอบเขตตำแหน่งในระบบสาธิต
(1) Frontend Websiteหน้าแรก สมัคร/เข้าสู่ระบบ ถาม-ตอบ Dhamma Talk ประวัติ สิทธิเจ้าของข้อมูล ศูนย์เอกสาร ตาราง TORsrc/app/[locale]/*, src/components/*
(2) Backend Service & AI EngineREST/SSE API, RAG pipeline (LangGraph.js), Guardrail, Token Budget, Ingestionsrc/app/api/*, src/application/*, src/server/*, src/domain/*
(3) AI Evaluation & Benchmarkingชุดทดสอบ 7 ชุด 3 workflow บันทึกเวอร์ชัน เปรียบเทียบรอบsrc/application/evaluation/*, scripts/eval/run-acceptance.ts
(4) Admin Portalผู้ใช้/PDPA/Audit, ตั้งค่า AI, Prompt, Guardrail, ฐานองค์ความรู้, Evaluation, Opssrc/app/[locale]/admin/*, src/components/admin/*
(5) Dashboard & Reportการใช้งาน โทเคน ค่าใช้จ่าย Feedback SLA Defect/admin/dashboard, /admin/sla-report, scripts/reports/*

นอกขอบเขตของระบบสาธิต (ระบุตาม Build Plan ข้อ 14): การติดตั้งบนคลาวด์/Kubernetes ของมหาวิทยาลัย โดเมน dhammaverse.mbu.ac.th จริง Auto Scaling จริง WAF/Anti-DDoS ระดับคลาวด์ OAuth กับ Google/Facebook ด้วย credential จริง SMTP จริง Elasticsearch/bge-m3 จริง และ การทดสอบเจาะระบบ (Penetration Test) ซึ่งไม่ได้ดำเนินการในรอบนี้ตามคำสั่งของผู้ว่าจ้าง

1.3 ผู้มีส่วนได้ส่วนเสียและบทบาทผู้ใช้

บทบาท (User.role)คำอธิบายสิทธิ์หลัก
GENERAL_USERผู้ใช้ทั่วไป (นักศึกษา พระภิกษุ ประชาชน)ถาม-ตอบ ประวัติ สิทธิเจ้าของข้อมูล; Dhamma Talk เมื่อผู้ดูแลเปิดสิทธิ์ (dhammaTalkEnabled)
ADMINผู้ดูแลระบบของมหาวิทยาลัย / คณะกรรมการตรวจรับAdmin Portal ทั้งหมด ยกเว้นสำรองข้อมูล ทำลายข้อมูล และเปลี่ยน role
SUPER_ADMINผู้ดูแลระบบสูงสุดทุกสิทธิ์ รวมสำรองข้อมูล (/admin/backup) ทำลายข้อมูล (/admin/data-destruction) และเปลี่ยน role

บทบาททั้ง 3 นิยามใน src/domain/identity/rbac.ts (ROLES) และตรวจทุก endpoint ผ่าน withApi({ auth }) ใน src/server/api/with-api.ts

1.4 นิยามศัพท์

คำความหมาย
RAGRetrieval-Augmented Generation — ค้นข้อความอ้างอิงจากคลังก่อนให้ LLM สร้างคำตอบ
Chunkหน่วยข้อความที่ทำดัชนี (พระไตรปิฎก/อรรถกถา = 1 ข้อ ต่อ 1 chunk)
Citationการอ้างอิงในรูปแบบ {abbreviation} ({edition}) {volume}/{item}/{page} เช่น ขุ.ขุ. (ไทย) 25/5/3
Precedenceลำดับความสำคัญของประเภทเอกสาร (พระไตรปิฎก 1.0 > อรรถกถา 0.9 > ฎีกา 0.8 > ร่วมสมัย 0.7 > ธรรมะพ่อแม่ครูอาจารย์ 0.6)
RRFReciprocal Rank Fusion — รวมผลค้นหาหลายรายการด้วย Σ 1/(k + rank)
SSEServer-Sent Events — ส่งผลลัพธ์แบบ streaming จากเซิร์ฟเวอร์
TTFTTime To First Token — เวลาตั้งแต่รับคำถามจนได้โทเคนแรกของคำตอบ
Guardrailกฎป้องกันคำถามนอกขอบเขต/อ่อนไหว/prompt injection และตรวจคำตอบ
Unavailable Modeโหมดปิดบริการแชตชั่วคราว (สั่งโดยผู้ดูแล หรืออัตโนมัติเมื่อโทเคนใกล้เพดาน)

1.5 เอกสารอ้างอิง

  • docs/build-plan.md (แผนพัฒนาระบบสาธิต ครอบคลุม TOR 200 ข้อ) และตาราง TOR ที่หน้า /tor (src/data/tor-map.ts)
  • /documents/requirements-summary — สรุปความต้องการ (TOR 4.2.1)
  • /documents/ux-deliverables — User Flow/Wireframe/Prototype (TOR 4.3.1–4.3.3)
  • /documents/data-ingestion-plan — แผนนำเข้าข้อมูล (TOR 4.5.1)
  • /documents/master-test-plan — แผนการทดสอบหลักและ Traceability (TOR 4.7.1)
  • /api/openapi.json — ข้อกำหนด API (OpenAPI 3.0.3)

2. ภาพรวมระบบ

2.1 เทคโนโลยีที่ใช้จริง (จาก package.json)

ชั้นเทคโนโลยี
Web/BackendNext.js 15.5 (App Router, Route Handlers), React 19.1, TypeScript strict
UITailwind CSS 4, next-themes (dark/light), next-intl 4 (th/en), react-markdown + remark-gfm, recharts
ข้อมูลPrisma 6.19 + SQLite (prisma/dev.db; ชุดทดสอบใช้ฐานข้อมูลแยกต่อไฟล์)
AI Orchestration@langchain/langgraph 1.4 (StateGraph 8 โหนด) + @langchain/core
LLMTyphoon typhoon-v2.5-30b-a3b-instruct ผ่าน OpenAI-compatible API (ค่าเริ่มต้นใน src/domain/config/ai-config.ts) สลับเป็น OpenAI / Gemini / Claude / Mock ได้
Auth/Securityjose (JWT HS256), bcryptjs, CSRF double-submit, zod v4 validation
Parsersunpdf (PDF), mammoth (DOCX), fast-xml-parser (TEI-XML), gray-matter, papaparse (CSV), diff
ทดสอบvitest 5 + @vitest/coverage-v8, Testing Library (jsdom), Playwright 1.63

2.2 โครงสร้างชั้นซอฟต์แวร์ (Clean Architecture)

flowchart LR
  subgraph Presentation["src/app (หน้าเว็บ + Route Handlers)"]
    P1["[locale]/... pages"]
    P2["api/... route.ts (withApi)"]
  end
  subgraph Application["src/application (use cases)"]
    A1["chat/ask.ts"]
    A2["evaluation/runner.ts"]
    A3["ingestion/kb-admin.ts"]
    A4["privacy, identity, config, ops, report"]
  end
  subgraph Adapters["src/server (adapters)"]
    S1["ai/graph.ts (LangGraph)"]
    S2["llm/* (typhoon/openai-compatible, gemini, claude, mock)"]
    S3["retrieval/* (KnowledgeIndex, reranker)"]
    S4["ingestion/* (pipeline, parsers)"]
    S5["db.ts, repos, audit, notification, logger"]
  end
  subgraph Domain["src/domain (pure TypeScript)"]
    D1["ai: guardrail-policy, citation, token-budget-policy"]
    D2["retrieval: bm25, embedding, rrf, synonyms, heuristic-reranker, relevance-scoring"]
    D3["ingestion: chunking-policy, sanitize"]
    D4["identity, privacy, audit, evaluation, report, config"]
  end
  Presentation --> Application --> Adapters --> Domain
  Presentation --> Adapters

กติกา: src/domain/* ไม่ import framework ใด ๆ (ทดสอบหน่วยได้โดยตรง) ชั้นบนเรียกชั้นล่างเท่านั้น และ coverage gate ของ vitest วัดเฉพาะ src/domain/** + src/application/** (vitest.config.ts)


3. ความต้องการเชิงหน้าที่ (Functional Requirements)

ตารางต่อไปนี้ระบุรหัส FR, ข้อกำหนด, ข้อ TOR ที่รองรับ และตำแหน่งที่ implement ในระบบสาธิต

3.1 การยืนยันตัวตนและสิทธิ์ (Identity & Access)

รหัสข้อกำหนดTORImplement
FR-001ผู้ใช้สมัครสมาชิกด้วยอีเมล รหัสผ่าน ชื่อแสดง และติ๊กยินยอมนโยบาย เก็บเฉพาะข้อมูลที่จำเป็น บันทึก consentVersion/consentAtก.3.3.1, 14.2/register, POST /api/auth/register
FR-002เข้าสู่ระบบ/ออกจากระบบด้วยอีเมล+รหัสผ่าน ออก JWT (HS256 อายุ 24 ชม.) ใน httpOnly cookie mbu_session; บัญชีสถานะ suspended/deleted เข้าไม่ได้ก.3.3.1/login, POST /api/auth/login, POST /api/auth/logout, src/server/auth/session.ts
FR-003เข้าสู่ระบบด้วย Gmail/Facebook (OAuth 2.0 Authorization Code + state/PKCE) และ Mock Provider ภายในเมื่อยังไม่ตั้ง client idก.3.3.2src/server/identity/oauth/*, `/api/auth/oauth/[provider]/start
FR-004ป้องกัน brute force: ผิด 5 ครั้ง/15 นาที/บัญชี หรือ 20 ครั้ง/15 นาที/IP → ล็อก 15 นาที และ rate limit /api/auth/login 10 ครั้ง/นาที/IPก.2.1.4src/domain/identity/lockout-policy.ts, LoginAttempt, src/middleware.ts
FR-005ทุกคำขอ POST/PUT/PATCH/DELETE ต้องแนบ CSRF token (double-submit cookie + header x-csrf-token) มิฉะนั้นตอบ 403 CSRF_INVALIDก.2.1.5src/middleware.ts, GET /api/csrf
FR-006ควบคุมสิทธิ์แบบ RBAC 3 บทบาท ทุก endpoint ประกาศสิทธิ์ (public/user/รายการ role)ก.6.1.1, ก.7.1.3src/domain/identity/rbac.ts, withApi({ auth })
FR-007นโยบายรหัสผ่าน: ผู้ใช้ทั่วไป ≥8 ตัวมีตัวอักษรและตัวเลข; ผู้ดูแล ≥12 ตัวผสม 4 ชนิด; ส่วนผู้ดูแล (/admin*, /api/admin*) จำกัดเฉพาะ IP ใน ADMIN_IP_ALLOWLIST (นอกนั้นตอบ 403 และบันทึก audit)ก.6.1.4src/domain/identity/password-policy.ts, src/middleware.ts, /api/internal/security-event
FR-008ผู้ใช้ดู/แก้ชื่อแสดงของตนเองก.3.3.1GET/PATCH /api/me

3.2 ถาม-ตอบ (Q&A)

รหัสข้อกำหนดTORImplement
FR-010ผู้ใช้ส่งคำถามแล้วได้คำตอบแบบ streaming (SSE) จาก RAG pipeline พร้อมอ้างอิงที่ตรวจสอบย้อนกลับได้ ในบุคลิกบรรณารักษ์2.1, 2.4, ก.4.5.2/chat, POST /api/chat/qa, src/application/chat/ask.ts
FR-011คำตอบแสดง 2 ส่วน: (1) รายการอ้างอิง ≤10 รายการ แสดง 3 รายการแรก + ปุ่ม “แสดงผลทั้งหมด/Expand All” (2) บทสรุป และข้อความเตือนท้ายคำตอบก.3.6.2src/components/chat/AnswerParts.tsx, config retrieval.finalK (≤10)
FR-012Citation ในบทสรุปใช้รูปแบบมาตรฐาน {abbreviation} ({edition}) {volume}/{item}/{page} แสดงเป็นชิปสีทอง คลิกเปิด Modal เนื้อหาเต็มพร้อม metadataก.3.6.3, ก.4.6.3src/domain/ai/citation.ts, GET /api/chunks/[id]
FR-013แสดงข้อความเตือนใต้กล่องพิมพ์คำถามตลอดเวลาก.3.6.1src/components/chat/ChatApp.tsx + messages/*/
FR-014ผู้ใช้ให้ Feedback 👍/👎 (พร้อมความเห็น) ต่อคำตอบ บันทึกลงตาราง Feedback (1 รายการต่อผู้ใช้ต่อข้อความ)ก.3.8.1POST /api/messages/[id]/feedback
FR-015แสดง RAG Trace ของ 8 โหนด (เวลา ms และรายละเอียดแต่ละโหนด รวม model, prompt version, โทเคน)ก.4.10.1Message.traceJson, SSE done.trace
FR-016ประวัติบทสนทนาของตนเอง แสดงวันหมดอายุ ลบ/เปลี่ยนชื่อได้; ระยะเก็บเริ่มต้น 90 วัน (retention.conversationDays)ก.3.5.1/chat/history, GET/PATCH/DELETE /api/conversations[/id]
FR-017ผู้ใช้เข้าถึงได้เฉพาะบทสนทนาของตนเอง (ไม่ใช่เจ้าของ = 404 กัน IDOR)ก.2.1.1loadOwnedConversation() ใน src/application/chat/ask.ts

3.3 Dhamma Talk

รหัสข้อกำหนดTORImplement
FR-020สร้างบทธรรมบรรยายแบบ Artifact (Markdown) ทีละรอบ แสดงใน Response Modal แยก มีแท็บแก้ไข/ตัวอย่าง ผู้ใช้แก้ไขแล้วบันทึก และคำสั่งรอบถัดไปใช้ฉบับที่ผู้ใช้แก้ล่าสุดเป็นบริบท พร้อมข้อความเตือนในโหมดนี้ก.3.7.1–ก.3.7.4/chat/dhamma-talk, POST /api/chat/dhamma-talk, PATCH /api/conversations/[id] (artifactMd), DhammaTalkApp.tsx
FR-021สิทธิ์ใช้ Dhamma Talk แยกรายบัญชี ค่าเริ่มต้นปิด (User.dhammaTalkEnabled = false) ผู้ดูแลเปิด/ปิดได้ก.3.3.3, ก.6.1.3PATCH /api/admin/users/[id], ตรวจสิทธิ์ใน route

3.4 AI Engine และการค้นคืน (RAG)

รหัสข้อกำหนดTORImplement
FR-030Orchestrator ด้วย LangGraph.js StateGraph 8 โหนด และ LLM แบบ model-agnostic ผ่าน interface LlmGateway (typhoon/openai/gemini/claude/mock) เปลี่ยนได้จาก config โดยไม่แก้โค้ดก.4.1.2, 4.4.6, 2.6, ก.6.2.3src/server/ai/graph.ts, src/server/llm/*
FR-031Hybrid Search: BM25 (ตัดคำไทยด้วย Intl.Segmenter) + pseudo-embedding 512 มิติ (feature hashing) รวมด้วย RRF (k=60) รองรับไทย–บาลี–อังกฤษด้วยพจนานุกรม synonymก.4.6.1src/domain/retrieval/{tokenizer,bm25,embedding,rrf,synonyms}.ts, src/server/retrieval/index.ts
FR-032Query Expansion/Rewriting: ขยายคำพ้อง + ขอคำค้นเพิ่มจาก LLM ด้วย prompt query-rewrite (เมื่อใช้ LLM จริง)ก.4.7.2โหนด inputGuardrail/rewriteQuery
FR-033Re-ranker เป็นโมดูลแยก สลับได้ด้วย retrieval.reranker (heuristic ค่าเริ่มต้น หรือ tei เมื่อตั้ง TEI_RERANK_URL)ก.4.7.1src/domain/retrieval/heuristic-reranker.ts, src/server/retrieval/{reranker,tei-reranker}.ts
FR-034ปรับคะแนนด้วย Precedence ของประเภทเอกสาร (ปรับรายเอกสารได้ 0–2) แล้วตัดเหลือ finalK ≤10ก.4.8.3, ก.6.3.3src/domain/retrieval/relevance-scoring.ts
FR-035บุคลิกบรรณารักษ์: ช่วยค้น/สรุปพร้อม Citation ไม่ชี้ขาดการตีความ ปฏิเสธคำถามใช่/ไม่ใช่ก.4.5.1, ก.4.5.3config/prompts/qa-persona.v*.md, guardrail yes_no_pattern

3.5 Guardrails

รหัสข้อกำหนดTORImplement
FR-040ตอบเฉพาะคำถามด้านพระไตรปิฎก อรรถกถา หลักธรรม และแนวทางปฏิบัติ/ปรัชญาชีวิต (หัวข้อ scope_allow); คำถามนอกขอบเขตถูกปฏิเสธอย่างสุภาพ เมื่อใช้ LLM จริงยืนยันด้วย prompt guard-classifierก.4.3.1, ก.4.3.2src/domain/ai/guardrail-policy.ts, โหนด inputGuardrail
FR-041ป้องกัน Prompt Injection ทั้งทางตรง (รูปแบบ injection_pattern) และทางอ้อม (sanitize ตอนนำเข้า + ครอบบริบทด้วย delimiter <<<DATA ...>>> และระบุว่า “เป็นข้อมูล ไม่ใช่คำสั่ง”)ก.4.4.1src/domain/ingestion/sanitize.ts, src/server/ai/context.ts
FR-042ปฏิเสธหัวข้อการเมือง การเปรียบเทียบศาสนา/นิกาย และสุขภาพจิต พร้อมข้อความเฉพาะหัวข้อก.4.4.3GuardrailTopic kind refuse_sensitive (key politics, religion_comparison, mental_health)
FR-043Output guardrail: แทน [[cite:...]] ด้วย label มาตรฐาน ตัด citation ที่ไม่อยู่ในรายการอ้างอิง ติดป้าย “(ไม่มีอ้างอิง — โปรดตรวจสอบ)” ให้ย่อหน้าที่ไม่มีอ้างอิง และลดทอนถ้อยคำชี้ขาดก.4.5.3, ก.4.6.3resolveCitations(), evaluateOutput()
FR-044ผู้ดูแลเพิ่ม/แก้/ลบ/เปิดปิดหัวข้อขอบเขตและหัวข้อปฏิเสธ และทดสอบข้อความได้ทันทีก.6.2.5, ก.4.4.1/admin/guardrails, /api/admin/guardrails*

3.6 งบประมาณโทเคนและโหมดปิดบริการ

รหัสข้อกำหนดTORImplement
FR-050เพดานโทเคนรวม 24 ชั่วโมงล่าสุด ค่าเริ่มต้น 10,000,000 (tokens.dailyCap)ก.3.4.1src/domain/ai/token-budget-policy.ts (checkDailyBudget), src/server/ai/availability.ts
FR-051เพดานโทเคนต่อ prompt: ถาม-ตอบ ≤10,000 และ Dhamma Talk ≤50,000 (schema บังคับช่วง) และปรับ Max Tokens ได้ก.4.2.1, ก.3.4.2, ก.6.2.1tokens.maxPerPromptQa, tokens.maxPerPromptDhammaTalk, tokens.maxOutput, fitContext()
FR-052เข้า Unavailable อัตโนมัติก่อนยอดจริงเกินเพดาน (คำขอที่ทำให้ยอดจองเกินเพดานถูกปฏิเสธก่อนเรียก LLM; margin ≤10%) พร้อมแจ้งเตือน auto_unavailableก.3.4.4checkAvailability(), reserveTokens()
FR-053ป้องกัน Cost Surge: จำกัดความยาว input (input.maxChars ค่าเริ่มต้น 2,000 → 400 INPUT_TOO_LONG) และ thinking budget (tokens.thinkingBudget)ก.4.4.2โหนด checkAvailability, generate
FR-054ผู้ดูแลเปิด Unavailable Mode พร้อมข้อความ ไทย/อังกฤษ ทุกหน้าแสดงแบนเนอร์ (อ่าน /api/status) และ API แชตตอบ 503ก.3.4.3, ก.6.6.1/admin/unavailable, GET/PUT /api/admin/unavailable-mode, GET /api/status

3.7 การเก็บรักษาข้อมูลและ PDPA

รหัสข้อกำหนดTORImplement
FR-060ลบบทสนทนาที่พ้น retentionUntil อัตโนมัติทุก retention.purgeIntervalMinutes (ค่าเริ่มต้น 60 นาที) และสั่งทันทีได้ก.3.5.1, ก.6.2.2src/application/privacy/purge.ts (เริ่มใน src/instrumentation-node.ts), POST /api/admin/maintenance/purge-conversations, npm run purge:conversations
FR-061ผู้ใช้ดาวน์โหลดสำเนาข้อมูลตนเอง (JSON) และยื่นคำขอ access_copy / erase / withdraw_consent14.4/privacy, GET /api/privacy/export, GET/POST /api/privacy/requests
FR-062ผู้ดูแลดูและดำเนินการคำขอ PDPA (สร้างไฟล์สำเนา, ลบบทสนทนา+ทำให้ข้อมูลผู้ใช้เป็นนิรนาม, ระงับบัญชีเมื่อถอนยินยอม) และสร้างคำขอแทนผู้ใช้14.4, ก.6.1.2/admin/privacy-requests, /api/admin/privacy-requests*
FR-063เข้ารหัสอีเมล/ชื่อด้วย AES-256-GCM (DATA_ENCRYPTION_KEY) ค้นหาด้วย emailHash; สร้างสำเนาข้อความที่ปิดบัง PII (Message.contentMasked)14.3src/server/crypto.ts, src/server/repos/user-repo.ts, src/domain/privacy/pii-masking.ts
FR-064แบนเนอร์ยินยอมคุกกี้ โหลด Google Analytics 4 เฉพาะเมื่อผู้ใช้ยินยอมและตั้ง NEXT_PUBLIC_GA_ID; หน้า /privacy-policy ระบุผู้ควบคุม/ผู้ประมวลผลข้อมูลก.1.1.3, ก.1.5.1, 14.1src/components/layout/Banners.tsx, /privacy-policy

3.8 ฐานองค์ความรู้และการนำเข้า

รหัสข้อกำหนดTORImplement
FR-070อัปโหลดเอกสาร PDF / TXT / Markdown / TEI-XML / DOCX ≤20MB พร้อม metadata แล้วนำเข้าแบบ asyncก.4.8.1, ก.6.3.1/admin/knowledge-base, POST /api/admin/knowledge-base/upload, src/server/ingestion/parsers/index.ts
FR-071Chunking: พระไตรปิฎก/อรรถกถาแบ่ง 1 ข้อ = 1 chunk ไม่ตัดกลางข้อ (ข้อยาว >3,000 tokens ติดธง oversized); เอกสารอื่นใช้ semantic chunking (~400 tokens ตัดเมื่อ cosine ระหว่างย่อหน้า <0.35)ก.4.8.2, ก.4.6.2src/domain/ingestion/chunking-policy.ts
FR-072Metadata: ปิฎก นิกาย สูตร เล่ม ข้อ หน้า ประเภทเอกสาร ฉบับ ตัวย่อ เล่ม มมร. คำบาลี และ precedence แก้ไขได้ (อัปเดตดัชนีทันที)ก.4.8.3, ก.6.3.1, ก.6.3.3PATCH /api/admin/knowledge-base/[id]
FR-073Pipeline ปรับดัชนี: งาน ingest / reindex_all / reindex_selected / delete แสดงสถานะรายเอกสาร (stage parse→sanitize→chunk→embed→index) รายการที่ล้มเหลว และรันซ้ำเฉพาะที่ล้มเหลวก.4.5.3, ก.4.7.3, ก.6.3.2src/server/ingestion/pipeline.ts, /admin/knowledge-base/jobs, /api/admin/knowledge-base/{reindex,jobs*}
FR-074รายงานความครอบคลุมเทียบพระไตรปิฎกและอรรถกถาฉบับ มมร. 91 เล่ม4.5.2, 7.2.5, ข.1GET /api/admin/knowledge-base/coverage, src/application/ingestion/coverage.ts, npm run report:ingestion
FR-075Sanitize ข้อความก่อนนำเข้า: ตัด control/invisible chars และบรรทัดที่เข้ารูปแบบคำสั่งแฝง บันทึกจำนวนบรรทัดที่ตัดก.4.4.1src/domain/ingestion/sanitize.ts, KnowledgeDocument.sanitizedLines

3.9 การตั้งค่า AI และ Prompt

รหัสข้อกำหนดTORImplement
FR-080ผู้ดูแลปรับค่า AI (provider/model, เพดานโทเคน, retention, retrieval, เกณฑ์ประเมิน ฯลฯ) มีผลทันทีโดยไม่ redeploy ทุกการแก้สร้าง ConfigSnapshot และย้อนกลับได้ก.6.2.1–ก.6.2.3, ก.6.2.6/admin/ai-config, /api/admin/ai-config*, src/server/config/ai-config-store.ts
FR-081ทดสอบการเชื่อมต่อ LLM และแสดงรายการ provider/model ที่ adapter รองรับ (ระบุว่าตั้ง API key แล้วหรือไม่ โดยไม่เปิดเผยค่า)ก.6.2.3, 4.6.1POST /api/admin/ai-config/test-llm, GET /api/admin/ai-config/models
FR-082Prompt ไม่ hard-code: เก็บเป็นเวอร์ชัน (PromptVersion) ดู/เพิ่ม/แก้ (สร้างเวอร์ชันใหม่)/diff/activate (rollback) พร้อมผู้แก้และเวลาก.4.9.1, ก.6.2.4/admin/prompts, /api/admin/prompts*
FR-083Remote configuration ด้วยสคริปต์: npm run prompts:publish อ่าน config/prompts/*.md สร้างเวอร์ชันเมื่อเนื้อหาต่างก.4.9.2scripts/prompts/publish.ts
FR-084กำหนดอัตราค่าโทเคนต่อ model (input/thinking/output ต่อ 1M) ใช้ประมาณค่าใช้จ่ายก.7.3.3/admin/pricing, /api/admin/pricing*, LlmPricing

3.10 การประเมินคุณภาพ AI

รหัสข้อกำหนดTORImplement
FR-090ชุดทดสอบ 7 ชุด: DS-1 ธรรมศึกษาปรนัย 100, NT-2 นักธรรมตอบสั้น 50, RET-1/2/3 Retrieval วินัย/สุตตันตะ/อภิธรรม ชุดละ 100, GR-1 นอกขอบเขต 100, GR-2 หัวข้อสุ่มเสี่ยง 50ก.5.1.1–ก.5.1.7seed/benchmarks/*.csv, seed/benchmarks/SOURCES.md
FR-091จัดการชุดและข้อทดสอบผ่านเว็บ (เพิ่ม/แก้/ลบ) นำเข้า CSV และดาวน์โหลดแม่แบบ ทุกการเปลี่ยนข้อทดสอบเพิ่ม versionก.5.2.1, ก.6.4.1, ก.5.2.4/api/admin/evaluation/datasets*
FR-0923 workflow: Accuracy (exact match ตัวเลือก), Recall@5 (retrieval), LLM-as-Judge 0–3 และ Refusal (guardrail) ผ่าน pipeline จริงก.5.2.2src/application/evaluation/runner.ts (evaluateItem), src/domain/evaluation/scorers.ts
FR-093สั่งรันรายชุดแบบ async บันทึกผลรายข้อ + prompt version + dataset version + model + judge modelก.5.2.3, ก.5.2.4, ก.6.4.2POST/GET /api/admin/evaluation/runs*
FR-094เปรียบเทียบผลระหว่าง 2 รอบของชุดเดียวกัน (รายข้อ + delta + เวอร์ชัน)ก.6.4.3GET /api/admin/evaluation/compare
FR-095เกณฑ์ขั้นต่ำ (80% / Judge 2.00) และค่ารับประกันของผู้รับจ้าง แสดงผ่าน/ไม่ผ่านเกณฑ์ตรวจรับก.5.3, ก.5.3.1, 16.2GET /api/admin/evaluation/thresholds, eval.guaranteed
FR-096รายงาน AI Security: รัน GR-1 + GR-2 + ชุด Prompt Injection 20 เคส แสดงอัตราป้องกันต่อชุด/ประเภทการโจมตี7.2.7GET/POST /api/admin/evaluation/security-report
FR-097ส่งออกรายงาน Baseline/Acceptance และ Guardrail เป็น Markdown ลง docs/deliverables4.7.7, 7.2.3POST /api/admin/evaluation/export, npm run report:evaluation

3.11 ผู้ดูแลผู้ใช้ และบันทึกการตรวจสอบ (Audit)

รหัสข้อกำหนดTORImplement
FR-100ค้นหา/ดู/แก้/ระงับ/ลบ (soft) ผู้ใช้ แสดงสถานะ วันสมัคร การใช้งานล่าสุด สิทธิ์ Dhamma Talkก.6.1.2/admin/users, /admin/users/[id], /api/admin/users*
FR-101การเปลี่ยน role ทำได้เฉพาะ SUPER_ADMIN และบันทึก audit role_changedก.6.1.1PATCH /api/admin/users/[id]
FR-102การเปิดดูบทสนทนาของผู้ใช้โดยผู้ดูแลถูกบันทึก audit conversation_accessedก.6.5.1GET /api/admin/users/[id]/conversations
FR-110บันทึก Audit: login/logout สำเร็จ-ไม่สำเร็จ สิทธิ์ config/prompt เอกสาร การเข้าถึงบทสนทนา การสั่งประเมิน คำขอ PDPA ทำลายข้อมูล defect พร้อม timestamp ผู้กระทำ IP ประเภท ก่อน/หลัง ผลลัพธ์ก.6.5.1, ก.6.5.2src/server/audit/audit-logger.ts, AuditLog
FR-111Audit Log แก้ไข/ลบไม่ได้ (SQLite trigger audit_no_update/audit_no_delete) และตรวจความสมบูรณ์ด้วย hash chain (sha256)ก.6.5.3prisma/migrations/20261002110735_audit_immutable, src/domain/audit/hash-chain.ts, GET /api/admin/audit-log/verify
FR-112ค้นหา/กรอง Audit Log และส่งออก CSV หรือพิมพ์เป็น PDFก.6.5.4/admin/audit-log, GET /api/admin/audit-log/export

3.12 Dashboard และการแจ้งเตือน

รหัสข้อกำหนดTORImplement
FR-120Dashboard (เฉพาะ ADMIN/SUPER_ADMIN): บัญชีทั้งหมด DAU MAU sessions queries เฉลี่ยต่อ session แยกโหมด เลือกช่วง วัน/สัปดาห์/เดือน/ปี/กำหนดเอง และเทียบช่วงก่อนหน้า ข้อมูลอ่านสดจากฐานข้อมูลก.7.1.1, ก.7.1.2, ก.7.1.3, ก.7.2.1, ก.7.2.2/admin/dashboard, GET /api/admin/dashboard/usage
FR-121โทเคน input/thinking/output รายวัน/เดือนเทียบเพดาน โทเคนเฉลี่ยต่อคำถาม/บทสนทนาเทียบเป้า ประมาณการค่าใช้จ่าย และ Top usersก.7.3.1–ก.7.3.4GET /api/admin/dashboard/tokens
FR-122อัตรา Feedback 👍/👎ก.3.8.1GET /api/admin/dashboard/feedback
FR-130แจ้งเตือนทางอีเมล (SMTP เมื่อตั้งค่า มิฉะนั้นบันทึกใน AlertEmailLog): โทเคนถึงเกณฑ์ (tokens.alertThresholdPct), ค่าใช้จ่ายถึงเกณฑ์, error rate แชต >5% ใน 15 นาที, เข้า Unavailable อัตโนมัติ, งานนำเข้าล้มเหลวก.4.10.2, ก.7.3.5src/server/notification/index.ts, src/server/ai/usage.ts, /admin/alerts

3.13 การปฏิบัติการ (Ops) และเว็บไซต์

รหัสข้อกำหนดTORImplement
FR-140Defect Log: ความรุนแรง critical/high = 24 ชม., medium = 48 ชม., low = 80 ชม. ทำการ (10 วันทำการ) คำนวณ deadline แบบชั่วโมงทำการ ปิด high/critical ต้องมี resolutionNote การยืนยันของมหาวิทยาลัยคำนวณชั่วโมงเกินและค่าปรับ 0.025%/ชม.12.3, 12.4, 10.2, 4.7.8/admin/defect-log, src/domain/report/sla-policy.ts
FR-141รายงาน SLA: Availability, p95 latency, TTFT p95, error rate, defect เกิน SLA ส่งออกรายงานรายเดือน12.1, 12.2, 12.5/admin/sla-report, /api/admin/sla-report*, npm run report:sla
FR-142สำรองฐานข้อมูลและไฟล์อัปโหลด (SUPER_ADMIN) พร้อมคำสั่งกู้คืนก.1.6.3/admin/backup, scripts/backup-db.sh, scripts/restore-db.sh
FR-143ทำลายข้อมูลตามขอบเขตที่เลือกพร้อมหนังสือรับรอง (SUPER_ADMIN, ต้องพิมพ์ข้อความยืนยัน)4.11.3/admin/data-destruction, POST /api/admin/data-destruction
FR-144Health/Status/OpenAPI: /api/health (สถานะ DB และจำนวน chunk), /api/status (Unavailable + % งบโทเคน ไม่เปิดเผยความลับ), /api/openapi.jsonก.4.1.1src/app/api/{health,status,openapi.json}
FR-145Logging: JSON lines ลง logs/app.log + stdout และบันทึกทุกคำขอ API ลง ApiRequestLog (path, status, durationMs, ttftMs)ก.4.10.1src/server/logger.ts, withApi
FR-150สองภาษา ไทย/อังกฤษ (/th, /en) มีเมนูสลับภาษาทุกหน้าก.3.2.1next-intl, messages/{th,en}/*.json, LanguageSwitcher.tsx
FR-151ธีมมืด/สว่าง พร้อมปุ่มสลับทุกหน้าก.3.1.3next-themes, ThemeToggle.tsx
FR-152Responsive รองรับมือถือ แท็บเล็ต เดสก์ท็อป (390/768/1280 px) ไม่มี horizontal scrollก.1.2.1, 4.3.3Tailwind, Playwright 3 viewport
FR-153หน้า /tor (ตาราง 200 ข้อ ค้นหา/กรอง/ส่งออก CSV) และศูนย์เอกสาร /documents (ดาวน์โหลด .md / พิมพ์ PDF)7.5, 4.4src/app/[locale]/{tor,documents}
FR-154หน้า /help วิธีใช้และช่องทางติดต่อช่วงรับประกัน11.2src/app/[locale]/help

4. ความต้องการที่ไม่ใช่เชิงหน้าที่ (Non-Functional Requirements)

รหัสหมวดข้อกำหนดTORวิธีรองรับ/ตรวจวัดในระบบสาธิต
NFR-001ประสิทธิภาพFCP ≤5 วินาที, TTFT ≤15 วินาที, Error rate ≤5% ที่ p9512.2วัด TTFT ทุกคำขอ (Message.ttftMs, ApiRequestLog.ttftMs); E2E ตรวจ FCP <5s; npm run report:perf → /documents/performance-report
NFR-002รองรับผู้ใช้พร้อมกัน≥500 concurrent sessionsก.1.4.1, 4.7.6ทดสอบโหลดระดับเครื่องเดียวด้วย scripts/perf/load-test.ts; ระดับ 500 ต้องใช้สถาปัตยกรรม Production (HPA)
NFR-003ขนาดข้อมูลผู้ใช้รองรับ 1,000,000 บัญชีก.1.4.2npm run seed:perf-users -- --count=1000000 ลง prisma/perf.db แล้ววัดเวลาค้นหา (index emailHash unique)
NFR-004ความพร้อมใช้งาน≥95% ต่อเดือน12.1, ก.1.6.1คำนวณจาก uptime ของ process + ช่วง 5xx (/admin/sla-report); Production ใช้ replica ≥2 + probe
NFR-005การขยายระบบMicroservices, Horizontal/Auto Scalingก.1.3.1, ก.1.3.2, ก.1.6.2โค้ดแบ่ง bounded context; Helm chart 8 deployment + HPA (CPU 70%) ที่ infra/helm/mbu-buddhist-ai/
NFR-006ความมั่นคงปลอดภัยOWASP Top 10, TLS, Server-side validation, CSRF, Brute force, Security headers, Rate limit, WAFก.2.1.1–ก.2.1.5, ก.2.2.1, ก.1.1.2zod ทุก endpoint, Prisma (parameterized), CSP/X-Frame-Options/nosniff/Referrer-Policy/Permissions-Policy/COOP (next.config.ts), HSTS เมื่อ ENABLE_HSTS=1, rate limit 10/60/600 ต่อนาที, TLS self-signed (server.https.js); WAF เป็นตัวอย่างใน Helm values
NFR-007ความเป็นส่วนตัวPDPA: minimisation, consent, เข้ารหัส in transit/at rest, ปิดบัง PII, สิทธิเจ้าของข้อมูล14.1–14.4, ก.1.1.3FR-001, FR-061–FR-064
NFR-008การเข้าถึงContrast ≥4.5:1 (ข้อความปกติ) / ≥3:1 (ข้อความใหญ่) ทั้ง light/dark; มาตรฐานเว็บไซต์ภาครัฐ 3.0ก.3.1.1, ก.1.1.1src/styles/tokens.ts (contrastPairs) + tests/unit/contrast.test.ts; ประเมินตนเองใน /documents/test-checklist
NFR-009ความเข้ากันได้Chrome Android/Safari iOS, Chrome/Firefox/Safari/Edgeก.1.2.1, ก.1.2.2ทดสอบอัตโนมัติด้วย Chromium 3 viewport (เบราว์เซอร์อื่นยังไม่ได้ทดสอบใน sandbox)
NFR-010การบำรุงรักษาแยกชั้น, Unit coverage ≥80% (ไม่นับ config)4.7.3coverage gate lines/functions/statements ≥80%, branches ≥75% (vitest.config.ts)
NFR-011การติดตาม/วินิจฉัยLog เพียงพอต่อการวินิจฉัยก.4.10.1FR-145, RAG Trace (FR-015), x-request-id ทุกคำขอ
NFR-012ความยืดหยุ่นของโมเดลเปลี่ยน LLM/องค์ประกอบ AI โดยไม่กระทบระบบ4.4.6, 2.6LlmGateway, SearchGateway, RerankerGateway, NotificationGateway
NFR-013ความถูกต้องของ Auditเก็บ ≥90 วัน แก้/ลบไม่ได้ก.6.5.3ไม่มีกลไกลบ AuditLog (trigger ห้าม UPDATE/DELETE; ทำลายข้อมูลไม่ลบ AuditLog)
NFR-014การสำรองและกู้คืนคู่มือ + ทดสอบ restore ≥1 ครั้งก.1.6.3/documents/backup-restore-guide, npm run report:restore-test → /documents/restore-test-report
NFR-015ความน่าเชื่อถือของคำตอบทุกคำตอบอ้างอิงตรวจสอบย้อนกลับได้ ไม่ตัดตอนพระไตรปิฎก2.1, ก.4.6.2FR-012, FR-043, FR-071
NFR-016ภาษาและวันที่วันที่แสดงเป็นพุทธศักราชเมื่อ locale = thก.3.2.1fmtDate(d, locale)

5. สถาปัตยกรรมระบบ

5.1 แผนภาพสถาปัตยกรรมระบบสาธิต

flowchart TB
  U["ผู้ใช้ / ผู้ดูแล (เบราว์เซอร์)"] -->|HTTPS (server.https.js :3443) หรือ HTTP| MW["middleware.ts<br/>requestId · rate limit · CSRF · admin IP allowlist · locale"]
  MW --> PAGES["หน้าเว็บ src/app/[locale]/*"]
  MW --> API["Route Handlers src/app/api/* (withApi: RBAC · zod · audit · ApiRequestLog)"]
  API --> UC["Use cases src/application/*"]
  UC --> GRAPH["LangGraph StateGraph 8 โหนด<br/>src/server/ai/graph.ts"]
  GRAPH --> IDX["KnowledgeIndex (in-memory)<br/>BM25 + pseudo-embedding 512 + RRF"]
  GRAPH --> RR["Reranker: Heuristic | TEI"]
  GRAPH --> LLM["LlmGateway<br/>Typhoon (OpenAI-compatible) · OpenAI · Gemini · Claude · Mock"]
  UC --> DB[("SQLite ผ่าน Prisma<br/>prisma/dev.db")]
  GRAPH --> DB
  UC --> ING["Ingestion pipeline<br/>parse → sanitize → chunk → embed → index"]
  ING --> STORE[["storage/uploads"]]
  ING --> IDX
  UC --> NOTI["NotificationGateway<br/>AlertEmailLog | SMTP"]
  API --> LOG[["logs/app.log (JSON lines)"]]
  LLM -.->|HTTPS| TY["api.opentyphoon.ai/v1"]

5.2 การแมปสถาปัตยกรรม Production ↔ ระบบสาธิต

Production (Solution Architecture)ระบบสาธิต
8 microservices (NestJS) บน Kubernetes: identity, chat, ai-orchestrator, retrieval, ingestion, evaluation, config, reportNext.js 15 แอปเดียว แบ่ง bounded context ใน src/domain, src/application, src/server + Helm chart 8 deployment ที่ infra/helm/mbu-buddhist-ai/
PostgreSQL 16 / Redis 7 (token budget, lockout, config pub/sub, BullMQ)SQLite (Prisma) / ตาราง TokenUsage, LoginAttempt; config อ่านจาก DB ทุกคำขอ (hot reload); คิวงาน = IngestionJob ประมวลผล async ใน process เดียว
Elasticsearch BM25 + kNN (bge-m3) + RRFBM25 in-memory (ตัดคำไทย Intl.Segmenter) + pseudo-embedding 512 มิติ + RRF (SearchGateway; มี ElasticsearchSearchAdapter เป็นโครง)
bge-reranker-v2-m3 (TEI)HeuristicReranker (สลับเป็น TeiRerankerAdapter ได้เมื่อตั้ง TEI_RERANK_URL)
LangGraph.jsLangGraph.js จริง (@langchain/langgraph)
SMTP / S3 / cert-manager / PrometheusAlertEmailLog (หรือ SMTP เมื่อตั้งค่า) / storage/uploads / self-signed TLS / logs/app.log + ApiRequestLog
GitHub Actions + Helm + HPA + WAFไฟล์ส่งมอบ .github/workflows/ci-cd.yml, infra/helm/ (ยังไม่ได้ deploy จริง และยังไม่ได้รันบน GitHub Actions)

5.3 Workflow: ถาม-ตอบผ่าน RAG pipeline 8 โหนด

ลำดับโหนดใน src/server/ai/graph.ts: checkAvailability → inputGuardrail → rewriteQuery → hybridSearch → rerank → generate → outputGuardrail → persist โดย refusal ที่โหนด 1–2 ข้ามไปโหนด persist ทันที (conditional edge) ก่อนเปิด SSE ระบบตรวจ availability ล่วงหน้าเพื่อให้ตอบ HTTP 503/400 เป็น JSON ได้

sequenceDiagram
  autonumber
  actor U as ผู้ใช้ (/chat)
  participant API as POST /api/chat/qa (withApi)
  participant UC as askStream (application/chat/ask.ts)
  participant G as ragGraph (LangGraph)
  participant IDX as KnowledgeIndex
  participant L as LlmGateway (Typhoon)
  participant DB as SQLite (Prisma)
  U->>API: { question, conversationId?, locale } + cookie + x-csrf-token
  API->>UC: ตรวจสิทธิ์ user + zod
  UC->>DB: โหลดบทสนทนา (ownership) + getAiConfig()
  UC->>UC: checkAvailability (unavailable / เพดาน 24 ชม. / input.maxChars)
  alt ไม่ผ่าน
    UC-->>U: HTTP 503 SERVICE_UNAVAILABLE หรือ 400 INPUT_TOO_LONG (บันทึก refusal)
  else ผ่าน
    UC-->>U: เปิด SSE (text/event-stream)
    UC->>G: runRagGraph(input) + reserveTokens()
    G->>G: 1 checkAvailability (trace)
    G->>DB: 2 inputGuardrail โหลด GuardrailTopic
    opt LLM จริง
      G->>L: guard-classifier + query-rewrite (ขนานกัน)
    end
    alt ถูกปฏิเสธ (scope/injection/sensitive/yes_no)
      G->>DB: 8 persist (Message.refusalReason)
      G-->>U: event: refusal
    else ผ่าน
      G->>G: 3 rewriteQuery (synonym ไทย-บาลี-อังกฤษ + LLM queries)
      G->>IDX: 4 hybridSearch BM25 + vector + RRF (candidateK=50)
      G->>G: 5 rerank (Heuristic) + precedence → finalK ≤10
      G-->>U: event: references
      G->>L: 6 generate (persona + DATA delimiters, fitContext ≤ maxPerPrompt)
      loop streaming
        L-->>G: delta
        G-->>U: event: delta (วัด TTFT)
      end
      G->>G: 7 outputGuardrail (resolveCitations, evaluateOutput)
      G->>DB: 8 persist (Message + contentMasked + Citation + TokenUsage + traceJson)
    end
    G->>DB: afterUsageRecorded (threshold / error-rate alerts)
    UC-->>U: event: done {conversationId, messageId, answer, references, usage, trace, ttftMs, latencyMs, model, promptVersion}
  end
  Note over UC,U: ข้อผิดพลาดระหว่างสร้างคำตอบ → event: error {code: GENERATION_FAILED}

Dhamma Talk (POST /api/chat/dhamma-talk) ใช้กราฟเดียวกันด้วย mode = DHAMMA_TALK: โหนด rewriteQuery เพิ่มหัวเรื่องของ artifact เป็นคำค้น โหนด generate ใช้ prompt dhamma-talk พร้อม artifact ฉบับที่ผู้ใช้แก้ล่าสุดครอบด้วย <<<ARTIFACT>>> เพดาน tokens.maxPerPromptDhammaTalk และโหนด persist บันทึก Conversation.artifactMd

5.4 Workflow: การนำเข้าเอกสาร (Ingestion)

sequenceDiagram
  autonumber
  actor A as ผู้ดูแล (/admin/knowledge-base)
  participant API as POST /api/admin/knowledge-base/upload
  participant FS as storage/uploads
  participant DB as SQLite
  participant Q as enqueueJob (job chain ใน process)
  participant P as pipeline.ts
  participant IDX as KnowledgeIndex
  A->>API: multipart: file (pdf/txt/md/xml/docx ≤20MB) + metadata
  API->>FS: บันทึกไฟล์
  API->>DB: KnowledgeDocument (pending) + IngestionJob(ingest) + IngestionJobItem + audit document_imported
  API-->>A: { document, jobId }
  API->>Q: enqueueJob(jobId)
  Q->>P: runJob
  P->>DB: item.stage = parse
  P->>P: parseFile (unpdf / mammoth / TEI→marker / txt) + stripFrontmatter
  P->>P: sanitize (ตัด control chars + บรรทัดคำสั่งแฝง)
  P->>P: chunk (ChunkingPolicy: ตามข้อ หรือ semantic)
  P->>P: embed (pseudo-embedding 512 มิติ)
  P->>DB: index: transaction ลบ/อัปเดต/สร้าง Chunk (คง id ของ chunk ที่เนื้อหาเดิม)
  P->>IDX: reindexDocument(documentId)
  alt สำเร็จทุก item
    P->>DB: job.status = completed
  else มีรายการล้มเหลว
    P->>DB: job.status = partial | failed + item.errorMsg
    P->>DB: AlertEmailLog (ingestion_failed)
  end
  A->>API: GET /api/admin/knowledge-base/jobs/[id] (ติดตามสถานะ) / POST .../retry-failed

5.5 Workflow: การประเมินคุณภาพ AI (Evaluation)

sequenceDiagram
  autonumber
  actor A as ผู้ดูแล (/admin/evaluation) หรือ CLI (scripts/eval/run-acceptance.ts)
  participant API as POST /api/admin/evaluation/runs
  participant R as runner.ts (createRun / executeRun)
  participant G as ragGraph
  participant J as Judge LLM
  participant DB as SQLite
  A->>API: { datasetId, label: baseline | acceptance | adhoc }
  API->>R: createRun (บันทึก datasetVersion, promptKey/promptVersion, model, judgeModel) + audit evaluation_run
  API-->>A: { run (queued) }
  R->>DB: status = running
  loop ทุกข้อในชุด
    alt accuracy (DS-1)
      R->>G: answerStyle = mcq, skipInputGuard → exactMatchScore
    else retrieval (RET-1/2/3)
      R->>G: stopAfter = rerank → recallAtK(top-5, expectedRefs)
    else guardrail (GR-1/GR-2/INJ)
      R->>G: stopAfter = guard → refusalScore
    else judge (NT-2)
      R->>G: ถาม-ตอบเต็ม pipeline
      R->>J: prompt judge → คะแนน 0–3 (mock: token overlap)
    end
    R->>DB: EvaluationResult (botAnswer, retrievedIds, score, passed, judgeReason, latencyMs)
  end
  R->>DB: EvaluationRun.score / passed (เทียบ threshold) / finishedAt
  A->>API: GET /runs/[id] · GET /compare?runA&runB · GET /thresholds · POST /export

5.6 การจัดการความลับ

  • ไฟล์ .env ไม่อยู่ใน repository มีเพียง .env.example ที่ระบุชื่อคีย์ API key ของ LLM (เช่น TYPHOON_API_KEY) เป็นค่าที่มหาวิทยาลัยจัดหาและใส่เอง (TOR 4.6.1)
  • ระบบไม่ส่งค่า API key ออกทาง API ใด (/api/admin/ai-config/models คืนเพียง configured: true/false)
  • Production: เก็บใน Kubernetes Secret mbu-buddhist-ai-secrets (ดู infra/helm/mbu-buddhist-ai/values.yaml → existingSecret)

6. พจนานุกรมข้อมูล (Data Dictionary)

ตารางต่อไปนี้ สร้างจากไฟล์ prisma/schema.prisma จริง ด้วยสคริปต์แยกวิเคราะห์ (parse) ทุกบรรทัด model { ... } — ครบ 25 model / 295 field (รวม field ความสัมพันธ์) คอลัมน์ “ข้อกำหนด/ค่าเริ่มต้น” คัดลอกจาก attribute ของ Prisma ตามตัวอักษร คอลัมน์ “คำอธิบาย” ใช้ความเห็น (comment) ใน schema เมื่อมี และสำหรับ field ที่ไม่มีความเห็น ผู้จัดทำเขียนคำอธิบายจากการใช้งานจริงในโค้ด

ข้อสังเกตทั่วไป:

  • SQLite ไม่มีชนิด enum จึงเก็บค่าที่เป็นรายการเป็น String และตรวจค่าด้วย zod ในชั้น application/API (ค่าที่อนุญาตระบุในคำอธิบาย)
  • ฟิลด์ JSON (เช่น patterns, choices, expectedRefs, traceJson, before/after, embedding) เก็บเป็น String ที่เป็น JSON
  • ตาราง AuditLog มี trigger ระดับฐานข้อมูลห้าม UPDATE/DELETE (migration 20261002110735_audit_immutable)
  • คอลัมน์ emailEnc / displayNameEnc เข้ารหัส AES-256-GCM ชั้น repository เท่านั้นที่ถอดรหัส

User (กลุ่ม Identity)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
emailHashString@uniquesha256(lowercase email) ใช้ค้นหา/ล็อกอิน
emailEncString—อีเมลเข้ารหัส AES-256-GCM (14.3)
displayNameEncString—ชื่อแสดง เข้ารหัส
passwordHashString?—null สำหรับบัญชี OAuth
providerString@default("local")local | google | facebook | mock
providerIdString?—รหัสผู้ใช้ฝั่งผู้ให้บริการ OAuth
roleString@default("GENERAL_USER")SUPER_ADMIN | ADMIN | GENERAL_USER
statusString@default("active")active | suspended | deleted
dhammaTalkEnabledBoolean@default(false)ก.3.3.3 default ปิด
consentVersionString?—เวอร์ชันนโยบายที่ยินยอม
consentAtDateTime?—วันเวลาที่ให้ความยินยอมตามนโยบายความเป็นส่วนตัว
consentWithdrawnAtDateTime?—วันเวลาที่ถอนความยินยอม
lastLoginAtDateTime?—วันเวลาที่เข้าสู่ระบบครั้งล่าสุด
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)
deletedAtDateTime?—วันเวลาที่ลบแบบ soft delete (null = ยังไม่ลบ)
conversationsConversation[]—ความสัมพันธ์ไปยัง Conversation (หนึ่งต่อหลาย)
feedbacksFeedback[]—ความสัมพันธ์ไปยัง Feedback (หนึ่งต่อหลาย)
tokenUsagesTokenUsage[]—ความสัมพันธ์ไปยัง TokenUsage (หนึ่งต่อหลาย)
privacyRequestsPrivacyRequest[]—ความสัมพันธ์ไปยัง PrivacyRequest (หนึ่งต่อหลาย)

ดัชนี/ข้อจำกัดระดับตาราง: @@unique([provider, providerId]), @@index([role, status]), @@index([lastLoginAt])

LoginAttempt (กลุ่ม Identity)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
emailHashString—sha256 ของอีเมลที่ใช้พยายามเข้าสู่ระบบ
ipString—IP ของผู้พยายามเข้าสู่ระบบ
successBoolean—ผลการเข้าสู่ระบบ (ใช้คำนวณ lockout)
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@index([emailHash, createdAt]), @@index([ip, createdAt])

Conversation (กลุ่ม Chat)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
userIdString—เจ้าของบทสนทนา (FK → User)
userUser@relation(fields: [userId], references: [id])ความสัมพันธ์ไปยัง User
modeString—QA | DHAMMA_TALK
titleString—ชื่อบทสนทนา (40 ตัวอักษรแรกของคำถามแรก)
artifactMdString?—Dhamma Talk: artifact ล่าสุด (markdown)
retentionUntilDateTime—createdAt + retentionDays (ก.3.5.1)
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)
messagesMessage[]—ความสัมพันธ์ไปยัง Message (หนึ่งต่อหลาย)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([userId, updatedAt]), @@index([retentionUntil])

Message (กลุ่ม Chat)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
conversationIdString—FK → Conversation
conversationConversation@relation(fields: [conversationId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง Conversation
roleString—user | assistant | system
contentString—เนื้อหาข้อความ (คำถาม/คำตอบ/ข้อความปฏิเสธ)
contentMaskedString?—สำเนาปิดบัง PII สำหรับใช้ปรับปรุงระบบ (14.3)
refusalReasonString?—scope | injection | sensitive_politics | sensitive_religion | sensitive_mental | yes_no | budget | unavailable
promptVersionIdString?—PromptVersion ที่ใช้สร้างคำตอบ
modelString?—ชื่อโมเดล LLM ที่ตอบ
traceJsonString?—RAG trace (JSON) สำหรับ Trace panel
latencyMsInt?—เวลาตอบรวม (มิลลิวินาที)
ttftMsInt?—time to first token (12.2)
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
citationsCitation[]—ความสัมพันธ์ไปยัง Citation (หนึ่งต่อหลาย)
feedbacksFeedback[]—ความสัมพันธ์ไปยัง Feedback (หนึ่งต่อหลาย)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([conversationId, createdAt])

Citation (กลุ่ม Chat)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
messageIdString—FK → Message
messageMessage@relation(fields: [messageId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง Message
chunkIdString—FK → Chunk ที่ถูกอ้างอิง
chunkChunk@relation(fields: [chunkId], references: [id])ความสัมพันธ์ไปยัง Chunk
rankInt—1..10
labelString—"ข.ช. (ไทย) 27/87/87"
scoreFloat—คะแนนหลัง rerank และคูณ precedence

Feedback (กลุ่ม Chat)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
messageIdString—FK → Message ที่ให้ความเห็น
messageMessage@relation(fields: [messageId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง Message
userIdString—FK → User ผู้ให้ความเห็น
userUser@relation(fields: [userId], references: [id])ความสัมพันธ์ไปยัง User
valueInt—1 = thumb-up, -1 = thumb-down
commentString?—ความเห็นเพิ่มเติม (ไม่บังคับ)
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@unique([messageId, userId])

KnowledgeDocument (กลุ่ม Knowledge Base)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
titleString—ชื่อเอกสาร
docTypeString—tipitaka | atthakatha | tika | contemporary | teacher_dhamma (ก.4.8.3(2))
editionString?—มมร. | ไทย | อื่น ๆ
pitakaString?—vinaya | sutta | abhidhamma
nikayaString?—นิกาย/วรรค
suttaString?—ชื่อสูตร
volumeInt?—เล่ม (มมร. 1–91)
abbreviationString?—ตัวย่อสำหรับ citation เช่น "ข.ช." "วิ.มหา."
precedenceScoreFloat@default(1.0)ก.4.8.3(3) ปรับได้ใน admin
sourceTypeString—upload | seed | online
sourceFileString?—path ใน storage/uploads
sourceUrlString?—URL ต้นทาง (ข้อมูลออนไลน์)
mimeTypeString?—ชนิดไฟล์ต้นฉบับ
statusString@default("pending")pending | indexed | failed | deleted
chunkCountInt@default(0)จำนวน chunk ที่ทำดัชนีแล้ว
sourceNoteString?—หมายเหตุที่มา/ความถูกต้องของข้อความ
mbuVolumeInt?—เล่มในฉบับ มมร. 91 เล่ม (เมื่อทราบ) — ใช้คำนวณ coverage ภาคผนวก ข
paliAliasesString?—คำบาลี/อังกฤษของเอกสาร คั่นด้วย |
sanitizedLinesInt@default(0)จำนวนบรรทัดที่ถูกตัดตอน sanitize
createdByString?—ผู้นำเข้าเอกสาร
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)
deletedAtDateTime?—วันเวลาที่ลบแบบ soft delete (null = ยังไม่ลบ)
chunksChunk[]—ความสัมพันธ์ไปยัง Chunk (หนึ่งต่อหลาย)
jobItemsIngestionJobItem[]—ความสัมพันธ์ไปยัง IngestionJobItem (หนึ่งต่อหลาย)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([docType, status])

Chunk (กลุ่ม Knowledge Base)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
documentIdString—FK → KnowledgeDocument
documentKnowledgeDocument@relation(fields: [documentId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง KnowledgeDocument
ordinalInt—ลำดับ chunk ในเอกสาร (เริ่ม 0)
contentString—เนื้อหาเต็มของ chunk (1 ข้อ สำหรับพระไตรปิฎก/อรรถกถา)
volumeInt?—เล่ม
itemInt?—ข้อ
pageInt?—หน้า
pageEndInt?—หน้าสุดท้ายของข้อ (ข้อที่คร่อมหลายหน้า)
headingString?—หัวข้อ/ชื่อสูตรของ chunk
paliAliasesString?—คำบาลีถอดอักษร คั่นด้วย |
tokenCountInt—จำนวนโทเคนโดยประมาณ (ตัวอักษร ÷ 3)
oversizedBoolean@default(false)ข้อยาวเกิน 3,000 tokens แต่ไม่ถูกแบ่ง (ก.4.6.2)
embeddingString?—JSON number[512] (pseudo-embedding) — cache
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
citationsCitation[]—ความสัมพันธ์ไปยัง Citation (หนึ่งต่อหลาย)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([documentId, ordinal]), @@index([volume, item])

IngestionJob (กลุ่ม Knowledge Base)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
typeString—ingest | reindex_all | reindex_selected | delete
statusString@default("queued")queued | running | completed | failed | partial
requestedByString—ผู้สั่งงาน (userId)
totalItemsInt@default(0)จำนวนรายการทั้งหมดในงาน
successItemsInt@default(0)จำนวนรายการที่สำเร็จ
failedItemsInt@default(0)จำนวนรายการที่ล้มเหลว
startedAtDateTime?—เวลาเริ่มประมวลผล
finishedAtDateTime?—เวลาประมวลผลเสร็จ
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
itemsIngestionJobItem[]—ความสัมพันธ์ไปยัง IngestionJobItem (หนึ่งต่อหลาย)

IngestionJobItem (กลุ่ม Knowledge Base)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
jobIdString—FK → IngestionJob
jobIngestionJob@relation(fields: [jobId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง IngestionJob
documentIdString?—FK → KnowledgeDocument
documentKnowledgeDocument?@relation(fields: [documentId], references: [id])ความสัมพันธ์ไปยัง KnowledgeDocument
fileNameString?—ชื่อไฟล์ที่นำเข้า
stageString@default("queued")queued | parse | sanitize | chunk | embed | index | done | failed
statusString@default("queued")queued
errorMsgString?—ข้อความผิดพลาดเมื่อล้มเหลว
chunkCountInt@default(0)จำนวน chunk ที่ได้
durationMsInt?—เวลาที่ใช้ประมวลผล (มิลลิวินาที)

PromptVersion (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
keyString—qa-persona | dhamma-talk | guard-classifier | judge | query-rewrite
versionInt—หมายเลขเวอร์ชัน (เพิ่มทีละ 1 ต่อ key)
contentString—เนื้อหา prompt
changelogString?—บันทึกการเปลี่ยนแปลง
isActiveBoolean@default(false)เวอร์ชันที่ใช้งานอยู่ (1 เวอร์ชันต่อ key)
createdByString—ผู้สร้างเวอร์ชัน
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@unique([key, version]), @@index([key, isActive])

GuardrailTopic (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
kindString—scope_allow | refuse_sensitive | injection_pattern | yes_no_pattern
keyString?—politics | religion_comparison | mental_health | ... (ใช้จำแนก refusalReason)
labelThString—ชื่อหัวข้อภาษาไทย
labelEnString—ชื่อหัวข้อภาษาอังกฤษ
patternsString—JSON string[] (keyword/regex)
refusalMsgThString?—ข้อความปฏิเสธภาษาไทย
refusalMsgEnString?—ข้อความปฏิเสธภาษาอังกฤษ
enabledBoolean@default(true)เปิด/ปิดหัวข้อ
sortOrderInt@default(0)ลำดับการแสดง/ประเมิน
updatedByString?—ผู้แก้ไขล่าสุด
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)

AiConfigSetting (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
keyString@idดูรายการ key ในข้อ 7.2
valueString—JSON
updatedByString?—ผู้แก้ไขล่าสุด
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)

ConfigSnapshot (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
scopeString—ai-config | guardrails | unavailable | pricing
snapshotString—JSON ของค่าทั้ง scope ก่อนเปลี่ยน
reasonString?—เหตุผลการเปลี่ยนแปลง
createdByString—ผู้เปลี่ยนแปลง
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@index([scope, createdAt])

TokenUsage (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
userIdString?—FK → User (null = งานระบบ เช่น ประเมินผล)
userUser?@relation(fields: [userId], references: [id])ความสัมพันธ์ไปยัง User
conversationIdString?—บทสนทนาที่เกี่ยวข้อง
messageIdString?—ข้อความที่เกี่ยวข้อง
modeString—QA | DHAMMA_TALK | EVALUATION | GUARD
modelString—ชื่อโมเดล
inputTokensInt—โทเคนขาเข้า
thinkingTokensInt@default(0)โทเคนการคิด (ไม่เกิน thinkingBudget)
outputTokensInt—โทเคนขาออก
totalTokensInt—ผลรวมโทเคน (ใช้คำนวณเพดาน 24 ชม.)
estimatedCostFloat@default(0)คำนวณจาก LlmPricing ณ เวลาบันทึก
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt]), @@index([userId, createdAt])

LlmPricing (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
providerString—ผู้ให้บริการ LLM
modelString—ชื่อโมเดล
inputPerMillionFloat—USD หรือ THB ต่อ 1M tokens (currency ใน config)
thinkingPerMillionFloat@default(0)อัตราต่อ 1M thinking tokens
outputPerMillionFloat—อัตราต่อ 1M output tokens
currencyString@default("THB")สกุลเงิน
updatedByString?—ผู้แก้ไขล่าสุด
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)

ดัชนี/ข้อจำกัดระดับตาราง: @@unique([provider, model])

AlertEmailLog (กลุ่ม AI Config)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
typeString—threshold_tokens | threshold_cost | auto_unavailable | error_rate | ingestion_failed | system
severityString—info | warning | critical
toString—ผู้รับ
subjectString—หัวเรื่อง
bodyString—เนื้อหาแจ้งเตือน
deliveryString@default("logged")logged | sent | failed
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt])

EvaluationDataset (กลุ่ม Evaluation)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
nameString—ชื่อชุดทดสอบ
kindString—accuracy | judge | retrieval | guardrail
benchmarkCodeString—DS-1 | NT-2 | RET-1 | RET-2 | RET-3 | GR-1 | GR-2
versionInt@default(1)เพิ่มทุกครั้งที่ items เปลี่ยน (ก.5.2.4)
thresholdFloat—เกณฑ์ขั้นต่ำ 0.8 / 2.0
guaranteedFloat—ค่าที่ผู้รับจ้างรับประกัน (9.4/16.2)
expectedCountInt—จำนวนข้อตาม TOR (100/50/...)
createdByString—ผู้สร้างชุด
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)
itemsEvaluationItem[]—ความสัมพันธ์ไปยัง EvaluationItem (หนึ่งต่อหลาย)
runsEvaluationRun[]—ความสัมพันธ์ไปยัง EvaluationRun (หนึ่งต่อหลาย)

EvaluationItem (กลุ่ม Evaluation)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
datasetIdString—FK → EvaluationDataset
datasetEvaluationDataset@relation(fields: [datasetId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง EvaluationDataset
ordinalInt—เลขข้อ
questionString—คำถาม
choicesString?—JSON string[] สำหรับปรนัย
expectedAnswerString?—เฉลย (choice label หรือข้อความ)
expectedRefsString?—JSON [{volume,item}] หรือ chunkId[] สำหรับ Recall@5
expectRefusalBoolean@default(false)guardrail
levelString?—ตรี | โท | เอก
resultsEvaluationResult[]—ความสัมพันธ์ไปยัง EvaluationResult (หนึ่งต่อหลาย)

EvaluationRun (กลุ่ม Evaluation)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
datasetIdString—FK → EvaluationDataset
datasetEvaluationDataset@relation(fields: [datasetId], references: [id])ความสัมพันธ์ไปยัง EvaluationDataset
datasetVersionInt—เวอร์ชันชุดทดสอบ ณ เวลารัน (ก.5.2.4)
promptVersionIdString—FK เชิงตรรกะ → PromptVersion
promptKeyString—key ของ prompt หลักของ workflow
promptVersionInt—เวอร์ชัน prompt ณ เวลารัน (ก.5.2.4)
modelString—โมเดล LLM ที่ใช้ตอบ
judgeModelString?—โมเดลที่ใช้เป็นผู้ตัดสิน (NT-2)
labelString?—baseline | acceptance | adhoc
statusString@default("queued")queued | running | completed | failed
scoreFloat?—accuracy %, recall@5 %, judge avg, refusal %
passedBoolean?—score ≥ threshold
itemCountInt@default(0)จำนวนข้อที่ประเมิน
startedAtDateTime?—เวลาเริ่ม
finishedAtDateTime?—เวลาเสร็จ
requestedByString—ผู้สั่งรัน
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
resultsEvaluationResult[]—ความสัมพันธ์ไปยัง EvaluationResult (หนึ่งต่อหลาย)

EvaluationResult (กลุ่ม Evaluation)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
runIdString—FK → EvaluationRun
runEvaluationRun@relation(fields: [runId], references: [id], onDelete: Cascade)ความสัมพันธ์ไปยัง EvaluationRun
itemIdString—FK → EvaluationItem
itemEvaluationItem@relation(fields: [itemId], references: [id])ความสัมพันธ์ไปยัง EvaluationItem
botAnswerString—คำตอบของระบบ
retrievedIdsString?—JSON chunkId[] top-5
scoreFloat—0/1, 0/1, 0–3, 0/1
passedBoolean—ผ่าน/ไม่ผ่านรายข้อ
judgeReasonString?—เหตุผลของผู้ตัดสิน/ตัวให้คะแนน
latencyMsInt?—เวลาที่ใช้ต่อข้อ (มิลลิวินาที)

AuditLog (กลุ่ม Audit / Privacy / Ops)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
seqInt@id @default(autoincrement())SQLite อนุญาต autoincrement เฉพาะ primary key
idString@unique @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
timestampDateTime@default(now())วันเวลาที่เกิดเหตุการณ์
actorIdString?—ผู้กระทำ (userId; null = ระบบ/ผู้ไม่ระบุตัวตน)
actorEmailHashString?—sha256 อีเมลของผู้กระทำ (กรณี login ล้มเหลว)
ipString—IP ของผู้กระทำ
eventTypeString—login_success | login_failed | logout | user_changed | role_changed | config_changed | prompt_changed | document_imported | document_deleted | conversation_accessed | evaluation_run | privacy_request | data_destruction | defect_changed
targetTypeString?—ชนิดของสิ่งที่ถูกกระทำ
targetIdString?—รหัสของสิ่งที่ถูกกระทำ
beforeString?—JSON
afterString?—JSON
resultString—success | failure
prevHashString—hash ของแถวก่อนหน้า (genesis = 0×64)
hashString—sha256(prevHash + fields)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([timestamp]), @@index([eventType, timestamp]), @@index([actorId, timestamp])

PrivacyRequest (กลุ่ม Audit / Privacy / Ops)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
userIdString—เจ้าของข้อมูล (FK → User)
userUser@relation(fields: [userId], references: [id])ความสัมพันธ์ไปยัง User
typeString—access_copy | erase | withdraw_consent
statusString@default("pending")pending | completed | rejected
noteString?—หมายเหตุการดำเนินการ
exportPathString?—ไฟล์ JSON สำเนาข้อมูล
handledByString?—ผู้ดูแลที่ดำเนินการ
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
completedAtDateTime?—วันเวลาที่ดำเนินการเสร็จ

DefectLog (กลุ่ม Audit / Privacy / Ops)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
moduleString—โมดูลที่พบข้อบกพร่อง
descriptionString—รายละเอียดข้อบกพร่อง
severityString—critical | high | medium | low
statusString@default("open")open | in_progress | resolved | closed
reportedByString—ผู้แจ้ง
reportedAtDateTime@default(now())เวลาที่มหาวิทยาลัยแจ้ง (12.4 เริ่มนับ)
assignedToString?—ผู้รับผิดชอบแก้ไข
foundInPhaseString?—ระยะที่พบ (เช่น SIT/UAT)
resolutionNoteString?—วิธีแก้และมาตรการป้องกันซ้ำ (บังคับสำหรับ high/critical)
resolvedAtDateTime?—วันเวลาที่แก้ไขเสร็จ
confirmedAtDateTime?—มหาวิทยาลัยยืนยันผล (12.4 สิ้นสุดนับ)
slaHoursInt—24 | 48 | 80 (10 วันทำการ×8 ชม.)
slaDeadlineDateTime—กำหนดเวลาแก้ไขตาม SLA (ชั่วโมงทำการ)
overdueHoursInt?—คำนวณเมื่อ confirm
penaltyEstimateFloat?—overdueHours × 0.025% × contractValue (10.2)
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน
updatedAtDateTime@updatedAtวันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ)

ดัชนี/ข้อจำกัดระดับตาราง: @@index([severity, status])

ApiRequestLog (กลุ่ม Audit / Privacy / Ops)

ฟิลด์ชนิดข้อกำหนด/ค่าเริ่มต้นคำอธิบาย
idString@id @default(uuid())รหัสอ้างอิงของระเบียน (UUID)
requestIdString—รหัสคำขอ (x-request-id)
pathString—route pattern ของ API
methodString—HTTP method
statusCodeInt—HTTP status ที่ตอบ
durationMsInt—เวลาตอบสนอง (มิลลิวินาที)
ttftMsInt?—Time to first token ของคำขอ SSE
createdAtDateTime@default(now())วันเวลาที่สร้างระเบียน

ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt])

6.1 ความสัมพันธ์ระหว่างตารางหลัก

erDiagram
  User ||--o{ Conversation : owns
  User ||--o{ Feedback : gives
  User ||--o{ TokenUsage : consumes
  User ||--o{ PrivacyRequest : files
  Conversation ||--o{ Message : contains
  Message ||--o{ Citation : cites
  Message ||--o{ Feedback : receives
  Chunk ||--o{ Citation : referenced_by
  KnowledgeDocument ||--o{ Chunk : split_into
  KnowledgeDocument ||--o{ IngestionJobItem : processed_in
  IngestionJob ||--o{ IngestionJobItem : has
  EvaluationDataset ||--o{ EvaluationItem : has
  EvaluationDataset ||--o{ EvaluationRun : run_as
  EvaluationRun ||--o{ EvaluationResult : produces
  EvaluationItem ||--o{ EvaluationResult : scored_in

ตารางที่ไม่มีความสัมพันธ์แบบ foreign key: LoginAttempt, PromptVersion, GuardrailTopic, AiConfigSetting, ConfigSnapshot, LlmPricing, AlertEmailLog, AuditLog, DefectLog, ApiRequestLog

6.2 ค่าตั้งค่า AI (AiConfigSetting.key)

ค่าตั้งต้นจาก AI_CONFIG_DEFAULTS ใน src/domain/config/ai-config.ts (ผู้ดูแลแก้ได้ที่ /admin/ai-config ค่าที่ไม่ผ่าน schema ถูกละเว้นและใช้ค่าตั้งต้นแทน)

keyค่าตั้งต้นช่วงที่ schema ยอมรับTOR
llm.provider / llm.modeltyphoon / typhoon-v2.5-30b-a3b-instructข้อความก.4.1.2, ก.6.2.3
llm.fallbackModels[]≤10 รายการ—
tokens.dailyCap10,000,0001 – 1,000,000,000ก.3.4.1
tokens.marginPct100 – 50ก.3.4.4
tokens.alertThresholdPct801 – 100ก.7.3.5
tokens.maxPerPromptQa10,000500 – 10,000ก.4.2.1
tokens.maxPerPromptDhammaTalk50,000500 – 50,000ก.4.2.1
tokens.thinkingBudget2,0000 – 32,000ก.4.4.2
tokens.maxOutput1,50064 – 16,000ก.3.4.2
input.maxChars2,0001 – 20,000ก.4.4.2
retention.conversationDays901 – 3,650ก.3.5.1
retention.purgeIntervalMinutes601 – 1,440ก.6.2.2
unavailable.enabled / unavailable.autofalse / falsebooleanก.3.4.3, ก.3.4.4
unavailable.messageTh / unavailable.messageEnข้อความแจ้งปิดบริการ1 – 1,000 ตัวอักษรก.6.6.1
retrieval.candidateK / retrieval.finalK / retrieval.rrfK50 / 10 / 601–200 / 1–10 / 1–1,000ก.3.6.2, ก.4.6.1
retrieval.rerankerheuristicข้อความ (heuristic | tei)ก.4.7.1
eval.judgeModeltyphoon-v2.5-30b-a3b-instructข้อความก.5.2.2
eval.guaranteedDS-1 0.85, NT-2 2.3, RET-1/2/3 0.85, GR-1/GR-2 0.90 – 3 ต่อชุด16.2
contract.value5,000,000≥010.2
pricing.currencyTHBรหัส 3 ตัวอักษรก.7.3.3
privacy.policyVersion1.0ข้อความ14

7. User Flow

7.1 ผู้ใช้ทั่วไป: สมัคร → ถาม-ตอบ → ตรวจอ้างอิง → ประวัติ

flowchart TD
  H["หน้าแรก /"] --> R{"มีบัญชีแล้ว?"}
  R -- ไม่มี --> REG["/register: อีเมล รหัสผ่าน ชื่อ + ยินยอม"] --> LOGIN
  R -- มี --> LOGIN["/login: อีเมล+รหัสผ่าน หรือ Gmail/Facebook"]
  LOGIN -->|ผิด 5 ครั้ง| LOCK["ล็อก 15 นาที"]
  LOGIN --> CHAT["/chat: พิมพ์คำถาม (เห็นคำเตือนใต้กล่อง)"]
  CHAT --> G{"Guardrail"}
  G -- ปฏิเสธ --> REF["การ์ดปฏิเสธพร้อมเหตุผล"] --> CHAT
  G -- ผ่าน --> REFS["รายการอ้างอิง 3 รายการ + Expand All"]
  REFS --> SUM["บทสรุปแบบ streaming + citation chip"]
  SUM --> MODAL["คลิก chip → Modal เนื้อหาเต็ม + metadata"]
  SUM --> FB["👍/👎 + ความเห็น"]
  SUM --> TRACE["RAG Trace 8 โหนด"]
  SUM --> HIST["/chat/history: เปิดต่อ / ลบ / ดูวันหมดอายุ"]
  HIST --> PRIV["/privacy: ดาวน์โหลดสำเนา / ขอลบ / ถอนยินยอม"]

7.2 Dhamma Talk

flowchart TD
  DT["/chat/dhamma-talk"] --> P{"dhammaTalkEnabled?"}
  P -- ไม่ --> ASK["หน้าอธิบายให้ขอสิทธิ์จากผู้ดูแล"]
  P -- ใช่ --> PROMPT["พิมพ์หัวข้อบทธรรมบรรยาย"] --> ART["Response Modal: artifact Markdown (แท็บแก้ไข/ตัวอย่าง)"]
  ART --> EDIT["ผู้ใช้แก้ไข → บันทึก (PATCH artifactMd)"]
  EDIT --> NEXT["สั่งรอบถัดไป → AI ใช้ฉบับที่แก้ล่าสุดเป็นบริบท"] --> ART
  ART --> DL["ดาวน์โหลด .md"]

7.3 ผู้ดูแลระบบ

flowchart LR
  AL["/login (บัญชี ADMIN/SUPER_ADMIN จาก IP ใน allowlist)"] --> DASH["/admin/dashboard"]
  DASH --> USERS["ผู้ใช้ / คำขอ PDPA / Audit Log"]
  DASH --> AI["ตั้งค่า AI / Prompt / Guardrails / Unavailable / ราคาโทเคน"]
  DASH --> KB["ฐานองค์ความรู้ / งานนำเข้า"]
  DASH --> EVAL["Evaluation / ชุดทดสอบ / เปรียบเทียบ / รายงานความปลอดภัย"]
  DASH --> OPS["Defect Log / SLA / Backup* / ทำลายข้อมูล*"]

* เฉพาะ SUPER_ADMIN

Wireframe และ Prototype ของหน้าจอหลักอยู่ใน /documents/ux-deliverables


8. ข้อกำหนดส่วนต่อประสาน (API) — OpenAPI 3.0

  • เอกสาร OpenAPI 3.0.3 ฉบับเครื่องอ่านได้: GET /api/openapi.json สร้างจาก registry ใน src/server/api/openapi/index.ts (buildOpenApi) ซึ่งรวมไฟล์กลุ่ม core.ts, privacy.ts, admin-users.ts, admin-config.ts, admin-kb.ts, admin-eval.ts, admin-ops.ts
  • รูปแบบผลลัพธ์ทุก endpoint: { ok: true, data } หรือ { ok: false, error: { code, message } } ยกเว้น endpoint แบบ SSE (text/event-stream) และการดาวน์โหลด CSV/Markdown
  • การยืนยันตัวตน: cookie mbu_session (securityScheme sessionCookie); คำขอที่แก้ไขข้อมูลต้องมี header x-csrf-token
  • ชุดทดสอบ tests/api/openapi-coverage.test.ts ยืนยันว่าทุก path+method ใน registry มีกรณีทดสอบใน tests/api และเมื่อรัน npm test ระบบตรวจการเรียกจริงซ้ำอีกชั้น (regression ทุก endpoint — TOR 4.7.5; รายละเอียดใน /documents/test-reports-index)

8.1 สรุปกลุ่ม endpoint (รวม 107 operation ใน registry)

กลุ่ม (tag)จำนวนสิทธิ์ตัวอย่าง
System4publicGET /api/health, GET /api/status, GET /api/openapi.json, GET /api/csrf
Documents2publicGET /api/documents, GET /api/documents/[slug]
Auth10public / userPOST /api/auth/register, POST /api/auth/login, POST /api/auth/logout, GET /api/auth/oauth/[provider]/start, .../callback, GET /api/auth/oauth-status, POST /api/mock-oauth/token, GET /api/mock-oauth/userinfo, GET/PATCH /api/me
Chat8userPOST /api/chat/qa (SSE), POST /api/chat/dhamma-talk (SSE), GET /api/conversations, GET/PATCH/DELETE /api/conversations/[id], GET /api/chunks/[id], POST /api/messages/[id]/feedback
Privacy3userGET /api/privacy/export, GET/POST /api/privacy/requests
Admin — Users5ADMIN+GET /api/admin/users, GET/PATCH/DELETE /api/admin/users/[id], GET /api/admin/users/[id]/conversations
Admin — Privacy Requests3ADMIN+GET/POST /api/admin/privacy-requests, POST /api/admin/privacy-requests/[id]/complete
Admin — Audit Log3ADMIN+GET /api/admin/audit-log, GET .../export, GET .../verify
Admin — AI Config6ADMIN+GET/PUT /api/admin/ai-config, GET .../snapshots, POST .../revert/[snapshotId], GET .../models, POST .../test-llm
Admin — Prompts5ADMIN+GET /api/admin/prompts, GET/POST /api/admin/prompts/[key], POST .../activate/[version], GET .../diff
Admin — Guardrails5ADMIN+GET/POST /api/admin/guardrails, PATCH/DELETE /api/admin/guardrails/[id], POST .../test
Admin — Unavailable2ADMIN+GET/PUT /api/admin/unavailable-mode
Admin — Pricing4ADMIN+GET/POST /api/admin/pricing, PATCH/DELETE /api/admin/pricing/[id]
Admin — Maintenance1ADMIN+POST /api/admin/maintenance/purge-conversations
Admin — Knowledge Base11ADMIN+GET /api/admin/knowledge-base, POST .../upload, GET/PATCH/DELETE .../[id], GET .../[id]/chunks, POST .../reindex, GET .../jobs, GET .../jobs/[id], POST .../jobs/[id]/retry-failed, GET .../coverage
Admin — Evaluation Datasets11ADMIN+CRUD /api/admin/evaluation/datasets[/id], items CRUD, POST .../items/import (CSV), GET .../items/template
Admin — Evaluation Runs4ADMIN+POST/GET /api/admin/evaluation/runs, GET .../runs/[id], GET .../compare
Admin — Evaluation Reports4ADMIN+GET .../thresholds, GET/POST .../security-report, POST .../export
Admin — Dashboard3ADMIN+GET /api/admin/dashboard/{usage,tokens,feedback}
Admin — Alerts2ADMIN+GET /api/admin/alerts, POST /api/admin/alerts/test
Admin — Ops11ADMIN+ / SUPER_ADMINDefect log CRUD + export, GET /api/admin/sla-report + export, GET/POST /api/admin/data-destruction (SUPER_ADMIN), GET/POST /api/admin/backup (SUPER_ADMIN)

หมายเหตุ: route ภายใน POST /api/internal/security-event (ให้ middleware บันทึก audit เมื่อ IP นอก allowlist เข้าถึงส่วนผู้ดูแล ตรวจด้วย header ภายใน) ไม่ได้เปิดเป็น API สาธารณะ จึงไม่อยู่ใน OpenAPI registry

8.2 เหตุการณ์ SSE ของ /api/chat/qa และ /api/chat/dhamma-talk

eventข้อมูลเวลาที่ส่ง
referencesรายการอ้างอิง ≤10 (n, chunkId, label, title, snippet, score, volume, item ...)หลังโหนด rerank
deltaข้อความบางส่วนของคำตอบระหว่างโหนด generate
refusal{ reason, message, topicLabel? }เมื่อถูกปฏิเสธ
done{ conversationId, messageId, answer, refusal, artifactMd, references, usage{input,thinking,output,total}, trace[], ttftMs, latencyMs, model, promptVersion }จบคำขอ
error{ code: "GENERATION_FAILED", message }เกิดข้อผิดพลาดระหว่างสร้างคำตอบ

9. ข้อจำกัดและสมมติฐาน

  1. ระบบสาธิตรันบนเครื่องเดียว (Node.js ≥22 + SQLite) ตัวเลขด้านประสิทธิภาพที่ระดับ 500 concurrent และ Availability รายเดือนต้องยืนยันบนโครงสร้างพื้นฐานจริงของมหาวิทยาลัย
  2. ฐานองค์ความรู้ปัจจุบันเป็นพระไตรปิฎกฉบับออนไลน์บางส่วนตามที่ TOR ภาคผนวก ข อนุญาตในระยะแรก (ดู /documents/data-ingestion-plan และ /documents/ingestion-report)
  3. เมื่อไม่ได้ตั้ง API key ของ provider ที่เลือก ระบบสลับไปใช้ MockLlmAdapter (deterministic) อัตโนมัติ และชุดทดสอบ vitest บังคับใช้ mock เสมอ
  4. การทดสอบเจาะระบบไม่ได้ดำเนินการในรอบนี้ตามคำสั่งของผู้ว่าจ้าง (ดู /documents/pentest-report)