SKILL.md · Free

ตรวจ Google Ads จากคำค้น

ตรวจคำค้นกับสินค้าที่ร้านขาย คำนวณต้นทุน และแยกคำที่ควรเสนอให้หยุดจากคำที่ต้องถามเพิ่ม

ดาวน์โหลด Skill

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

  1. ไฟล์รายงานคำค้น
  2. สินค้าและบริการที่ร้านรับจริง
  3. ช่วงเวลาและนิยาม conversion

Skill ทำอะไรให้

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

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

  1. รายงานคำค้นพร้อมตัวเลข
  2. ข้อเสนอคำค้นเชิงลบให้คนตรวจ
  3. ยอดที่ไม่ครอบคลุมในไฟล์และคำถามค้าง

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

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

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

ร้านขายขวดใหม่ ไม่รับซ่อมหรือขายมือสอง ไฟล์คำค้นใช้เงิน 700 บาท แต่ยอดบัญชี 1,000 บาท ราคาส่งยังไม่ยืนยัน

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

ไฟล์ครอบคลุมเงินโฆษณา 70% ไม่อ้างว่าเป็นทั้งบัญชี เสนอให้ตรวจคำว่า ซ่อมขวดปั๊ม และ ขวดปั๊มมือสอง เพราะร้านไม่รับงานนี้ คำว่า ราคาส่ง ยังต้องถามร้าน ไม่ตัดทิ้งเพราะ CPA สูงอย่างเดียว

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

google-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
ตัวอย่างสมมติของ KlangPrompt ไม่ใช่แคมเปญลูกค้า
ร้านขายขวดปั๊มใหม่500ml ไม่ขายมือสอง ไม่รับซ่อม ส่งทั่วไทย เงื่อนไขราคาส่งยังไม่ยืนยัน
conversion_scope purchaseจากexportเดียวกัน ยอมรับค่าทศนิยมตามreportedattribution ไม่ใช่จำนวนคน
ยอดaccountช่วงเดียวกัน spend1000THB แต่searchtermsในไฟล์อาจไม่ครอบคลุมทั้งหมด
ให้ตรวจความตรงสินค้าและnegativeที่ควรเสนอ ไม่เปลี่ยนบัญชีหรืออัปโหลดnegative
examples/export.csv
row_id,start,end,currency,conversion_scope,ad_group,search_term,spend,impressions,clicks,conversions,conversion_value
G1,2026-09-01,2026-09-07,THB,purchase,ขวดใหม่,ซื้อขวดปั๊ม 500 ml,300,1000,30,2,1200
G2,2026-09-01,2026-09-07,THB,purchase,ขวดใหม่,ซ่อมขวดปั๊ม,120,400,12,0,0
G3,2026-09-01,2026-09-07,THB,purchase,ขวดใหม่,ขวดปั๊มมือสอง,80,200,8,0,0
G4,2026-09-01,2026-09-07,THB,purchase,ขวดใหม่,ขวดปั๊มราคาส่ง,200,2000,20,0.5,300
references/export.md
# รูปแบบรายงานคำค้น

คอลัมน์ตามลำดับ:
`row_id,start,end,currency,conversion_scope,ad_group,search_term,spend,impressions,clicks,conversions,conversion_value`

row_idคือรหัสที่ไม่ซ้ำของแถวต้นทาง ถ้าexportไม่มี ให้สร้างจากแถวหลังตรวจไม่ซ้ำแล้ว เก็บ mappingไว้; อย่าใช้ชื่อคำค้นเพียงอย่างเดียวเพราะคำเดียวกันอยู่หลายadgroupได้

start/end/currency/conversion_scopeเหมือนกันทั้งไฟล์ conversion_scopeระบุactionหรือชุดactionจากexportจริง ห้ามผสมยอด All conversions กับ Conversions โดยไม่บอกนิยาม แถวconversions/valueว่างให้เว้นว่าง Scriptรวมเป็นnullเมื่อมีข้อมูลขาด ไม่อ้างยอดรวมpartial

impressions/clicksจำนวนเต็ม; conversionsอาจเป็นทศนิยมตามreported attribution ห้ามปัดทิ้ง ค่าเงิน/มูลค่าไม่ติดลบ Scriptไม่คำนวณmargin ไม่แปลงสกุลเงิน และไม่อ่านบัญชีGoogleโดยตรง

สินค้าที่ขายและพื้นที่บริการอยู่ใน briefแยก ไม่ให้ตัวคำนวณเดาความหมายคำค้นอัตโนมัติ ข้อเสนอnegativeต้องผ่านการอ่านbrief+คำค้นจริงโดยAgent ไม่ใช่sortCPAแล้วลบทิ้ง
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: google-ads-quick-audit
description: ตรวจ Google Ads จากรายงานคำค้น คำนวณต้นทุนและ conversion แยกคำที่ตรงสินค้า คำที่ไม่ตรง และคำที่ยังตัดสินไม่ได้ ส่งข้อเสนอคำค้นเชิงลบให้คนตรวจ ไม่อัปโหลดหรือหยุดโฆษณาเอง
license: KlangPrompt Skills License 1.0
---

# ตรวจ Google Ads จากคำค้น

ใช้กับรายงาน search terms หนึ่งช่วงเวลา ไม่ใช้แทนการตรวจทุกชนิดแคมเปญ เช่น Performance Max ทั้งบัญชี รายงานต้องแยก “ความตรงกับสินค้า” จาก “คุ้มหรือไม่” เพราะคำตรงอาจยังมีข้อมูล conversionไม่พอ

## สิ่งที่ต้องเตรียม

รับ search-term export, ขอบเขตconversionที่นับ, ช่วงเวลา/สกุลเงิน, สินค้าที่ขายจริง/พื้นที่บริการ/สิ่งที่ไม่รับทำ และ account control totalsถ้ามี ไม่ขอบัญชีหรือรหัสผ่าน ไม่ส่งข้อมูลลูกค้าออกเครือข่าย

อ่าน [references/export.md](references/export.md) แปลงเป็นสำเนา CSV บันทึก mapping และระดับแถว ให้หนึ่งแถวไม่ทับซ้อนกัน ห้ามรวม campaigntotal กับคำค้นอีกครั้ง Googleอาจไม่ได้แสดงคำค้นครบทั้งหมด จึงใช้คำว่า “ยอดในไฟล์คำค้นนี้” ไม่ใช่ทั้งบัญชี จนกว่าจะกระทบยอดได้

## ขั้นตอน

1. รัน `python scripts/audit_export.py google FILE.csv` อ่านยอดรวมและmissingก่อนอันดับ แยก conversionaction ที่ความหมายต่างกัน เช่น leadsกับpurchases; ไม่บวก conversionvalueต่างหน่วยแล้วเรียกว่าrevenue
2. จัดคำค้นเป็น ตรงงาน / ไม่ตรงงาน / ต้องถาม โดยอ้างข้อเสนอจริงของร้าน ไม่เดาจากคำเดียว เช่น “ฟรี” อาจเป็นของแถมที่ร้านมี
3. พิจารณาแต่ละกลุ่มด้วย clicks/spend/conversions/CPA หากไม่มีconversionไม่อ้างว่าแพ้แน่นอน ต้องดูconversionlagและtrackingก่อน ห้ามใช้CTRอย่างเดียวตัดสินยอดขาย
4. เสนอ negative keyword เฉพาะคำที่มีหลักฐานว่าไม่ขาย/ไม่ให้บริการ พร้อม scope/match type ที่เสนอและความเสี่ยงบล็อกคำดี ให้เจ้าของตรวจอนุมัติ ห้าม applyเอง ห้ามสร้างnegativeจากตัวเลขต่ำอย่างเดียว
5. เทียบยอดในไฟล์กับ accounttotalช่วงเดียวกัน ถ้าไม่เท่าระบุ coverage และยอดต่าง ห้ามเติมคำค้นที่แพลตฟอร์มไม่แสดง ห้ามสรุปว่าความต่างทั้งหมดเป็นงบเสีย

## ผลส่งมอบ

- ยอดรวมในไฟล์และเงื่อนไขmeasurement
- ตารางคำค้น:กลุ่มความตรงสินค้า / ตัวเลข / หลักฐาน / สิ่งที่ทำต่อ
- รายการnegativeที่เสนอ พร้อมเหตุผลและคำที่อาจโดนบล็อก ต้องตรวจอีกครั้งก่อนอัปโหลด
- คำถามที่ยังตัดสินไม่ได้ และยอดที่หายจากcoverage

ตรวจทุกคำเสนอnegativeกับสินค้าที่ขายจริงอีกครั้ง ตรวจdecimalconversionไม่ถูกปัดเป็นคนเต็ม ตรวจว่าค่าundefinedเป็นnullไม่ใช่0 ไม่มีข้อความรับประกันลดงบหรืออันดับ ไม่แต่งpolicy; ถ้าผู้ใช้ขอคำตอบนโยบายให้อ้าง Google Ads Help ล่าสุดแยกจากการวิเคราะห์export
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
# ผลทดลองตรวจ Google Ads

รันตัวคำนวณจริงในCodexกับไฟล์สมมติ ไม่ใช่ข้อมูลบัญชีลูกค้า ช่วง1–7กันยายน2569 THB conversion_scope purchase

ยอดในไฟล์คำค้น700บาท / clicks70 / impressions3,600 / conversions2.5 / value1,500 CPC10บาท CPA280บาท ROAS2.142857 ตามreportedattribution ไม่ปัด2.5เป็นคน3คน

Control spendบัญชี1,000บาท เทียบไฟล์700บาท = coverage70%, ส่วนต่าง300บาท ยังไม่ทราบคำค้น/ส่วนอื่นที่ไม่แสดง ไม่ถือว่าส่วนต่างเป็นงบเสีย

| ID | คำค้น | ผลตรวจ | ทำต่อ |
|---|---|---|---|
| G1 | ซื้อขวดปั๊ม 500 ml | ตรงสินค้าใหม่500ml | เก็บในกลุ่มตรงงาน ไม่ประกาศว่าชนะจากsampleเดียว |
| G2 | ซ่อมขวดปั๊ม | ร้านไม่รับซ่อม ตามbrief | เสนอnegativeแบบexactสำหรับคำค้นนี้ในadgroupขวดใหม่ ให้คนอนุมัติ ยังไม่apply |
| G3 | ขวดปั๊มมือสอง | ร้านขายของใหม่เท่านั้น | เสนอnegativeแบบexactสำหรับคำค้นนี้ในadgroupขวดใหม่ ให้คนอนุมัติ ยังไม่apply |
| G4 | ขวดปั๊มราคาส่ง | ไม่ทราบเงื่อนไขราคาส่ง | ถามร้านก่อน ห้ามnegativeเพราะCPA400สูงกว่าG1 |

เลือกเสนอexactทั้งวลีเพื่อลดขอบเขตการบล็อกในรอบแรก ไม่เสนอคำเดี่ยวกว้างอย่าง“ขวด” ตรวจรูปคำและผลกระทบกับรายการnegativeจริงก่อนติดตั้ง ไม่อ้างว่าจะบล็อกทุกvariationได้ และไม่รวมเป็นไฟล์อัปโหลดอัตโนมัติ

ค่าconversion0ในG2/G3ไม่ใช่เหตุหลักที่เสนอบล็อก หลักฐานคือบริการไม่ตรงตามbrief ไม่มีการเปลี่ยนบัญชี/งบ