From 929c4b836ffaaa056ae0e940786a6b19df9aaa68 Mon Sep 17 00:00:00 2001 From: "qichi.liang" Date: Wed, 31 Dec 2025 05:21:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B0=BA=E5=AF=B8?= =?UTF-8?q?=E7=AE=B1=E9=87=8F=E8=A7=A3=E6=9E=90=E5=92=8C=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新ShipLog数据类以支持20尺和40尺箱量字段 - 修改日志解析器提取尺寸箱量数据(支持格式如'95TEU(20尺*95)'和'90TEU(20尺*52 40尺*19)') - 更新数据库表结构存储尺寸箱量 - 修改报告生成器在日报中显示尺寸箱量信息 - 修复解析器分隔符处理逻辑 - 确保二次靠泊记录尺寸箱量正确合并 --- src/confluence/log_parser.py | 67 +++++++++++++++++++++++++++++++----- src/database/daily_logs.py | 23 ++++++++----- src/report.py | 39 +++++++++++++++++---- 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/confluence/log_parser.py b/src/confluence/log_parser.py index b554c2b..6fed18e 100644 --- a/src/confluence/log_parser.py +++ b/src/confluence/log_parser.py @@ -22,6 +22,8 @@ class ShipLog: teu: Optional[int] = None efficiency: Optional[float] = None vehicles: Optional[int] = None + twenty_feet: Optional[int] = None # 20尺箱量 + forty_feet: Optional[int] = None # 40尺箱量 def to_dict(self) -> Dict[str, Any]: """转换为字典""" @@ -129,19 +131,21 @@ class HandoverLogParser: processed_text = '\n'.join(processed_lines) blocks = processed_text.split(self.SEPARATOR) + current_date = None for block in blocks: - if not block.strip() or '日期:' not in block: + if not block.strip(): continue - # 解析日期 + # 检查块中是否包含日期 date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block) - if not date_match: - continue + if date_match: + current_date = self.parse_date(date_match.group(1)) - date = self.parse_date(date_match.group(1)) - self._parse_block(block, date, logs) + # 如果当前有日期,解析该块 + if current_date: + self._parse_block(block, current_date, logs) - # 合并同日期同班次同船名的记录(累加TEU) + # 合并同日期同班次同船名的记录(累加TEU和尺寸箱量) merged: Dict[Tuple[str, str, str], ShipLog] = {} for log in logs: key = (log.date, log.shift, log.ship_name) @@ -152,7 +156,9 @@ class HandoverLogParser: ship_name=log.ship_name, teu=log.teu, efficiency=log.efficiency, - vehicles=log.vehicles + vehicles=log.vehicles, + twenty_feet=log.twenty_feet, + forty_feet=log.forty_feet ) else: # 累加TEU @@ -167,6 +173,18 @@ class HandoverLogParser: merged[key].vehicles = log.vehicles else: merged[key].vehicles += log.vehicles + # 累加20尺箱量 + if log.twenty_feet: + if merged[key].twenty_feet is None: + merged[key].twenty_feet = log.twenty_feet + else: + merged[key].twenty_feet += log.twenty_feet + # 累加40尺箱量 + if log.forty_feet: + if merged[key].forty_feet is None: + merged[key].forty_feet = log.forty_feet + else: + merged[key].forty_feet += log.forty_feet result = list(merged.values()) logger.info(f"日志解析完成,共 {len(result)} 条记录") @@ -243,13 +261,44 @@ class HandoverLogParser: 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 + vehicles=vehicles, + twenty_feet=twenty_feet, + forty_feet=forty_feet ) logs.append(log) diff --git a/src/database/daily_logs.py b/src/database/daily_logs.py index c3e7bad..cf3f463 100644 --- a/src/database/daily_logs.py +++ b/src/database/daily_logs.py @@ -40,6 +40,8 @@ class DailyLogsDatabase(DatabaseBase): teu INTEGER, efficiency REAL, vehicles INTEGER, + twenty_feet INTEGER, -- 20尺箱量 + forty_feet INTEGER, -- 40尺箱量 created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE ) @@ -48,8 +50,8 @@ class DailyLogsDatabase(DatabaseBase): # 检查是否需要迁移旧表结构 cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='daily_handover_logs'") table_sql = cursor.fetchone()[0] - if 'UNIQUE' not in table_sql: - logger.warning("检测到旧表结构,正在迁移...") + if 'twenty_feet' not in table_sql or 'forty_feet' not in table_sql: + logger.warning("检测到旧表结构,正在迁移以添加尺寸箱量字段...") # 重命名旧表 cursor.execute('ALTER TABLE daily_handover_logs RENAME TO daily_handover_logs_old') @@ -64,6 +66,8 @@ class DailyLogsDatabase(DatabaseBase): teu INTEGER, efficiency REAL, vehicles INTEGER, + twenty_feet INTEGER, -- 20尺箱量 + forty_feet INTEGER, -- 40尺箱量 created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE ) @@ -71,15 +75,15 @@ class DailyLogsDatabase(DatabaseBase): # 复制数据(忽略重复) cursor.execute(''' - INSERT OR IGNORE INTO daily_handover_logs + INSERT OR IGNORE INTO daily_handover_logs (date, shift, ship_name, teu, efficiency, vehicles, created_at) - SELECT date, shift, ship_name, teu, efficiency, vehicles, created_at + SELECT date, shift, ship_name, teu, efficiency, vehicles, created_at FROM daily_handover_logs_old ''') # 删除旧表 cursor.execute('DROP TABLE daily_handover_logs_old') - logger.info("迁移完成!") + logger.info("迁移完成!已添加尺寸箱量字段") # 创建索引 cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON daily_handover_logs(date)') @@ -111,13 +115,14 @@ class DailyLogsDatabase(DatabaseBase): """ try: query = ''' - INSERT OR REPLACE INTO daily_handover_logs - (date, shift, ship_name, teu, efficiency, vehicles, created_at) - VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + INSERT OR REPLACE INTO daily_handover_logs + (date, shift, ship_name, teu, efficiency, vehicles, twenty_feet, forty_feet, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP) ''' params = ( log['date'], log['shift'], log['ship_name'], - log.get('teu'), log.get('efficiency'), log.get('vehicles') + log.get('teu'), log.get('efficiency'), log.get('vehicles'), + log.get('twenty_feet'), log.get('forty_feet') ) self.execute_update(query, params) diff --git a/src/report.py b/src/report.py index e367189..4b86480 100644 --- a/src/report.py +++ b/src/report.py @@ -63,19 +63,29 @@ class DailyReportGenerator: try: logs = self.db.query_by_date(date) - # 按船名汇总 - ships: Dict[str, int] = {} + # 按船名汇总TEU和尺寸箱量 + ships: Dict[str, Dict[str, Any]] = {} for log in logs: ship = log['ship_name'] if ship not in ships: - ships[ship] = 0 + ships[ship] = { + 'teu': 0, + 'twenty_feet': 0, + 'forty_feet': 0 + } if log.get('teu'): - ships[ship] += log['teu'] + ships[ship]['teu'] += log['teu'] + if log.get('twenty_feet'): + ships[ship]['twenty_feet'] += log['twenty_feet'] + if log.get('forty_feet'): + ships[ship]['forty_feet'] += log['forty_feet'] + + total_teu = sum(ship_data['teu'] for ship_data in ships.values()) return { 'date': date, 'ships': ships, - 'total_teu': sum(ships.values()), + 'total_teu': total_teu, 'ship_count': len(ships) } @@ -245,9 +255,24 @@ class DailyReportGenerator: # 船次信息 if daily_data['ships']: ship_lines: List[str] = [] - for ship, teu in sorted(daily_data['ships'].items(), key=lambda x: -x[1]): + for ship, ship_data in sorted(daily_data['ships'].items(), key=lambda x: -x[1]['teu']): ship_lines.append(f"船名:{ship}") - ship_lines.append(f"作业量:{teu}TEU") + teu = ship_data['teu'] + twenty_feet = ship_data.get('twenty_feet', 0) + forty_feet = ship_data.get('forty_feet', 0) + + # 构建尺寸箱量字符串 + size_parts = [] + if twenty_feet > 0: + size_parts.append(f"20尺*{twenty_feet}") + if forty_feet > 0: + size_parts.append(f"40尺*{forty_feet}") + + if size_parts: + size_str = " ".join(size_parts) + ship_lines.append(f"作业量:{teu}TEU({size_str})") + else: + ship_lines.append(f"作业量:{teu}TEU") lines.extend(ship_lines) lines.append("")