diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:38:27 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:38:27 -0300 |
| commit | 399eac351b1bc29d5047dbac96486c281be1b05c (patch) | |
| tree | 81151d5bf139f07096a8f38313d64482250163c2 /R/models.R | |
| parent | 11d49bc25eb1643f1b742f9527212d8781f2e0de (diff) | |
| download | r-399eac351b1bc29d5047dbac96486c281be1b05c.tar.gz | |
A CEP that fails a lookup is rarely a random number: it is usually someone
who knows the city and guessed, and the classic guess is the -000 that 957
municipalities never received. The API now answers those 404s with a faixa
naming the municipality that owns the band the number fell into, which is
the difference between telling a user "invalid CEP" and "no such CEP in
Areal/RJ".
It is context for an error and never an address, so it rides on the
cepis_not_found condition instead of turning the failure into a success,
and cep_ini/cep_fim are the endpoints that exist rather than the range the
Correios publish - the interval between them is sparse, which is what ceps
counts. Parsing is deliberately unable to signal: a faixa that is null, an
array, a string or half a body leaves a clean not-found behind, because the
caller asked for the CEP, not for the decoration on its absence.
Diffstat (limited to 'R/models.R')
| -rw-r--r-- | R/models.R | 102 |
1 files changed, 96 insertions, 6 deletions
@@ -62,6 +62,53 @@ NULL #' @name cepis_geo NULL +#' The municipality a CEP that does not exist would have belonged to +#' +#' @description +#' The `faixa` field of a [cepis_not_found][cepis_conditions] condition, a +#' plain list of class `cepis_faixa`. It is **context for an error, not an +#' address**: the CEP asked for does not exist, and nothing here says +#' otherwise. What it does say is which municipality owns the band of numbers +#' the CEP fell into. +#' +#' It exists because a CEP that fails is rarely random — it is usually +#' someone who knows the city and guessed the number, and the classic guess is +#' the `-000` that 957 municipalities never received. +#' +#' @section Fields: +#' \describe{ +#' \item{`localidade`, `uf`, `estado`, `regiao`}{municipality, state code, +#' state name, region — the same names, and the same meaning, as in a +#' [cepis_address].} +#' \item{`ibge`}{the municipality's IBGE code, so the answer can be joined +#' to other data without matching on the name.} +#' \item{`cep_ini`, `cep_fim`}{the smallest and the largest CEP that +#' **actually exists** in that municipality. Not the range the Correios +#' publish: the DNE's own start for Areal/RJ is `25845000`, which is +#' precisely the CEP that just failed.} +#' \item{`ceps`}{how many CEPs the municipality really has.} +#' } +#' +#' @section The range is sparse: +#' Areal's 118 CEPs are scattered over the ~4900 numbers between `25845001` +#' and `25849899`. `cep_ini` and `cep_fim` are two endpoints that exist, not a +#' promise about anything in between — never present the pair as a list of +#' valid CEPs, and never iterate from one to the other. `ceps` is there so the +#' emptiness of the interval is visible: 118 out of 4899. +#' +#' @section When there is no faixa: +#' `NULL`, and not a list of empty strings, when the CEP falls in a band no +#' municipality owns. The API **omits** the field rather than sending null, +#' and this package keeps that distinction intact: `is.null(x$faixa)` means +#' the number is close to nothing, not that the fields came back blank. +#' +#' Only [cep_lookup()] reports it. In [cep_lookup_many()] a CEP that does not +#' exist is still `NULL` at its position: the endpoint behind a batch is CSV +#' and says nothing about ranges. +#' +#' @name cepis_faixa +NULL + #' The answer of `GET /health` #' #' @description @@ -96,6 +143,15 @@ text_field <- function(data, name) { as.character(value) } +count_field <- function(data, name) { + value <- data[[name]] + if (!is.numeric(value) && !is.character(value)) { + return(0L) + } + value <- suppressWarnings(as.numeric(value)) + if (length(value) != 1L || is.na(value)) 0L else as.integer(value) +} + new_geo <- function(block) { lat <- suppressWarnings(as.numeric(block[["lat"]])) lon <- suppressWarnings(as.numeric(block[["lon"]])) @@ -121,6 +177,35 @@ new_geo <- function(block) { ) } +# Never signals. A faixa is decoration on a 404, and a malformed one must not +# turn a clean "this CEP does not exist" into a parse failure: anything that +# is not an object naming a municipality is simply no faixa at all. +new_faixa <- function(block) { + if (!is.list(block) || is.null(names(block))) { + return(NULL) + } + + localidade <- text_field(block, "localidade") + uf <- text_field(block, "uf") + if (!nzchar(localidade) || !nzchar(uf)) { + return(NULL) + } + + structure( + list( + localidade = localidade, + uf = uf, + estado = text_field(block, "estado"), + regiao = text_field(block, "regiao"), + ibge = text_field(block, "ibge"), + cep_ini = text_field(block, "cep_ini"), + cep_fim = text_field(block, "cep_fim"), + ceps = count_field(block, "ceps") + ), + class = "cepis_faixa" + ) +} + new_address <- function(data) { geo <- NULL block <- data[["geo"]] @@ -135,10 +220,6 @@ new_address <- function(data) { } new_health <- function(data) { - count <- function(name) { - value <- suppressWarnings(as.numeric(data[[name]])) - if (length(value) != 1L || is.na(value)) 0L else as.integer(value) - } status <- text_field(data, "status") structure( @@ -146,8 +227,8 @@ new_health <- function(data) { status = status, ok = identical(status, "ok"), dne = text_field(data, "dne"), - ceps = count("ceps"), - geo = count("geo"), + ceps = count_field(data, "ceps"), + geo = count_field(data, "geo"), base_gerada_em = text_field(data, "base_gerada_em") ), class = "cepis_health" @@ -204,6 +285,15 @@ print.cepis_geo <- function(x, ...) { } #' @export +print.cepis_faixa <- function(x, ...) { + cat("<cepis_faixa> ", x$localidade, "/", x$uf, " (IBGE ", x$ibge, ")\n", + sep = "") + cat(" ", x$ceps, " CEP(s), the first ", formatted_cep(x$cep_ini), + " and the last ", formatted_cep(x$cep_fim), "\n", sep = "") + invisible(x) +} + +#' @export print.cepis_health <- function(x, ...) { cat("<cepis_health> ", x$status, "\n", sep = "") cat(" dne: ", x$dne, " (generated ", x$base_gerada_em, ")\n", sep = "") |