From d48fa20b7dad294ef26aff215bdbcb79e3cfa1ad Mon Sep 17 00:00:00 2001 From: Abdeladim Fadheli Date: Mon, 13 Jul 2026 07:35:52 +0000 Subject: [PATCH] Add RankBits AI visibility tracker tutorial code --- .../ai_visibility_tracker.py | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 general/rankbits-ai-visibility/ai_visibility_tracker.py diff --git a/general/rankbits-ai-visibility/ai_visibility_tracker.py b/general/rankbits-ai-visibility/ai_visibility_tracker.py new file mode 100644 index 00000000..e3cf32c4 --- /dev/null +++ b/general/rankbits-ai-visibility/ai_visibility_tracker.py @@ -0,0 +1,344 @@ +""" +Track Your AI Visibility with Python & RankBits API. + +This script demonstrates the full workflow: +1. Check your RankBits account and plan +2. Create an AI visibility scan for any domain +3. Poll until the scan completes +4. Parse the results and generate visualizations + +Requirements: + pip install requests matplotlib + +Usage: + export RANKBITS_TOKEN="rb_your_token_here" + python ai_visibility_tracker.py +""" + +import os +import sys +import time +import json +from datetime import datetime + +import requests +import matplotlib.pyplot as plt +import matplotlib.ticker as mticker + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +TOKEN = os.environ.get("RANKBITS_TOKEN", "rb_your_token_here") +BASE_URL = "https://rankbits.com/v1" +HEADERS = { + "Authorization": f"Bearer {TOKEN}", + "Content-Type": "application/json", +} + +# The domain you want to scan +TARGET_URL = "https://thepythoncode.com" + +# Free engines to use (omit "paid" providers like openai_pro, claude_pro, gemini_pro) +ENGINES = ["openai", "gemini", "perplexity", "claude", "google_ai_mode"] + +# Number of AI-generated prompts (plan caps apply) +PROMPT_COUNT = 5 + + +# --------------------------------------------------------------------------- +# Helper: pretty-print JSON +# --------------------------------------------------------------------------- + +def print_json(obj: dict, title: str = "") -> None: + """Print a dictionary as formatted JSON.""" + if title: + print(f"\n{'=' * 60}\n{title}\n{'=' * 60}") + print(json.dumps(obj, indent=2, default=str)) + + +# --------------------------------------------------------------------------- +# Step 1 – Check your account +# --------------------------------------------------------------------------- + +def check_account() -> dict: + """Fetch plan info and credit usage from /v1/me.""" + resp = requests.get(f"{BASE_URL}/me", headers=HEADERS) + resp.raise_for_status() + data = resp.json() + plan = data["plan"] + resp_info = plan["responses"] + + print("šŸ”‘ Account") + print(f" Plan: {plan['label']} (${plan['price_usd']}/mo)") + print(f" Monthly: {resp_info['used']}/{resp_info['monthly_limit']} responses") + print(f" Credits: {resp_info['purchased_remaining']} purchased remaining") + print(f" Engines: {len(plan['allowed_provider_keys'])} available") + return data + + +# --------------------------------------------------------------------------- +# Step 2 – Create a scan +# --------------------------------------------------------------------------- + +def create_scan( + url: str, + prompt_count: int = 5, + providers: list[str] | None = None, +) -> dict: + """Submit an async scan and return the public ID.""" + payload: dict = {"url": url, "prompt_count": prompt_count} + if providers: + payload["providers"] = providers + + resp = requests.post(f"{BASE_URL}/scans", headers=HEADERS, json=payload) + resp.raise_for_status() + data = resp.json() + + scan = data["scan"] + print(f"\nšŸš€ Scan created") + print(f" ID: {scan['public_id']}") + print(f" Domain: {scan['domain']}") + print(f" Status: {scan['status']}") + print(f" View live: https://rankbits.com{data['links']['app']}") + return data + + +# --------------------------------------------------------------------------- +# Step 3 – Poll until done +# --------------------------------------------------------------------------- + +def poll_scan(public_id: str, poll_seconds: float = 3.0, max_wait: float = 300.0) -> dict: + """Poll /v1/scans/{id} until status is 'done' or timeout.""" + url = f"{BASE_URL}/scans/{public_id}" + start = time.time() + last_completed = 0 + + print(f"\nā³ Polling scan {public_id} ...") + while True: + elapsed = time.time() - start + if elapsed > max_wait: + raise TimeoutError(f"Scan did not complete within {max_wait}s") + + resp = requests.get(url, headers=HEADERS) + resp.raise_for_status() + data = resp.json() + + status = data["scan"]["status"] + progress = data.get("progress", {}) + completed = progress.get("completed_results", 0) + expected = progress.get("expected_results", 0) + + # Print progress when it changes + if completed != last_completed: + pct = (completed / expected * 100) if expected else 0 + print(f" [{status}] {completed}/{expected} ({pct:.0f}%)") + last_completed = completed + + if status == "done": + print(" āœ… Scan complete!") + return data + if status in ("error", "failed"): + raise RuntimeError(f"Scan failed: {data}") + + time.sleep(poll_seconds) + + +# --------------------------------------------------------------------------- +# Step 4 – Parse & display results +# --------------------------------------------------------------------------- + +def summarize_results(data: dict) -> None: + """Print a human-readable summary of scan results.""" + aggregate = data.get("aggregate", {}) + overall = aggregate.get("overall", {}) + providers = aggregate.get("providers", {}) + results = data.get("results", []) + prompts = data.get("prompts", []) + + # ---- 4a. Overview ---- + print(f"\nšŸ“Š Visibility Summary for {data['scan']['domain']}") + print(f" Overall score: {overall.get('score', 'N/A')}") + print(f" Mention rate: {overall.get('mention_rate', 0):.1f}%") + print(f" Citation rate: {overall.get('citation_rate', 0):.1f}%") + print(f" Total results: {len(results)} rows") + + # ---- 4b. Per-engine breakdown ---- + print(f"\nšŸ¤– Engine Breakdown") + print(f" {'Engine':<20s} {'Score':>7s} {'Mention%':>9s} {'Citation%':>10s}") + print(f" {'-'*46}") + for key, pdata in sorted(providers.items(), key=lambda x: -x[1].get("score", 0)): + print( + f" {key:<20s} {pdata.get('score', 0):>7.1f} " + f"{pdata.get('mention_rate', 0):>8.1f}% {pdata.get('citation_rate', 0):>9.1f}%" + ) + + # ---- 4c. Prompts used ---- + print(f"\nšŸ’¬ Prompts ({len(prompts)})") + for p in prompts: + print(f" • {p['text']}") + + # ---- 4d. Share of voice (top 5) ---- + sov = aggregate.get("share_of_voice", []) + if sov: + print(f"\nšŸ”— Top Cited Domains (Share of Voice)") + for entry in sov[:5]: + print(f" {entry['domain']:40s} {entry.get('citation_count', 0)} citations") + + # ---- 4e. Where we were found ---- + found = [r for r in results if r.get("brand_mentioned") or r.get("brand_cited")] + if found: + print(f"\nāœ… Where {data['scan']['domain']} Appeared ({len(found)}/{len(results)})") + for r in found: + mentioned = "āœ…" if r["brand_mentioned"] else "āŒ" + cited = "āœ…" if r["brand_cited"] else "āŒ" + print(f" [{r['provider']:20s}] Mentioned: {mentioned} Cited: {cited}") + print(f" Prompt: {r['prompt'][:100]}") + else: + print(f"\nāš ļø {data['scan']['domain']} was NOT mentioned or cited in any result!") + print(" Time to improve your AI visibility! → https://rankbits.com") + + +# --------------------------------------------------------------------------- +# Step 5 – Generate charts +# --------------------------------------------------------------------------- + +def generate_charts(data: dict, output_dir: str = ".") -> None: + """Create matplotlib charts from scan results.""" + aggregate = data.get("aggregate", {}) + providers = aggregate.get("providers", {}) + domain = data["scan"]["domain"] + + if not providers: + print("āš ļø No provider data to chart.") + return + + # Sort engines by score descending + engines = sorted(providers.items(), key=lambda x: -x[1].get("score", 0)) + names = [e[0].replace("_", " ").title() for e in engines] + scores = [e[1].get("score", 0) for e in engines] + mention_rates = [e[1].get("mention_rate", 0) for e in engines] + citation_rates = [e[1].get("citation_rate", 0) for e in engines] + + # Colors + bar_color = "#7c3aed" + mention_color = "#10b981" + citation_color = "#f59e0b" + + # ---- Chart 1: Scores by engine ---- + fig1, ax1 = plt.subplots(figsize=(8, 5)) + bars = ax1.barh(names, scores, color=bar_color, edgecolor="white", linewidth=0.5, height=0.5) + ax1.set_xlabel("Visibility Score (0–100)", fontsize=11) + ax1.set_title(f"AI Visibility Score by Engine — {domain}", fontsize=13, fontweight="bold") + ax1.invert_yaxis() + ax1.xaxis.set_major_formatter(mticker.FormatStrFormatter("%.0f")) + for bar, val in zip(bars, scores): + ax1.text(bar.get_width() + 0.5, bar.get_y() + bar.get_height() / 2, + f"{val:.1f}", va="center", fontsize=10, fontweight="semibold") + ax1.set_xlim(0, max(scores) * 1.3 + 5 if max(scores) > 0 else 30) + plt.tight_layout() + fig1.savefig(f"{output_dir}/engine_scores.png", dpi=150) + print(f"\nšŸ“ˆ Chart saved: {output_dir}/engine_scores.png") + + # ---- Chart 2: Mention vs Citation rates ---- + fig2, ax2 = plt.subplots(figsize=(8, 5)) + x = range(len(names)) + width = 0.35 + ax2.bar([i - width / 2 for i in x], mention_rates, width, label="Mention Rate %", + color=mention_color, edgecolor="white", linewidth=0.5) + ax2.bar([i + width / 2 for i in x], citation_rates, width, label="Citation Rate %", + color=citation_color, edgecolor="white", linewidth=0.5) + ax2.set_xticks(x) + ax2.set_xticklabels(names, fontsize=9) + ax2.set_ylabel("Percentage (%)", fontsize=11) + ax2.set_title(f"Mention vs Citation Rate — {domain}", fontsize=13, fontweight="bold") + ax2.legend(fontsize=10, loc="upper right") + ax2.set_ylim(0, max(max(mention_rates), max(citation_rates)) * 1.4 + 5) + plt.tight_layout() + fig2.savefig(f"{output_dir}/mention_vs_citation.png", dpi=150) + print(f"šŸ“ˆ Chart saved: {output_dir}/mention_vs_citation.png") + + # ---- Chart 3: Results grid (heatmap-style table) ---- + results = data.get("results", []) + if results: + # Build a matrix: rows=prompts, cols=engines + prompt_texts = sorted({r["prompt"][:60] for r in results}) + engine_names = sorted({r["provider"] for r in results}) + + matrix = [] + for pt in prompt_texts: + row = [] + for eng in engine_names: + match = [r for r in results if r["prompt"].startswith(pt[:30]) and r["provider"] == eng] + if match: + m = match[0] + if m["brand_cited"]: + row.append(2) # cited (best) + elif m["brand_mentioned"]: + row.append(1) # mentioned + else: + row.append(0) # absent + else: + row.append(0) + matrix.append(row) + + fig3, ax3 = plt.subplots(figsize=(max(8, len(engine_names) * 1.2), + max(5, len(prompt_texts) * 0.6))) + cmap = plt.cm.RdYlGn + im = ax3.imshow(matrix, cmap=cmap, aspect="auto", vmin=0, vmax=2) + + ax3.set_xticks(range(len(engine_names))) + ax3.set_xticklabels([e.replace("_", " ").title() for e in engine_names], + rotation=30, ha="right", fontsize=9) + ax3.set_yticks(range(len(prompt_texts))) + ax3.set_yticklabels(prompt_texts, fontsize=8) + + # Add text in each cell + for i in range(len(prompt_texts)): + for j in range(len(engine_names)): + val = matrix[i][j] + symbol = {0: "ā—‹", 1: "ā–²", 2: "ā˜…"}[val] + ax3.text(j, i, symbol, ha="center", va="center", + fontsize=14, color="black" if val == 2 else "white") + + ax3.set_title(f"Presence Grid — {domain}\nā—‹ Absent ā–² Mentioned ā˜… Cited", + fontsize=12, fontweight="bold") + plt.tight_layout() + fig3.savefig(f"{output_dir}/presence_grid.png", dpi=150) + print(f"šŸ“ˆ Chart saved: {output_dir}/presence_grid.png") + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main() -> None: + if TOKEN == "rb_your_token_here": + print("āŒ Set your RANKBITS_TOKEN environment variable first.") + print(" Get one at: https://rankbits.com/signup") + sys.exit(1) + + print(f"šŸŽÆ Tracking AI visibility for: {TARGET_URL}") + print(f" Engines: {', '.join(ENGINES)}") + + # 1. Check account + check_account() + + # 2. Start scan + scan_data = create_scan(TARGET_URL, prompt_count=PROMPT_COUNT, providers=ENGINES) + public_id = scan_data["scan"]["public_id"] + + # 3. Poll until complete + results = poll_scan(public_id) + + # 4. Summarize + summarize_results(results) + + # 5. Charts + generate_charts(results) + + print("\n✨ Done! Track ongoing visibility at https://rankbits.com") + + +if __name__ == "__main__": + main()