How to Generate a PDF Report with Charts from GEO‑SEO Claude
You can generate a PDF report with charts from GEO‑SEO Claude by extending the Flask web application with a new route that aggregates prospect data, creates a matplotlib visualization, and compiles both into a ReportLab PDF streamed directly to the browser.
GEO‑SEO Claude stores prospect data in a local JSON file at ~/.geo-prospects/prospects.json and serves it through a lightweight Flask web UI located in scripts/webapp/app.py. To create visual reports, you will add server-side logic that reuses existing data helpers, generates charts in-memory, and delivers the final PDF without writing temporary files to disk.
Installation Requirements
Before implementing the reporting feature, install the required visualization and PDF libraries into your project environment.
pip install matplotlib reportlab
These packages integrate cleanly with the existing Flask application and require no external services or API keys.
Step 1: Load Prospect Data
The application already provides a load_prospects() helper in scripts/webapp/app.py that reads and parses the local JSON storage. Reuse this function to ensure your report always uses the current dataset.
As implemented in the source code, load_prospects() handles file I/O and returns a list of prospect dictionaries. This keeps your new reporting code consistent with existing routes like /prospect/<pid>/pdf found at lines 92‑108 in app.py.
Step 2: Aggregate Statistics
Compute summary metrics using the same logic as the existing crm_stats() function (defined at lines 58‑72 in scripts/webapp/app.py). This ensures your report text matches the statistics displayed elsewhere in the UI.
Typical aggregations include total prospect count, average geo_score, and status groupings. Process these values in pure Python before passing them to the PDF generator.
Step 3: Generate the Chart
Create a chart visualization using matplotlib and render it to a BytesIO buffer. This avoids filesystem clutter and keeps the workflow stateless.
The following function counts prospects by geo-score tiers and returns PNG bytes:
import io
import matplotlib.pyplot as plt
def build_chart(prospects: list[dict]) -> bytes:
"""Return PNG bytes of a prospect distribution chart."""
tiers = {"good": 0, "moderate": 0, "poor": 0, "critical": 0}
for p in prospects:
score = p.get("geo_score", 0)
if score >= 80:
tiers["good"] += 1
elif score >= 60:
tiers["moderate"] += 1
elif score >= 40:
tiers["poor"] += 1
else:
tiers["critical"] += 1
fig, ax = plt.subplots(figsize=(6, 4))
ax.bar(tiers.keys(), tiers.values(), color="#4c8bf5")
ax.set_title("Prospect Geo‑Score Distribution")
ax.set_ylabel("Count")
plt.tight_layout()
buf = io.BytesIO()
fig.savefig(buf, format="png")
plt.close(fig)
buf.seek(0)
return buf.read()
Step 4: Compose the PDF Document
Use ReportLab to layout text, statistics, and the embedded chart in a single PDF document. The following function accepts the PNG bytes and a statistics dictionary, then returns the complete PDF as bytes:
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
def create_pdf(chart_png: bytes, stats: dict) -> bytes:
"""Return PDF bytes containing text stats and the chart image."""
buf = io.BytesIO()
c = canvas.Canvas(buf, pagesize=A4)
width, height = A4
# Header
c.setFont("Helvetica-Bold", 16)
c.drawString(40, height - 50, "GEO‑SEO Claude – Prospect Summary")
# Stats table
c.setFont("Helvetica", 12)
y = height - 90
for key, value in stats.items():
c.drawString(40, y, f"{key.capitalize()}: {value}")
y -= 20
# Embed chart
img = ImageReader(io.BytesIO(chart_png))
img_width, img_height = img.getSize()
max_width = 500
scale = min(max_width / img_width, 1)
img_width *= scale
img_height *= scale
c.drawImage(img, 40, y - img_height - 20,
width=img_width, height=img_height)
c.showPage()
c.save()
buf.seek(0)
return buf.read()
Step 5: Register the Flask Route
Add a new endpoint to scripts/webapp/app.py that orchestrates the data loading, chart generation, and PDF compilation. Use Flask’s send_file utility to stream the result, following the same pattern as the existing individual prospect PDF route.
from flask import send_file, abort
@app.route("/report/pdf")
def download_report():
"""Generate and download a full prospect report with charts."""
prospects = load_prospects()
if not prospects:
abort(404, description="No prospect data available")
chart_png = build_chart(prospects)
stats = crm_stats(prospects)
pdf_bytes = create_pdf(chart_png, stats)
return send_file(
io.BytesIO(pdf_bytes),
as_attachment=True,
download_name="geo_seo_report.pdf",
mimetype="application/pdf",
)
Integrating with the Web Interface
Expose the new functionality by adding a download link or button in your HTML templates. Because the PDF is generated on the server and streamed back, the client only needs a standard anchor tag:
<a href="/report/pdf" class="btn btn-primary">Download PDF Report</a>
Clicking this link triggers the full pipeline—data aggregation, chart rendering, and PDF compilation—and immediately prompts the browser to save geo_seo_report.pdf.
Summary
- Data source: GEO‑SEO Claude stores prospects in
~/.geo-prospects/prospects.json, accessed viaload_prospects()inscripts/webapp/app.py. - Charting: Use matplotlib to generate visualizations and buffer them as PNG bytes in memory.
- PDF creation: Use ReportLab to embed text, statistics, and images into a single PDF document without temporary files.
- Delivery: Extend the Flask app with a
/report/pdfroute that streams the final document usingsend_file, matching the architecture of existing download endpoints. - Dependencies: Requires only
matplotlibandreportlab, maintaining the project’s "no-external-services" design philosophy.
Frequently Asked Questions
Do I need external APIs to generate PDFs in GEO‑SEO Claude?
No. The solution relies entirely on local Python libraries. ReportLab and matplotlib process everything server-side using the existing prospect JSON file, so no third-party PDF services or cloud APIs are required.
Can I customize the chart colors to match my brand?
Yes. Modify the color parameter in the ax.bar() call within the build_chart() function. You can also import color values from white-label/brand_config.py if you have defined brand-specific palettes elsewhere in the project.
Is it possible to add multiple charts to the same report?
Yes. Extend the create_pdf() function to accept additional image buffers and call c.drawImage() for each chart at different vertical positions on the page. You can also create new pages with c.showPage() before adding the next visualization.
How do I troubleshoot if the PDF generation fails?
First, verify that ~/.geo-prospects/prospects.json exists and contains valid data, as the route calls abort(404) when the prospect list is empty. Next, check that matplotlib and reportlab are installed in the same Python environment running the Flask server. Finally, inspect the server logs for specific errors from build_chart() or create_pdf(), which usually indicate data formatting issues or memory constraints when processing large prospect lists.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →