First-party
Below is the complete skill definition this hub loads when the skill is triggered — what the agent sees as its instructions, verbatim and unabridged.
# ReportLab PDF Factory — Professional Report Generation
IMI delivers insight through documents. This skill gives Claude the complete toolkit
for generating professional, branded PDF reports programmatically — from a single-page
topline to a 40-page research report with embedded charts and data tables.
---
## Two PDF Generation Approaches
### Approach 1: ReportLab (Canvas + Platypus)
Best for: pixel-precise layouts, complex multi-column designs, direct chart embedding.
### Approach 2: WeasyPrint (HTML → PDF)
Best for: complex text-heavy documents, when you already have HTML/CSS output,
faster to write but less control over pagination.
---
## ReportLab — Full IMI Report Template
```python
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, inch, cm
from reportlab.lib.colors import HexColor, black, white, grey
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, KeepTogether, HRFlowable, Image, BalancedColumns
)
from reportlab.graphics.shapes import Drawing, Rect, String
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from io import BytesIO
from pathlib import Path
# ──────────────────────────────────────────────
# IMI Brand Colors
# ──────────────────────────────────────────────
IMI_NAVY = HexColor('#1B2B4B')
IMI_BLUE = HexColor('#0066CC')
IMI_TEAL = HexColor('#00A89D')
IMI_ORANGE = HexColor('#F5821E')
IMI_LIGHT_BG = HexColor('#F5F7FA')
IMI_MID_GREY = HexColor('#888888')
IMI_BORDER = HexColor('#DDDDDD')
# ──────────────────────────────────────────────
# Typography Styles
# ──────────────────────────────────────────────
def get_imi_styles():
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
'ImiTitle',
fontSize=28, textColor=IMI_NAVY, spaceAfter=6,
fontName='Helvetica-Bold', leading=34
))
styles.add(ParagraphStyle(
'ImiSubtitle',
fontSize=14, textColor=IMI_TEAL, spaceAfter=12,
fontName='Helvetica', leading=18
))
styles.add(ParagraphStyle(
'ImiH1',
fontSize=16, textColor=IMI_NAVY, spaceBefore=16, spaceAfter=8,
fontName='Helvetica-Bold', leading=20, borderPad=4
))
styles.add(ParagraphStyle(
'ImiH2',
fontSize=13, textColor=IMI_BLUE, spaceBefore=12, spaceAfter=6,
fontName='Helvetica-Bold', leading=16
))
styles.add(ParagraphStyle(
'ImiBody',
fontSize=10, textColor=black, spaceBefore=4, spaceAfter=6,
fontName='Helvetica', leading=14, alignment=TA_JUSTIFY
))
styles.add(ParagraphStyle(
'ImiInsight',
fontSize=11, textColor=IMI_NAVY, spaceBefore=8, spaceAfter=8,
fontName='Helvetica-Bold', leading=15,
backColor=IMI_LIGHT_BG, borderColor=IMI_TEAL, borderWidth=1,
borderPad=8, leftIndent=8
))
styles.add(ParagraphStyle(
'ImiBullet',
fontSize=10, textColor=black, spaceBefore=2, spaceAfter=2,
fontName='Helvetica', leading=14, leftIndent=16, bulletIndent=8
))
styles.add(ParagraphStyle(
'ImiCaption',
fontSize=8, textColor=IMI_MID_GREY, spaceAfter=8,
fontName='Helvetica-Oblique', leading=10, alignment=TA_CENTER
))
return styles
# ──────────────────────────────────────────────
# Header / Footer (Canvas callback)
# ──────────────────────────────────────────────
def make_header_footer(study_name: str, client_name: str, wave: str):
def _draw(canvas, doc):
canvas.saveState()
w, h = A4
# Header bar
canvas.setFillColor(IMI_NAVY)
canvas.rect(0, h - 18*mm, w, 18*mm, fill=1, stroke=0)
canvas.setFillColor(white)
canvas.setFont('Helvetica-Bold', 10)
canvas.drawString(15*mm, h - 11*mm, "IMI International — CONFIDENTIAL")
canvas.setFont('Helvetica', 9)
canvas.drawRightString(w - 15*mm, h - 11*mm, f"{study_name} | {wave}")
# Footer line
canvas.setStrokeColor(IMI_TEAL)
canvas.setLineWidth(1)
canvas.line(15*mm, 14*mm, w - 15*mm, 14*mm)
canvas.setFillColor(IMI_MID_GREY)
canvas.setFont('Helvetica', 8)
canvas.drawString(15*mm, 9*mm, f"© IMI International 2026 | Prepared for {client_name}")
canvas.drawRightString(w - 15*mm, 9*mm, f"Page {doc.page}")
canvas.restoreState()
return _draw
# ──────────────────────────────────────────────
# Tables — metric scorecards
# ──────────────────────────────────────────────
def build_scorecard_table(data: list[dict]) -> Table:
"""
data = [
{'metric': 'Overall Appeal', 'score': 67, 'norm': 58, 'vs_norm': '+9', 'flag': '▲'},
...
]
"""
headers = ['Metric', 'Score', 'Category Norm', 'vs. Norm', 'Status']
rows = [headers] + [
[d['metric'], f"{d['score']}%", f"{d['norm']}%", d['vs_norm'], d['flag']]
for d in data
]
col_widths = [60*mm, 22*mm, 32*mm, 25*mm, 22*mm]
table = Table(rows, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([
# Header
('BACKGROUND', (0, 0), (-1, 0), IMI_NAVY),
('TEXTCOLOR', (0, 0), (-1, 0), white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 9),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
# Data rows
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 9),
('ALIGN', (1, 1), (-1, -1), 'CENTER'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
# Alternating row shading
*[('BACKGROUND', (0, i), (-1, i), IMI_LIGHT_BG) for i in range(2, len(rows), 2)],
# Grid
('GRID', (0, 0), (-1, -1), 0.5, IMI_BORDER),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
('LEFTPADDING', (0, 0), (-1, -1), 6),
]))
return table
# ──────────────────────────────────────────────
# Charts — bar charts for concept scores
# ──────────────────────────────────────────────
def build_bar_chart(labels: list[str], values: list[float], norm: float,
title: str, width: float = 130*mm, height: float = 70*mm) -> Drawing:
drawing = Drawing(width, height)
chart = VerticalBarChart()
chart.x = 20
chart.y = 20
chart.width = width - 40
chart.height = height - 50
chart.data = [values]
chart.categoryAxis.categoryNames = labels
chart.categoryAxis.labels.fontSize = 8
chart.valueAxis.labels.fontSize = 8
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = max(max(values), norm) * 1.15
chart.bars[0].fillColor = IMI_TEAL
# Norm reference line
from reportlab.graphics.shapes import Line
norm_y = chart.y + (norm / chart.valueAxis.valueMax) * chart.height
drawing.add(Line(chart.x, norm_y, chart.x + chart.width, norm_y,
strokeColor=IMI_ORANGE, strokeWidth=1.5, strokeDashArray=[4, 2]))
# Title
drawing.add(String(width / 2, height - 15, title, textAnchor='middle',
fontSize=9, fontName='Helvetica-Bold', fillColor=IMI_NAVY))
drawing.add(chart)
return drawing
# ──────────────────────────────────────────────
# Full Report Generator
# ──────────────────────────────────────────────
def generate_topline_pdf(
output_path: str,
study_name: str,
client_name: str,
wave: str,
essential_insight: str,
findings: list[dict],
recommendation: str,
scorecard_data: list[dict] | None = None,
) -> str:
styles = get_imi_styles()
doc = SimpleDocTemplate(
output_path, pagesize=A4,
leftMargin=15*mm, rightMargin=15*mm,
topMargin=25*mm, bottomMargin=20*mm
)
story = []
# Cover block
story.append(Paragraph(f"{study_name}", styles['ImiTitle']))
story.append(Paragraph(f"Topline Report | {wave} | Prepared for {client_name}", styles['ImiSubtitle']))
story.append(HRFlowable(width="100%", thickness=2, color=IMI_TEAL, spaceAfter=12))
# Essential insight callout
story.append(Paragraph("Essential Insight", styles['ImiH1']))
story.append(Paragraph(essential_insight, styles['ImiInsight']))
story.append(Spacer(1, 8*mm))
# Scorecard
if scorecard_data:
story.append(Paragraph("Key Metric Performance", styles['ImiH1']))
story.append(build_scorecard_table(scorecard_data))
story.append(Spacer(1, 8*mm))
# Key findings
story.append(Paragraph("Key Findings", styles['ImiH1']))
for i, finding in enumerate(findings, 1):
story.append(KeepTogether([
Paragraph(f"{i}. {finding['title']}", styles['ImiH2']),
Paragraph(finding['body'], styles['ImiBody']),
Paragraph(f"Base: n={finding.get('n', 'N/A')} | {finding.get('base_flag', '')}",
styles['ImiCaption']),
Spacer(1, 4*mm)
]))
# Recommendation
story.append(PageBreak())
story.append(Paragraph("IMI Recommendation", styles['ImiH1']))
story.append(Paragraph(recommendation, styles['ImiInsight']))
doc.build(story, onFirstPage=make_header_footer(study_name, client_name, wave),
onLaterPages=make_header_footer(study_name, client_name, wave))
return output_path
```
---
## WeasyPrint — HTML to PDF (Simpler Path)
```python
# pip install weasyprint
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
def html_to_pdf(html_content: str, output_path: str, base_url: str | None = None) -> str:
"""Convert HTML string to PDF with WeasyPrint."""
font_config = FontConfiguration()
# IMI brand CSS
imi_css = CSS(string="""
@page {
margin: 15mm 15mm 20mm 15mm;
@top-center { content: "IMI International — CONFIDENTIAL"; font-size: 8pt; }
@bottom-right { content: "Page " counter(page); font-size: 8pt; }
}
body { font-family: Arial, Helvetica, sans-serif; font-size: 10pt; color: #333; }
h1 { color: #1B2B4B; border-bottom: 2px solid #00A89D; padding-bottom: 4px; }
h2 { color: #0066CC; }
.insight-box {
background: #F5F7FA; border-left: 4px solid #00A89D;
padding: 12px; margin: 12px 0; font-weight: bold;
}
table { width: 100%; border-collapse: collapse; font-size: 9pt; }
th { background: #1B2B4B; color: white; padding: 6px; }
td { padding: 5px; border: 1px solid #DDD; }
tr:nth-child(even) { background: #F5F7FA; }
.above-norm { color: #00A89D; font-weight: bold; }
.below-norm { color: #CC3300; font-weight: bold; }
.directional { color: #888; font-style: italic; }
""", font_config=font_config)
HTML(string=html_content, base_url=base_url).write_pdf(
output_path,
stylesheets=[imi_css],
font_config=font_config
)
return output_path
```
---
## Quick Reference: PDF Generation Decision Tree
```
Need a PDF? Start here:
├── Is the content data-heavy (tables, charts, scorecards)?
│ → ReportLab Platypus (full control)
├── Is the content text-heavy (report, proposal, topline prose)?
│ → WeasyPrint (write HTML → convert) — faster to develop
├── Is there an existing HTML/Jinja2 template?
│ → WeasyPrint — drop-in
├── Need pixel-precise brand control?
│ → ReportLab Canvas (lower level but maximum control)
└── Merging/splitting existing PDFs?
→ pypdf (see pdf skill)
```
---
*See also: python-data-engine (data for the reports), pptx-engine (slide decks), excel-automation (spreadsheet exports)*
## 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
| Category | Tools | Use Case |
|----------|-------|----------|
| **Memory** | `read_memory`, `write_memory`, `list_memory` | Persist context across sessions |
| **Web** | `web_search`, `web_fetch` | Live data, docs, research |
| **File Ops** | `read_file`, `write_file` | Read/write any local file |
| **Fleet** | `fleet_ssh`, `axe_push` | Run commands on JL2/JL3/JL4, send notifications |
| **AI Models** | `query_team_channel`, `get_partner_state` | Cross-agent coordination |
| **Data** | `qdrant_search`, `qdrant_store` | Semantic memory & vector search |
| **Pipeline** | `hydra_add` | Add high-quality outputs to Edge training |
| **Skills** | `hub_list_skills`, `hub_get_skill`, `hub_search_skills`, `hub_get_registry`, `hub_skill_metadata` | Chain skills together |
| **Secrets** | `get_secret` | Retrieve API keys securely |
### Quick Start
```python
# 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.
```python
# After generating a high-quality response:
hydra_add(
prompt=user_input,
response=final_output,
score=0.9, # eval score
source="skill-name" # tracks provenance
)
```IMI delivers insight through documents. This skill gives Claude the complete toolkit
for generating professional, branded PDF reports programmatically — from a single-page
topline to a 40-page research report with embedded charts and data tables.
Best for: pixel-precise layouts, complex multi-column designs, direct chart embedding.
Best for: complex text-heavy documents, when you already have HTML/CSS output,
faster to write but less control over pagination.
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, inch, cm
from reportlab.lib.colors import HexColor, black, white, grey
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, KeepTogether, HRFlowable, Image, BalancedColumns
)
from reportlab.graphics.shapes import Drawing, Rect, String
from reportlab.graphics.charts.barcharts import VerticalBarChart
from reportlab.graphics.charts.linecharts import HorizontalLineChart
from io import BytesIO
from pathlib import Path
# ──────────────────────────────────────────────
# IMI Brand Colors
# ──────────────────────────────────────────────
IMI_NAVY = HexColor('#1B2B4B')
IMI_BLUE = HexColor('#0066CC')
IMI_TEAL = HexColor('#00A89D')
IMI_ORANGE = HexColor('#F5821E')
IMI_LIGHT_BG = HexColor('#F5F7FA')
IMI_MID_GREY = HexColor('#888888')
IMI_BORDER = HexColor('#DDDDDD')
# ──────────────────────────────────────────────
# Typography Styles
# ──────────────────────────────────────────────
def get_imi_styles():
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
'ImiTitle',
fontSize=28, textColor=IMI_NAVY, spaceAfter=6,
fontName='Helvetica-Bold', leading=34
))
styles.add(ParagraphStyle(
'ImiSubtitle',
fontSize=14, textColor=IMI_TEAL, spaceAfter=12,
fontName='Helvetica', leading=18
))
styles.add(ParagraphStyle(
'ImiH1',
fontSize=16, textColor=IMI_NAVY, spaceBefore=16, spaceAfter=8,
fontName='Helvetica-Bold', leading=20, borderPad=4
))
styles.add(ParagraphStyle(
'ImiH2',
fontSize=13, textColor=IMI_BLUE, spaceBefore=12, spaceAfter=6,
fontName='Helvetica-Bold', leading=16
))
styles.add(ParagraphStyle(
'ImiBody',
fontSize=10, textColor=black, spaceBefore=4, spaceAfter=6,
fontName='Helvetica', leading=14, alignment=TA_JUSTIFY
))
styles.add(ParagraphStyle(
'ImiInsight',
fontSize=11, textColor=IMI_NAVY, spaceBefore=8, spaceAfter=8,
fontName='Helvetica-Bold', leading=15,
backColor=IMI_LIGHT_BG, borderColor=IMI_TEAL, borderWidth=1,
borderPad=8, leftIndent=8
))
styles.add(ParagraphStyle(
'ImiBullet',
fontSize=10, textColor=black, spaceBefore=2, spaceAfter=2,
fontName='Helvetica', leading=14, leftIndent=16, bulletIndent=8
))
styles.add(ParagraphStyle(
'ImiCaption',
fontSize=8, textColor=IMI_MID_GREY, spaceAfter=8,
fontName='Helvetica-Oblique', leading=10, alignment=TA_CENTER
))
return styles
# ──────────────────────────────────────────────
# Header / Footer (Canvas callback)
# ──────────────────────────────────────────────
def make_header_footer(study_name: str, client_name: str, wave: str):
def _draw(canvas, doc):
canvas.saveState()
w, h = A4
# Header bar
canvas.setFillColor(IMI_NAVY)
canvas.rect(0, h - 18*mm, w, 18*mm, fill=1, stroke=0)
canvas.setFillColor(white)
canvas.setFont('Helvetica-Bold', 10)
canvas.drawString(15*mm, h - 11*mm, "IMI International — CONFIDENTIAL")
canvas.setFont('Helvetica', 9)
canvas.drawRightString(w - 15*mm, h - 11*mm, f"{study_name} | {wave}")
# Footer line
canvas.setStrokeColor(IMI_TEAL)
canvas.setLineWidth(1)
canvas.line(15*mm, 14*mm, w - 15*mm, 14*mm)
canvas.setFillColor(IMI_MID_GREY)
canvas.setFont('Helvetica', 8)
canvas.drawString(15*mm, 9*mm, f"© IMI International 2026 | Prepared for {client_name}")
canvas.drawRightString(w - 15*mm, 9*mm, f"Page {doc.page}")
canvas.restoreState()
return _draw
# ──────────────────────────────────────────────
# Tables — metric scorecards
# ──────────────────────────────────────────────
def build_scorecard_table(data: list[dict]) -> Table:
"""
data = [
{'metric': 'Overall Appeal', 'score': 67, 'norm': 58, 'vs_norm': '+9', 'flag': '▲'},
...
]
"""
headers = ['Metric', 'Score', 'Category Norm', 'vs. Norm', 'Status']
rows = [headers] + [
[d['metric'], f"{d['score']}%", f"{d['norm']}%", d['vs_norm'], d['flag']]
for d in data
]
col_widths = [60*mm, 22*mm, 32*mm, 25*mm, 22*mm]
table = Table(rows, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([
# Header
('BACKGROUND', (0, 0), (-1, 0), IMI_NAVY),
('TEXTCOLOR', (0, 0), (-1, 0), white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 9),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
# Data rows
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 9),
('ALIGN', (1, 1), (-1, -1), 'CENTER'),
('ALIGN', (0, 1), (0, -1), 'LEFT'),
# Alternating row shading
*[('BACKGROUND', (0, i), (-1, i), IMI_LIGHT_BG) for i in range(2, len(rows), 2)],
# Grid
('GRID', (0, 0), (-1, -1), 0.5, IMI_BORDER),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
('LEFTPADDING', (0, 0), (-1, -1), 6),
]))
return table
# ──────────────────────────────────────────────
# Charts — bar charts for concept scores
# ──────────────────────────────────────────────
def build_bar_chart(labels: list[str], values: list[float], norm: float,
title: str, width: float = 130*mm, height: float = 70*mm) -> Drawing:
drawing = Drawing(width, height)
chart = VerticalBarChart()
chart.x = 20
chart.y = 20
chart.width = width - 40
chart.height = height - 50
chart.data = [values]
chart.categoryAxis.categoryNames = labels
chart.categoryAxis.labels.fontSize = 8
chart.valueAxis.labels.fontSize = 8
chart.valueAxis.valueMin = 0
chart.valueAxis.valueMax = max(max(values), norm) * 1.15
chart.bars[0].fillColor = IMI_TEAL
# Norm reference line
from reportlab.graphics.shapes import Line
norm_y = chart.y + (norm / chart.valueAxis.valueMax) * chart.height
drawing.add(Line(chart.x, norm_y, chart.x + chart.width, norm_y,
strokeColor=IMI_ORANGE, strokeWidth=1.5, strokeDashArray=[4, 2]))
# Title
drawing.add(String(width / 2, height - 15, title, textAnchor='middle',
fontSize=9, fontName='Helvetica-Bold', fillColor=IMI_NAVY))
drawing.add(chart)
return drawing
# ──────────────────────────────────────────────
# Full Report Generator
# ──────────────────────────────────────────────
def generate_topline_pdf(
output_path: str,
study_name: str,
client_name: str,
wave: str,
essential_insight: str,
findings: list[dict],
recommendation: str,
scorecard_data: list[dict] | None = None,
) -> str:
styles = get_imi_styles()
doc = SimpleDocTemplate(
output_path, pagesize=A4,
leftMargin=15*mm, rightMargin=15*mm,
topMargin=25*mm, bottomMargin=20*mm
)
story = []
# Cover block
story.append(Paragraph(f"{study_name}", styles['ImiTitle']))
story.append(Paragraph(f"Topline Report | {wave} | Prepared for {client_name}", styles['ImiSubtitle']))
story.append(HRFlowable(width="100%", thickness=2, color=IMI_TEAL, spaceAfter=12))
# Essential insight callout
story.append(Paragraph("Essential Insight", styles['ImiH1']))
story.append(Paragraph(essential_insight, styles['ImiInsight']))
story.append(Spacer(1, 8*mm))
# Scorecard
if scorecard_data:
story.append(Paragraph("Key Metric Performance", styles['ImiH1']))
story.append(build_scorecard_table(scorecard_data))
story.append(Spacer(1, 8*mm))
# Key findings
story.append(Paragraph("Key Findings", styles['ImiH1']))
for i, finding in enumerate(findings, 1):
story.append(KeepTogether([
Paragraph(f"{i}. {finding['title']}", styles['ImiH2']),
Paragraph(finding['body'], styles['ImiBody']),
Paragraph(f"Base: n={finding.get('n', 'N/A')} | {finding.get('base_flag', '')}",
styles['ImiCaption']),
Spacer(1, 4*mm)
]))
# Recommendation
story.append(PageBreak())
story.append(Paragraph("IMI Recommendation", styles['ImiH1']))
story.append(Paragraph(recommendation, styles['ImiInsight']))
doc.build(story, onFirstPage=make_header_footer(study_name, client_name, wave),
onLaterPages=make_header_footer(study_name, client_name, wave))
return output_path
# pip install weasyprint
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
def html_to_pdf(html_content: str, output_path: str, base_url: str | None = None) -> str:
"""Convert HTML string to PDF with WeasyPrint."""
font_config = FontConfiguration()
# IMI brand CSS
imi_css = CSS(string="""
@page {
margin: 15mm 15mm 20mm 15mm;
@top-center { content: "IMI International — CONFIDENTIAL"; font-size: 8pt; }
@bottom-right { content: "Page " counter(page); font-size: 8pt; }
}
body { font-family: Arial, Helvetica, sans-serif; font-size: 10pt; color: #333; }
h1 { color: #1B2B4B; border-bottom: 2px solid #00A89D; padding-bottom: 4px; }
h2 { color: #0066CC; }
.insight-box {
background: #F5F7FA; border-left: 4px solid #00A89D;
padding: 12px; margin: 12px 0; font-weight: bold;
}
table { width: 100%; border-collapse: collapse; font-size: 9pt; }
th { background: #1B2B4B; color: white; padding: 6px; }
td { padding: 5px; border: 1px solid #DDD; }
tr:nth-child(even) { background: #F5F7FA; }
.above-norm { color: #00A89D; font-weight: bold; }
.below-norm { color: #CC3300; font-weight: bold; }
.directional { color: #888; font-style: italic; }
""", font_config=font_config)
HTML(string=html_content, base_url=base_url).write_pdf(
output_path,
stylesheets=[imi_css],
font_config=font_config
)
return output_path
Need a PDF? Start here:
├── Is the content data-heavy (tables, charts, scorecards)?
│ → ReportLab Platypus (full control)
├── Is the content text-heavy (report, proposal, topline prose)?
│ → WeasyPrint (write HTML → convert) — faster to develop
├── Is there an existing HTML/Jinja2 template?
│ → WeasyPrint — drop-in
├── Need pixel-precise brand control?
│ → ReportLab Canvas (lower level but maximum control)
└── Merging/splitting existing PDFs?
→ pypdf (see pdf skill)
*See also: python-data-engine (data for the reports), pptx-engine (slide decks), excel-automation (spreadsheet exports)*
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.
| Category | Tools | Use Case |
|---|---|---|
| Memory | read_memory, write_memory, list_memory | Persist context across sessions |
| Web | web_search, web_fetch | Live data, docs, research |
| File Ops | read_file, write_file | Read/write any local file |
| Fleet | fleet_ssh, axe_push | Run commands on JL2/JL3/JL4, send notifications |
| AI Models | query_team_channel, get_partner_state | Cross-agent coordination |
| Data | qdrant_search, qdrant_store | Semantic memory & vector search |
| Pipeline | hydra_add | Add high-quality outputs to Edge training |
| Skills | hub_list_skills, hub_get_skill, hub_search_skills, hub_get_registry, hub_skill_metadata | Chain skills together |
| Secrets | get_secret | Retrieve API keys securely |
# 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")
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
)
Fetch this skill’s definition over the open API — no key required.
curl -s /v1/skills/reportlab-pdf-factory