SKILL.md · Free
ตรวจ Google Ads จากคำค้น
ตรวจคำค้นกับสินค้าที่ร้านขาย คำนวณต้นทุน และแยกคำที่ควรเสนอให้หยุดจากคำที่ต้องถามเพิ่ม
ดาวน์โหลด Skillต้องเตรียมอะไร
- ไฟล์รายงานคำค้น
- สินค้าและบริการที่ร้านรับจริง
- ช่วงเวลาและนิยาม conversion
Skill ทำอะไรให้
- ตรวจขอบเขตรายงาน
- คำนวณต้นทุน
- จัดคำค้นตามสินค้าที่ขาย
- เสนอสิ่งที่ควรแก้พร้อมข้อควรระวัง
ได้อะไรกลับมา
- รายงานคำค้นพร้อมตัวเลข
- ข้อเสนอคำค้นเชิงลบให้คนตรวจ
- ยอดที่ไม่ครอบคลุมในไฟล์และคำถามค้าง
ตัวอย่างข้อมูลและผลทดลอง
ทดลองกับข้อมูลสมมติของ 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 ไม่มีการเปลี่ยนบัญชี/งบ
