# Conferência da população dos municípios do Mapa de CT&I da SECTI-RJ
# Página: https://mapa.fanara.com.br/diagnostico (denominador de todas as taxas por habitante)
# Tabela conferida: https://mapa.fanara.com.br/dados/populacao-rj.csv
#
# O script parte da API do SIDRA, do IBGE, e compara a população de cada município e do estado com a
# tabela publicada. Usa só o R básico, sem pacotes, e termina em segundos.
#
# Uso:
#   Rscript populacao-rj.R            consulta o SIDRA e guarda a resposta numa pasta temporária
#   Rscript populacao-rj.R <pasta>    usa a resposta já guardada nessa pasta (sidra-6579-AAAA.csv),
#                                     ou consulta e guarda lá
# Para conferir outra cópia da tabela: POPULACAO_TABELA=<caminho ou URL> Rscript populacao-rj.R
#
# Fonte: IBGE, Estimativas de População, população residente estimada em 1º de julho do ano;
# tabela 6579 do SIDRA, variável 9324 (https://sidra.ibge.gov.br/tabela/6579). O ano consultado é o da
# coluna ano da tabela.
#
# Colunas da tabela (uma linha por território; o estado vem primeiro, com código 33):
#   ano           ano da estimativa
#   codigo_ibge   código do município no IBGE, com 7 dígitos (33 para o estado)
#   municipio     nome do município
#   regiao        Região de Governo da CEPERJ. NÃO é conferida aqui: vem de outra fonte e está na tabela
#                 para quem quiser somar por região. Vazia na linha do estado.
#   populacao     pessoas
#
# Confere a população de cada um dos 92 municípios e a do estado, contra o SIDRA (93 valores). Um município
# que falte na tabela, ou que sobre nela, conta como divergência. A população do estado na tabela é a soma
# dos 92 municípios, e o script verifica também que o SIDRA dá o mesmo total para o estado.
#
# Ao final, consulta o último ano disponível no SIDRA para a tabela 6579 e diz se a tabela do mapa usa a
# edição mais recente. A última linha resume a conferência para o robô que a roda todo mês:
#   #conferencia valores=<comparados> divergentes=<diferentes> tabela=<ano da tabela> fonte=<ano mais novo no SIDRA>
# Saída do Rscript: 0 quando todos os valores batem, 1 quando algum não bate, 2 quando não foi possível conferir
# (SIDRA fora do ar, tabela ilegível).

options(timeout = 3600)
# 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 populacao-rj.R")
args <- commandArgs(trailingOnly = TRUE)
pasta <- if (length(args) >= 1) args[1] else tempdir()
tabela <- Sys.getenv("POPULACAO_TABELA")
if (tabela == "") tabela <- "https://mapa.fanara.com.br/dados/populacao-rj.csv"
sidra <- "https://apisidra.ibge.gov.br/values/t/6579/n3/33/n6/in%20n3%2033/v/9324/p/"

# Baixa com outro nome e só renomeia no fim: download interrompido não deixa arquivo pela metade na pasta.
baixar <- function(url, destino) {
  if (file.exists(destino)) return(destino)
  parcial <- paste0(destino, ".part")
  for (tentativa in 1:3) {
    ok <- tryCatch(download.file(url, parcial, mode = "wb", quiet = TRUE) == 0, error = function(e) FALSE)
    if (ok && file.rename(parcial, destino)) return(destino)
    unlink(parcial)
    message("download de ", url, " falhou (tentativa ", tentativa, ")")
  }
  stop("não foi possível baixar ", url)
}

# A resposta do SIDRA tem duas linhas de cabeçalho: os códigos das colunas (NC, D1C, V...) e os nomes.
# NC é o nível territorial (3 estado, 6 município), D1C o código do território e V o valor.
ler_sidra <- function(arquivo) {
  nomes <- scan(arquivo, what = "", sep = ";", nlines = 1, quiet = TRUE, fileEncoding = "UTF-8")
  d <- read.csv2(arquivo, skip = 2, header = FALSE, col.names = nomes, colClasses = "character",
                 fileEncoding = "UTF-8")
  if (!all(c("NC", "D1C", "D3C", "V") %in% names(d))) stop(arquivo, ": resposta do SIDRA fora do formato")
  d
}

# Último ano da tabela 6579 no SIDRA. Se a consulta falhar, "NA": não impede a conferência.
edicao_fonte <- function() {
  op <- options(timeout = 30)  # consulta curta: não pode segurar a conferência por uma hora
  on.exit(options(op))
  tryCatch({
    arquivo <- tempfile(fileext = ".csv")
    download.file("https://apisidra.ibge.gov.br/values/t/6579/n3/33/v/9324/p/last%201?formato=csv",
                  arquivo, mode = "wb", quiet = TRUE)
    ano <- ler_sidra(arquivo)$D3C[1]
    if (grepl("^[0-9]{4}$", ano)) ano else "NA"
  }, error = function(e) "NA", warning = function(w) "NA")
}

if (grepl("^https?://", tabela)) {
  local <- file.path(tempdir(), "populacao-rj.csv")
  download.file(tabela, local, mode = "wb", quiet = TRUE)
  tabela <- local
}
publicado <- read.csv2(tabela, fileEncoding = "UTF-8-BOM", colClasses = "character")
if (!all(c("ano", "codigo_ibge", "municipio", "populacao") %in% names(publicado))) stop("tabela sem as colunas esperadas")
ano <- unique(publicado$ano)
if (length(ano) != 1 || !grepl("^[0-9]{4}$", ano)) stop("a tabela deve ter um só ano, e tem: ", paste(ano, collapse = ", "))

fonte <- ler_sidra(baixar(paste0(sidra, ano, "?formato=csv"), file.path(pasta, sprintf("sidra-6579-%s.csv", ano))))
if (any(fonte$D3C != ano)) stop("o SIDRA respondeu com outro ano")
estado <- fonte[fonte$NC == "3", ]
municipios <- fonte[fonte$NC == "6", ]
if (nrow(estado) != 1 || nrow(municipios) != 92) stop("o SIDRA respondeu ", nrow(municipios), " municípios e ", nrow(estado), " estado")
if (sum(as.numeric(municipios$V)) != as.numeric(estado$V)) stop("no SIDRA, a soma dos 92 municípios difere do total do estado")
message("SIDRA, tabela 6579, ", ano, ": ", nrow(municipios), " municípios e o estado")

# Junta pelo código, com todos os territórios dos dois lados: o que falta em um deles fica vazio e não bate.
refeito <- data.frame(codigo_ibge = c(estado$D1C, municipios$D1C), populacao = c(estado$V, municipios$V))
conferir <- merge(publicado[c("codigo_ibge", "municipio", "populacao")], refeito, by = "codigo_ibge",
                  all = TRUE, suffixes = c("", ".sidra"))
a <- ifelse(is.na(conferir$populacao), "", format(as.numeric(conferir$populacao), scientific = FALSE, trim = TRUE))
b <- ifelse(is.na(conferir$populacao.sidra), "", format(as.numeric(conferir$populacao.sidra), scientific = FALSE, trim = TRUE))
diferentes <- which(a != b)
total <- nrow(conferir)
erros <- length(diferentes)
cat(sprintf("%-40s %4d de %4d batem\n", "populacao", total - erros, total))
for (i in head(diferentes, 5)) {
  cat(sprintf("    %s %s: publicado %s, SIDRA %s\n", conferir$codigo_ibge[i],
              ifelse(is.na(conferir$municipio[i]), "(fora da tabela)", conferir$municipio[i]),
              ifelse(a[i] == "", "(ausente)", a[i]), ifelse(b[i] == "", "(ausente no SIDRA)", b[i])))
}

cat(sprintf("\nPopulação estimada do estado em %s: %s pessoas\n", ano,
            format(as.numeric(estado$V), big.mark = ".", decimal.mark = ",", scientific = FALSE)))
ultima <- edicao_fonte()
cat(if (ultima == "NA") "Não foi possível consultar a edição mais nova no SIDRA.\n"
    else if (ultima == ano) sprintf("A tabela usa a edição mais recente da fonte (%s).\n", ano)
    else sprintf("Edição mais nova na fonte: %s; a tabela usa %s.\n", ultima, ano))
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, ano, ultima))
if (!interactive()) quit(save = "no", status = if (erros == 0) 0 else 1)
