MiroxMirox
  • Plataforma

    • Filosofia
    • Visão geral da plataforma
    • Recursos da plataforma
    • Disponibilidade e janelas de manutenção
    • Lançamentos e versões
  • Mirox-Cloud

    • Visão geral da cloud
    • Microsserviços ligados
  • Mirox-Agent

    • Visão geral do agente
    • Opções de implementação
    • Data Scraper
    • Gémeo digital
  • Detalhes técnicos

    • Recolha de métricas
  • Informações

    • Centrais suportadas
  • Tipos de central

    • Centrais solares
    • Seguidores solares e controladores do parque
    • Parques eólicos
    • Armazenamento por baterias
    • Sistema de alarme
  • Monitorização e visualização

    • Monitorização em tempo real
    • Gémeo digital
    • Estados dos componentes
    • Códigos de estado do inversor
    • Eventos do inversor
    • Deteção de perdas
    • Limites de potência e corte
    • Deteção de eficiência
    • Painel de KPI
  • Gestão de dados

    • Eventos
    • Níveis de alarme
    • Tickets
    • Previsões
    • Relatórios
    • Métricas
  • Alert Manager

    • Visão geral
    • Regras de alerta
    • Alertas e notificações
    • Exemplos
  • Integração e partilha

    • Cooperações
    • Tokens de API
    • VPN
    • Conflitos de endereços VPN
    • Proxy
  • IA

    • Assistente de IA e assistentes
    • Acesso agêntico (MCP)
  • Faturação

    • Mercado e tarifas
    • Como a Receita É Calculada
    • Contabilidade e faturação
    • Contratos
  • Colaboração

    • Convites
  • Segurança

    • Autenticação
    • Bloqueio de conta
    • Política de segurança da organização
    • Sistema de permissões
    • Segmentação de rede
    • Restrições de cooperação
    • Registo de auditoria de acesso
    • Atividade e auditoria
    • Isolamento de Dados
  • Primeiros passos

    • Onboarding
    • Configuração inicial
  • Pessoal

    • Utilizar a VPN
    • Utilizar o proxy
    • Autenticação de dois fatores
    • Sessões
    • Tokens de API
    • Notificações
    • Ligar o Microsoft Teams
  • Por central

    • Contactos
    • Dispositivos de rede
    • Registadores de dados
    • Leitor Modbus genérico
    • Componentes
    • VPN direta (por agente)
    • Volume de dados
    • Importar histórico
  • Organização

    • Permissões de membros
    • Política de segurança
    • Cooperações
    • Armazenamento de ficheiros
    • Serviços VPN
    • Trabalhar com Relatórios
  • Exportação de dados

    • Exportação de métricas
    • API de exportação de métricas
    • Migrar para a exportação de métricas
    • API de exportação legada
    • Geração externa de relatórios
    • Grafana
    • Dashboards Grafana
    • Visão geral da API
    • Fórmulas MiroxQL (API)
  • Suporte

    • Solicitar uma integração
  • Relatórios

    • Gerador de relatórios externo
    • Exportação de métricas para Excel
  • Acesso Remoto
  • Segurança da conta
  • IA na Mirox
  • Importação do histórico
  • Relatórios
  • Exportação de dados
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Plataforma

    • Filosofia
    • Visão geral da plataforma
    • Recursos da plataforma
    • Disponibilidade e janelas de manutenção
    • Lançamentos e versões
  • Mirox-Cloud

    • Visão geral da cloud
    • Microsserviços ligados
  • Mirox-Agent

    • Visão geral do agente
    • Opções de implementação
    • Data Scraper
    • Gémeo digital
  • Detalhes técnicos

    • Recolha de métricas
  • Informações

    • Centrais suportadas
  • Tipos de central

    • Centrais solares
    • Seguidores solares e controladores do parque
    • Parques eólicos
    • Armazenamento por baterias
    • Sistema de alarme
  • Monitorização e visualização

    • Monitorização em tempo real
    • Gémeo digital
    • Estados dos componentes
    • Códigos de estado do inversor
    • Eventos do inversor
    • Deteção de perdas
    • Limites de potência e corte
    • Deteção de eficiência
    • Painel de KPI
  • Gestão de dados

    • Eventos
    • Níveis de alarme
    • Tickets
    • Previsões
    • Relatórios
    • Métricas
  • Alert Manager

    • Visão geral
    • Regras de alerta
    • Alertas e notificações
    • Exemplos
  • Integração e partilha

    • Cooperações
    • Tokens de API
    • VPN
    • Conflitos de endereços VPN
    • Proxy
  • IA

    • Assistente de IA e assistentes
    • Acesso agêntico (MCP)
  • Faturação

    • Mercado e tarifas
    • Como a Receita É Calculada
    • Contabilidade e faturação
    • Contratos
  • Colaboração

    • Convites
  • Segurança

    • Autenticação
    • Bloqueio de conta
    • Política de segurança da organização
    • Sistema de permissões
    • Segmentação de rede
    • Restrições de cooperação
    • Registo de auditoria de acesso
    • Atividade e auditoria
    • Isolamento de Dados
  • Primeiros passos

    • Onboarding
    • Configuração inicial
  • Pessoal

    • Utilizar a VPN
    • Utilizar o proxy
    • Autenticação de dois fatores
    • Sessões
    • Tokens de API
    • Notificações
    • Ligar o Microsoft Teams
  • Por central

    • Contactos
    • Dispositivos de rede
    • Registadores de dados
    • Leitor Modbus genérico
    • Componentes
    • VPN direta (por agente)
    • Volume de dados
    • Importar histórico
  • Organização

    • Permissões de membros
    • Política de segurança
    • Cooperações
    • Armazenamento de ficheiros
    • Serviços VPN
    • Trabalhar com Relatórios
  • Exportação de dados

    • Exportação de métricas
    • API de exportação de métricas
    • Migrar para a exportação de métricas
    • API de exportação legada
    • Geração externa de relatórios
    • Grafana
    • Dashboards Grafana
    • Visão geral da API
    • Fórmulas MiroxQL (API)
  • Suporte

    • Solicitar uma integração
  • Relatórios

    • Gerador de relatórios externo
    • Exportação de métricas para Excel
  • Acesso Remoto
  • Segurança da conta
  • IA na Mirox
  • Importação do histórico
  • Relatórios
  • Exportação de dados
  • English
  • Deutsch
  • Español
  • Français
  • Português
  • Italiano
  • English
  • Relatórios

    • Gerador de relatórios externo
    • Exportação de métricas para Excel

Gerador de relatórios externo

Este exemplo obtém do Mirox os dados de uma central e cria a partir deles um relatório próprio: primeiro os três pedidos com curl, depois um gerador de relatórios em Python completo que os transforma num ficheiro PDF.

Procura antes trabalho em folhas de cálculo?

Para o Excel — curvas de produção, várias centrais lado a lado, inversores individuais, sensores, turbinas — consulte Exportação de métricas para Excel.

Pré-requisitos

  1. Uma conta Mirox que possa ler a central.
  2. O UID da central — o identificador único do recurso de central sobre o qual quer criar o relatório.
  3. Um token de API do grupo Metric Export — consulte Utilização de tokens de API.

Os três pedidos

DadosPedidoResposta
Informações da centralGET /v1/export/report/{park_uid}/infoJSON
EventosGET /v1/export/report/{park_uid}/events.csvCSV
Valores de produçãoGET /v1/export/series/template/{template_uid}CSV, ou JSON com format=json

Todos os caminhos são relativos a https://service.mirox.io/api. Os parâmetros do terceiro pedido estão descritos na API de exportação de métricas, os dos dois primeiros na API de exportação legada.

Exemplo 1: informações da central

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

Exemplo 2: eventos

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 selecionam o período. Sem eles, a exportação cobre o ano em curso.
  • Sem os dois parâmetros de separador, as colunas são separadas por ; e o separador decimal é ,.
  • A data de fim de um evento ainda aberto é Ongoing.

Filtrar eventos por prioridade

Uma Priority de 1000 ou mais assinala um evento importante, como um corte que custa produção ou um alarme. Os valores mais baixos são informativos, como uma ligação que foi restabelecida.

Exemplo 3: valores mensais de produção

O modelo do sistema Relatório técnico (ABCD12340002) para o primeiro trimestre, por mês, com IDs como cabeçalhos das colunas:

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á cabeçalhos de colunas que nunca mudam. Com header=name, a predefinição, os cabeçalhos são os nomes das métricas no idioma que pedir, como Grid feed-in (kWh).
  • Uma célula vazia significa que não há valor. Não significa zero.
  • Várias centrais ou uma carteira inteira cabem num só pedido: park=ABC123DEF456,DEF456ABC123 ou portfolio=<uid>, com multi_park_agg=split para uma coluna por central.
  • As colunas do modelo estão listadas no guia de migração.

Muitas centrais num período longo

Uma central ao longo de um ano inteiro cabe num único pedido, e 25 centrais também. Um pedido demasiado grande responde 400 over_budget e indica o período mais longo que cabe. Peça então um trimestre de cada vez, ou um portefólio de cada vez, e junte os ficheiros. Consulte Dividir uma exportação grande.

O modelo anterior

Os relatórios assentes no modelo anterior Report Technical v1 continuam a funcionar. A sua ligação e as suas colunas não mudaram:

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

O que difere no sucessor ABCD12340002:

ABCD12340001 (legado)ABCD12340002
Ligação/v1/export/metrics/template/…/v1/export/series/template/…
Cabeçalhos das colunasos nomes da exportação anterior, como Energy Production (kWh)os nomes da coleção de métricas, como Grid feed-in (kWh), ou IDs com header=id
Primeira colunaDate (YYYY-MM)Date, ou timestamp com header=id
Coluna adicionalDays in Month (d) na resolução mensalnenhuma
Valor em falta0vazio
Dias e mesesUTCa hora local da central
Colunas calculadasincluídaso performance ratio, a produção esperada e a produção específica estão incluídos; as restantes ficam de fora

Usar os pedidos noutras ferramentas

  • Python: a biblioteca requests, como no script abaixo.
  • Excel: Power Query com o cabeçalho Authorization — consulte Excel com Power Query.
  • Power BI: a mesma ligação Power Query do Excel.
  • Tableau e outras: qualquer ferramenta que leia uma ligação CSV ou JSON e possa enviar um cabeçalho.

Resolução de problemas

RespostaSignificadoO que fazer
401O token está em falta, expirou ou foi revogado.Crie um novo token, ou rode o existente.
403 no_accessible_parksNão pode ler as medições da central.Verifique o UID e a sua função na central.
400 over_budgetA exportação é demasiado grande.Use o passo ou o período que a resposta indica em hint, ou divida a exportação.
400 step_not_servedO modelo não tem valor neste passo.Use um dos passos em hint.offered_common.
429Demasiadas exportações em simultâneo, ou o limite horário está esgotado.Aguarde os segundos indicados no cabeçalho Retry-After.
Os caracteres especiais aparecem erradosOs ficheiros estão em UTF-8.Indique a codificação quando importar o ficheiro.

Exemplo em Python: gerador de relatórios SolarViz

O script obtém as informações da central, os eventos importantes do trimestre em curso e os valores mensais do ano até à data, e cria um PDF com um resumo, um gráfico e duas tabelas.

Pré-requisitos

pip install requests pandas matplotlib reportlab
  • requests — os pedidos ao Mirox
  • pandas — ler e somar os dados CSV
  • matplotlib — o gráfico
  • reportlab — o ficheiro PDF

O script lê o token da variável de ambiente MIROX_TOKEN:

export MIROX_TOKEN="YOUR_API_TOKEN"
python external_report_generator.py

Código de exemplo completo

Também pode transferir o 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()

Como funciona

  1. Configuração. O endereço do Mirox, a central e o modelo estão no topo. O token vem do ambiente, pelo que nunca vai parar a um ficheiro que partilhe.
  2. Uma função para todos os pedidos. get envia o token, aguarda e repete quando o Mirox responde 429, e transforma uma recusa da exportação num erro que contém code, params e hint.
  3. Valores mensais. fetch_metrics pede o modelo para o ano com step=month e header=id, e imprime as notas da resposta. Num ano ainda em curso, o período termina agora.
  4. Valores. As células vazias são lidas como «sem dados» e ficam de fora das somas.
  5. Relatório. O ReportLab cria o PDF: informações da central, resumo, gráfico, tabela mensal e eventos importantes.

Personalização

  • Período: altere YEAR para os valores e QUARTER para os eventos.
  • Colunas: use outro modelo, ou peça métricas diretamente com GET /v1/export/series e metrics=….
  • Dados mais finos: step=day dá linhas diárias. Para curvas de 15 minutos, peça as métricas de potência com GET /v1/export/series.
  • Várias centrais: passe vários UIDs em park, ou um portfolio.
  • Automatização: execute o script a partir de um agendador, com o token no ambiente da tarefa.

Funcionalidades relacionadas

  • Exportação de métricas para Excel — receitas e Power Query
  • Geração externa de relatórios — quando criar relatórios fora do Mirox
  • API de exportação de métricas — a referência dos pedidos aqui usados
  • Utilização de tokens de API — criar e gerir o token
  • Relatórios — os relatórios que o próprio Mirox gera
Next
Exportação de métricas para Excel
© 2026 Mirox Verwaltungs GmbH. Todos os direitos reservados. | Política de Privacidade