mirror of
https://devops.liangqichi.top/qichi.liang/Orbitin.git
synced 2026-02-10 07:41:29 +08:00
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
|
teu: Optional[int] = None
|
||||||
efficiency: Optional[float] = None
|
efficiency: Optional[float] = None
|
||||||
vehicles: Optional[int] = None
|
vehicles: Optional[int] = None
|
||||||
|
twenty_feet: Optional[int] = None # 20尺箱量
|
||||||
|
forty_feet: Optional[int] = None # 40尺箱量
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""转换为字典"""
|
"""转换为字典"""
|
||||||
@@ -129,19 +131,21 @@ class HandoverLogParser:
|
|||||||
processed_text = '\n'.join(processed_lines)
|
processed_text = '\n'.join(processed_lines)
|
||||||
blocks = processed_text.split(self.SEPARATOR)
|
blocks = processed_text.split(self.SEPARATOR)
|
||||||
|
|
||||||
|
current_date = None
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
if not block.strip() or '日期:' not in block:
|
if not block.strip():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 解析日期
|
# 检查块中是否包含日期
|
||||||
date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block)
|
date_match = re.search(r'日期:(\d{4}\.\d{2}\.\d{2})', block)
|
||||||
if not date_match:
|
if date_match:
|
||||||
continue
|
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] = {}
|
merged: Dict[Tuple[str, str, str], ShipLog] = {}
|
||||||
for log in logs:
|
for log in logs:
|
||||||
key = (log.date, log.shift, log.ship_name)
|
key = (log.date, log.shift, log.ship_name)
|
||||||
@@ -152,7 +156,9 @@ class HandoverLogParser:
|
|||||||
ship_name=log.ship_name,
|
ship_name=log.ship_name,
|
||||||
teu=log.teu,
|
teu=log.teu,
|
||||||
efficiency=log.efficiency,
|
efficiency=log.efficiency,
|
||||||
vehicles=log.vehicles
|
vehicles=log.vehicles,
|
||||||
|
twenty_feet=log.twenty_feet,
|
||||||
|
forty_feet=log.forty_feet
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 累加TEU
|
# 累加TEU
|
||||||
@@ -167,6 +173,18 @@ class HandoverLogParser:
|
|||||||
merged[key].vehicles = log.vehicles
|
merged[key].vehicles = log.vehicles
|
||||||
else:
|
else:
|
||||||
merged[key].vehicles += log.vehicles
|
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())
|
result = list(merged.values())
|
||||||
logger.info(f"日志解析完成,共 {len(result)} 条记录")
|
logger.info(f"日志解析完成,共 {len(result)} 条记录")
|
||||||
@@ -243,13 +261,44 @@ class HandoverLogParser:
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(f"车辆数解析失败: {vehicles_match.group(1)}, 错误: {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(
|
log = ShipLog(
|
||||||
date=date,
|
date=date,
|
||||||
shift=shift,
|
shift=shift,
|
||||||
ship_name=ship_name,
|
ship_name=ship_name,
|
||||||
teu=teu,
|
teu=teu,
|
||||||
efficiency=None, # 目前日志中没有效率数据
|
efficiency=None, # 目前日志中没有效率数据
|
||||||
vehicles=vehicles
|
vehicles=vehicles,
|
||||||
|
twenty_feet=twenty_feet,
|
||||||
|
forty_feet=forty_feet
|
||||||
)
|
)
|
||||||
logs.append(log)
|
logs.append(log)
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
teu INTEGER,
|
teu INTEGER,
|
||||||
efficiency REAL,
|
efficiency REAL,
|
||||||
vehicles INTEGER,
|
vehicles INTEGER,
|
||||||
|
twenty_feet INTEGER, -- 20尺箱量
|
||||||
|
forty_feet INTEGER, -- 40尺箱量
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE
|
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'")
|
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='daily_handover_logs'")
|
||||||
table_sql = cursor.fetchone()[0]
|
table_sql = cursor.fetchone()[0]
|
||||||
if 'UNIQUE' not in table_sql:
|
if 'twenty_feet' not in table_sql or 'forty_feet' not in table_sql:
|
||||||
logger.warning("检测到旧表结构,正在迁移...")
|
logger.warning("检测到旧表结构,正在迁移以添加尺寸箱量字段...")
|
||||||
|
|
||||||
# 重命名旧表
|
# 重命名旧表
|
||||||
cursor.execute('ALTER TABLE daily_handover_logs RENAME TO daily_handover_logs_old')
|
cursor.execute('ALTER TABLE daily_handover_logs RENAME TO daily_handover_logs_old')
|
||||||
@@ -64,6 +66,8 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
teu INTEGER,
|
teu INTEGER,
|
||||||
efficiency REAL,
|
efficiency REAL,
|
||||||
vehicles INTEGER,
|
vehicles INTEGER,
|
||||||
|
twenty_feet INTEGER, -- 20尺箱量
|
||||||
|
forty_feet INTEGER, -- 40尺箱量
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE
|
UNIQUE(date, shift, ship_name) ON CONFLICT REPLACE
|
||||||
)
|
)
|
||||||
@@ -79,7 +83,7 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
|
|
||||||
# 删除旧表
|
# 删除旧表
|
||||||
cursor.execute('DROP TABLE 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)')
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_date ON daily_handover_logs(date)')
|
||||||
@@ -112,12 +116,13 @@ class DailyLogsDatabase(DatabaseBase):
|
|||||||
try:
|
try:
|
||||||
query = '''
|
query = '''
|
||||||
INSERT OR REPLACE INTO daily_handover_logs
|
INSERT OR REPLACE INTO daily_handover_logs
|
||||||
(date, shift, ship_name, teu, efficiency, vehicles, created_at)
|
(date, shift, ship_name, teu, efficiency, vehicles, twenty_feet, forty_feet, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
'''
|
'''
|
||||||
params = (
|
params = (
|
||||||
log['date'], log['shift'], log['ship_name'],
|
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)
|
self.execute_update(query, params)
|
||||||
|
|||||||
@@ -63,19 +63,29 @@ class DailyReportGenerator:
|
|||||||
try:
|
try:
|
||||||
logs = self.db.query_by_date(date)
|
logs = self.db.query_by_date(date)
|
||||||
|
|
||||||
# 按船名汇总
|
# 按船名汇总TEU和尺寸箱量
|
||||||
ships: Dict[str, int] = {}
|
ships: Dict[str, Dict[str, Any]] = {}
|
||||||
for log in logs:
|
for log in logs:
|
||||||
ship = log['ship_name']
|
ship = log['ship_name']
|
||||||
if ship not in ships:
|
if ship not in ships:
|
||||||
ships[ship] = 0
|
ships[ship] = {
|
||||||
|
'teu': 0,
|
||||||
|
'twenty_feet': 0,
|
||||||
|
'forty_feet': 0
|
||||||
|
}
|
||||||
if log.get('teu'):
|
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 {
|
return {
|
||||||
'date': date,
|
'date': date,
|
||||||
'ships': ships,
|
'ships': ships,
|
||||||
'total_teu': sum(ships.values()),
|
'total_teu': total_teu,
|
||||||
'ship_count': len(ships)
|
'ship_count': len(ships)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,9 +255,24 @@ class DailyReportGenerator:
|
|||||||
# 船次信息
|
# 船次信息
|
||||||
if daily_data['ships']:
|
if daily_data['ships']:
|
||||||
ship_lines: List[str] = []
|
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"船名:{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.extend(ship_lines)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user