feat: 添加尺寸箱量解析和显示功能
- 更新ShipLog数据类以支持20尺和40尺箱量字段 - 修改日志解析器提取尺寸箱量数据(支持格式如'95TEU(20尺*95)'和'90TEU(20尺*52 40尺*19)') - 更新数据库表结构存储尺寸箱量 - 修改报告生成器在日报中显示尺寸箱量信息 - 修复解析器分隔符处理逻辑 - 确保二次靠泊记录尺寸箱量正确合并
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -79,7 +83,7 @@ class DailyLogsDatabase(DatabaseBase):
|
||||
|
||||
# 删除旧表
|
||||
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)')
|
||||
@@ -112,12 +116,13 @@ class DailyLogsDatabase(DatabaseBase):
|
||||
try:
|
||||
query = '''
|
||||
INSERT OR REPLACE INTO daily_handover_logs
|
||||
(date, shift, ship_name, teu, efficiency, vehicles, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
(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)
|
||||
|
||||
@@ -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("")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user