AMLO Screening — Data Tier Handoff (สิ่งที่ลงจริงแล้ว)
สรุปของที่ merge และ migrate ขึ้น DEV/UAT แล้วจริง — 3 ตาราง, กฎที่บังคับอยู่ที่ระดับ DB, หลักฐานการทดสอบ, และงานชิ้นถัดไปที่ยังไม่มีใครเขียน (ตัวสร้าง generation)
อัปเดต: 2026-08-21
หน้านี้คือสถานะจริง ณ 21/08/2026 ไม่ใช่แผน — สิ่งที่เขียนในนี้รันอยู่บน
DEV_CentralizedDbและUAT_CentralizedDbแล้ว design เบื้องหลังอยู่ที่ Phase 1 Design & Handoff · ความเสี่ยงที่ยังเปิดอยู่ → Open Concerns repo เดียวที่เกี่ยว:Backend_Centralized
1. สรุปใน 5 บรรทัด
- ตารางใหม่ 3 ตัว + migration ลงแล้ว:
amlo_screening_snapshot_header,amlo_screening_snapshot_detail,amlo_screened_company(schemalos) - domain model + EF configuration + test ครบ —
dotnet testผ่าน 851/851,dotnet build -c Releaseผ่าน (analyzer เป็น error) - migrate ขึ้น DEV และ UAT แล้ว พร้อมทดสอบพฤติกรรมจริงบน Postgres 10/10 ข้อ ทั้งสอง env
- ยังไม่มีโค้ด production ที่เขียนลงตารางเหล่านี้เลย —
Create/Compute/CopyFromถูกเรียกจากtests/เท่านั้น - งานชิ้นถัดไปคือ ตัวสร้าง generation (อ่าน staging → จัดกลุ่ม → เขียน 3 ตาราง → seal) ซึ่งยังไม่ได้เริ่ม
| หัวข้อ | ค่า |
|---|---|
| branch | feature/sprint-13/migration/amlo-screening-generation (push แล้ว ยังไม่เปิด PR) |
| commit | 1281311 — feat(amlo): add screening snapshot and screened company tables (26 ไฟล์, +3334) |
| migration | 20260821134240_AddAmloScreeningSnapshotAndScreenedCompany |
| DEV / UAT | อยู่ที่ migration นี้แล้ว (เดิมอยู่ที่ 20260806094117_AddCustCodeLogDatetimeUtcIndexToAmloScreeningMatch) |
| SIT | ไม่แตะ — ยังค้างที่ 20260608112601_RenameDocumentNumberTables (ช้ากว่า 3 migration) เพราะทีมเลิกใช้ SIT แล้ว |
2. ตารางทั้ง 3 ทำหน้าที่อะไร
los.amlo_screening_match ← staging (ของเดิม, ชั่วคราว, ลบได้)
│ copy ทั้งแถว
▼
los.amlo_screening_snapshot_header ← 1 แถว = 1 generation (รอบตรวจ)
├── _detail ← คลังหลักฐาน (สำเนาเต็มรวมชื่อ)
└── amlo_screened_company ← คำตอบ (ปลายทางอ่านตารางนี้ตารางเดียว)
| ตาราง | grain | ใครอ่าน |
|---|---|---|
snapshot_header | 1 แถวต่อ generation | ระบบเอง + audit |
snapshot_detail | 1 แถวต่อ แถวหลักฐาน ที่ copy มาจาก staging | audit / การสืบย้อนเท่านั้น — มี PII ชื่อบุคคล ห้ามโผล่ใน API หรือ log |
screened_company | 1 แถวต่อ CustCode ต่อ generation | ระบบปลายทาง (Phase 2) |
3. กฎที่บังคับอยู่ที่ระดับ DB แล้ว (ไม่ต้องเขียนโค้ดคุมซ้ำ)
| กฎ | บังคับด้วย |
|---|---|
| มี generation ที่เป็น current ได้ ใบเดียว | unique index บน IsCurrent แบบ WHERE "IsCurrent" |
GenerationNo ห้ามซ้ำ แต่ draft หลายใบถือ NULL พร้อมกันได้ | unique index แบบ WHERE "GenerationNo" IS NOT NULL |
RunId ห้ามซ้ำ (กัน re-entry ของ background job) | unique index |
| 1 CustCode มีคำตอบได้ใบเดียวต่อ generation | unique (SnapshotHeaderId, CustCodeNormalized) |
| ลบ generation แล้วคำตอบ+หลักฐานหายตาม | FK cascade |
| หลักฐานต้องรอดแม้ staging ถูก purge | SourceBatchId ตั้งใจไม่ผูก FK (ADR 0006) |
index ที่เหลือ: detail มี (SnapshotHeaderId, CustCodeNormalized) (ไม่ unique — ค้นหลักฐาน), SourceBatchId, RecordGuid · header มี (GateResult, GenerationNo)
ชนิดข้อมูลที่ verify บน Postgres จริงแล้ว: MatchedRoles = jsonb, Similarity = numeric(9,8), CustCodeNormalized = varchar(50), EvidenceFingerprint = varchar(64)
4. กฎที่อยู่ในโค้ด ไม่ใช่ใน DB
| กฎ | อยู่ที่ |
|---|---|
Normalize = trim + upper เท่านั้น ห้ามตัดศูนย์นำหน้า | AmloCustCode.Normalize — ดู ADR 0007 |
MatchedRoles ถูก trim → distinct → เรียง ordinal → ถ้าว่างเป็น null | AmloScreenedCompany.NormalizeRoles |
fingerprint กับ FingerprintVersion เกิดจาก call เดียวกัน ผู้เรียกยัดเองไม่ได้ | AmloScreenedCompany.Create — ดู ADR 0008 |
AmloType ที่มีขึ้นบรรทัดคั่นกลางถูกปฏิเสธ (กัน fingerprint ชนกัน) | AmloEvidenceFingerprint.NormalizeAmloType |
| CustCode ว่างถูกปฏิเสธตั้งแต่ขาเข้า staging | AmloScreeningMatch.Create |
| หลักฐานชุดว่างสร้างคำตอบไม่ได้ | AmloScreenedCompany.Create |
เหตุผลที่ไม่ใส่ guard ใน CopyFrom: CopyFrom อ่านแถวที่ EF materialize ขึ้นมา ซึ่งเลี่ยง Create ได้ ถ้า throw ตรงนั้นแถวเก่า 1 แถวจะล้ม generation ทั้งรอบ — จึงกันที่ทางเข้า (Create) แทน
5. หลักฐานว่ามันทำงานจริง
ทดสอบพฤติกรรมกับ Postgres จริง 10 ข้อ ผ่านทั้ง DEV และ UAT (รันใน BEGIN … ROLLBACK, ตรวจแล้วว่าไม่มีแถวตกค้าง: headers=0 companies=0)
- mark generation เป็น current ได้
- current ใบที่สอง ทำไม่ได้ ✅
- non-current หลายใบได้
- draft หลายใบถือ
GenerationNo= NULL พร้อมกันได้ GenerationNoซ้ำ ทำไม่ได้ ✅RunIdซ้ำ ทำไม่ได้ ✅- insert คำตอบพร้อม role array (jsonb) ได้
"abc123"กับ" ABC123 "ชนกันจริง ✅ (พิสูจน์ ADR 0007)- jsonb round-trip ภาษาไทยไม่เพี้ยน
- ลบ generation แล้ว cascade ลงคำตอบจริง
dotnet ef migrations has-pending-model-changes → No changes have been made to the model since the last migration.
⚠️ has-pending-model-changes = none เป็นแค่ครึ่งเดียวของหลักฐาน — test suite ใช้ EnsureCreated() จึงไม่เคยรัน migration เลย รายละเอียดที่ Open Concerns §2
6. งานชิ้นถัดไป — ตัวสร้าง generation (ยังไม่มีใครเขียน)
ตารางพร้อมแล้ว แต่ไม่มีอะไรเติมข้อมูลลงไป ผู้รับงานต่อต้องเขียน service ที่ทำตามลำดับนี้:
- อ่าน
amlo_screening_matchของ batch ที่IsTerminalแล้วเท่านั้น (ADR 0006 — seal ข้อมูลที่ยัง process ไม่จบไม่ได้) CreateDraftheader (GenerationNoยังเป็น NULL)CopyFromทุกแถวลงsnapshot_detail- จัดกลุ่มด้วยคีย์ที่ Compliance ยืนยัน — ข้อเสนอคือ
(CustCode, RecordGuid)แต่ยังไม่มีมติ ดู Phase 1 §3 AmloScreenedCompany.Createต่อ 1 CustCode พร้อมส่งหลักฐานชุดเดียวกับที่เขียนลง detail- ผ่านด่านตรวจ 6 ข้อ (Phase 1 §5.3) →
PassedหรือHeld - ปั๊ม
GenerationNoแล้วสลับIsCurrentใน transaction เดียว
สิ่งที่ต้องตัดสินตอนเขียน (ยังไม่มีคำตอบเพราะยังไม่มีผู้เรียก): Compute โยน ArgumentException ได้แล้ว — จะให้ทั้ง generation Failed หรือส่งแถวนั้นไป reject?
ข้อควรระวังจาก Phase 1 ที่ยังใช้ได้อยู่: advisory lock ต้องซ่อนหลัง repository interface (SQLite ไม่มี pg_advisory_xact_lock) และ test harness ของ AmloIngestionRunService มี class ชื่อซ้ำกัน 2 namespace ต้องแก้ทั้งคู่
7. เดินสาย env เอง
dotnet ef migrations script <FromName> <ToName> --idempotent `
--project src/Centralized02.Infrastructure `
--startup-project src/Centralized01.API `
--configuration Release --no-build `
--output artifacts/amlo-snapshot.sql
| จุดที่พลาดบ่อย | ทำยังไง |
|---|---|
| ต้องใช้ ชื่อ migration ไม่ใช่ timestamp เปล่า | 20260806094117 → error; ต้องใช้ AddCustCodeLogDatetimeUtcIndexToAmloScreeningMatch |
--output เขียน UTF-8 BOM ออกมา แล้ว Postgres ตอบ syntax error at or near "START" | ตัด BOM ก่อนส่ง (.replace(/^\uFEFF/, '')) |
หลัง migrations remove ต้อง build ใหม่ ก่อน migrations add | ไม่งั้น --no-build อ่าน assembly เก่าแล้วบอกว่าชื่อซ้ำ |
| ไม่มี pipeline ไหนรัน migration ให้ | เป็น manual step ทุกครั้ง — ci.yml, ci-sit.yml, ci-orchestrate.yml ไม่มี step นี้ |
migration นี้ เพิ่มอย่างเดียว ไม่มี ALTER/DROP ของเดิม ⇒ DB ล้ำหน้า code ที่ deploy อยู่ได้อย่างปลอดภัย