{
  "schemaVersion": 1,
  "title": "Extrato de evidência: corte de geração (curtailment) eólico e fotovoltaico, jan–abr/2026, recálculo a partir dos dados abertos do ONS",
  "descricao": "Extrato da conferência feita em 08/11/2026 para o texto/artigo corte-de-geracao-curtailment-baterias: o extrato traz as URLs, as somas SHA-256, o programa e os resultados; os CSVs não estão incluídos e devem ser baixados do ONS. Lista os 8 CSVs mensais do Portal de Dados Abertos do ONS (constrained-off, eólico e fotovoltaico, jan–abr/2026), as somas SHA-256 e os tamanhos, as fórmulas, a comparação com o Canal Solar e o programa completo de cálculo, para que qualquer leitor reproduza os números.",
  "dataColeta": "08/11/2026",
  "fonte": {
    "provedor": "Operador Nacional do Sistema Elétrico (ONS)",
    "portal": "Portal de Dados Abertos do ONS (dados.ons.org.br)",
    "conjuntos": [
      {
        "nome": "Dados de Restrição de Operação por Constrained-off de Usinas Eólicas",
        "paginaUrl": "https://dados.ons.org.br/dataset/restricao_coff_eolica_usi"
      },
      {
        "nome": "Dados de Restrição de Operação por Constrained-off de Usinas Fotovoltaicas",
        "paginaUrl": "https://dados.ons.org.br/dataset/restricao_coff_fotovoltaica"
      }
    ],
    "dicionarios": [
      "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_eolica_tm/DicionarioDados_RestricaoContrainedoff_UsiEolicas.json",
      "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_fotovoltaica_tm/DicionarioDados_RestricaoContrainedoff_UsiFotovoltaica.json"
    ]
  },
  "escopo": "Os conjuntos do ONS cobrem as usinas eólicas e fotovoltaicas classificadas nas modalidades Tipo I, Tipo II-B e Tipo II-C, conforme a descrição das páginas dos conjuntos: \"Informações associadas à apuração das restrições de operação por Constrained-off nas usinas eólicas/fotovoltaicas classificadas nas modalidades Tipo I, Tipo II-B e Tipo II-C\". O percentual de 17,17% é calculado sobre a geração de referência (val_geracaoreferencia) dessas usinas no período de 01/01 a 30/04/2026. O Canal Solar, na reportagem de 18/05/2026, fala em \"geração potencial das usinas afetadas\"; a equivalência entre os dois denominadores não foi verificada.",
  "periodo": "01/01/2026 a 30/04/2026",
  "formato": {
    "codificacao": "UTF-8 sem BOM",
    "separador": ";",
    "aspas": "campos citados entre aspas respeitados, inclusive quebras de linha dentro de aspas; aspas internas escapadas como \"\"",
    "separadorDecimal": "ponto (.)",
    "granularidade": "patamar semi-horário, um registro por usina ou conjunto de usinas por patamar de 0,5 h"
  },
  "arquivos": [
    {
      "arquivoLocal": "RESTRICAO_COFF_EOLICA_2026_01.csv",
      "nomeOriginal": "RESTRICAO_COFF_EOLICA_2026_01.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_eolica_tm/RESTRICAO_COFF_EOLICA_2026_01.csv",
      "bytes": 47825041,
      "sha256": "a88cc7dbe6b04ffd0a2569ae074da2867b41dffe947a7ffbddacc70cef45a960",
      "linhas": 229152,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-01-01 00:00:00",
      "din_instanteMax": "2026-01-31 23:30:00",
      "mwhGnrApurada": 2457543.4,
      "mwhReferencia": 10941487.9,
      "patamaresSemGnrApurada": 126002,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_EOLICA_2026_02.csv",
      "nomeOriginal": "RESTRICAO_COFF_EOLICA_2026_02.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_eolica_tm/RESTRICAO_COFF_EOLICA_2026_02.csv",
      "bytes": 34430370,
      "sha256": "33f7ea541d991a7c57e64e6bd3db0528a4f068e233ddcf846aed140bb554c296",
      "linhas": 205824,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-02-01 00:00:00",
      "din_instanteMax": "2026-02-28 23:30:00",
      "mwhGnrApurada": 504236.4,
      "mwhReferencia": 6052927.7,
      "patamaresSemGnrApurada": 170537,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_EOLICA_2026_03.csv",
      "nomeOriginal": "RESTRICAO_COFF_EOLICA_2026_03.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_eolica_tm/RESTRICAO_COFF_EOLICA_2026_03.csv",
      "bytes": 39735637,
      "sha256": "bc717373e9ff8919920f20d7f91931589312a12800af5db311bb6b085e86451b",
      "linhas": 227664,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-03-01 00:00:00",
      "din_instanteMax": "2026-03-31 23:30:00",
      "mwhGnrApurada": 850093.4,
      "mwhReferencia": 7186083.4,
      "patamaresSemGnrApurada": 159363,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_EOLICA_2026_04.csv",
      "nomeOriginal": "RESTRICAO_COFF_EOLICA_2026_04.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_eolica_tm/RESTRICAO_COFF_EOLICA_2026_04.csv",
      "bytes": 39428467,
      "sha256": "9ff1ab834bf2e0ba7ba617ba497560196600a3ee37bcb72ec74936475ae60a1b",
      "linhas": 220320,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-04-01 00:00:00",
      "din_instanteMax": "2026-04-30 23:30:00",
      "mwhGnrApurada": 1421000.6,
      "mwhReferencia": 8941441.5,
      "patamaresSemGnrApurada": 137617,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_01.csv",
      "nomeOriginal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_01.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_fotovoltaica_tm/RESTRICAO_COFF_FOTOVOLTAICA_2026_01.csv",
      "bytes": 19392832,
      "sha256": "63570e3e7c0a58c2bd6728790f5e8ebfba8f9684f66b2bcdddd6226aea6d862e",
      "linhas": 106464,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-01-01 00:00:00",
      "din_instanteMax": "2026-01-31 23:30:00",
      "mwhGnrApurada": 746625.2,
      "mwhReferencia": 3693569,
      "patamaresSemGnrApurada": 77372,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_02.csv",
      "nomeOriginal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_02.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_fotovoltaica_tm/RESTRICAO_COFF_FOTOVOLTAICA_2026_02.csv",
      "bytes": 16212939,
      "sha256": "fafd04ccf7c043e7522f97cf20bf0c3ecc0d62abd5b0de8eb888de9302123618",
      "linhas": 98112,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-02-01 00:00:00",
      "din_instanteMax": "2026-02-28 23:30:00",
      "mwhGnrApurada": 345403.3,
      "mwhReferencia": 3227023.2,
      "patamaresSemGnrApurada": 81868,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_03.csv",
      "nomeOriginal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_03.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_fotovoltaica_tm/RESTRICAO_COFF_FOTOVOLTAICA_2026_03.csv",
      "bytes": 19439832,
      "sha256": "a8fbee7af431a0e2ae2de7242ac03bcb647cf40972beea629e139549f5d72314",
      "linhas": 112992,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-03-01 00:00:00",
      "din_instanteMax": "2026-03-31 23:30:00",
      "mwhGnrApurada": 864503.9,
      "mwhReferencia": 3873341.4,
      "patamaresSemGnrApurada": 79948,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    },
    {
      "arquivoLocal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_04.csv",
      "nomeOriginal": "RESTRICAO_COFF_FOTOVOLTAICA_2026_04.csv",
      "urlDownload": "https://ons-aws-prod-opendata.s3.amazonaws.com/dataset/restricao_coff_fotovoltaica_tm/RESTRICAO_COFF_FOTOVOLTAICA_2026_04.csv",
      "bytes": 19146719,
      "sha256": "daa1a5529494a913a92a7b35c551a0ab4bf103e77a7fbdcf2a321668a31689de",
      "linhas": 112320,
      "colunas": 24,
      "linhasComColunasInvalidas": 0,
      "din_instanteMin": "2026-04-01 00:00:00",
      "din_instanteMax": "2026-04-30 23:30:00",
      "mwhGnrApurada": 1043683.5,
      "mwhReferencia": 4040804.9,
      "patamaresSemGnrApurada": 76887,
      "patamaresSemGnrApuradaComCodigoDeRazao": 0
    }
  ],
  "camposUsados": [
    {
      "campo": "din_instante",
      "descricao": "Data/Hora do patamar."
    },
    {
      "campo": "val_geracao",
      "descricao": "Geração verificada, em MW médios. Valor recebido pelo ONS pelo sistema SCADA, podendo ser alterado pelo agente em ambiente de pós-operação (dicionário do ONS)."
    },
    {
      "campo": "val_geracaoreferencia",
      "descricao": "Geração de referência ou estimada, em MW médios: quanto a usina poderia gerar sem limitação, conforme mecanismos estabelecidos na RO-AO.BR.13 (dicionário do ONS)."
    },
    {
      "campo": "val_geracaonaorealizadaapurada",
      "descricao": "Geração Não Realizada Apurada (GNRa), em MW médios: diferença entre a geração de referência e a geração verificada, com piso em zero, nos períodos em que houve limitação de geração (dicionário do ONS)."
    },
    {
      "campo": "cod_razaorestricao",
      "descricao": "Código da razão da restrição: ENE (razão energética), CNF (atendimento a requisitos de confiabilidade), REL (indisponibilidade externa elétrica) e PAR (restrição indicada no parecer de acesso; ausente nas somas de jan–abr/2026)."
    }
  ],
  "regraDeVazios": "Campo val_geracaonaorealizadaapurada vazio = não apurado = não somado (o dicionário define a GNRa só nos períodos em que houve limitação de geração). Em 909.594 patamares o campo está vazio; em nenhum deles o cod_razaorestricao está preenchido. Não houve valores negativos de GNRa.",
  "formulas": {
    "energiaPorPatamarMwh": "val_geracaonaorealizadaapurada (MW médios) × 0,5 h, linha a linha, em todas as usinas dos arquivos.",
    "energiaTotalMwh": "soma da energia de todos os patamares com valor apurado.",
    "mwMedios": "MWh ÷ 2.880 horas (jan–abr/2026 = 120 dias).",
    "percentual": "MWh cortada ÷ (soma de val_geracaoreferencia × 0,5 h em TODAS as 1.312.848 linhas, todas com geração de referência). O dicionário não limita val_geracaoreferencia a patamares restringidos, ao contrário de val_geracaoreferenciafinal, calculada apenas para REL e não usada na soma total.",
    "regraPrincipal": "(a) soma pura da GNRa apurada.",
    "regraSensibilidade": "(b) (a) preenchendo vazios com max(0, referência − verificada) apenas onde há cod_razaorestricao; como esse conjunto é vazio, (b) = (a)."
  },
  "resultados": {
    "total": {
      "mwh": 8233089.7,
      "mwMedios": 2858.7
    },
    "porRazao": {
      "ENE": {
        "mwh": 5135440.9,
        "mwMedios": 1783.1
      },
      "CNF": {
        "mwh": 1876112.4,
        "mwMedios": 651.4
      },
      "REL": {
        "mwh": 1221536.3,
        "mwMedios": 424.1
      },
      "semCodigo": {
        "mwh": 0,
        "mwMedios": 0
      }
    },
    "porFonte": {
      "eolica": {
        "mwh": 5232873.7,
        "mwMedios": 1817
      },
      "fotovoltaica": {
        "mwh": 3000215.9,
        "mwMedios": 1041.7
      }
    },
    "referenciaTotalMwh": 47956679,
    "linhasComReferencia": 1312848,
    "percentualSobreReferencia": 17.17,
    "linhas": {
      "eolica": 882960,
      "fotovoltaica": 429888,
      "total": 1312848
    },
    "patamaresSemValorApurado": 909594,
    "patamaresSemValorApuradoComCodigoDeRazao": 0,
    "gnrApuradaNegativos": 0,
    "sensibilidadeIgualPrincipal": true
  },
  "comparacaoCanalSolar": {
    "reportagem": {
      "titulo": "Brasil já cortou quase 3 GW médios de energia solar e eólica em 2026",
      "veiculo": "Canal Solar",
      "publicadaEm": "18/05/2026",
      "url": "https://canalsolar.com.br/brasil-cortou-tres-gw-medios-energia-solar-eolica/",
      "dataExtracaoDosDados": "não informada na reportagem",
      "totalMwMedios": 2843,
      "percentual": "17,2% da geração potencial das usinas afetadas",
      "porRazaoMwMedios": {
        "ENE": 1772,
        "CNF": 651,
        "REL": 420
      },
      "porFonteMwMedios": {
        "eolica": 1806,
        "fotovoltaica": 1037
      }
    },
    "diferencasConferenciaMenosCanalSolar": {
      "totalMwMedios": 15.7,
      "totalPercentual": 0.6,
      "ENE": 11.1,
      "CNF": 0.4,
      "REL": 4.1,
      "eolica": 11,
      "fotovoltaica": 4.7,
      "percentualConferencia": "17,17%",
      "percentualCanalSolar": "17,2%"
    },
    "nota": "A reportagem foi publicada em 18/05/2026; a data de extração dos dados não é informada. A conferência usou os CSVs baixados em 08/11/2026. A equivalência entre o denominador desta conferência (geração de referência das usinas dos conjuntos no período) e o da reportagem (geração potencial das usinas afetadas) não foi verificada. A comparação acima apenas registra os números; nenhuma causa é atribuída à diferença."
  },
  "limites": [
    "Os dados do ONS fazem parte de processo de consistência recorrente e podem ser atualizados após a publicação, conforme as páginas dos conjuntos; um recálculo futuro pode diferir.",
    "Não foram recalculados 2025, regiões, estados ou usinas; os XLSX e os PARQUET não foram cruzados.",
    "O campo val_geracaoreferenciafinal (calculado apenas para REL) não foi usado na soma total nem no denominador do percentual.",
    "A equivalência com o denominador do Canal Solar não foi verificada (ver comparacaoCanalSolar).",
    "A reprodução EXATA dos números depende de obter arquivos com os mesmos SHA-256 listados em arquivos[]. Se o ONS atualizar os arquivos, o método se repete mas o resultado pode diferir; compare os totais por arquivo (mwhGnrApurada e mwhReferencia) para localizar a divergência."
  ],
  "programaCalculo": {
    "linguagem": "JavaScript (Node.js), sem dependências",
    "instrucaoUso": "node calcula.js <pasta-com-os-8-csv> — a pasta argumentada deve conter os 8 arquivos com os nomes originais do ONS (RESTRICAO_COFF_EOLICA_2026_01.csv a RESTRICAO_COFF_EOLICA_2026_04.csv e RESTRICAO_COFF_FOTOVOLTAICA_2026_01.csv a RESTRICAO_COFF_FOTOVOLTAICA_2026_04.csv), sem renomear; a fonte (eólica/fotovoltaica) e o mês são detectados pelo nome.",
    "codigo": "// Recalculo ONS 01/01-30/04/2026 - eolica + fotovoltaica\n// Uso: node calcula.js <pasta-com-os-8-csv>\n// A pasta argumentada deve conter os 8 CSVs com os NOMES ORIGINAIS do ONS, sem renomear:\n//   RESTRICAO_COFF_EOLICA_2026_01.csv ... RESTRICAO_COFF_EOLICA_2026_04.csv\n//   RESTRICAO_COFF_FOTOVOLTAICA_2026_01.csv ... RESTRICAO_COFF_FOTOVOLTAICA_2026_04.csv\n// A fonte (eolica/fotovoltaica) e o mes sao detectados pelo nome do arquivo (EOLICA/FOTOVOLTAICA).\n// Parser CSV com estado (respeita aspas, inclusive quebras de linha dentro de aspas,\n// separador ';' e \"\" escapado). Deteccao de encoding UTF-8 (com/sem BOM) vs latin1 e decimal.\n// Fonte dos dados: CSVs mensais do Portal de Dados Abertos do ONS.\n// PRINCIPAL (a): soma pura de val_geracaonaorealizadaapurada * 0,5 h; vazio = nao apurado = nao somado.\n// SENSIBILIDADE (b): mesma soma, preenchendo vazios com max(0, ref - verificada) so onde ha cod_razaorestricao.\n// MW medios = MWh / 2880. Percentual = (a) / soma(val_geracaoreferencia * 0,5) em TODAS as linhas com referencia,\n// pois o dicionario nao limita val_geracaoreferencia a patamares restringidos (ao contrario de\n// val_geracaoreferenciafinal, calculada apenas para REL).\nconst fs = require('fs');\nconst path = require('path');\nconst DIR = process.argv[2] || '.';\nconst allCsv = fs.readdirSync(DIR).filter((f) => /\\.csv$/i.test(f));\nfunction srcOf(f) {\n  if (/FOTOVOLTAICA/i.test(f)) return 'fotovoltaica';\n  if (/EOLICA/i.test(f)) return 'eolica';\n  return null;\n}\nfunction monthOf(f) {\n  const m = f.match(/2026[_\\-]?(\\d{2})/i);\n  if (m) return m[1];\n  const m2 = f.match(/(\\d{2})\\.csv$/i);\n  return m2 ? m2[1] : null;\n}\nconst FILES = [];\nfor (const f of allCsv) {\n  const s = srcOf(f);\n  const m = monthOf(f);\n  if (s && m) FILES.push([s, f, m]);\n}\nFILES.sort((a, b) => a[0].localeCompare(b[0]) || a[2].localeCompare(b[2]) || a[1].localeCompare(b[1]));\nif (FILES.length !== 8) {\n  console.error('Esperados 8 CSVs eolica/fotovoltaica jan-abr/2026 com nomes originais do ONS; achados: ' + FILES.map((x) => x[1]).join(', '));\n  process.exit(1);\n}\nconst FILES2 = FILES.map(([s, f]) => [s, f]);\n\n// Parser de estado: le o texto inteiro; registros terminam em \\n fora de aspas;\n// campos terminados em ';' fora de aspas; dentro de aspas, \"\" = aspa literal.\nfunction parseCSVText(text) {\n  const rows = [];\n  let row = [];\n  let field = '';\n  let inQuotes = false;\n  let i = 0;\n  const n = text.length;\n  while (i < n) {\n    const c = text[i];\n    if (inQuotes) {\n      if (c === '\"') {\n        if (i + 1 < n && text[i + 1] === '\"') { field += '\"'; i += 2; continue; }\n        inQuotes = false; i++; continue;\n      }\n      field += c; i++; continue;\n    }\n    if (c === '\"' && (field === '' || field === undefined || (i === 0 || text[i - 1] === ';' || text[i - 1] === '\\n' || text[i - 1] === '\\r'))) {\n      // abre campo citado (aspas no inicio do campo)\n      if (field === '') { inQuotes = true; i++; continue; }\n      field += c; i++; continue;\n    }\n    if (c === ';') { row.push(field); field = ''; i++; continue; }\n    if (c === '\\r') { i++; continue; }\n    if (c === '\\n') { row.push(field); field = ''; rows.push(row); row = []; i++; continue; }\n    field += c; i++;\n  }\n  // ultimo registro sem \\n final\n  if (field !== '' || row.length > 0) { row.push(field); rows.push(row); }\n  return rows;\n}\n\nfunction detectEncoding(buf) {\n  const hasBOM = buf.length >= 3 && buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF;\n  const utf8 = buf.toString('utf8');\n  const latin1 = buf.toString('latin1');\n  const hasUtf8Accent = /ã|ç|é|í|õ|ú|â|ê|ô|á|à/.test(utf8.split('\\n')[1] || '');\n  const lugarUtf8 = utf8.includes('Maranhão') || utf8.includes('Nordeste');\n  const lugarLat1 = latin1.includes('Maranh');\n  // Acento correto so aparece no utf8 (em latin1 o 'ã' vira 'Ã£').\n  if (hasBOM || lugarUtf8) return { enc: 'utf-8' + (hasBOM ? ' (com BOM)' : ' (sem BOM)'), text: utf8.replace(/^﻿/, '') };\n  if (lugarLat1 && !lugarUtf8) return { enc: 'latin1', text: latin1 };\n  return { enc: 'utf-8 (sem BOM)', text: utf8 };\n}\n\nfunction detectDecimal(rows, header) {\n  const ri = header.indexOf('val_geracaoreferencia');\n  let dot = 0, comma = 0;\n  for (let r = 1; r < Math.min(rows.length, 3000); r++) {\n    const v = (rows[r][ri] || '').trim();\n    if (!v) continue;\n    if (/^-?\\d+\\.\\d+$/.test(v)) dot++;\n    if (/^-?\\d+,\\d+$/.test(v)) comma++;\n    if (dot > 5) break;\n  }\n  return dot > comma ? 'ponto' : (comma > 0 ? 'vírgula' : 'ponto (sem marca decimal observada)');\n}\n\nconst num = (s) => {\n  if (s === undefined || s === null) return null;\n  s = String(s).trim();\n  if (s === '') return null;\n  const v = parseFloat(s.replace(',', '.'));\n  return isNaN(v) ? null : v;\n};\nconst fmts = (x) => x.toFixed(1).replace('.', ',');\n\nconst results = {};\nlet totalA = 0, totalB = 0, totalRef = 0, totalRefRows = 0;\nconst byReasonA = {}, byReasonB = {}, bySourceA = {}, bySourceB = {};\n\nfor (const [src, file] of FILES2) {\n  const buf = fs.readFileSync(path.join(DIR, file));\n  const { enc, text } = detectEncoding(buf);\n  const rows = parseCSVText(text);\n  const header = rows[0].map(h => h.trim());\n  const expected = header.length;\n  const idx = {};\n  header.forEach((h, i) => idx[h] = i);\n  const dec = detectDecimal(rows, header);\n  let invalid = 0;\n  const invalidEx = [];\n  const data = rows.slice(1).filter(r => r.length > 1 && !(r.length === 1 && r[0].trim() === ''));\n  let emptyGnra = 0, emptyGnraWithReason = 0, negativeGnra = 0;\n  let minDate = null, maxDate = null;\n  let sumA = 0, sumB = 0, sumRef = 0, refRows = 0;\n  for (const r of data) {\n    if (r.length !== expected) { invalid++; if (invalidEx.length < 2) invalidEx.push(r.slice(0, 8).join('|')); continue; }\n    const get = (k) => (r[idx[k]] || '').trim();\n    const dt = get('din_instante');\n    if (dt) { if (!minDate || dt < minDate) minDate = dt; if (!maxDate || dt > maxDate) maxDate = dt; }\n    const ref = num(get('val_geracaoreferencia'));\n    const ver = num(get('val_geracao'));\n    if (ref !== null) { sumRef += ref; refRows++; }\n    const g = num(get('val_geracaonaorealizadaapurada'));\n    const reason = get('cod_razaorestricao');\n    let a = null, b = null;\n    if (g === null) {\n      emptyGnra++;\n      if (reason !== '') emptyGnraWithReason++;\n    } else {\n      if (g < 0) { negativeGnra++; }\n      a = Math.max(0, g); // GNRa com piso em zero (dicionario); negativos registrados, nenhum ocorrencia esperada\n      b = a;\n    }\n    if (g === null && reason !== '' && ref !== null && ver !== null) {\n      b = Math.max(0, ref - ver);\n    }\n    if (a !== null) {\n      sumA += a * 0.5;\n      byReasonA[reason || '(sem código)'] = (byReasonA[reason || '(sem código)'] || 0) + a * 0.5;\n      bySourceA[src] = (bySourceA[src] || 0) + a * 0.5;\n    }\n    if (b !== null) {\n      sumB += b * 0.5;\n      byReasonB[reason || '(sem código)'] = (byReasonB[reason || '(sem código)'] || 0) + b * 0.5;\n      bySourceB[src] = (bySourceB[src] || 0) + b * 0.5;\n    }\n  }\n  const key = file;\n  results[key] = { src, enc, dec, expected, rows: data.length, invalid, minDate, maxDate, emptyGnra, emptyGnraWithReason, negativeGnra, sumA, sumB, sumRef };\n  totalA += sumA; totalB += sumB; totalRef += sumRef; totalRefRows += refRows;\n  console.log(`=== ${file} (${src}) ===`);\n  console.log(` encoding=${enc} decimal=${dec}`);\n  console.log(` colunas cabecalho=${expected} | linhas=${data.length} | colunas invalidas=${invalid}` + (invalidEx.length ? ` ex: ${invalidEx.join(' ;; ')}` : ''));\n  console.log(` din_instante min=${minDate} max=${maxDate}`);\n  console.log(` GNRa vazio=${emptyGnra} (com cod_razaorestricao=${emptyGnraWithReason}) | GNRa negativos=${negativeGnra}`);\n  console.log(` MWh (a)=${fmts(sumA)} (b)=${fmts(sumB)} | ref MWh=${fmts(sumRef * 0.5)}`);\n}\n\nconst H = 2880;\nconsole.log('\\n======== TOTAL JAN-ABR/2026 (eolica + fotovoltaica) ========');\nconsole.log(`(a) PRINCIPAL soma pura GNRa: MWh=${fmts(totalA)} MWmed=${fmts(totalA / H)}`);\nconsole.log(`(b) SENSIBILIDADE preenchimento: MWh=${fmts(totalB)} MWmed=${fmts(totalB / H)}`);\nconsole.log(`Ref MWh (TODAS as linhas com referencia; denominador do percentual) = ${fmts(totalRef * 0.5)} em linhas=${totalRefRows}`);\nconsole.log(`Percentual (a) sobre referencia = ${((totalA / (totalRef * 0.5)) * 100).toFixed(2).replace('.', ',')}%`);\nconsole.log('Por razao (a):');\nfor (const [r, v] of Object.entries(byReasonA).sort((x, y) => y[1] - x[1]))\n  console.log(`  ${r}: MWh=${fmts(v)} MWmed=${fmts(v / H)}`);\nconsole.log('Por razao (b):');\nfor (const [r, v] of Object.entries(byReasonB).sort((x, y) => y[1] - x[1]))\n  console.log(`  ${r}: MWh=${fmts(v)} MWmed=${fmts(v / H)}`);\nconsole.log('Por fonte (a):');\nfor (const [s, v] of Object.entries(bySourceA).sort((x, y) => y[1] - x[1]))\n  console.log(`  ${s}: MWh=${fmts(v)} MWmed=${fmts(v / H)}`);\nconsole.log('Por fonte (b):');\nfor (const [s, v] of Object.entries(bySourceB).sort((x, y) => y[1] - x[1]))\n  console.log(`  ${s}: MWh=${fmts(v)} MWmed=${fmts(v / H)}`);\nconsole.log('\\nCanal Solar (18/05/2026): total 2843 MWmed; ENE 1772 / CNF 651 / REL 420; eolica 1806 / solar 1037; 17,2%');\nconsole.log(`Dif (a)-Canal: total ${((totalA / H) - 2843).toFixed(1)} MWmed`);\nconsole.log(`Dif (b)-Canal: total ${((totalB / H) - 2843).toFixed(1)} MWmed`);\n"
  },
  "comoUsar": {
    "versaoNodeUsada": "v24.15.0",
    "passos": [
      "1. Instale Node.js; confira com `node --version` (versão usada nesta conferência: v24.15.0).",
      "2. Salve este extrato como extrato.json na pasta de trabalho.",
      "3. Extraia o programa: node -e \"require('fs').writeFileSync('calcula.js', require('./extrato.json').programaCalculo.codigo)\"",
      "4. Baixe os 8 CSVs pelas URLs de arquivos[].urlDownload, mantendo os nomes originais do ONS (nomeOriginal), numa pasta nova.",
      "5. Confira o SHA-256 de cada um: no Windows, certutil -hashfile <arquivo> SHA256; no Linux/macOS, sha256sum <arquivo>; compare com arquivos[].sha256.",
      "6. Rode: node calcula.js <pasta-com-os-8-csv> (a pasta deve conter os 8 CSVs com os nomes originais, sem renomear).",
      "7. Compare a saída com \"resultados\" e com os totais por arquivo em arquivos[]."
    ]
  }
}
