parse_order_info.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 订单信息解析脚本:支持编号列表格式和自然语言格式
  5. """
  6. import re
  7. def parse_structured_order(text: str) -> dict:
  8. """
  9. 解析编号列表格式的订单信息
  10. """
  11. result = {}
  12. # 基本信息提取
  13. m = re.search(r'姓名[::]\s*(.+?)[;;\n]', text)
  14. result['sales_name'] = m.group(1).strip() if m else ''
  15. m = re.search(r'任职[::]\s*(.+?)[;;\n]', text)
  16. result['sales_title'] = m.group(1).strip() if m else ''
  17. m = re.search(r'合同签约公司中文名称[::]\s*(.+?)[;;\n]', text)
  18. result['buyer_cn'] = m.group(1).strip() if m else ''
  19. m = re.search(r'合同签约公司英文名称[::]\s*(.+?)(?:\n|$)', text)
  20. result['buyer_en'] = m.group(1).strip() if m else ''
  21. m = re.search(r'合同签约公司中文地址[::]\s*(.+?)[;;\n]', text)
  22. result['address_cn'] = m.group(1).strip() if m else ''
  23. m = re.search(r'合同签约公司英文地址[::]\s*(.+?)(?:\n|$)', text)
  24. result['address_en'] = m.group(1).strip() if m else ''
  25. m = re.search(r'合同联系电话[::]\s*([\d+\-]+)', text)
  26. result['tel'] = m.group(1).strip() if m else ''
  27. m = re.search(r'国内出发港口[::]\s*(.+?)[;;\n]', text)
  28. result['departure_port'] = m.group(1).strip() if m else ''
  29. m = re.search(r'目的地国家[::]\s*(.+?)(?:\n|$)', text)
  30. result['destination_country'] = m.group(1).strip() if m else ''
  31. m = re.search(r'目的地港口[::]\s*(.+?)(?:\n|$)', text)
  32. result['destination_port'] = m.group(1).strip() if m else ''
  33. m = re.search(r'结算方式[::]\s*(FCA|FOB|EXW|CIF)', text, re.IGNORECASE)
  34. result['trade_term'] = m.group(1).upper() if m else 'FCA'
  35. # 车型列表解析 - 使用行扫描方式
  36. vehicles = []
  37. lines = text.split('\n')
  38. i = 0
  39. while i < len(lines):
  40. line = lines[i].strip()
  41. # 匹配车型起始行: (数字)... 型号:XXX
  42. m = re.match(r'[((](\d+)[))]\s*(.+?)\s*型号[::]\s*(\w+)', line)
  43. if m:
  44. seq = m.group(1)
  45. name_part = m.group(2).strip()
  46. model_code = m.group(3).strip()
  47. # 解析车型名称(中英文分离)
  48. name_cn, name_en = split_name_cn_en(name_part)
  49. # 下一行提取排放、发动机代码、数量、颜色
  50. engine_code = ''
  51. emission = ''
  52. quantity = 1
  53. color = ''
  54. if i + 1 < len(lines):
  55. next_line = lines[i + 1].strip()
  56. # 模式: 排放标准 代码:XXX X台颜色
  57. m2 = re.search(r'(.+?)\s+代码[::]\s*(\w+)\s+(\d+)台(\w+)', next_line)
  58. if m2:
  59. emission = m2.group(1).strip()
  60. engine_code = m2.group(2).strip()
  61. quantity = int(m2.group(3))
  62. color = m2.group(4).strip()
  63. i += 1 # 跳过已处理的下一行
  64. vehicles.append({
  65. 'seq': seq,
  66. 'name_cn': name_cn,
  67. 'name_en': name_en,
  68. 'model_code': model_code,
  69. 'emission': emission,
  70. 'engine_code': engine_code,
  71. 'quantity': quantity,
  72. 'color': color,
  73. 'unit_price_usd': None,
  74. 'config_desc': '',
  75. })
  76. i += 1
  77. result['vehicles'] = vehicles
  78. return result
  79. def split_name_cn_en(name_part: str) -> tuple:
  80. """
  81. 将车型描述分割为中文名和英文名
  82. 例如:
  83. '荣光新双排货车 N350 Double Cab Pickup,-' -> ('荣光新双排货车', 'N350 Double Cab Pickup')
  84. '五菱荣光新卡双后轮 2.0L 5MT 单排' -> ('五菱荣光新卡双后轮 2.0L 5MT 单排', '')
  85. """
  86. # 移除末尾的标点符号
  87. name_part = re.sub(r'[,,、\-]+$', '', name_part).strip()
  88. # 查找中英文分界点(连续英文字母序列)
  89. # 策略:找到最后一个中文字符之后的主要英文部分
  90. # 更简单的策略:如果包含明显的中英分界(中文字符后接大段英文)
  91. # 匹配模式: 中文部分 + 空格 + 英文部分(英文部分以字母/数字开头,包含字母、数字和空格)
  92. m = re.match(r'([\u4e00-\u9fff][\u4e00-\u9fff\s\d.]+?)\s+([A-Za-z0-9][A-Za-z0-9\s\(\)]+)$', name_part)
  93. if m:
  94. return m.group(1).strip(), m.group(2).strip()
  95. # 如果没有明显的英文部分,全部作为中文名
  96. if re.search(r'[\u4e00-\u9fff]', name_part):
  97. return name_part, ''
  98. # 如果没有中文,全部作为英文名
  99. return '', name_part
  100. def parse_natural_language(text: str) -> dict:
  101. """
  102. 使用正则从自然语言中提取关键字段
  103. """
  104. result = {}
  105. # 买方公司名称
  106. m = re.search(r'(?:买方|买方公司|签约公司|客户)[是|::]\s*([^,,;;。\n]+)', text)
  107. if m:
  108. name = m.group(1).strip()
  109. if re.search(r'[\u4e00-\u9fff]', name):
  110. result['buyer_cn'] = name
  111. result['buyer_en'] = ''
  112. else:
  113. result['buyer_en'] = name
  114. result['buyer_cn'] = ''
  115. if not result.get('buyer_en'):
  116. m = re.search(r'英文名称[是|::]\s*([^,,;;。\n]+)', text)
  117. if m:
  118. result['buyer_en'] = m.group(1).strip()
  119. if not result.get('buyer_cn'):
  120. m = re.search(r'中文名称[是|::]\s*([^,,;;。\n]+)', text)
  121. if m:
  122. result['buyer_cn'] = m.group(1).strip()
  123. # 地址
  124. m = re.search(r'(?:地址|ADD)[是|::]\s*([^,,;;。\n]+)', text)
  125. if m:
  126. addr = m.group(1).strip()
  127. if re.search(r'[\u4e00-\u9fff]', addr):
  128. result['address_cn'] = addr
  129. result['address_en'] = ''
  130. else:
  131. result['address_en'] = addr
  132. result['address_cn'] = ''
  133. # 电话
  134. m = re.search(r'(?:电话|Tel|联系电话)[是|::]\s*([\d+\-]+)', text)
  135. if m:
  136. result['tel'] = m.group(1).strip()
  137. # 贸易条款
  138. m = re.search(r'(?:贸易条款|结算方式|Incoterm)[是|::]?\s*(FCA|FOB|EXW|CIF)', text, re.IGNORECASE)
  139. result['trade_term'] = m.group(1).upper() if m else 'FCA'
  140. # 出发港口
  141. m = re.search(r'(?:出发港|出发港口|起运港)[是|::]\s*([^,,;;。\n]+)', text)
  142. result['departure_port'] = m.group(1).strip() if m else ''
  143. # 目的地国家
  144. m = re.search(r'(?:目的地国家|目的国|出口到|发往)[是|::]?\s*([^,,;;。\n]+)', text)
  145. result['destination_country'] = m.group(1).strip() if m else ''
  146. # 车型解析
  147. vehicles = []
  148. # 模式1: ... 型号:XXX ... 代码:XXX ... X台
  149. pattern = r'(?:车型[\d]*[::、..]?\s*)?(.+?)\s+(?:型号[::]\s*)?(LZW\w+).*?(?:代码[::]\s*)?(\w+).*?(\d+)台'
  150. matches = re.findall(pattern, text, re.DOTALL)
  151. for m in matches:
  152. name_part = m[0].strip()
  153. name_cn, name_en = split_name_cn_en(name_part)
  154. vehicles.append({
  155. 'name_cn': name_cn,
  156. 'name_en': name_en,
  157. 'model_code': m[1].strip(),
  158. 'emission': '',
  159. 'engine_code': m[2].strip(),
  160. 'quantity': int(m[3]),
  161. 'color': '',
  162. 'unit_price_usd': None,
  163. 'config_desc': '',
  164. })
  165. if not vehicles:
  166. pattern2 = r'(LZW\w+)\s+(?:发动机代码[::]\s*)?(\w+).*?(\d+)台'
  167. matches = re.findall(pattern2, text)
  168. for m in matches:
  169. vehicles.append({
  170. 'name_cn': '',
  171. 'name_en': '',
  172. 'model_code': m[0].strip(),
  173. 'emission': '',
  174. 'engine_code': m[1].strip(),
  175. 'quantity': int(m[2]),
  176. 'color': '',
  177. 'unit_price_usd': None,
  178. 'config_desc': '',
  179. })
  180. result['vehicles'] = vehicles
  181. return result
  182. def parse_order_info(text: str) -> dict:
  183. """
  184. 主解析函数:自动判断格式并解析
  185. """
  186. text = text.strip()
  187. # 判断是否为结构化编号列表格式
  188. is_structured = bool(re.search(r'^\d+[..]\s*(姓名|任职|意向车型|合同签约公司)', text, re.MULTILINE))
  189. if is_structured:
  190. result = parse_structured_order(text)
  191. else:
  192. result = parse_natural_language(text)
  193. # 填充默认值
  194. if not result.get('trade_term'):
  195. result['trade_term'] = 'FCA'
  196. if not result.get('departure_port'):
  197. result['departure_port'] = 'Guangzhou nansha Port'
  198. return result
  199. if __name__ == '__main__':
  200. import json
  201. import sys
  202. sys.stdout.reconfigure(encoding='utf-8')
  203. with open('../assets/订单合同信息案例.txt', encoding='utf-8') as f:
  204. test_text = f.read()
  205. result = parse_order_info(test_text)
  206. print(json.dumps(result, ensure_ascii=False, indent=2))