MiroxMirox
  • Platform

    • Philosophy
    • Platform Overview
    • Platform Resources
  • Mirox-Cloud

    • Cloud Overview
    • Connected Microservices
  • Mirox-Agent

    • Agent Overview
    • Deployment Options
    • Data Scraper
    • Digital Twin
  • Technical Details

    • Metric Collection
  • Information

    • Supported Plants
  • Plant Types

    • Solar Plants
    • Wind Plants
    • Battery Storage
  • Monitoring & Visualization

    • Real-time Monitoring
    • Digital Twin
    • Component States
    • Loss Detection
    • Efficiency Detection
    • KPI Dashboard
  • Data Management

    • Events
    • Tickets
    • Forecasts
    • Reports
  • Integration & Sharing

    • Cooperations
    • API Tokens
    • VPN
    • Proxy
  • AI

    • AI Assistant & Wizards
    • Agentic Access (MCP)
  • Billing

    • Market & Tariffs
    • Accounting & Billing
  • Collaboration

    • Invitations
  • Security

    • Authentication
    • Permission System
    • Cooperation Restrictions
    • Access Audit Logging
  • Nodes

    • mrxnode
  • Application

    • Door Control
    • Generic Relay
  • Edge Cluster

    • Orchestration
  • Getting Started

    • First Steps
  • Personal

    • Using the VPN
    • Using the Proxy
    • Two-Factor Authentication
    • Sessions
    • API Tokens
  • Per Park

    • Contacts
    • Network Devices
    • Data Loggers
    • Components
    • Direct VPN (per Agent)
  • Organization

    • Member Permissions
    • Cooperations
    • File Storage
  • Data Export

    • Export Metric API
    • MiroxQL Query Language
    • External Report Generation
    • Grafana
    • API Overview
  • Support

    • Request Integration Guide
  • mrxnode

    • Overview
    • How-To Guide
    • Container Deployment
    • Command Cheatsheet
    • Troubleshooting
  • Reporting

    • External Report Generator
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Platform

    • Philosophy
    • Platform Overview
    • Platform Resources
  • Mirox-Cloud

    • Cloud Overview
    • Connected Microservices
  • Mirox-Agent

    • Agent Overview
    • Deployment Options
    • Data Scraper
    • Digital Twin
  • Technical Details

    • Metric Collection
  • Information

    • Supported Plants
  • Plant Types

    • Solar Plants
    • Wind Plants
    • Battery Storage
  • Monitoring & Visualization

    • Real-time Monitoring
    • Digital Twin
    • Component States
    • Loss Detection
    • Efficiency Detection
    • KPI Dashboard
  • Data Management

    • Events
    • Tickets
    • Forecasts
    • Reports
  • Integration & Sharing

    • Cooperations
    • API Tokens
    • VPN
    • Proxy
  • AI

    • AI Assistant & Wizards
    • Agentic Access (MCP)
  • Billing

    • Market & Tariffs
    • Accounting & Billing
  • Collaboration

    • Invitations
  • Security

    • Authentication
    • Permission System
    • Cooperation Restrictions
    • Access Audit Logging
  • Nodes

    • mrxnode
  • Application

    • Door Control
    • Generic Relay
  • Edge Cluster

    • Orchestration
  • Getting Started

    • First Steps
  • Personal

    • Using the VPN
    • Using the Proxy
    • Two-Factor Authentication
    • Sessions
    • API Tokens
  • Per Park

    • Contacts
    • Network Devices
    • Data Loggers
    • Components
    • Direct VPN (per Agent)
  • Organization

    • Member Permissions
    • Cooperations
    • File Storage
  • Data Export

    • Export Metric API
    • MiroxQL Query Language
    • External Report Generation
    • Grafana
    • API Overview
  • Support

    • Request Integration Guide
  • mrxnode

    • Overview
    • How-To Guide
    • Container Deployment
    • Command Cheatsheet
    • Troubleshooting
  • Reporting

    • External Report Generator
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Reporting

    • External Report Generation - API Examples

External Report Generation - API Examples

Pull plant data straight from the Mirox export APIs and build your own reports in whatever tool you prefer. This example walks through the relevant endpoints with curl for clarity, but the same principles apply with Python, Power BI, Tableau, or any HTTP client.

Prerequisites

Before you can export data for external report generation, you need:

  1. Mirox Account - A valid Mirox user account
  2. Permissions - Your account must have permission to read the park and generate reports for that park
  3. Park UID - The unique identifier of the park resource you want to report on
  4. API Token - An API token with at least the Reporting permission group (see API Token Usage for creation instructions)

Available Export Endpoints

The Mirox platform provides several API endpoints for exporting data in formats suitable for external report generation. Park information and event exports live under the /v1/export/report/ API path; production metrics use the dedicated template export endpoint /v1/export/metrics/template/{template_uid}.

Important Note

The API endpoints shown in this document are examples for illustration. The exact endpoints may change. Please consult the current API documentation at https://service.mirox.io/docs for the latest information.

Park Information (JSON)

Basic park information can be exported as JSON with:

GET /v1/export/report/{park_uid}/info

This provides park metadata including:

  • Name, type, and description
  • Geographic location and timezone
  • Technical specifications (peak production, etc.)
  • Organization and portfolio information
  • Address and commissioning details

For full details on the response schema, see the Park Information API Documentation.

Park Events (CSV)

Event data can be exported as CSV with:

GET /v1/export/report/{park_uid}/events.csv

This endpoint supports the following query parameters:

  • year: Filter by year (e.g., 2025)
  • quarter: Filter by quarter (1-4)
  • month: Filter by month (1-12)

The returned CSV contains the following columns:

  • Event ID
  • Start Date
  • End Date
  • Duration (hours)
  • Type
  • Creator
  • Description
  • Priority

Event Filtering by Priority

The Priority field indicates event importance:

  • Values >= 1000: Important events (production-impacting shutdowns, alarms)
  • Values < 1000: Informative events (connections, minor state changes)

Filter by priority in your reporting tool to highlight significant events.

If no time parameters are specified, the endpoint returns events from the most recently completed quarter.

For details on request parameters and response format, see the Park Event API Documentation.

Park Metrics (CSV)

Production and performance metrics can be exported as CSV with the template export endpoint:

GET /v1/export/metrics/template/{template_uid}

This endpoint supports the following query parameters:

  • park: One or more park UIDs (comma-separated) to include in the export
  • portfolio: One or more portfolio UIDs (comma-separated); each portfolio resolves to its parks
  • resolution: Aggregation interval (daily, weekly, monthly, quarterly, or yearly; defaults to monthly)
  • year: Filter by year (e.g., 2025)
  • quarter: Filter by quarter (1-4)
  • month: Filter by month (1-12)
  • week: Filter by ISO week (weekly resolution)
  • day: Filter by day (requires month)

The resolution parameter determines how data is aggregated:

  • daily: Returns one row per day with daily measurements
  • monthly: Returns one row per month with aggregated monthly totals and an additional "Days in Month" column

Your business intelligence tool must handle these different resolution formats appropriately for visualization and analysis.

Multi-Plant Exports

A single call can export several plants or whole portfolios at once by passing comma-separated park and portfolio UIDs. This is the recommended path when you build a report spanning more than one plant.

The columns are defined by the export template you reference. The example below uses template ABCD12340001 ("Report Technical v1"), which includes:

  • Energy Production (kWh)
  • Energy Report (kWh)
  • Energy Shutdown by grid (kWh)
  • Energy Shutdown by external (kWh)
  • Energy Target (kWh)
  • Irradiation (kWh)

For full details on request parameters and response format, consult the current API documentation at https://service.mirox.io/docs.

API Usage Examples

Below are examples of how to use the export endpoints directly with curl. These examples demonstrate the structure of API calls and the format of responses.

Example 1: Retrieving Park Information (JSON)

Request:

curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/info" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response (partial):

{
  "uid": "ABC123DEF456",
  "name": "Sunnyside Solar Park",
  "type": "solar",
  "description": "Sunnyside Solar Park is a 42 MW photovoltaic installation...",
  "latitude": 48.7511,
  "longitude": 9.1225,
  "timezone": "Europe/Berlin",
  "peak_production_w": 42000000,
  "active": true,
  "created_at": "2024-05-16T10:30:25.104830Z",
  "portfolio": {
    "uid": "GHI789JKL012",
    "name": "Renewable South"
  },
  "organization": {
    "uid": "MNO345PQR678",
    "name": "GreenPower Inc."
  },
  "address": {
    "address1": "123 Solar Way",
    "zip_code": "70123",
    "city": "Sunnyville",
    "country": "Germany"
  }
}

Example 2: Exporting Event Data (CSV)

Request:

curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/events.csv" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response (CSV):

Event ID,Start Date,End Date,Duration (hours),Type,Creator,Description,Priority
9876543210123456789,2025-01-15 08:45,Ongoing,720.25,Sensor Error,System,Irradiation sensor malfunction detected,1200
8765432101234567890,2025-02-10 14:30,2025-02-15 09:15,114.75,Data Availability,System,Connection issues with data acquisition system,1100

Note the Priority field with values greater than 1000, indicating significant production-impacting events.

Example 3: Exporting Monthly Metrics (CSV)

Request:

curl "https://service.mirox.io/api/v1/export/metrics/template/ABCD12340001?park=ABC123DEF456&resolution=monthly" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response (CSV):

Date (YYYY-MM),Energy Production (kWh),Energy Report (kWh),Energy Shutdown by grid (kWh),Energy Shutdown by external (kWh),Energy Target (kWh),Irradiation (kWh),Days in Month (d)
2025-01,950000.45,980000.00,15000.50,25000.75,980000.00,18.350,31
2025-02,1250000.32,1300000.00,2000.25,18500.40,1300000.00,22.780,28
2025-03,1850000.75,1900000.00,45000.20,30000.65,1900000.00,35.420,31

This example demonstrates monthly aggregated data with the "Days in Month (d)" column specific to the monthly interval option.

Usage in Different Tools

These API calls can be applied in various environments:

  • Python/Requests: Use the requests library to make API calls in Python scripts
  • Power BI: Use "Web" data sources to import JSON/CSV data directly
  • Tableau: Configure web data connectors for API integration
  • Excel: Use Power Query to import and transform API data
  • Custom Scripts: Implement in any language with HTTP client support

Best Practices

For effective API usage:

  1. Use API Tokens Securely - Never hardcode tokens in scripts; use environment variables
  2. Handle Interval Data Correctly - Ensure your analysis properly handles daily and monthly data formats
  3. Filter Events Appropriately - Use the priority field to focus on significant events (>=1000)
  4. Consider Data Volume - For large parks or long time periods, export data in manageable chunks
  5. Implement Error Handling - Handle HTTP errors and network issues appropriately

Troubleshooting

Authentication Errors

If you receive an HTTP 401 error:

  1. Check your API token: Ensure you're using a valid, non-expired API token. Tokens can be generated in your Mirox user settings.

  2. Verify the park UID: Ensure the park UID is valid and you have access to it. The example value ABC123DEF456 is a placeholder.

  3. Token permissions: Verify that your token has been granted the necessary permissions to access export endpoints.

CSV Encoding Issues

CSV exports use UTF-8 encoding. In some tools (e.g., Excel), you may need to explicitly specify the encoding when importing.

Python Implementation Example: SolarViz Report Generator

The following example shows a complete Python implementation that uses the APIs described above to create a custom energy production report.

Prerequisites

To run this Python example, you'll need the following libraries:

pip install requests pandas matplotlib seaborn tabulate
  • requests: For making API calls to the Mirox platform
  • pandas: For data manipulation and analysis
  • matplotlib and seaborn: For creating visualizations
  • tabulate: For formatting tables in the report

Complete Example Code

Below is the complete code for the SolarViz report generator:

#!/usr/bin/env python3
"""
SolarViz - Mirox External Report Generator

This script demonstrates how to fetch data from the Mirox API and generate
a custom energy production report with visualizations as a PDF file.
"""

import os
import requests
import json
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime, timezone
import io
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle
from reportlab.graphics.shapes import Drawing
from reportlab.lib.units import inch, cm

# Configuration - Replace with your values
API_URL = "https://service.mirox.io"
API_TOKEN = "YOUR_API_TOKEN"  # Your personal access token
PARK_UID = "ABC123DEF456"  # The UID of the park you want to work with
TEMPLATE_UID = "ABCD12340001"  # The export template ("Report Technical v1")

# Time range parameters
YEAR = datetime.now(timezone.utc).year
QUARTER = (datetime.now(timezone.utc).month - 1) // 3 + 1

# Setup headers
headers = {
    "Authorization": f"Bearer {API_TOKEN}",
    "Content-Type": "application/json"
}


def fetch_park_info():
    """Fetch basic park information as JSON"""
    response = requests.get(
        f"{API_URL}/api/v1/export/report/{PARK_UID}/info",
        headers=headers
    )
    response.raise_for_status()
    return response.json()


def fetch_events(year=None, quarter=None):
    """Fetch park events as DataFrame"""
    params = {}
    if year:
        params["year"] = year
    if quarter:
        params["quarter"] = quarter

    response = requests.get(
        f"{API_URL}/api/v1/export/report/{PARK_UID}/events.csv",
        headers=headers,
        params=params
    )
    response.raise_for_status()

    # Convert CSV content to DataFrame
    events_df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))

    # Filter for significant events (priority >= 1000)
    significant_events = events_df[events_df['Priority'] >= 1000]

    # Process events data - remove ID, Priority, and format duration
    if not significant_events.empty:
        # Remove any ID-related columns (there could be 'ID', 'Event ID', etc.)
        id_columns = [col for col in significant_events.columns if 'id' in col.lower()]
        significant_events = significant_events.drop(id_columns, axis=1, errors='ignore')

        # Remove Priority column as well
        if 'Priority' in significant_events.columns:
            significant_events = significant_events.drop('Priority', axis=1)

        # Convert duration from minutes to days/hours
        if 'DurationMin' in significant_events.columns:
            def format_duration(minutes):
                if minutes < 60:
                    return f"{minutes} min"
                elif minutes < 24*60:
                    hours = minutes / 60
                    return f"{hours:.1f} hours"
                else:
                    days = minutes / (24*60)
                    return f"{days:.1f} days"

            significant_events['Duration'] = significant_events['DurationMin'].apply(format_duration)
            significant_events = significant_events.drop('DurationMin', axis=1)

    return significant_events


def fetch_metrics(resolution="monthly", year=None, quarter=None):
    """Fetch park production metrics as DataFrame"""
    params = {"park": PARK_UID, "resolution": resolution}
    if year:
        params["year"] = year
    if quarter:
        params["quarter"] = quarter

    response = requests.get(
        f"{API_URL}/api/v1/export/metrics/template/{TEMPLATE_UID}",
        headers=headers,
        params=params
    )
    response.raise_for_status()

    # Convert CSV content to DataFrame
    metrics_df = pd.read_csv(io.StringIO(response.content.decode('utf-8')))

    return metrics_df


def create_production_chart(metrics_df):
    """Create a chart visualizing energy production and target with stacked losses"""
    plt.figure(figsize=(8, 4))

    # Set seaborn style
    sns.set_style("whitegrid")

    # Prepare data for stacked bar chart
    x_positions = range(len(metrics_df))
    months = metrics_df.iloc[:, 0]

    # Create stacked bar chart
    # Base layer: Actual production
    plt.bar(x_positions, metrics_df['Energy Production (kWh)'],
            color='#2986cc', label='Actual Production')

    # Stack grid shutdown losses on top
    grid_shutdown = metrics_df['Energy Shutdown by grid (kWh)']
    external_shutdown = metrics_df['Energy Shutdown by external (kWh)']

    # Second layer: Grid shutdown losses
    plt.bar(x_positions, grid_shutdown,
            bottom=metrics_df['Energy Production (kWh)'],
            color='#e06666', label='Grid Shutdown Losses')

    # Third layer: External shutdown losses
    plt.bar(x_positions, external_shutdown,
            bottom=metrics_df['Energy Production (kWh)'] + grid_shutdown,
            color='#f1c232', label='External Shutdown Losses')

    # Plot Target as a line
    plt.plot(x_positions, metrics_df['Energy Target (kWh)'],
             marker='o', color='#6aa84f', linewidth=2, label='Target')

    # Customize the chart
    plt.title('Monthly Energy Production vs Target with Losses')
    plt.xlabel('Month')
    plt.ylabel('Energy (kWh)')
    plt.xticks(x_positions, months, rotation=45)

    # Add legend
    plt.legend(loc='upper left')

    plt.tight_layout()

    # Save chart to a bytes buffer
    buffer = io.BytesIO()
    plt.savefig(buffer, format='png', dpi=300)
    buffer.seek(0)
    plt.close()

    return buffer


def calculate_performance_metrics(metrics_df):
    """Calculate performance metrics based on the production data"""
    performance = {}

    # Total energy production
    performance['total_production'] = metrics_df['Energy Production (kWh)'].sum()

    # Total target
    performance['total_target'] = metrics_df['Energy Target (kWh)'].sum()

    # Performance ratio (actual vs target)
    performance['performance_ratio'] = (performance['total_production'] / performance['total_target']) * 100

    # Total shutdown energy
    performance['grid_shutdown'] = metrics_df['Energy Shutdown by grid (kWh)'].sum()
    performance['external_shutdown'] = metrics_df['Energy Shutdown by external (kWh)'].sum()
    performance['total_shutdown'] = performance['grid_shutdown'] + performance['external_shutdown']

    # Lost production percentage
    performance['lost_production_pct'] = (performance['total_shutdown'] /
                                          (performance['total_production'] + performance['total_shutdown'])) * 100

    return performance


def generate_pdf_report(park_info, events_df, metrics_df, performance, chart_data):
    """Generate a PDF report with the data"""
    # Define the output filename
    output_file = f"SolarViz_Report_{park_info['name'].replace(' ', '_')}_{YEAR}_Q{QUARTER}.pdf"

    # Create PDF document
    doc = SimpleDocTemplate(output_file, pagesize=A4)
    styles = getSampleStyleSheet()

    # Modify existing styles
    styles['Title'].fontName = 'Helvetica-Bold'
    styles['Title'].fontSize = 18
    styles['Title'].alignment = 1  # Center aligned
    styles['Title'].spaceAfter = 12

    # Modify Heading2 style
    styles['Heading2'].fontName = 'Helvetica-Bold'
    styles['Heading2'].fontSize = 14
    styles['Heading2'].spaceBefore = 12
    styles['Heading2'].spaceAfter = 6

    # Modify Normal style
    styles['Normal'].fontName = 'Helvetica'
    styles['Normal'].fontSize = 10
    styles['Normal'].spaceBefore = 6
    styles['Normal'].spaceAfter = 6

    # Add a custom style for tables
    styles.add(ParagraphStyle(name='TableHeader',
                              parent=styles['Normal'],
                              fontName='Helvetica-Bold',
                              fontSize=9,
                              alignment=1))

    # Create document elements
    elements = []

    # Title
    elements.append(Paragraph(f"Energy Production Report: {park_info['name']}", styles['Title']))
    elements.append(Spacer(1, 0.5*cm))

    # Park Information section
    elements.append(Paragraph("Park Information", styles['Heading2']))

    park_info_data = [
        ["Name:", park_info['name']],
        ["Type:", park_info['type']],
        ["Location:", f"{park_info['address']['city']}, {park_info['address']['country']}"],
        ["Peak Capacity:", f"{park_info['peak_production_w'] / 1000000:.2f} MW"],
        ["Portfolio:", park_info['portfolio']['name']],
        ["Organization:", park_info['organization']['name']]
    ]

    park_info_table = Table(park_info_data, colWidths=[3*cm, 10*cm])
    park_info_table.setStyle(TableStyle([
        ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
        ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 3),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 3),
    ]))

    elements.append(park_info_table)
    elements.append(Spacer(1, 0.5*cm))

    # Performance Summary section
    elements.append(Paragraph(f"Performance Summary ({YEAR} Q{QUARTER})", styles['Heading2']))

    performance_data = [
        ["Total Energy Production:", f"{performance['total_production']:,.2f} kWh"],
        ["Target Production:", f"{performance['total_target']:,.2f} kWh"],
        ["Performance Ratio:", f"{performance['performance_ratio']:.2f}%"],
        ["Total Energy Lost to Shutdowns:", f"{performance['total_shutdown']:,.2f} kWh"],
        ["  • Grid Shutdowns:", f"{performance['grid_shutdown']:,.2f} kWh"],
        ["  • External Shutdowns:", f"{performance['external_shutdown']:,.2f} kWh"],
        ["Lost Production Percentage:", f"{performance['lost_production_pct']:.2f}%"]
    ]

    performance_table = Table(performance_data, colWidths=[6*cm, 7*cm])
    performance_table.setStyle(TableStyle([
        ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
        ('FONTNAME', (1, 0), (1, -1), 'Helvetica'),
        ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ('LEFTPADDING', (0, 0), (-1, -1), 6),
        ('TOPPADDING', (0, 0), (-1, -1), 3),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 3),
    ]))

    elements.append(performance_table)
    elements.append(Spacer(1, 0.5*cm))

    # Monthly Production Chart
    elements.append(Paragraph("Monthly Production Chart", styles['Heading2']))

    # Add the chart image
    img = Image(chart_data, width=16*cm, height=8*cm)
    elements.append(img)
    elements.append(Spacer(1, 0.5*cm))

    # Add metrics data table below the graph
    elements.append(Paragraph("Monthly Production Data", styles['Heading2']))

    # Prepare metrics data for table
    # Select relevant columns
    if not metrics_df.empty:
        # Create a copy of the dataframe with selected columns
        table_df = metrics_df.copy()

        # Use the first column as the month column
        month_col_name = table_df.columns[0]

        # Create table data with headers
        metrics_data = [['Month', 'Production (kWh)', 'Target (kWh)',
                        'Grid Shutdown (kWh)', 'External Shutdown (kWh)']]

        # Add data rows with formatted numbers
        for _, row in table_df.iterrows():
            metrics_data.append([
                row[month_col_name],
                f"{row['Energy Production (kWh)']:,.2f}",
                f"{row['Energy Target (kWh)']:,.2f}",
                f"{row['Energy Shutdown by grid (kWh)']:,.2f}",
                f"{row['Energy Shutdown by external (kWh)']:,.2f}"
            ])

        # Create table and set style
        metrics_table = Table(metrics_data, repeatRows=1)
        metrics_table.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('ALIGN', (0, 0), (-1, 0), 'CENTER'),
            ('ALIGN', (1, 1), (-1, -1), 'RIGHT'),  # Right-align numeric columns
            ('BOTTOMPADDING', (0, 0), (-1, 0), 6),
            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
            ('FONTSIZE', (0, 0), (-1, -1), 9),
        ]))
        elements.append(metrics_table)
    else:
        elements.append(Paragraph("No production data available for the selected period.", styles['Normal']))

    elements.append(Spacer(1, 0.5*cm))

    # Significant Events section
    elements.append(Paragraph("Significant Events", styles['Heading2']))

    if not events_df.empty:
        # Double check to make sure all ID columns and Priority are removed
        id_columns = [col for col in events_df.columns if 'id' in col.lower()]
        events_df = events_df.drop(id_columns, axis=1, errors='ignore')

        # Also remove Priority column if it still exists
        if 'Priority' in events_df.columns:
            events_df = events_df.drop('Priority', axis=1)

        # Convert DataFrame to a list of lists
        events_data = [events_df.columns.tolist()] + events_df.values.tolist()

        # Create table and set style
        events_table = Table(events_data, repeatRows=1)
        events_table.setStyle(TableStyle([
            ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
            ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
            ('ALIGN', (0, 0), (-1, 0), 'CENTER'),
            ('BOTTOMPADDING', (0, 0), (-1, 0), 6),
            ('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
            ('FONTSIZE', (0, 0), (-1, -1), 8),
        ]))
        elements.append(events_table)
    else:
        elements.append(Paragraph("No significant events recorded during this period.", styles['Normal']))

    elements.append(Spacer(1, 1*cm))

    # Footer section
    elements.append(Paragraph("Data Source", styles['Heading2']))
    elements.append(Paragraph(
        f"This report was generated using data exported from the Mirox platform via its external reporting API.<br/>"
        f"Report creation date: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')}<br/><br/>"
        f"<i>Generated by SolarViz - External Reporting Tool</i>",
        styles['Normal']))

    # Build the PDF document
    doc.build(elements)

    return output_file


def main():
    """Main function that orchestrates the report generation process"""
    try:
        print("Fetching park information...")
        park_info = fetch_park_info()

        print("Fetching events data...")
        events_df = fetch_events(year=YEAR, quarter=QUARTER)

        print("Fetching production metrics...")
        metrics_df = fetch_metrics(resolution="monthly", year=YEAR)

        print("Calculating performance metrics...")
        performance = calculate_performance_metrics(metrics_df)

        print("Creating visualization...")
        chart_data = create_production_chart(metrics_df)

        print("Generating PDF report...")
        output_file = generate_pdf_report(park_info, events_df, metrics_df, performance, chart_data)

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

    except Exception as e:
        print(f"Error generating report: {str(e)}")


if __name__ == "__main__":
    main()

How It Works

  1. Configuration: The script sets the Mirox API URL, API token, and park UID at the beginning.

  2. Data Collection: The script fetches three types of data from Mirox:

    • Park information (JSON) including name, location, and capacity
    • Significant events (CSV) filtered by priority
    • Monthly energy production metrics (CSV)
  3. Data Processing: The script:

    • Converts CSV data to Pandas DataFrames for easy manipulation
    • Calculates performance metrics like production ratio and shutdown impact
    • Creates a stacked-bar visualization of monthly production and shutdown losses against the target line
  4. Report Generation: The script builds a PDF report (using ReportLab) containing:

    • Park information summary
    • Performance metrics
    • Energy production chart
    • Table of significant events
  5. Output: The report is saved as a PDF file that can be:

    • Opened in any PDF viewer
    • Shared with stakeholders directly
    • Archived or integrated into reporting workflows

Customization Options

The SolarViz report generator can be customized in several ways:

  • Time Periods: Change the YEAR and QUARTER variables to generate reports for different periods
  • Chart Types: Modify the create_production_chart function to use different visualization styles
  • Additional Metrics: Fetch and include additional metrics from Mirox or other data sources
  • Report Format: Adapt the ReportLab layout, or swap it for an HTML or other output format as needed
  • Automation: Integrate the script into automated workflows with cron jobs or task schedulers

Related Features

  • External Report Generation Guide — conceptual overview and additional integration options
  • Metrics Export API — full reference for the template export endpoint used above
  • API Token Usage — create and manage the token this example needs
  • Reports — generate ready-made PDF/CSV reports directly in the platform
MIT Licensed | Copyright 2026 Mirox Verwaltungs GmbH