MiroxMirox
  • Piattaforma

    • Filosofia
    • Panoramica della piattaforma
    • Risorse della piattaforma
    • Disponibilità e finestre di manutenzione
    • Rilasci e versioni
  • Mirox-Cloud

    • Panoramica del cloud
    • Microservizi connessi
  • Mirox-Agent

    • Panoramica dell'agente
    • Opzioni di distribuzione
    • Data Scraper
    • Gemello digitale
  • Dettagli tecnici

    • Raccolta delle metriche
  • Informazioni

    • Impianti supportati
  • Tipi di impianto

    • Impianti solari
    • Inseguitori solari e controllori di parco
    • Parchi eolici
    • Accumulo a batteria
    • Sistema di allarme
  • Monitoraggio e visualizzazione

    • Monitoraggio in tempo reale
    • Gemello digitale
    • Stati dei componenti
    • Codici di stato dell'inverter
    • Eventi dell'inverter
    • Rilevamento delle perdite
    • Limiti di potenza e riduzione
    • Rilevamento dell'efficienza
    • Dashboard KPI
  • Gestione dei dati

    • Eventi
    • Livelli di allarme
    • Ticket
    • Previsioni
    • Report
    • Metriche
  • Alert Manager

    • Panoramica
    • Regole di allarme
    • Allarmi e notifiche
    • Esempi
  • Integrazione e condivisione

    • Cooperazioni
    • Token API
    • VPN
    • Conflitti di indirizzi VPN
    • Proxy
  • IA

    • Assistente IA e wizard
    • Accesso agentico (MCP)
  • Fatturazione

    • Mercato e tariffe
    • Come vengono calcolati i ricavi
    • Contabilità e fatturazione
    • Contratti
  • Collaborazione

    • Inviti
  • Sicurezza

    • Autenticazione
    • Blocco account
    • Criterio di sicurezza dell'organizzazione
    • Sistema di permessi
    • Segmentazione di rete
    • Restrizioni di cooperazione
    • Audit log degli accessi
    • Attività e audit trail
    • Isolamento dei dati
  • Per iniziare

    • Onboarding
    • Configurazione iniziale
  • Personale

    • Usare la VPN
    • Usare il proxy
    • Autenticazione a due fattori
    • Sessioni
    • Token API
    • Notifiche
    • Collegare Microsoft Teams
  • Per impianto

    • Contatti
    • Dispositivi di rete
    • Datalogger
    • Lettore Modbus generico
    • Componenti
    • VPN diretta (per agente)
    • Volume dati
    • Importa lo storico
  • Organizzazione

    • Permessi dei membri
    • Criterio di sicurezza
    • Cooperazioni
    • Archiviazione file
    • Servizi VPN
    • Lavorare con i report
  • Esportazione di dati

    • Metric Export
    • Metric Export API
    • Migrare al Metric Export
    • API di esportazione legacy
    • Generazione esterna di report
    • Grafana
    • Dashboard Grafana
    • Panoramica dell'API
    • Formule MiroxQL (API)
  • Assistenza

    • Richiedi un'integrazione
  • Reportistica

    • Generatore di report esterno
    • Metric Export per Excel
  • Accesso remoto
  • Sicurezza dell'account
  • AI in Mirox
  • Importazione dello storico
  • Report
  • Esportazione dati
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Piattaforma

    • Filosofia
    • Panoramica della piattaforma
    • Risorse della piattaforma
    • Disponibilità e finestre di manutenzione
    • Rilasci e versioni
  • Mirox-Cloud

    • Panoramica del cloud
    • Microservizi connessi
  • Mirox-Agent

    • Panoramica dell'agente
    • Opzioni di distribuzione
    • Data Scraper
    • Gemello digitale
  • Dettagli tecnici

    • Raccolta delle metriche
  • Informazioni

    • Impianti supportati
  • Tipi di impianto

    • Impianti solari
    • Inseguitori solari e controllori di parco
    • Parchi eolici
    • Accumulo a batteria
    • Sistema di allarme
  • Monitoraggio e visualizzazione

    • Monitoraggio in tempo reale
    • Gemello digitale
    • Stati dei componenti
    • Codici di stato dell'inverter
    • Eventi dell'inverter
    • Rilevamento delle perdite
    • Limiti di potenza e riduzione
    • Rilevamento dell'efficienza
    • Dashboard KPI
  • Gestione dei dati

    • Eventi
    • Livelli di allarme
    • Ticket
    • Previsioni
    • Report
    • Metriche
  • Alert Manager

    • Panoramica
    • Regole di allarme
    • Allarmi e notifiche
    • Esempi
  • Integrazione e condivisione

    • Cooperazioni
    • Token API
    • VPN
    • Conflitti di indirizzi VPN
    • Proxy
  • IA

    • Assistente IA e wizard
    • Accesso agentico (MCP)
  • Fatturazione

    • Mercato e tariffe
    • Come vengono calcolati i ricavi
    • Contabilità e fatturazione
    • Contratti
  • Collaborazione

    • Inviti
  • Sicurezza

    • Autenticazione
    • Blocco account
    • Criterio di sicurezza dell'organizzazione
    • Sistema di permessi
    • Segmentazione di rete
    • Restrizioni di cooperazione
    • Audit log degli accessi
    • Attività e audit trail
    • Isolamento dei dati
  • Per iniziare

    • Onboarding
    • Configurazione iniziale
  • Personale

    • Usare la VPN
    • Usare il proxy
    • Autenticazione a due fattori
    • Sessioni
    • Token API
    • Notifiche
    • Collegare Microsoft Teams
  • Per impianto

    • Contatti
    • Dispositivi di rete
    • Datalogger
    • Lettore Modbus generico
    • Componenti
    • VPN diretta (per agente)
    • Volume dati
    • Importa lo storico
  • Organizzazione

    • Permessi dei membri
    • Criterio di sicurezza
    • Cooperazioni
    • Archiviazione file
    • Servizi VPN
    • Lavorare con i report
  • Esportazione di dati

    • Metric Export
    • Metric Export API
    • Migrare al Metric Export
    • API di esportazione legacy
    • Generazione esterna di report
    • Grafana
    • Dashboard Grafana
    • Panoramica dell'API
    • Formule MiroxQL (API)
  • Assistenza

    • Richiedi un'integrazione
  • Reportistica

    • Generatore di report esterno
    • Metric Export per Excel
  • Accesso remoto
  • Sicurezza dell'account
  • AI in Mirox
  • Importazione dello storico
  • Report
  • Esportazione dati
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Reportistica

    • Generatore di report esterni
    • Metric Export per Excel

Generatore di report esterni

Questo esempio preleva da Mirox i dati di un impianto e ne ricava un report proprio: prima le tre richieste con curl, poi un generatore di report in Python completo che le trasforma in un file PDF.

Cerchi invece il lavoro con i fogli di calcolo?

Per Excel — curve di produzione, più impianti affiancati, singoli inverter, sensori, turbine — vedi Metric Export per Excel.

Prerequisiti

  1. Un account Mirox che possa leggere l'impianto.
  2. L'UID dell'impianto — l'identificatore univoco della risorsa impianto su cui vuoi creare il report.
  3. Un token API del gruppo Metric Export — vedi Utilizzo dei token API.

Le tre richieste

DatiRichiestaRisposta
Informazioni sull'impiantoGET /v1/export/report/{park_uid}/infoJSON
EventiGET /v1/export/report/{park_uid}/events.csvCSV
Valori di produzioneGET /v1/export/series/template/{template_uid}CSV, oppure JSON con format=json

Tutti i percorsi seguono https://service.mirox.io/api. I parametri della terza richiesta sono descritti nella Metric Export API, le prime due nell'API di esportazione legacy.

Esempio 1: informazioni sull'impianto

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"
  }
}

Esempio 2: eventi

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 e month scelgono il periodo. Senza di essi, l'esportazione copre l'anno corrente.
  • Senza i due parametri dei separatori, le colonne sono separate da ; e il segno decimale è ,.
  • La data di fine di un evento ancora aperto è Ongoing.

Filtra gli eventi per priorità

Una Priority di 1000 o più contrassegna un evento importante, come uno spegnimento che costa produzione o un allarme. I valori più bassi sono informativi, come una connessione che è tornata.

Esempio 3: valori mensili di produzione

Il modello di sistema Report tecnico (ABCD12340002) per il primo trimestre, per mese, con gli ID come intestazioni di colonna:

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 dà intestazioni di colonna che non cambiano mai. Con header=name, il valore predefinito, le intestazioni sono i nomi delle metriche nella lingua che richiedi, come Grid feed-in (kWh).
  • Una cella vuota significa che non c'è alcun valore. Non significa zero.
  • Più impianti o un intero portfolio entrano in una sola richiesta: park=ABC123DEF456,DEF456ABC123 oppure portfolio=<uid>, con multi_park_agg=split per una colonna per impianto.
  • Le colonne del modello sono elencate nella guida alla migrazione.

Molti impianti su un periodo lungo

Un impianto su un anno intero entra in una sola richiesta, e così 25 impianti. Una richiesta troppo grande risponde 400 over_budget e nomina il periodo più lungo che ci sta. In tal caso richiedi un trimestre alla volta, oppure un portfolio alla volta, e unisci i file. Vedi Dividere un'esportazione grande.

Il modello precedente

I report costruiti sul modello precedente Report Technical v1 continuano a funzionare. Il suo link e le sue colonne sono invariati:

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

Che cosa differisce nel successore ABCD12340002:

ABCD12340001 (legacy)ABCD12340002
Link/v1/export/metrics/template/…/v1/export/series/template/…
Intestazioni di colonnai nomi dell'esportazione precedente, come Energy Production (kWh)i nomi della raccolta di metriche, come Grid feed-in (kWh), oppure gli ID con header=id
Prima colonnaDate (YYYY-MM)Date, oppure timestamp con header=id
Colonna aggiuntivaDays in Month (d) con risoluzione mensilenessuna
Valore mancante0vuoto
Giorni e mesiUTCl'ora locale dell'impianto
Colonne calcolateincluseil performance ratio, la produzione attesa e la resa specifica sono inclusi; le altre restano fuori

Usare le richieste in altri strumenti

  • Python: la libreria requests, come nello script più sotto.
  • Excel: Power Query con l'header Authorization — vedi Excel con Power Query.
  • Power BI: la stessa connessione Power Query di Excel.
  • Tableau e altri: qualsiasi strumento che legga un link CSV o JSON e possa inviare un header.

Risoluzione dei problemi

RispostaSignificatoChe cosa fare
401Il token manca, è scaduto o è stato revocato.Crea un nuovo token, oppure ruota quello esistente.
403 no_accessible_parksNon puoi leggere le misure dell'impianto.Controlla l'UID e il tuo ruolo sull'impianto.
400 over_budgetL'esportazione è troppo grande.Usa il passo o il periodo che la risposta nomina in hint, oppure dividi l'esportazione.
400 step_not_servedIl modello non ha valori con questo passo.Usa uno dei passi in hint.offered_common.
429Troppe esportazioni contemporaneamente, oppure il limite orario è esaurito.Attendi i secondi indicati nell'header Retry-After.
I caratteri speciali appaiono sbagliatiI file sono in UTF-8.Indica la codifica quando importi il file.

Esempio Python: generatore di report SolarViz

Lo script recupera le informazioni sull'impianto, gli eventi importanti del trimestre in corso e i valori mensili dell'anno fino a oggi, e costruisce un PDF con un riepilogo, un grafico e due tabelle.

Prerequisiti

pip install requests pandas matplotlib reportlab
  • requests — le richieste a Mirox
  • pandas — lettura e somma dei dati CSV
  • matplotlib — il grafico
  • reportlab — il file PDF

Lo script legge il token dalla variabile d'ambiente MIROX_TOKEN:

export MIROX_TOKEN="YOUR_API_TOKEN"
python external_report_generator.py

Codice di esempio completo

Puoi anche scaricare lo script.

#!/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()

Come funziona

  1. Configurazione. L'indirizzo di Mirox, l'impianto e il modello stanno in cima. Il token viene dall'ambiente, così non finisce mai in un file che condividi.
  2. Una sola funzione per tutte le richieste. get invia il token, attende e ripete quando Mirox risponde 429, e trasforma un rifiuto dell'esportazione in un errore che porta code, params e hint.
  3. Valori mensili. fetch_metrics richiede il modello sull'anno con step=month e header=id e stampa le note della risposta. In un anno ancora in corso, il periodo termina adesso.
  4. Valori. Le celle vuote vengono lette come «nessun dato» ed escluse dalle somme.
  5. Report. ReportLab costruisce il PDF: informazioni sull'impianto, riepilogo, grafico, tabella mensile ed eventi importanti.

Personalizzazione

  • Periodo: modifica YEAR per i valori e QUARTER per gli eventi.
  • Colonne: usa un altro modello, oppure richiedi le metriche direttamente con GET /v1/export/series e metrics=….
  • Dati più fini: step=day dà righe giornaliere. Per curve a 15 minuti, richiedi le metriche di potenza con GET /v1/export/series.
  • Più impianti: passa più UID in park, oppure un portfolio.
  • Automazione: esegui lo script da uno scheduler, con il token nell'ambiente del job.

Funzionalità correlate

  • Metric Export per Excel — ricette e Power Query
  • Generazione di report esterni — quando costruire i report fuori da Mirox
  • Metric Export API — il riferimento delle richieste usate qui
  • Utilizzo dei token API — crea e gestisci il token
  • Report — i report che Mirox genera da sé
Next
Metric Export per Excel
© 2026 Mirox Verwaltungs GmbH. Tutti i diritti riservati. | Informativa sulla privacy