1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
defmodule Cepis.Faixa do
@moduledoc """
The municipality a CEP that does **not** exist would have belonged to.
It arrives on `Cepis.NotFoundError`, and nowhere else. **It is context for an
error, not an address**: the lookup failed, the CEP is not in the DNE, and
nothing here says otherwise. Using it as a fallback address is the one wrong
use.
It is worth having because a failing CEP is rarely random — it is almost
always someone who knows the city and guessed the number, most often the
`-000`, which 957 municipalities never received. Naming the municipality
turns "does not exist" into "you are in Areal, but not at that number", which
is the difference between a dead end and a form the user can fix.
**Field names are the API's own, in Portuguese**, as everywhere else in this
library: `localidade`, `uf`, `estado`, `regiao`, `ibge`, `cep_ini`,
`cep_fim`, `ceps`.
> #### The range is sparse {: .warning}
>
> `cep_ini` and `cep_fim` are the smallest and the largest CEP that
> **actually exist** in that municipality — not the bounds the Correios
> publish. The DNE's own start for Areal is `25845000`, which is exactly the
> CEP that just failed; echoing it back would be useless. The numbers between
> the two bounds are mostly not CEPs at all: Areal's 118 are scattered across
> thousands of them. That is what `ceps` is for, and what `density/1`
> measures. Never expand the pair into a list of valid CEPs.
The API **omits** `faixa`, never sends it as null, when the CEP falls in a
numeric band no municipality owns. So `nil` is an ordinary answer, not a
failure, and every caller has to handle it.
"""
defstruct localidade: "",
uf: "",
estado: "",
regiao: "",
ibge: "",
cep_ini: "",
cep_fim: "",
ceps: 0
@type t :: %__MODULE__{
localidade: String.t(),
uf: String.t(),
estado: String.t(),
regiao: String.t(),
ibge: String.t(),
cep_ini: String.t(),
cep_fim: String.t(),
ceps: non_neg_integer()
}
@doc """
The share of the numbers between `cep_ini` and `cep_fim` that are real CEPs.
About `0.024` for Areal — 118 CEPs over the 4899 numbers between `25845001`
and `25849899`. It exists so the sparsity is measurable instead of being a
footnote nobody reads before iterating over the interval:
if Cepis.Faixa.density(faixa) < 0.5 do
# guessing another number in the range is not going to work
suggest_search_by_street(faixa.localidade, faixa.uf)
end
Returns `0.0` when the bounds are not two readable numbers.
"""
@spec density(t()) :: float()
def density(%__MODULE__{ceps: ceps, cep_ini: cep_ini, cep_fim: cep_fim}) do
with {first, ""} <- Integer.parse(cep_ini),
{last, ""} <- Integer.parse(cep_fim),
true <- last >= first do
ceps / (last - first + 1)
else
_ -> 0.0
end
end
@doc false
@spec from_map(term()) :: t() | nil
def from_map(%{"localidade" => localidade} = data)
when is_binary(localidade) and localidade != "" do
%__MODULE__{
localidade: localidade,
uf: text(data, "uf"),
estado: text(data, "estado"),
regiao: text(data, "regiao"),
ibge: text(data, "ibge"),
cep_ini: text(data, "cep_ini"),
cep_fim: text(data, "cep_fim"),
ceps: count(data, "ceps")
}
end
def from_map(_), do: nil
defp text(data, key) do
case Map.get(data, key) do
value when is_binary(value) -> value
value when is_number(value) -> to_string(value)
_ -> ""
end
end
defp count(data, key) do
case Map.get(data, key) do
value when is_integer(value) ->
value
value when is_float(value) ->
trunc(value)
value when is_binary(value) ->
case Integer.parse(value) do
{number, _} -> number
:error -> 0
end
_ ->
0
end
end
end
|