mirror of
https://devops.liangqichi.top/qichi.liang/Orbitin.git
synced 2026-02-10 07:41:29 +08:00
feat: 添加转堆作业解析功能
- 新增 _parse_relocation() 方法解析转堆作业 - 新增 _parse_log_entry() 方法复用解析逻辑 - 转堆作业使用 '转堆作业' 作为船名标识
This commit is contained in:
@@ -241,6 +241,12 @@ class HandoverLogParser:
|
||||
def _parse_ships(self, content: str, date: str, shift: str, logs: List[ShipLog]) -> None:
|
||||
"""解析船次"""
|
||||
try:
|
||||
# 首先解析转堆作业(无船名)
|
||||
relocation_content = self._parse_relocation(content, date, shift)
|
||||
if relocation_content:
|
||||
logs.append(relocation_content)
|
||||
|
||||
# 然后解析实船作业(按 "实船作业:" 分割)
|
||||
parts = content.split('实船作业:')
|
||||
|
||||
for part in parts:
|
||||
@@ -259,71 +265,163 @@ class HandoverLogParser:
|
||||
# 移除二次靠泊等标注
|
||||
ship_name = re.sub(r'(二次靠泊)|(再次靠泊)|\(二次靠泊\)|\(再次靠泊\)', '', ship_name).strip()
|
||||
|
||||
vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned)
|
||||
teu_eff_match = re.search(
|
||||
r'作业量/效率:(\d+)TEU[,,\s]*', cleaned
|
||||
)
|
||||
|
||||
# 解析TEU
|
||||
teu = None
|
||||
if teu_eff_match:
|
||||
try:
|
||||
teu = int(teu_eff_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"TEU解析失败: {teu_eff_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 解析车辆数
|
||||
vehicles = None
|
||||
if vehicles_match:
|
||||
try:
|
||||
vehicles = int(vehicles_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"车辆数解析失败: {vehicles_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 解析尺寸箱量
|
||||
twenty_feet = None
|
||||
forty_feet = None
|
||||
|
||||
# 查找作业量/效率后面的括号内的尺寸信息
|
||||
# 匹配模式:TEU数字后跟括号,括号内包含尺寸信息
|
||||
size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned)
|
||||
if not size_pattern:
|
||||
# 也尝试匹配没有逗号的情况
|
||||
size_pattern = re.search(r'TEU\s*(([^)]+))', cleaned)
|
||||
|
||||
if size_pattern:
|
||||
size_text = size_pattern.group(1)
|
||||
# 匹配20尺*数字
|
||||
twenty_match = re.search(r'20尺\*(\d+)', size_text)
|
||||
if twenty_match:
|
||||
try:
|
||||
twenty_feet = int(twenty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"20尺箱量解析失败: {twenty_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 匹配40尺*数字
|
||||
forty_match = re.search(r'40尺\*(\d+)', size_text)
|
||||
if forty_match:
|
||||
try:
|
||||
forty_feet = int(forty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"40尺箱量解析失败: {forty_match.group(1)}, 错误: {e}")
|
||||
|
||||
log = ShipLog(
|
||||
date=date,
|
||||
shift=shift,
|
||||
ship_name=ship_name,
|
||||
teu=teu,
|
||||
efficiency=None, # 目前日志中没有效率数据
|
||||
vehicles=vehicles,
|
||||
twenty_feet=twenty_feet,
|
||||
forty_feet=forty_feet
|
||||
)
|
||||
logs.append(log)
|
||||
# 解析车辆数、TEU、尺寸箱量
|
||||
self._parse_log_entry(cleaned, date, shift, ship_name, logs)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"解析船次失败: {date} {shift}, 错误: {e}")
|
||||
|
||||
def _parse_relocation(self, content: str, date: str, shift: str) -> Optional[ShipLog]:
|
||||
"""
|
||||
解析转堆作业(无船名的作业类型)
|
||||
|
||||
参数:
|
||||
content: 班次内容
|
||||
date: 日期
|
||||
shift: 班次
|
||||
|
||||
返回:
|
||||
ShipLog 对象,如果未找到转堆作业则返回 None
|
||||
"""
|
||||
try:
|
||||
# 检查是否包含转堆作业
|
||||
if '转堆作业:' not in content:
|
||||
return None
|
||||
|
||||
# 提取转堆作业部分(从 "转堆作业:" 到下一个空行或下一个实船作业)
|
||||
relocation_start = content.find('转堆作业:') + len('转堆作业:')
|
||||
|
||||
# 查找下一个 "实船作业:" 作为结束标记
|
||||
next_ship = content.find('实船作业:', relocation_start)
|
||||
if next_ship == -1:
|
||||
relocation_content = content[relocation_start:]
|
||||
else:
|
||||
relocation_content = content[relocation_start:next_ship]
|
||||
|
||||
cleaned = relocation_content.replace('\xa0', ' ').strip()
|
||||
|
||||
# 解析车辆数、TEU、尺寸箱量
|
||||
vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned)
|
||||
teu_eff_match = re.search(r'作业量/效率:(\d+)TEU', cleaned)
|
||||
|
||||
teu = None
|
||||
if teu_eff_match:
|
||||
try:
|
||||
teu = int(teu_eff_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"转堆作业 TEU 解析失败: {teu_eff_match.group(1)}, 错误: {e}")
|
||||
|
||||
vehicles = None
|
||||
if vehicles_match:
|
||||
try:
|
||||
vehicles = int(vehicles_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"转堆作业车辆数解析失败: {vehicles_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 解析尺寸箱量
|
||||
twenty_feet = None
|
||||
forty_feet = None
|
||||
size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned)
|
||||
if not size_pattern:
|
||||
size_pattern = re.search(r'TEU\s*(([^)]+))', cleaned)
|
||||
|
||||
if size_pattern:
|
||||
size_text = size_pattern.group(1)
|
||||
twenty_match = re.search(r'20尺\*(\d+)', size_text)
|
||||
if twenty_match:
|
||||
try:
|
||||
twenty_feet = int(twenty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"转堆作业 20尺箱量解析失败: {twenty_match.group(1)}, 错误: {e}")
|
||||
|
||||
forty_match = re.search(r'40尺\*(\d+)', size_text)
|
||||
if forty_match:
|
||||
try:
|
||||
forty_feet = int(forty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"转堆作业 40尺箱量解析失败: {forty_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 使用 "转堆作业" 作为船名标识
|
||||
log = ShipLog(
|
||||
date=date,
|
||||
shift=shift,
|
||||
ship_name='转堆作业',
|
||||
teu=teu,
|
||||
efficiency=None,
|
||||
vehicles=vehicles,
|
||||
twenty_feet=twenty_feet,
|
||||
forty_feet=forty_feet
|
||||
)
|
||||
|
||||
logger.info(f"解析转堆作业: {date} {shift} {log.teu}TEU")
|
||||
return log
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"解析转堆作业失败: {date} {shift}, 错误: {e}")
|
||||
return None
|
||||
|
||||
def _parse_log_entry(self, cleaned: str, date: str, shift: str, ship_name: str, logs: List[ShipLog]) -> None:
|
||||
"""解析单条日志记录(车辆数、TEU、尺寸箱量)"""
|
||||
try:
|
||||
vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned)
|
||||
teu_eff_match = re.search(r'作业量/效率:(\d+)TEU[,,\s]*', cleaned)
|
||||
|
||||
# 解析TEU
|
||||
teu = None
|
||||
if teu_eff_match:
|
||||
try:
|
||||
teu = int(teu_eff_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"TEU解析失败: {teu_eff_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 解析车辆数
|
||||
vehicles = None
|
||||
if vehicles_match:
|
||||
try:
|
||||
vehicles = int(vehicles_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"车辆数解析失败: {vehicles_match.group(1)}, 错误: {e}")
|
||||
|
||||
# 解析尺寸箱量
|
||||
twenty_feet = None
|
||||
forty_feet = None
|
||||
|
||||
# 查找作业量/效率后面的括号内的尺寸信息
|
||||
size_pattern = re.search(r'TEU[,,\s]*(([^)]+))', cleaned)
|
||||
if not size_pattern:
|
||||
size_pattern = re.search(r'TEU\s*(([^)]+))', cleaned)
|
||||
|
||||
if size_pattern:
|
||||
size_text = size_pattern.group(1)
|
||||
twenty_match = re.search(r'20尺\*(\d+)', size_text)
|
||||
if twenty_match:
|
||||
try:
|
||||
twenty_feet = int(twenty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"20尺箱量解析失败: {twenty_match.group(1)}, 错误: {e}")
|
||||
|
||||
forty_match = re.search(r'40尺\*(\d+)', size_text)
|
||||
if forty_match:
|
||||
try:
|
||||
forty_feet = int(forty_match.group(1))
|
||||
except ValueError as e:
|
||||
logger.warning(f"40尺箱量解析失败: {forty_match.group(1)}, 错误: {e}")
|
||||
|
||||
log = ShipLog(
|
||||
date=date,
|
||||
shift=shift,
|
||||
ship_name=ship_name,
|
||||
teu=teu,
|
||||
efficiency=None,
|
||||
vehicles=vehicles,
|
||||
twenty_feet=twenty_feet,
|
||||
forty_feet=forty_feet
|
||||
)
|
||||
logs.append(log)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"解析日志记录失败: {date} {shift} {ship_name}, 错误: {e}")
|
||||
|
||||
def parse_from_file(self, filepath: str) -> List[ShipLog]:
|
||||
"""
|
||||
从文件解析日志
|
||||
|
||||
Reference in New Issue
Block a user