#!/usr/bin/env python3 """Turn an ai_audit.py JSON result into a printable HTML report (then print to PDF with any browser: google-chrome --headless --print-to-pdf=report.pdf report.html). Standard library only.""" import html, json, sys e = lambda x: html.escape(str(x if x is not None else "")) R = json.load(open(sys.argv[1])); out = sys.argv[2] s = R["score"] names = {"robots_access": "AI crawler access (robots.txt)", "live_fetch": "Live fetch as each bot", "no_js_content": "Content visible without JavaScript", "structured_data": "Structured data (schema)", "meta_basics": "Title, description, canonical, H1, indexable", "sitemap": "Sitemap", "citation_readiness": "Citation readiness"} col = {"high": "#b42318", "medium": "#b54708", "low": "#475467", "info": "#175cd3", "good": "#067647"} lab = {"high": "HIGH", "medium": "MEDIUM", "low": "LOW", "info": "INFO", "good": "OK"} parts = "".join(f"{e(names[k])}{v:g} / {s['part_max'][k]}" for k, v in s["parts"].items()) finds = "".join(f"
  • {lab[f['severity']]} {e(f['area'])}: {e(f['text'])}
  • " for f in R["findings"]) rows = "" for a in R["agents"]: rob = "allowed" if a["robots_allowed"] else "BLOCKED " + e(a["robots_rule"]) live = "token only" if a["fetch_ok"] is None else f"{e(a['http_status'] or a.get('error'))} {'OK' if a['fetch_ok'] else 'PROBLEM'}" rows += f"{e(a['agent'])}{e(a['role'])}{rob}{live}{e(a['bytes'] or '')}" c, m, sc = R["content"], R["meta"], R["schema"] det = [("Words in raw HTML (no JavaScript)", f"{c['raw_html_words']:,}"), ("Words inside <main>/<article>", f"{c['main_words']:,}"), ("Title", m["title"]), ("Meta description", f"{m['description']} ({m['description_len']} chars)"), ("Canonical", m["canonical"]), ("H1", " | ".join(m["h1"])), ("Question-style headings", m["question_heading_count"]), ("JSON-LD types", ", ".join(sc["types"]) or "none"), ("Sitemap", f"{R['sitemap']['url']} (HTTP {R['sitemap']['status']}, {R['sitemap']['locs']} URLs)"), ("llms.txt (not scored)", f"present, {R['llms_txt']['bytes']:,} bytes" if R["llms_txt"]["present"] else "not present")] dets = "".join(f"{k}{e(v) if not str(v).startswith(('Words',)) else v}" for k, v in det) doc = f"""AI visibility audit: {e(R['url'])}

    Sample report · AI visibility audit · astorventuresllc.com/ai-visibility-audit

    Can ChatGPT, Claude and Perplexity read {e(R['url'])}?

    Checked {e(R['checked_at_utc'])} (UTC) with {e(R['tool'])}

    {s['total']}/100
    Grade {s['grade']}
    Technical readability for AI crawlers. This is a diagnosis, not a promise that any assistant will cite or recommend the site.
    {parts}

    Findings, most important first

    Crawler by crawler

    {rows}
    CrawlerWhat it doesrobots.txtLive fetchBytes

    Page details

    {dets}

    Limits

    """ open(out, "w").write(doc)