summaryrefslogtreecommitdiffstats
path: root/src/cepis-lookup.cob
blob: cc6e41243ffea646e5b9f73bbcaedb8511fa7c04 (plain)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
IDENTIFICATION DIVISION.
PROGRAM-ID. CEPIS-LOOKUP.

*> ---------------------------------------------------------------------
*> Look up one CEP.
*>
*>     CALL "CEPIS-LOOKUP" USING CEPIS-CONFIG CEPIS-CEP-INPUT
*>                               CEPIS-ADDRESS CEPIS-STATUS
*>
*> The CEP may be masked or not. It is validated locally first, so a
*> seven-digit CEP costs no round trip: CEPIS-INVALID-CEP comes back
*> without a socket ever being opened.
*>
*> On CEPIS-OK the address is filled. On anything else CEPIS-ADDRESS is
*> cleared, so a caller that forgets to test the status reads spaces
*> rather than the previous CEP's street.
*>
*> ADDR-GEO-PRESENT says whether the coordinate exists -- about 57% of
*> CEPs have one. When it does, read ADDR-GEO-PRECISAO before measuring
*> any distance with it: "logradouro" means the point is a stretch of
*> street, with ADDR-GEO-RAIO-M around 100-150 m, while "localidade"
*> means the CEP is a whole municipality and the point is its centroid,
*> with a radius of 7 to 40 km. Computing shipping from the second is
*> wrong by tens of kilometres.
*> ---------------------------------------------------------------------

DATA DIVISION.
WORKING-STORAGE SECTION.
COPY "cepis-http.cpy".
COPY "cepis-json.cpy".

01  WS-CEP                          PIC X(8).
01  WS-GEO-FROM                     PIC 9(9).
01  WS-GEO-TO                       PIC 9(9).
01  WS-GEO-OK                       PIC X.
01  WS-NUM-OK                       PIC X.
01  WS-LAT                          PIC S9(3)V9(6).
01  WS-LON                          PIC S9(3)V9(6).
01  WS-RAIO                         PIC 9(9).
01  WS-PRECISAO                     PIC X(16).
01  WS-I                            PIC 9(4).
01  WS-C                            PIC X.
01  WS-DOTS                         PIC 9(4).
01  WS-JUNK                         PIC 9(4).
01  WS-DIGITS                       PIC 9(4).

LINKAGE SECTION.
COPY "cepis.cpy".

PROCEDURE DIVISION USING CEPIS-CONFIG CEPIS-CEP-INPUT
                         CEPIS-ADDRESS CEPIS-STATUS.

MAIN-START.
    PERFORM CLEAR-ADDRESS

    CALL "CEPIS-NORMALIZE-CEP"
        USING CEPIS-CEP-INPUT WS-CEP CEPIS-STATUS
    END-CALL
    IF CEPIS-FAILED
        GOBACK
    END-IF

    MOVE SPACES TO HTTPQ-PATH
    STRING "/" WS-CEP DELIMITED BY SIZE INTO HTTPQ-PATH
    END-STRING
    MOVE 9 TO HTTPQ-PATH-LEN
    MOVE "N" TO HTTPQ-ALLOW-503

    CALL "CEPIS-REQUEST" USING CEPIS-CONFIG CEPIS-HTTP-REQUEST
                               CEPIS-HTTP-RESPONSE CEPIS-STATUS
    END-CALL
    IF CEPIS-FAILED
        GOBACK
    END-IF

    EVALUATE HTTP-STATUS
        WHEN 200
            PERFORM PARSE-ADDRESS
        WHEN 404
            SET CEPIS-NOT-FOUND TO TRUE
            MOVE SPACES TO CEPIS-STATUS-MESSAGE
            STRING "CEP " WS-CEP " does not exist in the DNE database"
                DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
            END-STRING
        WHEN 400
            SET CEPIS-INVALID-CEP TO TRUE
            PERFORM API-MESSAGE
        WHEN OTHER
            SET CEPIS-INVALID-RESPONSE TO TRUE
            MOVE SPACES TO CEPIS-STATUS-MESSAGE
            STRING "unexpected status " HTTP-STATUS
                   " for CEP " WS-CEP
                DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
            END-STRING
    END-EVALUATE
    GOBACK.

*> -------------------------------------------------------------------
CLEAR-ADDRESS.
    INITIALIZE CEPIS-ADDRESS
    SET ADDR-NOT-TRUNCATED TO TRUE
    SET ADDR-GEO-ABSENT TO TRUE
    MOVE SPACES TO ADDR-GEO-PRECISAO.

*> -------------------------------------------------------------------
*> The API's own 400 carries a message this client does not have: the
*> format already passed local validation, so the server knows something
*> we do not. Surface its "mensagem" when there is one.
API-MESSAGE.
    MOVE SPACES TO CEPIS-STATUS-MESSAGE
    MOVE "mensagem" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-TYPE = "S" AND JS-VALUE-LEN > 0
        MOVE JS-VALUE TO CEPIS-STATUS-MESSAGE
    ELSE
        STRING "CEP " WS-CEP " rejected by the API"
            DELIMITED BY SIZE INTO CEPIS-STATUS-MESSAGE
        END-STRING
    END-IF.

*> -------------------------------------------------------------------
GET-MEMBER.
    MOVE 1 TO JS-FROM
    MOVE HTTP-BODY-LEN TO JS-TO
    CALL "CEPIS-JSON-GET" USING HTTP-BODY CEPIS-JSON-SCAN
    END-CALL.

*> -------------------------------------------------------------------
GET-GEO-MEMBER.
    MOVE WS-GEO-FROM TO JS-FROM
    MOVE WS-GEO-TO TO JS-TO
    CALL "CEPIS-JSON-GET" USING HTTP-BODY CEPIS-JSON-SCAN
    END-CALL.

*> -------------------------------------------------------------------
PARSE-ADDRESS.
    MOVE "cep" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-TYPE = "E"
        SET CEPIS-INVALID-RESPONSE TO TRUE
        MOVE "response is not a JSON object -- something between you "
          &  "and the API is answering instead of it"
          TO CEPIS-STATUS-MESSAGE
        EXIT PARAGRAPH
    END-IF

    IF JS-VALUE-LEN > 8
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-CEP
    IF ADDR-CEP = SPACES
        MOVE WS-CEP TO ADDR-CEP
    END-IF
    STRING ADDR-CEP(1:5) "-" ADDR-CEP(6:3)
        DELIMITED BY SIZE INTO ADDR-CEP-MASKED
    END-STRING

    MOVE "logradouro" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 120
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-LOGRADOURO

    MOVE "complemento" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 120
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-COMPLEMENTO

    MOVE "unidade" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 80
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-UNIDADE

    MOVE "bairro" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 100
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-BAIRRO

    MOVE "localidade" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 100
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-LOCALIDADE

    MOVE "uf" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 2
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-UF

    MOVE "estado" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 60
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-ESTADO

    MOVE "regiao" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-VALUE-LEN > 30
        SET ADDR-WAS-TRUNCATED TO TRUE
    END-IF
    MOVE JS-VALUE TO ADDR-REGIAO

    MOVE "ibge" TO JS-KEY
    PERFORM GET-MEMBER
    MOVE JS-VALUE TO ADDR-IBGE

    MOVE "gia" TO JS-KEY
    PERFORM GET-MEMBER
    MOVE JS-VALUE TO ADDR-GIA

    MOVE "ddd" TO JS-KEY
    PERFORM GET-MEMBER
    MOVE JS-VALUE TO ADDR-DDD

    MOVE "siafi" TO JS-KEY
    PERFORM GET-MEMBER
    MOVE JS-VALUE TO ADDR-SIAFI

    PERFORM PARSE-GEO
    SET CEPIS-OK TO TRUE.

*> -------------------------------------------------------------------
*> The coordinate is omitted entirely when the database has none, so its
*> absence is normal, not an error. A geo block that is present but
*> malformed is dropped the same way: the rest of the address is still
*> useful and losing it over a bad float would be a poor trade.
PARSE-GEO.
    SET ADDR-GEO-ABSENT TO TRUE
    MOVE "geo" TO JS-KEY
    PERFORM GET-MEMBER
    IF JS-TYPE NOT = "O"
        EXIT PARAGRAPH
    END-IF
    MOVE JS-VSTART TO WS-GEO-FROM
    MOVE JS-VEND TO WS-GEO-TO
    MOVE "Y" TO WS-GEO-OK

    MOVE "lat" TO JS-KEY
    PERFORM GET-GEO-MEMBER
    PERFORM CHECK-NUMERIC
    IF WS-NUM-OK = "Y"
        COMPUTE WS-LAT = FUNCTION NUMVAL (JS-VALUE)
    ELSE
        MOVE "N" TO WS-GEO-OK
    END-IF

    MOVE "lon" TO JS-KEY
    PERFORM GET-GEO-MEMBER
    PERFORM CHECK-NUMERIC
    IF WS-NUM-OK = "Y"
        COMPUTE WS-LON = FUNCTION NUMVAL (JS-VALUE)
    ELSE
        MOVE "N" TO WS-GEO-OK
    END-IF

    MOVE "raio_m" TO JS-KEY
    PERFORM GET-GEO-MEMBER
    PERFORM CHECK-NUMERIC
    IF WS-NUM-OK = "Y"
        COMPUTE WS-RAIO ROUNDED = FUNCTION NUMVAL (JS-VALUE)
    ELSE
        MOVE "N" TO WS-GEO-OK
    END-IF

    MOVE "precisao" TO JS-KEY
    PERFORM GET-GEO-MEMBER
    IF JS-TYPE = "S" AND JS-VALUE-LEN > 0
        MOVE JS-VALUE TO WS-PRECISAO
    ELSE
        MOVE "N" TO WS-GEO-OK
    END-IF

    IF WS-GEO-OK = "Y"
        SET ADDR-GEO-KNOWN TO TRUE
        MOVE WS-LAT TO ADDR-GEO-LAT
        MOVE WS-LON TO ADDR-GEO-LON
        MOVE WS-RAIO TO ADDR-GEO-RAIO-M
        MOVE WS-PRECISAO TO ADDR-GEO-PRECISAO
    END-IF.

*> -------------------------------------------------------------------
*> FUNCTION NUMVAL answers 0 for text it cannot read, which would turn
*> "lat": "not a number" into a coordinate off the coast of Africa. The
*> text is therefore checked before it is converted.
CHECK-NUMERIC.
    MOVE "N" TO WS-NUM-OK
    IF JS-TYPE NOT = "N" AND JS-TYPE NOT = "S"
        EXIT PARAGRAPH
    END-IF
    IF JS-VALUE-LEN = 0 OR JS-VALUE-LEN > 32
        EXIT PARAGRAPH
    END-IF
    MOVE 0 TO WS-DIGITS
    MOVE 0 TO WS-DOTS
    MOVE 0 TO WS-JUNK
    PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > JS-VALUE-LEN
        MOVE JS-VALUE(WS-I:1) TO WS-C
        EVALUATE TRUE
            WHEN WS-C >= "0" AND WS-C <= "9"
                ADD 1 TO WS-DIGITS
            WHEN WS-C = "."
                ADD 1 TO WS-DOTS
            WHEN (WS-C = "-" OR WS-C = "+") AND WS-I = 1
                CONTINUE
            WHEN OTHER
                ADD 1 TO WS-JUNK
        END-EVALUATE
    END-PERFORM
    IF WS-DIGITS > 0 AND WS-DOTS <= 1 AND WS-JUNK = 0
        MOVE "Y" TO WS-NUM-OK
    END-IF.

END PROGRAM CEPIS-LOOKUP.