vos-data-utils 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vos-data-utils might be problematic. Click here for more details.
- vdutils/__init__.py +21 -45
- vdutils/bjd.py +40 -10
- vdutils/bjdconnector.py +322 -55
- vdutils/convaddr.py +121 -26
- vdutils/cordate.py +1 -1
- vdutils/data/__init__.py +10 -9
- vdutils/genpnu.py +623 -0
- vdutils/library/__init__.py +42 -0
- vdutils/library/data.py +51 -1
- vdutils/tests/test_convaddr.py +1 -3
- vdutils/tests/test_cordate.py +6 -1
- vdutils/tests/test_genpnu.py +1004 -0
- vdutils/tests/test_vid.py +247 -0
- vdutils/tests/tests.py +15 -5
- vdutils/vid.py +157 -99
- {vos_data_utils-0.0.2.dist-info → vos_data_utils-0.0.4.dist-info}/METADATA +3 -2
- vos_data_utils-0.0.4.dist-info/RECORD +21 -0
- vdutils/data/bjd.txt +0 -49844
- vdutils/data/bjd_changed.txt +0 -8579
- vdutils/data/bjd_connectors.pkl +0 -0
- vdutils/data/bjd_current.txt +0 -20560
- vdutils/data/bjd_frequency_dictionary.txt +0 -11290
- vdutils/data/bjd_smallest.txt +0 -9786
- vdutils/data/date_dictionary.txt +0 -738978
- vdutils/data/full_bjd_connectors.pkl +0 -0
- vdutils/data/multiple_word_sgg_list.txt +0 -65
- vdutils/data/pnu/bjd_20230701.pkl +0 -0
- vdutils/data/pnu/bjd_20240101.pkl +0 -0
- vdutils/data/pnu/bjd_20240118.pkl +0 -0
- vdutils/pnu.py +0 -221
- vos_data_utils-0.0.2.dist-info/RECORD +0 -31
- {vos_data_utils-0.0.2.dist-info → vos_data_utils-0.0.4.dist-info}/WHEEL +0 -0
- {vos_data_utils-0.0.2.dist-info → vos_data_utils-0.0.4.dist-info}/entry_points.txt +0 -0
- {vos_data_utils-0.0.2.dist-info → vos_data_utils-0.0.4.dist-info}/top_level.txt +0 -0
vdutils/genpnu.py
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import pkg_resources
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
List,
|
|
7
|
+
Dict,
|
|
8
|
+
Tuple,
|
|
9
|
+
Optional
|
|
10
|
+
)
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from vdutils.library.data import (
|
|
14
|
+
SGG_SPLIT_LIST,
|
|
15
|
+
LAST_NM_REFINE_MAP
|
|
16
|
+
)
|
|
17
|
+
from vdutils.library import Log
|
|
18
|
+
from vdutils.data import (
|
|
19
|
+
__sep__,
|
|
20
|
+
__index__,
|
|
21
|
+
__encoding__,
|
|
22
|
+
_get_folder_names
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class GenPnu():
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
base_dt: Optional[str] = None
|
|
33
|
+
):
|
|
34
|
+
|
|
35
|
+
if base_dt is not None:
|
|
36
|
+
if not isinstance(base_dt, str):
|
|
37
|
+
raise TypeError("type of object('base_dt') must be string")
|
|
38
|
+
|
|
39
|
+
if not base_dt.isdigit():
|
|
40
|
+
raise ValueError("object('base_dt') should be a string consisting of numbers")
|
|
41
|
+
|
|
42
|
+
if len(base_dt) != 8:
|
|
43
|
+
raise ValueError("object('base_dt') should be a string consisting of exactly 8(YYYYMMDD) digits")
|
|
44
|
+
else: pass
|
|
45
|
+
|
|
46
|
+
self.sep = __sep__
|
|
47
|
+
self.index: bool = __index__
|
|
48
|
+
self.encoding: str = __encoding__
|
|
49
|
+
self.base_dt: Optional[str] = base_dt
|
|
50
|
+
self.bjd_current_df: pd.DataFrame() = None
|
|
51
|
+
self.bjd_current_nm_cd_dic = defaultdict(list)
|
|
52
|
+
self.bjd_dic: Dict[str, Dict[str, str]] = {}
|
|
53
|
+
self.bjd_nm_change_dic: Dict[str, str] = {
|
|
54
|
+
"시도명": "sido_nm",
|
|
55
|
+
"시군구명": "sgg_nm",
|
|
56
|
+
"읍면동명": "emd_nm",
|
|
57
|
+
"리명": "ri_nm",
|
|
58
|
+
}
|
|
59
|
+
self.logger = Log('GeneratePnu').stream_handler("INFO")
|
|
60
|
+
self._get_base_dt()
|
|
61
|
+
self._get_file_names()
|
|
62
|
+
self._prepare()
|
|
63
|
+
self.base_dt_print: str = f"{self.base_dt[:4]}-{self.base_dt[4:6]}-{self.base_dt[6:8]}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _find_latest_base_dt(
|
|
67
|
+
self,
|
|
68
|
+
base_dts: List[str]
|
|
69
|
+
) -> str:
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
입력된 날짜(YYYYMMDD)와 법정동 데이터 시점 리스트와 비교하여 입력된 날짜보다 과거 시점 중 최신 시점을 반환
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
for date in base_dts:
|
|
76
|
+
if date < self.base_dt:
|
|
77
|
+
return date
|
|
78
|
+
|
|
79
|
+
# 입력된 날짜보다 작은 날짜가 없을 경우
|
|
80
|
+
self.logger.info("입력된 날짜보다 이전 시점의 법정동 데이터가 존재하지 않습니다. 보유한 데이터중 최신 데이터를 적용합니다.")
|
|
81
|
+
return base_dts[0]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_base_dt(self):
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
입력된 날짜(YYYYMMDD)와 법정동 데이터 시점 리스트와 비교하여 입력된 날짜보다 과거 시점 중 최신 시점을 반환 \n
|
|
88
|
+
입력된 날짜(YYYYMMDD)가 없으면 데이터 시점 리스트 중 최신 시점을 반환
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
base_dts = _get_folder_names(base_folder_path='vdutils/data/bjd')
|
|
92
|
+
base_dts = sorted(base_dts, reverse=True)
|
|
93
|
+
try:
|
|
94
|
+
if self.base_dt is None:
|
|
95
|
+
self.base_dt = base_dts[0]
|
|
96
|
+
else:
|
|
97
|
+
self.base_dt = self._find_latest_base_dt(base_dts=base_dts)
|
|
98
|
+
finally:
|
|
99
|
+
self.logger.info(f"적용 법정동 데이터 시점: {self.base_dt}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _get_file_names(self):
|
|
103
|
+
self.file_name_bjd_current = pkg_resources.resource_filename(
|
|
104
|
+
"vdutils",
|
|
105
|
+
f"data/bjd/{self.base_dt}/bjd_current.txt"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_bjd_current_df(
|
|
110
|
+
self,
|
|
111
|
+
file_name_bjd_current,
|
|
112
|
+
input_encoding,
|
|
113
|
+
input_index,
|
|
114
|
+
input_sep
|
|
115
|
+
):
|
|
116
|
+
try:
|
|
117
|
+
self.bjd_current_df: pd.DataFrame = pd.read_csv(
|
|
118
|
+
file_name_bjd_current,
|
|
119
|
+
sep=input_sep,
|
|
120
|
+
engine='python',
|
|
121
|
+
encoding=input_encoding,
|
|
122
|
+
dtype={
|
|
123
|
+
'과거법정동코드': str,
|
|
124
|
+
'법정동코드': str
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
self.logger.error(f"Failed to read {file_name_bjd_current}")
|
|
129
|
+
self.logger.error(e)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _create_bjd_current_nm_cd_dic(self):
|
|
133
|
+
try:
|
|
134
|
+
for idx, row in self.bjd_current_df.iterrows():
|
|
135
|
+
bjd_nms = []
|
|
136
|
+
for bjd_nm in ["시도명", "시군구명", "읍면동명", "리명"]:
|
|
137
|
+
if not pd.isna(row[bjd_nm]):
|
|
138
|
+
bjd_nms.append(row[bjd_nm])
|
|
139
|
+
bjd_nms = " ".join(bjd_nms)
|
|
140
|
+
self.bjd_current_nm_cd_dic[bjd_nms].append(
|
|
141
|
+
{
|
|
142
|
+
"bjd_cd": row["법정동코드"],
|
|
143
|
+
"deleted_dt": None if pd.isna(row["삭제일자"]) else row["삭제일자"]
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
self.logger.error(f"Failed to create bjd_current_nm_cd_dic")
|
|
148
|
+
self.logger.error(e)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _create_bjd_dic(self):
|
|
152
|
+
try:
|
|
153
|
+
for idx, row in self.bjd_current_df.iterrows():
|
|
154
|
+
bjd_cd: str = row["법정동코드"]
|
|
155
|
+
full_bjd_nm: str = row["법정동명"]
|
|
156
|
+
created_dt: str = row["생성일자"]
|
|
157
|
+
deleted_dt: str = row["삭제일자"]
|
|
158
|
+
bjd_datas: Dict[str, str] = {}
|
|
159
|
+
for bjd_nm in ["시도명", "시군구명", "읍면동명", "리명"]:
|
|
160
|
+
if not pd.isna(row[bjd_nm]):
|
|
161
|
+
bjd_datas[self.bjd_nm_change_dic[bjd_nm]] = row[bjd_nm]
|
|
162
|
+
else:
|
|
163
|
+
bjd_datas[self.bjd_nm_change_dic[bjd_nm]] = None
|
|
164
|
+
bjd_datas["full_bjd_nm"] = full_bjd_nm
|
|
165
|
+
bjd_datas["created_dt"] = created_dt
|
|
166
|
+
bjd_datas["deleted_dt"] = None if pd.isna(deleted_dt) else deleted_dt
|
|
167
|
+
self.bjd_dic[bjd_cd] = bjd_datas
|
|
168
|
+
except Exception as e:
|
|
169
|
+
self.logger.error(f"Failed to create bjd_dic")
|
|
170
|
+
self.logger.error(e)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _prepare(self):
|
|
174
|
+
self._get_bjd_current_df(
|
|
175
|
+
file_name_bjd_current=self.file_name_bjd_current,
|
|
176
|
+
input_encoding=self.encoding,
|
|
177
|
+
input_index=self.index,
|
|
178
|
+
input_sep=self.sep,
|
|
179
|
+
)
|
|
180
|
+
self._create_bjd_current_nm_cd_dic()
|
|
181
|
+
self._create_bjd_dic()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def get_bjd_cd(
|
|
185
|
+
self,
|
|
186
|
+
bjd_nm: str,
|
|
187
|
+
) -> Dict[str, Any]:
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
입력된 문자열(한글 법정동명)의 법정동 코드를 반환
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
bjd_nm (str): The input should be a string consisting of Korean administrative district names.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
TypeError: If the 'bjd_nm' object is not of type string.
|
|
197
|
+
ValueError: If the 'bjd_nm' object is not consist of only Korean characters and numbers.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Dict[str, Any]:
|
|
201
|
+
"error": bool,
|
|
202
|
+
"bjd_cd": Optional[str],
|
|
203
|
+
"deleted_dt": Optional[str],
|
|
204
|
+
"base_dt": str,
|
|
205
|
+
"msg": str
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
if not isinstance(bjd_nm, str):
|
|
209
|
+
raise TypeError("type of object('bjd_nm') must be string")
|
|
210
|
+
|
|
211
|
+
if not re.match("^[가-힣0-9 ]+$", bjd_nm):
|
|
212
|
+
raise ValueError("object('bjd_nm') should consist of only Korean characters and numbers")
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
not_a_valid_district_response: Dict[str, Any] = {
|
|
216
|
+
"error": True,
|
|
217
|
+
"bjd_cd": None,
|
|
218
|
+
"deleted_dt": None,
|
|
219
|
+
"base_dt": self.base_dt_print,
|
|
220
|
+
"msg": f"'{bjd_nm}' is not a valid legal district name"
|
|
221
|
+
}
|
|
222
|
+
bjd_nm = " ".join(bjd_nm.split())
|
|
223
|
+
|
|
224
|
+
if bjd_nm in self.bjd_current_nm_cd_dic:
|
|
225
|
+
bjd_cd_list = self.bjd_current_nm_cd_dic[bjd_nm]
|
|
226
|
+
if len(bjd_cd_list) > 1:
|
|
227
|
+
bjd_cd = list(
|
|
228
|
+
filter(
|
|
229
|
+
lambda bjd_cd_data: not bjd_cd_data["deleted_dt"], bjd_cd_list
|
|
230
|
+
)
|
|
231
|
+
)[0]
|
|
232
|
+
else:
|
|
233
|
+
bjd_cd = bjd_cd_list[0]
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
"error": False,
|
|
237
|
+
**bjd_cd,
|
|
238
|
+
"base_dt": self.base_dt_print,
|
|
239
|
+
"msg": ""
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
if len(bjd_nm.split()) == 1:
|
|
244
|
+
return not_a_valid_district_response
|
|
245
|
+
else:
|
|
246
|
+
sgg = bjd_nm.split()[1]
|
|
247
|
+
if sgg in SGG_SPLIT_LIST:
|
|
248
|
+
sgg_split_nm = f"{sgg[:2]}시 {sgg[2:]}"
|
|
249
|
+
bjd_nm = bjd_nm.replace(sgg, sgg_split_nm)
|
|
250
|
+
return self.get_bjd_cd(bjd_nm)
|
|
251
|
+
|
|
252
|
+
last_nm = bjd_nm.split()[-1]
|
|
253
|
+
if last_nm in LAST_NM_REFINE_MAP:
|
|
254
|
+
bjd_nm = bjd_nm.replace(last_nm, LAST_NM_REFINE_MAP[last_nm])
|
|
255
|
+
return self.get_bjd_cd(bjd_nm)
|
|
256
|
+
|
|
257
|
+
return not_a_valid_district_response
|
|
258
|
+
|
|
259
|
+
except:
|
|
260
|
+
return not_a_valid_district_response
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_bjd_data(
|
|
264
|
+
self,
|
|
265
|
+
bjd_cd: str
|
|
266
|
+
) -> Dict[str, Any]:
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
입력된 문자열(숫자 10자리의 법정동코드)의 법정동 데이터(각 단위 법정동명, 생성일자, 삭제일자)를 반환
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
bjd_cd (str): The Korean district code string consisting of exactly 10 digits.
|
|
273
|
+
|
|
274
|
+
Raises:
|
|
275
|
+
TypeError: If the 'bjd_cd' object is not of type string.
|
|
276
|
+
ValueError: If the 'bjd_cd' object does not consist of digits only.
|
|
277
|
+
ValueError: If the 'bjd_cd' object does not consist of exactly 10 digits.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Dict[str, Any]: {
|
|
281
|
+
"error": bool,
|
|
282
|
+
"sido_nm": Optional[str],
|
|
283
|
+
"sgg_nm": Optional[str],
|
|
284
|
+
"emd_nm": Optional[str],
|
|
285
|
+
"ri_nm": Optional[str],
|
|
286
|
+
"full_bjd_nm": Optional[str],
|
|
287
|
+
"created_dt": Optional[str],
|
|
288
|
+
"deleted_dt": Optional[str],
|
|
289
|
+
"base_dt": str
|
|
290
|
+
}
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
if not isinstance(bjd_cd, str):
|
|
294
|
+
raise TypeError("type of object('bjd_cd') must be string")
|
|
295
|
+
|
|
296
|
+
if not bjd_cd.isdigit():
|
|
297
|
+
raise ValueError("object('bjd_cd') should be a string consisting of numbers")
|
|
298
|
+
|
|
299
|
+
if len(bjd_cd) != 10:
|
|
300
|
+
raise ValueError("object('bjd_cd') should be a string consisting of exactly 10 digits")
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
not_a_valid_district_response: Dict[str, Any] = {
|
|
304
|
+
"error": True,
|
|
305
|
+
"sido_nm": None,
|
|
306
|
+
"sgg_nm": None,
|
|
307
|
+
"emd_nm": None,
|
|
308
|
+
"ri_nm": None,
|
|
309
|
+
"full_bjd_nm": None,
|
|
310
|
+
"created_dt": None,
|
|
311
|
+
"deleted_dt": None,
|
|
312
|
+
"base_dt": self.base_dt_print,
|
|
313
|
+
"msg": f"'{bjd_cd}' is not a valid legal district code"
|
|
314
|
+
}
|
|
315
|
+
if bjd_cd in self.bjd_dic:
|
|
316
|
+
return {"error": False, **self.bjd_dic[bjd_cd], "base_dt": self.base_dt_print, "msg": ""}
|
|
317
|
+
else:
|
|
318
|
+
return {"error": True, **not_a_valid_district_response}
|
|
319
|
+
except Exception as e:
|
|
320
|
+
return {"error": True, **not_a_valid_district_response.update({"msg": str(e)})}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@staticmethod
|
|
324
|
+
def _validate_jibun(
|
|
325
|
+
jibun: Optional[str]
|
|
326
|
+
) -> bool:
|
|
327
|
+
|
|
328
|
+
"""
|
|
329
|
+
입력된 지번 문자열이 올바른 형식인지 정규식을 이용하여 검증하여 반환 \n
|
|
330
|
+
단, 블록지번를 의미하는 음절이 포함되거나 '*' 가 포함될 경우 예외 적용하여 True 를 리턴
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
jibun (str): Validates the format of the given address.
|
|
334
|
+
The address should include '산' and only contain digits except for '산' and '-'.
|
|
335
|
+
The main and sub numbers should be separated by a hyphen, and both can have a maximum of 4 digits.
|
|
336
|
+
Examples:
|
|
337
|
+
With mountain and sub-number: 산 0000-0000
|
|
338
|
+
With mountain and no sub-number: 산 0000
|
|
339
|
+
Without mountain and with sub-number: 0000-0000
|
|
340
|
+
Without mountain and without sub-number: 0000
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
ValueError: If the 'jibun' object is not of the specified format.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
bool
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
msg = """
|
|
350
|
+
Invalid 'jibun' format. Please follow the specified format.
|
|
351
|
+
|
|
352
|
+
The address should include '산' and only contain digits except for '산' and '-'.
|
|
353
|
+
The main and sub numbers should be separated by a hyphen, and both can have a maximum of 4 digits.
|
|
354
|
+
Examples:
|
|
355
|
+
With mountain and sub-number: 산 0000-0000
|
|
356
|
+
With mountain and no sub-number: 산 0000
|
|
357
|
+
Without mountain and with sub-number: 0000-0000
|
|
358
|
+
Without mountain and without sub-number: 0000
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
if pd.isna(jibun) \
|
|
362
|
+
or jibun == "" \
|
|
363
|
+
or jibun is None \
|
|
364
|
+
or jibun[0] in ["B", "가", "지"]:
|
|
365
|
+
return True
|
|
366
|
+
if "*" in jibun:
|
|
367
|
+
return True
|
|
368
|
+
|
|
369
|
+
jibun = jibun.replace(" ", "")
|
|
370
|
+
pattern = re.compile(r'^(산\s*)?\d{1,4}-\d{1,4}$|^(산\s*)?\d{1,4}$|^\d{1,4}-\d{1,4}$|^\d{1,4}$')
|
|
371
|
+
|
|
372
|
+
if not bool(pattern.match(jibun)):
|
|
373
|
+
raise ValueError(msg)
|
|
374
|
+
|
|
375
|
+
return True
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@staticmethod
|
|
379
|
+
def _get_mountain_cd(
|
|
380
|
+
jibun: str
|
|
381
|
+
) -> Tuple[str, str]:
|
|
382
|
+
|
|
383
|
+
"""
|
|
384
|
+
입력된 지번 문자열(지번 문자열 적합성 확인된 입력값)에서 '산' 여부 판단하여 산코드를 반환
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
if jibun[0] in ["산"]:
|
|
388
|
+
mountain_cd = "2"
|
|
389
|
+
jibun = jibun.replace("산", "")
|
|
390
|
+
|
|
391
|
+
else:
|
|
392
|
+
mountain_cd = "1"
|
|
393
|
+
|
|
394
|
+
return jibun, mountain_cd
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def _get_jibun_datas(
|
|
399
|
+
jibun: str
|
|
400
|
+
) -> Tuple[str, str, str]:
|
|
401
|
+
|
|
402
|
+
"""
|
|
403
|
+
입력된 지번 문자열(지번 문자열 적합성 확인된 입력값)에서 본번과 부번을 분리하여 번, 지 코드를 반환
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
jibun_split = jibun.split("-")
|
|
407
|
+
|
|
408
|
+
if len(jibun_split) == 2:
|
|
409
|
+
bun, ji = [int(num) for num in jibun_split]
|
|
410
|
+
bunji_cd = "%04d%04d" % (bun, ji)
|
|
411
|
+
bun = str(bun)
|
|
412
|
+
ji = str(ji)
|
|
413
|
+
|
|
414
|
+
elif len(jibun_split) == 1:
|
|
415
|
+
bun = int(jibun)
|
|
416
|
+
bunji_cd = "%04d0000" % (bun)
|
|
417
|
+
bun = str(bun)
|
|
418
|
+
ji = "0"
|
|
419
|
+
|
|
420
|
+
else:
|
|
421
|
+
bunji_cd = "00000000"
|
|
422
|
+
bun, ji = "0", "0"
|
|
423
|
+
|
|
424
|
+
return bunji_cd, bun, ji
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def generate_pnu(
|
|
428
|
+
self,
|
|
429
|
+
bjd_cd: str,
|
|
430
|
+
jibun: str # '산'을 포함한 지번
|
|
431
|
+
) -> Dict[str, Any]:
|
|
432
|
+
|
|
433
|
+
"""
|
|
434
|
+
입력된 문자열(법정동 코드, 지번)을 필지관리번호(pnu)로 변환하여 반환
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
bjd_cd (str): The Korean district code string consisting of exactly 10 digits.
|
|
438
|
+
jibun (str): Validates the format of the given address.
|
|
439
|
+
The address should include '산' and only contain digits except for '산' and '-'.
|
|
440
|
+
The main and sub numbers should be separated by a hyphen, and both can have a maximum of 4 digits.
|
|
441
|
+
Examples:
|
|
442
|
+
With mountain and sub-number: 산 0000-0000
|
|
443
|
+
With mountain and no sub-number: 산 0000
|
|
444
|
+
Without mountain and with sub-number: 0000-0000
|
|
445
|
+
Without mountain and without sub-number: 0000
|
|
446
|
+
|
|
447
|
+
Raises:
|
|
448
|
+
TypeError: If the 'bjd_cd' object is not of type string.
|
|
449
|
+
TypeError: If the 'jibun' object is not of type string.
|
|
450
|
+
ValueError: If the 'bjd_cd' object does not consist of digits only.
|
|
451
|
+
ValueError: If the 'bjd_cd' object does not consist of exactly 10 digits.
|
|
452
|
+
ValueError: If the 'jibun' object is not of the specified format.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Dict[str, Any]: {
|
|
456
|
+
"error": bool,
|
|
457
|
+
"pnu": str,
|
|
458
|
+
"bjd_cd": str,
|
|
459
|
+
"mountain_cd": str,
|
|
460
|
+
"bunji_cd": str,
|
|
461
|
+
"bjd_datas": Dict[str, Any],
|
|
462
|
+
"bun": str,
|
|
463
|
+
"ji": str,
|
|
464
|
+
"msg": str,
|
|
465
|
+
"base_dt": str
|
|
466
|
+
}
|
|
467
|
+
"""
|
|
468
|
+
|
|
469
|
+
if not isinstance(bjd_cd, str):
|
|
470
|
+
raise TypeError("type of object('bjd_cd') must be string")
|
|
471
|
+
|
|
472
|
+
if not isinstance(jibun, str):
|
|
473
|
+
raise TypeError("type of object('jibun') must be string")
|
|
474
|
+
|
|
475
|
+
if not bjd_cd.isdigit():
|
|
476
|
+
raise ValueError("object('bjd_cd') should be a string consisting of numbers")
|
|
477
|
+
|
|
478
|
+
if len(bjd_cd) != 10:
|
|
479
|
+
raise ValueError("object('bjd_cd') should be a string consisting of exactly 10 digits")
|
|
480
|
+
|
|
481
|
+
if self._validate_jibun(jibun):
|
|
482
|
+
msg = ""
|
|
483
|
+
try:
|
|
484
|
+
bjd_datas = self.get_bjd_data(bjd_cd)
|
|
485
|
+
|
|
486
|
+
if pd.isna(jibun) \
|
|
487
|
+
or jibun == "" \
|
|
488
|
+
or jibun is None \
|
|
489
|
+
or jibun[0] in ["B", "가", "지"]:
|
|
490
|
+
error = True
|
|
491
|
+
mountain_cd = "1"
|
|
492
|
+
bunji_cd = "00000000"
|
|
493
|
+
bun, ji = "0", "0"
|
|
494
|
+
msg = "블록지번"
|
|
495
|
+
|
|
496
|
+
elif "*" in jibun:
|
|
497
|
+
error = True
|
|
498
|
+
mountain_cd = "1"
|
|
499
|
+
bunji_cd = "00000000"
|
|
500
|
+
bun, ji = "0", "0"
|
|
501
|
+
msg = "매칭필요"
|
|
502
|
+
|
|
503
|
+
else:
|
|
504
|
+
error = bjd_datas.get("error")
|
|
505
|
+
if error is True:
|
|
506
|
+
msg = bjd_datas.get("msg")
|
|
507
|
+
jibun = jibun.replace(" ", "")
|
|
508
|
+
jibun, mountain_cd = self._get_mountain_cd(jibun)
|
|
509
|
+
bunji_cd, bun, ji = self._get_jibun_datas(jibun)
|
|
510
|
+
|
|
511
|
+
except Exception as e:
|
|
512
|
+
error = True
|
|
513
|
+
mountain_cd = "1"
|
|
514
|
+
bunji_cd = "00000000"
|
|
515
|
+
bun, ji = "0", "0"
|
|
516
|
+
msg = str(e)
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
"error": error,
|
|
520
|
+
"pnu": f"{bjd_cd}{mountain_cd}{bunji_cd}",
|
|
521
|
+
"bjd_cd": bjd_cd,
|
|
522
|
+
"mountain_cd": mountain_cd,
|
|
523
|
+
"bunji_cd": bunji_cd,
|
|
524
|
+
"bjd_datas": bjd_datas,
|
|
525
|
+
"bun": bun,
|
|
526
|
+
"ji": ji,
|
|
527
|
+
"msg": msg,
|
|
528
|
+
"base_dt": self.base_dt_print
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def generate_pnu_from_bjd_nm(
|
|
533
|
+
self,
|
|
534
|
+
bjd_nm: str,
|
|
535
|
+
jibun: str # '산'을 포함한 지번
|
|
536
|
+
) -> Dict[str, Any]:
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
입력된 문자열(법정동 코드, 지번)을 필지관리번호(pnu)로 변환하여 반환
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
bjd_nm (str): The input should be a string consisting of Korean administrative district names.
|
|
543
|
+
jibun (str): Validates the format of the given address.
|
|
544
|
+
The address should include '산' and only contain digits except for '산' and '-'.
|
|
545
|
+
The main and sub numbers should be separated by a hyphen, and both can have a maximum of 4 digits.
|
|
546
|
+
Examples:
|
|
547
|
+
With mountain and sub-number: 산 0000-0000
|
|
548
|
+
With mountain and no sub-number: 산 0000
|
|
549
|
+
Without mountain and with sub-number: 0000-0000
|
|
550
|
+
Without mountain and without sub-number: 0000
|
|
551
|
+
|
|
552
|
+
Raises:
|
|
553
|
+
TypeError: If the 'bjd_nm' object is not of type string.
|
|
554
|
+
TypeError: If the 'jibun' object is not of type string.
|
|
555
|
+
ValueError: If the 'bjd_nm' object is not consist of only Korean characters and numbers.
|
|
556
|
+
ValueError: If the 'jibun' object is not of the specified format.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
Dict[str, Any]: {
|
|
560
|
+
"error": bool,
|
|
561
|
+
"pnu": str,
|
|
562
|
+
"bjd_cd": str,
|
|
563
|
+
"mountain_cd": str,
|
|
564
|
+
"bunji_cd": str,
|
|
565
|
+
"bjd_datas": Dict[str, Any],
|
|
566
|
+
"bun": str,
|
|
567
|
+
"ji": str,
|
|
568
|
+
"msg": str,
|
|
569
|
+
"base_dt": str
|
|
570
|
+
}
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
if not isinstance(bjd_nm, str):
|
|
574
|
+
raise TypeError("type of object('bjd_nm') must be string")
|
|
575
|
+
|
|
576
|
+
if not isinstance(jibun, str):
|
|
577
|
+
raise TypeError("type of object('jibun') must be string")
|
|
578
|
+
|
|
579
|
+
if not re.match("^[가-힣0-9 ]+$", bjd_nm):
|
|
580
|
+
raise ValueError("object('bjd_nm') should consist of only Korean characters and numbers")
|
|
581
|
+
|
|
582
|
+
if self._validate_jibun(jibun):
|
|
583
|
+
res = self.get_bjd_cd(bjd_nm=bjd_nm)
|
|
584
|
+
try:
|
|
585
|
+
if res.get("error") is not True:
|
|
586
|
+
return self.generate_pnu(
|
|
587
|
+
bjd_cd=res.get("bjd_cd"),
|
|
588
|
+
jibun=jibun
|
|
589
|
+
)
|
|
590
|
+
else:
|
|
591
|
+
error = res.get("error")
|
|
592
|
+
pnu = None
|
|
593
|
+
bjd_cd = None
|
|
594
|
+
mountain_cd = None
|
|
595
|
+
bunji_cd = None
|
|
596
|
+
bjd_datas = None
|
|
597
|
+
bun = None
|
|
598
|
+
ji = None
|
|
599
|
+
msg = f"올바르지 않은 법정동명({res.get('msg')})"
|
|
600
|
+
|
|
601
|
+
except Exception as e:
|
|
602
|
+
error = True
|
|
603
|
+
pnu = None
|
|
604
|
+
bjd_cd = None
|
|
605
|
+
mountain_cd = None
|
|
606
|
+
bunji_cd = None
|
|
607
|
+
bjd_datas = None
|
|
608
|
+
bun = None
|
|
609
|
+
ji = None
|
|
610
|
+
msg = str(e)
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
"error": error,
|
|
614
|
+
"pnu": pnu,
|
|
615
|
+
"bjd_cd": bjd_cd,
|
|
616
|
+
"mountain_cd": mountain_cd,
|
|
617
|
+
"bunji_cd": bunji_cd,
|
|
618
|
+
"bjd_datas": bjd_datas,
|
|
619
|
+
"bun": bun,
|
|
620
|
+
"ji": ji,
|
|
621
|
+
"msg": msg,
|
|
622
|
+
"base_dt": self.base_dt_print
|
|
623
|
+
}
|
vdutils/library/__init__.py
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Log:
|
|
5
|
+
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
name: str
|
|
9
|
+
):
|
|
10
|
+
self.log = logging.getLogger(name)
|
|
11
|
+
self.log.propagate = True
|
|
12
|
+
self.formatter = logging.Formatter("%(asctime)s | [%(levelname)s] | %(message)s",
|
|
13
|
+
"%Y-%m-%d %H:%M:%S")
|
|
14
|
+
self.levels = {
|
|
15
|
+
"DEBUG" : logging.DEBUG,
|
|
16
|
+
"INFO" : logging.INFO,
|
|
17
|
+
"WARNING" : logging.WARNING,
|
|
18
|
+
"ERROR" : logging.ERROR,
|
|
19
|
+
"CRITICAL" : logging.CRITICAL
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def stream_handler(
|
|
23
|
+
self,
|
|
24
|
+
level: str
|
|
25
|
+
):
|
|
26
|
+
if len(self.log.handlers) > 0:
|
|
27
|
+
return self.log # Logger already exists
|
|
28
|
+
else:
|
|
29
|
+
"""
|
|
30
|
+
level :
|
|
31
|
+
> "DEBUG" : logging.DEBUG ,
|
|
32
|
+
> "INFO" : logging.INFO ,
|
|
33
|
+
> "WARNING" : logging.WARNING ,
|
|
34
|
+
> "ERROR" : logging.ERROR ,
|
|
35
|
+
> "CRITICAL" : logging.CRITICAL ,
|
|
36
|
+
"""
|
|
37
|
+
self.log.setLevel(self.levels[level])
|
|
38
|
+
streamHandler = logging.StreamHandler()
|
|
39
|
+
streamHandler.setFormatter(self.formatter)
|
|
40
|
+
self.log.addHandler(streamHandler)
|
|
41
|
+
return self.log
|
|
42
|
+
|