summaryrefslogtreecommitdiffstats
path: root/src/models.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/models.lisp')
-rw-r--r--src/models.lisp54
1 files changed, 54 insertions, 0 deletions
diff --git a/src/models.lisp b/src/models.lisp
index 8905ae1..9ca0eaf 100644
--- a/src/models.lisp
+++ b/src/models.lisp
@@ -30,6 +30,36 @@ Shorthand for (string= (geo-precisao geo) \"logradouro\"), which is the check
to make before using the point to measure distance."
(string= (geo-precisao geo) "logradouro"))
+(defstruct (faixa (:copier nil))
+ "The municipality a CEP that does not exist would have belonged to.
+
+Context for an error, never an address. It arrives on the 404 of a CEP the DNE
+does not have, and names the municipality that owns the band the CEP falls in.
+A CEP that fails is rarely random: it is usually someone who knows the city and
+guessed the number — the classic guess being the `-000' that 957 municipalities
+never received.
+
+`faixa-cep-ini' and `faixa-cep-fim' are the smallest and the largest CEP that
+*actually exist* in that municipality, not the bounds the Correios publish. For
+Areal/RJ the DNE's own start is 25845000 — exactly the CEP that just failed —
+while `faixa-cep-ini' is 25845001.
+
+They are not the ends of a list, either: the range is sparse. Areal's 118 CEPs
+are scattered over the ~4,900 numbers between its two ends, which is what
+`faixa-ceps' counts. Offer the municipality to whoever typed the CEP; never
+present the pair as a range of valid CEPs.
+
+Both ends are strings, like every CEP in this library, because a leading zero
+is part of the number."
+ (localidade "" :type string :read-only t)
+ (uf "" :type string :read-only t)
+ (estado "" :type string :read-only t)
+ (regiao "" :type string :read-only t)
+ (ibge "" :type string :read-only t)
+ (cep-ini "" :type string :read-only t)
+ (cep-fim "" :type string :read-only t)
+ (ceps 0 :type integer :read-only t))
+
(defstruct (address (:copier nil))
"A CEP and everything the database knows about it.
@@ -117,6 +147,30 @@ useful."
:raio-m (round raio-m)
:precisao precisao)))))
+(defun faixa-from-json (value)
+ "Build a `faixa' from the parsed \"faixa\" block, or NIL when it is absent or
+malformed.
+
+The API omits the field entirely — it never sends null — when the CEP falls in
+a band no municipality owns, so an absent block is the ordinary case and not an
+anomaly.
+
+Anything that is not a block naming a municipality also yields NIL. That is the
+whole point of parsing it here and not at the call site: the response is a
+definitive not-found, and a field that only adds context must never be able to
+turn it into a parse failure."
+ (when (hash-table-p value)
+ (let ((localidade (json-string value "localidade")))
+ (when (plusp (length localidade))
+ (make-faixa :localidade localidade
+ :uf (json-string value "uf")
+ :estado (json-string value "estado")
+ :regiao (json-string value "regiao")
+ :ibge (json-string value "ibge")
+ :cep-ini (json-string value "cep_ini")
+ :cep-fim (json-string value "cep_fim")
+ :ceps (json-integer value "ceps"))))))
+
(defun address-from-json (data)
"Build an `address' from the hash table the JSON parser produced.