context_utils.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. # -*- coding: utf-8 -*-
  2. """上下文窗口与金额标准化纯函数。
  3. 按 ARCHITECTURE.md Phase 3 拆分建议,从 ``common/Utils.py`` 迁出。
  4. 类型:CORE(业务纯函数,人工主导)。
  5. 原位置:``common/Utils.py`` 中以下函数:
  6. 上下文窗口:
  7. - ``spanWindow`` / ``get_context`` / ``findAllIndex`` / ``find_index``
  8. 金额标准化:
  9. - ``getUnifyMoney`` / ``getDigitsDic`` / ``getMultipleFactor``
  10. - ``partMoney`` / ``uniform_num`` / ``uniform_package_name``
  11. - ``money_process`` / ``get_money_entity``
  12. 其他纯函数:
  13. - ``combine`` / ``fitDataByRule`` / ``clean_company``
  14. - ``cut_repeat_name`` / ``is_all_winner`` / ``is_deposit_project``
  15. - ``find_package``(含 ``package_number_pattern`` / ``filter_package_pattern``)
  16. 本文件自包含,不依赖 ``common/Utils.py``,避免循环 import。
  17. ``common/Utils.py`` 仍 re-export 以上全部名称,老 import 不受影响。
  18. 按 ARCHITECTURE.md §4.3 依赖方向约束:
  19. common 可被各层引用,但不反向依赖 pipeline / predictors / assembly / rules。
  20. """
  21. from __future__ import absolute_import
  22. import re
  23. from decimal import Decimal
  24. __all__ = [
  25. # 上下文窗口
  26. "spanWindow",
  27. "get_context",
  28. "findAllIndex",
  29. "find_index",
  30. # 金额标准化
  31. "getUnifyMoney",
  32. "getDigitsDic",
  33. "getMultipleFactor",
  34. "partMoney",
  35. "uniform_num",
  36. "uniform_package_name",
  37. "money_process",
  38. "get_money_entity",
  39. # 其他纯函数
  40. "combine",
  41. "fitDataByRule",
  42. "clean_company",
  43. "cut_repeat_name",
  44. "is_all_winner",
  45. "is_deposit_project",
  46. "find_package",
  47. # 模块级正则
  48. "package_number_pattern",
  49. "filter_package_pattern",
  50. ]
  51. # ============================================================
  52. # 上下文窗口
  53. # ============================================================
  54. def find_index(list_tofind, text):
  55. '''
  56. @summary: 查找所有词汇在字符串中第一次出现的位置
  57. @param:
  58. list_tofind:待查找词汇
  59. text:字符串
  60. @return: list,每个词汇第一次出现的位置
  61. '''
  62. result = []
  63. for item in list_tofind:
  64. index = text.find(item)
  65. if index >= 0:
  66. result.append(index)
  67. else:
  68. result.append(-1)
  69. return result
  70. def findAllIndex(substr, wholestr):
  71. '''
  72. @summary: 找到字符串的子串的所有begin_index
  73. @param:
  74. substr:子字符串
  75. wholestr:子串所在完整字符串
  76. @return: list,字符串的子串的所有begin_index
  77. '''
  78. copystr = wholestr
  79. result = []
  80. indexappend = 0
  81. while True:
  82. index = copystr.find(substr)
  83. if index < 0:
  84. break
  85. else:
  86. result.append(indexappend + index)
  87. indexappend += index + len(substr)
  88. copystr = copystr[index + len(substr):]
  89. return result
  90. def spanWindow(tokens, begin_index, end_index, size, center_include=False, word_flag=False, use_text=False, text=None):
  91. '''
  92. @summary:取得某个实体的上下文词汇
  93. @param:
  94. tokens:句子分词list
  95. begin_index:实体的开始index
  96. end_index:实体的结束index
  97. size:左右两边各取多少个词
  98. center_include:是否包含实体
  99. word_flag:词/字,默认是词
  100. @return: list,实体的上下文词汇
  101. '''
  102. if use_text:
  103. assert text is not None
  104. length_tokens = len(tokens)
  105. if begin_index > size:
  106. begin = begin_index - size
  107. else:
  108. begin = 0
  109. if end_index + size < length_tokens:
  110. end = end_index + size + 1
  111. else:
  112. end = length_tokens
  113. result = []
  114. if not word_flag:
  115. result.append(tokens[begin:begin_index])
  116. if center_include:
  117. if use_text:
  118. result.append(text)
  119. else:
  120. result.append(tokens[begin_index:end_index + 1])
  121. result.append(tokens[end_index + 1:end])
  122. else:
  123. result.append("".join(tokens[begin:begin_index]))
  124. if center_include:
  125. if use_text:
  126. result.append(text)
  127. else:
  128. result.append("".join(tokens[begin_index:end_index + 1]))
  129. result.append("".join(tokens[end_index + 1:end]))
  130. return result
  131. def get_context(sentence_text, begin_index, end_index, size=20, center_include=False):
  132. '''
  133. 返回实体上下文信息
  134. :param sentence_text: 句子文本
  135. :param begin_index: 实体字开始位置
  136. :param end_index: 实体字结束位置
  137. :param size: 字偏移量
  138. :param center_include:
  139. :return:
  140. '''
  141. result = []
  142. begin = begin_index - size if begin_index > size else 0
  143. end = end_index + size
  144. result.append(sentence_text[begin: begin_index])
  145. if center_include:
  146. result.append(sentence_text[begin_index: end_index])
  147. result.append(sentence_text[end_index: end])
  148. return result
  149. # ============================================================
  150. # 金额标准化
  151. # ============================================================
  152. def getDigitsDic(unit):
  153. '''
  154. @summary:拿到中文对应的数字
  155. '''
  156. DigitsDic = {"零": 0, "壹": 1, "贰": 2, "叁": 3, "肆": 4, "伍": 5, "陆": 6, "柒": 7, "捌": 8, "玖": 9,
  157. "〇": 0, "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9}
  158. return DigitsDic.get(unit)
  159. def getMultipleFactor(unit):
  160. '''
  161. @summary:拿到单位对应的值
  162. '''
  163. MultipleFactor = {"兆": Decimal(1000000000000), "亿": Decimal(100000000), "万": Decimal(10000), "仟": Decimal(1000),
  164. "千": Decimal(1000), "佰": Decimal(100), "百": Decimal(100), "拾": Decimal(10), "十": Decimal(10),
  165. "元": Decimal(1), "圆": Decimal(1), "角": round(Decimal(0.1), 1), "分": round(Decimal(0.01), 2)}
  166. return MultipleFactor.get(unit)
  167. def getUnifyMoney(money):
  168. '''
  169. @summary:将中文金额字符串转换为数字金额
  170. @param:
  171. money:中文金额字符串
  172. @return: decimal,数据金额
  173. '''
  174. MAX_MONEY = 1000000000000
  175. MAX_NUM = 12
  176. # 去掉逗号
  177. money = re.sub("[,,]", "", money)
  178. money = re.sub("[^0-9.零壹贰叁肆伍陆柒捌玖拾佰仟萬億圆十百千万亿元角分]", "", money)
  179. result = Decimal(0)
  180. chnDigits = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"]
  181. chnFactorUnits = ["兆", "亿", "万", "仟", '千', "佰", '百', "拾", '十', "圆", "元", "角", "分"]
  182. LowMoneypattern = re.compile("^[\d,]+(\.\d+)?$")
  183. BigMoneypattern = re.compile("^零?(?P<BigMoney>[%s])$" % ("".join(chnDigits)))
  184. try:
  185. if re.search(LowMoneypattern, money) is not None:
  186. return Decimal(money)
  187. elif re.search(BigMoneypattern, money) is not None:
  188. return getDigitsDic(re.search(BigMoneypattern, money).group("BigMoney"))
  189. for factorUnit in chnFactorUnits:
  190. if re.search(re.compile(".*%s.*" % factorUnit), money) is not None:
  191. subMoneys = re.split(re.compile("%s(?!.*%s.*)" % (factorUnit, factorUnit)), money)
  192. if re.search(re.compile("^(\d+)(\.\d+)?$"), subMoneys[0]) is not None:
  193. if MAX_MONEY / getMultipleFactor(factorUnit) < Decimal(subMoneys[0]):
  194. return Decimal(0)
  195. result += Decimal(subMoneys[0]) * (getMultipleFactor(factorUnit))
  196. elif len(subMoneys[0]) == 1:
  197. if re.search(re.compile("^[%s]$" % ("".join(chnDigits))), subMoneys[0]) is not None:
  198. result += Decimal(getDigitsDic(subMoneys[0])) * (getMultipleFactor(factorUnit))
  199. # subMoneys[0]中无金额单位,不可再拆分
  200. elif subMoneys[0] == "":
  201. result += 0
  202. elif re.search(re.compile("[%s]" % ("".join(chnFactorUnits))), subMoneys[0]) is None:
  203. result += Decimal(getUnifyMoney(subMoneys[0])) * (getMultipleFactor(factorUnit))
  204. else:
  205. result += Decimal(getUnifyMoney(subMoneys[0])) * (getMultipleFactor(factorUnit))
  206. if len(subMoneys) > 1:
  207. if re.search(re.compile("^(\d+(,)?)+(\.\d+)?[百千万亿]?\s?(元)?$"), subMoneys[1]) is not None:
  208. result += Decimal(subMoneys[1])
  209. elif len(subMoneys[1]) == 1:
  210. if re.search(re.compile("^[%s]$" % ("".join(chnDigits))), subMoneys[1]) is not None:
  211. result += Decimal(getDigitsDic(subMoneys[1]))
  212. else:
  213. result += Decimal(getUnifyMoney(subMoneys[1]))
  214. break
  215. except Exception:
  216. return Decimal(0)
  217. return result
  218. def partMoney(entity_text, input2_shape=[7]):
  219. '''
  220. @summary:对金额分段
  221. @param:
  222. entity_text:数值金额
  223. input2_shape:分类数
  224. @return: array,分段之后的独热编码
  225. '''
  226. import numpy as np
  227. money = float(entity_text)
  228. parts = np.zeros(input2_shape)
  229. if money < 100:
  230. parts[0] = 1
  231. elif money < 1000:
  232. parts[1] = 1
  233. elif money < 10000:
  234. parts[2] = 1
  235. elif money < 100000:
  236. parts[3] = 1
  237. elif money < 1000000:
  238. parts[4] = 1
  239. elif money < 10000000:
  240. parts[5] = 1
  241. else:
  242. parts[6] = 1
  243. return parts
  244. def uniform_num(num):
  245. d1 = {'一': '1', '二': '2', '三': '3', '四': '4', '五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'}
  246. d3 = {'Ⅰ': '1', 'Ⅱ': '2', 'Ⅲ': '3', 'Ⅳ': '4', 'Ⅴ': '5', 'Ⅵ': '6', 'Ⅶ': '7'}
  247. if num.isdigit():
  248. if re.search('^0[\d]$', num):
  249. num = num[1:]
  250. return num
  251. elif re.search('^[一二三四五六七八九十]+$', num):
  252. _digit = re.search('^[一二三四五六七八九十]+$', num).group(0)
  253. if len(_digit) == 1:
  254. num = d1[_digit]
  255. elif len(_digit) == 2 and _digit[0] == '十':
  256. num = '1' + d1[_digit[1]]
  257. elif len(_digit) == 2 and _digit[1] == '十':
  258. num = d1[_digit[0]] + '0'
  259. elif len(_digit) == 3 and _digit[1] == '十':
  260. num = d1[_digit[0]] + d1[_digit[2]]
  261. elif re.search('[ⅠⅡⅢⅣⅤⅥⅦ]', num):
  262. num = re.search('[ⅠⅡⅢⅣⅤⅥⅦ]', num).group(0)
  263. num = d3[num]
  264. return num
  265. def uniform_package_name(package_name):
  266. '''
  267. 统一规范化包号。数值类型统一为阿拉伯数字,字母统一为大写,包含施工监理等抽到前面, 例 A包监理一标段 统一为 监理A1 ; 包Ⅱ 统一为 2
  268. :param package_name: 字符串类型 包号
  269. :return:
  270. '''
  271. package_name_raw = package_name
  272. package_name = re.sub('pdf|doc|docs|xlsx|rar|\d{4}年', ' ', package_name)
  273. package_name = package_name.replace('标段(包)', '标段').replace('№', '')
  274. package_name = re.sub('\[|【', '', package_name)
  275. kw = re.search('(施工|监理|监测|勘察|设计|劳务)', package_name)
  276. name = ""
  277. if kw:
  278. name += kw.group(0)
  279. if re.search('^[a-zA-Z0-9-]{5,}$', package_name):
  280. _digit = re.search('^[a-zA-Z0-9-]{5,}$', package_name).group(0).upper()
  281. name += _digit
  282. elif re.search('(?P<eng>[a-zA-Z])包[:)]?第?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))标段?', package_name):
  283. ser = re.search('(?P<eng>[a-zA-Z])包[:)]?第?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))标段?', package_name)
  284. _char = ser.groupdict().get('eng')
  285. if _char:
  286. _char = _char.upper()
  287. _digit = ser.groupdict().get('num')
  288. _digit = uniform_num(_digit)
  289. name += _char.upper() + _digit
  290. elif re.search('第?(?P<eng>[0-9a-zA-Z-]{1,4})?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))(标[段号的包项]?|合同[包段]|([分子]?[包标]))', package_name):
  291. ser = re.search('第?(?P<eng>[0-9a-zA-Z-]{1,4})?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))(标[段号的包项]?|合同[包段]|([分子]?[包标]))', package_name)
  292. _char = ser.groupdict().get('eng')
  293. if _char:
  294. _char = _char.upper()
  295. _digit = ser.groupdict().get('num')
  296. _digit = uniform_num(_digit)
  297. if _char:
  298. name += _char.upper()
  299. name += _digit
  300. elif re.search('(标[段号的包项]?|项目|子项目?|采购包(?|([分子]?包|包[组件号]))编?号?[::]?(?P<eng>[0-9a-zA-Z-]{1,4})?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))', package_name):
  301. ser = re.search('(标[段号的包项]?|项目|子项目?|采购包(?|([分子]?包|包[组件号]))编?号?[::]?(?P<eng>[0-9a-zA-Z-]{1,4})?(?P<num>([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4}))', package_name)
  302. _char = ser.groupdict().get('eng')
  303. if _char:
  304. _char = _char.upper()
  305. _digit = ser.groupdict().get('num')
  306. _digit = uniform_num(_digit)
  307. if _char:
  308. name += _char.upper()
  309. name += _digit
  310. elif re.search('(标[段号的包项]|([分子]?包|包[组件号]))编?号?[::]?(?P<eng>[a-zA-Z-]{1,5})', package_name):
  311. _digit = re.search('(标[段号的包项]|([分子]?包|包[组件号]))编?号?[::]?(?P<eng>[a-zA-Z-]{1,5})', package_name).group('eng').upper()
  312. name += _digit
  313. elif re.search('(?P<eng>[a-zA-Z]{1,4})(标[段号的包项]|([分子]?[包标]|包[组件号]))', package_name):
  314. _digit = re.search('(?P<eng>[a-zA-Z]{1,4})(标[段号的包项]|([分子]?[包标]|包[组件号]))', package_name).group('eng').upper()
  315. name += _digit
  316. elif re.search('^([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4})$', package_name):
  317. _digit = re.search('^([0-9]{1,4}|[一二三四五六七八九十]{1,4}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,4})$', package_name).group(0)
  318. _digit = uniform_num(_digit)
  319. name += _digit
  320. elif re.search('^[a-zA-Z0-9-]+$', package_name):
  321. _char = re.search('^[a-zA-Z0-9-]+$', package_name).group(0)
  322. name += _char.upper()
  323. if name == "":
  324. return package_name_raw
  325. else:
  326. if name.isdigit():
  327. name = str(int(name))
  328. return name
  329. def money_process(money_text, header):
  330. '''
  331. 输入金额文本及金额列表头,返回统一数字化金额及金额单位
  332. :param money_text:金额字符串
  333. :param header:金额列表头,用于提取单位
  334. :return:
  335. '''
  336. money = 0
  337. money_unit = ""
  338. moneys, _ = get_money_entity('%s:%s' % (header, money_text))
  339. if len(moneys) == 1:
  340. money = float(moneys[0][0])
  341. money_unit = moneys[0][3]
  342. elif len(moneys) == 2 and moneys[0][0] == moneys[1][0]:
  343. money = float(moneys[0][0])
  344. money_unit = moneys[0][3]
  345. return (money, money_unit)
  346. def get_money_entity(sentence_text, found_yeji=0, in_attachment=False):
  347. money_list = []
  348. # 使用正则识别金额
  349. entity_type = "money"
  350. list_money_pattern = {"cn": "(()(?P<filter_kw>百分之)?(?P<money_cn>[零壹贰叁肆伍陆柒捌玖拾佰仟萬億圆十百千万亿元角分]{3,})())",
  351. "key_word": "((?P<text_key_word>(?:[¥¥]+,?|(中标|成交|合同|承租|投资|服务|起始))?(金?额|价格?)|价格|预算(金额)?|(监理|设计|勘察)(服务)?费|[单报标限总造]价款?|金额|租金|标的基本情况|CNY|成交结果|资金|(控制|拦标)价|投资|成本)(/折扣率|/投标费率(%))?(\d:|\d=\d[-+×]\d:)?(?:[,,\[(\(]*\s*(人民币|单位:?)?/?(?P<unit_key_word_before>[万亿]?(?:[美日欧]元|元(/(M2|[\u4e00-\u9fa5]{1,3}))?)?(?P<filter_unit2>[台个只吨]*))\s*(/?费率)?(人民币)?[\])\)]?)\s*[,,::]*(RMB|USD|EUR|JPY|CNY)?[::]?(\s*[^壹贰叁肆伍陆柒捌玖拾佰仟萬億分万元编号时间日期计采a-zA-Z]{,8}?))((可填写下浮率、折扣率或费率):?)?(第[123一二三]名[::])?(\d+(\*\d+%)+=)?(?P<money_key_word>\d+,\d+\.\d{2,6}|\d{1,3}([,,]\d{3,})+(\.\d+)?|\d+(\.\d+)?[百千]{,1})(?P<science_key_word>(E-?\d+))?(?:[(\(]?(?P<filter_>[%%‰折])*\s*,?((金额)?单位[::])?(?P<unit_key_word_behind>[万亿]?(?:[美日欧]元|元)?(?P<filter_unit1>[台只吨斤棵株页亩方条天年月日]*))\s*[)\)]?))",
  352. "front_m": "((?P<text_front_m>(?:[(\(]?\s*(?P<unit_front_m_before>[万亿]?(?:[美日欧]元|元))\s*[)\)]?)\s*[,,::]*(\s*[^壹贰叁肆伍陆柒捌玖拾佰仟萬億分万元编号时间日期计采a-zA-Z金额价格]{,2}?))(?P<money_front_m>\d{1,3}([,,]\d{3})+(\.\d+)?|\d+(\.\d+)?(?:,?)[百千]*)(?P<science_front_m>(E-?\d+))?())",
  353. "behind_m": "(()()(?P<money_behind_m>\d{1,3}([,,]\d{3})+(\.\d+)?|\d+(\.\d+)?(?:,?)[百千]*)(?P<science_behind_m>(E-?\d+))?(人民币)?[\((]?(?P<unit_behind_m>[万亿]?(?:[美日欧]元|元)(?P<filter_unit3>[台个只吨斤棵株页亩方条米]*))[\))]?)"}
  354. pattern_money = re.compile("%s|%s|%s|%s" % (
  355. list_money_pattern["cn"], list_money_pattern["key_word"], list_money_pattern["behind_m"],
  356. list_money_pattern["front_m"]))
  357. sentence_text = re.sub(r"(金额|价格|限价)[:为]?(\d+),(\d+)", r"\1\2\3", sentence_text)
  358. sentence_text = sentence_text.replace('总金额**亿元', 'xxxxxxx')
  359. ser = re.search('((收费标准|计算[方公]?式):|\w{3,5}\s*=)+\s*[中标投标成交金额招标人预算价格万元\s()()\[\]【】\d\.%%‰\+\-*×/]{20,}[,。]?', sentence_text)
  360. if ser:
  361. sentence_text = sentence_text.replace(ser.group(0), ' ' * len(ser.group(0)))
  362. all_match = re.finditer(pattern_money, sentence_text)
  363. for _match in all_match:
  364. if re.search('^元/1\d{10},$', _match.group(0)):
  365. continue
  366. elif re.search('元),?\d$', _match.group(0)) and re.match('[,、]', sentence_text[_match.end():]):
  367. continue
  368. if len(_match.group()) > 0:
  369. notes = ''
  370. unit = ""
  371. entity_text = ""
  372. start_index = ""
  373. end_index = ""
  374. text_beforeMoney = ""
  375. filter = ""
  376. filter_unit = False
  377. notSure = False
  378. science = ""
  379. if re.search('业绩(公示|汇总|及|报告|\w{,2}(内容|情况|信息)|[^\w])', sentence_text[:_match.span()[0]]):
  380. found_yeji += 1
  381. break
  382. for k, v in _match.groupdict().items():
  383. if v != "" and v is not None:
  384. if k == 'text_key_word':
  385. notSure = True
  386. if k.split("_")[0] == "money":
  387. entity_text = v
  388. if entity_text.endswith(',00'):
  389. entity_text = entity_text[:-3]
  390. if k.split("_")[0] == "unit":
  391. if 'behind' in k or unit == "":
  392. unit = v
  393. if k.split("_")[0] == "text":
  394. text_beforeMoney = v
  395. if k.split("_")[0] == "filter":
  396. filter = v
  397. if re.search("filter_unit", k) is not None:
  398. filter_unit = True
  399. if k.split("_")[0] == 'science':
  400. science = v
  401. if filter != "":
  402. continue
  403. if len(entity_text) > 30 or len(re.sub('[E-]', '', science)) > 2:
  404. continue
  405. start_index, end_index = _match.span()
  406. start_index += len(text_beforeMoney)
  407. if re.search('电话|手机|联系|方式|编号|编码|日期|数字|时间', text_beforeMoney):
  408. continue
  409. elif re.search('^1[3-9]\d{9}$', entity_text) and re.search(':\w{1,3}$', text_beforeMoney):
  410. continue
  411. elif re.search('^\d(.\d{1,2})?$', entity_text) and re.search('\d$', _match.group(0)) and re.search('^[、.]', sentence_text[_match.end():]):
  412. continue
  413. if unit == "":
  414. if (re.search('(¥|¥|RMB|CNY)[::]?$', text_beforeMoney) or re.search('[零壹贰叁肆伍陆柒捌玖拾佰仟萬億圆十百千万亿元角分]{3,}', entity_text)):
  415. if entity_text.endswith('万元'):
  416. unit = '万元'
  417. entity_text = entity_text[:-2]
  418. else:
  419. unit = '元'
  420. elif re.search('USD[::]?$', text_beforeMoney):
  421. unit = '美元'
  422. elif re.search('EUR[::]?$', text_beforeMoney):
  423. unit = '欧元'
  424. elif re.search('JPY[::]?$', text_beforeMoney):
  425. unit = '日元'
  426. elif re.search('^[-—]+[\d,.]+万元', sentence_text[end_index:]):
  427. unit = '万元'
  428. elif re.search('^,?(价格币种:\w{2,3},)?价格单位:万元', sentence_text[end_index:]):
  429. unit = '万元'
  430. elif re.search('万元', sentence_text[max(0, start_index - 10):start_index]):
  431. unit = '万元'
  432. elif re.search('([单报标限总造]价款?|金额|租金|(中标|成交|合同|承租|投资|控制|拦标))?[价额]|价格|预算(金额)?|(监理|设计|勘察)(服务)?费|成本|报价(单位))(小写)?[::为]*-?$', text_beforeMoney.strip()) and re.search('^0|1[3|4|5|6|7|8|9]\d{9}', entity_text) == None:
  433. if re.search('^[\d,,.]+$', entity_text) and float(re.sub('[,,]', '', entity_text)) < 500 and re.search('万元', sentence_text):
  434. unit = '万元'
  435. elif re.search('^[\d,,.]+$', entity_text) and float(re.sub('[,,]', '', entity_text)) < 100 and re.search('单位:%|费率|下浮率|[%%‰折]|优惠率', sentence_text):
  436. continue
  437. elif re.search('^\d{1,3}\.\d{4,6}$', entity_text) and re.search('0000$', entity_text) == None:
  438. unit = '万元'
  439. else:
  440. unit = '元'
  441. elif re.search('(^\d{,3}(,?\d{3})+(\.\d{2,7},?)$)|(^\d{,3}(,\d{3})+,?$)', entity_text):
  442. unit = '元'
  443. else:
  444. continue
  445. elif unit == '万元':
  446. if end_index < len(sentence_text) and sentence_text[end_index] == '元' and re.search('\d$', entity_text):
  447. unit = '元'
  448. elif re.search('^[5-9]\d{6,}\.\d{2}$', entity_text):
  449. unit = '元'
  450. if unit.find("万") >= 0 and entity_text.find("万") >= 0:
  451. unit = "元"
  452. if re.search('.*万元万元', entity_text):
  453. entity_text = entity_text.replace('万元万元', '万元')
  454. else:
  455. if filter_unit:
  456. continue
  457. entity_text = re.sub("[^0-9.零壹贰叁肆伍陆柒捌玖拾佰仟萬億圆十百千万亿元角分]", "", entity_text)
  458. if re.search('总投资|投资总额|总预算|总概算|(投资|招标|资金|存放|操作|融资)规模|批复概算|投资额|总规模|工程造价|总金额',
  459. sentence_text[max(0, _match.span()[0] - 10):_match.span()[1]]):
  460. notes = '总投资'
  461. elif re.search('投资|概算|建安费|其他费用|基本预备费',
  462. sentence_text[max(0, _match.span()[0] - 8):_match.span()[1]]):
  463. notes = '投资'
  464. elif (re.search('保证金', sentence_text[max(0, _match.span()[0] - 5):_match.span()[1]])
  465. or re.search('保证金的?(缴纳)?(金额|金\?|额|\?)?[\((]*(万?元|为?人民币|大写|调整|变更|已?修改|更改|更正)?[\))]*[::为]',
  466. sentence_text[max(0, _match.span()[0] - 10):_match.span()[1]])
  467. or re.search('保证金由[\d.,]+.{,3}(变更|修改|更改|更正|调整?)为',
  468. sentence_text[max(0, _match.span()[0] - 15):_match.span()[1]])):
  469. notes = '保证金'
  470. elif re.search('成本(警戒|预警)(线|价|值)[^0-9元]{,10}',
  471. sentence_text[max(0, _match.span()[0] - 10):_match.span()[0]]):
  472. notes = '成本警戒线'
  473. elif re.search('(监理|设计|勘察)(服务)?费(报价)?[约为:]|服务金额', sentence_text[_match.span()[0]:_match.span()[1]]) and re.search('缴纳', sentence_text[max(0, _match.span()[0] - 5):_match.span()[1]]) == None:
  474. notes = '招标或中标金额'
  475. elif re.search('单价|总金额', sentence_text[_match.span()[0]:_match.span()[1]]):
  476. notes = '单价'
  477. elif re.search('元[/每]', sentence_text[_match.start():_match.end() + 2]):
  478. notes = '单价'
  479. elif re.search('单价', sentence_text[max(0, _match.start() - 3):_match.start()]) and re.search('单价:\d+,', sentence_text[:_match.start()]) == None:
  480. notes = '单价'
  481. elif re.search('[零壹贰叁肆伍陆柒捌玖拾佰仟萬億圆]', entity_text) != None:
  482. notes = '大写'
  483. if entity_text[0] == "拾":
  484. entity_text = "壹" + entity_text
  485. if len(unit) > 0:
  486. if unit.find('万') >= 0 and len(entity_text.split('.')[0]) >= 8:
  487. entity_text = str(
  488. getUnifyMoney(entity_text) * getMultipleFactor(re.sub("[美日欧]", "", unit)[0]) / 10000)
  489. unit = '元'
  490. else:
  491. entity_text = str(getUnifyMoney(entity_text) * getMultipleFactor(re.sub("[美日欧]", "", unit)[0]))
  492. else:
  493. if entity_text.find('万') >= 0 and entity_text.split('.')[0].isdigit() and len(
  494. entity_text.split('.')[0]) >= 8:
  495. entity_text = str(getUnifyMoney(entity_text) / 10000)
  496. else:
  497. entity_text = str(getUnifyMoney(entity_text))
  498. if science and re.search('^E-?\d+$', science):
  499. entity_text = str(Decimal(entity_text + science)) if Decimal(entity_text + science) > 100 and Decimal(
  500. entity_text + science) < 10000000000 else entity_text
  501. if float(entity_text) > 100000000000:
  502. continue
  503. if notSure and unit == "" and float(entity_text) > 100 * 10000:
  504. continue
  505. if re.search('[%%‰折]|费率|下浮率', text_beforeMoney) and float(entity_text) < 1000:
  506. continue
  507. if notes == '单价' and float(entity_text) > 1000000 and re.search('单价((万元))?:', sentence_text[max(0, _match.start() - 3):_match.end()]) == None:
  508. notes = ""
  509. if float(entity_text) < 100 and notes != '单价':
  510. notes = '单价'
  511. money_list.append((entity_text, start_index, end_index, unit, notes))
  512. return money_list, found_yeji
  513. # ============================================================
  514. # 其他纯函数
  515. # ============================================================
  516. def combine(list1, list2):
  517. '''
  518. @summary:将两个list中的字符串两两拼接
  519. @param:
  520. list1:字符串list
  521. list2:字符串list
  522. @return:拼接结果list
  523. '''
  524. result = []
  525. for item1 in list1:
  526. for item2 in list2:
  527. result.append(str(item1) + str(item2))
  528. return result
  529. def fitDataByRule(data):
  530. # 根据规则补全编号或名称两边的符号
  531. symbol_dict = {"(": ")",
  532. "(": ")",
  533. "[": "]",
  534. "【": "】",
  535. ")": "(",
  536. ")": "(",
  537. "]": "[",
  538. "】": "【"}
  539. leftSymbol_pattern = re.compile("[\((\[【]")
  540. rightSymbol_pattern = re.compile("[\))\]】]")
  541. leftfinds = re.findall(leftSymbol_pattern, data)
  542. rightfinds = re.findall(rightSymbol_pattern, data)
  543. result = data
  544. if len(leftfinds) + len(rightfinds) == 0:
  545. return data
  546. elif len(leftfinds) == len(rightfinds):
  547. return data
  548. elif abs(len(leftfinds) - len(rightfinds)) == 1:
  549. if len(leftfinds) > len(rightfinds):
  550. if symbol_dict.get(data[0]) is not None:
  551. result = data[1:]
  552. else:
  553. result = data + symbol_dict.get(leftfinds[0])
  554. else:
  555. if symbol_dict.get(data[-1]) is not None:
  556. result = data[:-1]
  557. else:
  558. result = symbol_dict.get(rightfinds[0]) + data
  559. result = re.sub("[。]", "", result)
  560. return result
  561. def clean_company(entity_text):
  562. '''
  563. 清洗公司名称
  564. :param entity_text:
  565. :return:
  566. '''
  567. entity_text = re.sub('\s', '', entity_text)
  568. if re.search('^(\d{4}年)?[\-\d月日份]*\w{2,3}分公司$|^\w{,6}某(部|医院)$|空间布局$', entity_text):
  569. return ''
  570. elif re.match('xx|XX', entity_text):
  571. return ''
  572. elif re.match('\.?(rar|zip|pdf|df|doc|docx|xls|xlsx|jpg|png)', entity_text):
  573. entity_text = re.sub('\.?(rar|zip|pdf|df|doc|docx|xls|xlsx|jpg|png)', '', entity_text)
  574. elif re.match('(\d+)|\d+\.|\s|&nbsp', entity_text):
  575. entity_text = re.sub('(\d+)|\d+\.|\s|&nbsp', '', entity_text)
  576. elif re.match(
  577. '((\d{4}[年-])[\-\d:\s元月日份]*|\d{1,2}月[\d日.-]*(日?常?计划)?|\d{1,2}[.-]?|[A-Za-z](包|标段?)?|[a-zA-Z0-9]+-[a-zA-Z0-9-]*|[a-zA-Z]{1,2}|[①②③④⑤⑥⑦⑧⑨⑩]|\s|title\=|【[a-zA-Z0-9]+】|[^\w])[\u4e00-\u9fa5]+',
  578. entity_text):
  579. filter = re.match(
  580. '((\d{4}[年-])[\-\d:\s元月日份]*|\d{1,2}月[\d日.-]*(日?常?计划)?|\d{1,2}[.-]?|[A-Za-z](包|标段?)?|[a-zA-Z0-9]+-[a-zA-Z0-9-]*|[a-zA-Z]{1,2}|[①②③④⑤⑥⑦⑧⑨⑩]|\s|title\=|【[a-zA-Z0-9]+】|[^\w])[\u4e00-\u9fa5]+',
  581. entity_text).group(1)
  582. entity_text = entity_text.replace(filter, '')
  583. elif re.search('\]|\[|\]|[【】{}「?:∶〔·.\'#~_ΓΙεⅠ\丨]', entity_text):
  584. entity_text = re.sub('\]|\[|\]|[【】「?:∶〔·.\'#~_ΓΙεⅠ\丨]', '', entity_text)
  585. if len(re.sub('(项目|分|有限)?公司|集团|制造部|中心|医院|学校|大学|中学|小学|幼儿园', '', entity_text)) < 2:
  586. return ''
  587. return entity_text
  588. def cut_repeat_name(s):
  589. '''
  590. 公司连续重复名称去重
  591. :param s:
  592. :return:
  593. '''
  594. if len(s) >= 8:
  595. n = s.count(s[-4:])
  596. id = s.find(s[-4:]) + 4
  597. sub_s = s[:id]
  598. if n >= 2 and s == sub_s * n:
  599. s = sub_s
  600. return s
  601. def is_all_winner(title):
  602. '''
  603. 是否提取所有投标人作为中标人,存管类不分排名都作中标人;入围类按排名,无排名都做中标人
  604. :param title: 标题
  605. :return:
  606. '''
  607. if re.search('(资金|公款|存款)?竞争性存[放款]|(资金|公款|存款)存放|存放银行|存款服务|国库现金管理', title):
  608. return 1
  609. elif re.search('招募|入围|框架(协议)?采购|(单位|商|机构)入库|入库供应商|集中采购', title):
  610. return 2
  611. return False
  612. def is_deposit_project(title, name, requirement):
  613. '''
  614. 通过正则判断项目是否为银行存款类项目
  615. :param title: 标题
  616. :param name: 项目名称
  617. :param requirement: 采购内容
  618. :return:
  619. '''
  620. if re.search('(资金|公款|存款)?竞争性存[放款]|(资金|公款|存款)((.{2,10}))?存放|存放银行|存款(服务|业务|项目)|国库现金管理|存款账户开户|(管理|存款|合作)(定点|专户)?银行|贷款合作银行|资金监管账户|开户银行项目|专户开户银行|银行专户选择|定期存[款放]|专项债券?专用账户', title + name + requirement):
  621. return True
  622. return False
  623. package_number_pattern = re.compile(
  624. '((施工|监理|监测|勘察|设计|劳务)(标段)?:?第?([一二三四五六七八九十]{1,3}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,3}|[a-zA-Z0-9]{1,9}\-?[a-zA-Z0-9-]{,9})[分子]?(标[段包项]?|包[组件标]?|合同[包段]))\
  625. |(([a-zA-Z]包[:()]?)?第?([一二三四五六七八九十]{1,3}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,3}|[a-zA-Z0-9]{1,9}\-?[a-zA-Z0-9-]{,9})[分子]?(标[段包项]?|合同[包段]))\
  626. |(([,;。、:(]|第)?([一二三四五六七八九十]{1,3}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,3}|[a-zA-Z0-9]{1,9}\-?[a-zA-Z0-9-]{,9})[分子]?(标[段包项]?|包[组件标]?|合同[包段]))\
  627. |((标[段包项]|标段(包)|包[组件标]|[标分子(]包)(\[|【)?:?([一二三四五六七八九十]{1,3}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,3}|[a-zA-Z0-9]{1,9}\-?[a-zA-Z0-9-]{,9}))\
  628. |([,;。、:(]|^)(标的?|(招标|采购)?项目|子项目?|采购包()(\[|【)?:?([一二三四五六七八九十]+|[0-9]{1,9})\
  629. |((([标分子(]|合同|项目|采购)包|[,。]标的|子项目|[分子]标|标[段包项]|包[组件标]?)编?号[::]?[a-zA-Z0-9一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦ]{1,9}[a-zA-Z0-9一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦ-]{0,9})\
  630. |[,;。、:(]?(合同|分|子)?包:?([一二三四五六七八九十]{1,3}|[ⅠⅡⅢⅣⅤⅥⅦ]{1,3}|[a-zA-Z0-9]{1,9}\-?[a-zA-Z0-9-]{,9})')
  631. filter_package_pattern = 'CA标|(每个?|所有|相关|个|各|不分)[分子]?(标[段包项]?|包[组件标]?|合同包)|(质量|责任)三包|包[/每]|标段(划分|范围)|(承|压缩|软|皮|书|挂)包\
  632. |标[识注签贴配]|[商油]标号|第X包|第[一二三四五六七八九十]+至[一二三四五六七八九十]+(标[段包项]?|包[组件标]?|合同[包段])\
  633. |\.(docx|doc|pdf|xlsx|xls|jpg)|[一二三四五]次|五金|\d+[年月]|[\d.,]+万?元|\d+\.\d+'
  634. def find_package(content):
  635. '''
  636. 通过正则找包和标段号
  637. :param content:
  638. :return:
  639. '''
  640. packages = []
  641. content = content.replace('号,', '号:').replace(':', ':').replace('(', '(').replace(')', ')')
  642. content = re.sub('[一二三四五六七八九十\d](标[段包项]|包[组件标])编号', ' 标段编号', content)
  643. content = re.sub('打包|标段名称|标段(包)?名称|承,?包|共\d+包|标[厅室]', ' ', content)
  644. for it in re.finditer(filter_package_pattern, content):
  645. content = content.replace(it.group(0), ' ' * len(it.group(0)))
  646. for iter in re.finditer(package_number_pattern, content):
  647. if re.search('(业绩|信誉要求):|业绩(如下)?\d*[、:]', content[:iter.start()]):
  648. continue
  649. if re.match('\d', iter.group(0)) and re.search('\d\.$', content[:iter.start()]):
  650. continue
  651. if re.search('[承每书/]包|XX|xx', iter.group(0)) or re.search('\d包[/每]\w|一包[0-9一二三四五六七八九十]+', content[
  652. iter.start():iter.end() + 3]) or re.search(
  653. '[a-zA-Z0-9一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦ-]{6,}', iter.group(0)):
  654. continue
  655. elif iter.end() + 2 < len(content) and re.search('标的物|包装|划分|标(书|准|志|记|识|签|贴|帜|本|底|价|量)',
  656. content[iter.start():iter.end() + 2]):
  657. continue
  658. elif re.search('同一(标段?|包)', content[max(0, iter.start() - 2):iter.end()]):
  659. continue
  660. elif re.search('三包', content[max(0, iter.start() - 2):iter.end()]) and re.search('第三包', content[max(0,
  661. iter.start() - 2):iter.end()]) == None:
  662. continue
  663. elif re.search('[1-9]\d{2,}$|\d{4,}|^[1-9]\d{2,}|合同包[A-Za-z]{2,}', iter.group(0)):
  664. continue
  665. elif re.search('单位:包|1包\d|[张箱]|数量:', content[max(0, iter.start() - 3): iter.end() + 2]):
  666. continue
  667. elif re.search('(\d+|一)包', iter.group(0)) and re.search('(品牌|规格|参数)\w{,2}:', content[:iter.start()]) and re.search('标[段包]|包号', content[:iter.start()]) == None:
  668. continue
  669. elif iter.group(0) == '劳务分包':
  670. continue
  671. elif re.search('^包\d$', iter.group(0)) and re.search('[个只台件套次]', content[iter.end():iter.end() + 5]):
  672. continue
  673. packages.append(iter)
  674. return packages