# การเชื่อมระบบสำรวจและวิเคราะห์พื้นที่กับ WCS

ปรับปรุง 3 พฤศจิกายน 2569 สัญญาข้อมูล `wcs.exchange.v1` ใช้ service credential แยกจากบัญชีวิศวกร การรับข้อมูล การสร้างไฟล์ การส่งตรวจ และการอนุมัติเป็นคนละขั้นตอน ข้อมูลที่ระบบต้นทางบอกว่าวัดจริงยังต้องมีหลักฐานและผู้ตรวจพิจารณา ไม่ปิด HOLD อัตโนมัติ

## 1. เตรียมการเชื่อมต่อ

ผู้ดูแล WCS สร้าง connector ด้วย POST `/api/workflow/connectors` ผ่านบัญชี admin และ CSRF ของเว็บ กำหนดชื่อระบบต้นทาง ผู้รับผิดชอบ และ scopes เท่าที่จำเป็น: `requirements:read`, `submissions:write`, `submissions:read`, `drafts:generate` การสร้างคืน token ครั้งเดียว ให้เก็บใน secret manager ของระบบต้นทาง ห้ามใช้ worker token ของ WCS หรือบัญชีผู้ดูแลฐานข้อมูลเป็น credential ของระบบอื่น

หากใช้ callback ให้ระบุ HTTPS URL พอร์ต 443 ของระบบต้นทางตอนสร้าง connector WCS ปฏิเสธปลายทาง private/reserved, URL ที่มี user/password หรือ fragment และไม่ตาม redirect ผลสร้างจะแสดง callback_signing_key ครั้งเดียว ให้เก็บแยกจาก service token การ disable connector ระงับการใช้ token และการส่ง callback ของ connector นั้น

ตัวอย่าง client อยู่ใน `WATER_CAD_STUDIO/integrations/wcs_exchange_client.py` ใช้ Python และ requests รับ token จากตัวแปร `WCS_EXCHANGE_TOKEN` เท่านั้น ไม่มี argument ให้ใส่ token ใน command line ค่า base URL เริ่มต้นคือ https://cad-studio.entawisdom.com บังคับ HTTPS สำหรับปลายทางภายนอก และไม่ตาม redirect เพื่อไม่ส่ง credential ต่อไปยังปลายทางอื่น

## 2. อ่านข้อกำหนดก่อนเตรียมข้อมูล

| API | สิ่งที่ได้ |
|---|---|
| GET `/api/exchange/v1/templates` | รายการครบตามทะเบียน หมวดหลัก และ generation_supported |
| GET `/api/exchange/v1/requirements/{id}` | ช่องสำหรับสร้างแบบ หน่วย ตัวเลือก คำอธิบาย ตัวอย่างสมมติ และข้อกำหนดสำรวจ/ทดสอบ |

ทะเบียนที่ตรวจเมื่อ 3 พฤศจิกายน 2569 มี 169 รายการ โดย 128 รายการมี engine ที่เปิดใช้งาน การอ่านข้อกำหนดของรายการที่ยังไม่มี engine ไม่ทำให้สร้าง CAD ได้ และไม่เพิ่มระดับความพร้อมทางวิศวกรรม จำนวนนี้เป็น snapshot; ให้ระบบต้นทางอ่านค่าปัจจุบันจาก API

ฟิลด์สำคัญ:

- `fields`: ตัวแปรที่ engine รับใช้สร้างแบบ ไม่ใช่ผลสำรวจที่ WCS รับรองแล้ว
- `generation_supported`: ถ้า false ยังเรียกสร้างแบบไม่ได้ แม้มีข้อกำหนดเตรียมงาน
- `investigations.fields`: ข้อมูลพื้นที่และผลทดสอบที่ต้องจัดหา พร้อมหน่วยและคำแนะนำ
- `investigations.questions`: รายละเอียดเฉพาะ เช่น วงอัดแรง รอยต่อ สมอ กรอง วิธีขุด และหลักฐานรับแรงตามชนิดงาน
- `investigations.gates`: ขอบเขตที่ผู้ตรวจต้องพิจารณา ประกอบกับทะเบียน HOLD ของชุด CAD ที่สร้างจริง
- `investigations.coverage`: CATALOGUE_FIELDS_MAPPED หมายถึงทะเบียนเชื่อมหัวข้อข้อมูลแล้ว ไม่ได้แปลว่าผลสำรวจหรือรายการคำนวณผ่าน
- `example_defaults`: ข้อมูลตัวอย่างที่จัดเป็นสมมติ ห้ามคัดไปเป็น measured หรือเติมแทนข้อมูลที่ขาดโดยไม่แจ้ง
- `requirements_sha256`: hash ของข้อกำหนดชุดนี้ ให้เก็บพร้อม source revision และใส่กลับใน submission

client ตรวจ hash ของข้อกำหนดที่อ่านได้ก่อนส่งต่อผู้ใช้งาน WCS เก็บข้อกำหนดทั้งชุดไว้กับการรับข้อมูล และเชื่อม hash ไปถึง snapshot ของฉบับส่งตรวจ หากข้อกำหนดเปลี่ยนก่อนขอสร้าง ระบบจะหยุดก่อน dispatch และให้ทบทวน/ส่ง source revision ใหม่ ไม่ใช้ข้อมูลชุดเก่ากับช่องหรือเงื่อนไขใหม่โดยเงียบ ๆ

ตัวอย่างคำสั่งจากโฟลเดอร์โครงการ หลังติดตั้ง token ผ่าน secret manager ของระบบต้นทาง:

```powershell
python WATER_CAD_STUDIO/integrations/wcs_exchange_client.py templates
python WATER_CAD_STUDIO/integrations/wcs_exchange_client.py --output RH15_requirements_R1.json requirements RH-15
```

ไฟล์ output ใช้โหมดสร้างใหม่ ไม่เขียนทับข้อกำหนดที่บันทึกไว้แล้ว

## 3. จัดข้อมูลและที่มา

submission มี `schema_version`, `template`, `source_reference`, `requirements_sha256`, `title`, `paper`, `values` และอาจมี `location` / `evidence` ค่าเริ่มต้นกระดาษ A3; รุ่นนี้รองรับ A3 และ A1

แต่ละรายการใน values เป็น object:

```json
{
  "value": 2.5,
  "unit": "m",
  "classification": "user_supplied",
  "source_reference": "รายงานสำรวจ R1 / หน้าและตำแหน่งที่เกี่ยวข้อง"
}
```

ตัวอย่างนี้แสดงรูปแบบค่าเดี่ยวเท่านั้น ไม่ใช่ข้อมูลพื้นที่จริงหรือ submission RH-15 ที่ครบทุกช่อง ให้ใช้ key และหน่วยตามข้อกำหนดที่อ่านจาก WCS จริง ห้ามส่ง unknown/internal parameter เช่น output_base หรือ approved

classification เลือก `user_supplied`, `measured`, `standard`, `designer_selected`, `assumed` ค่า measured ต้องมี observed_on แบบ YYYY-MM-DD, method และ evidence_ids ที่อ้างรายการ evidence ใน submission แต่การผ่าน validation ยังไม่ใช่การตรวจเอกสารหรือการรับรองผลทดสอบ

evidence แต่ละรายการต้องมี id, title, source_reference และ SHA-256 ของเอกสารจริง id ใช้ A-Z/a-z/0-9/underscore/dot/colon/hyphen ยาว 1–120 ตัวอักษร หลังส่ง submission ให้ส่งไฟล์จริงผ่าน service ตามขั้นตอนถัดไป การรู้ hash หรือชื่อรายงานอย่างเดียวไม่เท่ากับมีหลักฐานให้ผู้ตรวจอ่าน

ช่องทางไฟล์คือ POST `/api/exchange/v1/submissions/{id}/evidence` ใช้ scope `submissions:write` และ connector เจ้าของ submission เท่านั้น JSON มี `source_evidence_id`, `filename`, `media_type`, `content_base64` รองรับ PDF/PNG/JPEG/XLSX ไม่เกิน 8MB ต่อไฟล์และ 32MB รวมต่อ submission เซิร์ฟเวอร์ตรวจขนาด ลายเซ็นส่วนต้นของชนิดไฟล์ และ SHA-256 ให้ตรงรายการที่ประกาศไว้ ไม่เปิดหรือเรียกใช้เนื้อหาเอกสาร การตรวจนี้ไม่ใช่การสแกนมัลแวร์หรือรับรองเนื้อหา

ต้องส่งไฟล์ให้ครบทุก evidence ที่ประกาศก่อนเรียก generate หรือ prepare-review หากขาดจะตอบ 409 พร้อมรายการ MISSING_FILE ข้อมูล/ไฟล์เปลี่ยนต้องออก source revision และ Idempotency-Key ใหม่ ไม่เขียนทับหลักฐานเดิม ส่งไฟล์เดิม ชื่อเดิม ชนิดเดิมซ้ำได้และจะได้ evidence UUID เดิม แม้เริ่มสร้างงานแล้ว แต่เมื่อเริ่มสร้างงานจะเพิ่ม/เปลี่ยนไฟล์ไม่ได้

```powershell
python WATER_CAD_STUDIO/integrations/wcs_exchange_client.py upload-evidence SUBMISSION_UUID survey-report-R1 report.pdf --media-type application/pdf --expected-sha256 SHA256_OF_ACTUAL_FILE
```

สถานะรับไฟล์คือ `RECEIVED_NOT_VERIFIED` เสมอ เมื่อ prepare-review ระบบคัดลอกไฟล์และ provenance เข้าหลักฐานโครงการใน transaction เดียวกับสร้างฉบับร่าง และผูกแผนที่ source ID → evidence UUID/SHA เข้ากับ fingerprint ของฉบับ ผู้ตรวจยังต้องอ่านและลงผลตรวจจริงก่อนปิด HOLD; การรับไฟล์ไม่ปิด HOLD หรือสร้างการอนุมัติ ไฟล์ต้นทางที่ผูกฉบับจะรวมอยู่ใน manifest ส่งกลับหลังอนุมัติ โดยตรวจ hash ซ้ำก่อนดาวน์โหลด

การเพิ่มกติกาไฟล์ทำให้ requirements SHA เปลี่ยน รายการเก่าที่ยังไม่เริ่มสร้างต้องอ่านข้อกำหนดล่าสุดและส่ง source revision ใหม่ ไม่แก้ snapshot ข้อกำหนดเดิม

location รองรับ EPSG:4326, longitude [-180,180], latitude [-85,85], source_reference และ classification เป็น user_supplied หรือ assumed รุ่นนี้ไม่แปลง UTM/datum และไม่ยืนยันพิกัดให้เอง หากต้นทางมีข้อมูลสำรวจ CRS อื่นให้คงหลักฐานต้นฉบับและแปลงด้วยวิธีที่ตรวจย้อนกลับได้

การใช้ข้อมูลกว้าง ๆ เช่นผลสำรวจดินหรือรายงานน้ำหลาก ต้องแยกจากค่าที่วิศวกรเลือกใช้ในแบบ ไม่ส่งค่าผลทดสอบไปทับ geometry/สมมติฐานที่มีความหมายคนละอย่าง การตรวจว่าข้อมูลทำให้แบบเปลี่ยนยังเป็นหน้าที่ของกระบวนการตรวจและสร้างฉบับใหม่

## 4. ส่งข้อมูลและสร้างฉบับร่าง

| ขั้นตอน | API |
|---|---|
| รับข้อมูล | POST `/api/exchange/v1/submissions` พร้อม Idempotency-Key |
| ส่งไฟล์หลักฐานที่ประกาศไว้ | POST `/api/exchange/v1/submissions/{id}/evidence` |
| อ่านสถานะเดิม | GET `/api/exchange/v1/submissions/{id}` |
| ขอสร้างแบบ | POST `/api/exchange/v1/submissions/{id}/generate` |
| เปิดฉบับส่งตรวจหลังงานเสร็จ | POST `/api/exchange/v1/submissions/{id}/prepare-review` |

Idempotency-Key ใช้รหัส source record + source revision ที่คงเดิมสำหรับข้อมูลชุดเดียวกัน 8–128 ตัวอักษร A-Z/a-z/0-9/underscore/dot/colon/hyphen key เดิมกับข้อมูลเดิมคืนรายการเดิม; key เดิมกับข้อมูลที่เปลี่ยนตอบ 409 ต้องออก source revision ใหม่ ไม่เพิ่ม key แบบสุ่มเพื่อหลบผล validation

ตัวอย่าง Python สำหรับข้อมูลที่มีที่มาครบ ไม่เติมตัวอย่างแทนข้อมูลจริง:

```python
import json, os
from wcs_exchange_client import WCSClient

c = WCSClient("https://cad-studio.entawisdom.com", os.environ["WCS_EXCHANGE_TOKEN"])
req = c.requirements("RH-15")
values = json.load(open("provided_values_R1.json", encoding="utf8"))
missing = [f["key"] for f in req["fields"] if f["required"] and f["key"] not in values]
if missing:
    raise ValueError("ยังมีข้อมูลจำเป็นที่ขาด: " + ", ".join(missing))
payload = dict(schema_version="wcs.exchange.v1", template="RH-15",
    source_reference="SITE-001:R1", requirements_sha256=req["requirements_sha256"],
    title="ชื่อโครงการตามต้นทาง", paper="A3", values=values)
received = c.submit(payload, "SITE-001:R1")
if received["state"] == "validated":
    started = c.submission(str(received["id"]), "generate")
```

`validated` ยืนยันรูปแบบข้อมูลและที่มาเบื้องต้น ไม่ใช่ผลสำรวจผ่านตรวจ `generating`, `generated`, `generation_failed` มาจากสถานะ worker เมื่อมี job ผูกแล้ว การติดตาม worker ขาดหายไม่ทำให้เปลี่ยนเป็นสร้างล้มเหลวโดยเดา หลัง prepare-review จะได้ project/version สำหรับให้ผู้รับผิดชอบและวิศวกรทำงานต่อ ไม่มีการอนุมัติอัตโนมัติจาก service credential

client ไม่ลอง POST ซ้ำเองเมื่อการเชื่อมต่อขาด การรับ submission ที่ไม่ทราบผลสามารถอ่าน/ส่ง key เดิมและข้อมูลเดิมตาม idempotency ได้ ส่วน generate ต้องอ่านสถานะ submission เดิมก่อน หากเป็น dispatch_uncertain หรือ dispatching โดยยังไม่มี job ให้ผู้ดูแลกระทบยอดด้วย observed job และ exchange_import_id ผ่าน API ผู้ดูแล ไม่สร้างงานใหม่เพื่อแทนการตรวจ

## 5. ตรวจ แก้ไข และอนุมัติ

ต้นทางอ่านสถานะได้ แต่ต้องใช้บัญชีผู้ตรวจใน WCS เพื่อมอบหมาย ตรวจเอกสารสิทธิ์ แสดงความคิดเห็น ตรวจ HOLD และลงผลตรวจแต่ละขอบเขต ผู้จัดทำอนุมัติโครงการตัวเองไม่ได้ การแก้ CAD/คำนวณ/QTO หรือแนบชุดราคาใหม่ต้องเป็นฉบับใหม่ ผลอนุมัติเดิมไม่ติดไปกับฉบับใหม่

การอนุมัติผูกกับ hash ของไฟล์จริง รายงานผลตรวจ และประวัติสิทธิ์ที่ใช้ตรวจ หากเพิกถอน/เปลี่ยนสิทธิ์ก่อนอนุมัติทั้งชุด ต้องให้ขอบเขตที่กระทบตรวจและลงผลใหม่ การแนบลายเซ็นเป็น PDF ไม่ได้แปลว่า WCS ตรวจลายเซ็นเชิงคริปโตแล้ว

## 6. รับ callback และเก็บงานที่ผ่านอนุมัติ

callback `package.approved` มี event_id, project_id, version_id, number และ artifact_fingerprint พร้อม header X-WCS-Event-ID, X-WCS-Timestamp และ X-WCS-Signature ใช้ `verify_callback()` ใน client ตรวจ HMAC กับ raw body ก่อนแปลง/จัดการข้อมูล

สูตรลายเซ็นใช้ HMAC-SHA256 ของ UTF-8 timestamp + จุด + raw body โดย key เป็นข้อความ UTF-8 ของ callback_signing_key ที่ WCS แสดง ไม่แปลงข้อความ key จาก hex เป็น bytes อีกครั้ง ยอมรับความคลาดเคลื่อนเวลาตามนโยบายผู้รับ เช่น 300 วินาที ต้องมีเวลาเครื่องที่ตรงกัน

ปลายทางควรบันทึก event ลงฐานข้อมูลและคิวของระบบต้นทางใน transaction เดียวกัน ใช้ UNIQUE(connector_id,event_id) ถ้า event ซ้ำและ payload hash เดิม ให้ตอบ 2xx หลังยืนยันว่ามี record เดิม ไม่ทำงานซ้ำ ถ้า event เดิมแต่เนื้อหาเปลี่ยนให้หยุดตรวจและไม่เขียนทับหลักฐาน การรับ callback แล้วบันทึกคิวสำเร็จยังไม่เท่ากับดาวน์โหลดครบ ให้แยกสถานะ received / downloading / verified / failed

ให้ตอบ 2xx หลังบันทึก durable receipt แล้ว งานดาวน์โหลดทำต่อใน background ตามคิวของต้นทาง ไม่ใช้ memory set เป็นหลักฐานกันซ้ำถาวร WCS ส่งแบบ at-least-once เพราะอาจเกิดกรณีปลายทางรับแล้วแต่การตอบกลับขาด

| API ฉบับอนุมัติ | ข้อมูล |
|---|---|
| GET `/api/exchange/v1/projects/{p}/versions/{v}/manifest` | hash แบบ ชุดราคา หลักฐานที่ผูกฉบับ และผลตัดสินใจ |
| GET `.../artifacts/{name}` | ไฟล์ CAD/เอกสารใน manifest |
| GET `.../pricing-artifacts/{name}` | Excel/PDF/ข้อมูลคำนวณราคาของฉบับเดียวกัน |
| GET `.../evidence/{id}` | เฉพาะหลักฐานที่ฉบับอนุมัติอ้างถึง ไม่เปิดไฟล์อื่นทั้งโครงการ |

ต้นทางใช้ได้เฉพาะ connector ที่เป็นเจ้าของโครงการและ scope submissions:read การอ่านสถานะ under_review ไม่เปิดสิทธิ์ดาวน์โหลดฉบับอนุมัติ

```powershell
python WATER_CAD_STUDIO/integrations/wcs_exchange_client.py download PROJECT_UUID VERSION_UUID --directory received --expected-fingerprint HASH_FROM_CALLBACK
```

client ดาวน์โหลดแยก cad / pricing / evidence ตรวจ SHA-256 ทุกไฟล์ก่อนสร้าง RELEASE_COMPLETE.json ตรวจ fingerprint ให้ตรง callback ไม่เขียนทับไฟล์เดิมที่ต่างกัน และใช้ไฟล์ที่ตรง hash เดิมได้เมื่อดาวน์โหลดซ้ำ ขนาดไฟล์ผ่านช่องทางนี้จำกัด 64MB ต่อไฟล์ หลักฐานอัปโหลด WCS จำกัด 8MB ต้องพัฒนาช่องทางไฟล์ใหญ่แยกเมื่อจำเป็น

RECEIVED_APPROVED_SNAPSHOT หมายถึงรับฉบับที่ WCS บันทึกผลอนุมัติแล้วครบไฟล์ตาม hash ไม่ใช่การรับรองเพิ่มโดย client และไม่เปลี่ยนสถานะการอนุมัติของระบบราชการอื่นให้อัตโนมัติ

## 7. รหัสผิดพลาดและการดำเนินการ

| HTTP | การดำเนินการ |
|---|---|
| 401/403 | ตรวจ token, scope, connector enabled และสิทธิ์ ไม่สลับไปใช้บัญชี admin/worker token |
| 409 | ตรวจ source revision, idempotency, requirements hash, สถานะงาน/ฉบับ และข้อรอตรวจก่อนส่งใหม่ |
| 413 | ใช้ช่องทางไฟล์ใหญ่ที่จัดสิทธิ์และตรวจ hash ห้ามตัดไฟล์แล้วอ้างว่าเอกสารครบ |
| 422 หรือ state rejected | แก้ข้อมูลและที่มา ไม่เติมค่าผลสำรวจหรือราคาให้ดูครบ |
| 503/การเชื่อมต่อขาด | อ่านสถานะ request เดิมและกระทบยอด ห้ามสร้างซ้ำโดยเดา |

เมื่อ WCS ตอบ 500 พร้อม incident_id ให้แจ้งรหัสนั้นแก่ผู้ดูแล log เก็บชนิดข้อผิดพลาด ตำแหน่งโค้ด และ route โดยไม่เก็บข้อความ exception, header, body, credential หรือค่าตัวแปรภายใน

## 8. ขอบเขตและเงื่อนไขการเปิดบริการ

รับข้อกำหนดทั้ง169รายการและสร้างแบบเป็นร่างได้ตาม128 engine ที่ประกาศ รองรับการส่งไฟล์หลักฐานโดย service และชุดราคางานอาคาร/ชลประทาน/ทาง–สะพาน/ชุดเดิมที่มีหลักฐานอำนาจแล้ว แต่ไม่ใช้ข้อมูลส่งเข้าหรือการสร้างไฟล์สำเร็จเพิ่ม readiness หรือปิด HOLD โดยอัตโนมัติ วิธีคำนวณเฉพาะโครงการที่ยังไม่รองรับต้องคง HOLD ไม่ใช้ฟอร์มอาคารทดแทนโดยไร้หลักฐานจัดประเภท ดูรายละเอียดใน WCS_PRICE_PACKAGE_CONTRACT.md

การเชื่อมองค์กรจริงต้องมีผู้รับผิดชอบต้นทาง service credential ที่กำหนด scope, URL callback ที่ผ่านข้อกำหนด และผู้รับ/ผู้ตรวจจริง ผู้ดูแลเปิด dispatcher หลังตรวจคิวและตั้งค่าแล้ว ขณะนี้ตั้ง SMTP แล้วและ TEST ที่ได้รับอนุญาตส่งถึง Inbox แต่ dispatcher จริงยังปิด การส่งอีเมลสำเร็จไม่ใช่การอนุมัติแบบ
