# Data and method — CityMETER Grocery

ข้อมูลต้นทาง CityMETER ตรวจถึง 22 ต.ค. 2569 · Foodplace ปรับปรุง 25 ต.ค. 2569 ผ่าน `data/retail-refresh.json` · ผลกระทบยอดต้นทางอยู่ใน `data/build-receipt.json`

หน้านี้อธิบายว่าตัวเลขบนหน้าเว็บมาจากไหน นับอย่างไร และอะไรที่ยังไม่รู้ เขียนสำหรับคนที่จะดูแลข้อมูลหรือสร้างใหม่ ส่วนผู้ใช้ทั่วไปเห็นเฉพาะข้อเท็จจริงที่ใช้ตัดสินใจ (สถานะ แหล่งข้อมูล ความแม่นพิกัด) ในรายละเอียดสาขา

## 1. หน่วยข้อมูลและขอบเขต

| หน่วย | ความหมาย | จำนวน |
|---|---|---|
| จุดต้นทาง CityMETER | หนึ่งแถวตาม source row id ใน snapshot เดิม | 27,607 |
| สาขาเพิ่มจากแบรนด์ | Foodplace ที่มีรายการและพิกัดในทำเนียบสาขา Big C เพิ่มจาก snapshot เดิม | 5 |
| จุดที่แสดงรวม | จุดต้นทาง + สาขาเพิ่ม ไม่ใช่ยอดรวมใหม่จาก API ของ CityMETER หรือสำมะโนหน้าร้าน | 27,612 |
| ยอดรวมบน CityMETER | `grocery/info/lv0` totalCount | 27,608 |
| แบรนด์ | 17 เชน (ทะเบียน `01_BRANDS`); Foodplace เป็นรูปแบบร้านภายใต้ Big C | 17 |
| ประเภทร้าน | ร้านสะดวกซื้อ · ซูเปอร์มาร์เก็ต · ไฮเปอร์มาร์เก็ต · ค้าส่ง · ร้านขายยา (Pure อยู่ในหน้า Grocery ของ production จึงคงไว้) | 5 |
| พื้นที่ | ประเทศ › 77 จังหวัด › 928 อำเภอ/เขต › 8,621 อปท./ย่าน (7,774 อปท. นอกกรุงเทพฯ + 847 Locale ในกรุงเทพฯ) | — |

ความต่าง 1 แถวระหว่างยอดรวมต้นทาง (27,608) กับข้อมูลรายจุดต้นทาง (27,607) อยู่ที่อำเภอเกาะพะงัน (8405) จังหวัดสุราษฎร์ธานี: API นับ 57 แต่ vector tile ส่งจุดมา 56 จุด เก็บส่วนต่างนี้ตามเดิม แผนที่รุ่นปรับปรุงแยก 27,607 จุดต้นทางกับ 5 สาขาเพิ่มจากแบรนด์ รวม 27,612 จุด โดยยังระบุยอด API ต้นทาง 27,608 ที่ท้ายแผงข้อมูล ไม่สร้างจุดเพื่อชดเชยยอด API

ข้อมูลครอบคลุมเฉพาะ 17 เชนที่ CityMETER เลือก ไม่ใช่ร้านขายของชำทุกร้านในไทย ร้านอิสระและเชนอื่นอยู่นอกชุดข้อมูล

## 2. แหล่งข้อมูลที่ใช้สร้าง

1. **ชีตวิจัย** “CityMETER Grocery Thailand — Source Audit / Refresh Master 2026-10-22” (ไฟล์ xlsx sha256 `31d9c8e9…7449`; ชีตเป็นเอกสารภายใน หน้าเว็บและ receipt ไม่เผยแพร่ URL) — แท็บที่ใช้: `01_BRANDS`, `02_STORES`, `07_VENUES`, `10_VENUE_LOGOS`, `12_SOURCE_CATALOG`
2. **CityMETER production API** (ดึง 22 ต.ค. 2569 15:57–16:25 น. ตามเวลาไทย; ทุก request มี URL, สถานะ, ขนาด และ sha256 ใน fetch log)
   - `base/info/lv0` และ `base/info/lv1/{provinceId}` — รายชื่อ รหัส พื้นที่ (ตร.ม.) และ extent ของจังหวัด อำเภอ ตำบล อปท. และ Locale
   - `grocery/info/lv0` และ `grocery/info/lv1/{provinceId}?currentDetailType=ML` — จำนวนสาขาและประชากรของทุกพื้นที่
   - `grocery/info/geom/{id}/{PROVINCE|DISTRICT|MUNICIPALITY|LOCALE}` — ขอบเขตจริงของ 9,626 พื้นที่
   - `grocery/overlayMvt/10/{x}/{y}?inTileType={MUNICIPALITY|LOCALE}&inTileId={id}` — สมาชิกของแต่ละ อปท./ย่าน (8,896 tiles ของ 5,692 พื้นที่ที่มีสาขา)
3. **ไฟล์โลโก้** จากโฟลเดอร์ที่ชีตอ้างถึง (ดาวน์โหลดตามที่เจ้าของอนุญาต 22 ต.ค. 2569): โลโก้แบรนด์ 17 ไฟล์ และโลโก้สถานที่ 128 ไฟล์ (ใหม่ 91 + ใช้ร่วมกับ Luxury Brands 37) ทุกไฟล์ตรวจ sha256 ตรงกับชีตก่อนใช้
4. **อาคารอ้างอิง venue 29 แห่งเดิม และ host context เพิ่ม 2 แห่ง** — นำรูปทรงเดิมและที่มาตรวจย้อนกลับจาก [ชุด Luxury Brands](https://montri-th.github.io/luxurybrands/data/venue-buildings.geojson) ที่ตรวจซ้ำ 22 ต.ค. 2569 (sha256 `bb281936…92a8`) มาใช้เฉพาะ venue ที่เชื่อมชื่อกับ source venue id ที่ยอมรับแล้ว เก็บใน `data/venue-reference-buildings.json`: 29 host footprints และ 47 ส่วนอาคารสำหรับแสดงผล 3D รวม 76 features รูปทรงและค่าความสูงอ้างอิงตรวจถึง 21 ต.ค. 2569; ไม่ใช่ชุดอาคารทั่วประเทศ รอบติดตาม 25 ต.ค. เพิ่ม host context ที่ระบุสาขาได้อีก 2 features / 2 venue / 2 แถว ตามหลักฐานในข้อ 13 รวมปัจจุบัน 31 venue / 78 features / 46 แถว โดยไม่แก้รูปทรงเดิม 76 features หรือผล point join
5. **CityMETER V4 building cache** — ใช้ public-response snapshot เดิมที่มี HTTP/SHA receipt ตรวจครบ ไม่ดึง API batch ใหม่ ถอด geometry แบบ affine EPSG:4326 ตาม backend จริง โดยใช้ `version=V4&hideWithParts=false` ผลครอบคลุม exact source-subdistrict/z14 query ของ 7,885 สาขา พร้อมบริบท query พื้นที่ข้างเคียงตาม bbox + retrieval halo 1 กม. ซึ่งไม่ใช่เกณฑ์ผูกสาขากับอาคาร
6. **Microsoft Global ML Building Footprints** — ใช้ [ชุดข้อมูลทางการ](https://github.com/microsoft/GlobalMLBuildingFootprints) release `2026-08-13` ของประเทศไทยที่ตรึง URL/hash เป็นรอยอาคาร/หลังคาจากแบบจำลองภาพถ่าย ไม่ใช่การสำรวจขอบเขตหรือผู้เช่า ตรวจจุดครบทั้ง 27,607 แถว แล้วใช้เติมช่องว่างตามกติกาลำดับแหล่งในข้อ 11 เก็บที่มาและสิทธิ์รวมใน `data/building-sources.json`

สคริปต์ดึงข้อมูลอยู่ใน `scripts/data/` (`fetch-catalog.py`, `fetch-geometry.py`, `assemble-geo.py`, `assign-geometry.py`, `fetch-unit-membership.py`, `merge-unit-membership.mjs`) ข้อมูลดิบไม่ commit เพราะใหญ่ (ขอบเขตเต็ม ~370 MB) แต่ทุกอย่างสร้างซ้ำได้จาก API สาธารณะ

## 3. สาขาอยู่ในพื้นที่ไหน (membership)

ไม่มีการเดาจากชื่อหรือจากระยะทาง ทุกความเป็นสมาชิกมาจาก production:

- **จังหวัดและอำเภอ** — จาก `district_code` ในชีต ซึ่งมาจากการที่แถวนั้นถูกส่งมาใน tile ที่ query ด้วยตำบล (SUBDISTRICT) ของแหล่งต้นทาง แล้ว crosswalk รหัสตำบล → อำเภอ ผลรวมรายอำเภอเท่ากับ API 927 จาก 928 อำเภอ (ต่างเฉพาะเกาะพะงันตามข้อ 1) และรายจังหวัด 76 จาก 77
- **อปท./ย่าน** — ดึง overlay tile ของแต่ละพื้นที่ด้วย `inTileType=MUNICIPALITY` (อปท.) หรือ `LOCALE` (ย่านกรุงเทพฯ) แล้วถอด id ของแถวที่ส่งกลับ จำนวนสมาชิกที่ถอดได้เท่ากับยอดของ API **ครบทั้ง 5,692 พื้นที่ที่มีสาขา** (อีก 2,929 พื้นที่มียอด 0 ซึ่งเป็นศูนย์ที่วัดได้)
  - 15 สาขาอยู่ในสองพื้นที่ที่ขอบเขตซ้อนกันในแหล่งข้อมูล (3 แห่งใน 2 ย่านของบางเขน, 2 แห่งใน 2 ย่านของวัฒนา, 9 แห่งที่ทั้งย่านขอบกรุงเทพฯ และเทศบาลในสมุทรปราการอ้างถึง, 1 แห่งใน ทต. และ อบต. คลองไผ่ ที่ซ้อนกัน) หน้าเว็บนับให้ทั้งสองพื้นที่เหมือน production แต่ในส่วนแบ่งของ Aha นับสาขาละครั้ง
  - 19 สาขาในกรุงเทพฯ ไม่อยู่ในย่านใดเลย (ส่วนใหญ่อยู่ในพื้นที่ทหารของเขตดุสิตที่ไม่มี Locale) จึงมีระดับละเอียดสุดคือเขต
- **ระดับบนของ อปท./ย่าน** — อปท. ใช้รหัสอำเภอที่ฝังในรหัส DOLA (ตัวที่ 3–6 ของรหัส 8 หลัก หรือ 2–5 ของรหัส 7 หลักเช่น เมืองพัทยา 3200408 → บางละมุง) ย่านกรุงเทพฯ ใช้เขตที่สาขาส่วนใหญ่ของย่านนั้นรายงาน (เฉพาะเขตในกรุงเทพฯ) ถ้าไม่มีสาขาใช้เขตที่ซ้อนทับพื้นที่มากที่สุด (`assign-geometry.py` บนขอบเขตเต็ม): DOLA 7,774 · เสียงข้างมาก 681 · ซ้อนทับ 166
- 69 สาขามีรหัสอำเภอจากตำบลต่างจากอำเภอของ อปท. ที่มันเป็นสมาชิก (ร้านริมเขตแดน; ทำให้ 25 อปท./ย่าน “คร่อม” เขตอำเภอ เช่น ทน.แหลมฉบัง มีสมาชิก 152 สาขา โดย 130 สาขามีตำบลในศรีราชา) สองระบบนี้ซ้อนกันไม่พอดี หน้าเว็บจึงใช้กติกาเดียว: **ยอดของพื้นที่หนึ่งเท่ากันทุกที่** — อำเภอนับตามตำบล (= API) และ อปท./ย่านนับสมาชิกทั้งหมดของ production (= API) แม้ในมุมมองเปรียบเทียบของอำเภอ (`js/hierarchy.js#comparisonRows`) ส่วน **ส่วนแบ่ง** ของอำเภอ (Aha และแท่งสาขาในกราฟ) นับเฉพาะสาขาของอำเภอนั้น (`inArea`) เพื่อไม่ให้ตัวเศษมีสาขาที่อยู่นอกตัวหาร ผลคือในอำเภอที่มี อปท. คร่อมเขต ผลรวมของป้ายอาจต่างจากตัวเศษของ Aha 1–2 สาขา (เช่น เขตวัฒนา: ป้าย 23 + 22 + 20 แต่ Aha นับ 64 จาก 168)

## 4. ประชากรและตัวชี้วัด

- ประชากร = ประชากรตามทะเบียนราษฎร กรมการปกครอง พ.ค. 2569 ที่ CityMETER ใช้ (ตาม `sources.ts` ของ production) ไม่ใช่ประชากรสด
- ประเทศ 65,723,008 = ผลรวม 77 จังหวัด = ผลรวม 928 อำเภอ (ตรวจใน receipt)
- 163 อปท./ย่าน ไม่มีประชากร (39 ย่านกรุงเทพฯ + 124 อปท.) → สาขาต่อคน = **ไม่มีข้อมูล** ไม่ใช่ศูนย์ และ Aha ระดับอำเภอที่มีพื้นที่ย่อยขาดประชากรจะไม่เทียบกับจำนวนคน
- ประชากรของ อปท. รวมกันไม่เท่าประชากรอำเภอทุกแห่ง (เป็นชุดตัวเลขคนละระดับ) Aha จึงเทียบภายในระดับเดียวกันเสมอ: ส่วนแบ่งคน = ประชากรของพื้นที่ที่ถูกเลือก ÷ ผลรวมประชากรของพื้นที่ย่อยทั้งหมดที่เทียบ
- **สาขาต่อ 1 หมื่นคน** = สาขา ÷ ประชากร × 10,000 (ตามตัวกรอง)

## 5. Aha แรก: สาขากระจุกเทียบคน

ที่ทุกระดับ (`js/hierarchy.js#buildAha`):

1. พื้นที่ย่อยของระดับปัจจุบัน: ประเทศ → 77 จังหวัด, จังหวัด → อำเภอ/เขต, อำเภอ → อปท./ย่าน
2. เรียงตามจำนวนสาขาในตัวกรอง (เท่ากัน → ชื่อ) เลือก k อันดับแรก: k = 3 เมื่อมีพื้นที่ย่อย ≥ 9, 2 เมื่อ ≥ 4, ไม่เช่นนั้น 1
3. ส่วนแบ่งสาขา = สาขาในพื้นที่ปัจจุบันที่อยู่ใน k พื้นที่นั้น (นับครั้งเดียว) ÷ สาขาทั้งหมดของพื้นที่ปัจจุบัน
4. ส่วนแบ่งคน = ประชากรของ k พื้นที่ ÷ ประชากรรวมของพื้นที่ย่อยทั้งหมด (แสดงเมื่อทุกพื้นที่ย่อยมีประชากร)
5. คำว่า “แค่” ใช้เมื่อส่วนแบ่งคน (ปัดเป็น %) น้อยกว่าส่วนแบ่งสาขา

ค่าจาก snapshot ต้นทาง 22 ต.ค. 2569 (ไม่กรอง): **30% ของสาขาอยู่ใน 3 จังหวัด (กรุงเทพฯ · ชลบุรี · สมุทรปราการ) ซึ่งมีคนแค่ 13% ของประเทศ** (8,228 จาก 27,607 สาขา; 29.80% เทียบ 12.85%) ตัวเลขนี้เป็น fixture ของ test ต้นทาง หน้าเว็บคำนวณใหม่จากจุดที่แสดงรวมสาขาเพิ่มและตัวกรอง ไม่ใช้ค่าคงที่นี้แทนผลปัจจุบัน

## 6. สีและระดับ

- จำนวนสาขา → LDS 0.9.7 `count` (scaleVersion `4ff5c1fb…0980`); ต่อ 1 หมื่นคน → `density.capita` (`0b49c15c…4ab1`) ใช้ LUT 41 ค่าตาม LDS โดยตรง HEX เดียวกันทั้งสองธีม
- ใช้ครบ 41 ขั้นของ LDS เสมอ (กติกาเจ้าของ 22 ต.ค. 2569) ทั้งแผนที่ คำอธิบายแผนที่ กราฟอันดับ และตาราง ไม่ใช้ชุด 5 ระดับ ไม่ไล่สีแบบ gradient
- วิธี `rank-41` (`js/scales.js#buildScale`): ปัดค่าตามที่แสดงก่อน (จำนวนเต็ม / ทศนิยม 1 ตำแหน่ง) แล้วให้แต่ละค่าได้ลำดับกลาง (mid-rank) ในกลุ่มพื้นที่ที่เทียบกัน ค่าน้อยสุด = ขั้น 0 ค่ามากสุด = ขั้น 40 ค่าเท่ากันได้ขั้นเดียวกัน ตัวเลขกับสีจึงตรงกันเสมอ; ค่าสูงผิดปกติไม่ถูกตัด (ขั้น 40 คือค่าสูงสุด)
- ใช้ลำดับ ไม่ใช้ระยะเชิงเส้น: กรุงเทพฯ มี 5,310 จาก 27,607 สาขา ถ้าไล่เชิงเส้น 70 จาก 77 จังหวัดจะอยู่ใน 5 ขั้นแรก ระดับประเทศแบบลำดับใช้ครบ 41 ขั้น (จำนวน) และ 31 ขั้น (ต่อคน ซึ่งค่าซ้ำกันมากเมื่อปัดทศนิยม 1 ตำแหน่ง)
- ศูนย์ = ขั้น 0 + เส้นขอบศูนย์ · ไม่มีข้อมูล = สีกลาง `#D5DAD6` · นอกการเทียบ = ไม่ระบาย
- ตัวเลขบนสีใช้หมึกที่ contrast สูงกว่าในสองสีพื้นฐาน; ขั้นที่ไม่ถึง 4.5:1 ทั้งสองหมึก (`count` 27–29, `density.capita` 31–35) วางตัวเลขบนแผ่นรองสีกลาง ไม่เปลี่ยนสีของขั้น

## 7. พิกัด

พิกัดถอดจาก vector tile สาธารณะของ CityMETER ที่ z10 extent 4096 (ตารางประมาณ 9.55 ม. ในพิกัด Web Mercator) ไม่ใช่ lat/lon ต้นฉบับของแหล่งข้อมูลและไม่ใช่ตำแหน่งประตูร้านที่สำรวจ ข้อมูลในเว็บปัดเป็นทศนิยม 5 ตำแหน่ง (~1.1 ม.) ซึ่งละเอียดกว่าตารางของ tile จึงไม่เปลี่ยนตำแหน่ง แหล่งข้อมูลบางชุดมีข้อจำกัดของตัวเอง (เช่น Lotus's ใช้ตำแหน่งจากข้อมูลเดิม 12 พ.ค. 2567 เพราะเว็บไม่มีพิกัด; Tops/Makro/GO Wholesale บางสาขากำหนดพิกัดด้วยมือ) ข้อจำกัดนี้แสดงในรายละเอียดสาขาและอยู่ใน `data/grocery.json → sources[].limitation`

ขนาดตารางและจำนวนทศนิยมไม่ใช่ค่าความแม่นของพิกัดจริง จุดต้นทางอาจคลาดจากร้านมากกว่านั้น การจับคู่กับอาคารในข้อ 11 จึงอธิบายเฉพาะจุดที่แสดงบนแผนที่ และไม่ขยับจุดเพื่อให้เข้าอาคาร

ข้อยกเว้นที่เจ้าของสั่งชัดเจน 25 ต.ค. 2569 คือสาขาเพิ่ม PARC `bigc-official-15073`: เลือกจุด Maps `[100.6477432, 13.6671705]` แทนจุดจัดแสดงเดิม โดยคง raw `longitude` / `latitude` / `coordinateRole` จากแบรนด์และ frozen `sourceFacts` ไว้ตามเดิม ใช้ `selectedCoordinate` เฉพาะ supplemental path ซึ่งต้องมี 9 fields ได้แก่ `kind`, `longitude`, `latitude`, `sourceURL`, `checkedOn`, `decision`, `pointRole`, `sourcePoint`, `reason` ตรวจชนิดและบทบาทที่รองรับ พิกัด finite ในขอบเขต วันที่จริง URL HTTPS สาธารณะ และ `sourcePoint` ที่ตรง raw point จึงใช้ได้; clone/freeze หลักฐานก่อนกำหนด `row.lon/lat` และบทบาทจุดที่เลือก UI ระบุ “พิกัด Maps ที่ผู้ใช้ยืนยัน” พร้อม URL และวันที่ ไม่ใช้ nearest หรือ snap และไม่อ้างเป็นพิกัดรังวัด ดู `data/parc-coordinate-correction.json`

## 8. สถานะสาขา

ทุกแถวคือ `not_revalidated`: อยู่ในรายการสาขาของแบรนด์ที่ CityMETER ใช้ แต่ยังไม่มีการตรวจเปิด–ปิดทีละสาขา (`08_STATUS_AUDIT` individually_checked_rows = 0) แท็บ `09_CLOSED_STORES` ว่างโดยตั้งใจ — ไม่ได้แปลว่าไม่มีร้านปิด หน้าเว็บจึงเขียนสถานะว่า “อยู่ในรายการสาขาของแบรนด์” ไม่เขียนว่า “เปิดอยู่” 4 แถวที่ชื่อมีคำว่าออนไลน์ถูกติดธงไว้ ไม่ถูกลบ 178 แถวไม่มีที่อยู่จากแหล่งข้อมูล (แสดงว่าไม่ระบุที่อยู่)

ค่าพื้นที่ขายและผู้ใช้บริการต่อวันในชีตเป็นค่าเดียวกันทั้งประเภทร้าน (ค่าตั้งต้นของ production) ไม่ใช่ค่าที่วัดจริง จึงไม่แสดงเป็นข้อเท็จจริงรายสาขา (เก็บใน `meta.estimates` เพื่อการอ้างอิง)

## 9. สถานที่ (venue) ตามชื่อสาขา

API ไม่มีฟิลด์ venue ทะเบียนฐานจึงรวบรวม **3,437 venue candidates จากชื่อสาขา 3,493 แถว** (`store_name`; รอบนี้ไม่มีคู่ที่มาจากที่อยู่) จากนั้น `scripts/reconcile-venue-names.py` ทบทวนคู่เดิมและตรวจชื่อกับทะเบียน Shopping Center สาธารณะ 10,869 รายการ ซึ่งตรวจซ้ำ 22 ต.ค. 2569 ว่า hash ตรงกับ snapshot เดิม ทะเบียนดังกล่าวมีทั้งตลาด ศูนย์การค้า และตัวร้าน จึงกรองชื่อเชนร้านค้าและชื่อถนน/พื้นที่ที่ไม่มีตัวตน venue เฉพาะออกก่อน

การเพิ่มคู่ต้องพบชื่อ venue หรือ alias ที่ระบุชัดในชื่อสาขา รักษาขอบคำอังกฤษเพื่อไม่ให้ `Park Village` กลายเป็น `K Village` และต้องมีจุดอ้างอิงจาก detail ของ venue ในจังหวัดเดียวกัน: คู่เพิ่มประเภทตลาดต้องห่างไม่เกิน 300 ม. ส่วนสถานที่มีชื่อเฉพาะประเภทอื่นไม่เกิน 750 ม. ระยะนี้ใช้คัดความขัดแย้งของชื่อ ไม่จับสถานที่ใกล้ที่สุดเมื่อชื่อไม่ตรง และไม่ใช้เป็นหลักฐานว่าร้านอยู่ภายในสถานที่ คงรหัสสาขาสถานีบริการแต่ละแห่งแยกกัน แม้ใช้โลโก้ผู้ให้บริการเดียวกัน

ผลทบทวนเพิ่ม **141 คู่** ยกเลิก **9 คู่เดิมที่ขัดแย้งหรือยังแยกสาขาไม่ได้** และเปลี่ยนตัวตน venue เดิม 1 คู่ที่มีหลักฐานตรงกัน จึงเหลือ **3,625 แถวที่เชื่อมชื่อสถานที่** เพิ่มตัวตนจาก source venue UUID อีก 105 รายการ (ตลาด 96 + สถานที่อื่น 9) รวมทะเบียน 3,542 รายการโดยคงรหัสและตำแหน่งของทะเบียนฐานไว้ ชื่อปัจจุบัน **ALLY Village Amorini** และ **ALLY Village Ramkamhaeng** ใช้ตามที่เจ้าของยืนยัน พร้อมเก็บชื่อเดิมเป็น alias; ไม่เปลี่ยน The Crystal Park, Seacon ที่ไม่ระบุสาขา หรือ Grand Canal Nonthaburi เป็นสถานที่อื่นโดยอัตโนมัติ

| หลักฐานชื่อสถานที่ | สถานะ | จำนวนแถว |
|---|---|---:|
| ชื่อเดิมระบุสถานที่ แต่ยังไม่มีจุดอ้างอิงอิสระ | `name_candidate_no_anchor` | 3,374 |
| ชื่อ/alias ตรงและผ่านเกณฑ์จุดอ้างอิงในจังหวัดเดียวกัน | `name_and_source_point` | 248 |
| คงชื่อเดิมไว้ แต่ตำแหน่งขัดกับจุดอ้างอิง ต้องทบทวน | `name_match_coordinate_review` | 3 |
| คู่เดิมถูกยกเลิกหรือแยกสาขาไม่ได้ | `rejected_prior_name_match` / `ambiguous_venue_branch` | 9 |
| ไม่พบชื่อที่จับคู่ได้ | `not_matched` | 23,973 |

หลักฐานตรวจย้อนกลับอยู่ใน `data/venue-joins.json` รวมคู่เพิ่ม คู่ยกเลิก ระยะ และ crosswalk ระหว่าง research venue id กับ source venue UUID การเชื่อมชื่อไม่ยืนยันผู้เช่า การเปิดร้าน ชั้น หรือพิกัดหน้าร้าน และไม่ย้ายพิกัดสาขาหรือเปลี่ยนสมาชิกพื้นที่ทางปกครอง การซ้อนทับกับรูปทรงอาคารเป็นการตรวจอีกขั้นตามข้อ 11 หน้าเว็บจึงเรียกว่า “สถานที่ตามชื่อสาขา” โลโก้ใช้กับ 2,547 ตัวตน โดยแยกบทบาทโลโก้สถานที่ออกจากแบนเนอร์ผู้ให้บริการที่ใช้ร่วมกัน

## 10. โลโก้

- **แบรนด์ 17** — `scripts/build-brand-logos.py` ฝังไบต์ต้นฉบับใน `assets/brand-logos.svg` (symbol 320×160) ตัดขอบว่างโปร่งใส/สีกลางรอบนอกเท่านั้น พื้นรองรับ: โปร่งใส → `#E5E9E6`, งานสีอ่อน → `#182327`, ไฟล์ทึบ → สีของไฟล์เอง ทุกไฟล์ตรวจ sha256 กับชีต (test ตรวจ data URI ในสไปรต์ทีละไฟล์) โลโก้ Big C มาจาก Wikimedia Commons ตามที่เจ้าของอนุญาต (หน้า Commons ระบุ PD-textlogo แต่ structured data ระบุ CC BY-SA 4.0 — คงที่มาและตรวจต่อก่อนอ้างสิทธิ์ใด)
- **โลโก้รูปแบบ Foodplace 1** — เก็บแยกใน `data/brand-logos.json → formats` ด้วย key `BIG_C_FOODPLACE` และ parent `BIG_C` ใช้กับสาขาใน format family ที่ระบุชัด รวม Bangkok Marché ที่ One Bangkok ทะเบียนแบรนด์ยังมี 17 รายการ สไปรต์มี artwork เพิ่มสำหรับรูปแบบร้าน โดยเก็บ hash ของ SVG สำหรับเว็บแยกจาก PDF ต้นทาง ไม่สร้างแบรนด์หรือสาขาเพิ่มจากจำนวนไฟล์โลโก้
- **สถานที่ 131 ไฟล์** — เก็บ 128 ไฟล์เดิมและเพิ่ม The Old Siam Plaza, PARC Bangna และ The Pantip at Ngamwongwan อีก 3 ไฟล์ ตรวจ hash และบทบาทแหล่งแยกกัน Mahatun Plaza ยังไม่มีโลโก้ที่ยืนยัน ทะเบียนไฟล์อยู่ใน `data/venue-logos.json` ส่วนการผูก venue → โลโก้อยู่ใน `data/grocery.json` และ venue overlay ใน `data/retail-refresh.json`; ไม่เปลี่ยน 128 records เดิม
- การรวบรวมไฟล์ไม่ใช่ใบอนุญาตเครื่องหมายการค้า โลโก้ใช้เพื่อช่วยจำตัวตนในเดโมที่เจ้าของขอเท่านั้น

## 11. ขอบเขตพื้นที่บนแผนที่

ขอบเขตเต็มจาก `grocery/info/geom` ถูกย่อด้วย mapshaper (`-simplify weighted keep-shapes`): จังหวัดเก็บ 1.5% ทศนิยม 4 ตำแหน่ง (`data/geo/provinces.json` 0.57 MB), อำเภอและ อปท./ย่านเก็บ 3% ทศนิยม 5 ตำแหน่ง แยกไฟล์ต่อจังหวัด (`data/geo/p/<รหัส>.json` 35–720 KB โหลดเมื่อเปิดจังหวัด) จุดวางป้ายใช้ polylabel ของส่วนที่ใหญ่ที่สุด ขอบเขตที่ย่อเป็นภาพเพื่ออ่าน ไม่ใช่ข้อมูลทางปกครอง สมาชิกของ 27,607 แถวเดิมคงใช้ production membership ส่วน 5 สาขาเพิ่มใช้ผลเชื่อม polygon เพื่อจัดแสดงที่บันทึกวิธีตรวจและข้อจำกัดแยกไว้ตามข้อ 13

### จุดสาขากับอาคาร

`scripts/combine-spatial-sources.py` รวมผลทดสอบพิกัดเดิมจาก snapshot สามชุดโดยไม่เรียกเครือข่าย: CityMETER V4 → footprint อ้างอิงที่ตรวจแล้ว → Microsoft ใช้ Polygon/MultiPolygon เดิมพร้อม holes ไม่ปรับมุม ไม่ทำ buffer และไม่ snap ส่วนอาคาร 3D ไม่ถูกนับเป็น host ซ้ำ

ลำดับนี้เลือก **ผลที่พบรูปทรงครอบจุด** ของ CityMETER ก่อน รวมกรณีชิดขอบหรือกำกวม ถ้าชุดแรกไม่มีรูปทรงครอบหรือไม่มี query จึงพิจารณารูปทรงอ้างอิงที่ตรวจแล้ว และสุดท้ายใช้ Microsoft เติมช่องว่าง ไม่ยกระดับผลชิดขอบ/กำกวมของแหล่งก่อนหน้าด้วยผลอยู่ลึกภายในของแหล่งถัดไป ไม่รวมรูปทรงซ้อนจากผู้ให้บริการต่างกันให้กลายเป็นหลาย host เก็บ `decisionSource`, สถานะของแต่ละแหล่ง และข้อขัดแย้งไว้ตรวจย้อนกลับ แหล่งที่ใช้ตัดสินหลังเลือกจุด PARC ตามเจ้าของ: CityMETER 3,059 แถว (315 ภายใน + 2,744 ชิดขอบ), อ้างอิง 12 แถว (10 + 2), Microsoft 24,541 แถว (483 ภายใน + 6,584 ชิดขอบ + 17,474 ไม่พบรูปทรงครอบ) ผลเดิม 27,607 แถวและสาขาเพิ่มอีก 4 จุดไม่เปลี่ยน PARC ใช้ผล native ชิดขอบ ส่วน Microsoft เป็นการตรวจเทียบจากสอง raw partitions ที่ตรึงรุ่นและสแกนครบใหม่ที่จุดเลือก ไม่ใช้ candidate ของจุดเดิมอ้างว่าครอบคลุมจุดใหม่

กติกาใช้ระยะจากจุดถึงขอบนอกและขอบ holes อย่างน้อย 10 ม. เป็น **เกณฑ์กันกรณีชิดขอบเพื่อทบทวน** ไม่ใช่ค่าความแม่นที่ตรวจสอบแล้วหรือรัศมีความคลาดเคลื่อน การอยู่ภายในจึงหมายถึงเฉพาะพิกัดที่พล็อต ไม่ใช่หลักฐานว่าร้านเช่าอยู่ในอาคาร เปิดอยู่ หรือเป็นสมาชิกของ venue นั้น

| สถานะ | กติกา | จำนวนจุด |
|---|---|---:|
| `point_inside` | มีเพียงหนึ่ง host footprint ของแหล่งที่เลือกที่ครอบจุดและห่างขอบอย่างน้อย 10 ม. | 808 |
| `boundary_candidate` | มี host footprint ครอบจุด แต่ทุก host ห่างขอบน้อยกว่า 10 ม. (รวมจุดบนขอบ) | 9,330 |
| `ambiguous` | มีมากกว่าหนึ่ง host footprint ที่ครอบจุดและห่างขอบอย่างน้อย 10 ม. | 0 |
| `no_building` | ไม่พบรูปทรงครอบจุดใน snapshot ที่เลือก ไม่ใช่ข้อสรุปว่าไม่มีอาคารจริง | 17,474 |
| `unavailable` | แหล่งที่จำเป็นไม่พร้อมหรือยังไม่ได้ประมวลผล | 0 |

ผลรวมครบ 27,612 จุด แยกไฟล์ครบ 77 จังหวัด ได้แก่ 27,607 จุดเดิมและ 5 จุดจากทะเบียนสาขาเพิ่ม การประมวลผลครบทุกแถวไม่เท่ากับมีอาคารจริงครบทุกหลังในชุดข้อมูล จุดอาจคลาดเคลื่อนหรือแบบจำลองอาจตกหล่น/ตีขอบต่างกัน ไม่หาอาคารใกล้ที่สุด เก็บ raw points ทุกแถวและคงสมาชิกพื้นที่ของ 27,607 แถวเดิมตามต้นทาง; การเลือกจุด PARC ตามข้อ 7 เปลี่ยนผลเพียงหนึ่งแถวจากไม่พบรูปทรงครอบเป็นชิดขอบ กรุงเทพมหานครปัจจุบันมี 255 ภายใน / 2,114 ชิดขอบ / 2,945 ไม่พบ และ candidate features 2,310 รายการ โดยเพิ่ม component native เดิมหนึ่งรายการเข้าชุด candidate ไม่สร้างหรือแก้รูปทรงใหม่

`buildingId` ของ CityMETER (`cmv4-*`) และ Microsoft (`msml-20260813:*`) ระบุ geometry snapshot ไม่ใช่ UUID ของอาคารจริงหรือ OSM id ที่สร้างขึ้นเอง ส่วนอ้างอิงเดิมคง id (`ref:osm:...`); host context ที่เพิ่ม 25 ต.ค. ใช้ public namespace `followup:<originalHostId>` พร้อมเก็บ source/snapshot id เดิมแยกไว้ ไม่อ้าง prefix นี้เป็นรหัสจากผู้ให้บริการ ทุกชุดเก็บที่มา/hash และรูปทรงเดิม ไม่ใช่ขอบเขตกรรมสิทธิ์ ความสูงเป็น source/model context ไม่ใช่ผลรังวัด โดยเฉพาะ Microsoft ที่ `height=-1` หรือไม่มีค่า: `sourceHeightM=null`, `heightRole=unknown_display_fallback`; 3 ม. เป็นความสูงวาดภาพเท่านั้น แผงรายละเอียดระบุ “ยังไม่มีความสูงอาคาร” ส่วนค่าที่มีระบุว่าเป็นค่าประมาณ และลิงก์ไปหน้าชุดข้อมูลทางการแทนไฟล์ดาวน์โหลดขนาดใหญ่

การเชื่อมชื่อ venue ไปยังรูปทรงอ้างอิงและการที่จุดซ้อนกับรูปทรงเป็นหลักฐานคนละอย่าง ร้านอาจมีชื่อ venue ที่เชื่อมได้แต่จุดอยู่นอกรูปทรง หรือพิกัดของร้านอาจอยู่ภายในรูปทรงโดยยังไม่มีชื่อ venue ที่ยืนยัน การแสดงรูปทรงตามชื่อไม่เปลี่ยนสถานะการซ้อนทับของจุด และไม่ใช้ย้ายจุดเข้าอาคาร

### ที่มา สิทธิ์ข้อมูล และข้อกำหนดบริการอาคาร

รูปทรงอ้างอิงชุดเดิมมาจาก geometry เดิมของ OpenStreetMap และส่วนอาคารจาก CityMETER public API ที่ระบุเป็น OSM-derived คง URL และบทบาทของแต่ละ feature ส่วน Forum ที่เพิ่ม 25 ต.ค. เป็น OSM way เดิม และ PARC เป็น component จาก snapshot CityMETER V4 แบบผสมแหล่งตามย่อหน้าถัดไป ไม่อ้างว่า PARC เป็น OSM-derived เฉพาะหลัง ให้เครดิต `© OpenStreetMap contributors` พร้อมลิงก์ [ลิขสิทธิ์ของ OpenStreetMap](https://www.openstreetmap.org/copyright) และ [ใบอนุญาต ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/) ภาพพื้นหลังและอาคารบริบทที่โหลดแสดงจาก [OpenFreeMap](https://openfreemap.org/) คงเครดิต `OpenFreeMap · © OpenMapTiles · Data from OpenStreetMap` แยกจากชุดผลจับคู่ ไม่ใช้ภาพพื้นหลังยืนยัน host ของสาขา ใบอนุญาตซอฟต์แวร์ของผู้ให้บริการไม่ใช่ใบอนุญาตใหม่ของข้อมูล OSM หรือสิทธิ์เครื่องหมายการค้า

CityMETER V4 ผสม OSM, 3D-GloBFP (Che et al., [CC BY 4.0](https://doi.org/10.5281/zenodo.15459025)) และ Google Open Buildings (CC BY 4.0/ODbL) ค่าใน tile ไม่ระบุ upstream source ต่ออาคาร จึงคงเครดิตรวมและบทบาทความสูงแบบจำลอง/ค่าประเมิน ส่วน Microsoft release ที่ใช้ระบุ [CDLA Permissive 2.0](https://cdla.dev/permissive-2-0/) ไม่เปลี่ยนรอยอาคารจาก ML ให้เป็นผลสำรวจหรือหลักฐานชื่อ venue ดู provenance/สิทธิ์แยกชุดใน `data/building-sources.json`

การทดลองจับคู่จาก OpenFreeMap ทั่วประเทศเป็น **งานวิเคราะห์ที่ยังไม่เผยแพร่และไม่รวมในข้อมูลเว็บนี้** เก็บผลและ receipt ไว้นอก public repo หลังตรวจพบว่า batch 8,563 tiles เกิน [ข้อกำหนดบริการ](https://openfreemap.org/tos/) ฉบับ 14 ต.ค. 2569 ซึ่งระบุขอบเขต offline download ไม่เกิน 3,000 tiles หรือ 50 MB, พร้อมกันไม่เกิน 4 requests และต้องมี User-Agent ระบุแอป หยุดดึงเพิ่มเติมแล้ว สคริปต์การทดลองบังคับขอบเขตดังกล่าว และ batch ใหญ่กว่านั้นต้องมี receipt การอนุญาตจากผู้ให้บริการอย่างชัดเจน ไม่แบ่ง batch ย่อยเพื่อเลี่ยงข้อกำหนด

## 12. ไฟล์ในเว็บ

| ไฟล์ | เนื้อหา | ขนาด |
|---|---|---|
| `data/grocery.json` | 27,607 แถว (key 8 หลักแรกของ uuid, แบรนด์, ประเภท, รูปแบบ, ชื่อ, lon, lat, อำเภอ, อปท., แหล่งข้อมูล, สถานที่, ธง), 17 แบรนด์, 19 แหล่งข้อมูล; ทะเบียนสถานที่ฐาน 3,437 รายการก่อน overlay เพิ่ม 105 รวม 3,542 รายการ | 3.5 MB (gzip ~0.8 MB) |
| `data/areas.json` | ประเทศ 77 จังหวัด 928 อำเภอ 8,621 อปท./ย่าน: รหัส ชื่อ ประชากร พื้นที่ ยอด API กรอบ จุดป้าย | 0.8 MB |
| `data/retail-refresh.json` | รูปแบบ Foodplace ของ 12 แถวเดิม, สาขาเพิ่ม 5 แถวพร้อมพิกัด/ที่มา/สมาชิกพื้นที่, venue เพิ่ม 4 ตัวตน และโลโก้รูปแบบภายใต้ Big C; ไม่เขียนทับ snapshot เดิม | — |
| `data/detail/<จังหวัด>.json` | uuid เต็ม ที่อยู่ ตำบล และ tile ต้นทางของทุกสาขา (โหลดเมื่อเปิดรายละเอียด) | รวม 6.9 MB |
| `data/build-receipt.json` | hash ของ input, fetch log, ผลกระทบยอดทุกระดับ, การตั้งค่าการย่อขอบเขต | — |
| `data/venue-joins.json` | หลักฐานการทบทวนชื่อ: 3,625 คู่ที่คงใช้, 9 คู่ยกเลิก, crosswalk ของ 105 ตัวตนเพิ่ม และจุดอ้างอิงที่ผ่านเกณฑ์ 187 venue | 1.2 MB |
| `data/venue-reference-buildings.json` | รูปทรงอ้างอิง 31 venue / 78 features: ชุดเดิม 29 host + 47 parts และ host context เพิ่ม 2 รูปทรงที่กำหนดสาขาไว้เฉพาะ พร้อม source URL/height role/สิทธิ์ตามแหล่ง | — |
| `data/retail-followup-evidence.json` | หลักฐานทบทวน Mini เยาวราช สถานะ Mahatun และ host context เฉพาะ Forum/PARC; ไม่แทนผล point join | — |
| `data/parc-coordinate-correction.json` | คำยืนยันเลือกจุด PARC, raw observation/ผลเดิม, selected decision, การตรวจ native/Microsoft และสมาชิกพื้นที่ซ้ำ | — |
| `data/building-sources.json` | แหล่งอาคาร รุ่น ที่มา สิทธิ์ บทบาทความสูงและข้อจำกัดของแต่ละชุด | — |
| `data/spatial-joins.json` | ผลรวมและลำดับแหล่งของ point join, hash ของ input, decision sources และ index/hash/สถานะรายจังหวัด | — |
| `data/spatial/<จังหวัด>.json` | สถานะครบทุกจุดในจังหวัด และรูปทรงเดิมเฉพาะ candidate ของแหล่งที่เลือก พร้อม provenance และสถานะเทียบแหล่งอื่น | รวม 38.6 MB |

## 13. Foodplace — ปรับปรุง 25 ต.ค. 2569

`data/retail-refresh.json` เป็นทะเบียนปรับปรุงแยกจาก `data/grocery.json` และ `data/areas.json` เดิม Foodplace เป็น **รูปแบบร้านใน Big C** ตามที่เจ้าของเลือก ไม่เพิ่ม brand ID ในทะเบียน 17 แบรนด์ ตัวกรอง `format=BIG_C_FOODPLACE` รวมรูปแบบ Foodplace ปกติกับ **Foodplace Bangkok Marché ที่ One Bangkok** เป็น family เดียว โดยยังแสดงชื่อและรูปแบบย่อย Bangkok Marché และนับจุดนี้ครั้งเดียว

family นี้มี 17 แถว: 9 แถวที่ต้นทางระบุ Foodplace อยู่แล้ว, 3 แถวที่มีหลักฐานรูปแบบใหม่แต่ต้นทางเดิมระบุ Big C Market (ประชาอุทิศ สวนหลวง สายไหม), และ 5 สาขาเพิ่มจากทำเนียบสาขาของ Big C (The Old Siam Plaza, Mahatun Plaza, PARC Bangna, Pantip Ngamwongwan และ One Bangkok) การทบทวนรูปแบบ 12 แถวเดิมไม่เพิ่มจำนวนสาขา Big C มี 1,744 แถวใน snapshot ต้นทาง และสาขาเพิ่ม 5 แถว รวมแสดง 1,749 แถวในแบรนด์เดียว

ตัวปรับโมเดลเก็บ `sourceFacts`, `sourceRows` และ `sourceMeta` ก่อนใช้ค่าจัดแสดง รักษา ID, brand index, ชื่อ พิกัด source index ประเภท ธง และสมาชิกพื้นที่ของ 27,607 แถวเดิม รูปแบบเดิมของ 3 แถว Big C Market ตรวจย้อนกลับได้ ไม่เปลี่ยนยอด aggregate หรือ vintage ของ CityMETER เพื่อให้ตรงกับสาขาเพิ่ม รายการ Mini เยาวราชตรวจตรงกับรหัสสาขาทางการ 22301 และ canonical outlet `9f9a188a-6a85-4adc-8900-aaea7b49f310` (map key `9f9a188a`) จึงคง Big C Mini / `C_STORE` ไม่เพิ่มแถวและไม่รวมใน Foodplace; Pure และ 7-Eleven ที่ One Bangkok ยังคงตัวตนเดิม การอยู่ในย่านหรือโครงการเดียวกันไม่ทำให้เป็นสาขาซ้ำของ Foodplace

สาขาเพิ่มใช้ key แบบ `bigc-official-<รหัสสาขา>` และเก็บ branch UUID แยกจาก contact-record UUID raw พิกัดเป็นค่าที่ทำเนียบสาขาส่งคืน เก็บทศนิยมเดิมและบทบาทจุดจากแบรนด์โดยไม่เขียนทับ; PARC มี selected point จาก Maps ที่เจ้าของสั่งใช้แยกตามข้อ 7 ไม่ใช่จุดกลาง venue หรือจุด snap เข้าอาคาร แหล่งไม่ระบุความแม่นยำหน้าร้านหรือพิกัดทางเข้า ที่อยู่ PARC ที่แหล่งเว้นว่างยังเป็น null สมาชิกพื้นที่ของสาขาเพิ่มมาจากการตรวจ polygon ที่มีอยู่และมีหลักฐานวิธีตรวจแยกจาก production overlay; เมื่อไม่พบหน่วยพื้นที่ที่ใช้ได้ให้คงว่าง ไม่เลือกหน่วยที่ใกล้ที่สุด

สถานะ `active`/`inactive` ในระบบติดต่อของ Big C เป็นสถานะรายการในระบบนั้น Mahatun มีค่า `inactive` จึงแสดง **รอยืนยันสถานะสาขา** พร้อมข้อเท็จจริงจากแหล่ง ไม่อนุมานวันปิดหรือปิดถาวร ประกาศ JobThai ของนายจ้างลงวันที่ 22 ต.ค. 2569 ระบุการรับสมัครหัวหน้าแคชเชียร์ประจำ Foodplace มหาทุนพลาซ่า เป็นหลักฐานกิจกรรมการรับสมัคร ไม่ใช่การตรวจว่าหน้าร้านเปิดให้บริการปัจจุบัน เวลาเปิดปัจจุบันยังไม่ยืนยันและโลโก้ Mahatun ยังไม่พบไฟล์ที่ยืนยันได้ สาขาอื่นเป็น source-listed และยังไม่ได้ตรวจการเปิดให้บริการที่หน้างาน หลักฐานระบุรูปแบบ Foodplace แยกจาก branch-type UUID ภายในที่ใช้ร่วมกับ Big C Market

### ติดตามอาคารเฉพาะสาขา — 25 ต.ค. 2569

เพิ่ม named host context ที่ยืนยันตัวตนได้ 2 รูปทรงใน `data/venue-reference-buildings.json` รวมเป็น **31 venue / 78 features / 46 แถวที่มีหลักฐานเชื่อม** โดยคงชุดเดิม 29 / 76 / 44 ทุกแถว ทั้งสองรายการใช้ `appliesToStoreKeys` เพียงสาขาที่ระบุด้านล่าง ไม่ใช้ชื่อโครงการแจกอาคารให้ผู้เช่าทุกราย และไม่ใช้ named context เปลี่ยนผล point join เป็นอยู่ภายใน การเลือกพิกัด PARC ตามเจ้าของภายหลังเป็นอีก decision หนึ่ง ทำให้ผลปัจจุบันเป็น 808 ภายใน / 9,330 ชิดขอบ / 17,474 ไม่พบรูปทรงครอบ โดย raw พิกัดทางการของสาขาเพิ่มทั้งห้าคงทศนิยมจากแหล่งเดิม

- **Forum · One Bangkok — เฉพาะ `bigc-official-15068`:** ที่อยู่ทางการระบุ Forum Bldg., Room FG01, Floor G ประกอบข่าวและคู่มือ Forum ของ One Bangkok เพื่อแยกอาคารจริงออกจากทั้งโครงการ ใช้รูปทรงเดิม OSM way `1264152504` ไม่ใช่ landuse ของโครงการหรืออาคารข้างเคียง จุด BigC Bangkok Marche ใน OSM อยู่ในรูปทรง แต่ node กับ way มาจาก editor/changeset/เวลาเดียวกัน จึงเป็นคนละ records ไม่ใช่การสำรวจอิสระ พิกัดสาขาทางการยังอยู่นอกรูปทรง 135.418 ม. และคง `no_building` ไม่มีการนำอาคาร Forum ไปผูก Pure หรือ 7-Eleven ของ One Bangkok
- **อาคารจอดรถ · PARC Bangna — เฉพาะ `bigc-official-15073`:** เจ้าของระบุร้านอยู่ชั้น Ground ของอาคารจอดรถ 3 ชั้น พร้อมภาพหน้าร้านและลิงก์สถานที่ Big C Foodplace เฉพาะสาขา ใช้ component เดิมจาก cache CityMETER V4 วันที่ 22 ต.ค. (`cmv4-70aa2830ff1ea4b186f1a46b94fa`) เป็นรอยอาคารแบบจำลองที่เก็บลำดับจุดตามต้นทาง ไม่ใช่ UUID อาคารจากผู้ให้บริการหรือรูปสำรวจผนังจริง ในรุ่น `194c118` จุดทางการอยู่นอก 64.954 ม. และผลเป็น `no_building`; เก็บพิกัดและผลนั้นเป็น raw/historical observation ปัจจุบันเจ้าของสั่งเลือกจุด Maps `[100.6477432, 13.6671705]` จึงใช้ผล native `boundary_candidate` ห่างขอบ 4.851 ม. ไม่ผ่านเกณฑ์ภายในลึก 10 ม. การสแกน Microsoft ครบสอง partitions ซ้ำได้ผลชิดขอบ 4.601 ม. โดยไม่เปลี่ยนลำดับเลือก native และไม่รวมสองแหล่งเป็นหลาย host เขต 1047 และ Locale `7430567c-5878-422e-97ba-2dbad5f0d9fe` คงเดิม ที่อยู่ทางการ PARC ที่ว่างยังคง null ไม่เติมจาก Google และไม่รวมตึก AIA/landuse/อาคารอื่นใน PARC เข้ามา

รูปทรงทั้งสองเก็บพิกัดและ component เดิม ไม่ปรับขนาดให้เท่าพื้นที่จาก detail API ความสูงที่เลือกเป็น null และใช้ 3 ม. เพื่อวาดภาพเท่านั้น ค่าแบบจำลอง Forum 98.83 ม. / 35 ชั้น และ PARC 16.75 ม. / 4 ชั้นเก็บเป็นหลักฐานที่ยังไม่ยอมรับเพื่อแสดงเป็นความสูงอาคารจริง ข้อมูลเจ้าของ PARC 3 ชั้นไม่ถูกแปลงเป็น 9 ม. ภาพส่วนตัวของเจ้าของใช้ระบุตัวตนเท่านั้น ไม่อ้างสิทธิ์เผยแพร่ภาพนั้นในเว็บ รูปทรง Forum คง ODbL/OSM attribution ส่วน PARC คงเครดิตชุดผสม CityMETER V4 ตามข้อ 11 วันตรวจ 25 ต.ค. ไม่ใช่วันปรับปรุง geometry หรือตรวจวัดใหม่ ดูแหล่งและข้อจำกัดใน `data/retail-followup-evidence.json`

รอบนี้มี observations ของแถวเดิม 13 รายการ: Foodplace 12 แถวและ Mini เยาวราช 1 แถวที่คงรูปแบบเดิม จำนวน Foodplace จึงยังเป็น 17 จาก Foodplace เดิม 9 + Market ที่ปรับรูปแบบ 3 + สาขาเพิ่ม 5 ไม่ใช่ Foodplace เดิม 13 หรือเพิ่ม 6 สาขา

## 14. สร้างใหม่

```sh
python3 scripts/data/fetch-catalog.py <raw>          # base/grocery info lv0 + lv1
python3 scripts/data/fetch-geometry.py <raw>         # 9,626 ขอบเขต
python3 scripts/data/assemble-geo.py <raw> <geo>     # provinces/districts/ml .full.geojson
python3 scripts/data/assign-geometry.py <geo> <sheet-csv> <raw> <geo>/ml-assignment.json
python3 scripts/data/fetch-unit-membership.py <raw>  # overlay tiles ของพื้นที่ที่มีสาขา
node scripts/data/merge-unit-membership.mjs <raw> <geo>/unit-membership.json
python3 scripts/build-data.py --sheet <csv> --xlsx <master.xlsx> --raw <raw> --geo <geo> --membership <geo>/unit-membership.json --mapshaper <mapshaper>
python3 scripts/build-brand-logos.py <csv> <Grocery_Brand_Logos_Web>     # ต้องมี Playwright (Chromium)
python3 scripts/build-venue-logos.py <csv> <venue logo files>
node scripts/build-reference-spatial-joins.mjs       # ใช้รูปทรงอ้างอิงที่มีอยู่ ไม่เรียกเครือข่าย
npm test
```

`build-data.py` หยุดทำงานถ้า membership ไม่เท่ากับยอด API ของทุกพื้นที่, ถ้า key 8 หลักชนกัน หรือถ้า อปท. มีระดับบนอยู่นอกจังหวัดของตัวเอง

ผลอ้างอิงฐาน 29 venue (ก่อนเพิ่ม host context เฉพาะสองสาขาในข้อ 13) สร้างด้วย `scripts/build-reference-spatial-joins.mjs` เป็นหนึ่ง input จากนั้นรวม snapshot ที่มี receipt ครบด้วยคำสั่ง offline `python3 scripts/combine-spatial-sources.py --native-dir <native-snapshot-dir> --reference-dir <reference-snapshot-dir> --microsoft-dir <microsoft-snapshot-dir> --data-dir data` เก็บ raw snapshots ขนาดใหญ่ไว้นอก public repo สคริปต์ `scripts/build-spatial-joins.mjs` เป็นการทดลอง OpenFreeMap ที่ยังไม่เผยแพร่ตามข้อ 11 ไม่ใช้แทนสายการสร้างนี้
