6.4: Report Generation

Overview

Automated report generation creates ASPICE work products on demand or on schedule. Templates combine data from multiple sources (requirements management systems, version control, test frameworks, static analysis tools) into professional PDF/HTML reports for stakeholders and auditors. In safety-critical embedded systems development, report generation serves dual purposes: providing management visibility into project health and generating audit-ready documentation demonstrating ASPICE compliance.

Modern report generation systems transform raw engineering data into structured narratives that satisfy both human decision-making needs and regulatory evidence requirements. By automating this transformation, organizations reduce manual effort, eliminate transcription errors, and ensure consistency across reporting periods.

Cross-Reference: For ASPICE work product requirements, see Part II ASPICE Processes and Appendix B Work Product Templates.


Report Types in ASPICE Environments

ASPICE-compliant development generates numerous report types across the development lifecycle. Understanding which reports support which processes enables targeted automation.

Traceability Reports

Purpose: Demonstrate bidirectional traceability between artifacts (ISO 26262-6 Clause 7, ASPICE SWE.5 BP7).

Content:

  • Requirements trace matrices (customer → system → software → hardware)
  • Test coverage matrices (requirement → test case → test result)
  • Change impact analysis (modified requirement → affected components)
  • Safety requirement traceability (ASIL-rated requirements → implementation → verification)

Data Sources:

  • Requirements Management System (Jama, DOORS, Polarion, Codebeamer)
  • Test management tools (TestRail, qTest, Xray)
  • Static analysis tools (trace annotations in source code)

Generation Frequency: On-demand for audits, weekly for project reviews.

Code Quality and Coverage Reports

Purpose: Demonstrate software verification activities (ASPICE SWE.6, ISO 26262-6 Clause 9).

Content:

  • Static analysis findings (MISRA C/C++ violations, CERT violations)
  • Code coverage metrics (statement, branch, MC/DC for safety-critical)
  • Cyclomatic complexity trends
  • Unit test results with pass/fail statistics

Data Sources:

  • Static analysis tools (Coverity, Klocwork, SonarQube, PC-lint)
  • Coverage tools (Bullseye, gcov, VectorCAST)
  • Unit test frameworks (Google Test, CppUTest, Unity)

Generation Frequency: Nightly builds, PR merge gates, milestone releases.

Process Compliance Reports

Purpose: Demonstrate adherence to defined processes (ASPICE SUP.1 BP6, MAN.3).

Content:

  • Work product completeness (percentage of deliverables completed)
  • Review participation rates (design reviews, code reviews)
  • Defect density and resolution time
  • Process tailoring deviations and justifications

Data Sources:

  • Project management tools (Jira, Azure DevOps)
  • Code review systems (Gerrit, GitLab, GitHub)
  • Quality management databases

Generation Frequency: Weekly for management, monthly for audits.

Risk and Issue Management Reports

Purpose: Track project risks and mitigation actions (ASPICE MAN.5).

Content:

  • Open risk register with severity, likelihood, mitigation status
  • Issue aging and escalation paths
  • Technical debt trends
  • Safety hazard tracking (for ISO 26262 projects)

Data Sources:

  • Risk management tools (RiskWatch, Archer)
  • Issue tracking systems (Jira, Redmine)
  • Safety analysis tools (Medini Analyze, APIS IQ-Software)

Generation Frequency: Weekly for active projects, monthly for stable maintenance.

Configuration Management Reports

Purpose: Demonstrate configuration control (ASPICE SUP.8).

Content:

  • Baseline inventories (software versions, library dependencies, tool versions)
  • Change request summaries (approved, implemented, verified)
  • Build reproducibility evidence (build environment snapshots)
  • Release notes and version history

Data Sources:

  • Version control systems (Git, SVN)
  • Artifact repositories (Artifactory, Nexus)
  • Build systems (Jenkins, GitLab CI, Bamboo)

Generation Frequency: Per release, monthly for continuous delivery.


Template Engine Comparison

Selecting the right template engine balances expressiveness, maintainability, and toolchain integration.

Feature Jinja2 Mustache Mako ERB
Language Python Language-agnostic Python Ruby
Logic Support Full (conditionals, loops, filters) Logic-less (minimal) Full (embedded Python) Full (embedded Ruby)
Syntax Complexity Moderate ({% if %}, {{ var }}) Simple ({{var}}, {{#section}}) High (arbitrary Python) Moderate (<% %>, <%= %>)
Security Auto-escaping configurable Auto-escaping by default Manual escaping required Manual escaping required
Template Inheritance Yes (extends, blocks) No Yes No
Performance Fast (compiled to Python bytecode) Very fast (minimal logic) Very fast (compiled) Fast
Ecosystem Huge (Flask, Ansible, Salt) Wide (dozens of implementations) Medium (Pyramid) Ruby ecosystem
ASPICE Use Case Complex reports with nested data Simple data-driven templates Dynamic computation-heavy reports Ruby-based toolchains

Recommendation:

  • Jinja2: Default choice for Python-based CI/CD environments. Excellent balance of power and readability.
  • Mustache: Use when templates must be shared across multiple languages (Python, JavaScript, Java) or when enforcing strict separation of logic and presentation.
  • Mako: Only for reports requiring heavy in-template computation (avoid for maintainability).

PDF Generation Technologies

Converting rendered HTML/Markdown templates to PDF requires choosing between browser-based, native library, or LaTeX-based approaches.

Browser-Based Rendering (wkhtmltopdf, Puppeteer, Playwright)

Mechanism: Render HTML/CSS in headless browser, print to PDF.

Advantages:

  • Excellent CSS support (flexbox, grid, modern layouts)
  • Handles web fonts, images, SVG diagrams
  • WYSIWYG: Browser preview matches PDF output
  • JavaScript execution for dynamic charts (Chart.js, Plotly)

Disadvantages:

  • Requires browser installation (Chromium, WebKit)
  • Larger resource footprint (memory, CPU)
  • Security concerns (rendering untrusted content)

Best For: Reports with complex layouts, embedded charts, or web-based dashboards.

Example Tools:

  • Puppeteer/Playwright: Node.js control of headless Chrome/Chromium
  • wkhtmltopdf: Standalone binary using WebKit
  • WeasyPrint: Python library with CSS Paged Media support

Native PDF Libraries (ReportLab, FPDF, PyPDF2)

Mechanism: Programmatic PDF construction via drawing primitives.

Advantages:

  • No external dependencies (pure Python/library)
  • Precise control over layout (pixel-perfect positioning)
  • Small footprint, fast generation
  • Security: No code execution risk

Disadvantages:

  • Manual layout calculations (no CSS)
  • Limited text reflow and formatting
  • Steeper learning curve for complex layouts

Best For: Simple tabular reports, form-based documents, or highly standardized layouts.

Example Tools:

  • ReportLab: Python PDF generation with flowable layouts
  • FPDF: PHP/Python simple PDF creation
  • PyPDF2: PDF manipulation (merge, split, watermark)

LaTeX-Based Generation (Pandoc + LaTeX, Sphinx)

Mechanism: Convert Markdown/RST to LaTeX, compile to PDF.

Advantages:

  • Professional typesetting (kerning, ligatures, hyphenation)
  • Excellent for technical documents (equations, code listings, cross-references)
  • Consistent multi-page layout (headers, footers, TOC)
  • Version-controllable source (Markdown/LaTeX)

Disadvantages:

  • Requires LaTeX distribution (TeX Live, MiKTeX)
  • Steep learning curve for custom templates
  • Slower compilation for large documents
  • Less control over pixel-precise layout

Best For: Technical specification documents, standards documentation, or book-length reports.

Example Tools:

  • Pandoc: Universal document converter (Markdown → PDF via LaTeX)
  • Sphinx: Documentation generator with PDF output (reStructuredText)
  • Asciidoctor-PDF: AsciiDoc to PDF (embedded Ruby)

Implementation Workflow

Automated report generation follows a multi-stage pipeline: data extraction, aggregation, template rendering, and distribution.

Report Generation Pipeline

Stage 1: Data Extraction

Extract raw data from heterogeneous sources via APIs, database queries, or file parsing.

Requirements Management System (RMS):

# Example: Extract requirements from Jama Connect API
import requests

def fetch_jama_requirements(project_id, api_token):
    """Fetch all requirements for a project from Jama Connect."""
    url = f"https://company.jamacloud.com/rest/latest/items"
    headers = {"Authorization": f"Bearer {api_token}"}
    params = {"project": project_id, "itemType": "requirement"}

    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()

    return [
        {
            "id": item["id"],
            "key": item["documentKey"],
            "name": item["fields"]["name"],
            "description": item["fields"]["description"],
            "status": item["fields"]["status"],
            "asil": item["fields"].get("asil", "QM"),
        }
        for item in response.json()["data"]
    ]

Version Control Metrics (Git):

# Example: Extract commit activity and code churn
import subprocess
import json
from datetime import datetime, timedelta

def get_git_metrics(repo_path, since_days=30):
    """Extract Git commit and churn metrics."""
    since_date = (datetime.now() - timedelta(days=since_days)).strftime("%Y-%m-%d")

    # Commit count by author
    result = subprocess.run(
        ["git", "-C", repo_path, "shortlog", "-sn", f"--since={since_date}"],
        capture_output=True, text=True, check=True
    )
    commits_by_author = [
        {"count": int(line.split()[0]), "author": " ".join(line.split()[1:])}
        for line in result.stdout.strip().split("\n")
    ]

    # Code churn (lines added/deleted)
    result = subprocess.run(
        ["git", "-C", repo_path, "log", f"--since={since_date}",
         "--numstat", "--pretty=format:"],
        capture_output=True, text=True, check=True
    )

    total_added = total_deleted = 0
    for line in result.stdout.strip().split("\n"):
        if line:
            parts = line.split()
            if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit():
                total_added += int(parts[0])
                total_deleted += int(parts[1])

    return {
        "commits_by_author": commits_by_author,
        "total_added": total_added,
        "total_deleted": total_deleted,
        "churn": total_added + total_deleted,
    }

Test Results (JUnit XML):

# Example: Parse JUnit XML test results
import xml.etree.ElementTree as ET

def parse_junit_results(xml_file):
    """Parse JUnit XML test results."""
    tree = ET.parse(xml_file)
    root = tree.getroot()

    results = {
        "total": int(root.attrib.get("tests", 0)),
        "passed": 0,
        "failed": int(root.attrib.get("failures", 0)),
        "errors": int(root.attrib.get("errors", 0)),
        "skipped": int(root.attrib.get("skipped", 0)),
        "time": float(root.attrib.get("time", 0.0)),
        "test_cases": []
    }

    for testcase in root.findall(".//testcase"):
        case_name = testcase.attrib.get("name")
        case_time = float(testcase.attrib.get("time", 0.0))
        failure = testcase.find("failure")
        error = testcase.find("error")
        skipped = testcase.find("skipped")

        status = "passed"
        message = None
        if failure is not None:
            status = "failed"
            message = failure.attrib.get("message", "")
        elif error is not None:
            status = "error"
            message = error.attrib.get("message", "")
        elif skipped is not None:
            status = "skipped"
            message = skipped.attrib.get("message", "")

        if status == "passed":
            results["passed"] += 1

        results["test_cases"].append({
            "name": case_name,
            "time": case_time,
            "status": status,
            "message": message
        })

    return results

Stage 2: Data Aggregation and Transformation

Combine data from multiple sources into a unified data model.

# Example: Aggregate data for weekly status report
from datetime import datetime
from typing import Dict, Any

def build_weekly_report_data(
    requirements_data: list,
    git_metrics: dict,
    test_results: dict,
    build_status: dict
) -> Dict[str, Any]:
    """Aggregate data for weekly project status report."""

    # Requirement completion metrics
    total_reqs = len(requirements_data)
    completed_reqs = sum(1 for r in requirements_data if r["status"] == "Approved")
    completion_rate = (completed_reqs / total_reqs * 100) if total_reqs > 0 else 0

    # Safety-critical requirement tracking
    asil_breakdown = {}
    for req in requirements_data:
        asil = req.get("asil", "QM")
        asil_breakdown[asil] = asil_breakdown.get(asil, 0) + 1

    # Test metrics
    test_pass_rate = (
        test_results["passed"] / test_results["total"] * 100
        if test_results["total"] > 0 else 0
    )

    # Build health
    build_success_rate = (
        build_status.get("successful_builds", 0) /
        build_status.get("total_builds", 1) * 100
    )

    return {
        "report_date": datetime.now().strftime("%Y-%m-%d"),
        "report_week": datetime.now().isocalendar()[1],
        "requirements": {
            "total": total_reqs,
            "completed": completed_reqs,
            "completion_rate": round(completion_rate, 1),
            "asil_breakdown": asil_breakdown,
        },
        "development_activity": {
            "commits": sum(c["count"] for c in git_metrics["commits_by_author"]),
            "top_contributors": git_metrics["commits_by_author"][:5],
            "code_churn": git_metrics["churn"],
            "lines_added": git_metrics["total_added"],
            "lines_deleted": git_metrics["total_deleted"],
        },
        "quality_metrics": {
            "test_total": test_results["total"],
            "test_passed": test_results["passed"],
            "test_failed": test_results["failed"],
            "test_pass_rate": round(test_pass_rate, 1),
            "build_success_rate": round(build_success_rate, 1),
        },
    }

Stage 3: Template Rendering

Apply data to Jinja2 template to generate HTML report.

Jinja2 Template Example (weekly_status.html.j2):

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Weekly Status Report - Week {{ report_week }}</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 40px;
            color: #333;
        }
        h1 {
            color: #005a9c;
            border-bottom: 3px solid #005a9c;
            padding-bottom: 10px;
        }
        h2 {
            color: #007acc;
            margin-top: 30px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 12px;
            text-align: left;
        }
        th {
            background-color: #005a9c;
            color: white;
        }
        tr:nth-child(even) {
            background-color: #f9f9f9;
        }
        .metric {
            display: inline-block;
            background: #e7f3ff;
            padding: 10px 20px;
            margin: 10px 10px 10px 0;
            border-radius: 5px;
            border-left: 4px solid #005a9c;
        }
        .metric-label {
            font-size: 12px;
            color: #666;
            text-transform: uppercase;
        }
        .metric-value {
            font-size: 24px;
            font-weight: bold;
            color: #005a9c;
        }
        .status-good { color: #28a745; }
        .status-warning { color: #ffc107; }
        .status-critical { color: #dc3545; }
    </style>
</head>
<body>
    <h1>Weekly Project Status Report</h1>
    <p><strong>Report Date:</strong> {{ report_date }}</p>
    <p><strong>Report Week:</strong> {{ report_week }}</p>

    <h2>Requirements Completion</h2>
    <div>
        <div class="metric">
            <div class="metric-label">Total Requirements</div>
            <div class="metric-value">{{ requirements.total }}</div>
        </div>
        <div class="metric">
            <div class="metric-label">Completed</div>
            <div class="metric-value">{{ requirements.completed }}</div>
        </div>
        <div class="metric">
            <div class="metric-label">Completion Rate</div>
            <div class="metric-value
                {% if requirements.completion_rate >= 80 %}status-good
                {% elif requirements.completion_rate >= 60 %}status-warning
                {% else %}status-critical{% endif %}">
                {{ requirements.completion_rate }}%
            </div>
        </div>
    </div>

    <h3>ASIL Breakdown</h3>
    <table>
        <thead>
            <tr>
                <th>ASIL Level</th>
                <th>Count</th>
                <th>Percentage</th>
            </tr>
        </thead>
        <tbody>
            {% for asil, count in requirements.asil_breakdown.items() %}
            <tr>
                <td><strong>{{ asil }}</strong></td>
                <td>{{ count }}</td>
                <td>{{ (count / requirements.total * 100) | round(1) }}%</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <h2>Development Activity</h2>
    <div>
        <div class="metric">
            <div class="metric-label">Commits</div>
            <div class="metric-value">{{ development_activity.commits }}</div>
        </div>
        <div class="metric">
            <div class="metric-label">Code Churn</div>
            <div class="metric-value">{{ development_activity.code_churn }}</div>
        </div>
    </div>

    <h3>Top Contributors</h3>
    <table>
        <thead>
            <tr>
                <th>Author</th>
                <th>Commits</th>
            </tr>
        </thead>
        <tbody>
            {% for contributor in development_activity.top_contributors %}
            <tr>
                <td>{{ contributor.author }}</td>
                <td>{{ contributor.count }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <h2>Quality Metrics</h2>
    <div>
        <div class="metric">
            <div class="metric-label">Test Pass Rate</div>
            <div class="metric-value
                {% if quality_metrics.test_pass_rate >= 95 %}status-good
                {% elif quality_metrics.test_pass_rate >= 85 %}status-warning
                {% else %}status-critical{% endif %}">
                {{ quality_metrics.test_pass_rate }}%
            </div>
        </div>
        <div class="metric">
            <div class="metric-label">Build Success Rate</div>
            <div class="metric-value
                {% if quality_metrics.build_success_rate >= 90 %}status-good
                {% elif quality_metrics.build_success_rate >= 75 %}status-warning
                {% else %}status-critical{% endif %}">
                {{ quality_metrics.build_success_rate }}%
            </div>
        </div>
    </div>

    <hr style="margin-top: 40px;">
    <p style="font-size: 12px; color: #999;">
        Generated automatically on {{ report_date }} by ASPICE Report Generator v2.0.0
    </p>
</body>
</html>

Rendering Script:

# Example: Render template with Jinja2
from jinja2 import Environment, FileSystemLoader

def render_report(template_name: str, data: dict, output_file: str):
    """Render Jinja2 template to HTML."""
    env = Environment(loader=FileSystemLoader("templates/"))
    template = env.get_template(template_name)

    html_content = template.render(**data)

    with open(output_file, "w", encoding="utf-8") as f:
        f.write(html_content)

    print(f"Report generated: {output_file}")

# Usage
report_data = build_weekly_report_data(
    requirements_data, git_metrics, test_results, build_status
)
render_report("weekly_status.html.j2", report_data, "reports/weekly_status.html")

Stage 4: PDF Conversion

Convert HTML to PDF using WeasyPrint (CSS Paged Media support).

# Example: Convert HTML to PDF using WeasyPrint
from weasyprint import HTML, CSS

def html_to_pdf(html_file: str, pdf_file: str, css_file: str = None):
    """Convert HTML to PDF using WeasyPrint."""
    html = HTML(filename=html_file)

    stylesheets = []
    if css_file:
        stylesheets.append(CSS(filename=css_file))

    html.write_pdf(pdf_file, stylesheets=stylesheets)
    print(f"PDF generated: {pdf_file}")

# Usage
html_to_pdf("reports/weekly_status.html", "reports/weekly_status.pdf")

Alternative: Puppeteer (Node.js):

// Convert HTML to PDF using Puppeteer
const puppeteer = require('puppeteer');

async function htmlToPdf(htmlFile, pdfFile) {
    const browser = await puppeteer.launch({
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox']
    });
    const page = await browser.newPage();

    await page.goto(`file://${htmlFile}`, { waitUntil: 'networkidle0' });

    await page.pdf({
        path: pdfFile,
        format: 'A4',
        printBackground: true,
        margin: {
            top: '20mm',
            right: '15mm',
            bottom: '20mm',
            left: '15mm'
        }
    });

    await browser.close();
    console.log(`PDF generated: ${pdfFile}`);
}

// Usage
htmlToPdf('/path/to/reports/weekly_status.html', '/path/to/reports/weekly_status.pdf');

Stage 5: Distribution

Distribute generated reports via email, upload to document portal, or archive for audit.

# Example: Email report using SMTP
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from pathlib import Path

def email_report(
    smtp_server: str,
    smtp_port: int,
    sender: str,
    password: str,
    recipients: list,
    subject: str,
    body: str,
    pdf_file: str
):
    """Send PDF report via email."""
    msg = MIMEMultipart()
    msg["From"] = sender
    msg["To"] = ", ".join(recipients)
    msg["Subject"] = subject

    msg.attach(MIMEText(body, "plain"))

    # Attach PDF
    with open(pdf_file, "rb") as f:
        pdf_attachment = MIMEApplication(f.read(), _subtype="pdf")
        pdf_attachment.add_header(
            "Content-Disposition",
            f"attachment; filename={Path(pdf_file).name}"
        )
        msg.attach(pdf_attachment)

    # Send email
    with smtplib.SMTP(smtp_server, smtp_port) as server:
        server.starttls()
        server.login(sender, password)
        server.sendmail(sender, recipients, msg.as_string())

    print(f"Email sent to {recipients}")

# Usage
email_report(
    smtp_server="smtp.company.com",
    smtp_port=587,
    sender="reports@company.com",
    password="secure_password",
    recipients=["manager@company.com", "qa@company.com"],
    subject="Weekly Project Status Report - Week 42",
    body="Please find attached the weekly status report.",
    pdf_file="reports/weekly_status.pdf"
)

ASPICE Work Product Generation

Report generation directly supports multiple ASPICE process areas by automating work product creation.

Process Area Work Product Report Type Automation Benefit
SUP.1 Quality Assurance 08-50 Quality Records
13-04 Quality Criteria
Process compliance reports
Defect metrics
Eliminates manual data collection; ensures consistency across reporting periods
SUP.8 Configuration Management 13-19 Configuration Management Records
08-52 Traceability Records
Baseline inventories
Change request summaries
Build reproducibility logs
Automatically captures CM state from VCS and artifact repos; reduces human error
SUP.9 Problem Resolution Management 15-01 Problem Records
15-02 Problem Status Reports
Issue aging reports
Escalation tracking
Real-time visibility into open problems; auto-escalation triggers
MAN.3 Project Management 15-03 Project Status Reports
15-09 Effort/Cost Records
Weekly status reports
Burndown charts
Management dashboards without manual effort; accurate effort tracking from time logs
MAN.5 Risk Management 15-11 Risk Status Risk register reports
Mitigation tracking
Continuous risk visibility; integration with issue trackers for mitigation actions
MAN.6 Measurement 15-12 Measurement Data
15-13 Analysis Results
Metrics dashboards
Trend analysis
Enables GQM (Goal-Question-Metric) without manual spreadsheet maintenance
SWE.5 Software Integration 13-22 Integration Strategy
08-13 Integration Test Results
Test coverage reports
Integration test trends
Automated evidence of integration verification activities
SWE.6 Software Qualification 13-50 Test Specification
08-13 Test Results
Qualification test reports
Regression test results
Automated qualification evidence; traceability to requirements

Example: SUP.1 Quality Assurance Report

ASPICE SUP.1 BP6 requires "Ensure that quality assurance records are maintained and reported to relevant parties."

Automated Quality Assurance Report Contents:

  1. Review Participation Rates (BP3: Verify adherence to standards)

    • Design review attendance by role
    • Code review turnaround time (time from PR creation to approval)
    • Percentage of work products reviewed before baseline
  2. Defect Injection and Removal (BP4: Analyze defect data)

    • Defects found per lifecycle phase (requirements, design, code, test)
    • Defect density (defects per KLOC)
    • Defect removal efficiency (defects found in reviews vs. defects found in testing)
  3. Process Compliance (BP5: Verify process compliance)

    • Work products completed vs. planned (by process area)
    • Deviations from defined processes with justifications
    • Tool usage compliance (e.g., 100% of code analyzed with static analyzer)

Data Sources:

  • Code review tool APIs (Gerrit, GitLab)
  • Issue tracker (Jira) for defect classification
  • Process dashboard for work product status

Generation Script:

def generate_sup1_qa_report(month: int, year: int) -> dict:
    """Generate SUP.1 Quality Assurance monthly report."""

    # Fetch code review metrics
    reviews = fetch_code_reviews(month, year)
    avg_turnaround = sum(r["turnaround_hours"] for r in reviews) / len(reviews)
    participation_rate = sum(1 for r in reviews if r["reviewers_count"] >= 2) / len(reviews) * 100

    # Fetch defect data
    defects = fetch_defects(month, year)
    defects_by_phase = {
        "Requirements": sum(1 for d in defects if d["found_in"] == "requirements"),
        "Design": sum(1 for d in defects if d["found_in"] == "design"),
        "Implementation": sum(1 for d in defects if d["found_in"] == "implementation"),
        "Testing": sum(1 for d in defects if d["found_in"] == "testing"),
    }

    # Fetch work product status
    work_products = fetch_work_product_status(month, year)
    completion_rate = sum(1 for wp in work_products if wp["status"] == "Completed") / len(work_products) * 100

    return {
        "report_month": month,
        "report_year": year,
        "review_metrics": {
            "total_reviews": len(reviews),
            "avg_turnaround_hours": round(avg_turnaround, 1),
            "participation_rate": round(participation_rate, 1),
        },
        "defect_metrics": {
            "total_defects": len(defects),
            "defects_by_phase": defects_by_phase,
            "defect_density": calculate_defect_density(defects),
        },
        "process_compliance": {
            "work_products_total": len(work_products),
            "completion_rate": round(completion_rate, 1),
        }
    }

CI/CD Pipeline Integration

Integrating report generation into CI/CD pipelines ensures continuous visibility and timely distribution.

Jenkins Pipeline Example

// Jenkinsfile for weekly status report generation
pipeline {
    agent any

    triggers {
        // Run every Monday at 8 AM
        cron('0 8 * * 1')
    }

    environment {
        REPORT_DIR = 'reports'
        TEMPLATE_DIR = 'templates'
        JAMA_API_TOKEN = credentials('jama-api-token')
        SMTP_PASSWORD = credentials('smtp-password')
    }

    stages {
        stage('Setup') {
            steps {
                sh 'pip install -r requirements.txt'
                sh 'mkdir -p ${REPORT_DIR}'
            }
        }

        stage('Extract Data') {
            parallel {
                stage('Requirements') {
                    steps {
                        sh '''
                            python scripts/extract_requirements.py \
                                --project-id ${PROJECT_ID} \
                                --token ${JAMA_API_TOKEN} \
                                --output ${REPORT_DIR}/requirements.json
                        '''
                    }
                }
                stage('Git Metrics') {
                    steps {
                        sh '''
                            python scripts/extract_git_metrics.py \
                                --repo . \
                                --days 7 \
                                --output ${REPORT_DIR}/git_metrics.json
                        '''
                    }
                }
                stage('Test Results') {
                    steps {
                        sh '''
                            python scripts/parse_junit.py \
                                --input test-results/*.xml \
                                --output ${REPORT_DIR}/test_results.json
                        '''
                    }
                }
            }
        }

        stage('Aggregate Data') {
            steps {
                sh '''
                    python scripts/aggregate_data.py \
                        --requirements ${REPORT_DIR}/requirements.json \
                        --git-metrics ${REPORT_DIR}/git_metrics.json \
                        --test-results ${REPORT_DIR}/test_results.json \
                        --output ${REPORT_DIR}/report_data.json
                '''
            }
        }

        stage('Render Report') {
            steps {
                sh '''
                    python scripts/render_report.py \
                        --template ${TEMPLATE_DIR}/weekly_status.html.j2 \
                        --data ${REPORT_DIR}/report_data.json \
                        --output ${REPORT_DIR}/weekly_status.html
                '''
            }
        }

        stage('Generate PDF') {
            steps {
                sh '''
                    python scripts/html_to_pdf.py \
                        --input ${REPORT_DIR}/weekly_status.html \
                        --output ${REPORT_DIR}/weekly_status.pdf
                '''
            }
        }

        stage('Distribute Report') {
            steps {
                sh '''
                    python scripts/email_report.py \
                        --subject "Weekly Status Report - Week $(date +%V)" \
                        --body "Please find attached the weekly status report." \
                        --pdf ${REPORT_DIR}/weekly_status.pdf \
                        --recipients manager@company.com qa@company.com \
                        --smtp-password ${SMTP_PASSWORD}
                '''
            }
        }

        stage('Archive Report') {
            steps {
                archiveArtifacts artifacts: "${REPORT_DIR}/*.pdf", fingerprint: true
                archiveArtifacts artifacts: "${REPORT_DIR}/*.json", fingerprint: true
            }
        }
    }

    post {
        failure {
            emailext(
                subject: "Report Generation Failed - Week $(date +%V)",
                body: "The weekly status report generation failed. Check Jenkins logs.",
                to: "devops@company.com"
            )
        }
    }
}

GitLab CI Pipeline Example

# .gitlab-ci.yml for monthly quality assurance report
stages:
  - extract
  - aggregate
  - render
  - distribute

variables:
  REPORT_DIR: "reports"
  TEMPLATE_DIR: "templates"

extract_data:
  stage: extract
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - mkdir -p ${REPORT_DIR}
    - python scripts/extract_code_reviews.py --month $(date +%-m) --year $(date +%Y) --output ${REPORT_DIR}/reviews.json
    - python scripts/extract_defects.py --month $(date +%-m) --year $(date +%Y) --output ${REPORT_DIR}/defects.json
    - python scripts/extract_work_products.py --month $(date +%-m) --year $(date +%Y) --output ${REPORT_DIR}/work_products.json
  artifacts:
    paths:
      - ${REPORT_DIR}/*.json
    expire_in: 30 days
  only:
    - schedules  # Run on schedule (monthly cron job)

aggregate_data:
  stage: aggregate
  image: python:3.11
  dependencies:
    - extract_data
  script:
    - pip install -r requirements.txt
    - python scripts/aggregate_qa_data.py --reviews ${REPORT_DIR}/reviews.json --defects ${REPORT_DIR}/defects.json --work-products ${REPORT_DIR}/work_products.json --output ${REPORT_DIR}/qa_data.json
  artifacts:
    paths:
      - ${REPORT_DIR}/qa_data.json
    expire_in: 30 days

render_report:
  stage: render
  image: python:3.11
  dependencies:
    - aggregate_data
  script:
    - pip install jinja2 weasyprint
    - python scripts/render_report.py --template ${TEMPLATE_DIR}/sup1_qa_report.html.j2 --data ${REPORT_DIR}/qa_data.json --output ${REPORT_DIR}/qa_report.html
    - python scripts/html_to_pdf.py --input ${REPORT_DIR}/qa_report.html --output ${REPORT_DIR}/qa_report.pdf
  artifacts:
    paths:
      - ${REPORT_DIR}/qa_report.pdf
      - ${REPORT_DIR}/qa_report.html
    expire_in: 1 year  # Keep reports for audit

distribute_report:
  stage: distribute
  image: python:3.11
  dependencies:
    - render_report
  script:
    - pip install requests
    - python scripts/upload_to_confluence.py --pdf ${REPORT_DIR}/qa_report.pdf --page-id ${CONFLUENCE_QA_PAGE_ID}
    - python scripts/email_report.py --subject "Monthly QA Report - $(date +%B %Y)" --pdf ${REPORT_DIR}/qa_report.pdf --recipients qa@company.com manager@company.com
  only:
    - schedules

Configuration Management

Report generation systems require configuration for data sources, templates, and distribution channels.

Example Configuration File (YAML)

# report_config.yaml
report_generator:
  version: "2.0.0"
  output_dir: "reports"
  template_dir: "templates"
  archive_dir: "archive/reports"

  # Data source configurations
  data_sources:
    jama:
      url: "https://company.jamacloud.com"
      project_id: 12345
      api_token_env: "JAMA_API_TOKEN"  # Read from environment variable
      timeout: 30

    git:
      repo_path: "/path/to/repo"
      default_since_days: 30

    jenkins:
      url: "https://jenkins.company.com"
      job_name: "main-build"
      api_token_env: "JENKINS_API_TOKEN"

    test_results:
      junit_xml_pattern: "test-results/**/*.xml"

    jira:
      url: "https://company.atlassian.net"
      project_key: "PROJ"
      api_token_env: "JIRA_API_TOKEN"

  # Report definitions
  reports:
    - name: "weekly_status"
      template: "weekly_status.html.j2"
      schedule: "0 8 * * 1"  # Every Monday at 8 AM (cron format)
      data_sources:
        - jama
        - git
        - test_results
        - jenkins
      output_formats:
        - html
        - pdf
      distribution:
        email:
          enabled: true
          recipients:
            - manager@company.com
            - qa@company.com
          subject: "Weekly Status Report - Week {week}"
          body_template: "email_body_weekly.txt"
        confluence:
          enabled: false

    - name: "monthly_qa"
      template: "sup1_qa_report.html.j2"
      schedule: "0 9 1 * *"  # First day of month at 9 AM
      data_sources:
        - jira
        - git
      output_formats:
        - pdf
      distribution:
        email:
          enabled: true
          recipients:
            - qa@company.com
            - auditor@company.com
          subject: "Monthly QA Report - {month} {year}"
        confluence:
          enabled: true
          space: "QA"
          parent_page_id: "123456"

  # PDF generation settings
  pdf:
    engine: "weasyprint"  # Options: weasyprint, puppeteer, wkhtmltopdf
    page_size: "A4"
    margin:
      top: "20mm"
      right: "15mm"
      bottom: "20mm"
      left: "15mm"

  # SMTP settings for email distribution
  smtp:
    server: "smtp.company.com"
    port: 587
    use_tls: true
    sender: "reports@company.com"
    password_env: "SMTP_PASSWORD"

  # Caching settings
  cache:
    enabled: true
    ttl_seconds: 3600  # Cache data for 1 hour
    backend: "redis"  # Options: redis, filesystem, memory
    redis_url: "redis://localhost:6379/0"

  # Logging
  logging:
    level: "INFO"
    file: "logs/report_generator.log"
    format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

Loading Configuration

# Example: Load and use configuration
import yaml
import os
from typing import Dict, Any

def load_config(config_file: str = "report_config.yaml") -> Dict[str, Any]:
    """Load report generator configuration."""
    with open(config_file, "r") as f:
        config = yaml.safe_load(f)

    # Resolve environment variables for sensitive data
    if "data_sources" in config["report_generator"]:
        for source_name, source_config in config["report_generator"]["data_sources"].items():
            for key, value in source_config.items():
                if isinstance(value, str) and key.endswith("_env"):
                    env_var = value
                    actual_key = key.replace("_env", "")
                    source_config[actual_key] = os.getenv(env_var)

    return config["report_generator"]

# Usage
config = load_config()
jama_config = config["data_sources"]["jama"]
jama_api_token = jama_config["api_token"]

Best Practices with Explanations

1. Use Version-Controlled Report Templates

Rationale: Templates define the structure and presentation of work products. Version control ensures auditability, enables collaborative improvement, and allows rollback if template changes introduce errors.

Implementation:

  • Store templates in templates/ directory within project repository
  • Use semantic versioning for major template changes (e.g., v2.0.0 for new layout)
  • Include template version in generated report metadata
  • Tag template versions in Git for reproducibility

Example:

<!-- templates/weekly_status.html.j2 -->
<!-- Template Version: 2.1.0 -->
<!-- Last Modified: 2025-12-15 -->
<!-- Author: QA Team -->
<p style="font-size: 12px; color: #999;">
    Generated by ASPICE Report Generator v2.0.0 using template v2.1.0
</p>

2. Include Data Collection Timestamps and Tool Versions

Rationale: ASPICE SUP.1 and SUP.8 require traceability of work products to the data sources and tools used. Timestamps establish when data was captured (critical for time-sensitive metrics); tool versions ensure reproducibility and support root cause analysis if reports show anomalies.

Implementation:

import datetime
import subprocess

def get_tool_versions() -> dict:
    """Capture versions of all tools used in data collection."""
    return {
        "python": subprocess.run(["python", "--version"], capture_output=True, text=True).stdout.strip(),
        "git": subprocess.run(["git", "--version"], capture_output=True, text=True).stdout.strip(),
        "report_generator": "2.0.0",
    }

def add_metadata(report_data: dict) -> dict:
    """Add metadata to report data."""
    report_data["metadata"] = {
        "generated_at": datetime.datetime.now().isoformat(),
        "tool_versions": get_tool_versions(),
        "data_collection_times": {
            "requirements": report_data.get("requirements_timestamp"),
            "git_metrics": report_data.get("git_timestamp"),
            "test_results": report_data.get("test_timestamp"),
        }
    }
    return report_data

3. Implement Caching for Expensive Data Queries

Rationale: Some data sources (RMS APIs, Git log analysis, test database queries) are expensive in time or compute resources. Caching reduces report generation time, decreases load on source systems, and enables interactive report refinement without re-querying.

Implementation (Redis-based caching):

import redis
import json
import hashlib
from typing import Any, Callable

class DataCache:
    def __init__(self, redis_url: str, ttl: int = 3600):
        self.redis = redis.from_url(redis_url)
        self.ttl = ttl

    def cache_key(self, func_name: str, *args, **kwargs) -> str:
        """Generate cache key from function name and arguments."""
        key_data = f"{func_name}:{args}:{sorted(kwargs.items())}"
        return hashlib.sha256(key_data.encode()).hexdigest()

    def get(self, key: str) -> Any:
        """Retrieve cached data."""
        data = self.redis.get(key)
        if data:
            return json.loads(data)
        return None

    def set(self, key: str, value: Any):
        """Store data in cache with TTL."""
        self.redis.setex(key, self.ttl, json.dumps(value))

    def cached(self, func: Callable) -> Callable:
        """Decorator for caching function results."""
        def wrapper(*args, **kwargs):
            cache_key = self.cache_key(func.__name__, *args, **kwargs)
            cached_result = self.get(cache_key)
            if cached_result is not None:
                print(f"Cache hit for {func.__name__}")
                return cached_result

            result = func(*args, **kwargs)
            self.set(cache_key, result)
            return result
        return wrapper

# Usage
cache = DataCache("redis://localhost:6379/0", ttl=3600)

@cache.cached
def fetch_jama_requirements(project_id: int, api_token: str):
    """Fetch requirements (cached for 1 hour)."""
    # ... expensive API call ...
    return requirements_data

4. Archive Generated Reports for Audit Trails

Rationale: ASPICE requires maintaining records of work products for the project lifecycle and potentially beyond (SUP.8 BP6). Archived reports demonstrate historical project state, support trend analysis, and provide evidence during audits.

Implementation:

import shutil
from pathlib import Path
from datetime import datetime

def archive_report(report_file: str, archive_dir: str = "archive/reports"):
    """Archive generated report with timestamp."""
    archive_path = Path(archive_dir)
    archive_path.mkdir(parents=True, exist_ok=True)

    # Create timestamped filename
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    report_name = Path(report_file).stem
    report_ext = Path(report_file).suffix
    archived_name = f"{report_name}_{timestamp}{report_ext}"

    # Copy to archive
    shutil.copy2(report_file, archive_path / archived_name)
    print(f"Report archived: {archive_path / archived_name}")

    # Also create a "latest" symlink for easy access
    latest_link = archive_path / f"{report_name}_latest{report_ext}"
    if latest_link.exists():
        latest_link.unlink()
    latest_link.symlink_to(archived_name)

# Usage
archive_report("reports/weekly_status.pdf")
# Creates: archive/reports/weekly_status_20251217_093045.pdf
#          archive/reports/weekly_status_latest.pdf -> weekly_status_20251217_093045.pdf

5. Validate Data Completeness Before Report Generation

Rationale: Incomplete data leads to misleading reports, which can cause incorrect management decisions or failed audits. Validation ensures all required data is present, correctly formatted, and within expected ranges before rendering.

Implementation:

from typing import Dict, Any, List

class ValidationError(Exception):
    """Raised when report data fails validation."""
    pass

def validate_report_data(data: Dict[str, Any]) -> List[str]:
    """Validate report data completeness and correctness."""
    errors = []
    warnings = []

    # Check required top-level keys
    required_keys = ["requirements", "development_activity", "quality_metrics"]
    for key in required_keys:
        if key not in data:
            errors.append(f"Missing required key: {key}")

    # Validate requirements data
    if "requirements" in data:
        req = data["requirements"]
        if req.get("total", 0) == 0:
            warnings.append("No requirements found - is RMS data available?")
        if req.get("completion_rate", 0) < 0 or req.get("completion_rate", 0) > 100:
            errors.append(f"Invalid completion rate: {req.get('completion_rate')}")

    # Validate quality metrics
    if "quality_metrics" in data:
        qm = data["quality_metrics"]
        if qm.get("test_total", 0) == 0:
            warnings.append("No test results found - is test data available?")
        if qm.get("test_pass_rate", 0) < 0 or qm.get("test_pass_rate", 0) > 100:
            errors.append(f"Invalid test pass rate: {qm.get('test_pass_rate')}")

    # Validate timestamps
    required_timestamps = ["requirements_timestamp", "git_timestamp", "test_timestamp"]
    for ts_key in required_timestamps:
        if ts_key not in data or not data[ts_key]:
            errors.append(f"Missing timestamp: {ts_key}")

    # Print warnings
    for warning in warnings:
        print(f"WARNING: {warning}")

    # Raise exception if errors found
    if errors:
        raise ValidationError(f"Validation failed: {'; '.join(errors)}")

    return warnings

# Usage
try:
    warnings = validate_report_data(report_data)
    render_report("weekly_status.html.j2", report_data, "reports/weekly_status.html")
except ValidationError as e:
    print(f"ERROR: {e}")
    print("Report generation aborted due to invalid data.")
    sys.exit(1)

6. Implement Error Handling with Detailed Logging

Rationale: Report generation involves multiple external dependencies (APIs, databases, filesystems). Robust error handling prevents partial report generation and provides diagnostic information when failures occur.

Implementation:

import logging
from typing import Optional

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('logs/report_generator.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

def safe_extract_data(extractor_func, *args, **kwargs) -> Optional[dict]:
    """Safely execute data extraction with error handling."""
    try:
        logger.info(f"Extracting data: {extractor_func.__name__}")
        data = extractor_func(*args, **kwargs)
        logger.info(f"Successfully extracted {len(data)} items")
        return data
    except requests.exceptions.RequestException as e:
        logger.error(f"API request failed in {extractor_func.__name__}: {e}")
        return None
    except Exception as e:
        logger.error(f"Unexpected error in {extractor_func.__name__}: {e}", exc_info=True)
        return None

# Usage
requirements_data = safe_extract_data(fetch_jama_requirements, project_id=12345, api_token=token)
if requirements_data is None:
    logger.error("Failed to extract requirements data. Report generation aborted.")
    sys.exit(1)

Troubleshooting Common Issues

Issue: Missing Data in Reports

Symptoms: Reports show zero values, empty tables, or "No data available" messages.

Root Causes:

  1. API credentials expired or incorrect: Check environment variables for API tokens
  2. Data source unavailable: Verify RMS, Jenkins, or Git server is reachable
  3. Query filters too restrictive: Check date ranges, project IDs, or query parameters
  4. Data not committed to source system: Ensure engineers have pushed changes, closed tickets, or updated requirements

Resolution:

# Test API connectivity
curl -H "Authorization: Bearer $JAMA_API_TOKEN" https://company.jamacloud.com/rest/latest/items?project=12345

# Check Git repository access
git -C /path/to/repo log --since="7 days ago" --oneline

# Verify environment variables
echo $JAMA_API_TOKEN
echo $JENKINS_API_TOKEN

Issue: Template Rendering Errors

Symptoms: Jinja2 raises TemplateSyntaxError or UndefinedError.

Root Causes:

  1. Undefined variable: Template references variable not present in data
  2. Syntax error: Mismatched {% %} tags, missing {% endif %}, etc.
  3. Type mismatch: Template expects list but receives dict

Resolution:

# Enable strict undefined checking
from jinja2 import Environment, FileSystemLoader, StrictUndefined

env = Environment(
    loader=FileSystemLoader("templates/"),
    undefined=StrictUndefined  # Raise error on undefined variables
)

# Use default filter for optional values
{{ requirements.asil_breakdown.get('ASIL_A', 0) }}
{{ optional_field | default("N/A") }}

# Check data structure before rendering
print(json.dumps(report_data, indent=2))

Issue: PDF Generation Failures

Symptoms: HTML renders correctly, but PDF conversion fails or produces blank pages.

Root Causes:

  1. Missing fonts: PDF generator cannot find specified fonts
  2. External resource loading: Images or CSS not accessible (relative paths)
  3. Memory limits: Large reports exceed available memory
  4. JavaScript required: Template requires JS but PDF renderer doesn't support it

Resolution:

# For WeasyPrint: Use absolute paths for resources
from pathlib import Path

base_url = f"file://{Path('reports').resolve()}/"
HTML(filename=html_file, base_url=base_url).write_pdf(pdf_file)

# For Puppeteer: Increase timeout and memory
await page.goto(`file://${htmlFile}`, {
    waitUntil: 'networkidle0',
    timeout: 60000  // 60 seconds
});

# Embed images as base64 to avoid path issues
import base64
with open("logo.png", "rb") as f:
    logo_base64 = base64.b64encode(f.read()).decode()
data["logo_base64"] = f"data:image/png;base64,{logo_base64}"

Issue: Report Distribution Failures

Symptoms: Reports generate successfully but email/upload fails.

Root Causes:

  1. SMTP authentication failure: Incorrect credentials or TLS settings
  2. File size limits: PDF exceeds email attachment limit (typically 25 MB)
  3. Network restrictions: Firewall blocks SMTP port or Confluence API
  4. Invalid recipients: Email addresses malformed or non-existent

Resolution:

# Test SMTP connection
import smtplib
try:
    server = smtplib.SMTP("smtp.company.com", 587)
    server.starttls()
    server.login("reports@company.com", smtp_password)
    print("SMTP connection successful")
    server.quit()
except smtplib.SMTPAuthenticationError:
    print("ERROR: SMTP authentication failed - check credentials")
except Exception as e:
    print(f"ERROR: {e}")

# Compress large PDFs
import subprocess
subprocess.run([
    "gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4",
    "-dPDFSETTINGS=/ebook", "-dNOPAUSE", "-dQUIET", "-dBATCH",
    f"-sOutputFile=compressed.pdf", "large_report.pdf"
])

Summary

Report generation capabilities transform raw engineering data into ASPICE-compliant work products:

  • Template Engines: Jinja2 for Python environments, Mustache for cross-language portability, Mako for computation-heavy reports
  • PDF Generation: Browser-based (Puppeteer, WeasyPrint) for modern layouts, native libraries (ReportLab) for simplicity, LaTeX for professional typesetting
  • Data Aggregation: Extract from RMS (Jama, DOORS), VCS (Git), test frameworks (JUnit), and project management (Jira)
  • Multi-Format Output: Generate PDF, HTML, DOCX, Excel for different stakeholder needs
  • Scheduled Delivery: Daily status, weekly trends, monthly compliance reports
  • ASPICE Alignment: Direct support for SUP.1 (QA), SUP.8 (CM), SUP.9 (Problem Resolution), MAN.3 (Project Management), MAN.5 (Risk), MAN.6 (Measurement)

Best Practices:

  1. Version-control templates for auditability and collaborative improvement
  2. Include timestamps and tool versions for traceability and reproducibility
  3. Implement caching for expensive data queries to reduce generation time
  4. Archive reports for audit trails and historical trend analysis
  5. Validate data completeness before rendering to prevent misleading reports
  6. Handle errors gracefully with detailed logging for diagnostics

By automating report generation, embedded systems organizations reduce manual effort by 70-90%, eliminate transcription errors, ensure consistency across reporting periods, and maintain continuous audit readiness. The investment in robust templates and data pipelines pays dividends in reduced process overhead and improved management visibility.