from __future__ import annotations import argparse import json import random import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from app.ads1.ads_analyst import build_ads_analyst_context, build_ads_analyst_prompt, rule_based_insights from app.ads1.budget_optimizer import build_budget_optimizer_context, rule_based_budget_actions from app.ads1.growth_finder import build_growth_finder_context, rule_based_growth_actions from app.ads1.keyword_inspector import build_keyword_features, build_keyword_prompt from app.ads1.sample_data import generate_sample_dfs from app.ads1.search_term_optimizer import ( build_search_optimizer_context, build_search_optimizer_prompt, rule_based_search_actions, ) SYSTEM = "You are a Google Ads analyst. Reply with concise actionable markdown bullets only." def chat_record(user: str, assistant: str, card: str, campaign_name: str | None = None) -> dict: return { "messages": [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": user}, {"role": "assistant", "content": assistant}, ], "metadata": { "card": card, "campaign_name": campaign_name, "source": "seed_sample_data", }, } def keyword_answer(context: dict) -> str: rows = context.get("keywords", []) bullets: list[str] = [] for row in rows: keyword = row.get("keyword", "this keyword") cost = float(row.get("cost", 0) or 0) clicks = int(row.get("clicks", 0) or 0) conversions = float(row.get("conversions", 0) or 0) ctr = float(row.get("ctr", 0) or 0) cpa = cost / conversions if conversions else cost if conversions >= 10: bullets.append( f"- Treat '{keyword}' as a winning keyword because it produced {conversions:g} conversions at CPA {cpa:.2f} and CTR {ctr:.2f}%." ) elif conversions == 0 and cost > 0: bullets.append( f"- Reduce or pause '{keyword}' because it spent {cost:.2f} across {clicks} clicks with 0 conversions." ) elif conversions > 0: bullets.append( f"- Investigate '{keyword}' because it has {conversions:g} conversions but CPA {cpa:.2f}; scale only if CPA improves." ) if len(bullets) >= 5: break return "\n\n".join(bullets) or "- No keyword action found because no usable keyword rows were available." def keyword_context(dfs: dict, campaign_name: str) -> dict: df = dfs["keywords"].copy() if "campaign_name" in df.columns: df = df[df["campaign_name"] == campaign_name] df = build_keyword_features(df) df = df.sort_values(["conversions", "cost"], ascending=[False, False]).head(12) return { "campaign_name": campaign_name, "keywords": df.round(2).to_dict("records"), } def build_records() -> list[dict]: dfs = generate_sample_dfs() campaigns = dfs["campaigns"]["name"].dropna().astype(str).tolist() records: list[dict] = [] for campaign_name in campaigns: ads_context = build_ads_analyst_context(dfs, campaign_name) records.append( chat_record( build_ads_analyst_prompt(ads_context), rule_based_insights(ads_context), "ads_analyst", campaign_name, ) ) kw_context = keyword_context(dfs, campaign_name) records.append( chat_record( build_keyword_prompt(kw_context), keyword_answer(kw_context), "keyword_inspector", campaign_name, ) ) search_context = build_search_optimizer_context(dfs, campaign_name) records.append( chat_record( build_search_optimizer_prompt(search_context), rule_based_search_actions(search_context), "search_term_cleaner", campaign_name, ) ) budget_context = build_budget_optimizer_context(dfs, campaign_name) user = ( f"Rewrite these computed budget decisions for {campaign_name} into 3 to 5 concise bullets.\n" "Each bullet must mention the campaign or keyword, the action, and the evidence.\n\n" f"Data (JSON):\n{json.dumps(budget_context, indent=2, default=str)}" ) records.append(chat_record(user, rule_based_budget_actions(budget_context), "budget_optimizer", campaign_name)) growth_context = build_growth_finder_context(dfs, campaign_name) user = ( f"Rewrite these computed growth decisions for {campaign_name} into concise growth opportunity bullets.\n" "Each bullet must mention the keyword, the scale action, and the evidence.\n\n" f"Data (JSON):\n{json.dumps(growth_context, indent=2, default=str)}" ) records.append(chat_record(user, rule_based_growth_actions(growth_context), "growth_finder", campaign_name)) return records def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--out", type=Path, default=ROOT / "fine_tuning" / "data" / "seed.jsonl") parser.add_argument("--shuffle", action="store_true") args = parser.parse_args() records = build_records() if args.shuffle: random.Random(42).shuffle(records) args.out.parent.mkdir(parents=True, exist_ok=True) with args.out.open("w", encoding="utf-8") as handle: for record in records: handle.write(json.dumps(record, ensure_ascii=False) + "\n") print(f"Wrote {len(records)} records to {args.out}") return 0 if __name__ == "__main__": raise SystemExit(main())