AXe Skills HubSearch /

← All skills

pdf-report-generation

AXe First-party 

Reference: full SKILL.md

Below is the complete skill definition this hub loads when the skill is triggered — what the agent sees as its instructions, verbatim and unabridged.

PDF Report Generation

Role

You are an elite document automation engineer. You generate pixel-perfect PDFs with

professional layouts, dynamic data, charts, and corporate branding at scale.

Part 1: ReportLab Basics — Tables, Styles, Layout

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    PageBreak, Image, HRFlowable,
)

def build_report(filename: str, data: dict):
    doc = SimpleDocTemplate(
        filename,
        pagesize=A4,
        rightMargin=20*mm, leftMargin=20*mm,
        topMargin=25*mm, bottomMargin=25*mm,
    )
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(
        name="BrandTitle",
        fontSize=24, leading=28, textColor=colors.HexColor("#1a1a2e"),
        spaceAfter=12, fontName="Helvetica-Bold",
    ))
    styles.add(ParagraphStyle(
        name="SectionHead",
        fontSize=14, leading=18, textColor=colors.HexColor("#16213e"),
        spaceBefore=16, spaceAfter=8, fontName="Helvetica-Bold",
    ))

    elements = []

    # Title
    elements.append(Paragraph(data["title"], styles["BrandTitle"]))
    elements.append(HRFlowable(width="100%", color=colors.HexColor("#0f3460")))
    elements.append(Spacer(1, 12))

    # Summary paragraph
    elements.append(Paragraph(data["summary"], styles["BodyText"]))
    elements.append(Spacer(1, 20))

    # Data table
    table_data = [data["columns"]] + data["rows"]
    t = Table(table_data, repeatRows=1)
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#0f3460")),
        ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, 0), 10),
        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#f0f0f0")]),
        ("PADDING", (0, 0), (-1, -1), 6),
    ]))
    elements.append(t)

    doc.build(elements)

Part 2: Headers, Footers & Page Numbers

from reportlab.lib.units import mm
from reportlab.lib import colors
from datetime import date

def header_footer(canvas, doc):
    canvas.saveState()

    # Header
    canvas.setFont("Helvetica-Bold", 9)
    canvas.setFillColor(colors.HexColor("#0f3460"))
    canvas.drawString(20*mm, doc.pagesize[1] - 15*mm, "ACME Corp — Confidential")
    canvas.drawRightString(doc.pagesize[0] - 20*mm, doc.pagesize[1] - 15*mm, date.today().isoformat())
    canvas.setStrokeColor(colors.HexColor("#0f3460"))
    canvas.line(20*mm, doc.pagesize[1] - 17*mm, doc.pagesize[0] - 20*mm, doc.pagesize[1] - 17*mm)

    # Footer
    canvas.setFont("Helvetica", 8)
    canvas.setFillColor(colors.grey)
    canvas.drawCentredString(doc.pagesize[0] / 2, 12*mm, f"Page {doc.page}")

    canvas.restoreState()

# Use in build:
doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)

Part 3: Charts in PDFs with ReportLab Graphics

from reportlab.graphics.shapes import Drawing
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.piecharts import Pie
from reportlab.graphics.charts.linecharts import HorizontalLineChart

def make_bar_chart(data: list[list[float]], categories: list[str], width=400, height=200):
    drawing = Drawing(width, height)
    chart = VerticalBarChart()
    chart.x = 50
    chart.y = 30
    chart.width = width - 80
    chart.height = height - 60
    chart.data = data
    chart.categoryAxis.categoryNames = categories
    chart.categoryAxis.labels.angle = 45
    chart.valueAxis.valueMin = 0
    chart.bars[0].fillColor = colors.HexColor("#0f3460")
    if len(data) > 1:
        chart.bars[1].fillColor = colors.HexColor("#e94560")
    drawing.add(chart)
    return drawing

def make_pie_chart(data: list[float], labels: list[str], width=300, height=200):
    drawing = Drawing(width, height)
    pie = Pie()
    pie.x = 60
    pie.y = 20
    pie.width = 140
    pie.height = 140
    pie.data = data
    pie.labels = labels
    pie.slices.strokeWidth = 0.5
    palette = ["#0f3460", "#e94560", "#16213e", "#533483", "#0a1931"]
    for i in range(len(data)):
        pie.slices[i].fillColor = colors.HexColor(palette[i % len(palette)])
    drawing.add(pie)
    return drawing

Part 4: WeasyPrint — HTML/CSS to PDF

from weasyprint import HTML, CSS
from jinja2 import Environment, FileSystemLoader

def html_to_pdf(template_name: str, context: dict, output: str):
    env = Environment(loader=FileSystemLoader("templates"))
    template = env.get_template(template_name)
    html_content = template.render(**context)

    css = CSS(string="""
        @page {
            size: A4;
            margin: 20mm;
            @top-center { content: "Monthly Report"; font-size: 9pt; color: #666; }
            @bottom-right { content: "Page " counter(page) " of " counter(pages); font-size: 8pt; }
        }
        body { font-family: 'Helvetica Neue', sans-serif; color: #1a1a2e; line-height: 1.6; }
        h1 { color: #0f3460; border-bottom: 2px solid #0f3460; padding-bottom: 8px; }
        table { width: 100%; border-collapse: collapse; margin: 16px 0; }
        th { background: #0f3460; color: white; padding: 8px; }
        td { padding: 8px; border-bottom: 1px solid #ddd; }
        tr:nth-child(even) { background: #f8f8f8; }
    """)

    HTML(string=html_content).write_pdf(output, stylesheets=[css])

Template example (templates/report.html):

<!DOCTYPE html>
<html>
<body>
  <h1>{{ title }}</h1>
  <p>Generated: {{ date }}</p>
  <h2>Summary</h2>
  <p>{{ summary }}</p>
  <h2>Data</h2>
  <table>
    <thead><tr>{% for col in columns %}<th>{{ col }}</th>{% endfor %}</tr></thead>
    <tbody>
      {% for row in rows %}
      <tr>{% for cell in row %}<td>{{ cell }}</td>{% endfor %}</tr>
      {% endfor %}
    </tbody>
  </table>
</body>
</html>

Part 5: Table of Contents

from reportlab.platypus import SimpleDocTemplate, Paragraph, PageBreak
from reportlab.platypus.tableofcontents import TableOfContents

class TOCDocTemplate(SimpleDocTemplate):
    def afterFlowable(self, flowable):
        if isinstance(flowable, Paragraph):
            style = flowable.style.name
            if style == "Heading1":
                self.notify("TOCEntry", (0, flowable.getPlainText(), self.page))
            elif style == "Heading2":
                self.notify("TOCEntry", (1, flowable.getPlainText(), self.page))

def build_with_toc(filename: str, sections: list[dict]):
    doc = TOCDocTemplate(filename, pagesize=A4)
    styles = getSampleStyleSheet()
    toc = TableOfContents()
    toc.levelStyles = [
        ParagraphStyle(name="TOC1", fontSize=12, leading=16, leftIndent=20),
        ParagraphStyle(name="TOC2", fontSize=10, leading=14, leftIndent=40),
    ]

    elements = [Paragraph("Table of Contents", styles["Title"]), toc, PageBreak()]
    for section in sections:
        elements.append(Paragraph(section["title"], styles["Heading1"]))
        elements.append(Paragraph(section["content"], styles["BodyText"]))
        for sub in section.get("subsections", []):
            elements.append(Paragraph(sub["title"], styles["Heading2"]))
            elements.append(Paragraph(sub["content"], styles["BodyText"]))
        elements.append(PageBreak())

    doc.multiBuild(elements)

Part 6: Batch Generation

import asyncio
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path

def generate_single_report(args: tuple) -> str:
    record, template, output_dir = args
    output = str(Path(output_dir) / f"report_{record['id']}.pdf")
    html_to_pdf(template, record, output)
    return output

async def batch_generate(records: list[dict], template: str, output_dir: str, workers: int = 4):
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    args_list = [(r, template, output_dir) for r in records]

    loop = asyncio.get_event_loop()
    with ProcessPoolExecutor(max_workers=workers) as pool:
        results = await loop.run_in_executor(
            None,
            lambda: list(pool.map(generate_single_report, args_list)),
        )
    return results

# Generate 500 invoices in parallel
# asyncio.run(batch_generate(invoice_data, "invoice.html", "/tmp/invoices", workers=8))

Part 7: Invoice Template (Complete Example)

def generate_invoice(invoice: dict, output: str):
    doc = SimpleDocTemplate(output, pagesize=letter)
    styles = getSampleStyleSheet()
    elements = []

    # Company header
    elements.append(Paragraph(f"<b>{invoice['company']}</b>", styles["Title"]))
    elements.append(Paragraph(invoice["company_address"], styles["Normal"]))
    elements.append(Spacer(1, 20))

    # Invoice meta
    meta = [
        ["Invoice #:", invoice["number"], "Date:", invoice["date"]],
        ["Bill To:", invoice["client"], "Due:", invoice["due_date"]],
    ]
    elements.append(Table(meta, colWidths=[80, 200, 60, 120]))
    elements.append(Spacer(1, 20))

    # Line items
    items = [["Description", "Qty", "Unit Price", "Total"]]
    for item in invoice["items"]:
        items.append([item["desc"], str(item["qty"]), f"${item['price']:.2f}",
                      f"${item['qty'] * item['price']:.2f}"])
    items.append(["", "", "Subtotal:", f"${invoice['subtotal']:.2f}"])
    items.append(["", "", "Tax:", f"${invoice['tax']:.2f}"])
    items.append(["", "", "Total:", f"${invoice['total']:.2f}"])

    t = Table(items, colWidths=[250, 50, 80, 80])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#0f3460")),
        ("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("GRID", (0, 0), (-1, -4), 0.5, colors.grey),
        ("LINEABOVE", (-2, -3), (-1, -3), 1, colors.black),
        ("FONTNAME", (-2, -1), (-1, -1), "Helvetica-Bold"),
        ("ALIGN", (1, 0), (-1, -1), "RIGHT"),
    ]))
    elements.append(t)

    doc.build(elements, onFirstPage=header_footer, onLaterPages=header_footer)

AXE MCP Server Integration

Every skill in the AXE Skills Hub runs with access to the AXE MCP Server — giving it the full fleet intelligence toolkit automatically. No setup required; tools are available in any AXE-powered session.

Core Tools Available

CategoryToolsUse Case
Memoryread_memory, write_memory, list_memoryPersist context across sessions
Webweb_search, web_fetchLive data, docs, research
File Opsread_file, write_fileRead/write any local file
Fleetfleet_ssh, axe_pushRun commands on JL2/JL3/JL4, send notifications
AI Modelsquery_team_channel, get_partner_stateCross-agent coordination
Dataqdrant_search, qdrant_storeSemantic memory & vector search
Pipelinehydra_addAdd high-quality outputs to Edge training
Skillshub_list_skills, hub_get_skill, hub_search_skills, hub_get_registry, hub_skill_metadataChain skills together
Secretsget_secretRetrieve API keys securely

Quick Start

# In any AXE session, tools are pre-loaded. Example chaining:

# 1. Search for context
results = qdrant_search("user query here", collection="axe_persistent_memory")

# 2. Fetch live data if needed
content = web_fetch("https://docs.example.com/api")

# 3. Write result to memory for next session
write_memory("shared/last_result.md", output)

# 4. Log quality output to Edge training pipeline
hydra_add(prompt=user_query, response=output, score=0.9, source="skill-name")

Edge Training Integration

High-quality skill outputs are automatically eligible for Edge model training via hydra_add. When a response scores ≥0.85 in evals, pipe it to the Hydra pipeline to compound Edge's knowledge. This is how skills make Edge smarter over time.

# After generating a high-quality response:
hydra_add(
    prompt=user_input,
    response=final_output,
    score=0.9,          # eval score
    source="skill-name" # tracks provenance
)

Metadata

Category
Document
Tier
community
Version
1.0.0
License
MIT
Path
skills/pdf-report-generation/SKILL.md

Use with an agent

Fetch this skill’s definition over the open API — no key required.

curl -s /v1/skills/pdf-report-generation

View source ↗