# Conferência dos números de comércio exterior do Mapa de CT&I da SECTI-RJ
# Página: https://mapa.fanara.com.br/diagnostico#comercio-exterior
# Tabela conferida: https://mapa.fanara.com.br/dados/comex-rj-2023-2025.csv
#
# O script parte dos arquivos oficiais do MDIC (base de dados bruta do Comex Stat, por município),
# refaz as somas do mapa e compara com a tabela publicada, coluna por coluna. Usa só o R básico,
# sem pacotes. Baixa seis arquivos de 55 a 90 MB e leva alguns minutos.
#
# Uso:
#   Rscript comex-rj-2023-2025.R            baixa os arquivos do MDIC numa pasta temporária
#   Rscript comex-rj-2023-2025.R <pasta>    usa os arquivos EXP_AAAA_MUN.csv e IMP_AAAA_MUN.csv
#                                           que já estiverem nessa pasta e baixa os que faltarem
# Para conferir outra cópia da tabela: COMEX_TABELA=<caminho ou URL> Rscript comex-rj-2023-2025.R
#
# Fonte: MDIC/Secex, https://www.gov.br/mdic/pt-br/assuntos/comercio-exterior/estatisticas/base-de-dados-bruta
# Critério territorial: município do domicílio fiscal da empresa declarante (campo CO_MUN), como em
# todo o módulo "Dados por Municípios" do Comex Stat. O módulo "Dados Gerais" atribui a exportação ao
# estado produtor e não deve ser comparado com este (FAQ do MDIC, pergunta 12).
#
# Colunas da tabela (uma linha por ano, fluxo e território; o estado tem código 33):
#   ano, fluxo (EXP exportação, IMP importação), codigo_ibge, municipio (nome da malha do IBGE)
#   valor_fob_usd                         soma de VL_FOB, dólares correntes (sem correção de inflação)
#   valor_fob_sem_petroleo_usd            idem, sem as posições SH4 2709 (óleo bruto) e 2710 (derivados)
#   peso_liquido_kg                       soma de KG_LIQUIDO
#   peso_liquido_sem_petroleo_kg          idem, sem 2709 e 2710
#   valor_alta_media_alta_tecnologia_usd  parte do valor em alta e média-alta intensidade tecnológica (OCDE).
#                                         NÃO é refeita aqui: depende da tabela NCM.csv do MDIC e do valor
#                                         nacional por NCM na API do Comex Stat (método na página).
#   valor_instrumentos_laboratorio_usd    soma de VL_FOB nas posições SH4 9011, 9012, 9024, 9027, 9030,
#                                         9031 e 3822
#   maior_linha_*                         a linha do arquivo de maior VL_FOB no ano (um SH4, um país, um mês);
#                                         no empate, a que vem primeiro no arquivo. Vazia sem operação.
#   maior_linha_sem_petroleo_*            idem, fora de 2709 e 2710
#
# Arquivos do MDIC revisados depois da data de extração da tabela podem dar diferença pequena em
# meses passados. Se aparecer, confira a data em "extraído em" na página.
#
# Ao final, o script consulta na API do Comex Stat até que mês vão os dados (general/dates/updated) e diz se
# a tabela chega ao último ano completo. A última linha resume a conferência para o robô que a roda todo mês:
#   #conferencia valores=<comparados> divergentes=<diferentes> tabela=<último ano da tabela> fonte=<último ano completo>
# Saída do Rscript: 0 quando todos os valores batem, 1 quando algum não bate, 2 quando não foi possível conferir
# (download que falhou, arquivo de outro ano, tabela ilegível).

# Códigos de saída só no Rscript; dentro do RStudio (source), o script mostra o resultado e não fecha a sessão.
if (!interactive()) options(error = function() quit(save = "no", status = 2))
# Sessão sem UTF-8 (locale C, comum em servidor) corta a leitura no primeiro acento e daria divergência falsa.
if (is.na(iconv("é", "UTF-8", ""))) stop("rode numa sessão UTF-8, por exemplo: LANG=C.UTF-8 Rscript comex-rj-2023-2025.R")
args <- commandArgs(trailingOnly = TRUE)
pasta <- if (length(args) >= 1) args[1] else tempdir()
tabela <- Sys.getenv("COMEX_TABELA")
if (tabela == "") tabela <- "https://mapa.fanara.com.br/dados/comex-rj-2023-2025.csv"
base <- "https://balanca.mdic.gov.br/balanca/bd/comexstat-bd/mun/"
anos <- 2023:2025
fluxos <- c("EXP", "IMP")
petroleo <- c("2709", "2710")
laboratorio <- c("9011", "9012", "9024", "9027", "9030", "9031", "3822")
options(timeout = 3600)

# O MDIC omite o certificado intermediário. O workflow fornece um bundle validado e com fingerprint fixo.
baixar_http <- function(url, destino) {
  curl <- Sys.which("curl")
  if (!nzchar(curl)) return(tryCatch(download.file(url, destino, mode = "wb", quiet = TRUE) == 0,
                                     error = function(e) FALSE))
  cacert <- Sys.getenv("CURL_CA_BUNDLE")
  ca_args <- if (nzchar(cacert)) c("--cacert", shQuote(cacert)) else character()
  codigo <- tryCatch(system2(curl, c("--fail", "--location", "--silent", "--show-error", ca_args,
    "--retry", "3", "--connect-timeout", "30", "--max-time", as.character(getOption("timeout")),
    "--output", shQuote(destino), shQuote(url)), stdout = FALSE, stderr = ""), error = function(e) 1L)
  if (!identical(as.integer(codigo), 0L)) message("curl falhou (código ", codigo, ") em ", url)
  identical(as.integer(codigo), 0L) && file.exists(destino) && file.info(destino)$size > 0
}

# Baixa com outro nome e só renomeia no fim: download interrompido não deixa arquivo pela metade na pasta.
baixar <- function(nome) {
  destino <- file.path(pasta, nome)
  if (file.exists(destino)) return(destino)
  parcial <- paste0(destino, ".part")
  for (tentativa in 1:3) {
    ok <- baixar_http(paste0(base, nome), parcial)
    if (ok && file.rename(parcial, destino)) return(destino)
    unlink(parcial)
    message("download de ", nome, " falhou (tentativa ", tentativa, ")")
  }
  stop("não foi possível baixar ", nome, " do MDIC")
}

# Tudo como texto: o read.csv2 só tira as aspas de colunas lidas como texto, e os códigos SH4 e de
# país têm zeros à esquerda ("063") que a leitura numérica perderia. Os números vêm depois.
ler_rj <- function(arquivo) {
  d <- read.csv2(arquivo, colClasses = "character")
  d <- d[d$SG_UF_MUN == "RJ", ]
  d$CO_ANO <- as.integer(d$CO_ANO)
  d$CO_MES <- as.integer(d$CO_MES)
  d$KG_LIQUIDO <- as.numeric(d$KG_LIQUIDO)
  d$VL_FOB <- as.numeric(d$VL_FOB)
  d
}

# A maior linha de cada território: ordena por território, valor decrescente e posição no arquivo.
maior_linha <- function(d, prefixo) {
  if (nrow(d) == 0) return(data.frame(codigo_ibge = character()))
  o <- order(d$codigo_ibge, -d$VL_FOB, d$posicao)
  m <- d[o, ][!duplicated(d$codigo_ibge[o]), ]
  r <- data.frame(m$codigo_ibge, m$SH4, m$CO_PAIS, m$CO_MES, m$VL_FOB)
  names(r) <- c("codigo_ibge", paste0(prefixo, c("_sh4", "_pais_codigo", "_mes", "_valor_usd")))
  r
}

refazer <- function(ano, fluxo) {
  nome <- sprintf("%s_%d_MUN.csv", fluxo, ano)
  d <- ler_rj(baixar(nome))
  if (any(d$CO_ANO != ano)) stop(nome, ": linha de outro ano")
  # As mesmas travas do gerador do mapa: ano com os 12 meses e nenhuma linha cortada.
  if (!all(1:12 %in% d$CO_MES) || anyNA(d$VL_FOB) || anyNA(d$KG_LIQUIDO)) {
    stop(nome, ": arquivo incompleto (falta mês ou há linha cortada); apague-o da pasta e rode de novo")
  }
  d$posicao <- seq_len(nrow(d))
  # Cada linha conta no município e no estado (código 33).
  d <- rbind(transform(d, codigo_ibge = CO_MUN), transform(d, codigo_ibge = "33"))
  sem <- !(d$SH4 %in% petroleo)
  lab <- d$SH4 %in% laboratorio
  soma <- function(x) tapply(x, d$codigo_ibge, sum)
  r <- data.frame(
    codigo_ibge = names(soma(d$VL_FOB)),
    valor_fob_usd = as.numeric(soma(d$VL_FOB)),
    valor_fob_sem_petroleo_usd = as.numeric(soma(d$VL_FOB * sem)),
    peso_liquido_kg = as.numeric(soma(d$KG_LIQUIDO)),
    peso_liquido_sem_petroleo_kg = as.numeric(soma(d$KG_LIQUIDO * sem)),
    valor_instrumentos_laboratorio_usd = as.numeric(soma(d$VL_FOB * lab))
  )
  r <- merge(r, maior_linha(d, "maior_linha"), all.x = TRUE)
  r <- merge(r, maior_linha(d[sem, ], "maior_linha_sem_petroleo"), all.x = TRUE)
  message(nome, ": ", format(sum(d$codigo_ibge != "33")), " linhas do RJ")
  cbind(ano = ano, fluxo = fluxo, r)
}

refeito <- do.call(rbind, lapply(anos, function(a) do.call(rbind, lapply(fluxos, function(f) refazer(a, f)))))

if (grepl("^https?://", tabela)) {
  local <- file.path(tempdir(), "comex-rj-2023-2025.csv")
  download.file(tabela, local, mode = "wb", quiet = TRUE)
  tabela <- local
}
texto <- c("codigo_ibge", "maior_linha_sh4", "maior_linha_pais_codigo",
           "maior_linha_sem_petroleo_sh4", "maior_linha_sem_petroleo_pais_codigo")
publicado <- read.csv2(tabela, fileEncoding = "UTF-8-BOM", colClasses = setNames(rep("character", length(texto)), texto))
if (nrow(publicado) == 0 || !all(c("ano", "fluxo", "codigo_ibge", "valor_fob_usd") %in% names(publicado))) {
  stop("tabela vazia ou sem as colunas esperadas")
}

# Junta com todos os territórios dos dois lados: um que só tenha operação nos arquivos do MDIC, e falte na
# tabela, não bate.
conferir <- merge(publicado, refeito, by = c("ano", "fluxo", "codigo_ibge"), all = TRUE, suffixes = c("", ".refeito"))
somas <- c("valor_fob_usd", "valor_fob_sem_petroleo_usd", "peso_liquido_kg", "peso_liquido_sem_petroleo_kg",
           "valor_instrumentos_laboratorio_usd")
# Território sem nenhuma linha no arquivo: as somas valem zero e a maior linha fica vazia.
for (col in somas) {
  r <- paste0(col, ".refeito")
  conferir[[r]][is.na(conferir[[r]])] <- 0
}
colunas <- c(somas, paste0(rep(c("maior_linha", "maior_linha_sem_petroleo"), each = 4),
                           c("_sh4", "_pais_codigo", "_mes", "_valor_usd")))
normal <- function(x) { x <- as.character(x); x[is.na(x)] <- ""; x }

total <- 0
erros <- 0
for (col in colunas) {
  a <- normal(conferir[[col]])
  b <- normal(conferir[[paste0(col, ".refeito")]])
  if (col %in% somas || grepl("_mes$|_valor_usd$", col)) {
    a <- ifelse(a == "", "", format(as.numeric(a), scientific = FALSE, trim = TRUE))
    b <- ifelse(b == "", "", format(as.numeric(b), scientific = FALSE, trim = TRUE))
  }
  diferentes <- which(a != b)
  total <- total + length(a)
  erros <- erros + length(diferentes)
  cat(sprintf("%-40s %4d de %4d batem\n", col, length(a) - length(diferentes), length(a)))
  for (i in head(diferentes, 5)) {
    cat(sprintf("    %s %s %s: publicado %s, refeito %s\n", conferir$ano[i], conferir$fluxo[i],
                conferir$codigo_ibge[i], a[i], b[i]))
  }
}

estado <- conferir[conferir$codigo_ibge == "33" & conferir$fluxo == "EXP" & conferir$ano == 2025, ]
cat(sprintf("\nExportação de 2025, empresas com domicílio fiscal no RJ: US$ %s\n",
            format(estado$valor_fob_usd.refeito, big.mark = ".", decimal.mark = ",", scientific = FALSE)))

# Último ano completo no Comex Stat. O R básico não lê JSON; a resposta é curta e as duas chaves saem por
# expressão regular. Se a consulta falhar, "NA": não impede a conferência.
ano_completo <- function() {
  op <- options(timeout = 30)  # consulta curta: não pode segurar a conferência por uma hora
  on.exit(options(op))
  tryCatch({
    json <- paste(readLines("https://api-comexstat.mdic.gov.br/general/dates/updated", warn = FALSE), collapse = "")
    ano <- as.integer(sub('.*"year":"?([0-9]{4}).*', "\\1", json))
    mes <- as.integer(sub('.*"monthNumber":"?([0-9]{1,2})".*', "\\1", json))
    as.character(if (mes == 12) ano else ano - 1)
  }, error = function(e) "NA", warning = function(w) "NA")
}
completo <- ano_completo()
ultimo <- as.character(max(as.integer(publicado$ano)))
cat(if (completo == "NA") "Não foi possível consultar na API do Comex Stat até quando vão os dados.\n"
    else if (completo == ultimo) sprintf("A tabela chega ao último ano completo na fonte (%s).\n", ultimo)
    else sprintf("Último ano completo na fonte: %s; a tabela vai até %s.\n", completo, ultimo))
if (erros == 0) {
  cat(sprintf("Conferência: todos os %d valores batem.\n", total))
} else {
  cat(sprintf("Conferência: %d de %d valores não batem.\n", erros, total))
}
cat(sprintf("#conferencia valores=%d divergentes=%d tabela=%s fonte=%s\n", total, erros, ultimo, completo))
if (!interactive()) quit(save = "no", status = if (erros == 0) 0 else 1)
