12 Commits
gui ... main

Author SHA1 Message Date
486313044c fix: 修复排班表读取时缓存优先的问题
- 移除了缓存优先逻辑,改为每次从飞书获取最新数据
- 每次都覆盖保存到数据库,确保人员变动能及时更新
- 移除了 check_sheet_update 检查(因为飞书表格即使人员变动,版本号也可能不变)
2026-01-07 20:06:56 +08:00
84e907b93e feat: 日志日期分片和尺寸解析增强
- 日志文件按日期分片存储 (logs/YYYY-MM/YYYY-MM-DD.log)
- 增强尺寸箱量解析支持多种格式 (*和×分隔符)
- 支持带括号和无括号两种尺寸格式
2026-01-04 01:19:07 +08:00
5fd05fcd3c feat: 添加转堆作业解析功能
- 新增 _parse_relocation() 方法解析转堆作业
- 新增 _parse_log_entry() 方法复用解析逻辑
- 转堆作业使用 '转堆作业' 作为船名标识
2026-01-03 22:37:30 +08:00
f04478fd8f docs: 重写README为项目说明文档,包含功能特性、项目结构、核心模块说明 2026-01-02 03:00:28 +08:00
f5ba5624aa docs: 修正月底数据调整的描述,适用于每个月最后一天 2026-01-02 02:54:18 +08:00
071a3f05f3 docs: 重写使用手册,更清晰的操作指南
- 新增目录结构,便于快速导航
- 添加GUI界面布局说明(ASCII图示)
- 详细说明各功能的使用场景和操作步骤
- 新增常见场景章节(日常使用、月底处理、月初补调、跨日船)
- 完善故障排除章节
- 添加附录(文件结构、数据库表结构、版本历史)
2026-01-02 02:48:15 +08:00
bb3f25a643 fix: 修复月份选择器问题,确保12月正确显示
- 修复跨年月份计算逻辑(1月时正确计算为去年12月)
- 改进_get_month_list()方法,生成正确的近12个月列表
- 增加Combobox宽度以完整显示月份值如'2025-12'
- 优化手动剔除次月多统计的船对话框
2026-01-02 02:46:56 +08:00
53eef800b4 feat: 新增月底/月初数据调整和Confluence月份页面映射功能
- 新增月底最后一天自动剔除12点后数据功能
- 实现月底剔除数据自动转移到次月1号
- 新增Confluence月份页面ID映射功能,解决每月页面ID变化问题
- 修复1月份页面解析问题,支持'2026.1.1'日期格式
- 优化GUI界面,增加页面ID配置管理
- 精简README文档,增加详细功能说明
- 修复月度统计计算包含调整数据的问题
2026-01-02 01:29:03 +08:00
1b688c1603 refactor: 移除月初1号的添加数据对话框
优化逻辑:既然月底剔除的数据已经自动添加到次月1号,月初1号就不需要再弹窗让用户手动填写了。

修改内容:
1. 修改_handle_post_fetch_adjustment()方法
   - 只保留月底最后一天的弹窗逻辑
   - 移除月初1号的弹窗逻辑
   - 简化用户操作流程

2. 业务逻辑优化
   - 月底剔除数据 → 自动添加到次月1号
   - 月初1号不再需要用户手动填写
   - 减少了不必要的弹窗干扰

测试通过:月初1号不再弹出添加数据对话框,月底逻辑正常工作。
2026-01-02 00:19:47 +08:00
0cbc587bf3 feat: 实现月底/月初数据调整功能
1. 新增月底/月初智能数据调整功能
   - 月底最后一天自动弹出剔除数据对话框
   - 月初1号自动弹出添加数据对话框
   - 普通日期不弹出对话框

2. 实现月底剔除数据自动转移到次月1号
   - 月底剔除的数据自动添加到次月1号统计
   - 支持跨月、跨年数据转移
   - 数据备注自动记录转移信息

3. 修复自动获取数据后不弹出调整对话框的问题
   - 修改auto_fetch_data()方法,成功获取数据后调用调整处理
   - 确保第一次打开GUI也能弹出相应对话框

4. 修复月度统计不包含调整数据的问题
   - 修改get_monthly_stats()方法包含手动调整数据
   - 确保调整数据正确影响月度统计

5. 恢复日报原始模板格式
   - 移除调整数据的详细说明
   - 保持原始日报模板,只显示最终结果

6. 数据库增强
   - 新增manual_adjustments表存储手动调整数据
   - 实现调整数据的增删改查方法
   - 实现包含调整数据的每日数据获取方法

测试通过:所有功能正常工作,数据计算准确。
2026-01-02 00:08:57 +08:00
9b19015156 feat: 添加飞书tenant_access_token自动获取功能
- 在FeishuSheetsClient中添加_get_tenant_access_token()方法
- 实现token自动缓存和刷新机制(提前30分钟刷新)
- 更新配置类支持FEISHU_APP_ID和FEISHU_APP_SECRET
- 从.env中移除FEISHU_TOKEN,完全使用应用凭证
- 更新report.py和gui.py支持新的配置检查逻辑
- 更新FeishuScheduleManager配置检查逻辑
- 更新文档和示例文件说明新的配置方式

系统现在支持两种认证方式:
1. 推荐:使用应用凭证(FEISHU_APP_ID + FEISHU_APP_SECRET)
2. 备选:使用手动token(FEISHU_TOKEN)

所有功能测试通过,系统能自动获取、缓存和刷新token。
2025-12-31 06:03:51 +08:00
929c4b836f feat: 添加尺寸箱量解析和显示功能
- 更新ShipLog数据类以支持20尺和40尺箱量字段
- 修改日志解析器提取尺寸箱量数据(支持格式如'95TEU(20尺*95)'和'90TEU(20尺*52 40尺*19)')
- 更新数据库表结构存储尺寸箱量
- 修改报告生成器在日报中显示尺寸箱量信息
- 修复解析器分隔符处理逻辑
- 确保二次靠泊记录尺寸箱量正确合并
2025-12-31 05:21:16 +08:00
14 changed files with 3366 additions and 448 deletions

View File

@@ -5,5 +5,12 @@ CONFLUENCE_CONTENT_ID=155764524
# 飞书表格配置 # 飞书表格配置
FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3 FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3
FEISHU_TOKEN=your-feishu-api-token
FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh
# 飞书应用凭证推荐方式自动获取tenant_access_token
# 创建飞书自建应用后获取app_id和app_secret
FEISHU_APP_ID=your-feishu-app-id
FEISHU_APP_SECRET=your-feishu-app-secret
# 备选手动配置token不推荐token会过期
# FEISHU_TOKEN=your-feishu-api-token

2
.gitignore vendored
View File

@@ -23,6 +23,8 @@ debug/
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
AGENTS.md
# IDE # IDE
.vscode/ .vscode/
plans/

View File

@@ -68,6 +68,7 @@ OrbitIn/
- `get_ships_with_monthly_teu(year_month)` - 获取当月每艘船的作业量 - `get_ships_with_monthly_teu(year_month)` - 获取当月每艘船的作业量
- `insert_unaccounted(year_month, teu, note)` - 添加未统计数据 - `insert_unaccounted(year_month, teu, note)` - 添加未统计数据
- `get_unaccounted(year_month)` - 获取未统计数据 - `get_unaccounted(year_month)` - 获取未统计数据
- `delete_unaccounted(year_month)` - 去除未统计数据(对称功能)
### DailyReportGenerator (src/report.py:15) ### DailyReportGenerator (src/report.py:15)
@@ -79,6 +80,7 @@ OrbitIn/
- tkinter 图形界面 - tkinter 图形界面
- 支持获取数据、生成日报、添加未统计数据 - 支持获取数据、生成日报、添加未统计数据
- 支持去除多余统计数据(对称功能)
- 日报内容可复制 - 日报内容可复制
### FeishuScheduleManager (src/feishu.py:150) ### FeishuScheduleManager (src/feishu.py:150)
@@ -118,6 +120,9 @@ python3 main.py parse-test
# 添加未统计数据 # 添加未统计数据
python3 main.py --unaccounted 118 --month 2025-12 python3 main.py --unaccounted 118 --month 2025-12
# 去除未统计数据
python3 main.py --remove-unaccounted --month 2025-12
# GUI界面 # GUI界面
python3 src/gui.py python3 src/gui.py
``` ```

323
README.md
View File

@@ -1,17 +1,44 @@
# OrbitIn - 码头作业日志管理系统 # OrbitIn
从 Confluence API 获取交接班日志,提取作业数据并生成统计报表。 码头作业日志管理工具。从 Confluence API 获取交接班日志,提取作业数据并生成统计报表。
## 功能特性 ## 功能特性
- 从 Confluence 获取交接班日志 HTML - **数据获取**从 Confluence API 获取交接班日志 HTML
- 提取保留布局的文本内容 - **文本提取**提取保留布局的文本内容,支持表格格式化
- SQLite3 数据库存储 - **数据库存储**SQLite3 数据库存储,支持二次靠泊记录合并
- 生成日报和月度统计 - **报表生成**生成日报和月度统计,包含完成率计算
- 支持未统计数据手动录入 - **数据调整**:支持手动添加/剔除统计数据,月底数据自动转移
- 支持二次靠泊记录合并 - **智能调整**月底最后一天自动询问剔除12点后数据月初自动添加上月数据
- GUI 图形界面(可选) - **GUI界面**tkinter 图形界面,支持一键操作
- 飞书排班表集成(自动获取班次人员) - **飞书集成**自动获取排班人员信息支持应用凭证自动刷新token
- **月份页面映射**支持配置各月份的Confluence页面ID解决每月页面ID变化问题
## 快速开始
### 安装依赖
```bash
pip install requests beautifulsoup4 python-dotenv
```
### 配置
```bash
cp .env.example .env
# 编辑 .env 文件,填入配置
```
### 使用
```bash
# GUI 方式
python3 src/gui.py
# CLI 方式
python3 main.py fetch-save # 获取并保存数据
python3 main.py report 2025-12-28 # 生成日报
```
## 项目结构 ## 项目结构
@@ -19,12 +46,8 @@
OrbitIn/ OrbitIn/
├── main.py # CLI 入口 ├── main.py # CLI 入口
├── README.md # 项目说明 ├── README.md # 项目说明
├── AGENTS.md # AI助手开发文档
├── .env # 环境配置(敏感信息) ├── .env # 环境配置(敏感信息)
├── .env.example # 环境配置示例 ├── .env.example # 环境配置示例
├── layout_output.txt # 缓存的布局文本
├── debug/ # 调试输出目录
│ └── layout_output_*.txt # 带时间戳的调试文件
├── data/ # 数据目录 ├── data/ # 数据目录
│ ├── daily_logs.db # SQLite3 数据库 │ ├── daily_logs.db # SQLite3 数据库
│ └── schedule_cache.json # 排班数据缓存 │ └── schedule_cache.json # 排班数据缓存
@@ -45,174 +68,130 @@ OrbitIn/
│ ├── parser.py # HTML 内容解析器 │ ├── parser.py # HTML 内容解析器
│ ├── text.py # HTML 文本提取器 │ ├── text.py # HTML 文本提取器
│ ├── log_parser.py # 日志解析器 │ ├── log_parser.py # 日志解析器
── manager.py # 内容管理器 ── manager.py # 内容管理器
│ └── __init__.py # 模块导出
└── feishu/ # 飞书 API 模块 └── feishu/ # 飞书 API 模块
├── client.py # 飞书 API 客户端 ├── client.py # 飞书 API 客户端
├── parser.py # 排班数据解析器 ├── parser.py # 排班数据解析器
── manager.py # 飞书排班管理器 ── manager.py # 飞书排班管理器
└── __init__.py # 模块导出
``` ```
## 快速开始 ## 核心模块
### 安装依赖 ### ConfluenceClient (src/confluence/client.py)
```bash - `fetch_content(content_id, expand)` - 获取页面内容
pip install requests beautifulsoup4 python-dotenv - `get_html(content_id)` - 获取 HTML 字符串
```
### 配置 ### HTMLTextExtractor (src/confluence/text.py)
- `extract(html)` - 从 HTML 提取保留布局的文本
- 使用 `html.parser`(非 lxml
- 移除带 `ac:name` 属性的 Confluence 宏元素
- 表格格式化使用 `ljust()` 列对齐
### HandoverLogParser (src/confluence/log_parser.py)
- `parse(text)` - 解析日志文本,返回 `ShipLog` 列表
- 自动合并同日期同班次同船名的记录(二次靠泊)
- `ShipLog` 数据类date, shift, ship_name, teu, efficiency, vehicles
### DailyLogsDatabase (src/database/daily_logs.py)
- `insert(log)` - 插入单条记录(存在则跳过)
- `insert_many(logs)` - 批量插入
- `query_by_date(date)` - 按日期查询
- `query_by_ship(ship_name)` - 按船名查询
- `query_all(limit)` - 查询所有
- `get_stats()` - 获取统计信息
- `get_ships_with_monthly_teu(year_month)` - 获取当月每艘船的作业量
- `insert_cross_month_exclusion()` - 跨月数据调整
- `insert_confluence_page()` - 保存月份页面ID映射
- `get_confluence_page_for_date()` - 获取指定日期对应的页面ID
### DailyReportGenerator (src/report.py)
- `generate_report(date)` - 生成日报
- `print_report(date)` - 打印日报
- `get_shift_personnel(date)` - 获取班次人员(从飞书排班表获取)
### OrbitInGUI (src/gui.py)
- tkinter 图形界面
- 支持获取数据、生成日报
- 支持手动剔除次月多统计的船
- 日报内容可复制
### FeishuScheduleManager (src/feishu/manager.py)
- `get_schedule_for_date(date)` - 获取指定日期的排班信息
- `get_schedule_for_today()` - 获取今天的排班信息
- `refresh_all_schedules(days)` - 批量刷新排班信息
## 文本格式约定
- 列表前缀:`•` 用于 `ul`,数字+点用于 `ol`
- 粗体使用 `**text**`,斜体使用 `*text*`
- 水平线使用 `─` (U+2500) 字符
- 链接渲染为 `text (url)`
## 配置
`.env` 文件中配置: `.env` 文件中配置:
```bash ```bash
# .env
# Confluence 配置 # Confluence 配置
CONFLUENCE_BASE_URL=https://your-confluence.atlassian.net/rest/api CONFLUENCE_BASE_URL=https://confluence.westwell-lab.com/rest/api
CONFLUENCE_TOKEN=your-api-token CONFLUENCE_TOKEN=your-api-token
CONFLUENCE_CONTENT_ID=155764524 CONFLUENCE_CONTENT_ID=155764524
# 飞书表格配置(用于获取排班人员信息) # 飞书表格配置
FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3 FEISHU_BASE_URL=https://open.feishu.cn/open-apis/sheets/v3
FEISHU_TOKEN=your-feishu-api-token FEISHU_TOKEN=your-feishu-api-token
FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh FEISHU_SPREADSHEET_TOKEN=EgNPssi2ghZ7BLtGiTxcIBUmnVh
FEISHU_APP_ID=your-feishu-app-id
# 数据库配置 FEISHU_APP_SECRET=your-feishu-app-secret
DATABASE_PATH=data/daily_logs.db
# 业务配置 # 业务配置
DAILY_TARGET_TEU=300 # 每日目标TEU数量用于计算完成率 DAILY_TARGET_TEU=300
DUTY_PHONE=13107662315 # 值班电话,显示在日报中 DUTY_PHONE=13107662315
SEPARATOR_CHAR=# 分隔线字符,用于格式化输出
SEPARATOR_LENGTH=50 # 分隔线长度
SCHEDULE_REFRESH_DAYS=30 # 排班数据刷新间隔(天)
``` ```
参考 `.env.example` 文件创建 `.env` 文件。 ## 命令
### 使用方法
#### 命令行方式
```bash ```bash
# 默认:获取、提取、解析并保存到数据库 # 默认:获取、提取、解析并保存到数据库
python3 main.py fetch-save python3 main.py
# 仅获取HTML并提取文本保存到debug目录 # 仅获取HTML并提取文本
python3 main.py fetch python3 main.py fetch
# 获取并保存带时间戳的debug文件 # 生成日报
python3 main.py fetch-debug
# 生成日报(指定日期)
python3 main.py report 2025-12-28 python3 main.py report 2025-12-28
# 生成日日报 # 生成日日报
python3 main.py report-today python3 main.py report-today
# 配置测试(验证所有连接) # 手动剔除次月多统计的船
python3 main.py --cross-exclude --source-date 2025-12-31 --target-date 2026-01-01 --ship-name "学友洋山" --teu 100
# 配置测试
python3 main.py config-test python3 main.py config-test
# 添加未统计数据 # GUI界面
python3 main.py --unaccounted 118 --month 2025-12
# 显示帮助
python3 main.py --help
```
#### GUI 方式
```bash
python3 src/gui.py python3 src/gui.py
``` ```
GUI 功能: ## 测试模式
- 获取并处理数据
- 获取 (Debug模式)
- 生成日报
- 今日日报(自动获取前一天数据)
- 添加未统计数据
- 数据库统计(显示当月每艘船的作业量)
- 日报内容可复制
- 自动刷新排班信息
## 数据格式 如果设置了环境变量 `DEBUG_MODE=true`,系统会使用本地 `layout_output.txt` 文件而不是从 Confluence API 获取数据,方便离线测试。
### 日报表 (daily_handover_logs) ## 注意事项
| 字段 | 类型 | 说明 | 1. 二次靠泊记录会在解析时自动合并
|------|------|------| 2. 重复获取数据不会累加TEU会跳过已存在的记录
| id | INTEGER | 主键 | 3. 未统计数据在报表中不显示,但会计算到当月实际作业量
| date | TEXT | 日期 YYYY-MM-DD | 4. 昨日日报按钮默认获取前一天的数据(因为通常在第二天汇报)
| shift | TEXT | 班次 (白班/夜班) | 5. 月底数据调整适用于**每个月最后一天**而不仅限12月
| ship_name | TEXT | 船名(不含船号前缀) |
| teu | INTEGER | 作业量 TEU |
| efficiency | REAL | 效率 |
| vehicles | INTEGER | 上场车辆数 |
| created_at | TEXT | 创建时间 |
### 未统计表 (monthly_unaccounted)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER | 主键 |
| year_month | TEXT | 年月 YYYY-MM |
| teu | INTEGER | 未统计的 TEU |
| note | TEXT | 备注 |
| created_at | TEXT | 创建时间 |
## 特性说明
### 二次靠泊合并
解析时会自动合并同一天的二次靠泊记录:
- 夜班 学友洋山: 273TEU
- 夜班 学友洋山(二次靠泊): 14TEU
- 合并后: 夜班 学友洋山: 287TEU
### 未统计数据
可以在数据库统计中查看当月每艘船的作业量总计,便于跟踪船舶运营情况。
## 示例输出
```
日期12/28
船名:学友洋山
作业量246TEU
当日实际作业量246TEU
当月计划作业量8400TEU (用天数*300TEU)
当月实际作业量12632TEU
当月完成比例150.38%
12/29 白班人员:
12/29 夜班人员:
24小时值班手机13107662315
```
## 核心模块说明
### Confluence 模块 (`src/confluence/`)
- **`client.py`** - Confluence API 客户端,负责 HTTP 请求和连接管理
- **`text.py`** - HTML 文本提取器,保留布局结构
- **`log_parser.py`** - 日志解析器,解析船次作业数据
- **`parser.py`** - HTML 内容解析器,提取链接、图片、表格
- **`manager.py`** - 内容管理器,提供高级内容管理功能
### 飞书模块 (`src/feishu/`)
- **`client.py`** - 飞书 API 客户端
- **`parser.py`** - 排班数据解析器
- **`manager.py`** - 飞书排班管理器,缓存和刷新排班信息
### 数据库模块 (`src/database/`)
- **`base.py`** - 数据库基类,提供统一的连接管理
- **`daily_logs.py`** - 每日交接班日志数据库
- **`schedules.py`** - 排班数据库
## 技术栈 ## 技术栈
@@ -220,65 +199,9 @@ GUI 功能:
- SQLite3 - SQLite3
- Requests (HTTP 客户端) - Requests (HTTP 客户端)
- BeautifulSoup4 (HTML 解析) - BeautifulSoup4 (HTML 解析)
- tkinter (GUI,可选) - tkinter (GUI)
- 类型提示 (Python 3.5+) - 类型提示
## 架构特点 ## 许可证
1. **模块化设计** - 每个模块职责单一,便于测试和维护
2. **统一配置** - 集中管理所有环境变量和业务配置
3. **统一日志** - 标准化的日志配置和文件轮转
4. **异常处理** - 详细的错误处理和日志记录
5. **类型安全** - 全面的 Python 类型提示
## 开发指南
### 添加新功能
1. **配置管理**: 所有配置项应在 `src/config.py` 中定义
2. **日志记录**: 使用 `from src.logging_config import get_logger` 获取日志器
3. **异常处理**: 为每个模块创建自定义异常类
4. **类型提示**: 所有函数和方法都应包含完整的类型提示
5. **数据库操作**: 使用 `src/database/base.py` 中的基类确保连接管理
### 测试
```bash
# 运行配置测试
python3 main.py config-test
# 测试特定功能
python3 main.py fetch
python3 main.py report-today
```
### 调试
1. **日志查看**: 查看 `logs/app.log` 获取详细运行信息
2. **调试文件**: 使用 `python3 main.py fetch-debug` 生成带时间戳的调试文件
### 代码规范
- 遵循 PEP 8 编码规范
- 使用 Black 格式化代码(可选)
- 使用 isort 排序导入
- 所有公开 API 应有文档字符串
## 故障排除
### 常见问题
1. **连接失败**: 检查 `.env` 文件中的 API 令牌和 URL
2. **数据库错误**: 确保 `data/` 目录存在且有写入权限
3. **解析错误**: 检查 Confluence 页面结构是否发生变化
4. **飞书数据获取失败**: 验证飞书表格权限和 token 有效性
### 日志级别
- 默认日志级别: INFO
- 调试日志级别: DEBUG (设置环境变量 `LOG_LEVEL=DEBUG`)
- 日志文件: `logs/app.log`,自动轮转
## License
MIT MIT

665
logs/2026-01/2026-01-04.log Normal file
View File

@@ -0,0 +1,665 @@
2026-01-04 01:09:29 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO
2026-01-04 01:09:29 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG
2026-01-04 01:09:29 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO
2026-01-04 01:09:29 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG
2026-01-04 01:09:29 - root - INFO - <string>:10 - 测试日期分片日志 - 时间: 2026-01-04 01:09:29
2026-01-04 01:09:29 - root - DEBUG - <string>:11 - Debug级别测试
2026-01-04 01:09:29 - root - WARNING - <string>:12 - Warning级别测试
2026-01-04 01:10:13 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO
2026-01-04 01:10:13 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG
2026-01-04 01:10:13 - __main__ - INFO - gui.py:614 - GUI启动开始自动获取新数据...
2026-01-04 01:10:13 - __main__ - INFO - gui.py:627 - 正在刷新排班信息...
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:10:13 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:10:13 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:13 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:230 - 开始刷新未来 7 天的排班信息
2026-01-04 01:10:13 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-04 的排班信息...
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:10:13 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-05 的排班信息...
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-05 的排班信息 (格式: 01/05/1月5日)
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:13 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:13 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-05 的排班信息,从飞书获取
2026-01-04 01:10:13 - src.feishu.client - INFO - client.py:98 - 正在获取tenant_access_token应用ID: cli_a9d9...
2026-01-04 01:10:14 - src.feishu.client - INFO - client.py:114 - 成功获取tenant_access_token有效期: 4411秒
2026-01-04 01:10:14 - src.feishu.client - INFO - client.py:156 - token获取成功将在 4411 秒后过期
2026-01-04 01:10:14 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:14 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:14 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4410秒
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月5日 解析表格: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月5日 -> 1月5日 (索引: 5)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-05
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-05 的排班信息到数据库
2026-01-04 01:10:15 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-06 的排班信息...
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-06 的排班信息 (格式: 01/06/1月6日)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-06 的排班信息,从飞书获取
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4409秒
2026-01-04 01:10:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4409秒
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月6日 解析表格: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月6日 -> 1月6日 (索引: 6)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-06
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-06 的排班信息到数据库
2026-01-04 01:10:15 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-07 的排班信息...
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-07 的排班信息 (格式: 01/07/1月7日)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-07 的排班信息,从飞书获取
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4409秒
2026-01-04 01:10:15 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4409秒
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月7日 解析表格: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:15 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月7日 -> 1月7日 (索引: 7)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-07
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-07 的排班信息到数据库
2026-01-04 01:10:15 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-08 的排班信息...
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-08 的排班信息 (格式: 01/08/1月8日)
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:15 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-08 的排班信息,从飞书获取
2026-01-04 01:10:15 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4409秒
2026-01-04 01:10:16 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4408秒
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月8日 解析表格: 2026年排班表
2026-01-04 01:10:16 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:16 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月8日 -> 1月8日 (索引: 8)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-08
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-08 的排班信息到数据库
2026-01-04 01:10:16 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-09 的排班信息...
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-09 的排班信息 (格式: 01/09/1月9日)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-09 的排班信息,从飞书获取
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4408秒
2026-01-04 01:10:16 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4408秒
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月9日 解析表格: 2026年排班表
2026-01-04 01:10:16 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:16 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:16 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月9日 -> 1月9日 (索引: 9)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-09
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-09 的排班信息到数据库
2026-01-04 01:10:16 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-10 的排班信息...
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-10 的排班信息 (格式: 01/10/1月10日)
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:16 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:16 - src.feishu.manager - INFO - manager.py:145 - 数据库中没有 2026-01-10 的排班信息,从飞书获取
2026-01-04 01:10:16 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4407秒
2026-01-04 01:10:17 - src.feishu.client - INFO - client.py:203 - 获取到 7 个表格
2026-01-04 01:10:17 - src.feishu.manager - INFO - manager.py:90 - 找到2026年年度表格: 2026年排班表
2026-01-04 01:10:17 - src.feishu.client - DEBUG - client.py:142 - token仍然有效剩余时间: 4407秒
2026-01-04 01:10:17 - src.feishu.client - DEBUG - client.py:252 - 获取表格数据成功: R35cIj, 范围: A:AF
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.schedules - DEBUG - schedules.py:144 - 表格无更新: 2026年排班表 (ID: R35cIj)
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.feishu.manager - INFO - manager.py:192 - 使用日期格式: 1月10日 解析表格: 2026年排班表
2026-01-04 01:10:17 - src.feishu.parser - INFO - parser.py:267 - 使用年度表格解析器: 2026年排班表
2026-01-04 01:10:17 - src.feishu.parser - DEBUG - parser.py:201 - 找到月份块: 1月 (行: 1)
2026-01-04 01:10:17 - src.feishu.parser - DEBUG - parser.py:108 - 找到日期列: 1月10日 -> 1月10日 (索引: 10)
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.schedules - DEBUG - schedules.py:182 - 保存排班信息: 2026-01-10
2026-01-04 01:10:17 - src.feishu.manager - INFO - manager.py:198 - 已保存 2026-01-10 的排班信息到数据库
2026-01-04 01:10:17 - src.feishu.manager - INFO - manager.py:246 - 排班信息刷新完成,成功: 7, 失败: 0
2026-01-04 01:10:17 - __main__ - INFO - gui.py:632 - 排班信息刷新完成
2026-01-04 01:10:17 - __main__ - INFO - gui.py:648 - 正在尝试获取最新作业数据...
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - __main__ - INFO - gui.py:673 - 正在从 Confluence 获取 HTML...
2026-01-04 01:10:17 - src.confluence.client - DEBUG - client.py:48 - Confluence客户端初始化完成基础URL: https://confluence.westwell-lab.com/rest/api
2026-01-04 01:10:17 - src.confluence.client - DEBUG - client.py:69 - 获取Confluence内容: 159049182
2026-01-04 01:10:17 - src.confluence.client - INFO - client.py:74 - 成功获取Confluence内容: 159049182
2026-01-04 01:10:17 - src.confluence.client - INFO - client.py:115 - 获取到Confluence HTML内容长度: 11084 字符
2026-01-04 01:10:17 - __main__ - INFO - gui.py:679 - 获取成功,共 11084 字符
2026-01-04 01:10:17 - __main__ - INFO - gui.py:683 - 正在提取布局文本...
2026-01-04 01:10:17 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML长度: 11084 字符
2026-01-04 01:10:17 - src.confluence.text - INFO - text.py:83 - HTML提取完成输出长度: 2104 字符
2026-01-04 01:10:17 - __main__ - INFO - gui.py:689 - 正在解析日志数据...
2026-01-04 01:10:17 - src.confluence.log_parser - INFO - log_parser.py:356 - 解析转堆作业: 2026-01-02 白班 2TEU
2026-01-04 01:10:17 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 14 条记录
2026-01-04 01:10:17 - __main__ - INFO - gui.py:696 - 正在保存到数据库...
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 金祥源
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 东方吉
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 转堆作业
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 学友洋山
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 嘉洋16
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 泽远
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 华信长和
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 华信长和
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 瀚旺
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 金祥源
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:17 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 弘旭968
2026-01-04 01:10:17 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 东方祥
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 学友洋山
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 夜班 学友洋山
2026-01-04 01:10:18 - src.database.daily_logs - INFO - daily_logs.py:202 - 批量插入完成,成功 14/14 条记录
2026-01-04 01:10:18 - __main__ - INFO - gui.py:700 - 已保存 14 条新记录
2026-01-04 01:10:18 - __main__ - INFO - gui.py:728 - 正在生成今日日报...
2026-01-04 01:10:18 - __main__ - INFO - gui.py:572 - 生成 2026-01-03 的日报...
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.report - INFO - report.py:34 - 日报生成器初始化完成
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:10:18 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:10:18 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:10:18 - src.report - INFO - report.py:235 - 获取 2026-01-03 日报的班次人员,对应排班表日期: 2026-01-04
2026-01-04 01:10:18 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:10:18 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:10:18 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:10:18 - src.report - INFO - report.py:340 - 日报生成完成: 2026-01-03
2026-01-04 01:10:18 - __main__ - INFO - gui.py:589 - 日报生成完成: 2026-01-03
2026-01-04 01:10:18 - __main__ - INFO - gui.py:733 - 自动获取完成GUI已就绪
2026-01-04 01:14:19 - __main__ - INFO - gui.py:253 - 开始获取数据...
2026-01-04 01:14:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:19 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:14:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:19 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:19 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:19 - __main__ - INFO - gui.py:282 - 使用页面ID映射: 2026-01-03 -> 159049182
2026-01-04 01:14:19 - __main__ - INFO - gui.py:287 - 正在从 Confluence 获取 HTML...
2026-01-04 01:14:19 - src.confluence.client - DEBUG - client.py:48 - Confluence客户端初始化完成基础URL: https://confluence.westwell-lab.com/rest/api
2026-01-04 01:14:19 - src.confluence.client - DEBUG - client.py:69 - 获取Confluence内容: 159049182
2026-01-04 01:14:20 - src.confluence.client - INFO - client.py:74 - 成功获取Confluence内容: 159049182
2026-01-04 01:14:20 - src.confluence.client - INFO - client.py:115 - 获取到Confluence HTML内容长度: 12190 字符
2026-01-04 01:14:20 - __main__ - INFO - gui.py:297 - 获取成功,共 12190 字符
2026-01-04 01:14:20 - __main__ - INFO - gui.py:301 - 正在提取布局文本...
2026-01-04 01:14:20 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML长度: 12190 字符
2026-01-04 01:14:20 - src.confluence.text - INFO - text.py:83 - HTML提取完成输出长度: 2312 字符
2026-01-04 01:14:20 - __main__ - INFO - gui.py:305 - 提取完成,共 2311 字符
2026-01-04 01:14:20 - __main__ - INFO - gui.py:309 - 正在解析日志数据...
2026-01-04 01:14:20 - src.confluence.log_parser - INFO - log_parser.py:356 - 解析转堆作业: 2026-01-02 白班 2TEU
2026-01-04 01:14:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 16 条记录
2026-01-04 01:14:20 - __main__ - INFO - gui.py:313 - 解析到 16 条记录
2026-01-04 01:14:20 - __main__ - INFO - gui.py:318 - 正在保存到数据库...
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 金祥源
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 东方吉
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 德盛6
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 海顺丰7
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 转堆作业
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 学友洋山
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 嘉洋16
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 泽远
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 华信长和
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 华信长和
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 瀚旺
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 金祥源
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 弘旭968
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 东方祥
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 学友洋山
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 夜班 学友洋山
2026-01-04 01:14:20 - src.database.daily_logs - INFO - daily_logs.py:202 - 批量插入完成,成功 16/16 条记录
2026-01-04 01:14:20 - __main__ - INFO - gui.py:322 - 已保存 16 条记录
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - __main__ - INFO - gui.py:327 - 数据库总计: 147 条记录, 30 艘船
2026-01-04 01:14:20 - __main__ - INFO - gui.py:572 - 生成 2026-01-03 的日报...
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.report - INFO - report.py:34 - 日报生成器初始化完成
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:14:20 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:14:20 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:14:20 - src.report - INFO - report.py:235 - 获取 2026-01-03 日报的班次人员,对应排班表日期: 2026-01-04
2026-01-04 01:14:20 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:14:20 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:14:20 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:14:20 - src.report - INFO - report.py:340 - 日报生成完成: 2026-01-03
2026-01-04 01:14:20 - __main__ - INFO - gui.py:589 - 日报生成完成: 2026-01-03
2026-01-04 01:14:20 - __main__ - INFO - gui.py:336 - 数据获取完成
2026-01-04 01:16:15 - __main__ - INFO - gui.py:253 - 开始获取数据...
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - __main__ - INFO - gui.py:282 - 使用页面ID映射: 2026-01-03 -> 159049182
2026-01-04 01:16:15 - __main__ - INFO - gui.py:287 - 正在从 Confluence 获取 HTML...
2026-01-04 01:16:15 - src.confluence.client - DEBUG - client.py:48 - Confluence客户端初始化完成基础URL: https://confluence.westwell-lab.com/rest/api
2026-01-04 01:16:15 - src.confluence.client - DEBUG - client.py:69 - 获取Confluence内容: 159049182
2026-01-04 01:16:15 - src.confluence.client - INFO - client.py:74 - 成功获取Confluence内容: 159049182
2026-01-04 01:16:15 - src.confluence.client - INFO - client.py:115 - 获取到Confluence HTML内容长度: 12190 字符
2026-01-04 01:16:15 - __main__ - INFO - gui.py:297 - 获取成功,共 12190 字符
2026-01-04 01:16:15 - __main__ - INFO - gui.py:301 - 正在提取布局文本...
2026-01-04 01:16:15 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML长度: 12190 字符
2026-01-04 01:16:15 - src.confluence.text - INFO - text.py:83 - HTML提取完成输出长度: 2312 字符
2026-01-04 01:16:15 - __main__ - INFO - gui.py:305 - 提取完成,共 2311 字符
2026-01-04 01:16:15 - __main__ - INFO - gui.py:309 - 正在解析日志数据...
2026-01-04 01:16:15 - src.confluence.log_parser - INFO - log_parser.py:356 - 解析转堆作业: 2026-01-02 白班 2TEU
2026-01-04 01:16:15 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 16 条记录
2026-01-04 01:16:15 - __main__ - INFO - gui.py:313 - 解析到 16 条记录
2026-01-04 01:16:15 - __main__ - INFO - gui.py:318 - 正在保存到数据库...
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 金祥源
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 东方吉
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 德盛6
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 海顺丰7
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 转堆作业
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 学友洋山
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 嘉洋16
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 泽远
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 华信长和
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 华信长和
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 瀚旺
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 金祥源
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 弘旭968
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 东方祥
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 学友洋山
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 夜班 学友洋山
2026-01-04 01:16:15 - src.database.daily_logs - INFO - daily_logs.py:202 - 批量插入完成,成功 16/16 条记录
2026-01-04 01:16:15 - __main__ - INFO - gui.py:322 - 已保存 16 条记录
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - __main__ - INFO - gui.py:327 - 数据库总计: 147 条记录, 30 艘船
2026-01-04 01:16:15 - __main__ - INFO - gui.py:572 - 生成 2026-01-03 的日报...
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.report - INFO - report.py:34 - 日报生成器初始化完成
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:16:15 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:16:15 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:16:15 - src.report - INFO - report.py:235 - 获取 2026-01-03 日报的班次人员,对应排班表日期: 2026-01-04
2026-01-04 01:16:15 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:16:15 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:16:15 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:16:15 - src.report - INFO - report.py:340 - 日报生成完成: 2026-01-03
2026-01-04 01:16:15 - __main__ - INFO - gui.py:589 - 日报生成完成: 2026-01-03
2026-01-04 01:16:15 - __main__ - INFO - gui.py:336 - 数据获取完成
2026-01-04 01:17:20 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO
2026-01-04 01:17:20 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG
2026-01-04 01:17:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 1 条记录
2026-01-04 01:17:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 1 条记录
2026-01-04 01:17:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 1 条记录
2026-01-04 01:17:20 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 1 条记录
2026-01-04 01:17:53 - root - INFO - logging_config.py:110 - 控制台日志级别: INFO
2026-01-04 01:17:53 - root - INFO - logging_config.py:111 - 文件日志级别: DEBUG
2026-01-04 01:17:53 - __main__ - INFO - gui.py:614 - GUI启动开始自动获取新数据...
2026-01-04 01:17:53 - __main__ - INFO - gui.py:627 - 正在刷新排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:17:53 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:17:53 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:230 - 开始刷新未来 7 天的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-04 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-05 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-05 的排班信息 (格式: 01/05/1月5日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-05 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-06 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-06 的排班信息 (格式: 01/06/1月6日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-06 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-07 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-07 的排班信息 (格式: 01/07/1月7日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-07 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-08 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-08 的排班信息 (格式: 01/08/1月8日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-08 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-09 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-09 的排班信息 (格式: 01/09/1月9日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-09 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - DEBUG - manager.py:239 - 刷新 2026-01-10 的排班信息...
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-10 的排班信息 (格式: 01/10/1月10日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-10 的排班信息
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:246 - 排班信息刷新完成,成功: 7, 失败: 0
2026-01-04 01:17:53 - __main__ - INFO - gui.py:632 - 排班信息刷新完成
2026-01-04 01:17:53 - __main__ - INFO - gui.py:648 - 正在尝试获取最新作业数据...
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - __main__ - INFO - gui.py:673 - 正在从 Confluence 获取 HTML...
2026-01-04 01:17:53 - src.confluence.client - DEBUG - client.py:48 - Confluence客户端初始化完成基础URL: https://confluence.westwell-lab.com/rest/api
2026-01-04 01:17:53 - src.confluence.client - DEBUG - client.py:69 - 获取Confluence内容: 159049182
2026-01-04 01:17:53 - src.confluence.client - INFO - client.py:74 - 成功获取Confluence内容: 159049182
2026-01-04 01:17:53 - src.confluence.client - INFO - client.py:115 - 获取到Confluence HTML内容长度: 12190 字符
2026-01-04 01:17:53 - __main__ - INFO - gui.py:679 - 获取成功,共 12190 字符
2026-01-04 01:17:53 - __main__ - INFO - gui.py:683 - 正在提取布局文本...
2026-01-04 01:17:53 - src.confluence.text - DEBUG - text.py:60 - 开始解析HTML长度: 12190 字符
2026-01-04 01:17:53 - src.confluence.text - INFO - text.py:83 - HTML提取完成输出长度: 2312 字符
2026-01-04 01:17:53 - __main__ - INFO - gui.py:689 - 正在解析日志数据...
2026-01-04 01:17:53 - src.confluence.log_parser - INFO - log_parser.py:390 - 解析转堆作业: 2026-01-02 白班 2TEU
2026-01-04 01:17:53 - src.confluence.log_parser - INFO - log_parser.py:209 - 日志解析完成,共 16 条记录
2026-01-04 01:17:53 - __main__ - INFO - gui.py:696 - 正在保存到数据库...
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 金祥源
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 白班 东方吉
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 德盛6
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-03 夜班 海顺丰7
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 转堆作业
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 学友洋山
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 嘉洋16
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 泽远
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 白班 华信长和
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 华信长和
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 瀚旺
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-02 夜班 金祥源
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 弘旭968
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 东方祥
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 白班 学友洋山
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:180 - 插入记录: 2026-01-01 夜班 学友洋山
2026-01-04 01:17:53 - src.database.daily_logs - INFO - daily_logs.py:202 - 批量插入完成,成功 16/16 条记录
2026-01-04 01:17:53 - __main__ - INFO - gui.py:700 - 已保存 16 条新记录
2026-01-04 01:17:53 - __main__ - INFO - gui.py:728 - 正在生成今日日报...
2026-01-04 01:17:53 - __main__ - INFO - gui.py:572 - 生成 2026-01-03 的日报...
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.daily_logs - DEBUG - daily_logs.py:155 - 数据库表结构初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.report - INFO - report.py:34 - 日报生成器初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:53 - 使用飞书应用凭证自动获取token
2026-01-04 01:17:53 - src.feishu.client - DEBUG - client.py:56 - 飞书客户端初始化完成基础URL: https://open.feishu.cn/open-apis/sheets/v3
2026-01-04 01:17:53 - src.feishu.client - DEBUG - client.py:57 - 使用应用ID: cli_a9d9... 如果配置
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.schedules - DEBUG - schedules.py:71 - 排班数据库表结构初始化完成
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:41 - 飞书排班管理器初始化完成
2026-01-04 01:17:53 - src.report - INFO - report.py:235 - 获取 2026-01-03 日报的班次人员,对应排班表日期: 2026-01-04
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:136 - 获取 2026-01-04 的排班信息 (格式: 01/04/1月4日)
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:57 - 数据库连接已建立: data/daily_logs.db
2026-01-04 01:17:53 - src.database.base - DEBUG - base.py:87 - 数据库连接已关闭
2026-01-04 01:17:53 - src.feishu.manager - INFO - manager.py:141 - 从数据库获取 2026-01-04 的排班信息
2026-01-04 01:17:53 - src.report - INFO - report.py:340 - 日报生成完成: 2026-01-03
2026-01-04 01:17:53 - __main__ - INFO - gui.py:589 - 日报生成完成: 2026-01-03
2026-01-04 01:17:53 - __main__ - INFO - gui.py:733 - 自动获取完成GUI已就绪

180
main.py
View File

@@ -189,6 +189,70 @@ def add_unaccounted(year_month: str, teu: int, note: str = ''):
raise raise
def remove_unaccounted(year_month: str, teu_to_reduce: int = None):
"""
去除未统计数据
参数:
year_month: 年月字符串,格式 "2025-12"
teu_to_reduce: 要减少的TEU数量如果为None则删除整个记录
"""
try:
db = DailyLogsDatabase()
if teu_to_reduce is None:
# 如果没有指定减少数量,则删除整个记录
result = db.delete_unaccounted(year_month)
if result:
logger.info(f"已删除 {year_month} 月未统计数据")
else:
logger.error("删除失败")
else:
# 减少指定数量的TEU
result = db.reduce_unaccounted(year_month, teu_to_reduce)
if result:
logger.info(f"已减少 {year_month} 月未统计数据: {teu_to_reduce}TEU")
else:
logger.error("减少失败")
except Exception as e:
logger.error(f"去除未统计数据失败: {e}")
raise
def add_cross_month_exclusion(source_date: str, target_date: str, ship_name: str, teu: int,
twenty_feet: int = 0, forty_feet: int = 0, reason: str = ''):
"""
添加跨月剔除调整(手动剔除次月多统计的船)
参数:
source_date: 源日期(上月底日期)
target_date: 目标日期(次月日期)
ship_name: 船名
teu: TEU数量
twenty_feet: 20尺箱量
forty_feet: 40尺箱量
reason: 调整原因
"""
try:
db = DailyLogsDatabase()
success = db.insert_cross_month_exclusion(
source_date=source_date,
target_date=target_date,
ship_name=ship_name,
teu=teu,
twenty_feet=twenty_feet,
forty_feet=forty_feet,
reason=reason
)
if success:
logger.info(f"已添加跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU")
else:
logger.error("添加跨月剔除调整失败")
except Exception as e:
logger.error(f"添加跨月剔除调整失败: {e}")
raise
def show_stats(date: str): def show_stats(date: str):
""" """
显示指定日期的统计 显示指定日期的统计
@@ -288,11 +352,30 @@ def main():
config-test 配置测试 config-test 配置测试
stats 显示今日统计 stats 显示今日统计
参数:
--unaccounted, -u TEU 添加未统计数据(需同时指定月份)
--remove-unaccounted, -r [TEU] 去除未统计数据需同时指定月份。如果指定TEU值则减少该数量如果不指定则删除整个记录
--month, -m YEAR-MONTH 指定月份(与 -u 或 -r 配合使用)
--cross-exclude, -c 手动剔除次月多统计的船需指定源日期、目标日期、船名和TEU
跨月剔除参数:
--source-date DATE 源日期(上月底日期),格式: YYYY-MM-DD
--target-date DATE 目标日期(次月日期),格式: YYYY-MM-DD
--ship-name NAME 船名
--teu TEU TEU数量
--twenty-feet COUNT 20尺箱量可选默认0
--forty-feet COUNT 40尺箱量可选默认0
--reason REASON 调整原因(可选)
示例: 示例:
python3 main.py fetch python3 main.py fetch
python3 main.py fetch-save python3 main.py fetch-save
python3 main.py report 2025-12-28 python3 main.py report 2025-12-28
python3 main.py config-test python3 main.py config-test
python3 main.py --unaccounted 118 --month 2025-12
python3 main.py --remove-unaccounted --month 2025-12 # 删除整个记录
python3 main.py --remove-unaccounted 118 --month 2025-12 # 减少118TEU
python3 main.py --cross-exclude --source-date 2025-12-31 --target-date 2026-01-01 --ship-name "学友洋山" --teu 100
''' '''
) )
parser.add_argument( parser.add_argument(
@@ -314,11 +397,67 @@ def main():
type=int, type=int,
help='添加未统计数据(需同时指定月份,如 -u 118 2025-12' help='添加未统计数据(需同时指定月份,如 -u 118 2025-12'
) )
parser.add_argument(
'--remove-unaccounted',
'-r',
metavar='TEU',
nargs='?',
const=None,
type=int,
help='去除未统计数据(需同时指定月份,如 -r 118 2025-12。如果指定TEU值则减少该数量如果不指定则删除整个记录'
)
parser.add_argument( parser.add_argument(
'--month', '--month',
'-m', '-m',
metavar='YEAR-MONTH', metavar='YEAR-MONTH',
help='指定月份(与 --unaccounted 配合使用)' help='指定月份(与 --unaccounted 或 --remove-unaccounted 配合使用)'
)
parser.add_argument(
'--cross-exclude',
'-c',
action='store_true',
help='手动剔除次月多统计的船'
)
parser.add_argument(
'--source-date',
metavar='DATE',
help='源日期(上月底日期),格式: YYYY-MM-DD'
)
parser.add_argument(
'--target-date',
metavar='DATE',
help='目标日期(次月日期),格式: YYYY-MM-DD'
)
parser.add_argument(
'--ship-name',
metavar='NAME',
help='船名'
)
parser.add_argument(
'--teu',
metavar='TEU',
type=int,
help='TEU数量'
)
parser.add_argument(
'--twenty-feet',
metavar='COUNT',
type=int,
default=0,
help='20尺箱量可选默认0'
)
parser.add_argument(
'--forty-feet',
metavar='COUNT',
type=int,
default=0,
help='40尺箱量可选默认0'
)
parser.add_argument(
'--reason',
metavar='REASON',
default='手动剔除次月多统计的船',
help='调整原因(可选,默认: "手动剔除次月多统计的船"'
) )
args = parser.parse_args() args = parser.parse_args()
@@ -333,6 +472,45 @@ def main():
sys.exit(1) sys.exit(1)
return return
# 去除未统计数据
# 检查是否提供了 --remove-unaccounted 或 -r 参数
has_remove_arg = any(arg in sys.argv for arg in ['--remove-unaccounted', '-r'])
if has_remove_arg:
year_month = args.month or datetime.now().strftime('%Y-%m')
try:
# args.remove_unaccounted 可能是整数指定TEU或 None未指定
if isinstance(args.remove_unaccounted, int):
# 指定了TEU值减少指定数量
remove_unaccounted(year_month, args.remove_unaccounted)
else:
# 未指定TEU值删除整个记录
remove_unaccounted(year_month)
except Exception as e:
logger.error(f"去除未统计数据失败: {e}")
sys.exit(1)
return
# 跨月剔除功能
if args.cross_exclude:
if not all([args.source_date, args.target_date, args.ship_name, args.teu]):
logger.error("跨月剔除功能需要指定以下参数: --source-date, --target-date, --ship-name, --teu")
sys.exit(1)
try:
add_cross_month_exclusion(
source_date=args.source_date,
target_date=args.target_date,
ship_name=args.ship_name,
teu=args.teu,
twenty_feet=args.twenty_feet,
forty_feet=args.forty_feet,
reason=args.reason
)
except Exception as e:
logger.error(f"跨月剔除失败: {e}")
sys.exit(1)
return
# 执行功能 # 执行功能
try: try:
if args.function == 'report' and args.date: if args.function == 'report' and args.date:

View File

@@ -23,6 +23,8 @@ class Config:
FEISHU_BASE_URL = os.getenv('FEISHU_BASE_URL', 'https://open.feishu.cn/open-apis/sheets/v3') FEISHU_BASE_URL = os.getenv('FEISHU_BASE_URL', 'https://open.feishu.cn/open-apis/sheets/v3')
FEISHU_TOKEN = os.getenv('FEISHU_TOKEN') FEISHU_TOKEN = os.getenv('FEISHU_TOKEN')
FEISHU_SPREADSHEET_TOKEN = os.getenv('FEISHU_SPREADSHEET_TOKEN') FEISHU_SPREADSHEET_TOKEN = os.getenv('FEISHU_SPREADSHEET_TOKEN')
FEISHU_APP_ID = os.getenv('FEISHU_APP_ID')
FEISHU_APP_SECRET = os.getenv('FEISHU_APP_SECRET')
# 数据库配置 # 数据库配置
DATABASE_PATH = os.getenv('DATABASE_PATH', 'data/daily_logs.db') DATABASE_PATH = os.getenv('DATABASE_PATH', 'data/daily_logs.db')
@@ -46,7 +48,7 @@ class Config:
# GUI 配置 # GUI 配置
GUI_FONT_FAMILY = os.getenv('GUI_FONT_FAMILY', 'SimHei') GUI_FONT_FAMILY = os.getenv('GUI_FONT_FAMILY', 'SimHei')
GUI_FONT_SIZE = int(os.getenv('GUI_FONT_SIZE', '10')) GUI_FONT_SIZE = int(os.getenv('GUI_FONT_SIZE', '10'))
GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '900x700') GUI_WINDOW_SIZE = os.getenv('GUI_WINDOW_SIZE', '1600x900')
# 排班刷新配置 # 排班刷新配置
SCHEDULE_REFRESH_DAYS = int(os.getenv('SCHEDULE_REFRESH_DAYS', '30')) SCHEDULE_REFRESH_DAYS = int(os.getenv('SCHEDULE_REFRESH_DAYS', '30'))
@@ -70,8 +72,17 @@ class Config:
errors.append("CONFLUENCE_CONTENT_ID 未配置") errors.append("CONFLUENCE_CONTENT_ID 未配置")
# 检查飞书配置(可选,但建议配置) # 检查飞书配置(可选,但建议配置)
if not cls.FEISHU_TOKEN: has_feishu_token = bool(cls.FEISHU_TOKEN)
print("警告: FEISHU_TOKEN 未配置,排班功能将不可用") has_app_credentials = bool(cls.FEISHU_APP_ID and cls.FEISHU_APP_SECRET)
if not has_feishu_token and not has_app_credentials:
print("警告: 飞书认证未配置,排班功能将不可用")
print(" 请配置 FEISHU_TOKEN 或 FEISHU_APP_ID + FEISHU_APP_SECRET")
elif has_app_credentials:
print("信息: 使用飞书应用凭证自动获取token")
elif has_feishu_token:
print("信息: 使用手动配置的FEISHU_TOKEN")
if not cls.FEISHU_SPREADSHEET_TOKEN: if not cls.FEISHU_SPREADSHEET_TOKEN:
print("警告: FEISHU_SPREADSHEET_TOKEN 未配置,排班功能将不可用") print("警告: FEISHU_SPREADSHEET_TOKEN 未配置,排班功能将不可用")
@@ -88,7 +99,21 @@ class Config:
"""打印配置摘要""" """打印配置摘要"""
print("配置摘要:") print("配置摘要:")
print(f" Confluence: {'已配置' if cls.CONFLUENCE_BASE_URL else '未配置'}") print(f" Confluence: {'已配置' if cls.CONFLUENCE_BASE_URL else '未配置'}")
print(f" 飞书: {'已配置' if cls.FEISHU_TOKEN else '未配置'}")
# 飞书配置详情
has_feishu_token = bool(cls.FEISHU_TOKEN)
has_app_credentials = bool(cls.FEISHU_APP_ID and cls.FEISHU_APP_SECRET)
has_spreadsheet_token = bool(cls.FEISHU_SPREADSHEET_TOKEN)
if has_app_credentials:
feishu_status = f"应用凭证 (ID: {cls.FEISHU_APP_ID[:8]}...)"
elif has_feishu_token:
feishu_status = "手动token"
else:
feishu_status = "未配置"
print(f" 飞书认证: {feishu_status}")
print(f" 飞书表格: {'已配置' if has_spreadsheet_token else '未配置'}")
print(f" 数据库路径: {cls.DATABASE_PATH}") print(f" 数据库路径: {cls.DATABASE_PATH}")
print(f" 每日目标TEU: {cls.DAILY_TARGET_TEU}") print(f" 每日目标TEU: {cls.DAILY_TARGET_TEU}")
print(f" 排班刷新天数: {cls.SCHEDULE_REFRESH_DAYS}") print(f" 排班刷新天数: {cls.SCHEDULE_REFRESH_DAYS}")

View File

@@ -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]:
"""转换为字典""" """转换为字典"""
@@ -108,7 +110,26 @@ class HandoverLogParser:
try: try:
logs: List[ShipLog] = [] logs: List[ShipLog] = []
# 预处理:移除单行分隔符(前后都是空行的分隔符) # 预处理:修复日期格式和特殊字符
# 1. 修复日期格式:将 "2026.1.1" 转换为 "2026.01.01"
def fix_date_format(match):
date_str = match.group(1)
parts = date_str.split('.')
if len(parts) == 3:
year, month, day = parts
# 补零
month = month.zfill(2)
day = day.zfill(2)
return f"日期:{year}.{month}.{day}"
return match.group(0)
# 修复日期格式
text = re.sub(r'日期:(\d{4}\.\d{1,2}\.\d{1,2})', fix_date_format, text)
# 2. 修复特殊空格字符(\xa0 转换为普通空格)
text = text.replace('\xa0', ' ')
# 3. 移除单行分隔符(前后都是空行的分隔符)
# 保留真正的内容分隔符(前后有内容的) # 保留真正的内容分隔符(前后有内容的)
lines = text.split('\n') lines = text.split('\n')
processed_lines: List[str] = [] processed_lines: List[str] = []
@@ -129,19 +150,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 +175,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 +192,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)} 条记录")
@@ -204,6 +241,12 @@ class HandoverLogParser:
def _parse_ships(self, content: str, date: str, shift: str, logs: List[ShipLog]) -> None: def _parse_ships(self, content: str, date: str, shift: str, logs: List[ShipLog]) -> None:
"""解析船次""" """解析船次"""
try: try:
# 首先解析转堆作业(无船名)
relocation_content = self._parse_relocation(content, date, shift)
if relocation_content:
logs.append(relocation_content)
# 然后解析实船作业(按 "实船作业:" 分割)
parts = content.split('实船作业:') parts = content.split('实船作业:')
for part in parts: for part in parts:
@@ -221,41 +264,230 @@ class HandoverLogParser:
ship_name = ship_match.group(2) ship_name = ship_match.group(2)
# 移除二次靠泊等标注 # 移除二次靠泊等标注
ship_name = re.sub(r'(二次靠泊)|(再次靠泊)|\(二次靠泊\)|\(再次靠泊\)', '', ship_name).strip() ship_name = re.sub(r'(二次靠泊)|(再次靠泊)|\(二次靠泊\)|\(再次靠泊\)', '', ship_name).strip()
vehicles_match = re.search(r'上场车辆数:(\d+)', cleaned) # 解析车辆数、TEU、尺寸箱量
teu_eff_match = re.search( self._parse_log_entry(cleaned, date, shift, ship_name, logs)
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}")
log = ShipLog(
date=date,
shift=shift,
ship_name=ship_name,
teu=teu,
efficiency=None, # 目前日志中没有效率数据
vehicles=vehicles
)
logs.append(log)
except Exception as e: except Exception as e:
logger.warning(f"解析船次失败: {date} {shift}, 错误: {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 = None
# 格式1: TEU20尺*240尺*3 或 TEU20尺*240尺*3
size_pattern = re.search(r'TEU[,\s]*([^]+)', cleaned)
if not size_pattern:
# 格式2: TEU20尺*240尺*3- 无空格版本
size_pattern = re.search(r'TEU[(]([^)]+)[)]', cleaned)
if not size_pattern:
# 格式3: 作业量/效率100TEU20尺*240尺*3
size_pattern = re.search(r'(\d+)尺\*(\d+)', cleaned)
if size_pattern:
size_text = size_pattern.group(1)
# 尝试直接匹配 20尺*数字 或 40尺*数字
twenty_match = re.search(r'20尺\s*[×x*]?\s*(\d+)', size_text)
if not twenty_match:
twenty_match = re.search(r'(\d+)\s*×\s*20尺', 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尺\s*[×x*]?\s*(\d+)', size_text)
if not forty_match:
forty_match = re.search(r'(\d+)\s*×\s*40尺', 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}")
# 备用解析直接在cleaned中查找尺寸信息
if twenty_feet is None:
twenty_match = re.search(r'20尺\s*[×x*]?\s*(\d+)', cleaned)
if twenty_match:
try:
twenty_feet = int(twenty_match.group(1))
except ValueError as e:
logger.warning(f"转堆作业 20尺箱量解析失败: {twenty_match.group(1)}, 错误: {e}")
if forty_feet is None:
forty_match = re.search(r'40尺\s*[×x*]?\s*(\d+)', cleaned)
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 = None
# 格式1: TEU20尺*240尺*3 或 TEU20尺*240尺*3
size_pattern = re.search(r'TEU[,\s]*([^]+)', cleaned)
if not size_pattern:
# 格式2: TEU20尺*240尺*3- 无空格版本
size_pattern = re.search(r'TEU[(]([^)]+)[)]', cleaned)
if not size_pattern:
# 格式3: 作业量/效率100TEU20尺*240尺*3
size_pattern = re.search(r'(\d+)尺\*(\d+)', cleaned)
if size_pattern:
size_text = size_pattern.group(1)
# 尝试直接匹配 20尺*数字 或 40尺*数字
twenty_match = re.search(r'20尺\s*[×x*]?\s*(\d+)', size_text)
if not twenty_match:
twenty_match = re.search(r'(\d+)\s*×\s*20尺', 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尺\s*[×x*]?\s*(\d+)', size_text)
if not forty_match:
forty_match = re.search(r'(\d+)\s*×\s*40尺', 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}")
# 备用解析直接在cleaned中查找尺寸信息
if twenty_feet is None:
twenty_match = re.search(r'20尺\s*[×x*]?\s*(\d+)', cleaned)
if twenty_match:
try:
twenty_feet = int(twenty_match.group(1))
except ValueError as e:
logger.warning(f"20尺箱量解析失败: {twenty_match.group(1)}, 错误: {e}")
if forty_feet is None:
forty_match = re.search(r'40尺\s*[×x*]?\s*(\d+)', cleaned)
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]: def parse_from_file(self, filepath: str) -> List[ShipLog]:
""" """
从文件解析日志 从文件解析日志

View File

@@ -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
) )
@@ -71,15 +75,15 @@ class DailyLogsDatabase(DatabaseBase):
# 复制数据(忽略重复) # 复制数据(忽略重复)
cursor.execute(''' 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) (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 FROM daily_handover_logs_old
''') ''')
# 删除旧表 # 删除旧表
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)')
@@ -96,6 +100,57 @@ class DailyLogsDatabase(DatabaseBase):
) )
''') ''')
# 创建手动调整数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS manual_adjustments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL, -- 调整适用的日期(目标日期)
ship_name TEXT NOT NULL, -- 船名
teu INTEGER NOT NULL, -- TEU数量
twenty_feet INTEGER DEFAULT 0, -- 20尺箱量
forty_feet INTEGER DEFAULT 0, -- 40尺箱量
adjustment_type TEXT NOT NULL, -- 'add''exclude'
note TEXT, -- 备注
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
# 检查是否需要添加新字段
cursor.execute("PRAGMA table_info(manual_adjustments)")
columns = [col[1] for col in cursor.fetchall()]
# 添加缺失的字段
if 'source_date' not in columns:
cursor.execute('ALTER TABLE manual_adjustments ADD COLUMN source_date TEXT')
logger.info("已添加 source_date 字段到 manual_adjustments 表")
if 'reason' not in columns:
cursor.execute('ALTER TABLE manual_adjustments ADD COLUMN reason TEXT')
logger.info("已添加 reason 字段到 manual_adjustments 表")
if 'status' not in columns:
cursor.execute('ALTER TABLE manual_adjustments ADD COLUMN status TEXT DEFAULT "pending"')
logger.info("已添加 status 字段到 manual_adjustments 表")
# 创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_date ON manual_adjustments(date)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_manual_type ON manual_adjustments(adjustment_type)')
# 创建Confluence页面ID映射表
cursor.execute('''
CREATE TABLE IF NOT EXISTS confluence_pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
month_key TEXT NOT NULL UNIQUE, -- 月份键,格式:'2025-12', '2026-01'
page_id TEXT NOT NULL, -- Confluence页面ID
page_title TEXT, -- 页面标题(可选)
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_confluence_month ON confluence_pages(month_key)')
conn.commit() conn.commit()
logger.debug("数据库表结构初始化完成") logger.debug("数据库表结构初始化完成")
@@ -111,13 +166,14 @@ 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)
@@ -287,6 +343,74 @@ class DailyLogsDatabase(DatabaseBase):
result = self.execute_query(query, (year_month,)) result = self.execute_query(query, (year_month,))
return result[0]['teu'] if result else 0 return result[0]['teu'] if result else 0
def reduce_unaccounted(self, year_month: str, teu_to_reduce: int) -> bool:
"""
减少指定月份的未统计数据
参数:
year_month: 年月字符串,格式 "2025-12"
teu_to_reduce: 要减少的TEU数量
返回:
是否成功
"""
try:
# 先获取当前值
current_teu = self.get_unaccounted(year_month)
# 计算新值(允许负数)
new_teu = current_teu - teu_to_reduce
if new_teu == 0:
# 如果减少后等于0则删除记录
query = 'DELETE FROM monthly_unaccounted WHERE year_month = ?'
self.execute_update(query, (year_month,))
logger.info(f"减少未统计数据后删除记录: {year_month},原值: {current_teu}TEU减少: {teu_to_reduce}TEU新值: 0TEU")
else:
# 更新记录(允许负数)
query = '''
INSERT OR REPLACE INTO monthly_unaccounted
(year_month, teu, note, created_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
'''
# 保留原有备注
note_query = 'SELECT note FROM monthly_unaccounted WHERE year_month = ?'
note_result = self.execute_query(note_query, (year_month,))
note = note_result[0]['note'] if note_result else ''
self.execute_update(query, (year_month, new_teu, note))
logger.info(f"减少未统计数据: {year_month},原值: {current_teu}TEU减少: {teu_to_reduce}TEU新值: {new_teu}TEU")
return True
except Exception as e:
logger.error(f"减少未统计数据失败: {e}")
return False
def delete_unaccounted(self, year_month: str) -> bool:
"""
删除指定月份的未统计数据
参数:
year_month: 年月字符串,格式 "2025-12"
返回:
是否成功删除如果记录不存在也返回True
"""
try:
query = 'DELETE FROM monthly_unaccounted WHERE year_month = ?'
result = self.execute_update(query, (year_month,))
if result > 0:
logger.info(f"删除未统计数据: {year_month}")
return True
else:
logger.warning(f"未找到 {year_month} 月的未统计数据")
return True # 记录不存在也算成功
except Exception as e:
logger.error(f"删除未统计数据失败: {e}")
return False
def delete_by_date(self, date: str) -> int: def delete_by_date(self, date: str) -> int:
""" """
删除指定日期的记录 删除指定日期的记录
@@ -299,6 +423,458 @@ class DailyLogsDatabase(DatabaseBase):
""" """
query = 'DELETE FROM daily_handover_logs WHERE date = ?' query = 'DELETE FROM daily_handover_logs WHERE date = ?'
return self.execute_update(query, (date,)) return self.execute_update(query, (date,))
def insert_manual_adjustment(self, date: str, ship_name: str, teu: int,
twenty_feet: int = 0, forty_feet: int = 0,
adjustment_type: str = 'add', note: str = '',
source_date: str = None, reason: str = '',
status: str = 'pending') -> bool:
"""
插入手动调整数据
参数:
date: 日期字符串(目标日期)
ship_name: 船名
teu: TEU数量
twenty_feet: 20尺箱量
forty_feet: 40尺箱量
adjustment_type: 调整类型 'add''exclude'
note: 备注
source_date: 源日期(上月底日期,可选)
reason: 调整原因
status: 调整状态:'pending', 'processed'
返回:
是否成功
"""
try:
query = '''
INSERT INTO manual_adjustments
(date, source_date, ship_name, teu, twenty_feet, forty_feet,
adjustment_type, note, reason, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
'''
params = (date, source_date, ship_name, teu, twenty_feet, forty_feet,
adjustment_type, note, reason, status)
self.execute_update(query, params)
logger.info(f"插入手动调整数据: {date} {ship_name} {teu}TEU ({adjustment_type})")
return True
except Exception as e:
logger.error(f"插入手动调整数据失败: {e}")
return False
def get_manual_adjustments(self, date: str) -> List[Dict[str, Any]]:
"""
获取指定日期的所有手动调整数据
参数:
date: 日期字符串
返回:
手动调整数据列表
"""
query = '''
SELECT * FROM manual_adjustments
WHERE date = ? ORDER BY created_at DESC
'''
return self.execute_query(query, (date,))
def get_manual_adjustments_by_type(self, date: str, adjustment_type: str) -> List[Dict[str, Any]]:
"""
获取指定日期和类型的调整数据
参数:
date: 日期字符串
adjustment_type: 调整类型 'add''exclude'
返回:
手动调整数据列表
"""
query = '''
SELECT * FROM manual_adjustments
WHERE date = ? AND adjustment_type = ? ORDER BY created_at DESC
'''
return self.execute_query(query, (date, adjustment_type))
def get_cross_month_adjustments(self, source_date: str = None, target_date: str = None,
status: str = None) -> List[Dict[str, Any]]:
"""
获取跨月调整数据
参数:
source_date: 源日期(上月底日期)
target_date: 目标日期(次月日期)
status: 调整状态
返回:
跨月调整数据列表
"""
try:
conditions = []
params = []
if source_date:
conditions.append("source_date = ?")
params.append(source_date)
if target_date:
conditions.append("date = ?")
params.append(target_date)
if status:
conditions.append("status = ?")
params.append(status)
where_clause = " AND ".join(conditions) if conditions else "1=1"
query = f'''
SELECT * FROM manual_adjustments
WHERE {where_clause} ORDER BY created_at DESC
'''
return self.execute_query(query, tuple(params))
except Exception as e:
logger.error(f"获取跨月调整数据失败: {e}")
return []
def get_pending_cross_month_adjustments(self) -> List[Dict[str, Any]]:
"""
获取待处理的跨月调整数据
返回:
待处理的跨月调整数据列表
"""
return self.get_cross_month_adjustments(status='pending')
def update_adjustment_status(self, adjustment_id: int, status: str) -> bool:
"""
更新调整状态
参数:
adjustment_id: 调整记录ID
status: 新状态
返回:
是否成功
"""
try:
query = 'UPDATE manual_adjustments SET status = ? WHERE id = ?'
result = self.execute_update(query, (status, adjustment_id))
if result > 0:
logger.info(f"更新调整状态: ID={adjustment_id} -> {status}")
return True
else:
logger.warning(f"未找到调整记录: ID={adjustment_id}")
return False
except Exception as e:
logger.error(f"更新调整状态失败: {e}")
return False
def insert_cross_month_exclusion(self, source_date: str, target_date: str,
ship_name: str, teu: int,
twenty_feet: int = 0, forty_feet: int = 0,
reason: str = '') -> bool:
"""
插入跨月剔除调整(手动剔除次月多统计的船)
参数:
source_date: 源日期(上月底日期)
target_date: 目标日期(次月日期)
ship_name: 船名
teu: TEU数量
twenty_feet: 20尺箱量
forty_feet: 40尺箱量
reason: 调整原因
返回:
是否成功
"""
try:
# 1. 插入剔除记录(从月底最后一天扣除)
exclude_success = self.insert_manual_adjustment(
date=source_date,
source_date=source_date,
ship_name=ship_name,
teu=teu,
twenty_feet=twenty_feet,
forty_feet=forty_feet,
adjustment_type='exclude',
note=f"手动剔除次月多统计的船,目标日期: {target_date}",
reason=reason,
status='pending'
)
if not exclude_success:
return False
# 2. 自动将相同数据添加到次月1号
add_success = self.insert_manual_adjustment(
date=target_date,
source_date=source_date,
ship_name=ship_name,
teu=teu,
twenty_feet=twenty_feet,
forty_feet=forty_feet,
adjustment_type='add',
note=f"{source_date}转移的数据: {reason}",
reason=reason,
status='pending'
)
if add_success:
logger.info(f"插入跨月剔除调整: {source_date} -> {target_date} {ship_name} {teu}TEU")
return True
else:
logger.error(f"插入跨月剔除调整失败: 添加数据到次月1号失败")
return False
except Exception as e:
logger.error(f"插入跨月剔除调整失败: {e}")
return False
def delete_manual_adjustment(self, adjustment_id: int) -> bool:
"""
删除指定ID的手动调整数据
参数:
adjustment_id: 调整记录ID
返回:
是否成功删除
"""
try:
query = 'DELETE FROM manual_adjustments WHERE id = ?'
result = self.execute_update(query, (adjustment_id,))
if result > 0:
logger.info(f"删除手动调整数据: ID={adjustment_id}")
return True
else:
logger.warning(f"未找到手动调整数据: ID={adjustment_id}")
return False
except Exception as e:
logger.error(f"删除手动调整数据失败: {e}")
return False
def clear_manual_adjustments(self, date: str) -> int:
"""
清除指定日期的所有手动调整数据
参数:
date: 日期字符串
返回:
删除的记录数
"""
query = 'DELETE FROM manual_adjustments WHERE date = ?'
result = self.execute_update(query, (date,))
logger.info(f"清除 {date} 的手动调整数据: {result} 条记录")
return result
def get_daily_data_with_adjustments(self, date: str) -> Dict[str, Any]:
"""
获取指定日期的数据(包含手动调整)
参数:
date: 日期字符串
返回:
包含调整的每日数据字典
"""
try:
# 获取原始数据
logs = self.query_by_date(date)
# 获取手动调整数据
adjustments = self.get_manual_adjustments(date)
# 按船名汇总原始数据
ships: Dict[str, Dict[str, Any]] = {}
for log in logs:
ship = log['ship_name']
if ship not in ships:
ships[ship] = {
'teu': 0,
'twenty_feet': 0,
'forty_feet': 0,
'adjustments': []
}
if log.get('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_adjustment_teu = 0
for adj in adjustments:
ship = adj['ship_name']
if ship not in ships:
ships[ship] = {
'teu': 0,
'twenty_feet': 0,
'forty_feet': 0,
'adjustments': []
}
# 记录调整
ships[ship]['adjustments'].append(adj)
# 根据调整类型计算
if adj['adjustment_type'] == 'add':
ships[ship]['teu'] += adj['teu']
ships[ship]['twenty_feet'] += adj['twenty_feet']
ships[ship]['forty_feet'] += adj['forty_feet']
total_adjustment_teu += adj['teu']
elif adj['adjustment_type'] == 'exclude':
ships[ship]['teu'] -= adj['teu']
ships[ship]['twenty_feet'] -= adj['twenty_feet']
ships[ship]['forty_feet'] -= adj['forty_feet']
total_adjustment_teu -= adj['teu']
# 计算总TEU
total_teu = sum(ship_data['teu'] for ship_data in ships.values())
return {
'date': date,
'ships': ships,
'total_teu': total_teu,
'ship_count': len(ships),
'adjustments': adjustments,
'total_adjustment_teu': total_adjustment_teu
}
except Exception as e:
logger.error(f"获取包含调整的每日数据失败: {date}, 错误: {e}")
return {
'date': date,
'ships': {},
'total_teu': 0,
'ship_count': 0,
'adjustments': [],
'total_adjustment_teu': 0
}
def insert_confluence_page(self, month_key: str, page_id: str, page_title: str = '') -> bool:
"""
插入或更新Confluence页面ID映射
参数:
month_key: 月份键,格式:'2025-12', '2026-01'
page_id: Confluence页面ID
page_title: 页面标题(可选)
返回:
是否成功
"""
try:
query = '''
INSERT OR REPLACE INTO confluence_pages
(month_key, page_id, page_title, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
'''
params = (month_key, page_id, page_title)
self.execute_update(query, params)
logger.info(f"插入Confluence页面映射: {month_key} -> {page_id}")
return True
except Exception as e:
logger.error(f"插入Confluence页面映射失败: {e}")
return False
def get_confluence_page(self, month_key: str) -> Optional[Dict[str, Any]]:
"""
获取指定月份的Confluence页面ID映射
参数:
month_key: 月份键,格式:'2025-12', '2026-01'
返回:
页面映射字典如果不存在则返回None
"""
query = 'SELECT * FROM confluence_pages WHERE month_key = ?'
result = self.execute_query(query, (month_key,))
return result[0] if result else None
def get_all_confluence_pages(self) -> List[Dict[str, Any]]:
"""
获取所有Confluence页面ID映射
返回:
页面映射列表
"""
query = 'SELECT * FROM confluence_pages ORDER BY month_key DESC'
return self.execute_query(query)
def delete_confluence_page(self, month_key: str) -> bool:
"""
删除指定月份的Confluence页面ID映射
参数:
month_key: 月份键,格式:'2025-12', '2026-01'
返回:
是否成功删除
"""
try:
query = 'DELETE FROM confluence_pages WHERE month_key = ?'
result = self.execute_update(query, (month_key,))
if result > 0:
logger.info(f"删除Confluence页面映射: {month_key}")
return True
else:
logger.warning(f"未找到Confluence页面映射: {month_key}")
return False
except Exception as e:
logger.error(f"删除Confluence页面映射失败: {e}")
return False
def get_confluence_page_for_date(self, date: str) -> Optional[str]:
"""
根据日期获取对应的Confluence页面ID
参数:
date: 日期字符串,格式:'2025-12-31'
返回:
Confluence页面ID如果不存在则返回None
"""
try:
# 从日期中提取年月
year_month = date[:7] # '2025-12-31' -> '2025-12'
# 查询数据库
page_info = self.get_confluence_page(year_month)
if page_info:
return page_info['page_id']
# 如果没有找到,尝试从环境变量中获取
import os
from src.config import config
# 检查环境变量中的配置
env_key = f"CONFLUENCE_PAGE_{year_month.replace('-', '_')}"
page_id = os.getenv(env_key)
if page_id:
# 保存到数据库以便下次使用
self.insert_confluence_page(year_month, page_id, f"从环境变量获取: {env_key}")
return page_id
# 使用默认配置
default_page_id = config.CONFLUENCE_CONTENT_ID
if default_page_id:
logger.warning(f"未找到 {year_month} 的Confluence页面映射使用默认页面ID: {default_page_id}")
return default_page_id
return None
except Exception as e:
logger.error(f"获取Confluence页面ID失败: {date}, 错误: {e}")
return None
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -2,9 +2,11 @@
""" """
飞书表格 API 客户端模块 飞书表格 API 客户端模块
统一版本,支持月度表格和年度表格 统一版本,支持月度表格和年度表格
支持自动获取和刷新 tenant_access_token
""" """
import requests import requests
from typing import Dict, List, Optional import time
from typing import Dict, List, Optional, Tuple
import logging import logging
from src.config import config from src.config import config
@@ -21,32 +23,147 @@ class FeishuClientError(Exception):
class FeishuSheetsClient: class FeishuSheetsClient:
"""飞书表格 API 客户端""" """飞书表格 API 客户端"""
def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None, def __init__(self, base_url: Optional[str] = None, token: Optional[str] = None,
spreadsheet_token: Optional[str] = None): spreadsheet_token: Optional[str] = None, app_id: Optional[str] = None,
app_secret: Optional[str] = None):
""" """
初始化客户端 初始化客户端
参数: 参数:
base_url: 飞书 API 基础URL如果为None则使用配置 base_url: 飞书 API 基础URL如果为None则使用配置
token: Bearer 认证令牌如果为None则使用配置 token: Bearer 认证令牌如果为None则使用配置或自动获取
spreadsheet_token: 表格 token如果为None则使用配置 spreadsheet_token: 表格 token如果为None则使用配置
app_id: 飞书应用ID用于获取tenant_access_token
app_secret: 飞书应用密钥用于获取tenant_access_token
""" """
self.base_url = (base_url or config.FEISHU_BASE_URL).rstrip('/') self.base_url = (base_url or config.FEISHU_BASE_URL).rstrip('/')
self.spreadsheet_token = spreadsheet_token or config.FEISHU_SPREADSHEET_TOKEN self.spreadsheet_token = spreadsheet_token or config.FEISHU_SPREADSHEET_TOKEN
self.token = token or config.FEISHU_TOKEN self.app_id = app_id or config.FEISHU_APP_ID
self.app_secret = app_secret or config.FEISHU_APP_SECRET
self.headers = { # Token管理相关属性
'Authorization': f'Bearer {self.token}', self._token = token or config.FEISHU_TOKEN
'Content-Type': 'application/json', self._token_expire_time = 0 # token过期时间戳
'Accept': 'application/json' self._token_obtained_time = 0 # token获取时间戳
}
# 使用 Session 重用连接 # 使用 Session 重用连接
self.session = requests.Session() self.session = requests.Session()
self.session.headers.update(self.headers)
self.session.timeout = config.REQUEST_TIMEOUT self.session.timeout = config.REQUEST_TIMEOUT
# 初始化headers
self._update_session_headers()
logger.debug(f"飞书客户端初始化完成基础URL: {self.base_url}") logger.debug(f"飞书客户端初始化完成基础URL: {self.base_url}")
logger.debug(f"使用应用ID: {self.app_id[:8]}... 如果配置" if self.app_id else "未配置应用ID")
def _update_session_headers(self):
"""更新session的headers"""
if self._token:
self.session.headers.update({
'Authorization': f'Bearer {self._token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
})
else:
# 如果没有token移除Authorization头
if 'Authorization' in self.session.headers:
del self.session.headers['Authorization']
def _get_tenant_access_token(self) -> Tuple[str, int]:
"""
获取tenant_access_token
返回:
(token, expire_time): token字符串和过期时间
异常:
requests.exceptions.RequestException: 网络请求失败
ValueError: API返回错误
"""
if not self.app_id or not self.app_secret:
raise ValueError("未配置飞书应用ID和密钥无法获取tenant_access_token")
token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
payload = {
"app_id": self.app_id,
"app_secret": self.app_secret
}
headers = {
"Content-Type": "application/json; charset=utf-8"
}
try:
logger.info(f"正在获取tenant_access_token应用ID: {self.app_id[:8]}...")
response = requests.post(token_url, json=payload, headers=headers, timeout=config.REQUEST_TIMEOUT)
response.raise_for_status()
data = response.json()
if data.get('code') != 0:
error_msg = f"获取tenant_access_token失败: {data.get('msg')}"
logger.error(error_msg)
raise ValueError(error_msg)
token = data.get('tenant_access_token')
expire = data.get('expire', 7200) # 默认2小时
if not token:
raise ValueError("API返回的token为空")
logger.info(f"成功获取tenant_access_token有效期: {expire}")
return token, expire
except requests.exceptions.RequestException as e:
logger.error(f"获取tenant_access_token网络请求失败: {e}")
raise
except Exception as e:
logger.error(f"获取tenant_access_token失败: {e}")
raise
def _ensure_valid_token(self):
"""
确保当前token有效如果无效则重新获取
返回:
bool: token是否有效
"""
current_time = time.time()
# 如果有手动配置的token直接使用
if config.FEISHU_TOKEN and self._token == config.FEISHU_TOKEN:
logger.debug("使用手动配置的FEISHU_TOKEN")
return True
# 检查token是否过期提前30分钟刷新
if self._token and self._token_expire_time > 0:
time_remaining = self._token_expire_time - current_time
if time_remaining > 1800: # 剩余时间大于30分钟
logger.debug(f"token仍然有效剩余时间: {int(time_remaining)}")
return True
elif time_remaining > 0: # 剩余时间小于30分钟但大于0
logger.info(f"token即将过期剩余时间: {int(time_remaining)}秒,重新获取")
else: # 已过期
logger.info("token已过期重新获取")
# 需要获取新token
try:
token, expire = self._get_tenant_access_token()
self._token = token
self._token_obtained_time = current_time
self._token_expire_time = current_time + expire
self._update_session_headers()
logger.info(f"token获取成功将在 {expire} 秒后过期")
return True
except Exception as e:
logger.error(f"获取token失败: {e}")
# 如果配置了备用token尝试使用
if config.FEISHU_TOKEN and config.FEISHU_TOKEN != self._token:
logger.warning("使用备用FEISHU_TOKEN")
self._token = config.FEISHU_TOKEN
self._update_session_headers()
return True
return False
def get_sheets_info(self) -> List[Dict[str, str]]: def get_sheets_info(self) -> List[Dict[str, str]]:
""" """
@@ -59,6 +176,10 @@ class FeishuSheetsClient:
requests.exceptions.RequestException: 网络请求失败 requests.exceptions.RequestException: 网络请求失败
ValueError: API返回错误 ValueError: API返回错误
""" """
# 确保token有效
if not self._ensure_valid_token():
raise FeishuClientError("无法获取有效的飞书token")
url = f'{self.base_url}/spreadsheets/{self.spreadsheet_token}/sheets/query' url = f'{self.base_url}/spreadsheets/{self.spreadsheet_token}/sheets/query'
try: try:
@@ -104,6 +225,10 @@ class FeishuSheetsClient:
requests.exceptions.RequestException: 网络请求失败 requests.exceptions.RequestException: 网络请求失败
ValueError: API返回错误 ValueError: API返回错误
""" """
# 确保token有效
if not self._ensure_valid_token():
raise FeishuClientError("无法获取有效的飞书token")
if range_ is None: if range_ is None:
range_ = config.SHEET_RANGE range_ = config.SHEET_RANGE
@@ -134,6 +259,26 @@ class FeishuSheetsClient:
logger.error(f"解析表格数据失败: {e}, sheet_id: {sheet_id}") logger.error(f"解析表格数据失败: {e}, sheet_id: {sheet_id}")
raise raise
def get_token_info(self) -> Dict[str, any]:
"""
获取当前token信息
返回:
token信息字典
"""
current_time = time.time()
time_remaining = self._token_expire_time - current_time if self._token_expire_time > 0 else 0
return {
'has_token': bool(self._token),
'token_preview': self._token[:20] + '...' if self._token and len(self._token) > 20 else self._token,
'token_obtained_time': self._token_obtained_time,
'token_expire_time': self._token_expire_time,
'time_remaining': max(0, time_remaining),
'using_app_credentials': bool(self.app_id and self.app_secret),
'using_manual_token': bool(config.FEISHU_TOKEN and self._token == config.FEISHU_TOKEN)
}
def test_connection(self) -> bool: def test_connection(self) -> bool:
""" """
测试飞书连接是否正常 测试飞书连接是否正常
@@ -142,6 +287,12 @@ class FeishuSheetsClient:
连接是否正常 连接是否正常
""" """
try: try:
# 首先测试token获取
if not self._ensure_valid_token():
logger.error("无法获取有效的飞书token")
return False
# 然后测试表格访问
sheets = self.get_sheets_info() sheets = self.get_sheets_info()
if sheets: if sheets:
logger.info(f"飞书连接测试成功,找到 {len(sheets)} 个表格") logger.info(f"飞书连接测试成功,找到 {len(sheets)} 个表格")
@@ -152,6 +303,27 @@ class FeishuSheetsClient:
except Exception as e: except Exception as e:
logger.error(f"飞书连接测试失败: {e}") logger.error(f"飞书连接测试失败: {e}")
return False return False
def refresh_token(self) -> bool:
"""
强制刷新token
返回:
刷新是否成功
"""
try:
logger.info("强制刷新token...")
current_time = time.time()
token, expire = self._get_tenant_access_token()
self._token = token
self._token_obtained_time = current_time
self._token_expire_time = current_time + expire
self._update_session_headers()
logger.info(f"token刷新成功将在 {expire} 秒后过期")
return True
except Exception as e:
logger.error(f"强制刷新token失败: {e}")
return False
if __name__ == '__main__': if __name__ == '__main__':
@@ -164,8 +336,17 @@ if __name__ == '__main__':
# 测试连接 # 测试连接
client = FeishuSheetsClient() client = FeishuSheetsClient()
# 显示token信息
token_info = client.get_token_info()
print("当前token信息:")
print(f" 是否有token: {token_info['has_token']}")
print(f" token预览: {token_info['token_preview']}")
print(f" 剩余时间: {int(token_info['time_remaining'])}")
print(f" 使用应用凭证: {token_info['using_app_credentials']}")
print(f" 使用手动token: {token_info['using_manual_token']}")
if client.test_connection(): if client.test_connection():
print("飞书连接测试成功") print("\n飞书连接测试成功")
# 获取表格信息 # 获取表格信息
sheets = client.get_sheets_info() sheets = client.get_sheets_info()
@@ -177,6 +358,10 @@ if __name__ == '__main__':
sheet_id = sheets[0]['sheet_id'] sheet_id = sheets[0]['sheet_id']
data = client.get_sheet_data(sheet_id, 'A1:C5') data = client.get_sheet_data(sheet_id, 'A1:C5')
print(f"获取到表格数据,版本: {data.get('revision', '未知')}") print(f"获取到表格数据,版本: {data.get('revision', '未知')}")
# 再次显示token信息
token_info = client.get_token_info()
print(f"\n测试后token剩余时间: {int(token_info['time_remaining'])}")
else: else:
print("飞书连接测试失败") print("\n飞书连接测试失败")
sys.exit(1) sys.exit(1)

View File

@@ -42,8 +42,17 @@ class FeishuScheduleManager:
def _check_config(self, token: Optional[str], spreadsheet_token: Optional[str]) -> None: def _check_config(self, token: Optional[str], spreadsheet_token: Optional[str]) -> None:
"""检查必要配置""" """检查必要配置"""
if not token and not config.FEISHU_TOKEN: # 检查是否有任何可用的认证方式
logger.warning("飞书令牌未配置,排班功能将不可用") has_token = bool(token or config.FEISHU_TOKEN)
has_app_credentials = bool(config.FEISHU_APP_ID and config.FEISHU_APP_SECRET)
if not has_token and not has_app_credentials:
logger.warning("飞书认证未配置,排班功能将不可用")
logger.warning("请配置 FEISHU_TOKEN 或 FEISHU_APP_ID + FEISHU_APP_SECRET")
elif has_app_credentials:
logger.info("使用飞书应用凭证自动获取token")
elif has_token:
logger.info("使用手动配置的FEISHU_TOKEN")
if not spreadsheet_token and not config.FEISHU_SPREADSHEET_TOKEN: if not spreadsheet_token and not config.FEISHU_SPREADSHEET_TOKEN:
logger.warning("飞书表格令牌未配置,排班功能将不可用") logger.warning("飞书表格令牌未配置,排班功能将不可用")
@@ -105,6 +114,8 @@ class FeishuScheduleManager:
""" """
获取指定日期的排班信息 获取指定日期的排班信息
修复:每次都从飞书获取最新数据并覆盖数据库,确保日报中显示最新排班信息
参数: 参数:
date_str: 日期字符串,格式 "2025-12-30" date_str: 日期字符串,格式 "2025-12-30"
@@ -126,22 +137,13 @@ class FeishuScheduleManager:
logger.info(f"获取 {date_str} 的排班信息 (格式: {target_date_mm_dd}/{target_date_chinese})") logger.info(f"获取 {date_str} 的排班信息 (格式: {target_date_mm_dd}/{target_date_chinese})")
# 1. 首先尝试从数据库获取 # 1. 获取表格信息
cached_schedule = self.db.get_schedule(date_str)
if cached_schedule:
logger.info(f"从数据库获取 {date_str} 的排班信息")
return self._format_db_result(cached_schedule)
# 2. 数据库中没有,需要从飞书获取
logger.info(f"数据库中没有 {date_str} 的排班信息,从飞书获取")
# 获取表格信息
sheets = self.client.get_sheets_info() sheets = self.client.get_sheets_info()
if not sheets: if not sheets:
logger.error("未获取到表格信息") logger.error("未获取到表格信息")
return self._empty_result() return self._empty_result()
# 选择最合适的表格 # 2. 选择最合适的表格
selected_sheet = self._select_sheet_for_date(sheets, target_year_month) selected_sheet = self._select_sheet_for_date(sheets, target_year_month)
if not selected_sheet: if not selected_sheet:
logger.error("未找到合适的表格") logger.error("未找到合适的表格")
@@ -157,23 +159,12 @@ class FeishuScheduleManager:
return self._empty_result() return self._empty_result()
values = sheet_data.get('valueRange', {}).get('values', []) values = sheet_data.get('valueRange', {}).get('values', [])
revision = sheet_data.get('revision', 0)
if not values: if not values:
logger.error("表格数据为空") logger.error("表格数据为空")
return self._empty_result() return self._empty_result()
# 4. 检查表格是否有更新 # 4. 解析数据 - 根据表格类型选择合适的日期格式
need_update = self.db.check_sheet_update(
sheet_id, sheet_title, revision, {'values': values}
)
if not need_update and cached_schedule:
# 表格无更新,且数据库中有缓存,直接返回
logger.info(f"表格无更新,使用数据库缓存")
return self._format_db_result(cached_schedule)
# 5. 解析数据 - 根据表格类型选择合适的日期格式
# 如果是年度表格使用中文日期格式否则使用mm/dd格式 # 如果是年度表格使用中文日期格式否则使用mm/dd格式
if '' in sheet_title and '排班表' in sheet_title: if '' in sheet_title and '排班表' in sheet_title:
target_date = target_date_chinese # "1月1日" target_date = target_date_chinese # "1月1日"
@@ -183,10 +174,12 @@ class FeishuScheduleManager:
logger.info(f"使用日期格式: {target_date} 解析表格: {sheet_title}") logger.info(f"使用日期格式: {target_date} 解析表格: {sheet_title}")
result = self.parser.parse(values, target_date, sheet_title) result = self.parser.parse(values, target_date, sheet_title)
# 6. 保存到数据库 # 5. 每次都保存到数据库,覆盖旧数据,确保人员变动能及时更新
if result['day_shift'] or result['night_shift']: if result['day_shift'] or result['night_shift']:
self.db.save_schedule(date_str, result, sheet_id, sheet_title) self.db.save_schedule(date_str, result, sheet_id, sheet_title)
logger.info(f"保存 {date_str} 的排班信息到数据库") logger.info(f"更新 {date_str} 的排班信息到数据库: 白班={result['day_shift']}, 夜班={result['night_shift']}")
else:
logger.warning(f"解析结果为空,{date_str} 未保存到数据库")
return result return result

1280
src/gui.py

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,13 @@
""" """
统一日志配置模块 统一日志配置模块
提供统一的日志配置,避免各模块自行配置 提供统一的日志配置,避免各模块自行配置
支持按日期分片存储日志
""" """
import os import os
import logging import logging
import sys import sys
from logging.handlers import RotatingFileHandler from datetime import datetime
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
from typing import Optional from typing import Optional
from src.config import config from src.config import config
@@ -16,6 +18,8 @@ def setup_logging(
log_file: Optional[str] = None, log_file: Optional[str] = None,
console_level: int = logging.INFO, console_level: int = logging.INFO,
file_level: int = logging.DEBUG, file_level: int = logging.DEBUG,
use_date_split: bool = True,
date_folder_format: str = "%Y-%m", # 按月份分文件夹
max_bytes: int = 10 * 1024 * 1024, # 10MB max_bytes: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5 backup_count: int = 5
) -> logging.Logger: ) -> logging.Logger:
@@ -26,21 +30,31 @@ def setup_logging(
log_file: 日志文件路径如果为None则使用默认路径 log_file: 日志文件路径如果为None则使用默认路径
console_level: 控制台日志级别 console_level: 控制台日志级别
file_level: 文件日志级别 file_level: 文件日志级别
use_date_split: 是否使用日期分片
date_folder_format: 日期文件夹格式(默认按月份,如 logs/2025-12/
max_bytes: 单个日志文件最大大小 max_bytes: 单个日志文件最大大小
backup_count: 备份文件数量 backup_count: 备份文件数量
返回: 返回:
配置好的根日志器 配置好的根日志器
""" """
# 创建日志目录 # 获取当前日期用于构建路径
now = datetime.now()
if log_file is None: if log_file is None:
log_dir = 'logs' log_dir = 'logs'
log_file = os.path.join(log_dir, 'app.log') if use_date_split:
# 按日期分片logs/2025-12/2025-12-30.log
date_folder = now.strftime(date_folder_format)
log_dir = os.path.join('logs', date_folder)
log_file = os.path.join(log_dir, now.strftime('%Y-%m-%d.log'))
else:
log_file = os.path.join(log_dir, 'app.log')
else: else:
log_dir = os.path.dirname(log_file) log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir): if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir, exist_ok=True)
# 获取根日志器 # 获取根日志器
logger = logging.getLogger() logger = logging.getLogger()
@@ -59,13 +73,28 @@ def setup_logging(
console_handler.setFormatter(console_formatter) console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler) logger.addHandler(console_handler)
# 文件handler轮转 # 文件handler日期分片或大小轮转)
file_handler = RotatingFileHandler( if use_date_split:
log_file, # 使用TimedRotatingFileHandler每天午夜轮转
maxBytes=max_bytes, file_handler = TimedRotatingFileHandler(
backupCount=backup_count, log_file,
encoding='utf-8' when='midnight',
) interval=1,
backupCount=backup_count,
encoding='utf-8',
atTime=datetime.strptime('00:00:00', '%H:%M:%S')
)
logger.info(f"日志系统已初始化,使用日期分片: {log_file}")
else:
# 使用RotatingFileHandler按大小轮转
file_handler = RotatingFileHandler(
log_file,
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
logger.info(f"日志系统已初始化,使用大小轮转: {log_file}")
file_handler.setLevel(file_level) file_handler.setLevel(file_level)
file_formatter = logging.Formatter( file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s', '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s',
@@ -78,7 +107,6 @@ def setup_logging(
logging.getLogger('urllib3').setLevel(logging.WARNING) logging.getLogger('urllib3').setLevel(logging.WARNING)
logging.getLogger('requests').setLevel(logging.WARNING) logging.getLogger('requests').setLevel(logging.WARNING)
logger.info(f"日志系统已初始化,日志文件: {log_file}")
logger.info(f"控制台日志级别: {logging.getLevelName(console_level)}") logger.info(f"控制台日志级别: {logging.getLevelName(console_level)}")
logger.info(f"文件日志级别: {logging.getLevelName(file_level)}") logger.info(f"文件日志级别: {logging.getLevelName(file_level)}")

View File

@@ -52,7 +52,7 @@ class DailyReportGenerator:
def get_daily_data(self, date: str) -> Dict[str, Any]: def get_daily_data(self, date: str) -> Dict[str, Any]:
""" """
获取指定日期的数据 获取指定日期的数据(包含手动调整)
参数: 参数:
date: 日期字符串,格式 "YYYY-MM-DD" date: 日期字符串,格式 "YYYY-MM-DD"
@@ -61,22 +61,39 @@ class DailyReportGenerator:
每日数据字典 每日数据字典
""" """
try: try:
# 使用数据库的新方法获取包含调整的数据
if hasattr(self.db, 'get_daily_data_with_adjustments'):
return self.db.get_daily_data_with_adjustments(date)
# 降级处理:如果没有新方法,使用原始逻辑
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),
'adjustments': [],
'total_adjustment_teu': 0
} }
except Exception as e: except Exception as e:
@@ -85,7 +102,9 @@ class DailyReportGenerator:
'date': date, 'date': date,
'ships': {}, 'ships': {},
'total_teu': 0, 'total_teu': 0,
'ship_count': 0 'ship_count': 0,
'adjustments': [],
'total_adjustment_teu': 0
} }
def get_monthly_stats(self, date: str) -> Dict[str, Any]: def get_monthly_stats(self, date: str) -> Dict[str, Any]:
@@ -106,12 +125,12 @@ class DailyReportGenerator:
# 只统计当月且在指定日期之前的数据 # 只统计当月且在指定日期之前的数据
monthly_logs = [ monthly_logs = [
log for log in logs log for log in logs
if log['date'].startswith(year_month) if log['date'].startswith(year_month)
and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date and datetime.strptime(log['date'], '%Y-%m-%d').date() <= target_date
] ]
# 按日期汇总 # 按日期汇总原始数据
daily_totals: Dict[str, int] = {} daily_totals: Dict[str, int] = {}
for log in monthly_logs: for log in monthly_logs:
d = log['date'] d = log['date']
@@ -120,6 +139,25 @@ class DailyReportGenerator:
if log.get('teu'): if log.get('teu'):
daily_totals[d] += log['teu'] daily_totals[d] += log['teu']
# 获取当月所有日期的调整数据
total_adjustment_teu = 0
adjustment_details: Dict[str, Dict[str, int]] = {}
# 获取当月所有日期的调整数据
for day in range(1, target_date.day + 1):
day_str = f"{year_month}-{day:02d}"
if day_str <= date: # 只统计到指定日期
# 获取该日期的调整数据
if hasattr(self.db, 'get_daily_data_with_adjustments'):
daily_data = self.db.get_daily_data_with_adjustments(day_str)
adjustment_teu = daily_data.get('total_adjustment_teu', 0)
if adjustment_teu != 0:
total_adjustment_teu += adjustment_teu
adjustment_details[day_str] = {
'adjustment_teu': adjustment_teu,
'total_teu': daily_data.get('total_teu', 0)
}
# 计算当月天数(已过的天数) # 计算当月天数(已过的天数)
current_date = datetime.strptime(date, '%Y-%m-%d') current_date = datetime.strptime(date, '%Y-%m-%d')
if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL: if current_date.day == config.FIRST_DAY_OF_MONTH_SPECIAL:
@@ -131,7 +169,8 @@ class DailyReportGenerator:
unaccounted = self.db.get_unaccounted(year_month) unaccounted = self.db.get_unaccounted(year_month)
planned = days_passed * config.DAILY_TARGET_TEU planned = days_passed * config.DAILY_TARGET_TEU
actual = sum(daily_totals.values()) + unaccounted # 实际作业量 = 原始数据总计 + 未统计数据 + 调整数据总计
actual = sum(daily_totals.values()) + unaccounted + total_adjustment_teu
completion = round(actual / planned * 100, 2) if planned > 0 else 0 completion = round(actual / planned * 100, 2) if planned > 0 else 0
@@ -141,8 +180,10 @@ class DailyReportGenerator:
'planned': planned, 'planned': planned,
'actual': actual, 'actual': actual,
'unaccounted': unaccounted, 'unaccounted': unaccounted,
'adjustment_total': total_adjustment_teu,
'completion': completion, 'completion': completion,
'daily_totals': daily_totals 'daily_totals': daily_totals,
'adjustment_details': adjustment_details
} }
except Exception as e: except Exception as e:
@@ -153,8 +194,10 @@ class DailyReportGenerator:
'planned': 0, 'planned': 0,
'actual': 0, 'actual': 0,
'unaccounted': 0, 'unaccounted': 0,
'adjustment_total': 0,
'completion': 0, 'completion': 0,
'daily_totals': {} 'daily_totals': {},
'adjustment_details': {}
} }
def get_shift_personnel(self, date: str) -> Dict[str, str]: def get_shift_personnel(self, date: str) -> Dict[str, str]:
@@ -171,9 +214,15 @@ class DailyReportGenerator:
班次人员字典 班次人员字典
""" """
try: try:
# 检查飞书配置 # 检查飞书配置支持应用凭证和手动token两种方式
if not config.FEISHU_TOKEN or not config.FEISHU_SPREADSHEET_TOKEN: has_feishu_config = bool(config.FEISHU_SPREADSHEET_TOKEN) and (
bool(config.FEISHU_APP_ID and config.FEISHU_APP_SECRET) or
bool(config.FEISHU_TOKEN)
)
if not has_feishu_config:
logger.warning("飞书配置不完整,跳过排班信息获取") logger.warning("飞书配置不完整,跳过排班信息获取")
logger.warning("需要配置 FEISHU_SPREADSHEET_TOKEN 和 (FEISHU_APP_ID+FEISHU_APP_SECRET 或 FEISHU_TOKEN)")
return self._empty_personnel() return self._empty_personnel()
# 初始化飞书排班管理器 # 初始化飞书排班管理器
@@ -245,9 +294,25 @@ 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("")