Open-Source Cold Email & Warmup Platform
Warmbly 是开源的冷邮件平台:温暖您的邮箱、发送活动、并对每条回复进行分流。可在我们的云端运行,也可自行托管整套系统。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗เว็บไซต์เต็มรูปแบบของ PARALLAX (Agentic Lead Generation & CRM Platform) เนื้อหาทั้งหมดเป็นภาษาไทย ตามทะเบียนเสียงที่กำหนดไว้ใน Master Blueprint v2
Next.js 16 (App Router · Turbopack) · React 19 · Tailwind CSS 4 · TypeScript
npm run dev
เปิด http://localhost:3040 (พอร์ตกำหนดไว้ใน .claude/launch.json ของโฟลเดอร์แม่)
npm run build && npm start
| เส้นทาง | เนื้อหา | อ้างอิง blueprint |
|---|---|---|
/ | หน้าแรก — ตำแหน่ง เครื่องยนต์ บรีฟเช้า และหลักฐาน | §0, 3, 6–10 |
/problem | ปัญหา สถิติ 2569 แรงที่เปลี่ยนตลาด และตำแหน่ง | §1, 2, 3 |
/platform | สถาปัตยกรรมห้าชั้น สามแกน การกำกับ มาร์จิ้น | §6, 11, 12, 22 |
/platform/keep | Keep — ห้าขั้นตอนและกลุ่มที่ระบบคืนมา | §7 |
/platform/reach | Reach — ห้าขั้นตอนและท่าทีต่อข้อมูล | §8 |
/platform/proof | Proof — กติกาวัดตามขนาดกลุ่ม และ ROI Tracker | §10 |
/platform/learning | การเรียนรู้ข้ามลูกค้า — คูเมืองที่ซื้อทางลัดไม่ได้ | §9 |
/product | ระบบชื่อสินค้า บรีฟเช้า ฟีเจอร์ทั้งหมด การเริ่มใช้ | §4, 13 |
/demo | เดโมสาธารณะของบรีฟเช้า เปิดได้ทันทีโดยไม่ต้องเข้าสู่ระบบ | — |
/customers | สี่รูปทรงวงจร เกณฑ์คัด และวิธีเจาะตลาด | §5 |
/playbook | MST Golf — ต้นแบบที่ต่อข้อมูลแล้ว | §15 |
/pricing | สี่ชั้นราคาและสิ่งที่ตั้งใจไม่ทำ | §17 |
/company | ชื่อ แบรนด์ สองทะเบียนเสียง ทีม แผนเดินทาง ภูมิศาสตร์ | §4, 20, 22, 23, 24 |
/investors | GTM ขนาดตลาด เศรษฐศาสตร์ต่อบัญชี BMC ความเสี่ยง | §14, 16, 18, 19, 21, ภาคผนวก |
/trust | PDPA ความยินยอม เพดานการส่ง | §11 |
/contact | แบบฟอร์มและช่องทางอีเมล | — |
/console | ประตูทางเข้าของ webapp — จะถูกแทนที่ในเฟสถัดไป | — |
/sitemap.xml · /robots.txt · /opengraph-image · /icon.svg สร้างอัตโนมัติ
/console ถูกตั้ง noindex ไว้แล้ว
POST /api/contact ตรวจสอบข้อมูลครบถ้วน แต่ ยังไม่มีปลายทาง
ถ้าไม่ตั้งค่า CONTACT_WEBHOOK_URL ระบบจะเขียน log บนเซิร์ฟเวอร์อย่างเดียว
และผู้ส่งจะเห็นหน้าขอบคุณ — แปลว่า lead จะหายไป
# .env.local
CONTACT_WEBHOOK_URL="https://hooks.zapier.com/..." # หรือ Make / Slack / n8n / API ของคุณเอง
ANTHROPIC_API_KEY="sk-ant-..." # ไม่ตั้งก็ใช้ได้ แต่ AI จะถอยไปใช้สูตรสำเร็จ
ANTHROPIC_API_KEY ไม่ใช่ของบังคับ — ถ้าไม่ตั้ง Console ยังทำงานครบทุกหน้า
โดยสลับไปใช้สูตรที่ deterministic และติดป้ายบอกไว้ทุกจุดที่ใช้งาน
รายละเอียดอยู่ที่หัวข้อ AI ใช้ตรงไหน
payload ที่ส่งไปยัง webhook:
{
"name": "", "company": "", "email": "", "phone": "",
"cycle": "", "baseSize": "", "topic": "", "message": "",
"receivedAt": "ISO-8601"
}
ถ้า webhook ตอบไม่ใช่ 2xx ระบบจะคืน 502 และหน้าเว็บจะแนะนำให้อีเมลหา
[email protected] แทน
ทั้งหมดอยู่ใน app/globals.css เป็น token และ utility ไม่ใช่คลาสกระจัดกระจาย
ทิศทาง — light-first พื้นหลักคือขาวและฟ้าอ่อน ตัวหนังสือเป็น navy เข้ม พื้นเข้มใช้เป็น accent เท่านั้น (ปกติ 1–2 บล็อกต่อหน้า และ footer) พาเลตต์เย็นล้วน ไม่มีโทนอุ่นเลย
สี — ink / ink-2 / ink-3 / ink-4 (navy สำหรับตัวหนังสือ) ·
paper / paper-2 / line (พื้นสว่างและเส้นไรผม) ·
signal #0047ff · azure · sky · cyan (แกนน้ำเงินอิ่ม) ·
abyss #00113d (พื้น accent) · frost (ตัวหนังสือบนพื้น accent)
ตัวอักษร — sans ล้วน ไม่มี serif
IBM Plex Sans Thai (เนื้อหาและตัวเลข) · IBM Plex Mono (ป้ายกำกับ)
ใช้ผ่าน utility t-display t-h1 t-h2 t-h3 t-lead t-body t-small
t-label t-numeral (น้ำหนัก 300 + tabular figures) t-thai
สนามไล่สี (CI) — <Field variant="aurora" | "wash" | "deep" />
aurora คือพื้นสว่างสำหรับ hero — ขาวฝั่งที่วางตัวหนังสือ ไล่ไปฟ้าอิ่มและ navy
ที่มุมขวาล่าง · wash คือเวอร์ชันเบาสำหรับ section ทั่วไป · deep คือ accent เข้ม
.field__blob เป็นตัวครอบที่รับ animation ส่วน <i> ข้างในรับ gradient และ
blur แยกกันเพื่อให้ compositor ใช้ blur ที่ raster ไว้แล้วซ้ำ แทนที่จะเบลอ
ก้อนขนาดพันพิกเซลใหม่ทุกเฟรม ขนาดคิดเป็น vmax เพื่อให้สีน้ำเงินยังมีน้ำหนัก
บนจอมือถือ ทุก animation ปิดเองเมื่อผู้ใช้เปิด prefers-reduced-motion
เกรน — .grain--light ใช้ soft-light จึงเกาะเฉพาะบริเวณที่มีสี
และปล่อยพื้นขาวไว้สะอาด · .grain--dark ใช้ overlay บนพื้น accent
แถบตัวเลขแบบ Stripe — <StatBand items={[{value,label,sub}]} />
เส้นไรผมคั่นทุกคอลัมน์ ตัวเลขใหญ่น้ำหนักบาง วางบนพื้นขาวเสมอ ไม่วางทับ gradient
เลย์เอาต์ — shell (คอนเทนเนอร์) · band / band-sm (ระยะห่างแนวตั้ง) ·
surface-dark / surface-paper / surface-mist
ทุกตัวอยู่ใน components/reveal.tsx (client) และ app/globals.css
เข้ามาเมื่อเลื่อนถึงครั้งเดียว แล้วเลิกสังเกต — การเคลื่อนไหวที่วนซ้ำ
ทุกครั้งที่เลื่อนผ่านเป็นสิ่งกวนใจตั้งแต่ครั้งที่สอง
| ตัว | ใช้ตอน |
|---|---|
Reveal + dir | up · left · right · scale · fade — คอลัมน์ซ้ายเข้าจากซ้าย กราฟิกเข้าแบบ scale จึงรู้สึกเป็นการจัดวาง ไม่ใช่เอฟเฟกต์ที่แปะทับ |
Stagger | ลูกในกริดเข้าไล่กัน แทน delay={i * 70} ที่เคยเขียนมือกระจายสิบกว่าจุดด้วยค่าไม่ตรงกัน |
Numeral | ตัวเลขไล่ขึ้นเมื่อเลื่อนถึง อยู่ใน Stat/StatBand จึงได้ทั้งเว็บ · แยก prefix/suffix เอง (฿786,800 · 12.49% · 6×) และค่าที่ไล่อยู่ถูกซ่อนจากโปรแกรมอ่านหน้าจอ |
LineReveal | หัวเรื่องเข้าทีละบรรทัด แบ่งด้วย \n ที่ส่งเข้ามา ไม่ใช่เดาจากความกว้าง (ซึ่งจะแตกคนละที่ทุกขนาดจอ) |
ReadingProgress | แถบบางบนสุด · rAF + listener แบบ passive |
More | รายละเอียดที่พับไว้ — details/summary จริง จึงค้นด้วย ⌘F ได้ |
ตัวกันเนื้อหาหายถาวร — สถานะตั้งต้นของทุกตัวคือ opacity: 0
ถ้า IntersectionObserver ไม่เคยรายงาน ผลไม่ใช่ "ไม่มีอนิเมชัน" แต่คือหน้าว่าง
และเกิดขึ้นจริง: แท็บที่อยู่เบื้องหลังมี visibilityState = hidden
จน observer ไม่รายงานการตัดกัน (เจอตอนทดสอบ ทั้งหน้าค้างที่ opacity 0)
จึงตั้งเวลาปลดล็อกไว้ 1.5 วินาที และเพิ่ม @media print ที่บังคับให้
ทุกอย่างมองเห็นและคลี่ตัวพับออก — การเคลื่อนไหวเป็นของเสริม เนื้อหาไม่ใช่
ทุกตัวเคารพ prefers-reduced-motion ทั้งทาง CSS และทาง JS —
ตัวเลขที่ไล่ขึ้นต้องขึ้นค่าสุดท้ายทันที ไม่ใช่ไล่เร็วขึ้น
ข้อความบนเว็บถูกตัดลงราว 11% โดยเฉลี่ย และ 14–25% ในหน้าที่หนักที่สุด (หน้าแรก · ราคา · platform · investors) ด้วยสามวิธี เรียงตามลำดับที่ควรลอง:
Moreกติกาของข้อ 3: พับได้เมื่อบรรทัดที่ยังเห็นอยู่สมบูรณ์ในตัว คำถาม FAQ พับคำตอบได้ · ห้าชั้นของ platform พับได้เพราะมี one-liner อยู่แล้ว แต่การ์ดที่มีแค่หัวเรื่องพับไม่ได้ — ครั้งแรกที่ลองพับคำอธิบายการ์ดทุกใบ ได้กริดที่มีแต่ชื่อกับปุ่ม "เพราะ" ห้อยอยู่ ซึ่งอ่านเหมือนหน้าโหลดไม่เสร็จ จึงคืนคำอธิบายเหล่านั้นให้เห็น แล้วไปตัดความยาวมันแทน
components/ui.tsx รวม primitive ทั้งหมด — Field Mark Wordmark Label
Section PageHero StatBand Stat Quote Note Card Steps ArrowLink
NextUp CTA
| ไฟล์ | หน้าที่ |
|---|---|
convergence.tsx | กราฟิกลายเซ็น — เส้นสายตาสองชุดจากสองตำแหน่ง ทอดไปยังกลุ่มลูกค้าเดียวกัน จุดที่บรรจบคือจุดที่รู้ระยะจริง สุ่มแบบ deterministic (hashed sine) เพื่อให้ server กับ client วาดตรงกัน |
site-header.tsx | เมนูหลัก dropdown และ drawer มือถือ (client) |
site-footer.tsx | ท้ายเว็บ — บล็อก accent เข้ม |
nav.ts | โครงเมนู — แก้ที่เดียว มีผลทั้ง header |
reveal.tsx | fade-up ตอนเลื่อนถึง ผ่าน IntersectionObserver (client) |
brief-mock.tsx | ภาพจำลองบรีฟเช้า ใช้ซ้ำ 4 หน้า อ่านได้ทั้งบนพื้นสว่างและพื้น accent |
engine-loop.tsx | ไดอะแกรม Keep → Reach → Proof |
contact-form.tsx | ฟอร์มติดต่อพร้อม validation (client) |
/appแอปจริงที่ใช้งานได้ ไม่ใช่ภาพ mockup โทนเข้ม แยก token ออกจากเว็บการตลาด
ทั้งหมด (app/(app)/console.css) เว็บอยู่ใน route group (site)
Console อยู่ใน (app) จึงไม่มี header/footer ของเว็บมาปน
/console เดิม redirect มาที่ /app
| หน้า | ทำอะไร |
|---|---|
/app | บรีฟเช้า — สามอย่างที่ควรทำวันนี้ พร้อมปุ่มอนุมัติและโหมดซ้อม |
/app/plays | คลัง play 23 ตัว เปิดปิดรายตัว ปรับ guard ดูนิยามเป็น YAML |
/app/campaigns | แคมเปญทั้งหมด และหน้ารายละเอียดที่ตรวจ arm ซ้ำได้ |
/app/proof | ส่วนต่าง verdict สามค่า และต้นทุนต่อลูกค้าที่กลับมาซื้อซ้ำ |
/app/customers | ตาราง feature ที่คำนวณล่วงหน้า |
/app/import | ลากไฟล์ CSV จาก POS วาง แม็ปคอลัมน์ด้วย AI แล้วนำเข้า |
/app/settings | เพดานที่บังคับใช้ · สถานะ AI และแคช · เครื่องมือเดโม · บันทึกการใช้งาน |
งานหลักคือวงจรเดียว และตั้งใจให้จบใน สองคลิก
บรีฟเช้า ──คลิก "อนุมัติ — ส่งราว N คน"──▶ หน้าแคมเปญนั้น
(แถบ "เหลืออีกขั้นเดียว")
──คลิก "ส่งข้อความ — N คน"────▶ "ส่งแล้ว รออ่านผล"
+ วันที่จะอ่านผลได้
สิ่งที่แก้จากรอบก่อน เพราะ flow เดิมตันกลางทาง:
hash(customer_id + campaign_id) และ campaign_id ยังไม่เกิดจนกดอนุมัติ
(เคยเห็น 921 บนปุ่มแล้วส่งจริง 943)status เป็น measuring ตั้งแต่ตอนอนุมัติ ตอนนี้ขึ้น
"ยังไม่ได้ส่ง" พร้อมแถบสรุปด้านบนว่ามีกี่แคมเปญ กี่คนที่ยังไม่ได้รับ฿551,155 ต้องการ 108px จึงถูกตัดใช้ node:sqlite ที่มากับ Node 22+ จึงไม่มี native dependency ไฟล์อยู่ที่
.data/parallax.db (อยู่ใน .gitignore แล้ว) ทุกการเข้าถึงผ่าน
lib/db.ts เท่านั้น — เปลี่ยนไป Postgres ตอนขึ้น production แก้ไฟล์เดียว
schema ตรงตาม Play Engine §5 · §6 · §7
เปิด /app ครั้งแรกระบบจะ seed ทั้งสี่บัญชีและคำนวณ feature ให้เอง
(ใช้เวลาราว 4–5 วินาที ครั้งเดียว) ชุดข้อมูลสุ่มแบบ deterministic —
reseed แล้วได้ตัวเลขเดิมทุกครั้ง และ MST Golf ตรงกับตัวเลขใน Playbook
ทั้งหก: ฐาน 1,240 · เคยซื้อ 999 · ซื้อซ้ำ 707 · เข้ามาใน 30 วัน 248 ·
เงียบเกิน 90 วัน 585 · บนสุด 2% ทำรายได้ 41%
เลือกให้ครบทั้งสี่ "รูปทรงวงจรรายได้" เพราะข้อความหลักของ PARALLAX คือ engine ทำนายจากวงจร ไม่ใช่จากอุตสาหกรรม — ถ้าเดโมมีแต่ร้านค้าปลีก ข้อความนั้นพิสูจน์ไม่ได้ สลับบัญชีได้จากปุ่ม “สลับ” ในแถบซ้าย
| บัญชี | ธุรกิจ | วงจร | ฐาน | play ที่ใช้ได้ |
|---|---|---|---|---|
| MST Golf | ค้าปลีกอุปกรณ์กอล์ฟ | ตัดสินใจนาน | 1,240 | 19/23 |
| HONG MOVE | แท็กซี่ EV สนามบินหาดใหญ่ | ถึงรอบ | 3,180 | 16/23 |
| Bangpakong Riverside CC | สนามกอล์ฟ 18 หลุม | เติมของ | 2,460 | 19/23 |
| พรรคประชาธิปัตย์ | งานทะเบียนสมาชิกพรรค | หมดอายุ | 26,000 | 11/23 |
โปรไฟล์ทั้งหมดอยู่ใน lib/tenants.ts เป็น ข้อมูล เพิ่มบัญชีใหม่คือ
เพิ่มแถว ไม่ต้องแก้ตัวสร้างข้อมูล เส้นแบ่งเดียวกับคลัง play
play ถูกกรองด้วยรูปทรงวงจรของบัญชี ไม่ใช่รันทุกตัวให้ทุกคน — บริษัทแท็กซี่จึงไม่ได้รับข้อเสนอ “ล้างสต๊อกค้าง” และพรรคการเมืองไม่ได้รับ “ขายพ่วงหลังซื้อของหลัก” ตัวเลข posterior ที่สืบทอดข้ามบัญชีก็อ่านเฉพาะ วงจรเดียวกัน ไม่ใช่แถวไหนก็ได้
คำเรียกเปลี่ยนตามบัญชี — ลูกค้า · ผู้โดยสาร · นักกอล์ฟ · สมาชิก ทั้งในหน้าจอและใน prompt ที่ส่งให้ AI เขียนข้อความ ถ้าเขียน “ลูกค้า” ตายตัว ข้อความที่พรรคส่งหาสมาชิกจะอ่านเหมือนโปรโมชันร้านค้า
ขอบเขตของบัญชีพรรคการเมืองถูกจำกัดโดยเจตนา — เป็นงานธุรการสมาชิก เท่านั้น (ต่ออายุค่าบำรุง · สมาชิกที่ขาดต่อ · ชวนร่วมกิจกรรม · ดูแลผู้บริจาค) ไม่ใช่เครื่องมือหาเสียงหรือโน้มน้าวผู้ออกเสียง และไม่มี play ใดที่แบ่งกลุ่ม ตามความคิดเห็นทางการเมือง คอนโซลขึ้นเตือนไว้บนหน้าบรีฟว่าความคิดเห็น ทางการเมืองเป็นข้อมูลอ่อนไหวตาม PDPA มาตรา 26 บัญชีนี้จึงตั้งเพดานไว้ที่ สัปดาห์ละหนึ่งข้อความและห้ามส่วนลดทุกกรณี
| ไฟล์ | ขั้น |
|---|---|
lib/plays.ts | คลัง play เป็นข้อมูล 15 KEEP + 8 REACH |
lib/seed.ts | INGEST — ชุดข้อมูลตัวอย่าง |
lib/derive.ts | DERIVE — คำนวณ feature รายคนล่วงหน้า |
lib/match.ts | MATCH — รัน selector ทุก play แล้วจัดอันดับ ไม่มี LLM |
lib/dispatch.ts | DISPATCH — แช่แข็ง audience แบ่ง arm ส่งแบบ idempotent |
lib/proof.ts | PROOF — attribution T+7/30/90 · verdict · ROI 90 วัน |
lib/ai.ts | ชั้น LLM ทั้งหมด มีทางถอยเป็นสูตรสำเร็จเมื่อไม่มีคีย์ |
lib/ingest.ts | อ่าน CSV จริง · normalise เบอร์/วันที่/เงิน · เขียนเข้าฐาน |
lib/demo.ts | สร้างประวัติแคมเปญ · เดินเวลา · ใส่ผลของแคมเปญลงข้อมูลจริง |
lib/tenants.ts | ทะเบียนสี่บัญชีตัวอย่าง — โปรไฟล์เป็นข้อมูล |
lib/active-tenant.ts | บัญชีที่เปิดอยู่ (cookie) — ของเดโม ไม่ใช่การแยกผู้เช่า |
verify/ | ชุดตรวจ invariant รันด้วย npm run verify |
ต่อกับ Claude ผ่าน @anthropic-ai/sdk โมเดล claude-opus-5 ใช้ structured
output (output_config.format) ทุกจุด ตั้ง ANTHROPIC_API_KEY ใน .env.local
แล้วรีสตาร์ท ถ้าไม่ตั้ง ระบบยังทำงานครบทุกหน้าจอ โดยสลับไปใช้สูตรสำเร็จ
ที่ deterministic แทน และติดป้าย "ข้อความสำเร็จรูป" ให้เห็นตรงที่ใช้งาน
ผลลัพธ์ทุกครั้งถูกแคชด้วย hash ของ input ในตาราง ai_cache เรียกซ้ำไม่เสียเงินซ้ำ
ดูสถานะและล้างแคชได้ที่ /app/settings
| ใช้ AI | เพราะ |
|---|---|
| แม็ปคอลัมน์ CSV | หัวคอลัมน์ของ POS แต่ละยี่ห้อไม่เหมือนกัน เขียนกฎตายตัวไม่ได้ |
| เดาบทบาทสินค้า | ต้องรู้ว่าอะไรคือของหลัก อะไรคือของที่ซื้อตาม |
| เขียนข้อความสามโทน | เรียกครั้งเดียวต่อแคมเปญ ไม่ใช่ต่อคน (F3) — ตัวกำหนดมาร์จิ้น |
| สรุปบรีฟเช้า | เจ้าของร้านไม่ต้องรู้ว่า RFM คืออะไร |
| อธิบายว่าทำไมแนะนำ | ต้องตอบได้ทุกครั้งว่าทำไมส่งหาคนนี้ (D7) |
ห้ามใช้ AI: การเลือกคนเข้ากลุ่ม · การให้คะแนนและจัดอันดับ play · การคำนวณส่วนต่างและช่วงความเชื่อมั่น · การตัดสินส่งหรือไม่ส่ง · การบังคับ cooldown และเพดานความถี่ ทั้งหมดเป็นคณิตศาสตร์ล้วน เพราะต้องรันซ้ำได้ผลเดิม อธิบายได้ทุกบรรทัด และไม่มีต้นทุนต่อการรัน
npm run typecheck # tsc --noEmit
npm run verify # H1 แช่แข็งกลุ่ม · H2 hash arm · F12 idempotency · B8 ความยินยอม · verdict สามค่า
npm run verify:caps # เพดานความถี่ · ช่วงห้ามส่ง · ล็อกกลุ่มควบคุม · เครดิตไม่ติดลบ
npm run verify:tenants # ทั้งสี่บัญชี: ขนาดฐานตรงเป้า · play ตรงวงจร · ความยินยอม · ฐานไม่ปนกัน
npm run verify:pricing # เพดานแผน · รอบบิล · บัญชีเครดิตกระทบยอด · ตัวหาร ROI ครอบชุดเดียวกับตัวตั้ง
ทั้งสี่ชุดรันบนฐานข้อมูลจริง (ลบ .data/ ก่อนถ้าต้องการเริ่มใหม่)
และตรวจพฤติกรรมจริงไม่ใช่ mock — ชุดนี้เป็นตัวที่จับบั๊กความยินยอมรั่ว
เข้าไปใน audience ที่ export และเพดานความถี่ที่ไม่เคยทำงานได้จริง
verify:pricing มีหลักข้อเดียว: เพดานทุกข้อที่หน้า /pricing ประกาศ
ต้องมีบรรทัดที่พิสูจน์ว่าระบบปฏิเสธจริงเมื่อถูกละเมิด ไม่ใช่แค่มี
ฟังก์ชันที่คืนข้อความไว้ — เช่น เรียก approveCampaign บนแผน Free
แล้วต้อง throw และต้องไม่มีแถวแคมเปญหลุดลงฐาน
ถ้าสี่ตัวนี้ไม่ได้ทำตั้งแต่วันแรก อีกร้อยฟีเจอร์จะพังทีหลังพร้อมกัน ทั้งสี่อยู่ในระบบแล้วและมีชุดตรวจยืนยัน
(campaign_id, customer_id) — กดส่งซ้ำไม่เพิ่มข้อความhash(customer_id + campaign_id) — หน้ารายละเอียดแคมเปญคำนวณซ้ำให้ดูว่าตรงกันความยินยอมบังคับกับทุก play รวมรายชื่อตัดออก — spec §10 บอกว่าส่งออก
audience โดยไม่กรองความยินยอมผิด PDPA แต่ §3 ให้ R6 เลือกคนที่ถอนความยินยอม
สองข้อนี้ขัดกัน จึงตีความว่า paid_only = ยินยอมอยู่แต่ไม่มีช่องทางตรง
(ไม่มี LINE) และคนที่ถอนความยินยอมเป็น none ห้ามทั้งส่งและส่งออก
ผลข้างเคียงที่ยอมรับ: คนที่ถอนจะไม่อยู่ในรายชื่อตัดออก จึงอาจยังเห็นโฆษณา
แยกล็อกกลุ่มควบคุมออกจากล็อกหน้าต่างตอบสนอง — spec ให้กรอง "คนที่ติดอยู่ใน แคมเปญที่ยังไม่จบ" ออก แต่ถ้าล็อกยาวเท่าหน้าต่างวัดผล 90 วัน จะทักลูกค้าได้ ปีละสี่ครั้ง จึงล็อกกลุ่ม treated แค่ 14 วันแล้วปล่อยให้เพดานรายสัปดาห์คุม ส่วนกลุ่ม holdout ล็อกตลอดหน้าต่างวัดผล เพราะถ้าถูกแตะกลุ่มควบคุมจะปนเปื้อน และส่วนต่างที่วัดได้จะไม่มีความหมาย
ตัวชี้วัดที่ตัดสินคือสัดส่วนคนที่กลับมาซื้อ ไม่ใช่ยอดต่อหัว — เดิมทดสอบ ส่วนต่างของยอดต่อหัว ซึ่งในค้าปลีกมีส่วนเบี่ยงเบนสูงกว่าค่าเฉลี่ยหลายเท่า (คนส่วนใหญ่ซื้อศูนย์บาท ไม่กี่คนซื้อหลายหมื่น) ผลคือช่วงความเชื่อมั่น คร่อมศูนย์ทุกครั้งจนกลุ่มถึงระดับหลายแสนคน — verdict เป็น "ยังไม่พอสรุป" ตลอดกาลทุกขนาดกลุ่ม ซึ่งไม่ใช่ความระมัดระวังแต่คือเครื่องวัดที่วัดอะไรไม่ได้ ตอนนี้ทดสอบสัดส่วนซื้อ/ไม่ซื้อ (ความแปรปรวนถูกจำกัดด้วย p(1−p)) จึงสรุปได้ ที่กลุ่มระดับพันคน ส่วนตัวเลขบาทคือส่วนต่างสัดส่วน × ค่ากลาง (median) ของยอดต่อคนที่ซื้อ — ใช้ median ไม่ใช่ค่าเฉลี่ย เพราะลูกค้ารายใหญ่ไม่กี่คน ลากค่าเฉลี่ยขึ้นได้หลายเท่า และคนที่แคมเปญเปลี่ยนใจได้ไม่ใช่คนกลุ่มนั้น
โหมด pooled_90d_holdout รวมผลจริง — โหมดนี้มีอยู่เพราะกลุ่ม 300–1,000 คน
เล็กเกินกว่าจะสรุปรายแคมเปญ แต่เดิมโค้ดวัดแยกรายแคมเปญแล้วประทับ
"ยังไม่พอสรุป" ให้ทุกช่วงที่ไม่ใช่ T+90 คือสัญญากำลังทางสถิติที่ไม่เคยส่งมอบ
ตอนนี้ที่ T+90 รวมกลุ่มทดสอบและกลุ่มควบคุมของทุกแคมเปญโหมดนี้ที่ครบกำหนด
เข้าด้วยกันแล้วสรุปครั้งเดียว แถวที่แชร์ผลกันมีคำกำกับไว้บนหน้า Proof
หน้าต่าง ROI 90 วันนับจากวันที่ผลออก ไม่ใช่วันอนุมัติ — เดิมกรอง
approved_at >= now-90d แต่ verdict ที่ T+90 ต้องรอให้แคมเปญอายุครบ 90 วัน
สองเงื่อนไขแทบไม่มีจุดตัดกัน ตัวเลขเรือธงจึงว่างเปล่าโดยโครงสร้าง
และเลือก "ช่วงที่สุกที่สุดที่สรุปได้" ต่อแคมเปญ (90 → 30 → 7)
ไม่ทิ้งผล T+30 ที่ใช้ได้จริงเพราะยังไม่ครบ 90 วัน
ตัวเลขเรือธงต้องเฉลี่ยทุกแคมเปญที่วัดได้ ไม่ใช่เฉพาะที่ผ่านนัยสำคัญ —
เดิมเฉลี่ยเฉพาะแคมเปญที่ช่วงความเชื่อมั่นพ้นศูนย์ ซึ่งสูงเกินจริงอย่างเป็นระบบ
เพราะแคมเปญที่พ้นศูนย์คือแคมเปญที่ค่าประเมินสูงพอดี (winner's curse)
บั๊กนี้มองไม่เห็นตอนมีบัญชีเดียวเพราะทั้งสี่แคมเปญของ MST Golf สรุปได้หมด
พอเพิ่มบัญชีพรรคที่วงจรเป็นรายปีจึงเห็น: สี่แคมเปญวัดได้ +15% · +96% ·
−3% · +34% เฉลี่ยแบบเลือกเฉพาะที่ผ่าน = +65% เฉลี่ยทั้งสี่ = +35%
ตอนนี้เพิ่มคอลัมน์ matured แยก "วัดได้แล้ว" ออกจาก "สรุปได้"
และเฉลี่ยจากทุกแถวที่วัดได้
หน้าต่างวัดผลต้องยาวพอเทียบกับวงจรของกลุ่ม — วัดการกลับมาจ่ายซ้ำ ในหน้าต่างที่สั้นกว่าวงจรของคนกลุ่มนั้นมาก ๆ ไม่มีความหมาย อัตราฐานเข้าใกล้ ศูนย์ ทำให้ไม่กี่รายการกลายเป็นส่วนต่างหลายร้อยเปอร์เซ็นต์ ตัวอย่างจริง: บัญชีพรรค (วงจรราว 360 วัน) รายงาน T+7 ว่า "ได้ผล +342%" ช่วงความเชื่อมั่น 78–687% ผ่านนัยสำคัญตามสูตรจริงแต่ไร้ความหมาย ตอนนี้บังคับว่าหน้าต่าง ต้องยาวอย่างน้อยหนึ่งในสี่ของวงจรมัธยฐานของกลุ่มนั้นจึงจะสรุปได้
เครดิตข้อความห้ามติดลบ — sendCampaign หักเครดิตโดยไม่ตรวจยอดคงเหลือ
ร้านจึงส่งเกินที่จ่ายไว้ได้ไม่จำกัดและยอดกลายเป็นลบเงียบ ๆ (พบตอนเพิ่มบัญชี
ที่ฐานใหญ่กว่าเครดิตที่ให้มา: −538 และ −674) เพดานที่ไม่บังคับใช้ไม่ใช่เพดาน
ตอนนี้ส่งเท่าที่เครดิตพอแล้วรายงาน skippedNoCredit ออกมาตรง ๆ
posterior ต้องอ่านของวงจรเดียวกัน — LIMIT 1 โดยไม่ระบุ cycle_shape
ทำให้สนามกอล์ฟหยิบสถิติของร้านค้าปลีกมาใช้ ขัดกับข้อความที่ว่าเรียนรู้ข้ามร้าน
"ในวงจรเดียวกัน" — มองไม่เห็นตอนมีบัญชีเดียว
ตัวหารของ ROI รวมส่วนลดที่ให้ไปจริง — เดิมนับแต่ค่าส่งข้อความ ค่าส่ง LINE ราว 0.75 บาท แต่ส่วนลด 10% ของบิล 2,500 บาทคือ 250 บาท มากกว่าสามร้อยเท่า ถ้าไม่นับ "ต้นทุนต่อบาทที่เพิ่มขึ้น" จะดูดีเกินจริง จนไม่มีความหมาย และนั่นคือตัวเลขที่ลูกค้าใช้ตัดสินใจต่อสัญญา
lib/demo.ts สร้างประวัติแคมเปญสี่ตัวกระจายในปฏิทินย้อนหลังกว่าปี
โดยเดินผ่านเส้นทางเดิมทั้งหมด — แช่แข็งกลุ่ม แบ่ง arm ด้วย hash ส่งจริง
แล้วเลื่อนวันย้อนหลังและวัดผล ไม่มีการเขียนผลลัพธ์ลงไปตรง ๆ
ชุดข้อมูลตัวอย่างถูกสร้างก่อนที่จะมีแคมเปญ จึงไม่มีผลของแคมเปญอยู่ในนั้นเลย
ถ้าวัดตรง ๆ ส่วนต่างจะเป็นศูนย์โดยโครงสร้าง injectCampaignEffect จึงทำให้
โลกจำลอง "มีผลจริง" — แทรกธุรกรรมเพิ่มให้ 15% ของคนในกลุ่มที่ได้รับข้อความ
ที่ไม่มีการซื้อในหน้าต่างนั้นอยู่แล้ว (ตัดสินด้วย hash จึงทำซ้ำได้)
แล้วปล่อยให้ estimator ตัวจริงไปค้นหาผลนั้นเองผ่าน holdout ตามปกติ
ผลที่ออกมาราว +20% ถึง +40% พร้อมช่วงความเชื่อมั่นที่พ้นศูนย์มาไม่มาก บางครั้งได้ "ยังไม่พอสรุป" ซึ่งถูกต้อง ไม่ใช่บั๊ก — กลุ่มควบคุมรวมกัน มีแค่ราวสองร้อยคน ที่ขนาดนั้นผลขนาดกลางไม่ผ่าน 95% ไม่ว่าจะจริงหรือไม่ นี่เป็นข้อจำกัดของฐานตัวอย่าง 1,240 คน ไม่ใช่ของเครื่องวัด: ร้านจริงที่ส่ง สัปดาห์ละครั้งตลอดปีจะมีกลุ่มควบคุมสะสมหลายพันคน
ปุ่ม "เดินเวลาไปข้างหน้า" ที่ /app/settings เลื่อนวันอนุมัติย้อนหลัง
แล้ววัดผลใหม่ ใช้ตอนเดโมเพื่อให้เห็นว่าชั้น Proof ทำอะไรได้
ทุกอย่างที่เหลือเป็นของจริง — holdout · การแบ่ง arm · สูตรคำนวณ
lib/plans.ts เป็นข้อมูลล้วน ห้าม import ฐานข้อมูล — ทั้งหน้า /pricing
บนเว็บหลักและหน้า /app/billing ในคอนโซลอ่านจากไฟล์นี้ไฟล์เดียว
lib/billing.ts ต่อข้อมูลนั้นเข้ากับฐานข้อมูลและเป็นที่เดียวที่ตอบว่า
"แผนนี้ทำอะไรได้"
| แผน | ค่าสมาชิก/เดือน | คนที่ระบุตัวตนได้ | เครดิตแรกเข้า | Reach |
|---|---|---|---|---|
| Free | 0 ฿ | ถึง 500 | — | ✗ |
| Growth | 1,900 ฿ | ถึง 5,000 | 1,000 | ✓ |
| Multi | 5,900 ฿ | ถึง 25,000 | 3,000 | ✓ |
| Chain | 15,000 ฿ ขึ้นไป | ตกลงกัน | 10,000 | ✓ |
เครดิตข้อความแยกจากค่าสมาชิก จ่ายล่วงหน้า ไม่หมดอายุ —
1,000 ข้อความ 1,500 ฿ · 5,000 ข้อความ 6,500 ฿ · 20,000 ข้อความ 22,000 ฿
ต้นทุนจริงราว 0.75 ฿/ข้อความ อยู่ที่ MESSAGE_COST_BAHT ที่เดียว
รายปีจ่ายสิบเดือนใช้สิบสองเดือน
เพดานสองข้อที่บังคับใช้จริง — จำนวนคนตรวจใน commitImport
ก่อนแตะฐานข้อมูล (แทนที่ทั้งฐานนับจากศูนย์ ไม่ใช่บวกทับ ไม่งั้นร้านที่
ใกล้เพดานแก้ไขข้อมูลตัวเองไม่ได้) · Reach ตรวจใน approveCampaign
ไม่ใช่ที่หน้าจอ เพราะปุ่มที่ซ่อนไว้ไม่ได้ป้องกันการยิง Server Action ตรง ๆ
รอบบิลคำนวณจากปฏิทินกับ tenants.billing_day ทุกครั้ง ไม่เก็บสถานะ
"รอบปัจจุบัน" ในฐานข้อมูล — สถานะแบบนั้นต้องมีงานตามเวลามาเลื่อนให้
และถ้างานนั้นไม่ทำงานคืนหนึ่ง ยอดใช้งานจะค้างในรอบเก่าอย่างเงียบ ๆ
วันตัดรอบมาจากวันเปิดบัญชี บีบไว้ที่ 28 เพื่อไม่ให้ข้ามเดือนกุมภาพันธ์
credit_purchases บันทึกทุกครั้งที่ยอดเครดิตเพิ่ม จึงกระทบยอดได้:
คงเหลือ + ที่ส่งไปแล้ว = ที่ซื้อมาทั้งหมด ทุกบัญชี (verify:pricing
ตรวจข้อนี้) ยอดคงเหลือลอย ๆ ตอบคำถาม "จ่ายไปเท่าไร" ไม่ได้
ตารางราคาขัดกับตัวเองในหน้าเดียว — แถว "ข้อความรวมในแพ็ก 2,000" อยู่ห่างจากการ์ด "ไม่ซ่อนค่าข้อความในค่าสมาชิก" สองย่อหน้า และขัดกับ ต้นทุนจริง: 2,000 ข้อความมีต้นทุน 1,500 ฿ จากค่าสมาชิก 1,900 ฿ เหลือ 400 ฿ เป็นค่าซอฟต์แวร์ ร้านที่ส่งหนักที่สุดจะเป็นร้านที่ทำให้ ขาดทุนมากที่สุด — ซึ่งเป็นสิ่งที่หน้าเดียวกันบอกว่าจะไม่ทำ ตอนนี้ค่าสมาชิกเป็นค่าซอฟต์แวร์ล้วน เครดิตแยก ตรงกับกลไกที่โค้ดบังคับ อยู่แล้ว (จ่ายล่วงหน้าและหยุดเมื่อหมด ไม่ใช่เก็บส่วนเกินย้อนหลัง ซึ่งไม่มีระบบรองรับ)
เพดานที่ประกาศแต่ไม่มีใครอ่าน — tenants.tier มีอยู่ในฐานข้อมูล
ตั้งแต่ต้นแต่ไม่มีบรรทัดใดอ่าน และหน้าราคาประกาศเพดาน "ถึง 500 คน"
โดยไม่มีการตรวจที่ไหนเลย เพดานที่ไม่บังคับใช้ไม่ใช่เพดาน แต่เป็นคำโฆษณา
ขายสิ่งที่ยังไม่มีสามอย่าง — ตารางเดิมให้ Multi/Chain ได้ "Native API" และ "โหมดส่ง autopilot" ทั้งที่ทั้งสองไม่มีในระบบ และแบ่ง Proof เป็น "รวมผล 90 วัน" กับ "เต็มรูปแบบ" ตามชั้นราคา ทั้งที่โหมดวัดผลเลือกจาก ขนาดกลุ่ม ไม่ใช่จากแผน — การขายความแม่นทางสถิติเป็นชั้นราคาคือการ โกหกเรื่องวิธีการ ตอนนี้ Proof เท่ากันทุกแผนที่ส่งได้ ส่วน API และ autopilot มีคำว่า "กำลังพัฒนา" กำกับ และมีหัวข้อบนหน้าราคาที่บอกตรง ๆ ว่ายังไม่พร้อมและไม่คิดเงินสำหรับมัน
ตัวหารของ ROI ครอบแคมเปญคนละชุดกับตัวตั้ง — ความเห็นในโค้ดเขียนว่า
"ต้นทุนนับจากแคมเปญชุดเดียวกับที่เข้าสมการ" แต่ query ส่วนลดกรอง
horizon_days = 90 AND verdict != 'insufficient_data' ซึ่งแคบกว่าตัวตั้ง
ที่รวมทุกแคมเปญ matured = 1 ที่ขอบเขตสุกที่สุดของมัน (90 หรือ 30 หรือ 7)
แคมเปญที่วัดได้ที่ T+30 หรือวัดได้ที่ T+90 แต่คร่อมศูนย์ จึงเอายอดเพิ่ม
เข้าตัวตั้งโดยไม่เอาส่วนลดเข้าตัวหารเลย ผลคือหน้าค่าใช้จ่ายรายงานว่า
"จ่าย 1 บาท ได้กลับ 455 บาท" ซึ่งไม่มีใครควรเชื่อ หลังแก้ HONG MOVE
(ส่วนลด 15%) เปลี่ยนจากตัวเลขที่พองเป็น 1.1 ฿ ต่อ 1 ฿ ตอนนี้อ่านส่วนลด
จากแถวที่ picked เลือกไว้แล้ว จึงเป็นชุดเดียวกันโดยโครงสร้าง
และค่าส่งก็นับจาก measuredIds ไม่ใช่ทุกแคมเปญที่อยู่ในหน้าต่าง
นำเข้าแบบแทนที่ลบข้อมูลของทุกบัญชี — commitImport ลบตารางที่ไม่มี
คอลัมน์ tenant_id ด้วย DELETE FROM <table> เปล่า ๆ ร้านหนึ่งติ๊ก
"แทนที่ข้อมูลเดิม" แล้วธุรกรรม ความยินยอม แคมเปญ และผลการวัดของอีก
สามบัญชีหายไปด้วยโดยไม่มีข้อความใดบอก ตอนนี้ลบจากลูกไปหาแม่ผ่าน
subquery ของบัญชีนั้นเท่านั้น ตามแบบเดียวกับ wipeTenant
Server Action รับ tenantId จากฟอร์ม — ทุก action อ่าน tenantId
จาก FormData แล้วตรวจแค่ว่ามีบัญชีนั้นในทะเบียน แปลว่าใครก็ยิง POST
ตรงมาพร้อม tenantId ของบัญชีอื่นแล้วอนุมัติแคมเปญ · ส่งข้อความ ·
ล้างข้อมูลของบัญชีนั้นได้ ช่องนี้มองไม่เห็นจากหน้าจอเพราะทุกฟอร์มส่ง
ค่าที่ถูกอยู่แล้ว — แต่ hidden input ไม่ใช่การควบคุมสิทธิ์ ตอนนี้อ่าน
จาก cookie ฝั่งเซิร์ฟเวอร์ ค่าในฟอร์มใช้ตรวจว่าหน้าจอที่กดมากับบัญชี
เดียวกันจริงเท่านั้น ยังไม่ใช่ auth — cookie แก้ได้ แต่จุดที่ต้อง
เปลี่ยนตอนต่อ session จริงเหลือฟังก์ชันเดียว (currentTenant)
ใบแจ้งค่าใช้จ่ายมีรายการที่ไม่ได้เกิดในรอบนั้น — ตอนแรกลงวันที่
ยอดเครดิตของชุดข้อมูลตัวอย่างเป็น "สองวันก่อน" เพื่อให้หน้ามีตัวเลข
ให้ดู ได้หน้าจอที่ขัดกันเอง: ใบแจ้งเดือนนี้ขึ้นค่าเครดิต 55,000 ฿
ขณะที่ข้อความที่เครดิตนั้นจ่ายไปถูกส่งเมื่อปีที่แล้ว (ประวัติเดโม
เลื่อน sent_at ย้อนหลังพร้อม approved_at ตามที่ควรเป็น)
ตอนนี้ลงวันที่ตอนเปิดบัญชี และตารางค่าส่งรายแคมเปญแสดงรายการล่าสุด
พร้อมธงว่าอยู่ในรอบนี้ไหม แทนที่จะกรองเฉพาะในรอบแล้วว่างเปล่าตลอด
เรียงตามลำดับที่ต้องแก้ ไม่ใช่ตามความยาก
ไม่มีระบบล็อกอิน — บัญชีมาจาก cookie และผู้กระทำถูกบันทึกเป็น
owner ทั้งหมด ต้องมี session จริงและตรวจว่าผู้ใช้เข้าถึงบัญชีนี้ได้
แก้ที่ currentTenant ใน app/(app)/actions.ts และ
lib/active-tenant.ts เป็นข้อเดียวที่กันคนนอกไม่ได้เลยตอนนี้
การส่ง LINE ยังไม่ยิง API — ชั้นส่งบันทึกแถวครบ มีคีย์กันซ้ำ หักเครดิตถูก แต่ไม่มีใครได้รับข้อความจริง ต้องต่อ Messaging API พร้อม retry ที่ไม่ทำลาย idempotency และเก็บสถานะส่งไม่สำเร็จ
injectCampaignEffect ต้องถอดออก — เป็นของเดโมเท่านั้น
ถ้าเหลืออยู่ตอนต่อข้อมูลลูกค้าจริง จะเขียนธุรกรรมปลอมลงฐานของลูกค้า
CONTACT_WEBHOOK_URL ไม่ตั้ง = lead หาย — ผู้ส่งเห็นหน้าขอบคุณ
แต่ข้อมูลอยู่แค่ใน log ของเซิร์ฟเวอร์ และยังไม่มี rate limit
บน POST /api/contact
SQLite อยู่บนดิสก์ของเครื่องที่รัน — บน Vercel ระบบไฟล์อ่านได้
อย่างเดียวยกเว้น /tmp ซึ่งเป็นของ instance ใครของมัน lib/db.ts
จึงย้ายไป /tmp เองเมื่อเจอ process.env.VERCEL ผลคือคอนโซลที่นั่น
เป็น เดโมที่รีเซ็ตตัวเอง ไม่ใช่ฐานข้อมูล — วัดได้ว่าคอนเทนเนอร์เย็น
ใช้เวลา ~11 วินาที (seed 4 วินาที · ไฟล์ 88 MB) ส่วนที่อุ่นแล้ว 0.4–0.8 วินาที
ข้อจำกัดที่แท้จริงไม่ใช่ภาษา SQL แต่เป็น API แบบ synchronous:
มี .prepare() 113 จุดใน 18 ไฟล์ และไดรเวอร์ที่โฮสต์ได้ทุกตัวเป็น async
การย้ายจึงไม่ใช่แค่เปลี่ยน connection string แต่ต้องเปลี่ยนทุก call site
ให้เป็น async ซึ่งจะไล่ขึ้นไปถึง runMatch · deriveFeatures · roiSummary
ต้องทำก่อนมีลูกค้าจริงคนแรก และต้องมีการสำรองข้อมูล
สิทธิของเจ้าของข้อมูลตาม PDPA ยังไม่มีหน้าจอ — หน้าราคาเขียนว่า ขอไฟล์กลับและขอให้ลบได้ แต่ยังไม่มีปุ่มไหนทำสองอย่างนั้น ต้องมีทั้งระดับบัญชีและระดับบุคคล พร้อมบันทึกว่าใครขอเมื่อไร
ไม่มีการชำระเงิน — ปุ่มซื้อเครดิตและย้ายแผนเปลี่ยนฐานข้อมูลตรง ๆ ระบบจริงต้องผ่าน payment gateway และออกใบกำกับภาษี
ยังไม่มี observability — error.tsx มีแล้วทั้งสองฝั่ง และความล้มเหลว
ที่คาดไว้ถูกส่งกลับเป็นข้อความให้ผู้ใช้อ่านแล้ว (ดู lib/action-state.ts)
แต่ยังไม่มีใครเก็บ digest ไปไว้ที่ไหน — ยังไม่รู้เลยว่ามีอะไรพังบ้าง
รายการหลักอยู่ที่ เหลืออะไรก่อนเป็น 1.0 ด้านบน ที่เหลือเป็นข้อเล็ก: แคมเปญที่ยังไม่มีธุรกรรมในหน้าต่างเวลาเลย จะใช้ ค่าจำลองจาก hash ของแคมเปญแทนการวัดจากข้อมูลจริง (เกิดเฉพาะแคมเปญที่ เพิ่งอนุมัติ และหายไปเองเมื่อมีธุรกรรมเข้ามา)
ตัวเลขในเว็บทุกจุดที่ยังไม่ได้วัดจริง มีข้อความกำกับไว้ว่าเป็นชุดข้อมูลตัวอย่าง
หรือเป็นเป้า ไม่ใช่ผลลัพธ์ — ตามที่ blueprint กำหนด ก่อนขึ้นเวทีจริงต้องหา
แหล่งอ้างอิงหรือถอดตัวเลขในภาคผนวกของ /investors ออก