MiroxMirox
  • Plattform

    • Philosophie
    • Plattform-Übersicht
    • Plattform-Ressourcen
    • Verfügbarkeit und Wartungsfenster
    • Releases und Versionen
  • Mirox-Cloud

    • Cloud-Übersicht
    • Verbundene Microservices
  • Mirox-Agent

    • Agent-Übersicht
    • Bereitstellungsoptionen
    • Data Scraper
    • Digital Twin
  • Technische Details

    • Metriksammlung
  • Information

    • Unterstützte Anlagen
  • Anlagentypen

    • Solaranlagen
    • Solartracker und Parkregler
    • Windanlagen
    • Batteriespeicher
    • Alarmanlage
  • Überwachung & Visualisierung

    • Echtzeit-Monitoring
    • Digitaler Zwilling
    • Komponentenzustände
    • Wechselrichter-Statuscodes
    • Wechselrichter-Ereignisse
    • Verlusterkennung
    • Leistungsgrenzen & Abregelung
    • Effizienzerkennung
    • KPI-Dashboard
  • Datenverwaltung

    • Ereignisse
    • Alarmstufen
    • Tickets
    • Prognosen
    • Berichte
    • Metriken
  • Alert Manager

    • Übersicht
    • Alarmregeln
    • Alarme & Benachrichtigungen
    • Beispiele
  • Integration & Freigabe

    • Kooperationen
    • API-Tokens
    • VPN
    • VPN-Adresskonflikte
    • Proxy
  • KI

    • KI-Assistent & Wizards
    • Agentischer Zugriff (MCP)
  • Abrechnung

    • Markt & Tarife
    • So werden Erlöse berechnet
    • Buchhaltung & Abrechnung
    • Verträge
  • Kollaboration

    • Einladungen
  • Sicherheit

    • Authentifizierung
    • Kontosperre
    • Sicherheitsrichtlinie der Organisation
    • Berechtigungssystem
    • Netzsegmentierung
    • Kooperationsbeschränkungen
    • Zugriffs-Audit-Logging
    • Aktivität & Audit-Trail
    • Datenisolierung
  • Erste Schritte

    • Onboarding
    • Einrichtung
  • Persönlich

    • VPN verwenden
    • Proxy verwenden
    • Zwei-Faktor-Authentifizierung
    • Sitzungen
    • API-Tokens
    • Benachrichtigungen
    • Microsoft Teams verbinden
  • Pro Anlage

    • Kontakte
    • Netzwerkgeräte
    • Datenlogger
    • Generischer Modbus-Leser
    • Komponenten
    • Direktes VPN (pro Agent)
    • Datenvolumen
    • Historie importieren
  • Organisation

    • Mitgliederberechtigungen
    • Sicherheitsrichtlinie
    • Kooperationen
    • Dateispeicher
    • VPN-Dienste
    • Mit Berichten arbeiten
  • Datenexport

    • Metric Export
    • Metric-Export-API
    • Umstieg auf den Metric Export
    • Legacy-Export-API
    • Externe Berichterstellung
    • Grafana
    • Grafana-Dashboards
    • API-Überblick
    • MiroxQL-Formeln (API)
  • Unterstützung

    • Eine Integration anfragen
  • Berichterstellung

    • Externer Berichtgenerator
    • Metric Export für Excel
  • Fernzugriff
  • Kontosicherheit
  • KI in Mirox
  • Historie importieren
  • Berichte
  • Daten-Export
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Plattform

    • Philosophie
    • Plattform-Übersicht
    • Plattform-Ressourcen
    • Verfügbarkeit und Wartungsfenster
    • Releases und Versionen
  • Mirox-Cloud

    • Cloud-Übersicht
    • Verbundene Microservices
  • Mirox-Agent

    • Agent-Übersicht
    • Bereitstellungsoptionen
    • Data Scraper
    • Digital Twin
  • Technische Details

    • Metriksammlung
  • Information

    • Unterstützte Anlagen
  • Anlagentypen

    • Solaranlagen
    • Solartracker und Parkregler
    • Windanlagen
    • Batteriespeicher
    • Alarmanlage
  • Überwachung & Visualisierung

    • Echtzeit-Monitoring
    • Digitaler Zwilling
    • Komponentenzustände
    • Wechselrichter-Statuscodes
    • Wechselrichter-Ereignisse
    • Verlusterkennung
    • Leistungsgrenzen & Abregelung
    • Effizienzerkennung
    • KPI-Dashboard
  • Datenverwaltung

    • Ereignisse
    • Alarmstufen
    • Tickets
    • Prognosen
    • Berichte
    • Metriken
  • Alert Manager

    • Übersicht
    • Alarmregeln
    • Alarme & Benachrichtigungen
    • Beispiele
  • Integration & Freigabe

    • Kooperationen
    • API-Tokens
    • VPN
    • VPN-Adresskonflikte
    • Proxy
  • KI

    • KI-Assistent & Wizards
    • Agentischer Zugriff (MCP)
  • Abrechnung

    • Markt & Tarife
    • So werden Erlöse berechnet
    • Buchhaltung & Abrechnung
    • Verträge
  • Kollaboration

    • Einladungen
  • Sicherheit

    • Authentifizierung
    • Kontosperre
    • Sicherheitsrichtlinie der Organisation
    • Berechtigungssystem
    • Netzsegmentierung
    • Kooperationsbeschränkungen
    • Zugriffs-Audit-Logging
    • Aktivität & Audit-Trail
    • Datenisolierung
  • Erste Schritte

    • Onboarding
    • Einrichtung
  • Persönlich

    • VPN verwenden
    • Proxy verwenden
    • Zwei-Faktor-Authentifizierung
    • Sitzungen
    • API-Tokens
    • Benachrichtigungen
    • Microsoft Teams verbinden
  • Pro Anlage

    • Kontakte
    • Netzwerkgeräte
    • Datenlogger
    • Generischer Modbus-Leser
    • Komponenten
    • Direktes VPN (pro Agent)
    • Datenvolumen
    • Historie importieren
  • Organisation

    • Mitgliederberechtigungen
    • Sicherheitsrichtlinie
    • Kooperationen
    • Dateispeicher
    • VPN-Dienste
    • Mit Berichten arbeiten
  • Datenexport

    • Metric Export
    • Metric-Export-API
    • Umstieg auf den Metric Export
    • Legacy-Export-API
    • Externe Berichterstellung
    • Grafana
    • Grafana-Dashboards
    • API-Überblick
    • MiroxQL-Formeln (API)
  • Unterstützung

    • Eine Integration anfragen
  • Berichterstellung

    • Externer Berichtgenerator
    • Metric Export für Excel
  • Fernzugriff
  • Kontosicherheit
  • KI in Mirox
  • Historie importieren
  • Berichte
  • Daten-Export
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Berichterstellung

    • Externer Berichtsgenerator
    • Metric Export für Excel

Externer Berichtsgenerator

Dieses Beispiel holt die Daten einer Anlage aus Mirox und erstellt daraus einen eigenen Bericht: zuerst die drei Anfragen mit curl, danach ein vollständiger Python-Berichtsgenerator, der daraus eine PDF-Datei macht.

Suchen Sie stattdessen Arbeit mit Tabellen?

Für Excel — Produktionskurven, mehrere Anlagen nebeneinander, einzelne Wechselrichter, Sensoren, Turbinen — siehe Metric Export für Excel.

Voraussetzungen

  1. Ein Mirox-Konto, das die Anlage lesen darf.
  2. Die UID der Anlage — die eindeutige Kennung der Anlagen-Ressource, über die Sie berichten möchten.
  3. Ein API-Token der Gruppe Metric Export — siehe API-Token-Nutzung.

Die drei Anfragen

DatenAnfrageAntwort
AnlageninformationenGET /v1/export/report/{park_uid}/infoJSON
EreignisseGET /v1/export/report/{park_uid}/events.csvCSV
ProduktionswerteGET /v1/export/series/template/{template_uid}CSV, oder JSON mit format=json

Alle Pfade folgen auf https://service.mirox.io/api. Die Parameter der dritten Anfrage beschreibt die Metric-Export-API, die der ersten beiden die Legacy-Export-API.

Beispiel 1: Anlageninformationen

curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/info" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
{
  "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"
  }
}

Beispiel 2: Ereignisse

curl "https://service.mirox.io/api/v1/export/report/ABC123DEF456/events.csv?year=2026&quarter=1&separator_csv=,&separator_decimal=." \
  -H "Authorization: Bearer YOUR_API_TOKEN"
Event ID,Start Date,End Date,Duration (hours),Type,Creator,Description,Priority
9876543210123456789,2026-01-15 08:45,Ongoing,720.25,Sensor Error,System,Irradiation sensor malfunction detected,1200
8765432101234567890,2026-02-10 14:30,2026-02-15 09:15,114.75,Data Availability,System,Connection issues with data acquisition system,1100
  • year, quarter und month wählen den Zeitraum. Ohne sie umfasst der Export das laufende Jahr.
  • Ohne die beiden Trennzeichen-Parameter sind die Spalten durch ; getrennt, und das Dezimalzeichen ist ,.
  • Das Enddatum eines noch offenen Ereignisses ist Ongoing.

Ereignisse nach Priorität filtern

Eine Priority von 1000 oder mehr kennzeichnet ein wichtiges Ereignis, etwa eine Abschaltung, die Produktion kostet, oder einen Alarm. Niedrigere Werte sind informativ, etwa eine Verbindung, die zurückgekehrt ist.

Beispiel 3: Monatliche Produktionswerte

Die Systemvorlage Technischer Bericht (ABCD12340002) für das erste Quartal, je Monat, mit IDs als Spaltenköpfen:

curl --compressed "https://service.mirox.io/api/v1/export/series/template/ABCD12340002?park=ABC123DEF456&year=2026&quarter=1&step=month&header=id&separator_csv=,&separator_decimal=." \
  -H "Authorization: Bearer YOUR_API_TOKEN"
timestamp,plant.grid_feedin,plant.rad_pyranometer,plant.rad_weather,availability.sunhours,availability.inverter_park,plant.shutdown_grid,plant.shutdown_external,plant.expected_weather,plant.expected_pyranometer,plant.pr_weather,plant.pr_pyranometer,plant.specific_yield
2026-01,950000.4,18.4,18.1,62.5,99.1,15000.5,25000.8,980000.0,975000.0,81.2,82.0,22.6
2026-02,1250000.3,22.8,22.5,96.0,99.4,2000.3,18500.4,1300000.0,1290000.0,81.0,81.6,29.8
2026-03,1850000.8,35.4,35.0,148.3,98.9,45000.2,30000.7,1900000.0,1885000.0,80.7,81.3,44.0
  • header=id ergibt Spaltenköpfe, die sich nie ändern. Mit header=name, dem Standard, sind die Spaltenköpfe die Namen der Metriken in der Sprache, die Sie anfordern, etwa Grid feed-in (kWh).
  • Eine leere Zelle bedeutet, dass es keinen Wert gibt. Sie bedeutet nicht null.
  • Mehrere Anlagen oder ein ganzes Portfolio passen in eine Anfrage: park=ABC123DEF456,DEF456ABC123 oder portfolio=<uid>, mit multi_park_agg=split für eine Spalte je Anlage.
  • Die Spalten der Vorlage stehen im Leitfaden zum Umstieg.

Viele Anlagen über einen langen Zeitraum

Eine Anlage über ein ganzes Jahr passt in eine Anfrage, ebenso 25 Anlagen. Eine zu große Anfrage antwortet mit 400 over_budget und nennt den längsten Zeitraum, der passt. Fordern Sie dann ein Quartal nach dem anderen oder ein Portfolio nach dem anderen an und fügen Sie die Dateien zusammen. Siehe Einen großen Export aufteilen.

Die bisherige Vorlage

Berichte, die auf der bisherigen Vorlage Report Technical v1 aufbauen, funktionieren weiter. Ihr Link und ihre Spalten sind unverändert:

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

Was sich beim Nachfolger ABCD12340002 unterscheidet:

ABCD12340001 (Legacy)ABCD12340002
Link/v1/export/metrics/template/…/v1/export/series/template/…
Spaltenköpfedie Namen des bisherigen Exports, etwa Energy Production (kWh)die Namen der Metriksammlung, etwa Grid feed-in (kWh), oder IDs mit header=id
Erste SpalteDate (YYYY-MM)Date, oder timestamp mit header=id
Zusätzliche SpalteDays in Month (d) bei monatlicher Auflösungkeine
Fehlender Wert0leer
Tage und MonateUTCdie Ortszeit der Anlage
Berechnete Spaltenenthaltendie Performance Ratio, die erwartete Erzeugung und der spezifische Ertrag sind enthalten; die übrigen werden ausgelassen

Die Anfragen in anderen Werkzeugen nutzen

  • Python: die Bibliothek requests, wie im Skript unten.
  • Excel: Power Query mit dem Header Authorization — siehe Excel mit Power Query.
  • Power BI: dieselbe Power-Query-Verbindung wie in Excel.
  • Tableau und andere: jedes Werkzeug, das einen CSV- oder JSON-Link liest und einen Header senden kann.

Fehlerbehebung

AntwortBedeutungWas zu tun ist
401Das Token fehlt, ist abgelaufen oder widerrufen.Erstellen Sie ein neues Token oder rotieren Sie das bestehende.
403 no_accessible_parksSie können die Messwerte der Anlage nicht lesen.Prüfen Sie die UID und Ihre Rolle auf der Anlage.
400 over_budgetDer Export ist zu groß.Nutzen Sie die Schrittweite oder den Zeitraum, den die Antwort in hint nennt, oder teilen Sie den Export auf.
400 step_not_servedDie Vorlage hat in dieser Schrittweite keinen Wert.Nutzen Sie eine der Schrittweiten in hint.offered_common.
429Zu viele Exporte gleichzeitig, oder das stündliche Kontingent ist aufgebraucht.Warten Sie die Sekunden im Header Retry-After ab.
Sonderzeichen sehen falsch ausDie Dateien sind UTF-8.Geben Sie die Kodierung an, wenn Sie die Datei importieren.

Python-Beispiel: SolarViz Report Generator

Das Skript holt die Anlageninformationen, die wichtigen Ereignisse des laufenden Quartals und die Monatswerte des bisherigen Jahres und erstellt eine PDF-Datei mit einer Zusammenfassung, einem Diagramm und zwei Tabellen.

Voraussetzungen

pip install requests pandas matplotlib reportlab
  • requests — die Anfragen an Mirox
  • pandas — das Lesen und Aufsummieren der CSV-Daten
  • matplotlib — das Diagramm
  • reportlab — die PDF-Datei

Das Skript liest das Token aus der Umgebungsvariablen MIROX_TOKEN:

export MIROX_TOKEN="YOUR_API_TOKEN"
python external_report_generator.py

Vollständiger Beispielcode

Sie können das Skript auch herunterladen.

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

Fetches the data of one plant from the Mirox API and builds an energy
production report with a chart as a PDF file:

  * plant information          GET /api/v1/export/report/{plant}/info
  * events                     GET /api/v1/export/report/{plant}/events.csv
  * monthly production figures GET /api/v1/export/series/template/{template}

Requires: pip install requests pandas matplotlib reportlab
The API token is read from the environment variable MIROX_TOKEN.
"""

import io
import os
import sys
import time
from datetime import datetime, timezone

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
import requests
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.platypus import Image, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle

# Configuration - replace with your values
API_URL = "https://service.mirox.io"
API_TOKEN = os.environ.get("MIROX_TOKEN", "YOUR_API_TOKEN")  # group "Metric Export"
PARK_UID = "ABC123DEF456"  # the UID of the plant
TEMPLATE_UID = "ABCD12340002"  # system template "Report Technical"

# The report covers this year so far; the events are those of the current quarter
YEAR = datetime.now(timezone.utc).year
QUARTER = (datetime.now(timezone.utc).month - 1) // 3 + 1

HEADERS = {"Authorization": f"Bearer {API_TOKEN}", "Accept-Encoding": "gzip"}

# One CSV dialect for every request, independent of regional settings
CSV_FORMAT = {"separator_csv": ",", "separator_decimal": "."}

# The columns the report reads, by metric id (header=id)
PRODUCTION = "plant.grid_feedin"
EXPECTED = "plant.expected_weather"
LOSS_GRID = "plant.shutdown_grid"
LOSS_MARKETER = "plant.shutdown_external"
PERFORMANCE_RATIO = "plant.pr_weather"


class ExportRefused(Exception):
    """A coded refusal of the Metric Export: what was wrong, and the way out."""

    def __init__(self, status, detail):
        self.status = status
        self.code = detail.get("code", "")
        self.params = detail.get("params", {})
        self.hint = detail.get("hint", {})
        super().__init__(f"{status} {self.code}: {detail.get('message', '')} "
                         f"params={self.params} hint={self.hint}")


def get(path, params=None, retries=3):
    """GET with the token. Waits and repeats when the export is busy (429)."""
    for _attempt in range(retries):
        response = requests.get(f"{API_URL}{path}", headers=HEADERS, params=params, timeout=90)
        if response.status_code == 429:
            time.sleep(int(response.headers.get("Retry-After", "5")))
            continue
        if response.status_code >= 400:
            try:
                detail = response.json().get("detail")
            except ValueError:
                detail = None
            if isinstance(detail, dict):
                raise ExportRefused(response.status_code, detail)
            response.raise_for_status()
        return response
    raise RuntimeError(f"{path}: still busy after {retries} attempts")


def fetch_park_info():
    """Plant information as a dictionary."""
    return get(f"/api/v1/export/report/{PARK_UID}/info").json()


def fetch_events(year, quarter):
    """The important events of a quarter as a DataFrame."""
    response = get(f"/api/v1/export/report/{PARK_UID}/events.csv",
                   {"year": year, "quarter": quarter, **CSV_FORMAT})
    events = pd.read_csv(io.StringIO(response.content.decode("utf-8")))
    if events.empty:
        return events
    # A priority of 1000 or more marks an important event
    events = events[events["Priority"] >= 1000]
    return events.drop(columns=["Event ID", "Priority"], errors="ignore")


def fetch_metrics(year):
    """Monthly figures of the template for the year so far.

    header=id gives column names that never change. A year in progress ends
    now; the notes of the answer say so.
    """
    response = get(f"/api/v1/export/series/template/{TEMPLATE_UID}", {
        "park": PARK_UID, "year": year, "step": "month", "header": "id", **CSV_FORMAT})
    for note in filter(None, response.headers.get("X-Mirox-Notes", "").split(";")):
        print(f"  note: {note}")
    metrics = pd.read_csv(io.StringIO(response.content.decode("utf-8")))
    # An empty cell means "no data"; it is read as NaN and left out of the sums
    return metrics.rename(columns={"timestamp": "month"})


def create_production_chart(metrics):
    """Monthly production with the shutdown losses stacked, against the expectation."""
    plt.figure(figsize=(8, 4))
    positions = range(len(metrics))
    production = metrics[PRODUCTION].fillna(0)
    loss_grid = metrics[LOSS_GRID].fillna(0)
    loss_marketer = metrics[LOSS_MARKETER].fillna(0)

    plt.bar(positions, production, color="#2986cc", label="Grid feed-in")
    plt.bar(positions, loss_grid, bottom=production, color="#e06666",
            label="Shutdown loss, grid operator")
    plt.bar(positions, loss_marketer, bottom=production + loss_grid, color="#f1c232",
            label="Shutdown loss, direct marketer")
    plt.plot(positions, metrics[EXPECTED], marker="o", color="#6aa84f", linewidth=2,
             label="Expected production")

    plt.title("Monthly production and shutdown losses")
    plt.xlabel("Month")
    plt.ylabel("Energy (kWh)")
    plt.xticks(positions, metrics["month"], rotation=45)
    plt.grid(axis="y", alpha=0.3)
    plt.legend(loc="upper left")
    plt.tight_layout()

    buffer = io.BytesIO()
    plt.savefig(buffer, format="png", dpi=300)
    buffer.seek(0)
    plt.close()
    return buffer


def calculate_performance(metrics):
    """The figures of the summary table."""
    production = metrics[PRODUCTION].sum()
    expected = metrics[EXPECTED].sum()
    loss_grid = metrics[LOSS_GRID].sum()
    loss_marketer = metrics[LOSS_MARKETER].sum()
    losses = loss_grid + loss_marketer
    return {
        "production": production,
        "expected": expected,
        "production_vs_expected": production / expected * 100 if expected else None,
        "loss_grid": loss_grid,
        "loss_marketer": loss_marketer,
        "losses": losses,
        "loss_share": losses / (production + losses) * 100 if production + losses else None,
    }


def number(value, unit="", digits=2):
    if value is None or pd.isna(value):
        return "-"
    return f"{value:,.{digits}f}{unit}"


def plain_table(rows, widths):
    table = Table(rows, colWidths=widths)
    table.setStyle(TableStyle([
        ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("TOPPADDING", (0, 0), (-1, -1), 3),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
    ]))
    return table


def grid_table(rows, font_size=9):
    table = Table(rows, repeatRows=1)
    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"),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("FONTSIZE", (0, 0), (-1, -1), font_size),
    ]))
    return table


def generate_pdf_report(park_info, events, metrics, performance, chart):
    """Build the PDF and return its file name."""
    output_file = f"SolarViz_Report_{park_info['name'].replace(' ', '_')}_{YEAR}_Q{QUARTER}.pdf"
    doc = SimpleDocTemplate(output_file, pagesize=A4)
    styles = getSampleStyleSheet()
    styles["Title"].fontSize = 18
    address = park_info.get("address") or {}
    portfolio = park_info.get("portfolio") or {}
    organization = park_info.get("organization") or {}

    elements = [
        Paragraph(f"Energy Production Report: {park_info['name']}", styles["Title"]),
        Spacer(1, 0.5 * cm),
        Paragraph("Plant Information", styles["Heading2"]),
        plain_table([
            ["Name:", park_info["name"]],
            ["Type:", park_info.get("type", "")],
            ["Location:", f"{address.get('city', '')}, {address.get('country', '')}"],
            ["Peak power:", number((park_info.get("peak_production_w") or 0) / 1e6, " MW")],
            ["Portfolio:", portfolio.get("name", "")],
            ["Organization:", organization.get("name", "")],
        ], [3 * cm, 10 * cm]),
        Spacer(1, 0.5 * cm),
        Paragraph(f"Summary ({YEAR} to date)", styles["Heading2"]),
        plain_table([
            ["Grid feed-in:", number(performance["production"], " kWh")],
            ["Expected production:", number(performance["expected"], " kWh")],
            ["Feed-in against expectation:", number(performance["production_vs_expected"], " %")],
            ["Shutdown losses:", number(performance["losses"], " kWh")],
            ["  by the grid operator:", number(performance["loss_grid"], " kWh")],
            ["  by the direct marketer:", number(performance["loss_marketer"], " kWh")],
            ["Share of the losses:", number(performance["loss_share"], " %")],
        ], [6 * cm, 7 * cm]),
        Spacer(1, 0.5 * cm),
        Paragraph("Monthly Production", styles["Heading2"]),
        Image(chart, width=16 * cm, height=8 * cm),
        Spacer(1, 0.5 * cm),
    ]

    rows = [["Month", "Feed-in (kWh)", "Expected (kWh)", "Loss grid (kWh)",
             "Loss marketer (kWh)", "PR (%)"]]
    for _, row in metrics.iterrows():
        rows.append([row["month"], number(row[PRODUCTION]), number(row[EXPECTED]),
                     number(row[LOSS_GRID]), number(row[LOSS_MARKETER]),
                     number(row[PERFORMANCE_RATIO], digits=1)])
    elements += [grid_table(rows), Spacer(1, 0.5 * cm),
                 Paragraph("Important Events", styles["Heading2"])]

    if events.empty:
        elements.append(Paragraph("No important events in this quarter.", styles["Normal"]))
    else:
        elements.append(grid_table([events.columns.tolist()] + events.values.tolist(), 8))

    elements += [
        Spacer(1, 1 * cm),
        Paragraph("Data Source", styles["Heading2"]),
        Paragraph(
            "Data exported from the Mirox platform through the Metric Export API.<br/>"
            f"Created: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')} UTC<br/><br/>"
            "<i>Generated by SolarViz - External Reporting Tool</i>", styles["Normal"]),
    ]
    doc.build(elements)
    return output_file


def main():
    try:
        print("Fetching plant information...")
        park_info = fetch_park_info()

        print("Fetching events...")
        events = fetch_events(YEAR, QUARTER)

        print("Fetching monthly figures...")
        metrics = fetch_metrics(YEAR)

        print("Building the report...")
        performance = calculate_performance(metrics)
        chart = create_production_chart(metrics)
        output_file = generate_pdf_report(park_info, events, metrics, performance, chart)
        print(f"Report created: {output_file}")
    except ExportRefused as refused:
        print(f"The export was refused: {refused}")
        if refused.code == "over_budget":
            print("Use a coarser step or a shorter range, see hint.step and hint.max_days.")
        sys.exit(1)
    except requests.RequestException as error:
        print(f"The request failed: {error}")
        sys.exit(1)


if __name__ == "__main__":
    main()

So funktioniert es

  1. Konfiguration. Die Adresse von Mirox, die Anlage und die Vorlage stehen oben. Das Token kommt aus der Umgebung, es landet also nie in einer Datei, die Sie weitergeben.
  2. Eine Funktion für alle Anfragen. get sendet das Token, wartet und wiederholt, wenn Mirox mit 429 antwortet, und macht aus einer Ablehnung des Exports einen Fehler, der code, params und hint trägt.
  3. Monatswerte. fetch_metrics fordert die Vorlage über das Jahr mit step=month und header=id an und gibt die Hinweise der Antwort aus. In einem noch laufenden Jahr endet der Zeitraum jetzt.
  4. Werte. Leere Zellen werden als „keine Daten“ gelesen und aus den Summen ausgelassen.
  5. Bericht. ReportLab erstellt die PDF-Datei: Anlageninformationen, Zusammenfassung, Diagramm, Monatstabelle und wichtige Ereignisse.

Anpassung

  • Zeitraum: Ändern Sie YEAR für die Werte und QUARTER für die Ereignisse.
  • Spalten: Nutzen Sie eine andere Vorlage oder fordern Sie Metriken direkt mit GET /v1/export/series und metrics=… an.
  • Feinere Daten: step=day ergibt Tageszeilen. Für 15-Minuten-Kurven fordern Sie die Leistungs-Metriken mit GET /v1/export/series an.
  • Mehrere Anlagen: Übergeben Sie mehrere UIDs in park oder ein portfolio.
  • Automatisierung: Starten Sie das Skript über einen Scheduler, mit dem Token in der Umgebung des Jobs.

Verwandte Funktionen

  • Metric Export für Excel — Rezepte und Power Query
  • Externe Berichtserstellung — wann Sie Berichte außerhalb von Mirox erstellen
  • Metric-Export-API — die Referenz der hier verwendeten Anfragen
  • API-Token-Nutzung — das Token erstellen und verwalten
  • Berichte — die Berichte, die Mirox selbst erstellt
Next
Metric Export für Excel
© 2026 Mirox Verwaltungs GmbH. Alle Rechte vorbehalten. | Datenschutz