ai_invest/backend/airflow/dags/plugins/utils/scraplib.py

3368 lines
164 KiB
Python
Raw Normal View History

import inspect
import pymysql
import os
from selenium import webdriver
from selenium.webdriver.ie.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.common.exceptions import TimeoutException
from bs4 import BeautifulSoup
import lxml.html
# from lxml.cssselect import CSSSelector
import requests
import json
import time
import re
# from konfig import Config
import urllib
from datetime import datetime, timedelta
import sys
import pandas as pd
import html.entities # safe_encode
import uuid
import random
import logging
# pluins
from plugins.sql.hooks.mysql_hook import CommonHookMySQL
#환경설정 파일 로드
# CONF = Config(os.path.dirname(__file__) + "\\conf.ini")
# DB_CONF = CONF.get_map("DB")
# DRIVER = CONF.get_map("DRIVER")
# DRIVER["EXT_PATH"] = os.path.dirname(__file__) + DRIVER["EXT_PATH"]
# DB 연결 ID
DB_CONN_ID = "COLLECT_SERVER_DB"
#===============================================================================================================================================
#데이터베이스 커넥터
#===============================================================================================================================================
class dbconn(CommonHookMySQL):
def __init__(self):
pass
def change_DB(self, DB_CONF):
self.__init__(host=DB_CONF["host"], user=DB_CONF["user"], passwd=DB_CONF["passwd"], db=DB_CONF["db"], cs=DB_CONF["cs"])
#DB 접속
def sql_exec(self, qry, type):
#print(qry)
if type == "TEST":
return ''
else:
if (type == "DS" or type=="DS_one"):
cur = self.conn.cursor(pymysql.cursors.DictCursor)
else:
cur = self.conn.cursor()
cur.execute(qry)
if (type=="S" or type=="DS"):
rows = cur.fetchall()
if (type == "S_one" or type == "DS_one"):
rows = cur.fetchone()
#cur.close()
#conn.close()
if(type=="S" or type=="DS"):
return rows
if (type == "S_one" or type == "DS_one"):
if rows is not None:
return rows[0]
else:
return ''
#쿼리 문자열 클린징
def clean_param(self, _str):
_str = Util.clean_str(self, _str, [["'","\\'"],])
return _str
def Upsert_table(self, dataset, exec_mode="EXEC"):
# del_col은 비교시 제외할 컬럼 리스트
# 샘플 데이터셋
# dataset = {
# "key": {"n_num": last_nbbs_detail["n_num"], "biz_num": last_nbbs_detail["biz_num"]},
# "table": "last_nbbs_detail",
# "value": last_nbbs_detail
# "type": "update" ==> update : 없으면 입력 있으면 변경분 수정 , insert : 없으면 입력 있으면 아무것도 안함, updateonly : 없으면 입력 안함, insertonly : 입력만 함
# "del_col": ["code", "reg_date", "ipchal_date"]
# "orderby": ""
# }
orderby = dataset["orderby"] if "orderby" in dataset else ""
whereis = dataset["whereis"] if "whereis" in dataset else ""
#키가 있는지 확인
if dataset["table"] != 'bid_content':
row = self.get_one_row(dataset["key"], dataset["table"], orderby)
elif dataset["table"] == 'bid_content':
try:
attchd_lnk = dataset["value"]['attchd_lnk'] if "attchd_lnk" in dataset['value'] else ""
except:
attchd_lnk = ''
# bid_html로 오류가 날수잇으므로 발주처를 따로 관리, 나라장터 물품건 따로 HTML 넣어주기 Or 나라장터, 조달청 물품건 HTML 저장.
# 나라장터 경우 첨부파일 없을 경우 bid_html 넣어주며, 공사/용역/물품 전부 포함. -> 차세대 나라장터 오픈 후 제거
# if whereis in ["17","03","96","81","91","44","21"] or ( whereis in ['01',"84","85","86"] and not attchd_lnk):
if whereis in ["17","03","96","81","91","44","21"]:
row = self.get_one_row(dataset["key"], dataset["table"], orderby,"bidid,bidcomment_mod,bidcomment,nbidcomment,bid_html, orign_lnk,s_orign_lnk,attchd_lnk,pur_lnk,bid_file,nbid_file,pur_goods,partition_seq")
else: #bid_html로 오류가 날수잇으므로 오류가 나는 발주처는 빼준다
row = self.get_one_row(dataset["key"], dataset["table"], orderby,"bidid,bidcomment_mod,bidcomment,nbidcomment,orign_lnk,s_orign_lnk,attchd_lnk,pur_lnk,bid_file,nbid_file,pur_goods,partition_seq")
#print("+++")
#print(row)
#print("+++")
if row is None or dataset["type"] in ["insertonly", "insert"]:
print("Upsert_table 입력모드 ==> dataset:", dataset)
if dataset["type"] not in ["updateonly"]:
print("self.Insert_table 실행")
# ================================================================
# 인서트 처리부분
if exec_mode == "EXEC":
self.Insert_table(dataset["value"], dataset["table"])
# ================================================================
else:
print("updateonly 입력안함")
else:
if dataset["type"] in ["update", "updateonly"]:
Etl = scraplib.Etl()
#print("Upsert_table 수정모드 ==> row:", row)
# 비교제외 컬럼 삭제 후 비교
diff = Etl.diff_array(Etl.del_key(row, dataset["del_col"]), Etl.del_key(dataset["value"], dataset["del_col"]))
print("diff :", diff["MOD"])
if len(diff["MOD"]) > 0:
_data = {}
for _key, _value in diff["MOD"].items():
# if _value["NEW"] > _value["OLD"]: #새로운 데이터가 이전 데이터보다 클경우에만 수정
_data[_key] = _value["NEW"]
if len(_data) > 0:
print("self.Update_table 실행 [변경분 수정]=========>", _data)
# ================================================================
# 업데이트 처리부분
if exec_mode == "EXEC":
self.Update_table(_data, dataset["key"], dataset["table"])
# ================================================================
else:
print("[insert 모드 입력 안함] ==> row:", row)
def get_one_row(self, _where, tb_nm, orderby="", rtn_col="*"):
where = ''
for key, value in _where.items():
if value != None:
if type(value) == dict:
value_type = list(value.keys())[0]
value_content = list(value.values())[0]
if value_type == "KEY":
where = where + " AND {key} = {value}".format(key=key, value=value_content)
elif value_type == "LIKE":
where = where + " AND {key} LIKE '{value}'".format(key=key, value=value_content)
elif value_type == "IN":
where = where + " AND {key} IN {value}".format(key=key, value=value_content)
else:
where = where + " AND {key} = '{value}'".format(key=key, value=value)
query = "SELECT {rtn_col} FROM {tb_nm} WHERE 1 {where} {orderby} limit 1".format(tb_nm=tb_nm, where=where, orderby=orderby, rtn_col=rtn_col)
print(query)
row = self.select_sql_to_dict(query)
#print("@@@")
#print("get_one_row : ", row)
#print("get_one_row2 : ")
if( len(row) > 0 ):
for key, value in row[0].items():#반환데이터 전처리
if type(value) == datetime:#날짜 형식은 포맷팅해서 텍스트로 변환
row[0][key] = str(value.strftime('%Y-%m-%d %H:%M:%S'))
# print("=======================================================================>", key, row[0][key], type(row[0][key]))
return row[0]
else:
return None
def ck_Exist_one(self, _where, tb_nm):
where = ''
for key, value in _where.items():
if value != None:
where = where + " AND {key} = '{value}'".format(key=key, value=value)
query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where)
cnt = self.select_sql_to_dict(query)[0]
if( cnt > 0 ):
return True
else:
return False
def ck_Exist_cnt(self, _where, tb_nm):
where = ''
for key, value in _where.items():
if value != None:
where = where + " AND {key} = '{value}'".format(key=key, value=value)
query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where)
cnt = self.select_sql_to_dict(query)[0]
return cnt
def ck_Exist_one_difftype(self, _where, tb_nm):
where = ''
for key, value in _where.items():
if value is not None:
if value['type'] == 'equal':
where = where + " AND {key} = '{value}'".format(key=key, value=value['data'])
elif value['type'] == 'text':
where = where + " AND {key} {value}".format(key=key, value=value['data'])
query = "SELECT COUNT(*) FROM {tb_nm} WHERE 1 {where}".format(tb_nm=tb_nm, where=where)
print(query)
cnt = self.select_sql_to_dict(query)
if( cnt > 0 ):
return True
else:
return False
def Insert_table(self, _data, tb_nm, rtn_fg=False):
logging.info("[scraplib.Insert_table][쿼리실행] start")
data_str = ''
for key, value in _data.items():
if value != None:
if data_str != '':
data_str = data_str + " , "
if value == "NOW()":
data_str = data_str + "`{key}` = {value}".format(key=key, value=self.remove_quote(value))
else:
data_str = data_str + "`{key}` = '{value}'".format(key=key, value=self.remove_quote(value))
query = "INSERT INTO {tb_nm} SET {data_str}".format(tb_nm=tb_nm, data_str=data_str)
try:
self.insert_sql(query)
except:
logging.error('insert query error : ', query)
pass
# print(query)
if rtn_fg == True:
try:
row = self.select_sql_to_dict("SELECT LAST_INSERT_ID() AS seq")
logging.info("=====================>row:", row)
return row[0]["seq"]
except:
pass
logging.info("[scraplib.Insert_table][쿼리실행] end")
def remove_quote(self, _val):
if type(_val) == str:
return _val.replace("'", "\\'")
else:
return _val
def Update_table(self, _data, _where, tb_nm):
print("[scraplib.Update_table][쿼리실행] start")
data_str = ''
for key, value in _data.items():
if value != None:
if data_str != '':
data_str = data_str + " , "
if value == "NOW()":
data_str = data_str + "`{key}` = {value}".format(key=key, value=self.remove_quote(value))
else:
data_str = data_str + "`{key}` = '{value}'".format(key=key, value=self.remove_quote(value))
where = ''
for key, value in _where.items():
if where != '':
where = where + " AND "
where = where + "{key} = '{value}'".format(key=key, value=self.remove_quote(value))
if data_str != '':
query = "UPDATE {tb_nm} SET {data_str} WHERE {where}".format(tb_nm=tb_nm, data_str=data_str, where=where)
self.process_sql(query)
# print(query)
print("[scraplib.Update_table][쿼리실행] end")
def Delete_table(self, key, value, tb_nm):
print("[scraplib.Delete_table][쿼리실행] start")
if key and value and tb_nm:
query = "DELETE FROM {tb_nm} WHERE {key}='{value}'".format(tb_nm=tb_nm, key=key, value=value)
self.process_sql(query)
print("[scraplib.Delete_table][쿼리실행] end")
#===============================================================================================================================================
# 셀레늄 크롬드라이버 기반 크롤러
#===============================================================================================================================================
class chrome_selenium:
# chromedriver = os.path.dirname(__file__)+DRIVER["CHROMEDRIVER_PATH"]
# iedriver = os.path.dirname(__file__) + DRIVER["IEDRIVER_PATH"]
iedriver = ''
chromedriver = ''
# os.environ["webdriver.chrome.driver"] = chromedriver
driver = ""
exec_type = "exec"
crxs=[]
def __init__(self, _exec_type, _crxs=[]):
stack = inspect.stack()
caller_frame = stack[1]
caller_filename = caller_frame.filename
caller_function = caller_frame.function
if(_exec_type=="IE"):
ie_options = Options()
ie_options.ignore_protected_mode_settings = True
self.driver = webdriver.Ie(executable_path=self.iedriver, options=ie_options)
else:
chrome_options = webdriver.ChromeOptions()
if(_exec_type=="exec"):
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-gpu")
chrome_options.set_capability('unhandledPromptBehavior', 'accept')
if "www_kapt_go_kr" in caller_filename and caller_function == 'bid_collect_process':
# kapt의 경우 첨부파일 경로를 가져오기위해 api 호출시 크로스도메인 오류를 뱉어내 재호출없이 네트워크 로그의 출력물을 판단하기 위해 아래 로직을 추가함
chrome_options.set_capability('goog:loggingPrefs', {"performance": "ALL"})
for crs in _crxs:
chrome_options.add_extension(crs)
#chrome_options.set_capability('unhandledPromptBehavior', 'accept')
self.exec_type = _exec_type
self.crxs = _crxs
self.driver = webdriver.Chrome(self.chromedriver, options=chrome_options)
# 해당 xpath 경로 프레임으로 포커스 이동
def switch_to_frame(self, XPATH):
self.driver.switch_to_default_content() # 프레임 초기화
self.driver.switch_to.frame(self.driver.find_element_by_xpath(XPATH)) # 프레임 이동
# 최상위 프레임으로 포커스 복귀
def switch_to_default_frame(self):
self.driver.switch_to_default_content() # 프레임 초기화
def rtn_elements_bs4(self, _selector):
try:
rtn = []
page_source = self.driver.page_source
soup = BeautifulSoup(page_source, 'html.parser')
listdata = soup.select(_selector)
for i in listdata:
rtn.append(Util.clean_str(self, i.text))
except Exception as e:
print("rtn_elements_bs4 : ", e)
rtn = []
return rtn
def rtn_elements_bs4_request(self, _selector, _attr="TEXT"):
try:
rtn = []
soup = BeautifulSoup(self.page_source_request, 'html.parser')
listdata = soup.select(_selector)
for i in listdata:
if _attr == "TEXT":
_tmp = i.text
else:
_tmp = i.attrs[_attr]
rtn.append(Util.clean_str(self, _tmp))
except Exception as e:
print("chrome_selenium rtn_elements_bs4_request : ", e)
rtn = []
return rtn
def rtn_elements_lxml_request(self, _selector, _attr="TEXT"):
try:
rtn = []
tree = lxml.html.fromstring(self.page_source_request)
# listdata = tree.findall(_selector)
listdata = list(tree.xpath(_selector))
# print(listdata)
for i in listdata:
if _attr == "TEXT":
_tmp = i.text_content().replace(" ", "")
else:
_tmp = i.attrib[_attr]
rtn.append(Util.clean_str(self, _tmp))
except Exception as e:
print("chrome_selenium rtn_elements_lxml_request : ", e)
rtn = []
return rtn
def rtn_elements_lxml_request_g2b(self, _selector, _type="XPATH", _attr="TEXT"):
try:
rtn = []
tree = lxml.html.fromstring(self.page_source_request)
# listdata = tree.findall(_selector)
listdata = list(tree.xpath(_selector))
if _attr == "TEXT_BR":
print("rtn_elements_lxml_request_g2b listdata", listdata, type(listdata))
for _idx, i in enumerate(listdata):
if _attr == "TEXT":
_tmp = "".join(i.text_content())
elif _attr == "TEXT_BR":
if _idx == 0:
tmp_sub = []
tmp_sub.append(Util.clean_str(self, i.text))
# print("===>", Util.clean_str(self, i.text))
for a in i:
tmp_sub.append(Util.clean_str(self, a.tail))
# print("===>", Util.clean_str(self, a.tail))
_tmp = "<BR>".join(tmp_sub)
print(_tmp)
else:
_tmp = i.attrib[_attr]
rtn.append(Util.clean_str(self, _tmp))
if len(rtn) == 0:
rtn.append("")
except Exception as e:
print("chrome_selenium rtn_elements_lxml_request : ", e)
rtn = []
return rtn
def rtn_elements_regex_g2b(self, _selector):
try:
rtn = re.findall(_selector, self.page_source_request)
except Exception as e:
print("chrome_selenium rtn_elements_regex_g2b : ", e)
rtn = None
return rtn
# 페이지 소스에서 객체정보 추출 _type : CSS, XPATH
def rtn_element(self, _selector, _type="CSS", _attr=""):
try:
if(_type=="CSS_CLICK"):
rtn = self.driver.find_element_by_css_selector(_selector).click()
elif(_type=="XPATH_CLICK"):
rtn = self.driver.find_element_by_xpath(_selector).click()
elif (_type == "CSS"):
rtn = Util.clean_str(self, self.driver.find_element_by_css_selector(_selector).text)
elif (_type == "CSS_TEXT"):
self.driver.find_element_by_css_selector(_selector).clear()
self.driver.find_element_by_css_selector(_selector).send_keys(_attr)
rtn = ""
elif (_type == "XPATH_TEXT"):
self.driver.find_element_by_xpath(_selector).clear()
self.driver.find_element_by_xpath(_selector).send_keys(_attr)
rtn = ""
else:# XPATH
if (_attr == "getText"):
rtn = self.driver.find_element_by_xpath(_selector).getText()
elif (_attr != ""):
rtn = self.driver.find_element_by_xpath(_selector).get_attribute(_attr)
else:
rtn = Util.clean_str(self, self.driver.find_element_by_xpath(_selector).text)
except:
print("_selector not found : ", _selector)
rtn = ""
#특수문자 print 할경우 오류방생으로 주석
#print("rtn_element [", _selector, "] :", rtn)
return rtn
def rtn_elements(self, _selector, _type="CSS", _attr=""):
try:
if(_type=="CSS"):
tmp = self.driver.find_elements_by_css_selector(_selector)
else:
tmp = self.driver.find_elements_by_xpath(_selector)
rtn = []
for t in tmp:
if(_attr!="" and _type=="XPATH"):
rtn.append(t.get_attribute(_attr))
# 2021-08-23 추가, 상단 XPATH 제거해도 되는지 재택끝나면 확인
elif (_attr != ""):
rtn.append(t.get_attribute(_attr))
else:
rtn.append(Util.clean_str(self, t.text))
except:
print("_selector not found : ", _selector)
rtn = []
return rtn
def callback(self, callback=None,_lists=None, driver=None, _alert=False):
if (driver == None):
driver = chrome_selenium(self.exec_type, self.crxs)
for list in _lists:
print(list["url"])
try:
if _alert == True: #alert 처리
driver.get_URL(list["url"], 5, '', True)
else:
driver.get_URL(list["url"])
callback(list["list"], driver)
except Exception as e:
print('Exception : callback오류 ', e)
"""
error_text = str(e)
if error_text.find("WinError") != -1:
os.exit(0)
"""
def callback_for_s2b(self, callback=None,_lists=None, driver=None):
if (driver == None):
driver = chrome_selenium(self.exec_type, self.crxs)
for list in _lists:
print(list["url"])
try:
driver.get_URL(list["url"])
callback(list["list"], list["url"], driver)
except Exception as e:
print('Exception : callback오류 ', e)
"""
error_text = str(e)
if error_text.find("WinError") != -1:
os.exit(0)
"""
def callback_post(self, callback=None,_lists=None, driver=None):
for list in _lists:
print(list["url"],list["list"]["link_post"])
try:
try:
PARAMS = json.loads(list["list"]["link_post"])
except:
PARAMS = {}
driver.get_URL_request(list["url"], PARAMS, "POST", "TEXT")
callback(list["list"], driver)
except Exception as e:
print('Exception : ', e)
def callback_get(self, callback=None, _lists=None, driver=None):
for _list in _lists:
try:
driver.get_URL_request(_list["url"], "", "GET", "TEXT")
callback(_list["list"], driver)
except Exception as e:
print('Exception : ', e)
def callback_xml(self, callback=None, _lists=None, driver=None, _alert=False):
print("callback_xml")
if (driver == None):
driver = chrome_selenium(self.exec_type, self.crxs)
for list in _lists:
print(list)
try:
# driver.get_URL(list["url"])
headers = {'Content-Type': 'application/xml'}
response = requests.post(list["payload_url"], data=list["payload_xml"], headers=headers)
response_text = response.text
callback(list, response_text, driver)
except Exception as e:
print('Exception : callback오류 ', e)
"""
error_text = str(e)
if error_text.find("WinError") != -1:
os.exit(0)
"""
def get_URL_request(self, URL, PARAMS, METHOD="GET", RTN_TYPE="JSON", HEADERS=None, COOKIES=None, TIMEOUT=30):
self.page_source_request = Util.get_URL(self, URL, PARAMS, METHOD, RTN_TYPE, HEADERS, COOKIES, TIMEOUT)
# url 의 경우 단일일경우 문자열 "", 경유가 필요한경우 ["","" ...] 문자열리스트
def get_URL(self, url, sec=5, id="", fg_alert=False):
if (type(url) is str):#문자열일경우
try:
self.driver.get(url)
if(fg_alert==True):
try:
alert = self.driver.switch_to_alert()
alert.accept()
except Exception as e:
pass
# print('Exception : ', e)
time.sleep(1)
wait = WebDriverWait(self.driver, sec)
if id != '':
wait.until(EC.element_to_be_clickable((By.ID, id)))
except:
pass
else:#리스트일경우
try:
for _url in url:
self.driver.get(_url)
if(fg_alert==True):
try:
alert = self.driver.switch_to_alert()
alert.accept()
except Exception as e:
print('Exception : ', e)
time.sleep(1)
except Exception as e:
print('Exception : ', e)
#마지막 페이지만 기다렸다 수집
wait = WebDriverWait(self.driver, sec)
if id != '':
wait.until(EC.element_to_be_clickable((By.ID, id)))
pass
body = self.driver.page_source
return body
def check_until_alert_show(self, sec=7) -> None:
"""
loop until every alert, confirm is dismissed in given second
:param sec: second that dev can set
:return: None
"""
wait = WebDriverWait(self.driver, sec)
try:
wait.until(EC.alert_is_present())
confirm = self.driver.switch_to.alert
confirm.dismiss()
self.check_until_alert_show()
except Exception as TimeoutException:
pass
#===============================================================================================================================================
# 유틸리티 펑션
#===============================================================================================================================================
class Util:
# 네이트온 메세지 발송
def send_msg(self, msg, id="LOG"):
urls = {
"ERROR": "https://teamroom.nate.com/api/webhook/6585e4a8/BGnMXKey8HUTwQROkmIaDiDK",
"LOG": "https://teamroom.nate.com/api/webhook/6585e4a8/TapsrGOgqoSO8tfqCLwLpDqZ",
# 토지주택공사
"LBC": "https://teamroom.nate.com/api/webhook/6585e4a8/a4OWV4umnnNJImGiZWtkCU6Z",
"LBMC": "https://teamroom.nate.com/api/webhook/6585e4a8/a4OWV4umnnNJImGiZWtkCU6Z",
# 한국철도공사
"KRL_B": "https://teamroom.nate.com/api/webhook/6585e4a8/IfAZig74jUBJWyZxtGRG2TML",
"KRL_SB": "https://teamroom.nate.com/api/webhook/6585e4a8/IfAZig74jUBJWyZxtGRG2TML",
# 국가철도공단
"KRBC": "https://teamroom.nate.com/api/webhook/6585e4a8/cFH2bM8GleIbHESCKUGZrT1i",
# 국제협력단
"KOICA_B": "https://teamroom.nate.com/api/webhook/6585e4a8/M9ZTkpFS3TAtw9JqqOBIYvRk",
# 한국마사회
"KRABA": "https://teamroom.nate.com/api/webhook/6585e4a8/MtTy4Weo6km2B18A4BMehh3e",
# 전자통신연구원
"ETRI_B": "https://teamroom.nate.com/api/webhook/6585e4a8/lRNMkeMC8xSFjtzSr5Dq0UOy",
#도로공사
"EBC": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv",
"EBS": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv",
"EBI": "https://teamroom.nate.com/api/webhook/6585e4a8/eug9PTbaJJJvXkvr8mhe2Mxv",
#국토정보공사
"LX_B": "https://teamroom.nate.com/api/webhook/6585e4a8/KU8Np7Klx11XvNhnVZ0Sa52X",
#강원랜드
"K_LAND_B": "https://teamroom.nate.com/api/webhook/6585e4a8/QgqK5vPDid0bzdSuS289Z70D",
# 국방부
"D2B_B": "https://teamroom.nate.com/api/webhook/6585e4a8/s3FG0Tz2mk28nkp7jeNbAujZ",
# 수자원공사
"KWATER_B": "https://teamroom.nate.com/api/webhook/6585e4a8/NwL7xpqYiVlHcutUn9fErMX8",
# 나라장터 직접
"G2B_B": "https://teamroom.nate.com/api/webhook/6585e4a8/rSYzYnbAt3uCB5aambYDLDiZ",
}
try:
try:
url = urls[id]
except:
url = urls["LOG"]
msg = u"content=" + msg + " | " + str(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
txt = msg.encode('utf-8')
payload = txt
headers = {
'content-type': "application/x-www-form-urlencoded",
'cache-control': "no-cache",
'postman-token': "6176cbc2-3050-4c1f-2d2a-4c25ae984e3f"
}
response = requests.request("POST", url, data=payload, headers=headers)
print(response.text)
except Exception as ex:
print("send_msg() ERROR :", ex)
#==============================================================================
##텍스트 클린징 함수 _str 은 문자열("asdf") 또는 문자열리스트(["asdf","qwer","zxcv"])가 올수 있다
##
def clean_str(self, _str, cus_pattern=[]):
patterns=[#여기에 리스트형태로 리플레이스 패턴 추가 ["원래패턴","변경패턴"],
["",""],
["",""],
["&#8228;","."],
["\xa0","-"],
["\u2024","."],
["\u2e31","."],
["\u25ef", ""],
["\u2027","."],
["\uff62","["],
["\uff63","]"],
["\u2013","-"],
["\u2610",""],
["\u2205","Ø"],
["\r",""],
["\t",""],
["\n",""],
["","."],
["\u25e6", ""],
["\xa9", ""],
["\ufeff", ""],
["\u200b", ""],
["\u2219", ""],
["\u274d", ""],
["\u2782", ""],
["\u2981", ""],
["\u3007", ""],
["\u25fc", ""],
["\ufffd", ""],
["\u25ba", ""],
["\u20de", ""],
["\u302c", ""],
["\u0223", ""],
["\u22c5", ""],
["\u1100", ""],
["\u1161", ""],
["\u11bc", ""],
["\u1102", ""],
["\u11b7", ""],
["\u110b", ""],
["\u116f", ""],
["\u11ab", ""],
["\u1112", ""],
["\u116d", ""],
["\u1109", ""],
["\u1165", ""],
["\u1175", ""],
["\u2022", ""],
["\u2613", ""],
["\u27a1", ""],
["\U00010a50",""],
["\u25b9",""],
["\u2023",""],
["\u0228",""],
["\xb5",""],
["\u0387", ""],
["\u3693", ""],
["\u2715", ""],
["\u2714", ""],
["\u25a2", ""],
["\u1110", ""],
["\u1169", ""],
["\u11a8", ""],
["\u1107", ""],
["\u1173", ""],
["\u1111", ""],
["\u1167", ""],
["\u11af", ""],
["\u110e", ""],
["\u1103", ""],
["\u1162", ""],
["\u110c", ""],
["\u116e", ""],
["\uff65", ""],
["\uf09e", ""],
["\u24c7", ""],
["\u2002", ""],
["\u26aa", ""],
["\U000f0854", ""],
["\u2e33", ""],
["\U000f02ea", ""],
["\u2218",""],
["\u0278",""],
["\u2022", ""],
["\u022f", ""],
["\u24fd", ""],
["\u302e", ""],
["\u0368", ""],
["\u301c", "~"],
["\u02d1", "~"],
["\u21e8", "->"],
["\u25a2", ""],
["\u231f", ""],
["\u2780", ""],
["\u119e", ""],
["\u20a9", ""],
["\u25cc", ""],
["\uf022", ""],
["\u0301", ""],
["\u1168", ""],
["\u1163", ""],
["\u1166", ""],
["\u2215", ""],
["\u231c", ""],
["\U000f02ef", ""],
["\uf0a0", ""],
["\u2014", ""],
["\u301a", ""],
["\u301b", ""],
["\uf028", ""],
["\u30fb", ""],
["\uf076", ""],
["\u25aa", ""],
["\u1104", ""],
["\u2776", ""],
["\u2777", ""],
["\u2613", ""],
["\u2000", ""],
["\u25b8", ""],
["\u2219", ""],
["\u2012", ""],
["\u233d", ""],
["\u8f66", ""],
["\u65f6", ""],
["\u95f4", ""],
["\u27f6", ""],
["\uf0a6", ""],
["\u21db", ""],
["\u2783", ""],
["\u2784", ""],
["\u2785", ""],
["\u2010", "-"],
["\U0001d635", ""],
["\u206d", ""],
["\u279f", ""],
["\u2d41", ""],
["\u278a", ""],
["\u278b", ""],
["\u278c", ""],
["\u27f9", ""],
["\u2035", ""],
["\u02dc", ""],
["\u2053", ""],
["\u301e", ""],
]
# ["원래패턴", "변경패턴"],
if(len(cus_pattern)>0):#커스텀 패턴이 있는경우 커스텀패턴 적용
patterns = cus_pattern
if (type(_str) is str):#문자열일경우
try:
_str = _str.strip()
for pattern in patterns:
_str=_str.replace(pattern[0],pattern[1])
except:
pass
else:#리스트일경우
try:
_tmp = []
for _s in _str:
_s = _s.strip()
for pattern in patterns:
_s = _s.replace(pattern[0], pattern[1])
_tmp.append(_s)
_str = _tmp
except:
pass
return _str
#==============================================================================
##None 을 공백으로
def ck_None(self, _str):
if(_str is None):
_str = ""
return _str
#==============================================================================
## 문서에서 패턴을 매칭하여 반환
## 샘플 : aa = spilt_rex_doc(text, "(var\sbidRateCode\s=\s')((\d|\.){2,6})(';)", 2)
## idx 는 정규식 그룹중 추출할 인덱스
def spilt_rex_doc(self, _doc, _pattern, _idx):
for para in _doc.splitlines():
para_tmp = para.strip()
print("para_tmp:",para_tmp)
line = re.match(_pattern, para_tmp)
print("line:",line)
if line is not None:
rtn = re.sub(_pattern, r"\{idx}".format(idx=_idx), para_tmp)
return rtn
return None
#==============================================================================
## URL 직접수집
## URL : 수집주소, PARAMS : 파라미터, METHOD : 전송방법(POST,GET), RTN_TYPE : 리턴타입(JSON,TEXT,PLAIN) ==> 필수파라미터
## HEADERS=None, COOKIES=None, TIMEOUT=3 ==> 선택파라미터
# response.content # 응답 데이터(binary형식 내용,이미지파일 등)
# response.text # 응답 데이터(텍스트형식 내용, 텍스트 파일에 씀)
# response.json # 응답 데이터 JSON형태
# response.url # 해당 url 반환
# response.status_code # 응답 상태코드 (200 이면 성공)
# response.headers # 응답 헤더의 {Key:Value} 형식의 딕셔너리 자료반환
# response.encoding = 'utf-8' # 응답 객체에 인코딩 지정
# HEADERS = {'User-Agent' : ('Mozilla/5.0 (Windows NT 10.0;Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'),'Referer': 'http://www.igunsul.net/'}
def get_URL(self, URL, PARAMS, METHOD="GET", RTN_TYPE="JSON", HEADERS=None, COOKIES=None, TIMEOUT=60):
#print("URL:", re.findall("^https:", URL))
try:
if len(re.findall("^https:", URL)) > 0 or len(re.findall("^http://www.d2b.go.kr", URL)) > 0:#ssl 통신일경우 옵션 변경, 2024.10.18 국방부 SSL인증 임시조치
VAL_verify = False
else:
VAL_verify = True
except:
VAL_verify = True
if RTN_TYPE == "JSON":
NORMAL_PARAMS = ''
JSON_PARAMS = PARAMS
else:
NORMAL_PARAMS = PARAMS
JSON_PARAMS = ''
if(METHOD=="GET"):
if(HEADERS is not None and COOKIES is not None):
response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is not None and COOKIES is None):
response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is None and COOKIES is not None):
response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
# 나라장터 API 수집시 json 수집임에도 param을 json으로 넘겨주면 데이터를 못가져온다. json이 아닌 param으로 호출하도록 함
elif len(re.findall("apis.data.go.kr", URL)) > 0:
response = requests.get(URL, params=JSON_PARAMS, json='', timeout=TIMEOUT, verify=VAL_verify)
else:
response = requests.get(URL, params=NORMAL_PARAMS, json=JSON_PARAMS, timeout=TIMEOUT, verify=VAL_verify)
else: #POST
if(HEADERS is not None and COOKIES is not None):
response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is not None and COOKIES is None):
response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is None and COOKIES is not None):
response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
else:
response = requests.post(URL, data=NORMAL_PARAMS, json=JSON_PARAMS, timeout=TIMEOUT, verify=VAL_verify)
'''
if(METHOD=="GET"):
if(HEADERS is not None and COOKIES is not None):
response = requests.get(URL, params=PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is not None and COOKIES is None):
response = requests.get(URL, params=PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is None and COOKIES is not None):
response = requests.get(URL, params=PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
else:
response = requests.get(URL, params=PARAMS, timeout=TIMEOUT, verify=VAL_verify)
else: #POST
if(HEADERS is not None and COOKIES is not None):
response = requests.post(URL, data=PARAMS, headers=HEADERS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is not None and COOKIES is None):
response = requests.post(URL, data=PARAMS, headers=HEADERS, timeout=TIMEOUT, verify=VAL_verify)
elif(HEADERS is None and COOKIES is not None):
response = requests.post(URL, data=PARAMS, cookies=COOKIES, timeout=TIMEOUT, verify=VAL_verify)
else:
response = requests.post(URL, data=PARAMS, timeout=TIMEOUT, verify=VAL_verify)
'''
#print("status_code:", response.status_code)
#print("response.text:", response.text)
#print("response.headers:", response.headers)
if(response.status_code == 200):
if(RTN_TYPE=="JSON"):
RTN = json.loads(response.text)
elif(RTN_TYPE=="TEXT"):
RTN = response.text
else:
RTN = response
else:
RTN = response.status_code
return RTN
#===============================================================================================================================================
# ETL 펑션
#===============================================================================================================================================
class Etl:
dbconn_BI = dbconn()
Util = Util()
array_diff_list = []
DIC_G2BPartCode = {}
def g2b_init(self):
self.DIC_G2BPartCode = self.G2BPartCode()
def del_key(self, _dic, _keys):# 딕셔너리 컬럼 삭제
for _col in _keys:
try:
del _dic[_col]
except:
pass
return _dic
def array_diff(self, a, b, path=None): # a : 기준값, b : 변경값 딕셔너리간 다른값 찾기
keys = a.keys()# a 를 기준으로 확인
for key in keys:
if self.getValue(a, key, {}) == self.getValue(b, key, {}):# 하위 값이 같으면 패스
pass
else: #하위값이 다른경우 내용을 풀어 확인한다
_path = key if path == None else path + "|" + key
if type(self.getValue(a, key, {})) == dict and type(self.getValue(b, key, {})) == dict:
self.array_diff(self.getValue(a, key, {}), self.getValue(b, key, {}), _path)
else:
self.array_diff_list.append([_path, self.getValue(a, key, {}), self.getValue(b, key, {})])
_path = path
def diff_array(self, a, b): # 변경데이터 교집합, 차집합 작성
try:
RTN = {}
a = a if type(a) == dict else json.loads(a, strict=False)
b = b if type(b) == dict else json.loads(b, strict=False)
self.array_diff_list = [] # 데이터 초기화
self.array_diff(a, b)
A = self.array_diff_list
self.array_diff_list = [] # 데이터 초기화
self.array_diff(b, a)
B = self.array_diff_list
dic_intersection = {}
dic_Acomplement = {} # A - B 차집합
dic_Bcomplement = {} # B - A 차집합
for Aval in A:
FG = False
for Bval in B:
if Aval[0]==Bval[0]:
FG = True
break
else:
FG = False
if FG == True:
dic_intersection[Aval[0]] = {"OLD": Aval[1], "NEW": Aval[2]}
else:
dic_Acomplement[Aval[0]] = {"OLD": Aval[1], "NEW": Aval[2]}
for Bval in B:
FG = False
for Aval in A:
if Bval[0]==Aval[0]:
FG = True
break
else:
FG = False
if FG == True:
pass
else:
dic_Bcomplement[Bval[0]] = {"NEW": Bval[1], "OLD": Bval[2]}
RTN["MOD"] = dic_intersection # 데이터가 수정된 경우
RTN["REMOVE"] = dic_Acomplement # 키가 사라진 경우
RTN["ADD"] = dic_Bcomplement # 키가 추가된 경우
return RTN
except Exception as e:
print(e)
def blank_none_zero_To_0(self, _param):
try:
rtn = float(_param)
except:
rtn = 0
return rtn
def change_detect_DB(self, _diff, syscollect, new): #데이터 변경시 테이블 자동반영
# ==================================================================================================================
# 기초금액, A값, 순공사원가 변경 허용할 발주처 세팅
# 기초금액 변경 확인 추가 -> ([53] 국가철도공산, 2021.7)
# A값 변경 확인 추가 -> ([53] 국가철도공산, 2021.7)
# 순공사원가 변경 확인 추가 -> ([05] 토지주택공사, 2021.7.21)
#
# ==================================================================================================================
print("===========================================================================")
print("=====================change_detect_DB===>start=============================")
allow_basic = ["52","53","91","08","05","04","03"]#기초금액 변경 확인 발주처 whereis
allow_basic_type2 = ["10"] # 기초금액 변경 확인시 투찰율+사정율+난이도계수 함께 업데이트 발주처 whereis
allow_premiumList = ["01","53","08","10","04"]#A값 변경 확인 발주처 whereis
allow_bid_const_cost_list = ["01","05","08","10","04"]#순공사원가 변경 확인 발주처 whereis
allow_bidcomment = ["91"] #전자통신연구원 참가자격 서류
allow_notice = ["10"] #공지사항 업데이트
# ==================================================================================================================
new = new if type(new) == dict else json.loads(new, strict=False)
bidid = syscollect["bidid"] # 파이프라인 detail_list_select 에서 데이터 추가
if bidid == "" or bidid is None:
print("===========================================================================")
print("=====================change_detect_DB======================================")
print("=====================bidid is None=========================================")
print("===========================================================================")
return None
if syscollect["a.whereis"] is not None:
whereis = syscollect["a.whereis"] # 파이프라인 detail_list_select 에서 데이터 추가
else:
whereis = None
# ==============================================================
# 변경감지에서 쌓인 로그를 임시로 남긴다. 정상적으로 쌓이는지 확인차
# 코드와 변경내용은 하단에서 종류별로 선언 후 insert
# ==============================================================
bid_notify = {}
bid_notify['case'] = 'Notify'
bid_notify['bidid'] = bidid
bid_notify['confirm'] = 'N'
bid_notify['uptime'] = "NOW()"
# =============================================================================================================
# A값 변경 확인 premiumList
if whereis in allow_premiumList:
ck_premiumList = ["cost1", "cost2", "cost3", "cost4", "cost5", "cost6", "cost7", "cost8", "cost_total"]# A값 확인 컬럼 리스트
mod_premiumList = list(filter(lambda x: x is not None, list(map(lambda x: x if "premiumList|" + x in _diff["MOD"] else None, ck_premiumList))))# 변경된 컬럼 리스트
if len(mod_premiumList) > 0:# 변경된 값에 A값이 있는지 확인
_premiumList = {}
for key in mod_premiumList:
_premiumList[key] = _diff["MOD"]["premiumList|" + key]["NEW"]# 변경된 값 딕셔너리 작성
query = "SELECT `cost1`,`cost2`,`cost3`,`cost4`,`cost5`,`cost6`,`cost7`,`cost8`,`cost_total` FROM `premiumList` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid)# 기존 입력된 데이터 가져오기
row = self.dbconn_BI.sql_exec(query, "DS")
if len(row) > 0:# 이미 입력된 건이 있는경우 변경 비교
pass #추후 자동변경될시에 작업
# _premiumList_mod = {}
# for key in _premiumList:
# if _premiumList[key] != row[0][key]:# 저장데이터와 수집데이터가 다른것만 업데이트
# _premiumList_mod[key] = _premiumList[key]
#self.dbconn_BI.Update_table(_premiumList_mod, {'bidid': bidid}, 'premiumList')
#변경완료 후 알림이 필요하면 알림 작성
# JB A값 점검페이지 로그 남기기
new_cost_total = new["premiumList"].get("cost_total", 0) if "premiumList" in new else 0
_data_change_log_cost_a = {}
for key in _premiumList:
if _premiumList[key] != row[0][key]: # 저장데이터와 수집데이터가 다른 경우
_data_change_log_cost_a['data_type'] = 'cost_a_change'
_data_change_log_cost_a['bidid'] = bidid
_data_change_log_cost_a['notinum'] = syscollect["dkey"]
_data_change_log_cost_a['whereis'] = whereis
_data_change_log_cost_a['prev_data'] = row[0]['cost_total']
_data_change_log_cost_a['change_data'] = new_cost_total
_data_change_log_cost_a['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(_data_change_log_cost_a, 'data_change_log')
# A값 항목별로 쌓을 필요가 없어서 1개 항목만 달라도 쌓고 종료
break
elif whereis != '01': # 입력된 건이 없는 경우 입력 - 나라장터는 하단에서 별도 처리 함
_premiumList["bidid"] = bidid
_premiumList["writedt"] = "NOW()"
self.dbconn_BI.Insert_table(_premiumList, 'premiumList')
query = "SELECT `state_a` FROM bid_key WHERE bidid = '{bidid}'".format(bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
if len(row) > 0:
if row[0]["state_a"] == "I":
update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid)
self.dbconn_BI.sql_exec(update_query, "U")
bid_notify['code'] = '0102'
bid_notify['note'] = '[{dcode}] {dkey} - A값이 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey'])
self.dbconn_BI.Insert_table(bid_notify, 'bid_notify')
# =============================================================================================================
# 순공사원가 변경확인 bid_const_cost_list
if whereis in allow_bid_const_cost_list:
ck_bid_const_cost_list = ["const_cost", "material", "labor", "expense", "tax", "const_cost_nbid", "updatedt_nbid"] # 순공사원가 확인 컬럼 리스트
mod_bid_const_cost_list = list(filter(lambda x: x is not None, list(map(lambda x: x if "bid_const_cost_list|" + x in _diff["MOD"] else None, ck_bid_const_cost_list)))) # 변경된 컬럼 리스트
if len(mod_bid_const_cost_list) > 0: # 변경된 값에 순공사원가 있는지 확인
_bid_const_cost_list = {}
for key in mod_bid_const_cost_list:
_bid_const_cost_list[key] = _diff["MOD"]["bid_const_cost_list|" + key]["NEW"] # 변경된 값 딕셔너리 작성
query = "SELECT `const_cost`,`material`,`labor`,`expense`,`tax`,`const_cost_nbid`,`updatedt_nbid` FROM `bid_const_cost_list` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid) # 기존 입력된 데이터 가져오기
row = self.dbconn_BI.sql_exec(query, "DS")
if len(row) > 0: # 이미 입력된 건이 있는경우 변경 비교
pass # 추후 자동변경될시에 작업
# 순공사원가 점검페이지 로그 남기기
_data_change_log = {}
for key in _bid_const_cost_list:
if _bid_const_cost_list[key] != row[0][key]: # 저장데이터와 수집데이터가 다른 경우
_data_change_log['data_type'] = 'const_cost'
_data_change_log['bidid'] = bidid
_data_change_log['notinum'] = syscollect["dkey"]
_data_change_log['whereis'] = whereis
_data_change_log['prev_data'] = row[0]['const_cost']
_data_change_log['change_data'] = _bid_const_cost_list[key]
_data_change_log['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(_data_change_log, 'data_change_log')
# 입력하는 부분
# _bid_const_cost_list_mod = {}
# for key in _bid_const_cost_list:
# if _bid_const_cost_list[key] != row[0][key]: # 저장데이터와 수집데이터가 다른것만 업데이트
# _bid_const_cost_list_mod[key] = _bid_const_cost_list[key]
# _bid_const_cost_list_mod["updatedt"] = "NOW()"
# self.dbconn_BI.Update_table(_bid_const_cost_list_mod, {'bidid': bidid}, 'bid_const_cost_list')
else: # 입력된 건이 없는 경우 입력
_bid_const_cost_list["bidid"] = bidid
_bid_const_cost_list["writedt"] = "NOW()"
# self.dbconn_BI.Insert_table(_bid_const_cost_list, 'bid_const_cost_list')
pass
bid_notify['code'] = '0103'
bid_notify['note'] = '[{dcode}] {dkey} - 순공사원가가 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey'])
# self.dbconn_BI.Insert_table(bid_notify, 'bid_notify')
#=============================================================================================================
# 기초금액 변경 확인
# print("=====================기초금액1=====================")
# print(whereis)
# print(syscollect)
if whereis in allow_basic:#whereis 값이 리스트에 허용된 발주처만 적용
print("=====================기초금액2=====================")
if "bid_key|basic" in _diff["MOD"]:# 변경된 값에 기초금액이 있는지 확인
basic = self.blank_none_zero_To_0(_diff["MOD"]["bid_key|basic"]["NEW"]) # 새로 수집된 기초금액
print("=====================기초금액3=====================")
if basic > 0:
print("=====================기초금액4=====================")
query = "SELECT `basic`, `bidtype`, `opt` FROM bid_key WHERE bidid = '{bidid}' AND constdt > NOW() limit 1".format(bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
print("=====================기초금액5=====================")
if len(row) > 0:# 공고가 조건(constdt > NOW() AND state = 'Y' AND bidproc = 'B')에 부합할때만 처리
if self.blank_none_zero_To_0(row[0]["basic"]) == 0: #기존 입력된 기초금액이 공백이거나 0일때 수정
print("=====================기초금액6=====================")
_data = {}
_data["basic"] = basic
_data["opt"] = row[0]["opt"] + 512
self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_key')
print("=====================기초금액7=====================")
bid_notify['code'] = '0101'
bid_notify['note'] = '[{dcode}] {dkey} - 기초금액이 업데이트 되었습니다.'.format(dcode=syscollect['dcode'], dkey=syscollect['dkey'])
self.dbconn_BI.Insert_table(bid_notify, 'bid_notify')
# =============================================================================================================
# 기초금액 변경 확인시 투찰율+사정율+난이도계수+A값+순공사원가 함께 업데이트 (국방부는 기초금액이 나올경우 해당항목들이 같이 나와서 같이 업데이트 해줘야 함)
if whereis in allow_basic_type2: # whereis 값이 리스트에 허용된 발주처만 적용
print("국방부 기초금액 업데이트 체크")
print(_diff)
_diff["MOD"].update(_diff["ADD"])
if "bid_key|basic" in _diff["MOD"]: # 변경된 값에 기초금액이 있는지 확인
print("1")
basic = self.blank_none_zero_To_0(_diff["MOD"]["bid_key|basic"]["NEW"]) # 새로 수집된 기초금액
new = new if type(new) == dict else json.loads(new, strict=False)
pct = new["bid_key"]["pct"] if "pct" in new["bid_key"] else '' # 투찰율
yegarng = new["bid_value"]["yegarng"] if "yegarng" in new["bid_value"] else '' # 예가범위
lvcnt = new["bid_value"]["lvcnt"] if "lvcnt" in new["bid_value"] else '' # 난이도계수
bidcomment = new["bid_content"]["bidcomment"] if "bidcomment" in new["bid_content"] else '' # 토목기초금액, 건설기초금액이 나오면 자격조건에 텍스트로만 넣어준다.
if basic > 0:
print("2")
query = "SELECT `basic`, `bidtype`, `opt` FROM bid_key WHERE bidid = '{bidid}' AND constdt > NOW() limit 1".format(bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
if len(row) > 0: # 공고가 조건(constdt > NOW() AND state = 'Y' AND bidproc = 'B')에 부합할때만 처리
if self.blank_none_zero_To_0(row[0]["basic"]) == 0: # 기존 입력된 기초금액이 공백이거나 0일때 수정
_data = {}
_data["basic"] = basic
_data["opt"] = row[0]["opt"] + 512
if pct: _data["pct"] = pct
self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_key')
# bid_value 업데이트
if yegarng or lvcnt:
_data = {}
if yegarng: _data["yegarng"] = yegarng
if lvcnt: _data["lvcnt"] = lvcnt
self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_value')
# bid_content 업데이트
if bidcomment.find('기초금액') >= 0: # 1. 토목기초금액, 건설기초금액 내용이 자격조건에 세팅되었을때 -> 해당금액들은 따로 컬럼이 없고 자격조건에 텍스트로만 넣어주고 있음
bidcomment_query = "SELECT `bidcomment` FROM bid_content WHERE bidid = '{bidid}'".format(bidid=bidid)
bidcomment_row = self.dbconn_BI.sql_exec(bidcomment_query, "DS")
if bidcomment_row[0]["bidcomment"].find('기초금액') < 0: # 2. 기존 자격조건에 토목기초금액, 건설기초금액 내용이 없을때
if bidcomment_row[0]["bidcomment"]: bidcomment = bidcomment_row[0]["bidcomment"] + '\n' + bidcomment
_data = {}
_data["bidcomment"] = bidcomment
self.dbconn_BI.Update_table(_data, {'bidid': bidid}, 'bid_content')
# 기초금액 공개시 A값 같이 나와서 업데이트 필요
cost1 = new["premiumList"]["cost1"] if "cost1" in new["premiumList"] else ''
cost2 = new["premiumList"]["cost2"] if "cost2" in new["premiumList"] else ''
cost3 = new["premiumList"]["cost3"] if "cost3" in new["premiumList"] else ''
cost4 = new["premiumList"]["cost4"] if "cost4" in new["premiumList"] else ''
cost5 = new["premiumList"]["cost5"] if "cost5" in new["premiumList"] else ''
cost6 = new["premiumList"]["cost6"] if "cost6" in new["premiumList"] else ''
cost8 = new["premiumList"]["cost8"] if "cost8" in new["premiumList"] else ''
if cost1 or cost2 or cost3 or cost4 or cost5 or cost6 or cost8:
premium_query = "SELECT * FROM premiumList WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid)
premium_check = self.dbconn_BI.sql_exec(premium_query, "S_one")
if premium_check is None or premium_check == '':
insert_query = "INSERT INTO premiumList SET cost1 = '{cost1}', cost2 = '{cost2}', cost3 = '{cost3}', cost4 = '{cost4}', cost5 = '{cost5}', cost6 = '{cost6}', cost8 = '{cost8}', bidid = '{bidid}'".format(cost1=cost1, cost2=cost2, cost3=cost3, cost4=cost4, cost5=cost5, cost6=cost6, cost8=cost8,bidid=bidid)
self.dbconn_BI.sql_exec(insert_query, "I")
if syscollect["ext_info2"] != "공개수의":
update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid)
self.dbconn_BI.sql_exec(update_query, "U")
else:
update_query = "UPDATE premiumList SET cost1 = '{cost1}', cost2 = '{cost2}', cost3 = '{cost3}', cost4 = '{cost4}', cost5 = '{cost5}', cost6 = '{cost6}', cost8 = '{cost8}' WHERE bidid = '{bidid}'".format(cost1=cost1, cost2=cost2, cost3=cost3, cost4=cost4, cost5=cost5, cost6=cost6, cost8=cost8,bidid=bidid)
self.dbconn_BI.sql_exec(update_query, "U")
query = "SELECT `state_a` FROM bid_key WHERE bidid = '{bidid}'".format(bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
if len(row) > 0:
if row[0]["state_a"] == "I":
update_query = "UPDATE bid_key SET state_a = 'Y' WHERE bidid = '{bidid}'".format(bidid=bidid)
self.dbconn_BI.sql_exec(update_query, "U")
# 기초금액 공개시 순공사원가 같이 나와서 업데이트 필요
const_cost = new["bid_const_cost_list"]["const_cost"] if "const_cost" in new["bid_const_cost_list"] else ''
expense = new["bid_const_cost_list"]["expense"] if "expense" in new["bid_const_cost_list"] else ''
labor = new["bid_const_cost_list"]["labor"] if "labor" in new["bid_const_cost_list"] else ''
material = new["bid_const_cost_list"]["material"] if "material" in new["bid_const_cost_list"] else ''
tax = new["bid_const_cost_list"]["tax"] if "tax" in new["bid_const_cost_list"] else ''
if const_cost or expense or labor or material or tax:
const_cost_query = "SELECT bidid FROM bid_const_cost_list WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid)
const_cost_check = self.dbconn_BI.sql_exec(const_cost_query, "S_one")
if const_cost_check:
update_query = "UPDATE bid_const_cost_list SET material = '{material}', labor = '{labor}', expense = '{expense}', tax = '{tax}', const_cost = '{const_cost}' WHERE bidid = '{bidid}'".format(material=material, labor=labor, expense=expense, tax=tax, const_cost=const_cost,bidid=bidid)
self.dbconn_BI.sql_exec(update_query, "U")
else:
insert_query = "INSERT INTO bid_const_cost_list SET material = '{material}', labor = '{labor}', expense = '{expense}', tax = '{tax}', const_cost = '{const_cost}', bidid = '{bidid}'".format(material=material, labor=labor, expense=expense, tax=tax, const_cost=const_cost,bidid=bidid)
self.dbconn_BI.sql_exec(insert_query, "I")
# =============================================================================================================
# 공지사항 업데이트
if whereis in allow_notice:
if "bid_notice_memo|notice_memo" in _diff["MOD"]:
prev_notice_query = "SELECT notice_memo FROM bid_notice_memo WHERE bidid = '{bidid}' ORDER BY reg_date LIMIT 1".format(bidid=bidid)
prev_notice = self.dbconn_BI.sql_exec(prev_notice_query, "S_one")
updateCheck = False
# 이전 공지사항이 있으면 비교하여 다를경우, 공지사항 입력된게 없을경우
if prev_notice is not None and prev_notice != '':
# DB에 있는 공지사항내용과 수집된 공지사항 내용이 다르면
if prev_notice != _diff["MOD"]["bid_notice_memo|notice_memo"]["NEW"]:
updateCheck = True
else: updateCheck = True
if updateCheck == True:
_data = {}
_data["notice_memo"] = _diff["MOD"]["bid_notice_memo|notice_memo"]["NEW"]
_data["writer_name"] = _diff["MOD"]["bid_notice_memo|writer_name"]["NEW"]
_data["status"] = _diff["MOD"]["bid_notice_memo|status"]["NEW"]
_data["reg_date"] = 'NOW()'
_data["bidid"] = bidid
self.dbconn_BI.Insert_table(_data, 'bid_notice_memo')
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# ======================================================
trigger_table = "log_bid_trigger_y"
if syscollect["ext_info"] == 'pur': trigger_table = "log_bid_trigger_pur"
self.trigger_insert(bidid, trigger_table)
# insert_query = "INSERT INTO {trigger_table} SET bidid='{bidid}', uptime= NOW()".format(trigger_table=trigger_table, bidid=bidid)
# self.dbconn_BI.sql_exec(insert_query, "I")
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# ======================================================
# =============================================================================================================
# 조달청 기초금액, A값, 순공사원가, 공지사항 업데이트
if syscollect["dcode"] in ["G2B_B"]:
print("=================>조달청 입찰 업데이트 start")
print("_diff :", _diff)
if "bidtype" in syscollect:
trigger_flag = 'N'
ck_bid_value_MOD = self.extract_diff(_diff["MOD"], "bid_value", "NEW") # bid_value
_diff_bid_value_MOD = _diff["ADD"] if len(ck_bid_value_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력
bid_value_dataset = self.extract_diff(_diff_bid_value_MOD, "bid_value", "NEW") # _diff 에서 해당 테이블 데이터만 추출
if len(bid_value_dataset) > 0: # yegarng 변경시
if 'yegarng' in bid_value_dataset and bid_value_dataset['yegarng'] is not None and bid_value_dataset['yegarng'] != '':
_bid_value_dataset = {
"key": {"bidid": bidid},
"table": "i2.bid_value",
"value": {"yegarng": bid_value_dataset['yegarng']},
"type": "updateonly",
"orderby": "",
"del_col": [],
}
print("i2.bid_value yegarng :", bid_value_dataset['yegarng'])
trigger_flag = 'Y'
self.dbconn_BI.Upsert_table(_bid_value_dataset, "EXEC")
if 'lvcnt' in bid_value_dataset and bid_value_dataset['lvcnt'] is not None and bid_value_dataset['lvcnt'] != '':
_bid_value_dataset = {
"key": {"bidid": bidid},
"table": "i2.bid_value",
"value": {"lvcnt": bid_value_dataset['lvcnt']},
"type": "updateonly",
"orderby": "",
"del_col": [],
}
print("i2.bid_value lvcnt :", bid_value_dataset['lvcnt'])
trigger_flag = 'Y'
self.dbconn_BI.Upsert_table(_bid_value_dataset, "EXEC")
ck_bid_key_MOD = self.extract_diff(_diff["MOD"], "bid_key", "NEW") #bid_key
_diff_bid_key_MOD = _diff["ADD"] if len(ck_bid_key_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력
bid_key_dataset = self.extract_diff(_diff_bid_key_MOD, "bid_key", "NEW") # _diff 에서 해당 테이블 데이터만 추출
if len(bid_key_dataset) > 0: #기초금액 변경시
if "basic" in bid_key_dataset and bid_key_dataset['basic'] is not None:
bid_key_query = "SELECT * FROM bid_key WHERE bidid = '{bidid}' ORDER BY writedt LIMIT 1".format(bidid=bidid)
bid_key_row = self.dbconn_BI.sql_exec(bid_key_query, "DS")[0]
if self.blank_none_zero_To_0(bid_key_row["basic"]) == 0: #입력된 기초금액이 없을때
if bid_key_row["opt"] is not None:
prev_opt_arr = self.conv_bin(bid_key_row["opt"])
else:
prev_opt_arr = []
prev_opt_arr.append(9)
mod_opt = self.pow_sum(list(set(prev_opt_arr)))
_bid_key_dataset = {
"key": {"bidid": bidid},
"table": "i2.bid_key",
"value": {"basic": bid_key_dataset['basic'], "opt": mod_opt},
"type": "updateonly",
"orderby": "",
"del_col": [],
}
print("i2.bid_key basic :", bid_key_dataset['basic'])
self.dbconn_BI.Upsert_table(_bid_key_dataset, "EXEC")
else:
if bid_key_dataset['basic'] is not None and bid_key_dataset['basic'] != '' and int(bid_key_row["basic"]) != int(bid_key_dataset['basic']):
code_pattern = [
{"pattern": {"P1": "con", }, "value": "0031"},
{"pattern": {"P1": "ser", }, "value": "0032"},
{"pattern": {"P1": "pur", }, "value": "0033"},
]
notify_code=self.mapping_pattern_value(code_pattern, syscollect['bidtype'], '')
#기초금액이 입력되있는데, 변경된경우
_notify_dataset = {
"key": {"bidid": bidid, "code": notify_code},
"table": "i2.bid_notify",
"value": {
"case": "Notify",
"code": notify_code,
"bidid": bidid,
"note": str(bid_key_row["basic"]) + "|" + str(bid_key_dataset['basic']),
"confirm": "N",
"uptime": "NOW()",
},
"type": "update",
"orderby": "",
"del_col": [],
}
print("i2.notify basic :", bid_key_dataset['basic'])
#잠ㅅㅣ빼놈
self.dbconn_BI.Upsert_table(_notify_dataset, "EXEC")
ck_premium_MOD = self.extract_diff(_diff["MOD"], "premiumList", "NEW") #premiumList
_diff_premium_MOD = _diff["ADD"] if len(ck_premium_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력
premium_dataset = self.extract_diff(_diff_premium_MOD, "premiumList", "NEW") # _diff 에서 해당 테이블 데이터만 추출
if len(premium_dataset) > 0: #A값 변경시
print("여기")
print(premium_dataset)
if "cost1" in premium_dataset and premium_dataset['cost1'] is not None and premium_dataset['cost1'] != '' or \
"cost2" in premium_dataset and premium_dataset['cost2'] is not None and premium_dataset['cost2'] != '' or \
"cost3" in premium_dataset and premium_dataset['cost3'] is not None and premium_dataset['cost3'] != '' or \
"cost4" in premium_dataset and premium_dataset['cost4'] is not None and premium_dataset['cost4'] != '' or \
"cost5" in premium_dataset and premium_dataset['cost5'] is not None and premium_dataset['cost5'] != '' or \
"cost6" in premium_dataset and premium_dataset['cost6'] is not None and premium_dataset['cost6'] != '' or \
"cost7" in premium_dataset and premium_dataset['cost7'] is not None and premium_dataset['cost7'] != '' or \
"cost8" in premium_dataset and premium_dataset['cost8'] is not None and premium_dataset['cost8'] != '' or \
"cost_total" in premium_dataset and premium_dataset['cost_total'] is not None and premium_dataset['cost_total'] != '' or \
"direct_labor_cost" in premium_dataset and premium_dataset['direct_labor_cost'] is not None and premium_dataset['direct_labor_cost'] != '':
trigger_flag = 'Y'
premium_dataset['bidid'] = bidid
premium_dataset['writedt'] = "NOW()"
g2b_premium_total_cost_query = "SELECT `cost_total` FROM `premiumList` WHERE bidid = '{bidid}' limit 1".format(bidid=bidid) # 기존 입력된 데이터 가져오기
g2b_before_premium_total_cost = self.dbconn_BI.sql_exec(g2b_premium_total_cost_query, "S_one")
_diff_premium_dataset = {
"key": {"bidid": bidid},
"table": "i2.premiumList",
"value": premium_dataset,
"type": "update",
"orderby": "",
"del_col": ['writedt'],
}
print("i2.premiumList :", premium_dataset)
self.dbconn_BI.Upsert_table(_diff_premium_dataset, "EXEC")
#총값이 있을경우에만 Y로 업데이트
if syscollect["bidtype"] in ["con"] and premium_dataset['cost_total'] is not None and premium_dataset['cost_total'] != '':
_diff_state_a_dataset = {
"key": {"bidid": bidid},
"table": "i2.bid_key",
"value": {"state_a": "Y"},
"type": "updateonly",
"orderby": "",
"del_col": [],
}
print("i2.bid_key state_a : Y")
self.dbconn_BI.Upsert_table(_diff_state_a_dataset, "EXEC")
g2b_after_premium_total_cost = self.dbconn_BI.sql_exec(g2b_premium_total_cost_query, "S_one")
# 3. G2B A값(premiumList) 변경시 공지사항 등록
# 비포, 애프터값이 있고 서로 다르면 공지
Etl.change_cost_notice_memo(self, g2b_before_premium_total_cost, g2b_after_premium_total_cost, bidid, "cost_a", whereis)
ck_const_cost_MOD = self.extract_diff(_diff["MOD"], "bid_const_cost_list", "NEW") # bid_const_cost_list
_diff_const_cost_MOD = _diff["ADD"] if len(ck_const_cost_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력
const_cost_dataset = self.extract_diff(_diff_const_cost_MOD, "bid_const_cost_list", "NEW") # _diff 에서 해당 테이블 데이터만 추출
if len(const_cost_dataset) > 0: # 순공사원가 변경시
trigger_flag = 'Y'
bid_key_query = "SELECT * FROM bid_key WHERE bidid = '{bidid}' ORDER BY writedt LIMIT 1".format(bidid=bidid)
bid_key_row = self.dbconn_BI.sql_exec(bid_key_query, "DS")[0]
g2b_const_cost_query = "SELECT const_cost FROM bid_const_cost_list WHERE bidid ='{bidid}' LIMIT 1".format(bidid=bidid)
g2b_before_const_cost = self.dbconn_BI.sql_exec(g2b_const_cost_query, "S_one")
self.confirm_const_cost_list_new(bid_key_row, syscollect['bidproc'], const_cost_dataset, bidid)
g2b_after_const_cost = self.dbconn_BI.sql_exec(g2b_const_cost_query, "S_one")
# 4. G2B 순공사원가(bid_const_cost_list) 변경시 공지사항 등록
# 비포, 애프터값이 있고 서로 다르면 공지
Etl.change_cost_notice_memo(self, g2b_before_const_cost, g2b_after_const_cost, bidid, "const_cost", whereis)
ck_bid_notice_memo_MOD = self.extract_diff(_diff["MOD"], "bid_notice_memo", "NEW") # bid_notice_memo
_diff_bid_notice_memo_MOD = _diff["ADD"] if len(ck_bid_notice_memo_MOD) == 0 else _diff["MOD"] # 첫입력일경우 _diff["ADD"] 데이터을 입력
bid_notice_memo_dataset = self.extract_diff(_diff_bid_notice_memo_MOD, "bid_notice_memo", "NEW") # _diff 에서 해당 테이블 데이터만 추출
if len(bid_notice_memo_dataset) > 0: # 공지사항이 수집된 경우에만 실행한다.
trigger_flag = 'Y'
bid_notice_memo_dataset['bidid']=bidid
dataset = {
"key": {"bidid": bidid},
"table": "i2.bid_notice_memo",
"value": bid_notice_memo_dataset,
"type": "update",
"orderby": "",
"del_col": ["writedt"],
}
print("i2.bid_notice_memo :", dataset)
self.dbconn_BI.Upsert_table(dataset, "EXEC")
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# =====================================================
if trigger_flag == 'Y':
if syscollect["bidtype"] in ["pur"]:
self.trigger_insert(bidid, "log_bid_trigger_pur")
else:
self.trigger_insert(bidid, "log_bid_trigger_y")
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# ======================================================
# =============================================================================================================
# 조달청 낙찰공지사항 업데이트
if syscollect["dcode"] in ["G2B_R"]:
print("=================>조달청 낙찰공지사항 업데이트 start")
print("_diff :", _diff)
if "bidtype" in syscollect:
ck_MOD = self.extract_diff(_diff["MOD"], "modify_nbbs", "NEW")# 이미 입력되어 있는 경우 수정사항이 있는지 확인. => 입력되지 않은 경우 {} 값을 리턴,
_diff_modify_nbbs = _diff["ADD"] if len(ck_MOD) == 0 else _diff["MOD"]# 첫입력일경우 _diff["ADD"] 데이터을 입력
modify_nbbs_dataset = self.extract_diff(_diff_modify_nbbs, "modify_nbbs", "NEW")# _diff 에서 해당 테이블 데이터만 추출
print("modify_nbbs_dataset :", modify_nbbs_dataset)
# if len(new["modify_nbbs"]) > 4:#공지사항이 수집된 경우에만 실행한다.
if len(modify_nbbs_dataset) > 0: # 공지사항이 수집된 경우에만 실행한다.
dataset = {
"key": {"bidid": bidid},
"table": "i2.modify_nbbs",
"value": modify_nbbs_dataset,
"type": "update",
"orderby": "",
"del_col": ["writedt"],
}
print("i2.modify_nbbs :", dataset)
self.dbconn_BI.Upsert_table(dataset, "TEST")
# self.dbconn_BI.Upsert_table(dataset, "EXEC")
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# ======================================================
# if syscollect["bidtype"] in ["pur"]:
# self.trigger_insert(bidid, "log_bid_trigger_pur")
# else:
# self.trigger_insert(bidid, "log_bid_trigger_y")
# ======================================================
# 추후에 통합하여 트리거로 대체후 제거해야함.
# ======================================================
#=============================================================================================================
#참가자격서류 변경 확인 allow_bidcomment
if whereis in allow_bidcomment: #whereis 값이 리스트에 허용된 발주처만 적용
if "bid_content|bidcomment" in _diff["MOD"]:# 변경된 값에 기초금액이 있는지 확인
bidcomment = _diff["MOD"]["bid_content|bidcomment"]["NEW"].strip() # 새로 수집된 bidcomment
query = "SELECT `bidcomment`,`bidid` FROM bid_content WHERE bidid = '{bidid}' limit 1".format(bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
if bidcomment != '':
if len(row) > 0:
if row[0]["bidcomment"] is None or row[0]["bidcomment"].strip() == '' or row[0]["bidcomment"].strip() == '* 공고 원문을 참조하시기 바랍니다. *' : #기존 입력된 bidcomment가 없을떄
_data = {}
_data["bidid"] = bidid
_data["whereis"] = whereis
_data["syscode"] = syscollect['dcode']
_data["notinum"] = syscollect['dkey']
if syscollect['dkey_ext']:
_data["notinum_ex"] = syscollect['dkey_ext']
_data["tableName"] = 'bid_content'
_data["fieldName"] = 'bidcomment'
_data["state"] = 'N'
_data["writedt"] = "NOW()"
#self.dbconn_BI.Insert_table(_data, 'modification_request_list')
print("===========================================================================")
print("=====================change_detect_DB===>end===============================")
#######################################################################
########################순공사 원가 #####################################
#######################################################################
# 낙찰제외금액 분류함수
def confirm_const_cost_list_new(self, bid_key_arr, proc, const_cost_arr, bidid):
print("여안탐?")
iu_log_query = ""
if proc == 'S' or proc == 'F':
self.upsert_const_cost_nbid(const_cost_arr, bidid)
else:
print("입찰")
presum = 0
if 'presum' in bid_key_arr and 'basic' in bid_key_arr:
if bid_key_arr['presum'] is not None and bid_key_arr['presum'] != '':
presum = bid_key_arr['presum']
if (bid_key_arr['presum'] is None or bid_key_arr['presum'] == '') and (
bid_key_arr['basic'] is not None and bid_key_arr['basic'] != ''):
try:
int_basic = int(bid_key_arr['basic'])
except Exception as e:
int_basic = 0
presum = int_basic * 0.9
if int(presum) <= 10000000000:
self.upsert_const_cost_bid(bid_key_arr, const_cost_arr, bidid)
def upsert_const_cost_bid(self, bid_key_arr, const_cost_arr, bidid):
print("입찰 순공사 입력")
if "material" not in const_cost_arr:
const_cost_arr['material'] = None
if "labor" not in const_cost_arr:
const_cost_arr['labor'] = None
if "expense" not in const_cost_arr:
const_cost_arr['expense'] = None
if "tax" not in const_cost_arr:
const_cost_arr['tax'] = None
if "const_cost" not in const_cost_arr:
const_cost_arr['const_cost'] = None
# 국방부
# 소스단쪽에서 따로 처리됨
"""
if bid_key_arr['whereis'] == '10':
material = 0
labor = 0
expense = 0
tax = 0
if const_cost_arr['material'] is not None:
material = const_cost_arr['material']
if const_cost_arr['labor'] is not None:
labor = const_cost_arr['labor']
if const_cost_arr['expense'] is not None:
expense = const_cost_arr['expense']
if const_cost_arr['tax'] is not None:
tax = const_cost_arr['tax']
self_const_cost = material + labor + expense + tax
if self_const_cost != 0:
const_cost_arr['const_cost'] = material + labor + expense + tax
"""
if const_cost_arr['material'] is not None or const_cost_arr['labor'] is not None or \
const_cost_arr['expense'] is not None or const_cost_arr['tax'] is not None or \
const_cost_arr['const_cost'] is not None:
updsert_data = {
"key": {"bidid": bidid},
"table": "bid_const_cost_list",
"type": "update",
"value": {"bidid": bidid, "material": const_cost_arr['material'], "labor": const_cost_arr['labor'],
"expense": const_cost_arr['expense'], "tax": const_cost_arr['tax'],
"const_cost": const_cost_arr['const_cost'], "writedt": "NOW()", "updatedt": "NOW()"},
"del_col": ["updatedt", "writedt", "updatedt_nbid"]
}
self.dbconn_BI.Upsert_table(updsert_data)
def upsert_const_cost_nbid(self, const_cost_arr, bidid):
if "const_cost_nbid" in const_cost_arr:
if const_cost_arr['const_cost_nbid'] is not None and const_cost_arr['const_cost_nbid'] != '':
updsert_data = {
"key": {"bidid": bidid},
"table": "bid_const_cost_list",
"type": "update",
"value": {"bidid": bidid, "const_cost_nbid": const_cost_arr['const_cost_nbid'],
"updatedt_nbid": "NOW()"},
"del_col": ["updatedt", "writedt", "updatedt_nbid"]
}
self.dbconn_BI.Upsert_table(updsert_data)
print("@")
# diff 딕셔너리에서 특정 테이블 정보만 추출하여 테이블컬럼을 키로 가지는 딕셔너리 반환
# _type 는 NEW, OLD 를 구분
def extract_diff(self, _diff, _table, _type="NEW"):
RTN = {}
# print("extract_diff _diff :", _diff)
for key, val in _diff.items():
tmp = key.split("|")
if tmp[0] == _table:
RTN[tmp[1]] = val[_type]
return RTN
def trigger_insert(self, bidid, tablenm):
qry = "INSERT INTO {tablenm} SET `bidid`='{bidid}', `uptime`=NOW()".format(bidid=bidid, tablenm=tablenm)
self.dbconn_BI.sql_exec(qry, "I")
def change_detect(self, old, new, syscollect): # old : 이전수집데이터(json str), new : 새수집데이터(json str), syscollect(dic)
try:
# ==============================================
# 데이터 체크 및 업데이트
# ==============================================
# 52 -> 기존 제외조건, 07 -> dkey_ext 에 텍스트가 들어가고 있어서 제외시킴
pass_whereis= ["52", "07", "05", "53", "03"]
err_diff_flag = 'Y'
if syscollect["dcode"] in ["G2B_R"]:
logging.info("pass - 나라장터 낙찰건 - G2B_R")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요
elif syscollect["dcode"] in ["ERC"]:
logging.info("pass - 나라장터 낙찰건 - ERC")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요
elif syscollect["dcode"] in ["ERS"]:
logging.info("pass - 나라장터 낙찰건 - ERS")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요
elif syscollect["dcode"] in ["ERI"]:
logging.info("pass - 나라장터 낙찰건 - ERI")#나라장터 낙찰건은 일단빼논다. 나중에 일괄작업하여 입낙찰구분필요
else:
# 국방부데이터 데이터가 잘못 들어가는 현상이 있어 예외처리, 하면서 타 발주처도 같이 처리
# 오류있을때 로그에 쌓아준다.
new_data = new if type(new) == dict else json.loads(new, strict=False)
old_data = old if type(old) == dict else json.loads(old, strict=False)
if new_data['bid_key']['whereis'] in pass_whereis:
# print("pass52")
pass
else:
if new_data['bid_key']['notinum_ex'] is None:
new_data['bid_key']['notinum_ex'] = ''
if old_data['bid_key']['notinum'] != new_data['bid_key']['notinum']:
err_diff_flag = 'N'
# print("잘못된 데이터")
if syscollect["dkey_ext"] is not None and syscollect["dkey_ext"] != '':
if old_data['bid_key']['notinum_ex'] != new_data['bid_key']['notinum_ex']:
err_diff_flag = 'N'
# print("잘못된 데이터2")
if err_diff_flag == 'Y':
# 국방부는 공고번호에 조달청공고번호까지 넣어준다.
bcc_dkey = str(syscollect["dkey"]) + "|" + str(syscollect["dkey_ext"]) if syscollect["dcode"] == "D2B_B" else syscollect["dkey"]
#bcc_dkey = syscollect["dkey"]
#복수공고 공고번호 만들어주기
if syscollect["dcode"] == "G2B_B" and syscollect["dkey_ext"] is not None and syscollect["dkey_ext"] != "":
bcc_dkey = str(syscollect["dkey"]) + "|" + str(syscollect["dkey_ext"])
query = "SELECT * FROM bid_change_check WHERE notinum = '{notinum}' AND syscode = '{syscode}' limit 1".format(notinum=bcc_dkey, syscode=syscollect["dcode"])
row = self.dbconn_BI.sql_exec(query, "DS")
_diff = self.diff_array(old, new)
RTN = _diff
if len(row) > 0:
# 비교 제외 데이터 삭제
try:
#예외처리(불필요한 컬럼제외)함수 만들면 여기다 적용한다. 김영주
#국방부
if new_data['bid_key']['whereis'] == '10':
if "bid_key|constnm" in _diff['MOD']:
del _diff['MOD']['bid_key|constnm'] #공고명이 바뀜
del _diff['MOD']['bid_key|writedt']
except Exception as e:
print("change_detect : ", e)
# 변경분이 있는경우
if len(_diff['ADD'])+len(_diff['MOD'])+len(_diff['REMOVE']) > 0:
print("변경분이 있는경우")
print(row[0])
_diff_str = json.dumps(_diff, ensure_ascii=False, default=self.json_default, sort_keys=True) # dic to str
update_data = {}
update_data['prevdt'] = row[0]["updatedt"]
update_data['updatedt'] = 'NOW()'
update_data['state'] = 'Y'
update_data['diff'] = _diff_str
self.dbconn_BI.Update_table(update_data, {'seq': row[0]["seq"]}, 'bid_change_check')
bcs_cnt_select_query = "SELECT count(*) as cnt FROM bid_change_source WHERE notinum = '{notinum}' AND syscode = '{syscode}' limit 1".format(notinum=bcc_dkey, syscode=syscollect["dcode"])
bcs_cnt_select = self.dbconn_BI.sql_exec(bcs_cnt_select_query, "DS")
# 데이터가 오류가 있는듯, 계속 쌓이는 공고들로인해 조건 걸어둠, 같은공고번호로 3건이상은 안쌓이도록
if bcs_cnt_select[0]["cnt"] < 4:
bcs_data = {}
bcs_data['notinum'] = bcc_dkey
bcs_data['syscode'] = syscollect["dcode"]
bcs_data['content'] = new
bcs_data['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(bcs_data, 'bid_change_source')
#==================================================================================================================
# 변경처리 => A값, 순공사원가, 기초금액 비어 있는경우 자동입력
# ==================================================================================================================
self.change_detect_DB(_diff, syscollect, new)
# ==================================================================================================================
# 변경내용 자동수정 (설정한 데이터 변경시 자동으로 공고 수정)
# ==================================================================================================================
self.change_detect_DB_etc(_diff, syscollect)
msg ="[{dcode}] notinum : {notinum}, _diff : {_diff}".format(dcode=syscollect["dcode"], notinum=syscollect["dkey"], _diff=_diff_str.replace("&","_"))
#발주처별 팀룸 (2023.10.27 사용하지 않는것으로 판단되어 우선 주석)
#self.Util.send_msg(msg, syscollect["dcode"])
#전체 로그 팀룸 (2023.10.27 사용하지 않는것으로 판단되어 우선 주석)
#self.Util.send_msg(msg)
# ==================================================================================================================
else:
print("전과 동일")
#첫 수집일 경우
else:
print("첫 수집일 경우")
insert_data = {}
insert_data['notinum'] = bcc_dkey
insert_data['syscode'] = syscollect["dcode"]
insert_data['prevdt'] = 'NOW()'
insert_data['updatedt'] = 'NOW()'
insert_data['writedt'] = 'NOW()'
insert_data['state'] = 'N'
self.dbconn_BI.Insert_table(insert_data, 'bid_change_check')
bcs_data = {}
bcs_data['notinum'] = bcc_dkey
bcs_data['syscode'] = syscollect["dcode"]
bcs_data['content'] = new
bcs_data['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(bcs_data, 'bid_change_source')
else:
#공고번호 및 공고번호_ex 가 매칭이 안된경우
if syscollect['dkey_ext'] is None or syscollect['dkey_ext'] == '':
syscollect['dkey_ext'] = ''
_err_diff_data_dataset = {
"key": {"whereis": new_data['bid_key']['whereis'],
"dkey": syscollect['dkey'],
"dkey_ext": syscollect['dkey_ext'],
"notinum": new_data['bid_key']['notinum'],
"notinum_ex": new_data['bid_key']['notinum_ex'],
},
"table": "err_diff_data",
"value": {"whereis": new_data['bid_key']['whereis'],
"dkey": syscollect['dkey'],
"dkey_ext": syscollect['dkey_ext'],
"notinum": new_data['bid_key']['notinum'],
"notinum_ex":new_data['bid_key']['notinum_ex'],
#"crawl_data_view_old": syscollect['crawl_data_view'],
#"crawl_data_view_old": new_data,
"writedt": "NOW()",
},
"type": "insertonly",
"orderby": "",
"del_col": [],
}
self.dbconn_BI.Upsert_table(_err_diff_data_dataset, "EXEC")
RTN = {}
except Exception as ex:
RTN = {}
print("change_detect Exception : ", ex)
change_detect_error = {}
change_detect_error['notinum'] = syscollect['dkey']
change_detect_error['notinum_ex'] = syscollect['dkey_ext']
change_detect_error['whereis'] = syscollect['whereis']
change_detect_error['err_code'] = ex
change_detect_error['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(change_detect_error, 'change_detect_error')
return RTN
def change_detect_DB_etc(self, _diff, syscollect):
print("[change_detect_DB_etc] 들어옴")
diff_arr = {
# 91전자통신연구원 패턴
'91' : {
'bid_content|bidcomment': {
'tableName': 'bid_content',
'fieldName': 'bidcomment',
'action': "update", #update : 데이터 업데이트, modify : 정정데이터 쌓기
'field_empty' : 'N', #저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크
'exlusion_pattern': [ #제외조건 데이터가 해당값일 경우엔 무시하고 저장
'^\* 공고 원문을 참조하시기 바랍니다. \*$',
'^$',
],
},
'bid_key|constnm': {
'tableName': 'bid_key',
'fieldName': 'constnm',
'action': "modify", # update : 데이터 업데이트, modify : 정정데이터 쌓기
'field_empty': 'N', # 저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크
'exlusion_pattern': [ # 제외조건
],
},
},
#08 도로공사 패턴
'08': {
'bid_key|constnm': {
'tableName': 'bid_key',
'fieldName': 'constnm',
'action': "modify", # update : 데이터 업데이트, modify : 정정데이터 쌓기
'field_empty': 'N', # 저장데이터 빈값 허용 여부 Y: 빈값체크x N: 빈값체크
'exlusion_pattern': [ # 제외조건
],
},
},
}
whereis = syscollect['whereis']
bidid = syscollect["bidid"]
if whereis in diff_arr:
for key, value in enumerate(diff_arr[whereis]):
if value in _diff["MOD"]: # 변경된 값에 키데이터가 있을때
updtate_data = _diff["MOD"][value]["NEW"].strip()
if diff_arr[whereis][value]['field_empty'] == 'Y' or updtate_data != '':
insert_yn = 'N' #저장은 기본적으로 하지 않는다.
#제외조건이 있을경우
if len(diff_arr[whereis][value]['exlusion_pattern']) > 0:
query = "SELECT {fieldName}, bidid FROM {tableName} WHERE bidid='{bidid}' limit 1 ".format(fieldName=diff_arr[whereis][value]['fieldName'], tableName=diff_arr[whereis][value]['tableName'], bidid=bidid)
row = self.dbconn_BI.sql_exec(query, "DS")
print(diff_arr[whereis][value]['exlusion_pattern'])
#db데이터 확인
if len(row) > 0: # 저장된 데이터값 없으면 패스
for value2 in diff_arr[whereis][value]['exlusion_pattern']:
field_tmp = row[0]["{fieldName}".format(fieldName=diff_arr[whereis][value]['fieldName'])]
print("테이블값 : "+field_tmp)
field_tmp = '' if field_tmp is None else field_tmp # table None 일때 공백처리
print("조건값 : "+value2)
if len(re.findall(value2, field_tmp)) > 0: #기존데이터가 exlusion_pattern에 걸리는 패턴일경우 저장하지 않는다.
print("저장")
#테이블 데이터와 저장데이터 조건이 맞는경우 저장한다.
insert_yn = 'Y'
else:
#제외조건이 없을경우 저장
insert_yn = 'Y'
if insert_yn == 'Y':
if diff_arr[whereis][value]['action'] == 'update':
self.insert_modification_request_list(syscollect, diff_arr[whereis][value]['tableName'], diff_arr[whereis][value]['fieldName'])
elif diff_arr[whereis][value]['action'] == 'modify':
self.self_mod_module(syscollect, updtate_data)
print("[change_detect_DB_etc] 나감")
def insert_modification_request_list(self, data, tableName, fieldName):
print("[insert_modification_request_list] 들어옴")
_data = {}
_data["bidid"] = data['bidid']
_data["whereis"] = data['whereis']
_data["syscode"] = data['dcode']
_data["notinum"] = data['dkey']
if data['dkey_ext']:
_data["notinum_ex"] = data['dkey_ext']
else:
_data["notinum_ex"] = ''
_data["tableName"] = tableName
_data["fieldName"] = fieldName
_data["state"] = 'N'
_data["writedt"] = "NOW()"
self.dbconn_BI.Insert_table(_data, 'modification_request_list')
def self_mod_module(self, syscollect,updtate_data):
self.mod_table_insert(syscollect, updtate_data)
self.mod_syscollect_insert(syscollect, updtate_data)
def mod_table_insert(self, syscollect,updtate_data): # 김영주
whereis = syscollect['whereis']
dcode = syscollect["dcode"]
notinum = syscollect["dkey"]
notinum_ex = syscollect["dkey_ext"] if syscollect["dkey_ext"] is not None else None
notinum_ex_q=''
if notinum_ex is not None:
notinum_ex_q="and dkey_ext='{dkey_ext}'".format(dkey_ext=notinum_ex)
# mod_data_table 쌓기
tmp_sql = "SELECT * FROM mod_data_table WHERE dcode='{dcode}' and dkey='{dkey}' {notinum_ex_q} and mod_data='{mod_data}' and whereis='{whereis}' order by uptime desc limit 1" \
.format(dcode=dcode, dkey=notinum, mod_data=updtate_data, whereis=whereis, notinum_ex_q=notinum_ex_q)
tmp_row = self.dbconn_BI.sql_exec(tmp_sql, "DS")
if len(tmp_row) > 0:
print("pass")
else:
# sys_collect 정보가져오기
sys_sql = "SELECT stats,rebid_no,seq FROM sys_collect WHERE dcode='{dcode}' and dkey='{dkey}' order by uptime desc limit 1".format(
dcode=dcode, dkey=notinum)
sys_row = self.dbconn_BI.sql_exec(sys_sql, "DS")
# 기본 정정차수는 1
mod_chasu = 1
if len(tmp_row) > 0:
# 이전 정정차수가 mod_data_table에 있을경우 정정차수를 업데이트 해준다
mod_chasu = int(tmp_row[0]['mod_chasu']) + 1
insert_data_arr = {}
insert_data_arr['dkey'] = notinum
insert_data_arr['dkey_ext'] = notinum_ex
insert_data_arr['dcode'] = dcode
insert_data_arr['stats'] = sys_row[0]['stats']
insert_data_arr['rebid_no'] = sys_row[0]['rebid_no']
insert_data_arr['mod_chasu'] = mod_chasu
insert_data_arr['mod_text'] = updtate_data
#insert_data_arr['prevcode'] = row['prevcode'] --사용x
#insert_data_arr['updatecode'] = row['updatecode'] --사용x
insert_data_arr['whereis'] = whereis
self.dbconn_BI.Insert_table(insert_data_arr, "mod_data_table")
update_data_arr = {}
update_data_arr['state'] = 'D'
update_data_arr['updatedt'] = "NOW()"
self.dbconn_BI.Update_table(update_data_arr, {'seq': tmp_row['seq']}, 'bid_change_check')
def mod_syscollect_insert(self, syscollect, updtate_data):
whereis = syscollect['whereis']
dcode = syscollect["dcode"]
notinum = syscollect["dkey"]
notinum_ex = syscollect["dkey_ext"] if syscollect["dkey_ext"] is not None else None
notinum_ex_q = ''
if notinum_ex is not None:
notinum_ex_q = 'and dkey_ext={dkey_ext}'.format(dkey_ext=notinum_ex)
mod_sql = "SELECT * FROM mod_data_table WHERE dcode='{dcode}' and dkey='{dkey}' {notinum_ex_q} and mod_data='{mod_data}' whereis='{whereis}' and state ='N' "\
.format(dcode=dcode, dkey=notinum, mod_data=updtate_data, whereis=whereis, notinum_ex_q=notinum_ex_q)
mod_row = self.dbconn_BI.sql_exec(mod_sql, "DS")
if len(mod_row) > 0:
sys_sql = "SELECT * FROM sys_collect WHERE dcode='{dcode}' and dkey='{dkey}' and stats='{stats}' and rebid_no ='{rebid_no}' order by uptime desc limit 1" \
.format(dcode=mod_row['dcode'], dkey=mod_row['dkey'], stats=mod_row['stats'], rebid_no=mod_row['rebid_no'])
sys_res = self.dbconn_BI.sql_exec(sys_sql, "DS")
insert_arr = {}
insert_arr['dkey'] = mod_row['dkey']
insert_arr['dkey_ext'] = mod_row['dkey_ext']
insert_arr['dcode'] = mod_row['dcode']
insert_arr['stats'] = "자체정정" + mod_row['mod_chasu'] + ""
insert_arr['rebid_no'] = mod_row['rebid_no']
insert_arr['link'] = sys_res[0]['link']
insert_arr['ext_info1'] = sys_res[0]['ext_info1']
insert_arr['ext_info2'] = sys_res[0]['ext_info2']
insert_arr['proc'] = 'new'
if dcode == 'KNOCBA':
insert_arr['link_post'] = sys_res[0]['link_post']
self.dbconn_BI.Insert_table(insert_arr, "sys_collect")
update_data_arr = {}
update_data_arr['state'] = 'I'
update_data_arr['updatedt'] = "NOW()"
self.dbconn_BI.Update_table(update_data_arr, {'seq': mod_row['seq']}, 'bid_change_check')
# ============================================================
# "CREATE TABLE `modification_request_list` (
# `seq` int(6) NOT NULL AUTO_INCREMENT,
# `bidid` char(24) NOT NULL COMMENT '입찰번호(일련번호-차수-재입찰번호-분류번호)',
# `whereis` char(2) DEFAULT NULL COMMENT '공고게시기관코드',
# `syscode` char(12) DEFAULT NULL COMMENT '수집시스템코드',
# `notinum` char(32) NOT NULL COMMENT '공고번호',
# `notinum_ex` char(32) NOT NULL,
# `tableName` varchar(30) NOT NULL COMMENT '테이블이름',
# `fieldName` varchar(30) NOT NULL COMMENT '컬럼이름',
# `writedt` datetime DEFAULT NULL COMMENT '요청등록일',
# `updatedt` datetime DEFAULT NULL COMMENT '처리시간',
# `state` char(1) DEFAULT NULL COMMENT '처리유무',
# PRIMARY KEY (`seq`)
# ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=euckr COMMENT='특정 데이터 업데이트 요청 리스트'" \
# 입력페이지에서 해당 테이블에 업데이트할 정보를 쌓는다.
# 테이블이름과, 컬럼이름을 매칭해서 데이터를 업데이트 한다.
# ============================================================
def modification_request_list(self, new, syscollect):
print("[modification_request_list] start")
tableName = syscollect["tableName"]
fieldName = syscollect["fieldName"]
seq = syscollect["modification_seq"]
bidid = syscollect["bidid"]
new_parser = new if type(new) == dict else json.loads(new, strict=False)
self.dbconn_BI.Update_table({fieldName: new_parser[tableName][fieldName]}, {'bidid': bidid}, tableName)
self.dbconn_BI.Update_table({'state': 'Y', 'updatedt': 'NOW()'}, {'seq': seq}, 'modification_request_list')
print("[modification_request_list] end")
def blank_None(self, _str):
if _str == "":
return None
else:
return _str
def clean_page_source(self, _html):
pattern = [
["\r",""],
["\t",""],
["\n",""],
["\"", ""],
["\u25e6", ""],
["\xa9", ""],
["\xa0", ""],
["\uf0e8", ""],
["\uff62", ""],
["\uff63", ""],
["\ufeff", ""],
["\u200b", ""],
["\u2013", ""],
["\u2024", ""],
["\u2027",""],
["\u2219", ""],
["\u25ef", ""],
["\u274d", ""],
["\u2782", ""],
["\u2981", ""],
["\u3007", ""],
["\uff63", ""],
["\u25fc", ""],
["\u2003", ""],
["\u231c", ""],
["\u0223", ""],
["\u2023", ""],
["\u0228", ""],
["\u2011", ""],
["\u0387", ""],
["\u2781", ""],
["\U000f02ea", ""],
["\u0278", ""],
["\u2022", ""],
["\u22c5",""],
["\u022f", ""],
["\u24fd", ""],
["\u302e", ""],
["\u0368", ""],
["\u301c", "~"],
["\u02d1", "~"],
["\u21e8", "->"],
["\u25a2", ""],
["\u231f", ""],
["\xb5", ""],
["\u2780", ""],
["\u119e", ""],
["\u20a9", ""],
["\u25cc", ""],
["\uf022", ""],
["\uf09e", ""],
["\u0301", ""],
["\uff65", ""],
["\u1168", ""],
["\u1163", ""],
["\u1166", ""],
["\u2215", ""],
["\u231c", ""],
["\U000f02ef", ""],
["\uf0a0", ""],
["\u2014", ""],
["\u2205", "Ø"],
["\u301a", ""],
["\u301b", ""],
["\uf028", ""],
["\u30fb", ""],
["\uf076", ""],
["\u25aa", ""],
["\u1104", ""],
["\u2776", ""],
["\u2777", ""],
["\u2613", ""],
["\u2000", ""],
["\u25b8", ""],
["\u2219", ""],
["\u2012", ""],
["\u233d", ""],
["\u8f66", ""],
["\u65f6", ""],
["\u95f4", ""],
["\u27f6", ""],
["\uf0a6", ""],
["\u21db", ""],
["\u2783", ""],
["\u2784", ""],
["\u2785", ""],
["\u2010", "-"],
["\U0001d635", ""],
["\u206d", ""],
["\u279f", ""],
["\u2d41", ""],
["\ufffd", ""],
["\u278a", ""],
["\u278b", ""],
["\u278c", ""],
["\u27f9", ""],
["\u2035", ""],
["\u02dc", ""],
["\u2053", ""],
["\u301e", ""],
]
return self.Util.clean_str(_html, pattern)
def enc_UTF8(self, _str, encoding='utf-8'):
return urllib.parse.unquote(_str, encoding=encoding)
# 기관 코드 가져오기
def getOrg(self, tblNm, orgNm, data_code_org_i, data_code_org_y, data_order_name):
# order_code 테이블에서 발주처명 변환이 필요한지 확인한다.
orgTmp = orgNm
if orgTmp:
# order_code_q = "SELECT * FROM order_name WHERE REPLACE(`before`, ' ', '') = '{orgNm}'".format(orgNm=orgNm.replace(" ", ""))
order_code_row = data_order_name[data_order_name['before'].str.replace(' ', '', regex=False) == orgNm.replace(" ", "")]
order_code_row = order_code_row.to_dict(orient='records')
# order_code_row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, order_code_q)
if order_code_row and len(order_code_row) > 0 and order_code_row[0].get('after'):
orgTmp = order_code_row[0]['after']
# print("order_name 추출 O")
# else:
# print("order_name 추출 X")
# print("orgTmp >>> ", orgTmp)
if tblNm == "code_org_y":
# query ="select order_code, order_name from code_org_y where replace(order_name, ' ', '') = replace('{orgNm}', ' ', '')".format(orgNm=orgTmp)
query_result = data_code_org_y[data_code_org_y['order_name'].str.replace(' ', '', regex=False) == orgTmp.replace(" ", "")]
query_result = query_result[['order_code', 'order_name']]
elif tblNm == "code_org_i":
# query = "select org_Scode as order_code, result_name as order_name from code_org_i where org_name = '{orgNm}'".format(orgNm=orgTmp)
query_result = data_code_org_i[data_code_org_i['org_name'].str.replace(' ', '', regex=False) == orgTmp.replace(" ", "")]
query_result = query_result[['org_Scode', 'result_name']]
query_result = query_result.rename(columns={'org_Scode': 'order_code', 'result_name': 'order_name'})
try:
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
row = query_result.to_dict(orient='records')
if len(row) > 0:
return row[0]
else:
return {"order_code": None, "order_name": orgNm}
except:
return {"order_code": None, "order_name": None}
#code_etc 가져오기
def getCodeEtc(self, kind, data_code_etc):
if data_code_etc.empty:
return None
# query = "select * from code_etc where kind='{kind}'".format(kind=kind)
query_result = data_code_etc[data_code_etc['kind'] == kind]
if kind == "succls":
#제한적 최저가가 먼저 체크되도록
# query = "select * from code_etc where kind='{kind}' ORDER BY CASE WHEN `code` = '03' THEN 0 ELSE 1 END, `code`;".format(kind=kind)
# 1. 'kind'로 먼저 필터링
filtered_df = data_code_etc[data_code_etc['kind'] == kind].copy() # 원본 수정을 피하기 위해 .copy() 사용
# 2. 정렬 우선순위를 위한 임시 컬럼 생성
# 'code'가 '03'이면 0, 아니면 1을 할당
filtered_df['sort_priority'] = filtered_df['code'].apply(lambda x: 0 if x == '03' else 1)
# 3. 'sort_priority'로 먼저 정렬하고, 그 다음 'code'로 정렬
# 두 컬럼 모두 오름차순으로 정렬 (ascending=True가 기본값)
query_result = filtered_df.sort_values(by=['sort_priority', 'code'])
# 4. 임시로 사용한 'sort_priority' 컬럼 제거
query_result = query_result.drop(columns=['sort_priority'])
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
row = query_result.to_dict(orient='records')
if len(row) > 0:
return row
else:
return None
# 종목코드 가져오기
def G2BPartCode(self):
query = "SELECT g2b_code, i2_code FROM code_item_match WHERE g2b_code NOT IN('1459','1460') AND i2_code IS NOT NULL"
row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
if len(row) > 0:
rtn = {}
for v in row:
rtn[v["g2b_code"]] = v["i2_code"]
return rtn
else:
return None
# 종목코드 가져오기
def KEPCOPartCode(self):
query = "SELECT bi_code, k_code FROM code_kepco WHERE state ='Y' AND bi_code IS NOT NULL"
row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
if len(row) > 0:
rtn = {}
for v in row:
rtn[v["k_code"]] = v["bi_code"]
return rtn
else:
return None
# 한전 종목코드 체크 후 없는 데이터일 경우 데이터삽입
def insertKEPCOPartCode(self,k_code,k_name):
query = "SELECT bi_code, k_code FROM code_kepco WHERE k_code='{k_code}' ".format(k_code=k_code)
row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
result = None
if len(row) <= 0 :
insert_query = "INSERT INTO code_kepco SET k_code = '{k_code}', k_name = '{k_name}', state = 'N', writedt = NOW() ".format(k_code=k_code, k_name=k_name)
result = self.dbconn_BI.process_sql(DB_CONN_ID, insert_query)
return result
# 물품분류번호로 면허찍어지는 코드 가져오기
def G2BpurCodeChange(self, data_g2b_pur_code_change):
# query = "SELECT *,LENGTH(g2b_code) AS codelength FROM g2b_pur_code_change ORDER BY codelength ASC"
if data_g2b_pur_code_change.empty:
return None
data_g2b_pur_code_change_copy = data_g2b_pur_code_change.copy()
data_g2b_pur_code_change_copy['codelength'] = data_g2b_pur_code_change_copy['g2b_code'].astype(str).str.len()
query_result = data_g2b_pur_code_change_copy.sort_values(by='codelength', ascending=True)
row = query_result.to_dict(orient='records')
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
if len(row) > 0:
rtn = {}
for v in row:
rtn[v["g2b_code"]] = v["change_code"]
return rtn
else:
return None
# 물품분류번호로 면허찍어지는 코드 가져오기
def G2BpurCodeLikeChange(self, gcode, data_g2b_pur_code_change):
tmpcode = gcode[0:2]
# 아래 항목일땐 2자리로 검색하지 않는다.
likecode = gcode
if tmpcode == '39' and gcode != '3911260201' and gcode != '3911260301' and gcode != '3911260302' and gcode != '3911260303' and gcode != '3911260401' and gcode != '3913170609' and gcode != '3913170610':
rtn = {}
rtn["39"] = 'C007'
return rtn
else:
# query = "SELECT *,LENGTH(g2b_code) AS codelength FROM g2b_pur_code_change WHERE g2b_code Like '{likecode}%' ORDER BY codelength ASC".format(likecode=likecode)
data_g2b_pur_code_change_copy = data_g2b_pur_code_change.copy()
data_g2b_pur_code_change = data_g2b_pur_code_change_copy[data_g2b_pur_code_change_copy['g2b_code'].str.contains(likecode)]
if data_g2b_pur_code_change.empty:
return None
data_g2b_pur_code_change_copy['codelength'] = data_g2b_pur_code_change_copy['g2b_code'].astype(str).str.len()
query_result = data_g2b_pur_code_change_copy.sort_values(by='codelength', ascending=True)
row = query_result.to_dict(orient='records')
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
# print(row)
if len(row) > 0:
rtn = {}
for v in row:
rtn[v["g2b_code"]] = v["change_code"]
return rtn
else:
return None
def pluscode_set(self, concode, sercode, pluscode):
rt = {}
g2b_code = []
prev_code = [concode, sercode]
for k, v in pluscode.items():
if len(re.findall("C", v)) > 0:
if concode is None: concode = v
elif len(re.findall(v, concode)) < 0: concode = "{code}|{pluscode}".format(code=concode, pluscode=v)
if len(re.findall("S", v)) > 0:
if sercode is None: sercode = v
elif len(re.findall(v, sercode)) < 0: sercode = "{code}|{pluscode}".format(code=sercode, pluscode=v)
g2b_code.append(k)
change_code = [concode, sercode]
g2b_pur_code_change_log = {}
g2b_pur_code_change_log['g2b_code'] = "|".join(list(filter(lambda x: x is not None, g2b_code)))
g2b_pur_code_change_log['prev_code'] = "|".join(list(filter(lambda x: x is not None, prev_code)))
g2b_pur_code_change_log['change_code'] = "|".join(list(filter(lambda x: x is not None, change_code)))
rt['concode'] = concode
rt['sercode'] = sercode
rt['g2b_pur_code_change_log'] = g2b_pur_code_change_log
return rt
# def G2BNmToYERAMCode(self, nm, data_code_item_match):
# # query = "SELECT g2b_code, i2_code, g2b_code_nm FROM code_item_match WHERE REPLACE(REPLACE(g2b_code_nm,'.',''),'·','') = REPLACE(REPLACE('{nm}','.',''),'·','') ".format(nm=nm)
# query = "SELECT g2b_code, i2_code, g2b_code_nm FROM code_item_match WHERE REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(g2b_code_nm,' ',''),'.',''),'·',''),',',''),'ㆍ', '') = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE('{nm}',' ',''),'.',''),'·',''),',',''),'ㆍ', ''),'기계설비가스공사업','기계가스설비공사업')".format(nm=nm)
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
# rtn = row[0] if len(row) > 0 else {"g2b_code": "", "i2_code": "", "g2b_code_nm": "", }
# return rtn
def G2BNmToYERAMCode(self, nm: str, data_code_item_match: pd.DataFrame):
"""
입력된 이름(nm) DataFrame(data_code_item_match) 'g2b_code_nm'
동일한 방식으로 전처리한 , 정확히 일치하는 행을 찾아 관련 정보를 반환합니다.
:param nm: 비교할 이름 문자열
:param data_code_item_match: 'g2b_code_nm', 'g2b_code', 'i2_code' 컬럼을 포함하는 DataFrame
:return: 일치하는 행의 정보를 담은 딕셔너리 또는 기본값 딕셔너리
"""
if not isinstance(nm, str):
# logging.warning(f"입력값 nm이 문자열이 아닙니다: {type(nm)}. 빈 문자열로 처리합니다.")
nm = ""
if not isinstance(data_code_item_match, pd.DataFrame) or data_code_item_match.empty:
# logging.warning("입력 DataFrame data_code_item_match가 비어있거나 DataFrame이 아닙니다.")
return {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""}
# 원본 DataFrame 수정을 피하기 위해 복사본 사용
data_code_item_match_copy = data_code_item_match.copy()
# 전처리할 문자들
chars_to_remove = [' ', '.', '·', ',', '']
# 특정 문자열 치환 (SQL의 마지막 REPLACE에 해당)
specific_replacements = {
'기계설비가스공사업': '기계가스설비공사업'
# 필요시 다른 치환 규칙 추가
}
# 1. DataFrame의 'g2b_code_nm' 컬럼 전처리
if 'g2b_code_nm' not in data_code_item_match_copy.columns:
logging.error("'g2b_code_nm' 컬럼이 DataFrame에 존재하지 않습니다.")
return {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""}
# NaN 값을 빈 문자열로 처리 후 문자열로 변환
processed_g2b_code_nm_col = data_code_item_match_copy['g2b_code_nm'].fillna('').astype(str)
for char in chars_to_remove:
processed_g2b_code_nm_col = processed_g2b_code_nm_col.str.replace(char, '', regex=False)
for old, new in specific_replacements.items(): # 특정 문자열 치환 적용
processed_g2b_code_nm_col = processed_g2b_code_nm_col.str.replace(old, new, regex=False)
# 전처리된 컬럼을 DataFrame에 임시로 저장하거나, 바로 비교에 사용
# data_code_item_match_copy['processed_g2b_code_nm'] = processed_g2b_code_nm_col
# 2. 입력 문자열 'nm' 전처리 (DataFrame 컬럼과 동일한 방식으로)
processed_nm = str(nm) # nm이 숫자로 들어올 경우를 대비해 문자열로 변환
for char in chars_to_remove:
processed_nm = processed_nm.replace(char, '')
for old, new in specific_replacements.items(): # 특정 문자열 치환 적용
processed_nm = processed_nm.replace(old, new)
# logging.debug(f"전처리된 g2b_code_nm 컬럼 (일부): \n{processed_g2b_code_nm_col.head()}")
# logging.debug(f"전처리된 입력 nm: '{processed_nm}'")
# 3. 전처리된 값들로 정확히 일치하는 행 필터링 (str.contains 대신 == 사용)
# data_code_item_match_copy = data_code_item_match_copy[processed_g2b_code_nm_col == processed_nm]
# 필터링된 결과를 새로운 변수에 할당하여 원본 data_code_item_match_copy의 참조 문제를 피함
filtered_df = data_code_item_match_copy[processed_g2b_code_nm_col == processed_nm]
# 결과가 여러 개일 경우 첫 번째 행을 사용할지, 아니면 다른 로직이 필요한지 결정해야 합니다.
# 현재 코드는 to_dict 후 첫 번째 결과를 사용합니다.
# 4. 결과를 딕셔너리 리스트로 변환
rows_list = filtered_df.to_dict(orient='records')
# 5. 최종 결과 반환
if rows_list: # 일치하는 행이 있는 경우
# SQL 쿼리는 g2b_code, i2_code, g2b_code_nm 컬럼만 선택했으므로, 여기서도 해당 컬럼만 추출
first_match = rows_list[0]
rtn = {
"g2b_code": first_match.get("g2b_code", ""),
"i2_code": first_match.get("i2_code", ""),
"g2b_code_nm": first_match.get("g2b_code_nm", "") # 원본 g2b_code_nm 반환
}
# logging.info(f"일치하는 데이터 찾음: {rtn}")
else:
rtn = {"g2b_code": "", "i2_code": "", "g2b_code_nm": ""}
# logging.info(f"일치하는 데이터를 찾지 못함. 입력 nm: '{nm}', 전처리된 nm: '{processed_nm}'")
return rtn
# g2b 종목코드 예람코드로 변환
# code 는 list 형태의 g2b 코드 ['1232','1145',...],
# 리턴값은 문자열 dic {'purcode': None, 'concode': 'C011|C013', 'sercode': None}
def G2BCodeToYERAMCode(self, code, data_DIC_G2BPartCode):
PartCode = data_DIC_G2BPartCode
YeramCode = list(filter(lambda x: x is not None, list(map(lambda x: PartCode[x] if x in PartCode else None, code))))
YeramCode = [x for x in YeramCode if x != '']
#코드별 예람 종목추가 적용[ key => g2b code, value => 추가할 예람코드(리스트)로 세팅]
add_code = {
"0001": ["C003"],
"0002": ["C003"],
"0032": ["C030"],
"0033": ["C030"],
"1121": ["S001", "S054"],
"1139": ["S031", "S032"],
"1177": ["S080", "C053"],
"1189": ["S072"],
"1401": ["C035", "C078"],
"3562": ["S999"],
"3585": ["S019"],
"3589": ["S037"],
"4817": ["S001"],
"4949": ["C030"],
"4950": ["C030"],
"5220": ["S057"],
"6117": ["C011", "S999"],
"6311": ["S031", "S032"],
"6312": ["S031", "S032"],
"6313": ["S031", "S032"],
"6815": ["C020", "S999"],
"4989": ["C021", "C034", "C032"], #B001 #지반조성.포장공사업 [토공|포장|보링.그라우팅]
"4990": ["C020"], # B002 #실내건축공사업 [실내건축]
"4991": ["C027", "C028"], # B003 #금속창호.지붕건축물조립공사업 [금속창호|지풍판금,건축물]
"4992": ["C024", "C022", "C023"], # B004 #도장.습식.방수.석공사업 [도장|습식.방수|석공]
"4993": ["C036", "C037"], # B005 # 조경식재.시설물공사업 [조경식재|조경시설물]
"4994": ["C029"], # B006 # 철근.콘크리트공사업 [철콘]
"4995": ["C025"], # B007 # 구조물해체.비계공사업 [비계구조]
"4996": ["C031"], # B008 # 상.하수도설비공사업 [상,하수도]
"4997": ["C033"], # B009 # 철도.궤도공사업 [철도,궤도]
"4998": ["C039", "C038"], # B010 # 철강구조물공사업 [철강재|강구조물]
"4999": ["C035", "C041"], # B011 # 수중.준설공사업 [수중|준설]
"6201": ["C042", "C040"], # B012 # 승강기,삭도공사업 [승강기|삭도]
"6202": ["C030", "C045"], # B013 # 기계가스설비공사업 [기계설비|가스1종]
"6203": ["C046", "C047", "C049", "C050", "C051"], # B013 # 가스난방공사업 [가스2종|가스3종|난방1종|난방2종|난방3종]
'토공사': ['C021','B001' ], # ========================= 여기서부터 주력공사 붙는 경우 해당 문구에 맞는 면허 반환될 수 있게 처리.
'포장공사': ['C034','B001' ],
'보링·그라우팅·파일공사': ['C032', 'B001'], #
'실내건축공사': ['C020', 'B002'], #
'금속구조물·창호·온실공사': ['C027','B003' ], #
'지붕판금·건축물조립공사': ['C028', 'B003'], #
'도장공사': ['C024','B004' ], #
'습식·방수공사': ['C022','B004' ], #
'석공사': ['C023','B004' ], #
'조경식재공사': ['C036','B005'],
'조경시설물설치공사': ['C037', 'B005'],
'철근·콘크리트공사': ['C029', 'B006'], #
'구조물해체·비계공사': ['C025', 'B007'], #
'상하수도설비공사': ['C031', 'B008'], #
'철도·궤도공사': ['C033', 'B009'],
'철강구조물공사' : ['C039', 'C038', 'B010'],
'수중공사': ['C035', 'B011'],
'준설공사': ['C041', 'B011'],
'승강기설치공사': ['C042', 'B012'],
'삭도설치공사': ['C040', 'B012'],
'기계설비공사': ['C030', 'B013'], #
'가스시설공사(제1종)': ['C045', 'B013'],
'가스시설공사(제2종)': ['C046', 'B014'],
'가스시설공사(제3종)': ['C047', 'B014'],
'난방공사(제1종)': ['C049', 'B014'],
'난방공사(제2종)': ['C050', 'B014'],
'난방공사(제3종)': ['C051', 'B014'],
'토공사 와 포장공사': ['C021', 'C034', 'B001'],
'토공사 와 보링·그라우팅·파일공사': ['C021', 'C032', 'B001'],
'포장공사 와 토공사': ['C021', 'C034', 'B001'],
'포장공사 와 보링·그라우팅·파일공사': ['C034', 'C032', 'B001'],
'보링·그라우팅·파일공사 와 포장공사': ['C034', 'C032', 'B001'],
'보링·그라우팅·파일공사 와 토공사': ['C021', 'C032', 'B001'],
}
AddYeramCode = list(filter(lambda x: x is not None, list(map(lambda x: add_code[x] if x in add_code else None, code))))
if len(AddYeramCode) > 0:
AddYeramCodeList = []
list(map(lambda x: list(map(lambda y: AddYeramCodeList.append(y), x)), AddYeramCode))
if len(AddYeramCodeList) > 0:
YeramCode = YeramCode + AddYeramCodeList #추가 코드 병합
YeramCode = list(set(YeramCode)) #중복제거
YeramCode.sort() #정렬
dic_partcode = {}
dic_partcode['concode'] = "|".join(list(filter(lambda x: x[0] == "C", YeramCode))) if len(list(filter(lambda x: x[0] == "C", YeramCode))) > 0 else None # 공사
dic_partcode['sercode'] = "|".join(list(filter(lambda x: x[0] == "S", YeramCode))) if len(list(filter(lambda x: x[0] == "S", YeramCode))) > 0 else None # 용역
dic_partcode['purcode'] = "|".join(list(filter(lambda x: x[0] == "P", YeramCode))) if len(list(filter(lambda x: x[0] == "P", YeramCode))) > 0 else None # 물품
dic_partcode['big_part'] = "|".join(list(filter(lambda x: x[0] == "B", YeramCode))) if len(list(filter(lambda x: x[0] == "B", YeramCode))) > 0 else None # 물품
return dic_partcode
def dicCodeEtc(self, kind, data_code_etc):
rtn = []
rows = self.getCodeEtc(kind, data_code_etc)
if rows is not None:
for row in rows:
rtn.append({"pattern": {"P1": row["val"], }, "value": row["code"]})
# 입찰방식 추가조건, 상단엔 DB데이터를 기반으로 세팅하기때문에 따로 넣어준다
if kind == 'bidcls':
rtn.append({"pattern": {"P1": '수기', }, "value": '00'})
return rtn
# xpath 로 구분한 테이블 시리얼 데이터를 줄단위 key value 데이터로 변환 리턴값 [{key:value,...},{key:value,...},{key:value,...}]
def change_serial_to_dic(self, _data, _keys):
_len = len(_data)
key_len = len(_keys)
_tmp = {}
RTN = []
for _idx, key in enumerate(_keys):
_tmp[key] = _data[_idx:_len:key_len]
for _idx, row in enumerate(_tmp[_keys[0]]):
list = {}
for idx, key in enumerate(_keys):
list[key] = _tmp[key][_idx]
RTN.append(list)
return RTN
# [{key:value,...},{key:value,...},{key:value,...}] 형식의 데이터중 컬럼하나만 함수적용하여 데이터 변경시 사용
def column_func(self, _list, _key, func, err_init=None):
for _idx, row in enumerate(_list):
try:
_list[_idx][_key] = func(row[_key])
except:
_list[_idx][_key] = err_init
return _list
def getPartCode(self, partNm):
try:
query = "select * from code_item where i2_name = '{partNm}' and state = 'y'".format(partNm=partNm)
row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
if len(row) > 0:
return row[0]
else:
return None
except:
return None
def getCodeLocal(self, locationNm, data_code_local):
try:
location_pattern = [
{"pattern": {"P1": "당진", }, "value": "충청남도 당진시"},
{"pattern": {"P1": "포항", }, "value": "경상북도 포항시"},
]
tmp = self.mapping_pattern_value(location_pattern, locationNm, None)
if tmp is not None:
locationNm = tmp
# query = "select * from code_local where `name` like '%{locationNm}%'".format(locationNm=locationNm)
query_result = data_code_local[data_code_local['name'].str.contains(locationNm)]
# row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
row = query_result.to_dict(orient='records')
if len(row) > 0:
return row[0]
else:
return None
except:
return None
# bid_local의 코드 앞자리 2개로 해당 지역 관내 개수 카운트
def count_code_local(self, code_value):
try:
query = "select count(*) as cnt from code_local where code like '{code_value}%'".format(code_value=code_value)
row = self.dbconn_BI.select_sql_to_dict(DB_CONN_ID, query)
return row[0]['cnt']
except:
return False
# 도내에 해당하는 관내가 모두 찍힌 경우 bid_local에서 제외하는 함수
def filter_bid_local(self, item_bid_local):
divide_bid_local_code = {}
filtered_bid_local = {}
new_key = 0
# bid_local의 코드 앞자리 2개를 key / 코드 뒷자리 2개를 value로 가지는 dict 세팅
for key, value in item_bid_local.items():
# 25/01/24 nil 처리 안되어 있어 추가
if value and value.get('code') and value['code'] and len(value['code']) == 4:
#if value['code'] and len(value['code']) == 4:
code = value['code']
local_code_key = code[:2]
local_code_value = code[2:]
if local_code_key in divide_bid_local_code:
divide_bid_local_code[local_code_key].append(local_code_value) # 키값 있으면 value append
else:
divide_bid_local_code[local_code_key] = [local_code_value] # 키값 없으면 키 세팅, value는 리스트형태로
# {'46': ['20', '14', '09', '17'], '20': ['16'], '49': ['03']} << 결과 예시
else:
logging.info("bid_local 코드 값 비정상@@@")
# 키 값 개수만큼 반복하여 bid_local_item+1과 DB의 지역 개수가 같은지 체크 (같으면 관내체크 안함)
# +1은 DB의 도내 값과 맞춰주기 위함(ex 1100, 서울특별시)
for key, value in divide_bid_local_code.items():
# 관내가 여러 지역이더라도 하나라도 관내면 관내이다.
if self.count_code_local(key) == len(value)+1:
continue
else:
for local_key, local_value in item_bid_local.items():
# 25/01/24 nil 처리 안되어 있어 추가
if local_value and local_value.get('code'):
if local_value['code'][:2] == key:
filtered_bid_local[new_key] = local_value
new_key = new_key + 1
return filtered_bid_local
# json => 문자열 변환시 value를 문자열로 변환하는 함수
def json_default(self, value):
return str(value)
# 딕셔너리에서 키 참조시 키가 존재하지 안는경우 에러가 나기 때문에 예외처리 및 키없는경우 초기값 지정
def getValue(self, arr, key, _init=""):
try:
return arr[key.strip()]
except:
return _init
# 숫자 스트링 클린징
def amt(self, _str):
_str = self.Util.clean_str(_str, [[",",""],[" ",""],["",""],["\t",""],["",""],])
_str = _str if _str and self.Not_None_Zero(_str) else ""
return _str
# 코드(정수) 리스트 => 리스트 값을 승수로 한 2의 승수 구하여 sum 값 반환
def pow_sum(self, arr):
rtn = 0
for i in arr:
rtn = rtn + pow(2, i)
return rtn
def conv_bin(self, num):
tmp = reversed(str(bin(num))[2:])
rtn = []
for _idx, row in enumerate(tmp):
if row == "1":
rtn.append(_idx)
return rtn
# _str 에서 정규식 패턴 리스트에 맞는 값 매칭
def mapping_rex_to_value(self, pattern_list, _str, _init=""):
rtn = _init
for pattern in pattern_list:
if len(re.findall(pattern[0],_str))>0 :
rtn=pattern[1]
return rtn
# 지역 리스트 => 지역코드 리스트 변환
def loction_to_code(self, arr):
local_pattern_arr = [
#["rex pattern", "value"]
["전국", 0],
["서울", 1],
["부산", 2],
["광주", 3],
["대전", 4],
["인천", 5],
["대구", 6],
["울산", 7],
["경기", 8],
["강원", 9],
["충북|충청북도", 10],
["충남|충청남도", 11],
["경북|경상북도", 12],
["경남|경상남도", 13],
["전북|전라북도", 14],
["전남|전라남도", 15],
["제주", 16],
["세종", 17],
]
rtn = []
for val in arr:
tmp = self.mapping_rex_to_value(local_pattern_arr, val, None)
if tmp is not None:
rtn.append(tmp)
rtn = list(set(rtn))
if len(rtn)>0:
return rtn
else:
return []
#raise Exception("loction_to_code : 지역값이 없습니다.")
#==============================================================================
## 문서에서 패턴을 매칭하여 반환
## 샘플 : aa = spilt_rex_doc(text, "(var\sbidRateCode\s=\s')((\d|\.){2,6})(';)", 2)
## idx 는 정규식 그룹중 추출할 인덱스
def spilt_rex_doc(self, _doc, _pattern, _idx):
try:
_idx_str = ""
if type(_idx) == list:
_idx_str = '|#==#|\\'.join(_idx)
_idx_str = "\{idx}".format(idx=_idx_str)
#print(_idx_str)
else:
_idx_str = "\{idx}".format(idx=_idx)
for para in _doc.splitlines():
para_tmp = para.strip()
#print("para_tmp:", para_tmp)
line = re.match(_pattern, para_tmp)
#print("line:", line)
if line is not None:
rtn = re.sub(_pattern, _idx_str, para_tmp)
if type(_idx)==list:
rtn = rtn.split("|#==#|")
return rtn
except:
pass
return None
def spilt_rex_doc_list(self, _list, _pattern, _idx):
#print("_list, _pattern, _idx",_list, _pattern, _idx)
rtn=[]
for list in _list:
rtn.append(self.spilt_rex_doc(list, _pattern, _idx))
return rtn
#==============================================================================
## 샘플 {"pattern": {"P1": "공동도급.", "P2": ".혼합허용", "N1": "ZZZ",}, "value": "1"}
## P(n) => 포함 조건 , P1, P2 ... and, or 조건은 정규식으로, = 조건은 ^word$
## N(n) => 제외 조건 , N1, N2 ... and, or 조건은 정규식으로, = 조건은 ^word$
def mapping_pattern_value(self, _patterns, _str, _init):
for _row in _patterns:
fg = True
for key in _row["pattern"]:
tmp = self.check_pattern(key, _row["pattern"][key], _str)
#print(key, _row["pattern"][key], tmp)
if (tmp != True): # 조건중 하나라도 참이 아니면 초기값 리턴
fg = False
if fg == True:
return _row["value"]
return _init
# K-APT 면허 매칭
def mapping_pattern_list(self, _patterns, _str): #매칭되는 모든 값을 리스트로 반환
rtn = []
for _row in _patterns:
fg = True
for key in _row["pattern"]:
tmp = self.check_pattern(key, _row["pattern"][key].rstrip('|'), _str)
# print(key, _row["pattern"][key], tmp)
if (tmp != True): # 조건중 하나라도 참이 아니면 초기값 리턴
fg = False
if fg == True:
rtn.append(_row["value"])
return rtn
def check_pattern(self, key, pattern, txt):
try:
if(key[0]=="P"):
if(len(re.findall(pattern, txt))>0):
rtn = True
else:
rtn = False
else:
if(len(re.findall(pattern, txt))<=0):
rtn = True
else:
rtn = False
except Exception as ex:
# print("check_pattern :", ex)
rtn = None
return rtn
def Not_None_Zero(self, _str):
try:
if float(_str.replace(",",""))>0:
return True
else:
return False
except:
return False
#JSON 변환전 딕셔너리 구조 정리 함수
def dict_reorganization(self, item):
returnData = {}
try:
for item_key, item_value in item.items():
if type(item_value) == int or type(item_value) == str or type(item_value) == float or item_value is None:
returnData[item_key] = item_value
else:
returnData[item_key] = self.dict_reorganization(item_value)
return returnData
except Exception as ex:
print("dict_reorganization :", ex)
def listTOdict(self, _lists):
rtn = {}
for _idx, _list in enumerate(_lists):
rtn[_idx] = _list
return rtn
# sys_collect 다음차수 공고 있는지 체크 (리스트 수집시 같은공고정보가 다수일경우 전차수가 뒤에 수집되기때문에 전차수가 나중에 수집되면 pass하기위해)
def prev_notinum_check(self, syscollect):
rtn = 'new'
dkey = syscollect['dkey']
# 현재 whereis 안들어가는 발주처가 많아서 dcode로 함
if syscollect['dcode'] == "D2B_B":
dkey = syscollect['dkey'].split("-")[0] + "-" + str(int(syscollect['dkey'].split("-")[1]) + 1)
where = {}
where['dcode'] = syscollect['dcode']
where['dkey'] = dkey
where['dkey_ext'] = syscollect['dkey_ext']
chk = self.dbconn_BI.ck_Exist_one(where, "sys_collect")
if chk == True:
print("다음차수 공고 있음")
rtn = 'closed'
else:
pass
return rtn
def null_turn(self, data):
if data is None: data = ''
if len(re.findall('0000-00-00', str(data))) > 0: data = ''
return data
def filter_num(self, num):
rt = 0
if num:
numbers = re.findall(r'\d+', str(num))
if numbers: rt = numbers[0]
return rt
def crawl_error_list(self, site, spiders, error_type, error_log):
logging.info("[crawl_error_list] start")
_insert = {}
_insert['site'] = site
_insert['spiders'] = spiders
_insert['error_type'] = error_type
_insert['error_log'] = str(error_log)
_insert['writedt'] = 'NOW()'
self.dbconn_BI.Insert_table(_insert, 'crawl_error_list')
logging.info("[crawl_error_list] end")
def crawl_moniter(self, type, site, spiders, spiderType):
print("[crawl_moniter] start")
# 특정 데이터를 업데이트 하는 경우 수집프로그램 구동으로 보지 않는다.
if spiderType != "modification":
columnName = ""
if type == "start": columnName = "startTime"
elif type == "end": columnName = "endTime"
_where = {}
_where['site'] = site
_where['spiders'] = spiders
_where['spiderType'] = spiderType
row = self.dbconn_BI.get_one_row(_where, "crawl_moniter")
if row == None:
_insert = {}
_insert['site'] = site
_insert['spiders'] = spiders
_insert['spiderType'] = spiderType
if columnName:
_insert[columnName] = "NOW()"
self.dbconn_BI.Insert_table(_insert, 'crawl_moniter')
else:
_update = {}
if columnName:
_update[columnName] = "NOW()"
# 종료시간 저장시 이전 종료시간을 남겨두기 위해 기록한다.
if type == "end": _update['prevTime'] = row['endTime']
self.dbconn_BI.Update_table(_update, _where, 'crawl_moniter')
print("[crawl_moniter] end")
return ""
def safe_encode(self, input_str, encoding='euc_kr'):
if input_str is None:
return ''
result = []
for char in input_str:
try:
char.encode(encoding)
result.append(char)
except UnicodeEncodeError:
# HTML 엔티티로 대체
if char in html.entities.codepoint2name:
# 공식 HTML 엔티티가 있으면 사용
name = html.entities.codepoint2name[ord(char)]
result.append('&{0};'.format(name))
else:
# HTML 엔티티가 없는 경우 숫자 참조를 사용
result.append('&#{0};'.format(ord(char)))
return ''.join(result)
def change_cost_notice_memo(self, before, after, bidid, type, whereis):
try:
# 국방부는 기초금액이 있을 때에만 수집되어서 변경에 대한 로그가 없을것으로 판단 제외함
print("change_cost_notice_memo")
if whereis == '01':
content1 = "g2b"
elif whereis == '10':
content1 = "d2b"
if type == 'const_cost':
type_kor = "순공사원가"
elif type == 'cost_a':
type_kor = "A값"
#수집시각
crawl_date_time = str(datetime.now().strftime('%Y/%m/%d %H:%M'))
if (before is not None and before != "" and int(before) > 0
and after is not None and after != "" and int(after) > 0
and before != after):
notice_memo_premium = "[수집시각: {}] 본 공고의 {} 금액이 정정이나 공지없이 변경되었습니다. 입찰에 참고하시기 바랍니다.(변경 전: {:,}원 / 변경 후: {:,}원)".format(crawl_date_time, type_kor, int(before), int(after))
crawl_monitor_data = {"category": "bid_change_notice_ck", "error_detail": notice_memo_premium, "content1": content1, "content2": type, "bidid": bidid}
Etl.crawl_monitor_error(self, crawl_monitor_data)
notice_data = {}
notice_data["notice_memo"] = notice_memo_premium
notice_data["writer_name"] = "자동수집"
notice_data["status"] = "done"
notice_data["category_type"] = ""
notice_data["reg_date"] = 'NOW()'
notice_data["bidid"] = bidid
self.dbconn_BI.Insert_table(notice_data, 'bid_notice_memo')
except Exception as e:
crawl_monitor_data = {"category": "bid_change_notice_ck", "error_detail": "TRY-CATCH - bidid({}), whereis({}) : {}".format(bidid, whereis, e), "content1": content1, "content2": type, "bidid": bidid}
Etl.crawl_monitor_error(self, crawl_monitor_data)
def crawl_monitor_error(self, data):
# 'category' 컬럼이 존재하는지 확인
category_column = data.get('category', None)
if category_column is not None:
# 'category' 컬럼에 공백, None이 아닌 값이 있는지 확인
if all(item is not None and str(item).strip() != '' for item in category_column):
# 에러 로그라 이후 구문에 문제 없도록 try catch
try:
data["reg_date"] = "NOW()"
self.dbconn_BI.Insert_table(data, 'crawl_monitor_error')
except Exception as e:
# 오류가 발생하더라도 로그로만 남기고 계속 진행
error = {
'category': data['category'],
'error_detail': e,
"reg_date": "NOW()"
}
self.dbconn_BI.Insert_table(error, 'crawl_monitor_error')
print("crawl_monitor_error 오류: " + e)
else:
print("crawl_monitor_error: category 공백")
# 자동 검수 코드 세팅
def auto_service_code_set(self, auto_service_code, item_bidkey, item_bidvalue, item_bidcontent):
# print("[auto_service_code_set] 공고 미입력 자동 서비스 start")
update_bid_key = {}
update_bid_value = {}
update_bid_content = {}
##########################
# 추후 추가할 항목들
##########################
# [K아파트] 자동점검
# [물품] 자동점검
# [나라장터] 특정용역 자동점검
# [나라장터] 재입찰 자동점검
# [나라장터] 취소 자동점검
################################################################################################
# [나라장터] 전자시담 자동점검 (start)
################################################################################################
e_sidam_whereis = {"01"}
e_sidam_bidcls = {"06"}
if item_bidkey["whereis"] in e_sidam_whereis and item_bidkey["bidcls"] in e_sidam_bidcls:
################################
# 1. state 세팅
################################
update_bid_key['state'] = 'A'
update_bid_key['inputer'] = '19'
################################
# 2. location 세팅 (시스템에서 수집한 면허가 없을 경우)
################################
if not item_bidkey["location"] or item_bidkey["location"] == '0':
update_bid_key["location"] = 1
################################
# 3. auto_service_code 세팅
################################
# bid_key에 코드가 세팅되어 있을 경우
if 'auto_service_code' in item_bidkey and item_bidkey['auto_service_code']:
# 세팅된 코드에 추가하려는 코드가 이미 있을 경우
if auto_service_code in item_bidkey['auto_service_code']: pass
# 세팅된 코드에 추가하려는 코드가 없을 경우
else: update_bid_key['auto_service_code'] = item_bidkey['auto_service_code'] + '|' + auto_service_code
# bid_key에 코드가 세팅되지 않은 경우
else: update_bid_key['auto_service_code'] = auto_service_code
# 시스템에서 수집한 면허가 없을 경우 -> 시설 : 기타공사, 용역 : 기타용역, 물품 : 기타물품
if not item_bidkey["concode"] and not item_bidkey["sercode"] and not item_bidkey["purcode"]:
if item_bidkey["bidtype"] == "con": update_bid_key["concode"] = "C999"
elif item_bidkey["bidtype"] == "ser": update_bid_key["sercode"] = "S999"
elif item_bidkey["bidtype"] == "pur": update_bid_key["purcode"] = "P501"
################################
# 공고명으로 텍스트 판단해서 면허 세팅 (시스템에서 수집한 면허가 없을 경우)
# 해당조건 제거 -> 나중에 살릴수 있으니 코드 남겨 놓음
################################
if False:
update_part_tmp = {"concode": item_bidkey["concode"], "sercode": item_bidkey["sercode"], "purcode": item_bidkey["purcode"]}
# 시스템에서 수집한 면허가 없을 경우
if not update_part_tmp["concode"] and not update_part_tmp["sercode"] and not update_part_tmp["purcode"]:
auto_part_check_query = "SELECT * FROM auto_partcode_check WHERE whereis = 'e_sidam' AND state IN ('T', 'TT')"
auto_part_check_data = self.dbconn_BI.sql_exec(auto_part_check_query, "DS")
for item in auto_part_check_data:
# 현재공고와 자동세팅된 정보의 기준이 동일할때 (시설, 용역, 물품)
if item['bidtype'] == item_bidkey['bidtype']:
# 매칭 조건만 있을 때
if item['state'] == 'T':
pattern_value_dict = {"pattern": {"P1": item['searchWord']}, "value": item['checkCode']}
# 매칭 + 제외 조건 있을 때
elif item['state'] == 'TT':
split_search_word = item['searchWord'].split('","')
pattern_value_dict = {"pattern": {"P1": split_search_word[0].replace('{"P":"', ''),
"N1": split_search_word[1].replace('N":"', '').replace('"}', '')},
"value": item['checkCode']}
# 공고명과 매칭해서 추가할 면허를 추출
mapping_part = self.mapping_pattern_list([pattern_value_dict], item_bidkey["constnm"].replace(" ", ""))
# 추출된 면허가 있으면
if mapping_part:
print("매핑", pattern_value_dict)
# 기존에 면허가 없으면 면허만 세팅
if not update_part_tmp[item['codetype']]:
update_part_tmp[item['codetype']] = mapping_part[0]
update_bid_key[item['codetype']] = mapping_part[0]
# 기존 면허정보에 추가할 면허가 없을 경우
elif mapping_part[0] not in update_part_tmp[item['codetype']]:
# 기존에 면허가 있으면 면허 뒤에 | 붙여서 세팅
update_part_tmp[item['codetype']] = update_part_tmp[item['codetype']] + "|" + mapping_part[0]
update_bid_key[item['codetype']] = update_bid_key[item['codetype']] + "|" + mapping_part[0]
else: print("시스템 수집 면허 있음")
################################
# 5. 자격조건, 공고문 세팅
################################
if not item_bidcontent['bidcomment']: update_bid_content['bidcomment'] = '[* 공고 원문을 참조하시기 바랍니다. *]'
elif '원문' not in item_bidcontent['bidcomment']: update_bid_content['bidcomment'] = item_bidcontent['bidcomment'] + '[* 공고 원문을 참조하시기 바랍니다. *]'
#else: update_bid_content['bidcomment'] = item_bidcontent['bidcomment'] + '[* 공고 원문을 참조하시기 바랍니다. *]'
#if not item_bidcontent['bid_html']: update_bid_content['bid_html'] = '[* 공고 원문을 참조하시기 바랍니다. *]'
#update_bid_content['bidcomment'] = '[* 공고 원문을 참조하시기 바랍니다. *]'
update_bid_content['bid_html'] = '[* 공고 원문을 참조하시기 바랍니다. *]'
################################################################################################
# [나라장터] 전자시담 자동점검 (end)
################################################################################################
# print("[auto_service_code_set] 공고 미입력 자동 서비스 end")
return update_bid_key, update_bid_value, update_bid_content
class g2b_attchd_lnk:
# make_guid 함수 정의
def make_guid(self, a=None):
if hasattr(uuid, 'uuid4'):
c = str(uuid.uuid4()).replace("-", "")
else:
c = ''.join([hex(random.randint(0, 65535))[2:] for _ in range(8)])
if a is not None:
c = "{}-{}".format(a, c)
return c
# 문자열 삽입 함수
def insertAt(self, a, c, b):
return a[:c] + b + a[c:]
# UTF-8 인코딩 함수
def utf8_encode(self, b):
b = b.replace('\r\n', '\n')
c = ""
for a in range(len(b)):
d = ord(b[a])
if d < 128:
c += chr(d)
else:
if 127 < d < 2048:
c += chr((d >> 6) | 192)
else:
c += chr((d >> 12) | 224)
c += chr(((d >> 6) & 63) | 128)
c += chr((d & 63) | 128)
return c
# Base64 인코딩 함수
def base64_encode(self, a):
keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
c = ""
l = 0
a = self.utf8_encode(a)
while l < len(a):
b = ord(a[l])
l += 1
if l < len(a):
e = ord(a[l])
l += 1
else:
e = None
if l < len(a):
f = ord(a[l])
l += 1
else:
f = None
d = b >> 2
if e is not None:
b_new = ((b & 3) << 4) | (e >> 4)
if f is not None:
h = ((e & 15) << 2) | (f >> 6)
k = f & 63
else:
h = ((e & 15) << 2)
k = 64 # Padding index for '='
else:
b_new = ((b & 3) << 4)
h = k = 64 # Padding indices for '='
c += keyStr[d]
c += keyStr[b_new]
c += keyStr[h]
c += keyStr[k]
return c
# 암호화 파라미터 생성 함수
def makeEncryptParam(self, a):
#a = self.base64_encode(self.utf8_encode(a))
# 중복 사용이어서 수정
a = self.base64_encode(a)
if len(a) >= 10:
a = self.insertAt(a, 8, "r")
a = self.insertAt(a, 6, "a")
a = self.insertAt(a, 9, "o")
a = self.insertAt(a, 7, "n")
a = self.insertAt(a, 8, "w")
a = self.insertAt(a, 6, "i")
a = self.insertAt(a, 9, "z")
else:
a = self.insertAt(a, len(a) - 1, "$")
a = self.insertAt(a, 0, "$")
a = a.replace('+', '%2B')
return a
# 최종 암호화 파라미터 함수
def makeEncryptParamFinal(self, a, c):
b = {
'name': "",
'value': ""
}
e = "1"
f = "1"
d = "2018.1548512.1555.33"
if "1" >= e:
if f == "0":
b['name'] = "k10"
b['value'] = self.makeEncryptParam(a) if c and c == 1 else d + self.makeEncryptParam(a)
else:
b['name'] = "k00"
b['value'] = self.makeEncryptParam(a)
return b
def attchd_lnk_set(self, attchd_lnk_data):
# 데이터 없을 경우 데이터 반환하지 않도록 처리 필요
# 첨부파일 여러개이면 여러개 데이터 세팅하도록 처리 필요
print("attchd_lnk_data > ", attchd_lnk_data)
returnData = ""
orgnlAtchFileNm = html.unescape(urllib.parse.unquote(attchd_lnk_data['orgnlAtchFileNm']))
atchFilePathNm = html.unescape(urllib.parse.unquote(attchd_lnk_data['atchFilePathNm']))
print("orgnlAtchFileNm > ", orgnlAtchFileNm)
print("atchFilePathNm > ", atchFilePathNm)
file_set_data = "kc\fc11\u000b"
file_set_data += "k01\f1\u000b"
file_set_data += "k12\f{}\u000b".format(self.make_guid())
file_set_data += "k26\f{}\u000b".format(atchFilePathNm)
file_set_data += "k31\f{}\u000b".format(orgnlAtchFileNm)
file_set_data += "k21\f{},{}\u000b".format(attchd_lnk_data['untyAtchFileNo'], attchd_lnk_data['atchFileSqno'])
print("file_set_data > ", file_set_data)
# 마지막 수직 탭 제거 (원하지 않으면 생략 가능)
# file_set_data = file_set_data.rstrip("\u000b")
# print("file_set_data 수직 탭 제거> ", file_set_data)
# Now, process a using makeEncryptParamFinal
file_set_data_result = self.makeEncryptParamFinal(file_set_data, c=1)
file_set_data_name = file_set_data_result.get('name')
file_set_data_value = file_set_data_result.get('value')
returnData = "{orgnlAtchFileNm}#=====#https://nwww.g2b.go.kr/fs/fsc/fsca/fileUpload.do?{file_set_data_name}={file_set_data_value}".format(orgnlAtchFileNm=orgnlAtchFileNm, file_set_data_name=file_set_data_name, file_set_data_value=file_set_data_value)
print("file_set_data_result > ", file_set_data_result)
print("https://nwww.g2b.go.kr/fs/fsc/fsca/fileUpload.do?" + file_set_data_name + "=")
print("file_set_data_value > ", file_set_data_value)
return returnData