SKILL.md · Free

ตรวจ Facebook Ads

ตรวจค่าโฆษณา คลิก และยอดซื้อจากไฟล์รายงาน แยกข้อมูลที่ขาดก่อนสรุปสิ่งที่ควรตรวจต่อ

ดาวน์โหลด Skill

ต้องเตรียมอะไร

  1. รายงานระดับโฆษณาในช่วงเดียวกัน
  2. เป้าหมายและเงื่อนไขการนับยอด
  3. ยอดรวมจาก export สำหรับเทียบ

Skill ทำอะไรให้

  1. ตรวจขอบเขตและคอลัมน์
  2. คำนวณจากยอดรวม
  3. แยกข้อค้นพบจากข้อสันนิษฐาน
  4. ส่งรายการตรวจต่อ

ได้อะไรกลับมา

  1. ตารางตัวเลขรวมและรายโฆษณา
  2. รายการข้อมูลที่ขาด
  3. งานตรวจต่อพร้อมเหตุผล

ตัวอย่างข้อมูลและผลทดลอง

ทดลองกับข้อมูลสมมติของ KlangPrompt ไม่ใช่ผลงานลูกค้า

โจทย์ที่ใช้ทดลอง

โฆษณา A/B/C ใช้เงินรวม 2,000 บาท มี link click 400 ครั้ง แถว C ขาดยอดซื้อและมูลค่า ต้องตรวจโดยไม่เปลี่ยนแคมเปญ

ผลที่ได้จากการทำตามขั้นตอน

Link CTR รวม 0.8% / CPC 5 บาท / CPM 40 บาท ยังสรุป CPA และ ROAS รวมไม่ได้ เพราะ C ขาดข้อมูล B มี landing 30 จาก 150 คลิก ควรตรวจวิธีนับและหน้าเว็บก่อนสรุปสาเหตุ

ดูไฟล์ในชุดนี้

facebook-ads-quick-audit/
  examples/brief.txt
  examples/export.csv
  references/export.md
  scripts/audit_export.py
  SKILL.md
  LICENSE.txt
  CHANGELOG.md
  examples/result.md
examples/brief.txt
ข้อมูลสมมติสำหรับทดสอบ ไม่ใช่ผลแคมเปญลูกค้า
ตรวจ A/B/C ใน export.csv ช่วง1–7กันยายน2569 THB เป้าหมายยอดขาย attributionเป็นค่าตัวอย่างที่เจ้าของระบุเหมือนกัน
Control totals: spend2000 impressions50000 linkclicks400
ยังไม่ส่งเป้าหมายCPA ไม่มีหลักฐานเหตุconversionตก แถวCขาดlanding/purchase/revenue ห้ามเติมศูนย์
ขอรายงานรวมและสิ่งที่ต้องตรวจต่อ ไม่แก้แคมเปญ
examples/export.csv
ad_id,start,end,currency,objective,attribution,spend,impressions,link_clicks,landing_views,purchases,revenue
A,2026-09-01,2026-09-07,THB,sales,owner-export-window,1000,10000,200,120,4,2400
B,2026-09-01,2026-09-07,THB,sales,owner-export-window,600,30000,150,30,0,0
C,2026-09-01,2026-09-07,THB,sales,owner-export-window,400,10000,50,,,
references/export.md
# CSV ที่ตัวคำนวณอ่าน

หนึ่งแถวต่อ ad_id ต่อช่วงรายงาน ไม่มี breakdownซ้อน ไม่มี total row ลำดับคอลัมน์ต้องตรง:

`ad_id,start,end,currency,objective,attribution,spend,impressions,link_clicks,landing_views,purchases,revenue`

วันที่ YYYY-MM-DD ทั้งไฟล์ต้องใช้ช่วงเดียวกัน; currency/objective/attributionต้องเหมือนกัน ไม่ได้กำหนดattributionตายตัว ให้คัดค่าจากexportจริงและบอกconversioneventที่ใช้ในรายงาน

spend/revenueเป็นจำนวนไม่ติดลบ, impressions/link_clicksเป็นจำนวนเต็มไม่ติดลบ; landing_views/purchases/revenueเว้นว่างได้เมื่อไม่มีข้อมูล ห้ามเติม0แทนช่องว่าง purchaseอนุญาตทศนิยมตามตัวเลขreported ไม่ถือว่าเป็นจำนวนคนที่ไม่ซ้ำกัน

link_clicksคือlinkclickไม่ใช่allclick; landing_viewsต้องตรงนิยามที่exportใช้ หากไม่มีก็เว้นว่าง ราคาทั้งหมดตามสกุลเดียวกัน ไม่รวมหลายสกุลหรือรวมreachรายแถวเพื่ออ้างunique reach

Scriptไม่อ่าน Excelตรง ไม่ตรวจว่าแพลตฟอร์มยิงeventถูกจริง และไม่พิสูจน์ว่าโฆษณาเป็นสาเหตุของการซื้อ ต้องตรวจข้อมูลต้นทางเพิ่มเมื่อโจทย์ต้องการ
scripts/audit_export.py
"""Read-only ad-export math; Python 3.9+, no external dependency/network."""
import argparse
import csv
import datetime
import json
import sys
from decimal import Decimal, InvalidOperation

SCHEMAS = {
 'facebook': 'ad_id,start,end,currency,objective,attribution,spend,impressions,link_clicks,landing_views,purchases,revenue'.split(','),
 'google': 'row_id,start,end,currency,conversion_scope,ad_group,search_term,spend,impressions,clicks,conversions,conversion_value'.split(',')
}


def divide(numerator, denominator, factor=1):
    if numerator is None or denominator in (None, Decimal(0)):
        return None
    return float((numerator / denominator * factor).quantize(Decimal('0.000001')))


def audit(path, mode):
    schema = SCHEMAS[mode]
    metrics = schema[6:] if mode == 'facebook' else schema[7:]
    optional = {'landing_views','purchases','revenue'} if mode == 'facebook' else {'conversions','conversion_value'}
    identity = ('start','end','currency','objective','attribution') if mode == 'facebook' else ('start','end','currency','conversion_scope')
    rows, seen, groups, missing = [], set(), set(), []
    with open(path, encoding='utf-8-sig', newline='') as stream:
        reader = csv.DictReader(stream)
        if reader.fieldnames != schema:
            raise ValueError('columns must match documented schema')
        for line, row in enumerate(reader, 2):
            if None in row or any(v is None for v in row.values()):
                raise ValueError(f'line {line}: malformed row')
            row = {k:v.strip() for k,v in row.items()}
            if any(not row[k] for k in schema if k not in optional):
                raise ValueError(f'line {line}: required value missing')
            key = row[schema[0]]
            if key in seen:
                raise ValueError(f'line {line}: duplicate ID; resolve overlapping breakdowns')
            seen.add(key)
            if datetime.date.fromisoformat(row['start']) > datetime.date.fromisoformat(row['end']):
                raise ValueError('reversed period')
            groups.add(tuple(row[k] for k in identity))
            if len(groups) > 1:
                raise ValueError('mixed period/currency/objective/measurement; split export')
            values = {}
            for key in metrics:
                if row[key] == '' and key in optional:
                    values[key] = None
                    missing.append({'line':line,'id':row[schema[0]],'metric':key})
                    continue
                value = Decimal(row[key])
                if not value.is_finite() or value < 0 or value > Decimal('1e18'):
                    raise ValueError(f'line {line}: invalid number {key}')
                if key in ('impressions','link_clicks','clicks','landing_views') and value != value.to_integral_value():
                    raise ValueError(f'line {line}: count must be integer {key}')
                values[key] = value
            rows.append({'source':row,'values':values})
    if not rows:
        raise ValueError('no data rows')
    totals = {k:None if any(r['values'][k] is None for r in rows) else sum((r['values'][k] for r in rows),Decimal(0)) for k in metrics}

    def summarize(v):
        clicks = v['link_clicks' if mode == 'facebook' else 'clicks']
        conversions = v['purchases' if mode == 'facebook' else 'conversions']
        revenue = v['revenue' if mode == 'facebook' else 'conversion_value']
        out = {k:None if n is None else str(n) for k,n in v.items()}
        out.update(ctr_pct=divide(clicks,v['impressions'],100),cpc=divide(v['spend'],clicks),cpa=divide(v['spend'],conversions),roas=divide(revenue,v['spend']))
        if mode == 'facebook':
            out.update(cpm=divide(v['spend'],v['impressions'],1000),landing_per_link_click_pct=divide(v['landing_views'],clicks,100))
        else:
            out.update(conversion_rate_pct=divide(conversions,clicks,100))
        return out
    return {'mode':mode,'scope':dict(zip(identity,next(iter(groups)))), 'totals':summarize(totals),
       'rows':[{'id':r['source'][schema[0]],'source_line':i+2,**({'ad_group':r['source']['ad_group'],'search_term':r['source']['search_term']} if mode=='google' else {}),'metrics':summarize(r['values'])} for i,r in enumerate(rows)],
       'missing':missing,'limits':['Reported attribution, not causal proof','Null is missing or division by zero; never substitute zero']}


def main():
    if hasattr(sys.stdout, 'reconfigure'):
        sys.stdout.reconfigure(encoding='utf-8')
        sys.stderr.reconfigure(encoding='utf-8')
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('mode',choices=SCHEMAS)
    parser.add_argument('csv_file')
    args=parser.parse_args()
    try:
        result=audit(args.csv_file,args.mode)
    except (OSError,ValueError,InvalidOperation) as exc:
        print(json.dumps({'error':str(exc)},ensure_ascii=False),file=sys.stderr)
        return 2
    print(json.dumps(result,ensure_ascii=False,indent=2))
    return 0


if __name__ == '__main__':
    sys.exit(main())
SKILL.md
---
name: facebook-ads-quick-audit
description: ตรวจรายงาน Facebook Ads หนึ่งช่วงเวลา แยกปัญหาคลิก หน้าเว็บ และยอดซื้อ คำนวณจากยอดรวมและจัดลำดับสิ่งที่ต้องตรวจต่อ ไม่แก้แคมเปญหรือสรุปสาเหตุจาก CTR อย่างเดียว
license: KlangPrompt Skills License 1.0
---

# ตรวจ Facebook Ads

ส่งรายงานสั้นที่เจ้าของโฆษณาตัดสินใจได้ว่าควรตรวจอะไรต่อ ไม่ตัดสินแพ้ชนะด้วยค่าเฉลี่ยวงการที่ไม่มีหลักฐาน

## ตั้งขอบเขต

รับ export ระดับโฆษณาหนึ่งช่วงเวลา สกุลเงิน เป้าหมาย และนิยาม attribution ที่ใช้ รับ CPA/ROAS เป้าหมายเฉพาะเมื่อผู้ใช้มีจริง หากไม่มีให้รายงานค่า ไม่สร้างเกณฑ์เอง ห้ามนำชื่อ/อีเมลของลูกค้ามาใช้ในรายงานนี้

อ่าน [references/export.md](references/export.md) แล้วบันทึก mapping ของคอลัมน์เดิมเป็น CSV สำเนา ไม่แก้ต้นทาง ไม่รวมแถว total กับแถวย่อย หรือ breakdown ที่ทับซ้อน ถ้าไฟล์มีหลายช่วงเวลา/วัตถุประสงค์/attribution ให้แยกก่อน ไม่เฉลี่ยรวมโดยอัตโนมัติ

## ตรวจและคำนวณ

1. รัน `python scripts/audit_export.py facebook FILE.csv` ตัวคำนวณไม่เชื่อมบัญชีโฆษณาและไม่เปลี่ยนงบ
2. ตรวจ warnings ก่อนอ่านผล ช่อง purchases/revenue ว่างคือไม่มีข้อมูล ไม่ใช่ศูนย์ ถ้ามีแถวขาดข้อมูล ยอดรวม metricนั้นเป็น null เพื่อไม่ให้รายงานยอดรวมบางส่วนเป็นทั้งหมด
3. อ่าน link CTR, CPC, CPM, อัตราจาก link click ถึง landing page view และ CPA/ROAS จากยอดรวม ไม่ใช้ค่าเฉลี่ยเปอร์เซ็นต์แต่ละแถวเป็นยอดรวม
4. เทียบแถวภายในกลุ่มเดียวกัน ระบุฐาน impressions/clicks/purchases เสมอ ศูนย์ conversion ทำให้ CPA คำนวณไม่ได้ ไม่ใช่ CPA0 หรืออนันต์ที่นำไปจัดอันดับได้
5. แยกสิ่งที่รู้กับข้อเสนอให้ตรวจ: click→landingต่างกันมากให้ตรวจความเร็วหน้า/วิธีนับ/redirect ยังไม่ฟันธงว่าสาเหตุคือหน้าเว็บ; ซื้อไม่เกิดให้ตรวจ measurement/ช่วงรอattribution/ข้อเสนอ อย่าเหมาว่า hookไม่ดี

## ส่งงาน

ส่งตารางรวมและรายโฆษณา พร้อมช่วงเวลา/attribution/นิยาม linkclick; รายการขาดข้อมูล; งานตรวจต่อที่ผูกกับแถวต้นทาง สำหรับแต่ละงานระบุ “พบอะไร / ยังไม่รู้ / จะตรวจอย่างไร / ใครทำ” ถ้าไม่มีเจ้าของงานให้เว้นว่าไม่ระบุ ไม่ตั้งชื่อคนเอง

ถ้าผู้ใช้ถามเรื่องนโยบายหรือการปฏิเสธโฆษณา ให้อ่านเหตุปฏิเสธและเอกสาร Meta ล่าสุดก่อน ไม่ใช้ไฟล์นี้เป็นรายการคำต้องห้าม ไม่ทำนายยอดขายหรือสั่งปิดโฆษณาอัตโนมัติ

## ตรวจผล

เทียบยอด spend/impressions/linkclick กับ control total ใน export หากต่างให้หยุดข้อเสนอปรับงบ ตรวจการหารศูนย์และ missing; ทุกข้อสรุปต้องชี้แถว/metric ไม่ใช้ตัวอย่างสมมติเป็น benchmark ความสำเร็จ คงข้อจำกัด attribution และรายงานเฉพาะสิ่งที่ตรวจจริง
LICENSE.txt
KlangPrompt Skills License 1.0 — 2026-09-22

KlangPrompt เป็นผู้จัดทำไฟล์ในชุดนี้ ไม่มีไฟล์ Skill ของผู้อื่นนำมาขายต่อ

ผู้ดาวน์โหลดรายการฟรีหรือผู้ซื้อที่ได้รับสิทธิ์ ใช้และปรับไฟล์กับงานของตนและงานลูกค้าได้ เก็บสำเนาสำรองได้ ผลงานที่ทำด้วย Skill นำไปใช้เชิงพาณิชย์ได้ตามสิทธิ์ของข้อมูลและสื่อที่นำมาใช้

ห้ามเผยแพร่ต่อหรือขายต่อไฟล์ Skill หรือสำเนาที่แก้ไขเป็นสินค้าแยก การแบ่งปันภายในทีมต้องมีสิทธิ์จาก KlangPrompt ก่อน ข้อจำกัดนี้ไม่ใช้กับผลลัพธ์งานของผู้ใช้

ไฟล์ไม่รวมค่าใช้บริการ AI หรือเครื่องมือภายนอก และไม่รับรองว่า Agent จะทำงานถูกทุกครั้ง ผู้ใช้ต้องตรวจข้อเท็จจริง สิทธิ์ของเนื้อหา และผลลัพธ์ก่อนใช้จริง สิทธิตามกฎหมายที่ไม่อาจจำกัดได้ยังคงอยู่
CHANGELOG.md
# 1.0.0 — 2026-09-22

รุ่นแรก แยกขั้นตอน ขอบเขตงาน และการตรวจผล พร้อมข้อมูลสมมติสำหรับทดลอง
examples/result.md
# ผลทดลองตรวจ Facebook Ads

รันตัวคำนวณจริงใน Codex กับ exportสมมติของKlangPrompt ช่วง1–7กันยายน2569 THB ไม่ใช่ผลลูกค้า ไม่ทดสอบติดตั้งAgentอัตโนมัติ

spend2,000 / impressions50,000 / linkclick400 ตรงcontroltotalsทั้งหมด LinkCTR0.8%, CPC5บาท, CPM40บาท ค่าCTRไม่ใช่ค่าเฉลี่ย2%/0.5%/0.5%

| โฆษณา | Link CTR | CPC | landing/click | CPA | ROAS |
|---|---:|---:|---:|---:|---:|
| A | 2% | 5 | 60% | 250 | 2.4 |
| B | 0.5% | 4 | 20% | คำนวณไม่ได้ | 0 |
| C | 0.5% | 8 | ไม่มีข้อมูล | ไม่มีข้อมูล | ไม่มีข้อมูล |

ยอดซื้อ/มูลค่ารวมและCPA/ROASรวมไม่รายงาน เพราะCขาด3ช่อง ไม่เอายอดA+Bไปอ้างทั้งไฟล์ ไม่เรียกCPAของBว่า0บาท

งานตรวจต่อ:
1. ขอlanding/purchases/revenueของCหรือยืนยันว่าไม่ได้เก็บ ก่อนสรุปยอดรวม (sourceแถว4)
2. Bมีlanding30จากlinkclick150 ต่ำกว่าAในไฟล์นี้ ตรวจหน้าโหลด วิธีนับ และredirect โดยยังไม่ฟันธงสาเหตุ ยังไม่มีหลักฐานว่าhookทำให้ซื้อไม่ได้ (แถว3)
3. ก่อนสั่งเพิ่ม/ลดงบ ต้องมีเป้าหมายCPAของร้านและสถานะtracking/attributionlag ไม่สรุปว่าAกำไรจากROAS2.4เมื่อไม่มีmargin

ไม่มีผู้รับผิดชอบระบุ จึงไม่แต่งชื่อเจ้าของงาน ไม่มีการเปลี่ยนแคมเปญหรือเรียกAPIบัญชี